mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2025-07-17 03:22:25 +03:00
Merge branch 'release/4.48.1'
This commit is contained in:
commit
2c0aede4aa
4
.github/workflows/publishing.yml
vendored
4
.github/workflows/publishing.yml
vendored
|
@ -70,10 +70,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: pypa/cibuildwheel@v2.23.3
|
uses: pypa/cibuildwheel@v3.0.0
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: cibw-wheels-x86-${{ matrix.os }}-${{ strategy.job-index }}
|
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
|
||||||
path: ./wheelhouse/*.whl
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
test-publish:
|
test-publish:
|
||||||
|
|
|
@ -7,6 +7,14 @@ that were made in every particular version.
|
||||||
From version 0.7.6 *Dependency Injector* framework strictly
|
From version 0.7.6 *Dependency Injector* framework strictly
|
||||||
follows `Semantic versioning`_
|
follows `Semantic versioning`_
|
||||||
|
|
||||||
|
4.48.1
|
||||||
|
------
|
||||||
|
|
||||||
|
* Improve performance of ``dependency_injector._cwiring.DependencyResolver``
|
||||||
|
* Add ``typing-extensions`` as a dependency for older Python versions (<3.11)
|
||||||
|
* Produce warning on ``@inject``s without ``Provide[...]`` marks
|
||||||
|
* Add support for `resource_type` in ``Lifespan``s
|
||||||
|
|
||||||
4.48.0
|
4.48.0
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,11 @@ classifiers = [
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||||
]
|
]
|
||||||
dynamic = ["version"]
|
dynamic = ["version"]
|
||||||
|
dependencies = [
|
||||||
|
# typing.Annotated since v3.9
|
||||||
|
# typing.Self since v3.11
|
||||||
|
"typing-extensions; python_version<'3.11'",
|
||||||
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
yaml = ["pyyaml"]
|
yaml = ["pyyaml"]
|
||||||
|
@ -108,6 +113,7 @@ markers = [
|
||||||
"pydantic: Tests with Pydantic as a dependency",
|
"pydantic: Tests with Pydantic as a dependency",
|
||||||
]
|
]
|
||||||
filterwarnings = [
|
filterwarnings = [
|
||||||
|
"ignore::dependency_injector.wiring.DIWiringWarning",
|
||||||
"ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
|
"ignore:Module \"dependency_injector.ext.aiohttp\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
|
||||||
"ignore:Module \"dependency_injector.ext.flask\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
|
"ignore:Module \"dependency_injector.ext.flask\" is deprecated since version 4\\.0\\.0:DeprecationWarning",
|
||||||
"ignore:Please use \\`.*?\\` from the \\`scipy.*?\\`(.*?)namespace is deprecated\\.:DeprecationWarning",
|
"ignore:Please use \\`.*?\\` from the \\`scipy.*?\\`(.*?)namespace is deprecated\\.:DeprecationWarning",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Top-level package."""
|
"""Top-level package."""
|
||||||
|
|
||||||
__version__ = "4.48.0"
|
__version__ = "4.48.1"
|
||||||
"""Version number.
|
"""Version number.
|
||||||
|
|
||||||
:type: str
|
:type: str
|
||||||
|
|
|
@ -5,24 +5,11 @@ from collections.abc import Awaitable
|
||||||
from inspect import CO_ITERABLE_COROUTINE
|
from inspect import CO_ITERABLE_COROUTINE
|
||||||
from types import CoroutineType, GeneratorType
|
from types import CoroutineType, GeneratorType
|
||||||
|
|
||||||
from .providers cimport Provider, Resource, NULL_AWAITABLE
|
from .providers cimport Provider, Resource
|
||||||
from .wiring import _Marker
|
from .wiring import _Marker
|
||||||
|
|
||||||
cimport cython
|
|
||||||
|
|
||||||
|
cdef inline bint _is_injectable(dict kwargs, object name):
|
||||||
@cython.internal
|
|
||||||
@cython.no_gc
|
|
||||||
cdef class KWPair:
|
|
||||||
cdef str name
|
|
||||||
cdef object value
|
|
||||||
|
|
||||||
def __cinit__(self, str name, object value, /):
|
|
||||||
self.name = name
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
|
|
||||||
cdef inline bint _is_injectable(dict kwargs, str name):
|
|
||||||
return name not in kwargs or isinstance(kwargs[name], _Marker)
|
return name not in kwargs or isinstance(kwargs[name], _Marker)
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,11 +25,8 @@ cdef class DependencyResolver:
|
||||||
self.injections = injections
|
self.injections = injections
|
||||||
self.closings = closings
|
self.closings = closings
|
||||||
|
|
||||||
async def _await_injection(self, kw_pair: KWPair, /) -> None:
|
async def _await_injection(self, name: str, value: object, /) -> None:
|
||||||
self.to_inject[kw_pair.name] = await kw_pair.value
|
self.to_inject[name] = await value
|
||||||
|
|
||||||
cdef object _await_injections(self, to_await: list):
|
|
||||||
return gather(*map(self._await_injection, to_await))
|
|
||||||
|
|
||||||
cdef void _handle_injections_sync(self):
|
cdef void _handle_injections_sync(self):
|
||||||
cdef Provider provider
|
cdef Provider provider
|
||||||
|
@ -60,7 +44,7 @@ cdef class DependencyResolver:
|
||||||
provide = provider()
|
provide = provider()
|
||||||
|
|
||||||
if provider.is_async_mode_enabled() or _isawaitable(provide):
|
if provider.is_async_mode_enabled() or _isawaitable(provide):
|
||||||
to_await.append(KWPair(name, provide))
|
to_await.append(self._await_injection(name, provide))
|
||||||
else:
|
else:
|
||||||
self.to_inject[name] = provide
|
self.to_inject[name] = provide
|
||||||
|
|
||||||
|
@ -93,13 +77,12 @@ cdef class DependencyResolver:
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
if to_await := self._handle_injections_async():
|
if to_await := self._handle_injections_async():
|
||||||
await self._await_injections(to_await)
|
await gather(*to_await)
|
||||||
return self.to_inject
|
return self.to_inject
|
||||||
|
|
||||||
def __aexit__(self, *_):
|
async def __aexit__(self, *_):
|
||||||
if to_await := self._handle_closings_async():
|
if to_await := self._handle_closings_async():
|
||||||
return gather(*to_await)
|
await gather(*to_await)
|
||||||
return NULL_AWAITABLE
|
|
||||||
|
|
||||||
|
|
||||||
cdef bint _isawaitable(object instance):
|
cdef bint _isawaitable(object instance):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import sys
|
import sys
|
||||||
from typing import Any
|
from typing import Any, Type
|
||||||
|
|
||||||
if sys.version_info >= (3, 11): # pragma: no cover
|
if sys.version_info >= (3, 11): # pragma: no cover
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
@ -7,6 +7,7 @@ else: # pragma: no cover
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from dependency_injector.containers import Container
|
from dependency_injector.containers import Container
|
||||||
|
from dependency_injector.providers import Resource
|
||||||
|
|
||||||
|
|
||||||
class Lifespan:
|
class Lifespan:
|
||||||
|
@ -29,24 +30,32 @@ class Lifespan:
|
||||||
app = Factory(Starlette, lifespan=lifespan)
|
app = Factory(Starlette, lifespan=lifespan)
|
||||||
|
|
||||||
:param container: container instance
|
:param container: container instance
|
||||||
|
:param resource_type: A :py:class:`~dependency_injector.resources.Resource`
|
||||||
|
subclass. Limits the resources to be initialized and shutdown.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
container: Container
|
container: Container
|
||||||
|
resource_type: Type[Resource[Any]]
|
||||||
|
|
||||||
def __init__(self, container: Container) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
container: Container,
|
||||||
|
resource_type: Type[Resource[Any]] = Resource,
|
||||||
|
) -> None:
|
||||||
self.container = container
|
self.container = container
|
||||||
|
self.resource_type = resource_type
|
||||||
|
|
||||||
def __call__(self, app: Any) -> Self:
|
def __call__(self, app: Any) -> Self:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def __aenter__(self) -> None:
|
async def __aenter__(self) -> None:
|
||||||
result = self.container.init_resources()
|
result = self.container.init_resources(self.resource_type)
|
||||||
|
|
||||||
if result is not None:
|
if result is not None:
|
||||||
await result
|
await result
|
||||||
|
|
||||||
async def __aexit__(self, *exc_info: Any) -> None:
|
async def __aexit__(self, *exc_info: Any) -> None:
|
||||||
result = self.container.shutdown_resources()
|
result = self.container.shutdown_resources(self.resource_type)
|
||||||
|
|
||||||
if result is not None:
|
if result is not None:
|
||||||
await result
|
await result
|
||||||
|
|
|
@ -6,6 +6,8 @@ import importlib.machinery
|
||||||
import inspect
|
import inspect
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import sys
|
import sys
|
||||||
|
from contextlib import suppress
|
||||||
|
from inspect import isbuiltin, isclass
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
|
@ -15,6 +17,7 @@ from typing import (
|
||||||
Dict,
|
Dict,
|
||||||
Iterable,
|
Iterable,
|
||||||
Iterator,
|
Iterator,
|
||||||
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
Protocol,
|
Protocol,
|
||||||
Set,
|
Set,
|
||||||
|
@ -24,6 +27,7 @@ from typing import (
|
||||||
Union,
|
Union,
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
@ -59,13 +63,11 @@ else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
MARKER_EXTRACTORS = []
|
MARKER_EXTRACTORS: List[Callable[[Any], Any]] = []
|
||||||
|
INSPECT_EXCLUSION_FILTERS: List[Callable[[Any], bool]] = [isbuiltin]
|
||||||
|
|
||||||
try:
|
with suppress(ImportError):
|
||||||
from fastapi.params import Depends as FastAPIDepends
|
from fastapi.params import Depends as FastAPIDepends
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
|
|
||||||
def extract_marker_from_fastapi(param: Any) -> Any:
|
def extract_marker_from_fastapi(param: Any) -> Any:
|
||||||
if isinstance(param, FastAPIDepends):
|
if isinstance(param, FastAPIDepends):
|
||||||
|
@ -74,11 +76,8 @@ else:
|
||||||
|
|
||||||
MARKER_EXTRACTORS.append(extract_marker_from_fastapi)
|
MARKER_EXTRACTORS.append(extract_marker_from_fastapi)
|
||||||
|
|
||||||
try:
|
with suppress(ImportError):
|
||||||
from fast_depends.dependencies import Depends as FastDepends
|
from fast_depends.dependencies import Depends as FastDepends
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
|
|
||||||
def extract_marker_from_fast_depends(param: Any) -> Any:
|
def extract_marker_from_fast_depends(param: Any) -> Any:
|
||||||
if isinstance(param, FastDepends):
|
if isinstance(param, FastDepends):
|
||||||
|
@ -88,16 +87,22 @@ else:
|
||||||
MARKER_EXTRACTORS.append(extract_marker_from_fast_depends)
|
MARKER_EXTRACTORS.append(extract_marker_from_fast_depends)
|
||||||
|
|
||||||
|
|
||||||
try:
|
with suppress(ImportError):
|
||||||
import starlette.requests
|
from starlette.requests import Request as StarletteRequest
|
||||||
except ImportError:
|
|
||||||
starlette = None
|
def is_starlette_request_cls(obj: Any) -> bool:
|
||||||
|
return isclass(obj) and _safe_is_subclass(obj, StarletteRequest)
|
||||||
|
|
||||||
|
INSPECT_EXCLUSION_FILTERS.append(is_starlette_request_cls)
|
||||||
|
|
||||||
|
|
||||||
try:
|
with suppress(ImportError):
|
||||||
import werkzeug.local
|
from werkzeug.local import LocalProxy as WerkzeugLocalProxy
|
||||||
except ImportError:
|
|
||||||
werkzeug = None
|
def is_werkzeug_local_proxy(obj: Any) -> bool:
|
||||||
|
return isinstance(obj, WerkzeugLocalProxy)
|
||||||
|
|
||||||
|
INSPECT_EXCLUSION_FILTERS.append(is_werkzeug_local_proxy)
|
||||||
|
|
||||||
from . import providers # noqa: E402
|
from . import providers # noqa: E402
|
||||||
|
|
||||||
|
@ -130,6 +135,10 @@ else:
|
||||||
Container = Any
|
Container = Any
|
||||||
|
|
||||||
|
|
||||||
|
class DIWiringWarning(RuntimeWarning):
|
||||||
|
"""Base class for all warnings raised by the wiring module."""
|
||||||
|
|
||||||
|
|
||||||
class PatchedRegistry:
|
class PatchedRegistry:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
@ -411,30 +420,11 @@ class ProvidersMap:
|
||||||
return providers_map
|
return providers_map
|
||||||
|
|
||||||
|
|
||||||
class InspectFilter:
|
def is_excluded_from_inspect(obj: Any) -> bool:
|
||||||
|
for is_excluded in INSPECT_EXCLUSION_FILTERS:
|
||||||
def is_excluded(self, instance: object) -> bool:
|
if is_excluded(obj):
|
||||||
if self._is_werkzeug_local_proxy(instance):
|
|
||||||
return True
|
return True
|
||||||
elif self._is_starlette_request_cls(instance):
|
return False
|
||||||
return True
|
|
||||||
elif self._is_builtin(instance):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _is_werkzeug_local_proxy(self, instance: object) -> bool:
|
|
||||||
return werkzeug and isinstance(instance, werkzeug.local.LocalProxy)
|
|
||||||
|
|
||||||
def _is_starlette_request_cls(self, instance: object) -> bool:
|
|
||||||
return (
|
|
||||||
starlette
|
|
||||||
and isinstance(instance, type)
|
|
||||||
and _safe_is_subclass(instance, starlette.requests.Request)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _is_builtin(self, instance: object) -> bool:
|
|
||||||
return inspect.isbuiltin(instance)
|
|
||||||
|
|
||||||
|
|
||||||
def wire( # noqa: C901
|
def wire( # noqa: C901
|
||||||
|
@ -455,7 +445,7 @@ def wire( # noqa: C901
|
||||||
|
|
||||||
for module in modules:
|
for module in modules:
|
||||||
for member_name, member in _get_members_and_annotated(module):
|
for member_name, member in _get_members_and_annotated(module):
|
||||||
if _inspect_filter.is_excluded(member):
|
if is_excluded_from_inspect(member):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if _is_marker(member):
|
if _is_marker(member):
|
||||||
|
@ -520,6 +510,11 @@ def unwire( # noqa: C901
|
||||||
def inject(fn: F) -> F:
|
def inject(fn: F) -> F:
|
||||||
"""Decorate callable with injecting decorator."""
|
"""Decorate callable with injecting decorator."""
|
||||||
reference_injections, reference_closing = _fetch_reference_injections(fn)
|
reference_injections, reference_closing = _fetch_reference_injections(fn)
|
||||||
|
|
||||||
|
if not reference_injections:
|
||||||
|
warn("@inject is not required here", DIWiringWarning, stacklevel=2)
|
||||||
|
return fn
|
||||||
|
|
||||||
patched = _get_patched(fn, reference_injections, reference_closing)
|
patched = _get_patched(fn, reference_injections, reference_closing)
|
||||||
return cast(F, patched)
|
return cast(F, patched)
|
||||||
|
|
||||||
|
@ -1054,7 +1049,6 @@ def is_loader_installed() -> bool:
|
||||||
|
|
||||||
|
|
||||||
_patched_registry = PatchedRegistry()
|
_patched_registry = PatchedRegistry()
|
||||||
_inspect_filter = InspectFilter()
|
|
||||||
_loader = AutoLoader()
|
_loader = AutoLoader()
|
||||||
|
|
||||||
# Optimizations
|
# Optimizations
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import AsyncIterator, Iterator
|
from typing import AsyncIterator, Iterator, TypeVar
|
||||||
from unittest.mock import ANY
|
from unittest.mock import ANY
|
||||||
|
|
||||||
from pytest import mark
|
from pytest import mark
|
||||||
|
@ -7,6 +7,12 @@ from dependency_injector.containers import DeclarativeContainer
|
||||||
from dependency_injector.ext.starlette import Lifespan
|
from dependency_injector.ext.starlette import Lifespan
|
||||||
from dependency_injector.providers import Resource
|
from dependency_injector.providers import Resource
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
class XResource(Resource[T]):
|
||||||
|
"""A test provider"""
|
||||||
|
|
||||||
|
|
||||||
class TestLifespan:
|
class TestLifespan:
|
||||||
@mark.parametrize("sync", [False, True])
|
@mark.parametrize("sync", [False, True])
|
||||||
|
@ -28,11 +34,15 @@ class TestLifespan:
|
||||||
yield
|
yield
|
||||||
shutdown = True
|
shutdown = True
|
||||||
|
|
||||||
|
def nope():
|
||||||
|
assert False, "should not be called"
|
||||||
|
|
||||||
class Container(DeclarativeContainer):
|
class Container(DeclarativeContainer):
|
||||||
x = Resource(sync_resource if sync else async_resource)
|
x = XResource(sync_resource if sync else async_resource)
|
||||||
|
y = Resource(nope)
|
||||||
|
|
||||||
container = Container()
|
container = Container()
|
||||||
lifespan = Lifespan(container)
|
lifespan = Lifespan(container, resource_type=XResource)
|
||||||
|
|
||||||
async with lifespan(ANY) as scope:
|
async with lifespan(ANY) as scope:
|
||||||
assert scope is None
|
assert scope is None
|
||||||
|
|
Loading…
Reference in New Issue
Block a user