Skip to content

Commit

Permalink
Fixes import of classes from implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Matyasek committed May 15, 2019
1 parent 109c3a3 commit 03d9332
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 39 deletions.
54 changes: 15 additions & 39 deletions ftoolz/unsafe/reflection.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Set, Type, TypeVar
from typing import List, Set, Type, TypeVar, cast

T = TypeVar('T')

Expand Down Expand Up @@ -46,47 +46,23 @@ def implementations(clz: Type[T], package: str) -> List[Type[T]]:
**Warning**: Class discovery uses `import_all` so any side-effect of any
import under the package will be executed as a side-effect of this function
>>> from abc import ABC, abstractmethod
>>> class A(ABC):
... @abstractmethod
... def test(self) -> None:
... pass
... @abstractmethod
... def test2(self) -> None:
... pass
>>> class B(A, ABC):
... def test(self) -> None:
... pass
>>> class C(B):
... def test2(self) -> None:
... pass
>>> class D(C):
... def test2(self) -> None:
... pass
>>> class E(A):
... def test(self) -> None:
... pass
... def test2(self) -> None:
... pass
>>> class F(E):
... __protected__ = True
>>> [c.__name__ for c in implementations(A, __package__)]
['C', 'D', 'E']
>>> [c.__name__ for c in implementations(B, __package__)]
['C', 'D']
>>> [c.__name__ for c in implementations(E, __package__)]
[]
"""
instances = (
c for c in subclasses(clz, package)
import importlib

def load(c: Type[T]) -> Type[T]:
module_name = f'{package}.{c.__module__}' \
if not str(c.__module__).startswith(package) \
else str(c.__module__)
module = importlib.import_module(module_name)
return cast(Type[T], getattr(module, c.__name__))

classes = (
load(c)
for c in subclasses(clz, package)
if not abstract(c) and not protected(c)
)
return sorted(instances, key=lambda c: c.__name__)

return sorted(classes, key=lambda c: c.__name__)


def import_all(package: str) -> None:
Expand Down
Empty file added tests/test_unsafe/__init__.py
Empty file.
38 changes: 38 additions & 0 deletions tests/test_unsafe/classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from abc import ABC, abstractmethod


class A(ABC):
@abstractmethod
def test(self) -> None:
pass

@abstractmethod
def test2(self) -> None:
pass


class B(A, ABC): # pylint: disable=W0223
def test(self) -> None:
pass


class C(B):
def test2(self) -> None:
pass


class D(C):
def test2(self) -> None:
pass


class E(A):
def test(self) -> None:
pass

def test2(self) -> None:
pass


class F(E):
__protected__ = True
22 changes: 22 additions & 0 deletions tests/test_unsafe/test_reflection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import List, Tuple, Type
from unittest import TestCase

from ftoolz.unsafe.reflection import implementations


class ReflectionTest(TestCase):

def test_implementations(self) -> None:
from tests.test_unsafe.classes import A, B, E

cases: List[Tuple[Type[A], List[str]]] = [
(A, ['C', 'D', 'E']),
(B, ['C', 'D']),
(E, []),
]

for clz, expected in cases:
with self.subTest(clz=clz):
impls = implementations(clz, __package__)
actual = [c.__name__ for c in impls]
self.assertListEqual(expected, actual)

0 comments on commit 03d9332

Please sign in to comment.