Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
5 changes: 5 additions & 0 deletions tests/application/discovery/sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .sample_helpers import SampleHelper


class Sample:
helper = SampleHelper
2 changes: 2 additions & 0 deletions tests/application/discovery/sample_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class SampleHelper:
pass
18 changes: 18 additions & 0 deletions tests/application/test_module_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,21 @@ def test_all_subclasses_with_exception(reference_type):
reference_type.side_effect = TypeError
exception_subclasses = list(get_all_subclasses(Exception))
assert not exception_subclasses


def test_get_all_classes_of_module():
from tests.application.discovery.sample import Sample

found_classes = list(get_all_classes('tests.application.discovery.sample'))

# SampleHelper is imported into the module but defined in a module whose name only starts the same
assert found_classes == [('Sample', Sample)]


def test_get_all_classes_of_package():
from tests.application.discovery.sample import Sample
from tests.application.discovery.sample_helpers import SampleHelper

found_classes = list(get_all_classes('tests.application.discovery'))

assert sorted(found_classes) == [('Sample', Sample), ('SampleHelper', SampleHelper)]
8 changes: 8 additions & 0 deletions tests/web/test_autodiscovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,11 @@ def test_find_package_routes():

# Assert
assert [route.url_path for route in routes] == ['api_1/', 'api_2/', 'api_2/', 'api_3/']


def test_find_module_routes():
# Act
routes = winter.web.find_package_routes('tests.api.package.api_2')

# Assert
assert [route.url_path for route in routes] == ['api_2/', 'api_2/']
7 changes: 6 additions & 1 deletion winter/application/module_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@


def get_all_classes(package: str) -> Generator[Tuple[str, Type], None, None]:
"""Classes defined in the modules of a package, or in a single module."""
classes_set = set()
for module in import_recursively(package):
try:
for class_name, class_ in inspect.getmembers(module, inspect.isclass):
if class_ in classes_set:
continue
if not class_.__module__.startswith(package):
if class_.__module__ != package and not class_.__module__.startswith(package + '.'):
continue
yield class_name, class_
classes_set.add(class_)
Expand All @@ -41,6 +42,10 @@ def get_all_subclasses(supertype: Type) -> Generator[Tuple[str, Type], None, Non


def import_recursively(package: str) -> Generator[ModuleType, None, None]:
"""Imports and yields the modules of a package and its subpackages, or a single module itself."""
package = importlib.import_module(package)
if not hasattr(package, '__path__'):
yield package
return
for loader, name, is_pkg in pkgutil.walk_packages(package.__path__, package.__name__ + '.'):
yield importlib.import_module(name)
1 change: 1 addition & 0 deletions winter/web/autodiscovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@


def find_package_routes(package_name: str) -> List[Route]:
"""Routes of the API classes in a package and its subpackages, or in a single module."""
import_recursively(package_name)
routes = []

Expand Down
Loading