Repository: repobee/repobee

Issue:
API meta class should only check that __init__ args are a subset
As GitLab does not need the `user` argument, the API meta class should only check that the arguments to `__init__` in an implementing class are a subset of the ones in the APISpec. This makes the whole thing more flexible for plugins.

Discussion:

Patch:
diff --git a/repobee/apimeta.py b/repobee/apimeta.py
index e88584a..274f896 100644
--- a/repobee/apimeta.py
+++ b/repobee/apimeta.py
@@ -21,6 +21,7 @@ NotImplementedError) for any unimplemented API methods.
 import inspect
 import enum
 import collections
+import itertools
 from typing import List, Iterable, Optional, Generator, Tuple, Mapping
 
 import daiquiri
@@ -406,7 +407,7 @@ def methods(attrdict):
         name: method
         for name, method in attrdict.items()
         if callable(method)
-        and not (name.startswith("_") or name == "__init__")
+        and (not name.startswith("_") or name == "__init__")
     }
 
 
@@ -418,13 +419,31 @@ def parameters(function):
     ]
 
 
+def check_init_params(reference_params, compare_params):
+    """Check that the compare __init__'s parameters are a subset of the
+    reference class's version.
+    """
+    extra = set(compare_params) - set(reference_params)
+    if extra:
+        raise exception.APIImplementationError(
+            "unexpected arguments to __init__: {}".format(extra)
+        )
+
+
 def check_parameters(reference, compare):
     """Check if the parameters match, one by one. Stop at the first diff and
     raise an exception for that parameter.
+
+    An exception is made for __init__, for which the compare may be a subset of
+    the reference in no particular order.
     """
     reference_params = parameters(reference)
     compare_params = parameters(compare)
-    for ref, cmp in zip(reference_params, compare_params):
+    if reference.__name__ == "__init__":
+        check_init_params(reference_params, compare_params)
+        return
+
+    for ref, cmp in itertools.zip_longest(reference_params, compare_params):
         if ref != cmp:
             raise exception.APIImplementationError(
                 "{}: expected parameter '{}', found '{}'".format(


Test patch:
diff --git a/tests/unit_tests/test_apimeta.py b/tests/unit_tests/test_apimeta.py
index 097d811..62b55c2 100644
--- a/tests/unit_tests/test_apimeta.py
+++ b/tests/unit_tests/test_apimeta.py
@@ -45,6 +45,31 @@ class TestAPI:
                 def get_teams(a):
                     pass
 
+    def test_accepts_init_with_strict_subset_of_args(self):
+        """Test that ``__init__`` can be defined with a strict subset of the
+        args in APISpec.__init__.
+        """
+
+        class API(apimeta.API):
+            def __init__(self, base_url):
+                pass
+
+        api = API("some-url")
+        assert isinstance(api, apimeta.API)
+
+    def test_raises_when_init_has_superset_of_args(self):
+        """Test that ``__init__`` cannot be defined with a superset of the args
+        in APISpec.__init__.
+        """
+
+        with pytest.raises(exception.APIImplementationError) as exc_info:
+
+            class API(apimeta.API):
+                def __init__(self, base_url, token, org_name, user, other):
+                    pass
+
+        assert "other" in str(exc_info.value)
+
     def test_accepts_correctly_defined_method(self):
         """API should accept a correctly defined method, and not alter it in any
         way.