diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b0ab87..c8cd4e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,11 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.2 + with: + python-version: "3.10" # Specify the desired Python version + - uses: pre-commit/action@v3.0.1 + with: + extra_args: --all-files tests: name: "Python ${{ matrix.python-version }}" @@ -20,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "pypy-3.7", "3.11", "3.12", "3.13"] steps: - uses: "actions/checkout@v2" - uses: "actions/setup-python@v2" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f0b7d32..789fd4f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,11 +2,11 @@ default_language_version: python: python3 repos: - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 25.1.0 hooks: - id: black - repo: https://github.com/myint/autoflake - rev: v1.4 + rev: v2.3.1 hooks: - id: autoflake args: @@ -15,7 +15,7 @@ repos: - --remove-all-unused-imports - --remove-duplicate-keys - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v5.0.0 hooks: - id: check-toml - id: check-yaml @@ -25,18 +25,19 @@ repos: rev: "3.9.2" hooks: - id: flake8 - additional_dependencies: ["flake8-bugbear==21.4.3"] + name: flake8 + additional_dependencies: ["flake8-bugbear==21.9.2"] - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.8.0 + rev: v5.10.1 hooks: - id: isort - - repo: https://github.com/myint/docformatter - rev: v1.4 + - repo: https://github.com/PyCQA/docformatter + rev: eb1df347edd128b30cd3368dddc3aa65edcfac38 hooks: - id: docformatter args: ["--in-place", "--wrap-summaries=88"] - repo: https://github.com/asottile/pyupgrade - rev: v2.29.1 + rev: v3.19.1 hooks: - id: pyupgrade args: ["--py3-plus", "--py36-plus"] diff --git a/news/18.bugfix.rst b/news/18.bugfix.rst new file mode 100644 index 0000000..c23ef4f --- /dev/null +++ b/news/18.bugfix.rst @@ -0,0 +1 @@ +When checking if an instance of extendable class is a type of itself, first check if both classes have the same __xreg_name__ attribute. diff --git a/src/extendable/main.py b/src/extendable/main.py index 2360bbc..e684ae0 100644 --- a/src/extendable/main.py +++ b/src/extendable/main.py @@ -87,9 +87,9 @@ def clone(self) -> "ExtendableClassDef": return clone -_extendable_class_defs_by_module: OrderedDict[ - str, List[ExtendableClassDef] -] = collections.OrderedDict() +_extendable_class_defs_by_module: OrderedDict[str, List[ExtendableClassDef]] = ( + collections.OrderedDict() +) def __register_class_def__(module: str, cls_def: ExtendableClassDef) -> None: @@ -108,7 +108,7 @@ class ExtendableMeta(ABCMeta): @no_type_check def __new__(metacls, name, bases, namespace, extends=None, **kwargs): - """create the expected class and 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 isinstance(extends, bool) and extends: @@ -251,6 +251,18 @@ def __call__(cls, *args, **kwargs) -> "ExtendableMeta": ############################################################### # concrete methods provided to the final class by the metaclass ############################################################### + def __instancecheck__(self, instance: Any) -> bool: # noqa: B902 + """Implement issubclass(sub, cls).""" + if not hasattr(instance, "__xreg_name__"): + return False + + if instance.__xreg_name__ == self.__xreg_name__: + # this is the same class + return True + # self is a class and instance is an instance of a class + if self.__xreg_name__ in instance.__xreg_all_base_names__: + return True + return super().__instancecheck__(instance) def __subclasscheck__(cls, subclass: Any) -> bool: # noqa: B902 """Implement issubclass(sub, cls).""" diff --git a/src/extendable/registry.py b/src/extendable/registry.py index 5092048..01da29c 100644 --- a/src/extendable/registry.py +++ b/src/extendable/registry.py @@ -8,8 +8,9 @@ class ExtendableRegistryListener: - def on_registry_initialized(self, registry: "ExtendableClassesRegistry") -> None: - ... + def on_registry_initialized( + self, registry: "ExtendableClassesRegistry" + ) -> None: ... def before_init_registry( self, diff --git a/tests/test_registry_init.py b/tests/test_registry_init.py index 1bbe051..775f869 100644 --- a/tests/test_registry_init.py +++ b/tests/test_registry_init.py @@ -1,4 +1,5 @@ """Test registry loading.""" + from extendable.registry import ExtendableClassesRegistry, ExtendableRegistryListener diff --git a/tests/test_simple.py b/tests/test_simple.py index 6ae6e3a..3f3cd02 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -331,3 +331,23 @@ class C(B): assert isinstance(C(), A().__class__) assert isinstance(B(), A) assert isinstance(B(), A().__class__) + + +def test_isinstance_no_extend(test_registry): + # in this test our class are not extended + # we chech that the isinstance works as expected + # when we have no extends by the classes + # are extendable by default + class A(metaclass=ExtendableMeta): + pass + + class B(A): + pass + + test_registry.init_registry() + + assert isinstance(A(), A) + assert isinstance(B(), A) + assert isinstance(B(), B) + assert isinstance(B(), A().__class__) + assert isinstance(B(), B().__class__) diff --git a/tox.ini b/tox.ini index b75fea2..2f3815a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,11 @@ [gh-actions] python = - 3.7: py37, typing 3.8: py38, typing 3.9: py39, typing, pypi-description 3.10: py310, typing 3.11: py311, typing 3.12: py312, typing + 3.13: py313, typing pypy-3.7: pypy37 [tox] @@ -17,6 +17,7 @@ envlist = py310 py311 py312 + py313 pypy37 lint typing