From 64f2f0329dd1fd67c0d69c739859b27db07f3039 Mon Sep 17 00:00:00 2001 From: Nikita Zavadin Date: Tue, 1 Oct 2024 17:05:34 +0200 Subject: [PATCH] Fix Python3.10 compatibility (#18) --- CHANGELOG.md | 3 +++ README.md | 2 +- src/magic_di/_utils.py | 9 ++++++--- src/magic_di/healthcheck.py | 2 +- src/magic_di/testing.py | 2 +- tests/test_injector.py | 15 +++++++++++++++ 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9233d43..fe219b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Fixed +- Fixed compatibility with Python3.10 for optional type hints + ## [0.2.0] - 2024-09-27 ### Added - Added `magic_di.healthcheck.DependenciesHealthcheck` class to make health checks of injected dependencies that implement `magic_di.healthcheck.PingableProtocol` interface diff --git a/README.md b/README.md index 96ee5c1..a52ca18 100644 --- a/README.md +++ b/README.md @@ -331,7 +331,7 @@ class Service(Connectable): dependency: Annotated[NonConnectableDependency, Injectable] ``` -## Healthchecks +## Healthcheck You can implement `Pingable` protocol to define healthchecks for your clients. The `DependenciesHealthcheck` will call the `__ping__` method on all injected clients that implement this protocol. ```python diff --git a/src/magic_di/_utils.py b/src/magic_di/_utils.py index f3589ce..fbfa449 100644 --- a/src/magic_di/_utils.py +++ b/src/magic_di/_utils.py @@ -1,11 +1,12 @@ from __future__ import annotations -from typing import Any, TypeVar, cast, get_args +from typing import Any, Optional, TypeVar, cast, get_args from typing import get_type_hints as _get_type_hints from magic_di import ConnectableProtocol LegacyUnionType = type(object | None) +LegacyOptionalType = type(Optional[object]) # noqa: UP007 try: from types import UnionType # type: ignore[import-error,unused-ignore] @@ -27,7 +28,7 @@ def get_cls_from_optional(cls: T) -> T: Extract the actual class from a union that includes None. If it is not a union type hint, it returns the same type hint. Example: - ```python + ``` py >>> get_cls_from_optional(Union[str, None]) str >>> get_cls_from_optional(str | None) @@ -36,13 +37,15 @@ def get_cls_from_optional(cls: T) -> T: str >>> get_cls_from_optional(int) int + >>> get_cls_from_optional(Optional[str]) + str ``` Args: cls (T): Type hint for class Returns: T: Extracted class """ - if not isinstance(cls, UnionType | LegacyUnionType): + if not isinstance(cls, UnionType | LegacyUnionType | LegacyOptionalType): return cls args = get_args(cls) diff --git a/src/magic_di/healthcheck.py b/src/magic_di/healthcheck.py index 7849eeb..2836f8f 100644 --- a/src/magic_di/healthcheck.py +++ b/src/magic_di/healthcheck.py @@ -19,7 +19,7 @@ class DependenciesHealthcheck(Connectable): Example usage: - ```python + ``` py from app.components.services.health import DependenciesHealthcheck async def main(redis: Redis, deps_healthcheck: DependenciesHealthcheck) -> None: diff --git a/src/magic_di/testing.py b/src/magic_di/testing.py index a22135d..37a2195 100644 --- a/src/magic_di/testing.py +++ b/src/magic_di/testing.py @@ -22,7 +22,7 @@ class InjectableMock(AsyncMock): and use AsyncMock instead of a real class instance Example: - ```python + ``` py @pytest.fixture() def client(): injector = DependencyInjector() diff --git a/tests/test_injector.py b/tests/test_injector.py index 2deef33..fd6ccc1 100644 --- a/tests/test_injector.py +++ b/tests/test_injector.py @@ -61,6 +61,21 @@ def run_service(service: Service) -> Service: assert isinstance(service, Service) +@pytest.mark.asyncio() +async def test_function_injection_success_legacy_optional(injector: DependencyInjector) -> None: + def run_service(service: Service | None) -> Service: + assert service is not None + return service + + injected = injector.inject(run_service) + + async with injector: + service = injected() + assert service.is_alive() + + assert isinstance(service, Service) + + def test_class_injection_missing_class(injector: DependencyInjector) -> None: with pytest.raises(InjectionError): injector.inject(BrokenService)