diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7facec2..6b56726 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "pypy3"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "pypy3", "3.11-dev"] steps: - uses: "actions/checkout@v2" - uses: "actions/setup-python@v2" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c4035f..f0b7d32 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: python3 repos: - repo: https://github.com/psf/black - rev: 21.12b0 + rev: 22.3.0 hooks: - id: black - repo: https://github.com/myint/autoflake diff --git a/src/extendable/main.py b/src/extendable/main.py index 0fe5d89..fd6d79c 100644 --- a/src/extendable/main.py +++ b/src/extendable/main.py @@ -81,7 +81,7 @@ class ExtendableMeta(ABCMeta): @no_type_check def __new__(metacls, name, bases, namespace, extends=None, **kwargs): - """create the expected class anc collect the class definition that will be used + """create the expected class and collect the class definition that will be used at the end of registry load process to build the final class.""" class_def = None if not _registry_build_mode: @@ -94,12 +94,16 @@ def __new__(metacls, name, bases, namespace, extends=None, **kwargs): # for the original class, we wrap the class methods to forward # the call to the aggregated one at runtime namespace = metacls._wrap_class_methods(namespace) - # We build the Origial class + # We build the original class new_cls = metacls._build_original_class( name=name, bases=bases, namespace=namespace, **kwargs ) if not _registry_build_mode and class_def: class_def.original_cls = new_cls + if False: + registry = extendable_registry.get() + with registry.build_mode(): + extendable_registry.get().build_extendable_class(class_def) return new_cls @no_type_check @@ -134,6 +138,8 @@ def _collect_class_def(metacls, name, bases, namespace, extends=None, **kwargs): # For each defined Extendable class, we keep a copy of the class # definition. This copy will be used to create the aggregated class other_bases = [b for b in bases if not metacls._is_extendable(b)] + # namespace = dict(namespace) + # namespace.pop("__classcell__", None) cls_def = ExtendableClassDef( original_name=name, bases=tuple(other_bases), @@ -156,9 +162,7 @@ def _build_original_class(metacls, name, bases, namespace, **kwargs): another type. In such a case, the new type should only override this method to call the __new__ method on the other type. """ - return super().__new__( - metacls, name=name, bases=bases, namespace=namespace, **kwargs - ) + return super().__new__(metacls, name, bases, namespace, **kwargs) @classmethod def _wrap_class_methods(metacls, namespace: Dict[str, Any]) -> Dict[str, Any]: diff --git a/src/extendable/registry.py b/src/extendable/registry.py index e2a6cc2..264246c 100644 --- a/src/extendable/registry.py +++ b/src/extendable/registry.py @@ -157,6 +157,7 @@ def init_registry(self, module_matchings: Optional[List[str]] = None) -> None: The module list accept wildcard expression as last character """ + self._extendable_classes.clear() module_matchings = module_matchings if module_matchings else ["*"] with self.build_mode(), ModuleIndex() as idx: for match in module_matchings: diff --git a/tests/test_simple.py b/tests/test_simple.py index 044a55c..837f3ba 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -42,6 +42,53 @@ def cls_sum(cls) -> int: assert isinstance(result.__class__, ExtendableMeta) +def test_simple_extends_same_class(test_registry): + class A(metaclass=ExtendableMeta): + prop_a: int = 1 + + def sum(self) -> int: + return self.prop_a + + @classmethod + def cls_sum(cls) -> int: + return 2 + + class B(A, extends=A): + prop_b: int = 2 + + def sum(self) -> int: + s = super() + return s.sum() + self.prop_b + + @classmethod + def cls_sum(cls) -> int: + return super().cls_sum() + 3 + + class C(A, extends=A): + prop_c: int = 3 + + def sum(self) -> int: + s = super() + return s.sum() + self.prop_c + + @classmethod + def cls_sum(cls) -> int: + return super().cls_sum() + 4 + + test_registry.init_registry() + + result: Union[A, B, C] = A() + assert isinstance(result, A) + assert isinstance(result, B) + assert isinstance(result, C) + assert result.prop_c == 3 + assert result.prop_b == 2 + assert result.prop_a == 1 + assert result.sum() == 6 + assert A.cls_sum() == 9 + assert isinstance(result.__class__, ExtendableMeta) + + def test_extends_new_model(test_registry): class A(metaclass=ExtendableMeta): value: int = 2 diff --git a/tox.ini b/tox.ini index 8edf8f7..7dda110 100644 --- a/tox.ini +++ b/tox.ini @@ -5,6 +5,7 @@ python = 3.8: py38, typing 3.9: py39, typing, pypi-description 3.10: py310, typing + 3.11: py311, typing pypy3: pypy3 [tox] @@ -15,6 +16,7 @@ envlist = py38 py39 py310 + py311 pypy3 lint typing