UseClsInClassmethodRule

Enforces using cls as the first argument in a @classmethod.

Message

When using @classmethod, the first argument must be cls.

Has Autofix: Yes

VALID Code Examples

# 1:

class foo:
    # classmethod with cls first arg.
    @classmethod
    def cm(cls, a, b, c):
        pass

# 2:

class foo:
    # non-classmethod with non-cls first arg.
    def nm(self, a, b, c):
        pass

# 3:

class foo:
    # staticmethod with non-cls first arg.
    @staticmethod
    def sm(a):
        pass

INVALID Code Examples

# 1:

class foo:
    # No args at all.
    @classmethod
    def cm():
        pass

Autofix:

---
+++
@@ -2,5 +2,5 @@
 class foo:
     # No args at all.
     @classmethod
-    def cm():
+    def cm(cls):
         pass

# 2:

class foo:
    # Single arg + reference.
    @classmethod
    def cm(a):
        return a

Autofix:

---
+++
@@ -2,5 +2,5 @@
 class foo:
     # Single arg + reference.
     @classmethod
-    def cm(a):
-        return a
+    def cm(cls):
+        return cls

# 3:

class foo:
    # Another "cls" exists: do not autofix.
    @classmethod
    def cm(a):
        cls = 2

# 4:

class foo:
    # Multiple args + references.
    @classmethod
    async def cm(a, b):
        b = a
        b = a.__name__

Autofix:

---
+++
@@ -2,6 +2,6 @@
 class foo:
     # Multiple args + references.
     @classmethod
-    async def cm(a, b):
-        b = a
-        b = a.__name__
+    async def cm(cls, b):
+        b = cls
+        b = cls.__name__

# 5:

class foo:
    # Do not replace in nested scopes.
    @classmethod
    async def cm(a, b):
        b = a
        b = lambda _: a.__name__
        def g():
            return a.__name__

        # Same-named vars in sub-scopes should not be replaced.
        b = [a for a in [1,2,3]]
        def f(a):
            return a + 1

Autofix:

---
+++
@@ -2,11 +2,11 @@
 class foo:
     # Do not replace in nested scopes.
     @classmethod
-    async def cm(a, b):
-        b = a
-        b = lambda _: a.__name__
+    async def cm(cls, b):
+        b = cls
+        b = lambda _: cls.__name__
         def g():
-            return a.__name__
+            return cls.__name__

         # Same-named vars in sub-scopes should not be replaced.
         b = [a for a in [1,2,3]]

# 6:

# Do not replace in surrounding scopes.
a = 1

class foo:
    a = 2

    def im(a):
        a = a

    @classmethod
    def cm(a):
        a[1] = foo.cm(a=a)

Autofix:

---
+++
@@ -9,5 +9,5 @@
         a = a

     @classmethod
-    def cm(a):
-        a[1] = foo.cm(a=a)
+    def cm(cls):
+        cls[1] = foo.cm(a=cls)

# 7:

def another_decorator(x): pass

class foo:
    # Multiple decorators.
    @another_decorator
    @classmethod
    @another_decorator
    async def cm(a, b, c):
        pass

Autofix:

---
+++
@@ -6,5 +6,5 @@
     @another_decorator
     @classmethod
     @another_decorator
-    async def cm(a, b, c):
+    async def cm(cls, b, c):
         pass