diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 90e7dbb..5b8b049 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,7 +14,6 @@ jobs: - uses: astral-sh/setup-uv@v5 - run: | (cd packages/modern-di && uv version $GITHUB_REF_NAME && just publish modern-di) - (cd packages/modern-di-litestar && uv version $GITHUB_REF_NAME && just publish modern-di-litestar) (cd packages/modern-di-faststream && uv version $GITHUB_REF_NAME && just publish modern-di-faststream) env: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/test-litestar.yml b/.github/workflows/test-litestar.yml deleted file mode 100644 index 523da25..0000000 --- a/.github/workflows/test-litestar.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: test litestar - -on: - push: - branches: - - main - paths: - - 'packages/modern-di-litestar/**' - - '.github/workflows/test-litestar.yml' - pull_request: - paths: - - 'packages/modern-di-litestar/**' - - '.github/workflows/test-litestar.yml' - -concurrency: - group: ${{ github.head_ref || github.run_id }} fastapi - cancel-in-progress: false - -jobs: - pytest: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: - - "3.10" - - "3.11" - - "3.12" - - "3.13" - - "3.14" - steps: - - uses: actions/checkout@v4 - - uses: extractions/setup-just@v2 - - uses: astral-sh/setup-uv@v5 - - run: uv python install ${{ matrix.python-version }} - - run: | - just install-ci modern-di-litestar - just test-litestar diff --git a/Justfile b/Justfile index c67da16..03b2f09 100644 --- a/Justfile +++ b/Justfile @@ -26,9 +26,6 @@ test *args: test-core *args: uv run --directory=packages/modern-di pytest {{ args }} -test-litestar *args: - uv run --directory=packages/modern-di-litestar pytest {{ args }} - test-faststream *args: uv run --directory=packages/modern-di-faststream pytest {{ args }} diff --git a/README.md b/README.md index de1fe0c..d5e87df 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ "Modern-DI" == -| Project | Badges | -|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| common | [![MyPy Strict](https://img.shields.io/badge/mypy-strict-blue)](https://mypy.readthedocs.io/en/stable/getting_started.html#strict-mode-and-configuration) [![GitHub stars](https://img.shields.io/github/stars/modern-python/modern-di)](https://github.com/modern-python/modern-di/stargazers) | -| modern-di | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di.svg)](https://pypi.python.org/pypi/modern-di ) [![downloads](https://img.shields.io/pypi/dm/modern-di.svg)](https://pypistats.org/packages/modern-di) | -| modern-di-fastapi | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-fastapi.svg)](https://pypi.python.org/pypi/modern-di-fastapi) [![downloads](https://img.shields.io/pypi/dm/modern-di-fastapi.svg)](https://pypistats.org/packages/modern-di-fastapi) | -| modern-di-faststream | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-faststream.svg)](https://pypi.python.org/pypi/modern-di-faststream) [![downloads](https://img.shields.io/pypi/dm/modern-di-faststream.svg)](https://pypistats.org/packages/modern-di-faststream) | -| modern-di-litestar | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-litestar.svg)](https://pypi.python.org/pypi/modern-di-litestar) [![downloads](https://img.shields.io/pypi/dm/modern-di-litestar.svg)](https://pypistats.org/packages/modern-di-litestar) | +| Project | Badges | +|----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| common | [![MyPy Strict](https://img.shields.io/badge/mypy-strict-blue)](https://mypy.readthedocs.io/en/stable/getting_started.html#strict-mode-and-configuration) [![GitHub stars](https://img.shields.io/github/stars/modern-python/modern-di)](https://github.com/modern-python/modern-di/stargazers) | +| modern-di | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di.svg)](https://pypi.python.org/pypi/modern-di ) [![downloads](https://img.shields.io/pypi/dm/modern-di.svg)](https://pypistats.org/packages/modern-di) | +| [modern-di-fastapi](https://github.com/modern-python/modern-di-fastapi) | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-fastapi.svg)](https://pypi.python.org/pypi/modern-di-fastapi) [![downloads](https://img.shields.io/pypi/dm/modern-di-fastapi.svg)](https://pypistats.org/packages/modern-di-fastapi) | +| modern-di-faststream | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-faststream.svg)](https://pypi.python.org/pypi/modern-di-faststream) [![downloads](https://img.shields.io/pypi/dm/modern-di-faststream.svg)](https://pypistats.org/packages/modern-di-faststream) | +| [modern-di-litestar ](https://github.com/modern-python/modern-di-litestar) | [![Supported versions](https://img.shields.io/pypi/pyversions/modern-di-litestar.svg)](https://pypi.python.org/pypi/modern-di-litestar) [![downloads](https://img.shields.io/pypi/dm/modern-di-litestar.svg)](https://pypistats.org/packages/modern-di-litestar) | `modern-di` is a python dependency injection framework which supports the following: diff --git a/packages/modern-di-litestar/README.md b/packages/modern-di-litestar/README.md deleted file mode 100644 index c06d2ed..0000000 --- a/packages/modern-di-litestar/README.md +++ /dev/null @@ -1,6 +0,0 @@ -"Modern-DI-LiteStar" -== - -Integration of [Modern-DI](https://github.com/modern-python/modern-di) to LiteStar - -📚 [Documentation](https://modern-di.readthedocs.io) diff --git a/packages/modern-di-litestar/modern_di_litestar/__init__.py b/packages/modern-di-litestar/modern_di_litestar/__init__.py deleted file mode 100644 index 808f919..0000000 --- a/packages/modern-di-litestar/modern_di_litestar/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -from modern_di_litestar.main import ( - FromDI, - ModernDIPlugin, - fetch_di_container, - litestar_request_provider, - litestar_websocket_provider, -) - - -__all__ = [ - "FromDI", - "ModernDIPlugin", - "fetch_di_container", - "litestar_request_provider", - "litestar_websocket_provider", -] diff --git a/packages/modern-di-litestar/modern_di_litestar/main.py b/packages/modern-di-litestar/modern_di_litestar/main.py deleted file mode 100644 index e590f03..0000000 --- a/packages/modern-di-litestar/modern_di_litestar/main.py +++ /dev/null @@ -1,80 +0,0 @@ -import contextlib -import dataclasses -import typing - -import litestar -from litestar.config.app import AppConfig -from litestar.di import Provide -from litestar.params import Dependency -from litestar.plugins import InitPlugin -from modern_di import Container, providers -from modern_di.scope import Scope -from modern_di.scope import Scope as DIScope - - -T_co = typing.TypeVar("T_co", covariant=True) - - -litestar_request_provider = providers.ContextProvider(scope=Scope.REQUEST, context_type=litestar.Request) -litestar_websocket_provider = providers.ContextProvider(scope=Scope.SESSION, context_type=litestar.WebSocket) - - -def fetch_di_container(app_: litestar.Litestar) -> Container: - return typing.cast(Container, app_.state.di_container) - - -@contextlib.asynccontextmanager -async def _lifespan_manager(app_: litestar.Litestar) -> typing.AsyncIterator[None]: - container = fetch_di_container(app_) - try: - yield - finally: - await container.close_async() - - -class ModernDIPlugin(InitPlugin): - __slots__ = ("container",) - - def __init__(self, container: Container) -> None: - self.container = container - - def on_app_init(self, app_config: AppConfig) -> AppConfig: - self.container.providers_registry.add_providers(litestar_request_provider, litestar_websocket_provider) - app_config.state.di_container = self.container - app_config.dependencies["di_container"] = Provide(build_di_container) - app_config.lifespan.append(_lifespan_manager) - return app_config - - -async def build_di_container( - request: litestar.Request[typing.Any, typing.Any, typing.Any], -) -> typing.AsyncIterator[Container]: - context: dict[type[typing.Any], typing.Any] = {} - scope: DIScope | None - if isinstance(request, litestar.WebSocket): - context[litestar.WebSocket] = request - scope = DIScope.SESSION - else: - context[litestar.Request] = request - scope = DIScope.REQUEST - container = fetch_di_container(request.app).build_child_container(context=context, scope=scope) - try: - yield container - finally: - await container.close_async() - - -@dataclasses.dataclass(slots=True, frozen=True) -class _Dependency(typing.Generic[T_co]): - dependency: providers.AbstractProvider[T_co] | type[T_co] - - async def __call__( - self, di_container: typing.Annotated[Container, Dependency(skip_validation=True)] - ) -> T_co | None: - if isinstance(self.dependency, providers.AbstractProvider): - return di_container.resolve_provider(self.dependency) - return di_container.resolve(dependency_type=self.dependency) - - -def FromDI(dependency: providers.AbstractProvider[T_co] | type[T_co]) -> Provide: # noqa: N802 - return Provide(dependency=_Dependency(dependency), use_cache=False) diff --git a/packages/modern-di-litestar/modern_di_litestar/py.typed b/packages/modern-di-litestar/modern_di_litestar/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/packages/modern-di-litestar/pyproject.toml b/packages/modern-di-litestar/pyproject.toml deleted file mode 100644 index e42f88d..0000000 --- a/packages/modern-di-litestar/pyproject.toml +++ /dev/null @@ -1,48 +0,0 @@ -[project] -name = "modern-di-litestar" -description = "Modern-DI integration for LiteStar" -authors = [{ name = "Artur Shiriev", email = "me@shiriev.ru" }] -requires-python = ">=3.10,<4" -license = "MIT" -readme = "README.md" -keywords = ["DI", "dependency injector", "ioc-container", "LiteStar", "python"] -classifiers = [ - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", - "Typing :: Typed", - "Topic :: Software Development :: Libraries", -] -dependencies = ["litestar", "modern-di>=2"] -version = "0" - -[project.urls] -repository = "https://github.com/modern-python/modern-di" -docs = "https://modern-di.readthedocs.io" - -[dependency-groups] -dev = [ - "pytest", - "pytest-cov", - "pytest-asyncio", - "ruff", - "mypy", - "typing-extensions", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build] -include = ["modern_di_litestar"] - -[tool.pytest.ini_options] -addopts = "--cov=. --cov-report term-missing" -asyncio_mode = "auto" -asyncio_default_fixture_loop_scope = "function" - -[tool.coverage.report] -exclude_also = ["if typing.TYPE_CHECKING:"] diff --git a/packages/modern-di-litestar/tests_litestar/__init__.py b/packages/modern-di-litestar/tests_litestar/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/packages/modern-di-litestar/tests_litestar/conftest.py b/packages/modern-di-litestar/tests_litestar/conftest.py deleted file mode 100644 index e5f7096..0000000 --- a/packages/modern-di-litestar/tests_litestar/conftest.py +++ /dev/null @@ -1,20 +0,0 @@ -import typing - -import litestar -import modern_di_litestar -import pytest -from litestar.testing import TestClient -from modern_di import Container - -from tests_litestar.dependencies import Dependencies - - -@pytest.fixture -async def app() -> litestar.Litestar: - return litestar.Litestar(debug=True, plugins=[modern_di_litestar.ModernDIPlugin(Container(groups=[Dependencies]))]) - - -@pytest.fixture -def client(app: litestar.Litestar) -> typing.Iterator[TestClient[litestar.Litestar]]: - with TestClient(app=app, raise_server_exceptions=True) as client: - yield client diff --git a/packages/modern-di-litestar/tests_litestar/dependencies.py b/packages/modern-di-litestar/tests_litestar/dependencies.py deleted file mode 100644 index e40352d..0000000 --- a/packages/modern-di-litestar/tests_litestar/dependencies.py +++ /dev/null @@ -1,36 +0,0 @@ -import dataclasses -import typing - -import litestar -from modern_di import Group, Scope, providers - - -@dataclasses.dataclass(kw_only=True, slots=True) -class SimpleCreator: - dep1: str - - -@dataclasses.dataclass(kw_only=True, slots=True) -class DependentCreator: - dep1: SimpleCreator - - -def fetch_method_from_request(request: litestar.Request[typing.Any, typing.Any, typing.Any] | None = None) -> str: - assert isinstance(request, litestar.Request) - return request.method - - -def fetch_url_from_websocket(websocket: litestar.WebSocket[typing.Any, typing.Any, typing.Any] | None = None) -> str: - assert isinstance(websocket, litestar.WebSocket) - return websocket.url.path - - -class Dependencies(Group): - app_factory = providers.Factory(creator=SimpleCreator, kwargs={"dep1": "original"}) - session_factory = providers.Factory(scope=Scope.SESSION, creator=DependentCreator, bound_type=None) - request_factory = providers.Factory( - scope=Scope.REQUEST, creator=DependentCreator, cache_settings=providers.CacheSettings(), bound_type=None - ) - action_factory = providers.Factory(scope=Scope.ACTION, creator=DependentCreator, bound_type=None) - request_method = providers.Factory(scope=Scope.REQUEST, creator=fetch_method_from_request, bound_type=None) - websocket_path = providers.Factory(scope=Scope.SESSION, creator=fetch_url_from_websocket, bound_type=None) diff --git a/packages/modern-di-litestar/tests_litestar/test_routes.py b/packages/modern-di-litestar/tests_litestar/test_routes.py deleted file mode 100644 index 3d0fcae..0000000 --- a/packages/modern-di-litestar/tests_litestar/test_routes.py +++ /dev/null @@ -1,56 +0,0 @@ -import litestar -from litestar import status_codes -from litestar.testing import TestClient -from modern_di import Container -from modern_di_litestar import FromDI - -from tests_litestar.dependencies import Dependencies, DependentCreator, SimpleCreator - - -def test_factories(client: TestClient[litestar.Litestar], app: litestar.Litestar) -> None: - @litestar.get( - "/", - dependencies={ - "app_factory_instance": FromDI(SimpleCreator), - "request_factory_instance": FromDI(Dependencies.request_factory), - }, - ) - async def read_root( - app_factory_instance: SimpleCreator, - request_factory_instance: DependentCreator, - ) -> None: - assert isinstance(app_factory_instance, SimpleCreator) - assert isinstance(request_factory_instance, DependentCreator) - assert request_factory_instance.dep1 is not app_factory_instance - - app.register(read_root) - - response = client.get("/") - assert response.status_code == status_codes.HTTP_200_OK, response.text - assert response.json() is None - - -def test_context_provider(client: TestClient[litestar.Litestar], app: litestar.Litestar) -> None: - @litestar.get("/", dependencies={"method": FromDI(Dependencies.request_method)}) - async def read_root(method: str) -> None: - assert method == "GET" - - app.register(read_root) - - response = client.get("/") - assert response.status_code == status_codes.HTTP_200_OK, response.text - assert response.json() is None - - -def test_factories_action_scope(client: TestClient[litestar.Litestar], app: litestar.Litestar) -> None: - @litestar.get("/") - async def read_root(di_container: Container) -> None: - action_container = di_container.build_child_container() - action_factory_instance = action_container.resolve_provider(Dependencies.action_factory) - assert isinstance(action_factory_instance, DependentCreator) - - app.register(read_root) - - response = client.get("/") - assert response.status_code == status_codes.HTTP_200_OK - assert response.json() is None diff --git a/packages/modern-di-litestar/tests_litestar/test_websockets.py b/packages/modern-di-litestar/tests_litestar/test_websockets.py deleted file mode 100644 index 21af833..0000000 --- a/packages/modern-di-litestar/tests_litestar/test_websockets.py +++ /dev/null @@ -1,56 +0,0 @@ -import litestar -from litestar.testing import TestClient -from modern_di import Container -from modern_di_litestar import FromDI - -from tests_litestar.dependencies import Dependencies, DependentCreator, SimpleCreator - - -async def test_factories(client: TestClient[litestar.Litestar], app: litestar.Litestar) -> None: - @litestar.websocket_listener( - "/ws", - dependencies={ - "app_factory_instance": FromDI(SimpleCreator), - "session_factory_instance": FromDI(Dependencies.session_factory), - }, - ) - async def websocket_handler( - data: str, - app_factory_instance: SimpleCreator, - session_factory_instance: DependentCreator, - ) -> None: - assert data == "test" - assert isinstance(app_factory_instance, SimpleCreator) - assert isinstance(session_factory_instance, DependentCreator) - assert session_factory_instance.dep1 is not app_factory_instance - - app.register(websocket_handler) - - with client.websocket_connect("/ws") as websocket: - websocket.send("test") - - -async def test_factories_request_scope(client: TestClient[litestar.Litestar], app: litestar.Litestar) -> None: - @litestar.websocket_listener("/ws") - async def websocket_handler(data: str, di_container: Container) -> None: - assert data == "test" - request_container = di_container.build_child_container() - request_factory_instance = request_container.resolve_provider(Dependencies.request_factory) - assert isinstance(request_factory_instance, DependentCreator) - - app.register(websocket_handler) - - with client.websocket_connect("/ws") as websocket: - websocket.send("test") - - -async def test_context_adapter(client: TestClient[litestar.Litestar], app: litestar.Litestar) -> None: - @litestar.websocket_listener("/ws", dependencies={"path": FromDI(Dependencies.websocket_path)}) - async def websocket_handler(data: str, path: str) -> None: - assert data == "test" - assert path == "/ws" - - app.register(websocket_handler) - - with client.websocket_connect("/ws") as websocket: - websocket.send("test")