From fd4a944472c8de034517688fb6fb5223d8105114 Mon Sep 17 00:00:00 2001 From: remimd Date: Mon, 26 May 2025 10:23:04 +0200 Subject: [PATCH 1/2] wip --- injection/__init__.pyi | 26 +++++++--- injection/_core/common/asynchronous.py | 1 + injection/_core/common/lazy.py | 16 +----- injection/_core/common/threading.py | 7 +++ injection/_core/module.py | 71 ++++++++++++++++---------- injection/_core/scope.py | 12 ++--- injection/ext/fastapi.py | 8 ++- tests/core/test_module.py | 22 -------- uv.lock | 48 ++++++++--------- 9 files changed, 107 insertions(+), 104 deletions(-) create mode 100644 injection/_core/common/threading.py diff --git a/injection/__init__.pyi b/injection/__init__.pyi index 2cb8d20..849bcfd 100644 --- a/injection/__init__.pyi +++ b/injection/__init__.pyi @@ -217,8 +217,13 @@ class Module: /, threadsafe: bool = ..., ) -> Callable[..., Awaitable[T]]: ... - async def afind_instance[T](self, cls: _InputType[T]) -> T: ... - def find_instance[T](self, cls: _InputType[T]) -> T: + async def afind_instance[T]( + self, + cls: _InputType[T], + *, + threadsafe: bool = ..., + ) -> T: ... + def find_instance[T](self, cls: _InputType[T], *, threadsafe: bool = ...) -> T: """ Function used to retrieve an instance associated with the type passed in parameter or an exception will be raised. @@ -229,18 +234,24 @@ class Module: self, cls: _InputType[T], default: Default, + *, + threadsafe: bool = ..., ) -> T | Default: ... @overload async def aget_instance[T]( self, cls: _InputType[T], default: None = ..., + *, + threadsafe: bool = ..., ) -> T | None: ... @overload def get_instance[T, Default]( self, cls: _InputType[T], default: Default, + *, + threadsafe: bool = ..., ) -> T | Default: """ Function used to retrieve an instance associated with the type passed in @@ -252,6 +263,8 @@ class Module: self, cls: _InputType[T], default: None = ..., + *, + threadsafe: bool = ..., ) -> T | None: ... @overload def aget_lazy_instance[T, Default]( @@ -259,7 +272,7 @@ class Module: cls: _InputType[T], default: Default, *, - cache: bool = ..., + threadsafe: bool = ..., ) -> Awaitable[T | Default]: ... @overload def aget_lazy_instance[T]( @@ -267,7 +280,7 @@ class Module: cls: _InputType[T], default: None = ..., *, - cache: bool = ..., + threadsafe: bool = ..., ) -> Awaitable[T | None]: ... @overload def get_lazy_instance[T, Default]( @@ -275,13 +288,12 @@ class Module: cls: _InputType[T], default: Default, *, - cache: bool = ..., + threadsafe: bool = ..., ) -> _Invertible[T | Default]: """ Function used to retrieve an instance associated with the type passed in parameter or `None`. Return a `Invertible` object. To access the instance contained in an invertible object, simply use a wavy line (~). - With `cache=True`, the instance retrieved will always be the same. Example: instance = ~lazy_instance """ @@ -292,7 +304,7 @@ class Module: cls: _InputType[T], default: None = ..., *, - cache: bool = ..., + threadsafe: bool = ..., ) -> _Invertible[T | None]: ... def init_modules(self, *modules: Module) -> Self: """ diff --git a/injection/_core/common/asynchronous.py b/injection/_core/common/asynchronous.py index 7227d90..2155abd 100644 --- a/injection/_core/common/asynchronous.py +++ b/injection/_core/common/asynchronous.py @@ -52,6 +52,7 @@ def call(self, /, *args: P.args, **kwargs: P.kwargs) -> T: def create_semaphore(value: int) -> AsyncContextManager[Any]: return anyio.Semaphore(value) + except ImportError: # pragma: no cover import asyncio diff --git a/injection/_core/common/lazy.py b/injection/_core/common/lazy.py index 1b39d08..dc15564 100644 --- a/injection/_core/common/lazy.py +++ b/injection/_core/common/lazy.py @@ -1,7 +1,6 @@ -from collections.abc import AsyncIterator, Awaitable, Callable, Iterator +from collections.abc import Callable, Iterator from functools import partial -from injection._core.common.asynchronous import SimpleAwaitable from injection._core.common.invertible import Invertible, SimpleInvertible @@ -18,19 +17,6 @@ def cache() -> Iterator[T]: return SimpleInvertible(getter) -def alazy[T](factory: Callable[..., Awaitable[T]]) -> Awaitable[T]: - async def cache() -> AsyncIterator[T]: - nonlocal factory - value = await factory() - del factory - - while True: - yield value - - getter = partial(anext, cache()) - return SimpleAwaitable(getter) - - class Lazy[T](Invertible[T]): __slots__ = ("__invertible", "__is_set") diff --git a/injection/_core/common/threading.py b/injection/_core/common/threading.py new file mode 100644 index 0000000..076f545 --- /dev/null +++ b/injection/_core/common/threading.py @@ -0,0 +1,7 @@ +from contextlib import nullcontext +from threading import RLock +from typing import Any, ContextManager + + +def get_lock(threadsafe: bool) -> ContextManager[Any]: + return RLock() if threadsafe else nullcontext() diff --git a/injection/_core/module.py b/injection/_core/module.py index 89256b1..8564796 100644 --- a/injection/_core/module.py +++ b/injection/_core/module.py @@ -1,6 +1,5 @@ from __future__ import annotations -import threading from abc import ABC, abstractmethod from collections import OrderedDict, deque from collections.abc import ( @@ -14,7 +13,7 @@ Iterator, Mapping, ) -from contextlib import asynccontextmanager, contextmanager, nullcontext, suppress +from contextlib import asynccontextmanager, contextmanager, suppress from dataclasses import dataclass, field from enum import StrEnum from functools import partial, partialmethod, singledispatchmethod, update_wrapper @@ -51,7 +50,8 @@ from injection._core.common.event import Event, EventChannel, EventListener from injection._core.common.invertible import Invertible, SimpleInvertible from injection._core.common.key import new_short_key -from injection._core.common.lazy import Lazy, alazy, lazy +from injection._core.common.lazy import Lazy, lazy +from injection._core.common.threading import get_lock from injection._core.common.type import ( InputType, TypeInfo, @@ -617,19 +617,28 @@ def make_async_factory[T]( ) return factory.__inject_metadata__.acall - async def afind_instance[T](self, cls: InputType[T]) -> T: - injectable = self[cls] - return await injectable.aget_instance() + async def afind_instance[T]( + self, + cls: InputType[T], + *, + threadsafe: bool = False, + ) -> T: + with get_lock(threadsafe): + injectable = self[cls] + return await injectable.aget_instance() - def find_instance[T](self, cls: InputType[T]) -> T: - injectable = self[cls] - return injectable.get_instance() + def find_instance[T](self, cls: InputType[T], *, threadsafe: bool = False) -> T: + with get_lock(threadsafe): + injectable = self[cls] + return injectable.get_instance() @overload async def aget_instance[T, Default]( self, cls: InputType[T], default: Default, + *, + threadsafe: bool = ..., ) -> T | Default: ... @overload @@ -637,15 +646,19 @@ async def aget_instance[T]( self, cls: InputType[T], default: None = ..., + *, + threadsafe: bool = ..., ) -> T | None: ... async def aget_instance[T, Default]( self, cls: InputType[T], default: Default | None = None, + *, + threadsafe: bool = False, ) -> T | Default | None: try: - return await self.afind_instance(cls) + return await self.afind_instance(cls, threadsafe=threadsafe) except (KeyError, SkipInjectable): return default @@ -654,6 +667,8 @@ def get_instance[T, Default]( self, cls: InputType[T], default: Default, + *, + threadsafe: bool = ..., ) -> T | Default: ... @overload @@ -661,15 +676,19 @@ def get_instance[T]( self, cls: InputType[T], default: None = ..., + *, + threadsafe: bool = ..., ) -> T | None: ... def get_instance[T, Default]( self, cls: InputType[T], default: Default | None = None, + *, + threadsafe: bool = False, ) -> T | Default | None: try: - return self.find_instance(cls) + return self.find_instance(cls, threadsafe=threadsafe) except (KeyError, SkipInjectable): return default @@ -679,7 +698,7 @@ def aget_lazy_instance[T, Default]( cls: InputType[T], default: Default, *, - cache: bool = ..., + threadsafe: bool = ..., ) -> Awaitable[T | Default]: ... @overload @@ -688,7 +707,7 @@ def aget_lazy_instance[T]( cls: InputType[T], default: None = ..., *, - cache: bool = ..., + threadsafe: bool = ..., ) -> Awaitable[T | None]: ... def aget_lazy_instance[T, Default]( @@ -696,12 +715,12 @@ def aget_lazy_instance[T, Default]( cls: InputType[T], default: Default | None = None, *, - cache: bool = False, + threadsafe: bool = False, ) -> Awaitable[T | Default | None]: - if cache: - return alazy(lambda: self.aget_instance(cls, default)) - - function = self.make_injected_function(lambda instance=default: instance) + function = self.make_injected_function( + lambda instance=default: instance, + threadsafe=threadsafe, + ) metadata = function.__inject_metadata__.set_owner(cls) return SimpleAwaitable(metadata.acall) @@ -711,7 +730,7 @@ def get_lazy_instance[T, Default]( cls: InputType[T], default: Default, *, - cache: bool = ..., + threadsafe: bool = ..., ) -> Invertible[T | Default]: ... @overload @@ -720,7 +739,7 @@ def get_lazy_instance[T]( cls: InputType[T], default: None = ..., *, - cache: bool = ..., + threadsafe: bool = ..., ) -> Invertible[T | None]: ... def get_lazy_instance[T, Default]( @@ -728,12 +747,12 @@ def get_lazy_instance[T, Default]( cls: InputType[T], default: Default | None = None, *, - cache: bool = False, + threadsafe: bool = False, ) -> Invertible[T | Default | None]: - if cache: - return lazy(lambda: self.get_instance(cls, default)) - - function = self.make_injected_function(lambda instance=default: instance) + function = self.make_injected_function( + lambda instance=default: instance, + threadsafe=threadsafe, + ) metadata = function.__inject_metadata__.set_owner(cls) return SimpleInvertible(metadata.call) @@ -996,7 +1015,7 @@ class InjectMetadata[**P, T](Caller[P, T], EventListener): def __init__(self, wrapped: Callable[P, T], /, threadsafe: bool) -> None: self.__dependencies = Dependencies.empty() - self.__lock = threading.RLock() if threadsafe else nullcontext() + self.__lock = get_lock(threadsafe) self.__owner = None self.__tasks = deque() self.__wrapped = wrapped diff --git a/injection/_core/scope.py b/injection/_core/scope.py index 51224c8..966e119 100644 --- a/injection/_core/scope.py +++ b/injection/_core/scope.py @@ -1,17 +1,10 @@ from __future__ import annotations import itertools -import threading from abc import ABC, abstractmethod from collections import defaultdict from collections.abc import AsyncIterator, Iterator, Mapping, MutableMapping -from contextlib import ( - AsyncExitStack, - ExitStack, - asynccontextmanager, - contextmanager, - nullcontext, -) +from contextlib import AsyncExitStack, ExitStack, asynccontextmanager, contextmanager from contextvars import ContextVar from dataclasses import dataclass, field from enum import StrEnum @@ -30,6 +23,7 @@ ) from injection._core.common.key import new_short_key +from injection._core.common.threading import get_lock from injection._core.slots import SlotKey from injection.exceptions import ( InjectionError, @@ -202,7 +196,7 @@ def _bind_scope( kind: ScopeKind | ScopeKindStr, threadsafe: bool, ) -> Iterator[ScopeFacade]: - lock = threading.RLock() if threadsafe else nullcontext() + lock = get_lock(threadsafe) with lock: match ScopeKind(kind): diff --git a/injection/ext/fastapi.py b/injection/ext/fastapi.py index 3d2a1a8..c5d6cbb 100644 --- a/injection/ext/fastapi.py +++ b/injection/ext/fastapi.py @@ -16,9 +16,15 @@ def __call__[T]( cls: type[T] | TypeAliasType | GenericAlias, /, default: T = NotImplemented, + *, module: Module | None = None, + threadsafe: bool = False, ) -> Any: - ainstance = (module or mod()).aget_lazy_instance(cls, default) + ainstance = (module or mod()).aget_lazy_instance( + cls, + default, + threadsafe=threadsafe, + ) async def dependency() -> T: return await ainstance diff --git a/tests/core/test_module.py b/tests/core/test_module.py index ab26812..a2fc14f 100644 --- a/tests/core/test_module.py +++ b/tests/core/test_module.py @@ -130,17 +130,6 @@ class A: ... assert isinstance(instance2, A) assert instance1 is not instance2 - async def test_aget_lazy_instance_with_cache_return_lazy_instance(self, module): - @module.injectable - class A: ... - - lazy_instance = module.aget_lazy_instance(A, cache=True) - instance1 = await lazy_instance - instance2 = await lazy_instance - assert isinstance(instance1, A) - assert isinstance(instance2, A) - assert instance1 is instance2 - async def test_aget_lazy_instance_with_no_injectable_return_lazy_none(self, module): lazy_instance = module.aget_lazy_instance(SomeClass) assert await lazy_instance is None @@ -160,17 +149,6 @@ class A: ... assert isinstance(instance2, A) assert instance1 is not instance2 - def test_get_lazy_instance_with_cache_return_lazy_instance(self, module): - @module.injectable - class A: ... - - lazy_instance = module.get_lazy_instance(A, cache=True) - instance1 = ~lazy_instance - instance2 = ~lazy_instance - assert isinstance(instance1, A) - assert isinstance(instance2, A) - assert instance1 is instance2 - def test_get_lazy_instance_with_no_injectable_return_lazy_none(self, module): lazy_instance = module.get_lazy_instance(SomeClass) assert ~lazy_instance is None diff --git a/uv.lock b/uv.lock index 53388bd..0ec0869 100644 --- a/uv.lock +++ b/uv.lock @@ -132,31 +132,31 @@ wheels = [ [[package]] name = "cryptography" -version = "45.0.2" +version = "45.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/47/92a8914716f2405f33f1814b97353e3cfa223cd94a77104075d42de3099e/cryptography-45.0.2.tar.gz", hash = "sha256:d784d57b958ffd07e9e226d17272f9af0c41572557604ca7554214def32c26bf", size = 743865, upload-time = "2025-05-18T02:46:34.986Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/52/49e6c86278e1b5ec226e96b62322538ccc466306517bf9aad8854116a088/cryptography-45.0.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cc31c66411e14dd70e2f384a9204a859dc25b05e1f303df0f5326691061b839", size = 4201098, upload-time = "2025-05-18T02:45:15.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/3a/201272539ac5b66b4cb1af89021e423fc0bfacb73498950280c51695fb78/cryptography-45.0.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:463096533acd5097f8751115bc600b0b64620c4aafcac10c6d0041e6e68f88fe", size = 4429839, upload-time = "2025-05-18T02:45:17.614Z" }, - { url = "https://files.pythonhosted.org/packages/99/89/fa1a84832b8f8f3917875cb15324bba98def5a70175a889df7d21a45dc75/cryptography-45.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:cdafb86eb673c3211accffbffdb3cdffa3aaafacd14819e0898d23696d18e4d3", size = 4205154, upload-time = "2025-05-18T02:45:19.874Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c5/5225d5230d538ab461725711cf5220560a813d1eb68bafcfb00131b8f631/cryptography-45.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05c2385b1f5c89a17df19900cfb1345115a77168f5ed44bdf6fd3de1ce5cc65b", size = 3897145, upload-time = "2025-05-18T02:45:22.209Z" }, - { url = "https://files.pythonhosted.org/packages/fe/24/f19aae32526cc55ae17d473bc4588b1234af2979483d99cbfc57e55ffea6/cryptography-45.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e9e4bdcd70216b08801e267c0b563316b787f957a46e215249921f99288456f9", size = 4462192, upload-time = "2025-05-18T02:45:24.773Z" }, - { url = "https://files.pythonhosted.org/packages/19/18/4a69ac95b0b3f03355970baa6c3f9502bbfc54e7df81fdb179654a00f48e/cryptography-45.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b2de529027579e43b6dc1f805f467b102fb7d13c1e54c334f1403ee2b37d0059", size = 4208093, upload-time = "2025-05-18T02:45:27.028Z" }, - { url = "https://files.pythonhosted.org/packages/7c/54/2dea55ccc9558b8fa14f67156250b6ee231e31765601524e4757d0b5db6b/cryptography-45.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10d68763892a7b19c22508ab57799c4423c7c8cd61d7eee4c5a6a55a46511949", size = 4461819, upload-time = "2025-05-18T02:45:29.39Z" }, - { url = "https://files.pythonhosted.org/packages/37/f1/1b220fcd5ef4b1f0ff3e59e733b61597505e47f945606cc877adab2c1a17/cryptography-45.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2a90ce2f0f5b695e4785ac07c19a58244092f3c85d57db6d8eb1a2b26d2aad6", size = 4329202, upload-time = "2025-05-18T02:45:31.925Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e0/51d1dc4f96f819a56db70f0b4039b4185055bbb8616135884c3c3acc4c6d/cryptography-45.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:59c0c8f043dd376bbd9d4f636223836aed50431af4c5a467ed9bf61520294627", size = 4570412, upload-time = "2025-05-18T02:45:34.348Z" }, - { url = "https://files.pythonhosted.org/packages/31/a3/a3e4a298d3db4a04085728f5ae6c8cda157e49c5bb784886d463b9fbff70/cryptography-45.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e328357b6bbf79928363dbf13f4635b7aac0306afb7e5ad24d21d0c5761c3253", size = 4189148, upload-time = "2025-05-18T02:45:42.538Z" }, - { url = "https://files.pythonhosted.org/packages/53/90/100dfadd4663b389cb56972541ec1103490a19ebad0132af284114ba0868/cryptography-45.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49af56491473231159c98c2c26f1a8f3799a60e5cf0e872d00745b858ddac9d2", size = 4424113, upload-time = "2025-05-18T02:45:44.316Z" }, - { url = "https://files.pythonhosted.org/packages/0d/40/e2b9177dbed6f3fcbbf1942e1acea2fd15b17007204b79d675540dd053af/cryptography-45.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f169469d04a23282de9d0be349499cb6683b6ff1b68901210faacac9b0c24b7d", size = 4189696, upload-time = "2025-05-18T02:45:46.622Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/ec29c79f481e1767c2ff916424ba36f3cf7774de93bbd60428a3c52d1357/cryptography-45.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9cfd1399064b13043082c660ddd97a0358e41c8b0dc7b77c1243e013d305c344", size = 3881498, upload-time = "2025-05-18T02:45:48.884Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4a/72937090e5637a232b2f73801c9361cd08404a2d4e620ca4ec58c7ea4b70/cryptography-45.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f8084b7ca3ce1b8d38bdfe33c48116edf9a08b4d056ef4a96dceaa36d8d965", size = 4451678, upload-time = "2025-05-18T02:45:50.706Z" }, - { url = "https://files.pythonhosted.org/packages/d3/fa/1377fced81fd67a4a27514248261bb0d45c3c1e02169411fe231583088c8/cryptography-45.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2cb03a944a1a412724d15a7c051d50e63a868031f26b6a312f2016965b661942", size = 4192296, upload-time = "2025-05-18T02:45:52.422Z" }, - { url = "https://files.pythonhosted.org/packages/d1/cf/b6fe837c83a08b9df81e63299d75fc5b3c6d82cf24b3e1e0e331050e9e5c/cryptography-45.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a9727a21957d3327cf6b7eb5ffc9e4b663909a25fea158e3fcbc49d4cdd7881b", size = 4451749, upload-time = "2025-05-18T02:45:55.025Z" }, - { url = "https://files.pythonhosted.org/packages/af/d8/5a655675cc635c7190bfc8cffb84bcdc44fc62ce945ad1d844adaa884252/cryptography-45.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ddb8d01aa900b741d6b7cc585a97aff787175f160ab975e21f880e89d810781a", size = 4317601, upload-time = "2025-05-18T02:45:56.911Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d4/75d2375a20d80aa262a8adee77bf56950e9292929e394b9fae2481803f11/cryptography-45.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c0c000c1a09f069632d8a9eb3b610ac029fcc682f1d69b758e625d6ee713f4ed", size = 4560535, upload-time = "2025-05-18T02:45:59.33Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/13/1f/9fa001e74a1993a9cadd2333bb889e50c66327b8594ac538ab8a04f915b7/cryptography-45.0.3.tar.gz", hash = "sha256:ec21313dd335c51d7877baf2972569f40a4291b76a0ce51391523ae358d05899", size = 744738, upload-time = "2025-05-25T14:17:24.777Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/3d/ac361649a0bfffc105e2298b720d8b862330a767dab27c06adc2ddbef96a/cryptography-45.0.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d377dde61c5d67eb4311eace661c3efda46c62113ff56bf05e2d679e02aebb5b", size = 4205541, upload-time = "2025-05-25T14:16:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/70/3e/c02a043750494d5c445f769e9c9f67e550d65060e0bfce52d91c1362693d/cryptography-45.0.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae1e637f527750811588e4582988932c222f8251f7b7ea93739acb624e1487f", size = 4433275, upload-time = "2025-05-25T14:16:16.421Z" }, + { url = "https://files.pythonhosted.org/packages/40/7a/9af0bfd48784e80eef3eb6fd6fde96fe706b4fc156751ce1b2b965dada70/cryptography-45.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ca932e11218bcc9ef812aa497cdf669484870ecbcf2d99b765d6c27a86000942", size = 4209173, upload-time = "2025-05-25T14:16:18.163Z" }, + { url = "https://files.pythonhosted.org/packages/31/5f/d6f8753c8708912df52e67969e80ef70b8e8897306cd9eb8b98201f8c184/cryptography-45.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af3f92b1dc25621f5fad065288a44ac790c5798e986a34d393ab27d2b27fcff9", size = 3898150, upload-time = "2025-05-25T14:16:20.34Z" }, + { url = "https://files.pythonhosted.org/packages/8b/50/f256ab79c671fb066e47336706dc398c3b1e125f952e07d54ce82cf4011a/cryptography-45.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f8f8f0b73b885ddd7f3d8c2b2234a7d3ba49002b0223f58cfde1bedd9563c56", size = 4466473, upload-time = "2025-05-25T14:16:22.605Z" }, + { url = "https://files.pythonhosted.org/packages/62/e7/312428336bb2df0848d0768ab5a062e11a32d18139447a76dfc19ada8eed/cryptography-45.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9cc80ce69032ffa528b5e16d217fa4d8d4bb7d6ba8659c1b4d74a1b0f4235fca", size = 4211890, upload-time = "2025-05-25T14:16:24.738Z" }, + { url = "https://files.pythonhosted.org/packages/e7/53/8a130e22c1e432b3c14896ec5eb7ac01fb53c6737e1d705df7e0efb647c6/cryptography-45.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c824c9281cb628015bfc3c59335163d4ca0540d49de4582d6c2637312907e4b1", size = 4466300, upload-time = "2025-05-25T14:16:26.768Z" }, + { url = "https://files.pythonhosted.org/packages/ba/75/6bb6579688ef805fd16a053005fce93944cdade465fc92ef32bbc5c40681/cryptography-45.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5833bb4355cb377ebd880457663a972cd044e7f49585aee39245c0d592904578", size = 4332483, upload-time = "2025-05-25T14:16:28.316Z" }, + { url = "https://files.pythonhosted.org/packages/2f/11/2538f4e1ce05c6c4f81f43c1ef2bd6de7ae5e24ee284460ff6c77e42ca77/cryptography-45.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bb5bf55dcb69f7067d80354d0a348368da907345a2c448b0babc4215ccd3497", size = 4573714, upload-time = "2025-05-25T14:16:30.474Z" }, + { url = "https://files.pythonhosted.org/packages/46/c7/c7d05d0e133a09fc677b8a87953815c522697bdf025e5cac13ba419e7240/cryptography-45.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5edcb90da1843df85292ef3a313513766a78fbbb83f584a5a58fb001a5a9d57", size = 4196181, upload-time = "2025-05-25T14:16:37.934Z" }, + { url = "https://files.pythonhosted.org/packages/08/7a/6ad3aa796b18a683657cef930a986fac0045417e2dc428fd336cfc45ba52/cryptography-45.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38deed72285c7ed699864f964a3f4cf11ab3fb38e8d39cfcd96710cd2b5bb716", size = 4423370, upload-time = "2025-05-25T14:16:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/4f/58/ec1461bfcb393525f597ac6a10a63938d18775b7803324072974b41a926b/cryptography-45.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5555365a50efe1f486eed6ac7062c33b97ccef409f5970a0b6f205a7cfab59c8", size = 4197839, upload-time = "2025-05-25T14:16:41.322Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3d/5185b117c32ad4f40846f579369a80e710d6146c2baa8ce09d01612750db/cryptography-45.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e4253ed8f5948a3589b3caee7ad9a5bf218ffd16869c516535325fece163dcc", size = 3886324, upload-time = "2025-05-25T14:16:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/67/85/caba91a57d291a2ad46e74016d1f83ac294f08128b26e2a81e9b4f2d2555/cryptography-45.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cfd84777b4b6684955ce86156cfb5e08d75e80dc2585e10d69e47f014f0a5342", size = 4450447, upload-time = "2025-05-25T14:16:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d1/164e3c9d559133a38279215c712b8ba38e77735d3412f37711b9f8f6f7e0/cryptography-45.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a2b56de3417fd5f48773ad8e91abaa700b678dc7fe1e0c757e1ae340779acf7b", size = 4200576, upload-time = "2025-05-25T14:16:46.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/e002d5ce624ed46dfc32abe1deff32190f3ac47ede911789ee936f5a4255/cryptography-45.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:57a6500d459e8035e813bd8b51b671977fb149a8c95ed814989da682314d0782", size = 4450308, upload-time = "2025-05-25T14:16:48.228Z" }, + { url = "https://files.pythonhosted.org/packages/87/ad/3fbff9c28cf09b0a71e98af57d74f3662dea4a174b12acc493de00ea3f28/cryptography-45.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f22af3c78abfbc7cbcdf2c55d23c3e022e1a462ee2481011d518c7fb9c9f3d65", size = 4325125, upload-time = "2025-05-25T14:16:49.844Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b4/51417d0cc01802304c1984d76e9592f15e4801abd44ef7ba657060520bf0/cryptography-45.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:232954730c362638544758a8160c4ee1b832dc011d2c41a306ad8f7cccc5bb0b", size = 4560038, upload-time = "2025-05-25T14:16:51.398Z" }, ] [[package]] @@ -572,14 +572,14 @@ wheels = [ [[package]] name = "pytest-asyncio" -version = "0.26.0" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156, upload-time = "2025-03-25T06:22:28.883Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/d4/14f53324cb1a6381bef29d698987625d80052bb33932d8e7cbf9b337b17c/pytest_asyncio-1.0.0.tar.gz", hash = "sha256:d15463d13f4456e1ead2594520216b225a16f781e144f8fdf6c5bb4667c48b3f", size = 46960, upload-time = "2025-05-26T04:54:40.484Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694, upload-time = "2025-03-25T06:22:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976, upload-time = "2025-05-26T04:54:39.035Z" }, ] [[package]] From 552fa09dec0f90582bd7438bd16af7b66120598b Mon Sep 17 00:00:00 2001 From: remimd Date: Mon, 26 May 2025 11:25:59 +0200 Subject: [PATCH 2/2] wip --- injection/__init__.pyi | 20 ++++++++++---------- injection/_core/module.py | 32 ++++++++++++++++---------------- injection/ext/fastapi.py | 18 ++++++++++-------- injection/ext/fastapi.pyi | 2 ++ tests/core/test_module.py | 36 ++++++++++++++++++++++-------------- tests/test_injectable.py | 2 +- tests/test_singleton.py | 2 +- 7 files changed, 62 insertions(+), 50 deletions(-) diff --git a/injection/__init__.pyi b/injection/__init__.pyi index 849bcfd..2e8628c 100644 --- a/injection/__init__.pyi +++ b/injection/__init__.pyi @@ -241,10 +241,10 @@ class Module: async def aget_instance[T]( self, cls: _InputType[T], - default: None = ..., + default: T = ..., *, threadsafe: bool = ..., - ) -> T | None: ... + ) -> T: ... @overload def get_instance[T, Default]( self, @@ -255,17 +255,17 @@ class Module: ) -> T | Default: """ Function used to retrieve an instance associated with the type passed in - parameter or return `None`. + parameter or return `NotImplemented`. """ @overload def get_instance[T]( self, cls: _InputType[T], - default: None = ..., + default: T = ..., *, threadsafe: bool = ..., - ) -> T | None: ... + ) -> T: ... @overload def aget_lazy_instance[T, Default]( self, @@ -278,10 +278,10 @@ class Module: def aget_lazy_instance[T]( self, cls: _InputType[T], - default: None = ..., + default: T = ..., *, threadsafe: bool = ..., - ) -> Awaitable[T | None]: ... + ) -> Awaitable[T]: ... @overload def get_lazy_instance[T, Default]( self, @@ -292,7 +292,7 @@ class Module: ) -> _Invertible[T | Default]: """ Function used to retrieve an instance associated with the type passed in - parameter or `None`. Return a `Invertible` object. To access the instance + parameter or `NotImplemented`. Return a `Invertible` object. To access the instance contained in an invertible object, simply use a wavy line (~). Example: instance = ~lazy_instance @@ -302,10 +302,10 @@ class Module: def get_lazy_instance[T]( self, cls: _InputType[T], - default: None = ..., + default: T = ..., *, threadsafe: bool = ..., - ) -> _Invertible[T | None]: ... + ) -> _Invertible[T]: ... def init_modules(self, *modules: Module) -> Self: """ Function to clean modules in use and to use those passed as parameters. diff --git a/injection/_core/module.py b/injection/_core/module.py index 8564796..abf0d9d 100644 --- a/injection/_core/module.py +++ b/injection/_core/module.py @@ -645,18 +645,18 @@ async def aget_instance[T, Default]( async def aget_instance[T]( self, cls: InputType[T], - default: None = ..., + default: T = ..., *, threadsafe: bool = ..., - ) -> T | None: ... + ) -> T: ... async def aget_instance[T, Default]( self, cls: InputType[T], - default: Default | None = None, + default: Default = NotImplemented, *, threadsafe: bool = False, - ) -> T | Default | None: + ) -> T | Default: try: return await self.afind_instance(cls, threadsafe=threadsafe) except (KeyError, SkipInjectable): @@ -675,18 +675,18 @@ def get_instance[T, Default]( def get_instance[T]( self, cls: InputType[T], - default: None = ..., + default: T = ..., *, threadsafe: bool = ..., - ) -> T | None: ... + ) -> T: ... def get_instance[T, Default]( self, cls: InputType[T], - default: Default | None = None, + default: Default = NotImplemented, *, threadsafe: bool = False, - ) -> T | Default | None: + ) -> T | Default: try: return self.find_instance(cls, threadsafe=threadsafe) except (KeyError, SkipInjectable): @@ -705,18 +705,18 @@ def aget_lazy_instance[T, Default]( def aget_lazy_instance[T]( self, cls: InputType[T], - default: None = ..., + default: T = ..., *, threadsafe: bool = ..., - ) -> Awaitable[T | None]: ... + ) -> Awaitable[T]: ... def aget_lazy_instance[T, Default]( self, cls: InputType[T], - default: Default | None = None, + default: Default = NotImplemented, *, threadsafe: bool = False, - ) -> Awaitable[T | Default | None]: + ) -> Awaitable[T | Default]: function = self.make_injected_function( lambda instance=default: instance, threadsafe=threadsafe, @@ -737,18 +737,18 @@ def get_lazy_instance[T, Default]( def get_lazy_instance[T]( self, cls: InputType[T], - default: None = ..., + default: T = ..., *, threadsafe: bool = ..., - ) -> Invertible[T | None]: ... + ) -> Invertible[T]: ... def get_lazy_instance[T, Default]( self, cls: InputType[T], - default: Default | None = None, + default: Default = NotImplemented, *, threadsafe: bool = False, - ) -> Invertible[T | Default | None]: + ) -> Invertible[T | Default]: function = self.make_injected_function( lambda instance=default: instance, threadsafe=threadsafe, diff --git a/injection/ext/fastapi.py b/injection/ext/fastapi.py index c5d6cbb..7568133 100644 --- a/injection/ext/fastapi.py +++ b/injection/ext/fastapi.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass, field from types import GenericAlias from typing import Annotated, Any, TypeAliasType @@ -5,11 +6,13 @@ from injection import Module, mod -__all__ = ("Inject",) +__all__ = ("Inject", "InjectThreadSafe") +@dataclass(eq=False, frozen=True, slots=True) class FastAPIInject: - __slots__ = () + module: Module = field(default_factory=mod) + threadsafe: bool = field(default=False) def __call__[T]( self, @@ -18,13 +21,11 @@ def __call__[T]( default: T = NotImplemented, *, module: Module | None = None, - threadsafe: bool = False, + threadsafe: bool | None = None, ) -> Any: - ainstance = (module or mod()).aget_lazy_instance( - cls, - default, - threadsafe=threadsafe, - ) + module = module or self.module + threadsafe = self.threadsafe if threadsafe is None else threadsafe + ainstance = module.aget_lazy_instance(cls, default, threadsafe=threadsafe) async def dependency() -> T: return await ainstance @@ -40,5 +41,6 @@ def __getitem__(self, params: Any, /) -> Any: Inject = FastAPIInject() +InjectThreadSafe = FastAPIInject(threadsafe=True) del FastAPIInject diff --git a/injection/ext/fastapi.pyi b/injection/ext/fastapi.pyi index 023368d..0a4bac2 100644 --- a/injection/ext/fastapi.pyi +++ b/injection/ext/fastapi.pyi @@ -3,3 +3,5 @@ from typing import Annotated from fastapi import Depends type Inject[T, *Metadata] = Annotated[T, Depends(...), *Metadata] + +type InjectThreadSafe[T, *Metadata] = Inject[T, *Metadata] diff --git a/tests/core/test_module.py b/tests/core/test_module.py index a2fc14f..94afad4 100644 --- a/tests/core/test_module.py +++ b/tests/core/test_module.py @@ -89,13 +89,17 @@ async def test_aget_instance_with_success_return_instance(self, module): instance = await module.aget_instance(SomeClass) assert isinstance(instance, SomeClass) - async def test_aget_instance_with_no_injectable_return_none(self, module): + async def test_aget_instance_with_no_injectable_return_not_implemented( + self, module + ): instance = await module.aget_instance(SomeClass) - assert instance is None + assert instance is NotImplemented - async def test_aget_instance_with_empty_annotated_return_none(self, module): + async def test_aget_instance_with_empty_annotated_return_not_implemented( + self, module + ): instance = await module.aget_instance(Annotated) - assert instance is None + assert instance is NotImplemented """ get_instance @@ -107,13 +111,13 @@ def test_get_instance_with_success_return_instance(self, module): instance = module.get_instance(SomeClass) assert isinstance(instance, SomeClass) - def test_get_instance_with_no_injectable_return_none(self, module): + def test_get_instance_with_no_injectable_return_not_implemented(self, module): instance = module.get_instance(SomeClass) - assert instance is None + assert instance is NotImplemented - def test_get_instance_with_empty_annotated_return_none(self, module): + def test_get_instance_with_empty_annotated_return_not_implemented(self, module): instance = module.get_instance(Annotated) - assert instance is None + assert instance is NotImplemented """ aget_lazy_instance @@ -130,9 +134,11 @@ class A: ... assert isinstance(instance2, A) assert instance1 is not instance2 - async def test_aget_lazy_instance_with_no_injectable_return_lazy_none(self, module): + async def test_aget_lazy_instance_with_no_injectable_return_lazy_not_implemented( + self, module + ): lazy_instance = module.aget_lazy_instance(SomeClass) - assert await lazy_instance is None + assert await lazy_instance is NotImplemented """ get_lazy_instance @@ -149,9 +155,11 @@ class A: ... assert isinstance(instance2, A) assert instance1 is not instance2 - def test_get_lazy_instance_with_no_injectable_return_lazy_none(self, module): + def test_get_lazy_instance_with_no_injectable_return_lazy_not_implemented( + self, module + ): lazy_instance = module.get_lazy_instance(SomeClass) - assert ~lazy_instance is None + assert ~lazy_instance is NotImplemented """ set_constant @@ -184,7 +192,7 @@ def test_set_constant_with_success_with_type_alias(self, module): module.set_constant(value, HelloWorld, alias=True) - assert module.get_instance(str) is None + assert module.get_instance(str) is NotImplemented assert module.get_instance(HelloWorld) is value """ @@ -201,7 +209,7 @@ def test_reserve_scoped_slot_with_success(self, module): assert module.get_instance(SomeClass) is instance - assert module.get_instance(SomeClass) is None + assert module.get_instance(SomeClass) is NotImplemented def test_reserve_scoped_slot_with_empty_raise_empty_slot_error(self, module): scope_name = "test" diff --git a/tests/test_injectable.py b/tests/test_injectable.py index dc02c0e..273140b 100644 --- a/tests/test_injectable.py +++ b/tests/test_injectable.py @@ -63,7 +63,7 @@ class SomeClass: ... def recipe(): return SomeClass() # pragma: no cover - assert get_instance(SomeClass) is None + assert get_instance(SomeClass) is NotImplemented def test_injectable_with_on(self): class A: ... diff --git a/tests/test_singleton.py b/tests/test_singleton.py index 78ad234..ed4cc87 100644 --- a/tests/test_singleton.py +++ b/tests/test_singleton.py @@ -61,7 +61,7 @@ class SomeClass: ... def recipe(): return SomeClass() # pragma: no cover - assert get_instance(SomeClass) is None + assert get_instance(SomeClass) is NotImplemented def test_singleton_with_on(self): class A: ...