Skip to content

Commit

Permalink
Fix Python3.10 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
RB387 committed Oct 1, 2024
1 parent a218c04 commit b635cb8
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions src/magic_di/_utils.py
Original file line number Diff line number Diff line change
@@ -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]
Expand All @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/magic_di/healthcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/magic_di/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
15 changes: 15 additions & 0 deletions tests/test_injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit b635cb8

Please sign in to comment.