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
6 changes: 5 additions & 1 deletion ellar/core/middleware/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ def __init__(self, cls: t.Type[T], **options: t.Any) -> None:
@t.no_type_check
def __call__(self, app: ASGIApp, injector: EllarInjector) -> T:
self.kwargs.update(app=app)
return injector.create_object(self.cls, additional_kwargs=self.kwargs)
try:
return injector.create_object(self.cls, additional_kwargs=self.kwargs)
except TypeError: # pragma: no cover
# TODO: Fix future typing for lower python version.
return self.cls(**self.kwargs)
23 changes: 15 additions & 8 deletions ellar/core/modules/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dataclasses
import typing as t

import click
from ellar.common import ControllerBase, ModuleRouter
from ellar.common.constants import MODULE_METADATA, MODULE_WATERMARK
from ellar.common.exceptions import ImproperConfiguration
Expand Down Expand Up @@ -34,26 +35,32 @@ class DynamicModule:
routers: t.Sequence[t.Union[BaseRoute, ModuleRouter]] = dataclasses.field(
default_factory=lambda: ()
)

commands: t.Sequence[t.Union[click.Command, click.Group, t.Any]] = (
dataclasses.field(default_factory=lambda: ())
)

_is_configured: bool = False

def __post_init__(self) -> None:
if not reflect.get_metadata(MODULE_WATERMARK, self.module):
raise ImproperConfiguration(f"{self.module.__name__} is not a valid Module")

# # Commands needs to be registered so that
# if self.commands:
# reflect.define_metadata(MODULE_METADATA.COMMANDS, self.commands, self.module)

def apply_configuration(self) -> None:
if self._is_configured:
return

kwargs = {
"controllers": list(self.controllers),
"routers": list(self.routers),
"providers": list(self.providers),
MODULE_METADATA.CONTROLLERS: list(self.controllers),
MODULE_METADATA.ROUTERS: list(self.routers),
MODULE_METADATA.PROVIDERS: list(self.providers),
MODULE_METADATA.COMMANDS: list(self.commands),
}
for key in [
MODULE_METADATA.CONTROLLERS,
MODULE_METADATA.ROUTERS,
MODULE_METADATA.PROVIDERS,
]:
for key in kwargs.keys():
value = kwargs[key]
if value:
reflect.delete_metadata(key, self.module)
Expand Down
4 changes: 2 additions & 2 deletions ellar/di/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
def fail_silently(func: t.Callable, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]:
try:
return func(*args, **kwargs)
except Exception: # pragma: no cover
pass
except Exception as ee: # pragma: no cover
print(ee)
return None
26 changes: 9 additions & 17 deletions ellar/reflect/_reflect.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,25 +131,17 @@ def _clone_meta_data(

@asynccontextmanager
async def async_context(self) -> t.AsyncGenerator[None, None]:
cached_meta_data = self._meta_data
try:
self._meta_data = self._clone_meta_data()
yield
finally:
self._meta_data.clear()
self._meta_data = cached_meta_data
cached_meta_data = self._clone_meta_data()
yield
reflect._meta_data.clear()
reflect._meta_data = WeakKeyDictionary(dict=cached_meta_data)

@contextmanager
def context(
self,
) -> t.Generator:
cached_meta_data = self._meta_data
try:
self._meta_data = self._clone_meta_data()
yield
finally:
self._meta_data.clear()
self._meta_data = cached_meta_data
def context(self) -> t.Generator:
cached_meta_data = self._clone_meta_data()
yield
reflect._meta_data.clear()
reflect._meta_data = WeakKeyDictionary(dict=cached_meta_data)


reflect = _Reflect()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ classifiers = [

dependencies = [
"injector == 0.21.0",
"starlette == 0.37.1",
"starlette == 0.37.2",
"pydantic >=2.5.1,<3.0.0",
"typing-extensions>=4.8.0",
"jinja2",
Expand Down
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import PurePath, PurePosixPath, PureWindowsPath
from uuid import uuid4

import click.testing
import pytest
from pydantic import create_model
from starlette.testclient import TestClient
Expand Down Expand Up @@ -33,3 +34,8 @@ def fixture_model_with_path(request):
@pytest.fixture
def random_type():
return type(f"Random{uuid4().hex[:6]}", (), {})


@pytest.fixture
def cli_runner():
return click.testing.CliRunner()
43 changes: 43 additions & 0 deletions tests/test_modules/test_module_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from abc import ABC
from unittest.mock import patch

import click
import pytest
from ellar.app import App
from ellar.common import (
Expand All @@ -23,6 +24,16 @@
from ..main import router


@click.command(name="command-one")
def command_one():
click.echo("Hello World command one")


@click.command(name="command-two")
def command_two():
click.echo("Hello World command two")


class IDynamic(ABC):
a: int
b: float
Expand Down Expand Up @@ -116,6 +127,17 @@ class LazyModuleImportWithSetup(ModuleBase):
pass


@Module(commands=[command_one])
class DynamicModuleRegisterCommand(ModuleBase, IModuleSetup):
@classmethod
def setup(cls, command_three_text: str) -> DynamicModule:
@click.command
def command_three():
click.echo(command_three_text)

return DynamicModule(cls, commands=[command_one, command_two, command_three])


def test_invalid_lazy_module_import():
with pytest.raises(ImproperConfiguration) as ex:
LazyModuleImport("tests.test_modules.test_module_config:IDynamic").get_module()
Expand Down Expand Up @@ -303,3 +325,24 @@ def test_can_not_apply_dynamic_module_twice():
with patch.object(reflect.__class__, "define_metadata") as mock_define_metadata:
dynamic_module.apply_configuration()
assert mock_define_metadata.called is False


def test_dynamic_command_register_command(cli_runner):
commands = reflect.get_metadata(
MODULE_METADATA.COMMANDS, DynamicModuleRegisterCommand
)
assert len(commands) == 1
res = cli_runner.invoke(commands[0], [])
assert res.stdout == "Hello World command one\n"

with reflect.context():
DynamicModuleRegisterCommand.setup("Command Three Here").apply_configuration()
commands = reflect.get_metadata(
MODULE_METADATA.COMMANDS, DynamicModuleRegisterCommand
)
assert len(commands) == 3

res = cli_runner.invoke(commands[2], [])
assert res.stdout == "Command Three Here\n"

assert len(reflect._meta_data) > 10
2 changes: 1 addition & 1 deletion tests/test_routing/test_route_endpoint_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def get_requests_case_2(
config: Inject[Config],
):
assert isinstance(config, Config) # True
assert host is None # Starlette TestClient client info is None
assert host == "testclient"
assert isinstance(session, dict) and len(session) == 0
return True

Expand Down