fastapi-injected 0.1.2__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/PKG-INFO +30 -8
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/README.md +28 -7
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/fastapi_injected/__init__.py +4 -2
- fastapi_injected-0.1.4/fastapi_injected/_fastapi_lifecycle.py +62 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/fastapi_injected/deps.py +10 -49
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/fastapi_injected/inject.py +2 -2
- fastapi_injected-0.1.2/fastapi_injected/solve.py → fastapi_injected-0.1.4/fastapi_injected/resolve.py +4 -4
- fastapi_injected-0.1.4/fastapi_injected/scope.py +108 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/pyproject.toml +3 -1
- fastapi_injected-0.1.4/tests/test_fastapi.py +35 -0
- fastapi_injected-0.1.2/tests/test_solve.py → fastapi_injected-0.1.4/tests/test_resolve.py +6 -6
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/tests/test_typing.py +3 -3
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/uv.lock +47 -45
- fastapi_injected-0.1.2/fastapi_injected/scope.py +0 -75
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/.github/dependabot.yml +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/.github/workflows/automerge.yml +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/.github/workflows/lint.yml +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/.github/workflows/publish.yml +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/.github/workflows/test.yml +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/.gitignore +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/.pre-commit-config.yaml +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/LICENSE +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/fastapi_injected/sign.py +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/fastapi_injected/types.py +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/tests/__init__.py +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/tests/deps.py +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/tests/ext/__init__.py +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/tests/ext/test_pydantic_ai.py +0 -0
- {fastapi_injected-0.1.2 → fastapi_injected-0.1.4}/tests/test_inject.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-injected
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Yet another library to reuse fastapi dependency injection
|
|
5
5
|
Project-URL: Repository, https://github.com/uriyyo/fastapi-injected
|
|
6
6
|
Author-email: Yurii Karabas <1998uriyyo@gmail.com>
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Requires-Python: >=3.12
|
|
17
17
|
Requires-Dist: fastapi>=0.139.2
|
|
18
|
+
Requires-Dist: typing-extensions>=4.16.0
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
|
|
20
21
|
# fastapi-injected
|
|
@@ -39,7 +40,7 @@ Declare dependencies as regular classes and annotate fields with `Dep[...]`:
|
|
|
39
40
|
from dataclasses import dataclass
|
|
40
41
|
from typing import AsyncIterator
|
|
41
42
|
|
|
42
|
-
from fastapi_injected import Dep, DepFactory,
|
|
43
|
+
from fastapi_injected import Dep, DepFactory, Injected, inject
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
@dataclass
|
|
@@ -66,7 +67,7 @@ class Service:
|
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
@inject
|
|
69
|
-
async def handler(*, service: Dep[Service] =
|
|
70
|
+
async def handler(*, service: Dep[Service] = Injected) -> None:
|
|
70
71
|
... # service is built and injected, session is closed on exit
|
|
71
72
|
|
|
72
73
|
|
|
@@ -75,29 +76,31 @@ await handler()
|
|
|
75
76
|
|
|
76
77
|
- `Dep[T]` — resolve `T` by calling it, same as FastAPI's `Annotated[T, Depends()]`.
|
|
77
78
|
- `DepFactory[T, factory]` — resolve `T` via a factory, same as `Annotated[T, Depends(factory)]`. Generator factories get proper teardown.
|
|
78
|
-
- `
|
|
79
|
+
- `Injected` — a sentinel default that exists purely to make type checkers happy: without it they would complain about a missing argument at call sites. At runtime the parameter is always filled in by `@inject`.
|
|
79
80
|
|
|
80
81
|
Injected parameters mix freely with regular ones — pass your own arguments as usual and the rest is injected:
|
|
81
82
|
|
|
82
83
|
```python
|
|
83
84
|
@inject
|
|
84
|
-
async def add(a: int, b: int, *, service: Dep[Service] =
|
|
85
|
+
async def add(a: int, b: int, *, service: Dep[Service] = Injected) -> int:
|
|
85
86
|
...
|
|
86
87
|
|
|
87
88
|
|
|
88
89
|
result = await add(1, 2)
|
|
89
90
|
```
|
|
90
91
|
|
|
91
|
-
###
|
|
92
|
+
### Resolving a type directly
|
|
92
93
|
|
|
93
94
|
No decorator needed — resolve a dependency graph on demand:
|
|
94
95
|
|
|
95
96
|
```python
|
|
96
|
-
from fastapi_injected import
|
|
97
|
+
from fastapi_injected import resolve
|
|
97
98
|
|
|
98
|
-
service = await
|
|
99
|
+
service = await resolve(Service)
|
|
99
100
|
```
|
|
100
101
|
|
|
102
|
+
Like `@inject`, `resolve` accepts `new_scope=True` to force a fresh scope instead of reusing the surrounding one.
|
|
103
|
+
|
|
101
104
|
### Scopes and caching
|
|
102
105
|
|
|
103
106
|
By default every call to an injected function gets its own scope: dependencies are built, cached within the call, and torn down when it returns. Wrap several calls in `push_inject_scope()` to share one cache (and defer teardown to the end of the scope):
|
|
@@ -113,6 +116,25 @@ async with push_inject_scope():
|
|
|
113
116
|
|
|
114
117
|
Use `@inject(new_scope=True)` to opt a function out of the surrounding scope and always get fresh dependencies.
|
|
115
118
|
|
|
119
|
+
### FastAPI request integration
|
|
120
|
+
|
|
121
|
+
Inside a FastAPI app, `@inject`-ed functions and `resolve` can share the request's own dependency cache — the same instances FastAPI built for the handler. Register `init_inject_scope` as a dependency:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from fastapi import Depends, FastAPI
|
|
125
|
+
from fastapi_injected import Dep, init_inject_scope, resolve
|
|
126
|
+
|
|
127
|
+
app = FastAPI(dependencies=[Depends(init_inject_scope)])
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.get("/")
|
|
131
|
+
async def route(service: Dep[Service]) -> str:
|
|
132
|
+
same = await resolve(Service) # same instance as `service`
|
|
133
|
+
...
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Anything called from the handler — including `@inject`-ed helpers — resolves against the request's cache, so a per-request dependency like a DB session stays a single instance for the whole request.
|
|
137
|
+
|
|
116
138
|
## License
|
|
117
139
|
|
|
118
140
|
MIT
|
|
@@ -20,7 +20,7 @@ Declare dependencies as regular classes and annotate fields with `Dep[...]`:
|
|
|
20
20
|
from dataclasses import dataclass
|
|
21
21
|
from typing import AsyncIterator
|
|
22
22
|
|
|
23
|
-
from fastapi_injected import Dep, DepFactory,
|
|
23
|
+
from fastapi_injected import Dep, DepFactory, Injected, inject
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
@dataclass
|
|
@@ -47,7 +47,7 @@ class Service:
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
@inject
|
|
50
|
-
async def handler(*, service: Dep[Service] =
|
|
50
|
+
async def handler(*, service: Dep[Service] = Injected) -> None:
|
|
51
51
|
... # service is built and injected, session is closed on exit
|
|
52
52
|
|
|
53
53
|
|
|
@@ -56,29 +56,31 @@ await handler()
|
|
|
56
56
|
|
|
57
57
|
- `Dep[T]` — resolve `T` by calling it, same as FastAPI's `Annotated[T, Depends()]`.
|
|
58
58
|
- `DepFactory[T, factory]` — resolve `T` via a factory, same as `Annotated[T, Depends(factory)]`. Generator factories get proper teardown.
|
|
59
|
-
- `
|
|
59
|
+
- `Injected` — a sentinel default that exists purely to make type checkers happy: without it they would complain about a missing argument at call sites. At runtime the parameter is always filled in by `@inject`.
|
|
60
60
|
|
|
61
61
|
Injected parameters mix freely with regular ones — pass your own arguments as usual and the rest is injected:
|
|
62
62
|
|
|
63
63
|
```python
|
|
64
64
|
@inject
|
|
65
|
-
async def add(a: int, b: int, *, service: Dep[Service] =
|
|
65
|
+
async def add(a: int, b: int, *, service: Dep[Service] = Injected) -> int:
|
|
66
66
|
...
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
result = await add(1, 2)
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
###
|
|
72
|
+
### Resolving a type directly
|
|
73
73
|
|
|
74
74
|
No decorator needed — resolve a dependency graph on demand:
|
|
75
75
|
|
|
76
76
|
```python
|
|
77
|
-
from fastapi_injected import
|
|
77
|
+
from fastapi_injected import resolve
|
|
78
78
|
|
|
79
|
-
service = await
|
|
79
|
+
service = await resolve(Service)
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
Like `@inject`, `resolve` accepts `new_scope=True` to force a fresh scope instead of reusing the surrounding one.
|
|
83
|
+
|
|
82
84
|
### Scopes and caching
|
|
83
85
|
|
|
84
86
|
By default every call to an injected function gets its own scope: dependencies are built, cached within the call, and torn down when it returns. Wrap several calls in `push_inject_scope()` to share one cache (and defer teardown to the end of the scope):
|
|
@@ -94,6 +96,25 @@ async with push_inject_scope():
|
|
|
94
96
|
|
|
95
97
|
Use `@inject(new_scope=True)` to opt a function out of the surrounding scope and always get fresh dependencies.
|
|
96
98
|
|
|
99
|
+
### FastAPI request integration
|
|
100
|
+
|
|
101
|
+
Inside a FastAPI app, `@inject`-ed functions and `resolve` can share the request's own dependency cache — the same instances FastAPI built for the handler. Register `init_inject_scope` as a dependency:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from fastapi import Depends, FastAPI
|
|
105
|
+
from fastapi_injected import Dep, init_inject_scope, resolve
|
|
106
|
+
|
|
107
|
+
app = FastAPI(dependencies=[Depends(init_inject_scope)])
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@app.get("/")
|
|
111
|
+
async def route(service: Dep[Service]) -> str:
|
|
112
|
+
same = await resolve(Service) # same instance as `service`
|
|
113
|
+
...
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Anything called from the handler — including `@inject`-ed helpers — resolves against the request's cache, so a per-request dependency like a DB session stays a single instance for the whole request.
|
|
117
|
+
|
|
97
118
|
## License
|
|
98
119
|
|
|
99
120
|
MIT
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
from ._fastapi_lifecycle import init_inject_scope
|
|
1
2
|
from .inject import inject
|
|
3
|
+
from .resolve import resolve
|
|
2
4
|
from .scope import push_inject_scope
|
|
3
|
-
from .solve import solve
|
|
4
5
|
from .types import Dep, DepFactory, Injected
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
7
8
|
"Dep",
|
|
8
9
|
"DepFactory",
|
|
9
10
|
"Injected",
|
|
11
|
+
"init_inject_scope",
|
|
10
12
|
"inject",
|
|
11
13
|
"push_inject_scope",
|
|
12
|
-
"
|
|
14
|
+
"resolve",
|
|
13
15
|
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from collections.abc import AsyncIterable
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from fastapi import Request, WebSocket, routing
|
|
6
|
+
from fastapi.dependencies import utils
|
|
7
|
+
|
|
8
|
+
from .scope import push_inject_scope
|
|
9
|
+
from .types import DependencyCache
|
|
10
|
+
|
|
11
|
+
_DEPENDENCY_CACHE_KEY = "__fastapi_injected_dependency_cache__"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MissingDependencyCacheError(Exception):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if not TYPE_CHECKING:
|
|
19
|
+
_base_solve_dependencies = utils.solve_dependencies
|
|
20
|
+
|
|
21
|
+
@wraps(_base_solve_dependencies)
|
|
22
|
+
async def _solve_dependencies(
|
|
23
|
+
*,
|
|
24
|
+
request: Request | WebSocket,
|
|
25
|
+
dependency_cache: DependencyCache | None = None,
|
|
26
|
+
**kwargs: Any,
|
|
27
|
+
) -> utils.SolvedDependency:
|
|
28
|
+
if (current_cache := request.scope.get(_DEPENDENCY_CACHE_KEY)) is not None:
|
|
29
|
+
dependency_cache = current_cache
|
|
30
|
+
elif dependency_cache is None:
|
|
31
|
+
dependency_cache = {}
|
|
32
|
+
|
|
33
|
+
request.scope[_DEPENDENCY_CACHE_KEY] = dependency_cache
|
|
34
|
+
|
|
35
|
+
return await _base_solve_dependencies(
|
|
36
|
+
request=request,
|
|
37
|
+
dependency_cache=dependency_cache,
|
|
38
|
+
**kwargs,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
utils.solve_dependencies = _solve_dependencies
|
|
42
|
+
routing.solve_dependencies = _solve_dependencies
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _get_dependency_cache(request: Request) -> DependencyCache:
|
|
46
|
+
try:
|
|
47
|
+
return request.scope[_DEPENDENCY_CACHE_KEY]
|
|
48
|
+
except KeyError:
|
|
49
|
+
raise MissingDependencyCacheError("Dependency cache not found") from None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def init_inject_scope(request: Request) -> AsyncIterable[None]:
|
|
53
|
+
async with push_inject_scope(
|
|
54
|
+
dependency_cache=_get_dependency_cache(request),
|
|
55
|
+
request=request,
|
|
56
|
+
):
|
|
57
|
+
yield
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
__all__ = [
|
|
61
|
+
"init_inject_scope",
|
|
62
|
+
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from collections.abc import Callable
|
|
3
|
+
from contextlib import AsyncExitStack
|
|
3
4
|
from contextvars import ContextVar
|
|
4
5
|
from copy import copy
|
|
5
6
|
from functools import lru_cache, wraps
|
|
@@ -7,49 +8,13 @@ from typing import Annotated, Any, Literal, cast, overload
|
|
|
7
8
|
|
|
8
9
|
from fastapi import Depends
|
|
9
10
|
from fastapi.dependencies.models import Dependant
|
|
10
|
-
from fastapi.dependencies.utils import get_dependant, get_typed_signature
|
|
11
|
-
from fastapi.dependencies.utils import solve_dependencies as _solve_dependencies
|
|
12
|
-
from starlette.requests import Request
|
|
13
|
-
from starlette.types import Message, Scope
|
|
11
|
+
from fastapi.dependencies.utils import get_dependant, get_typed_signature, solve_dependencies
|
|
14
12
|
|
|
15
13
|
from .scope import InjectScope
|
|
16
14
|
from .sign import prepare_sign, update_func_sign
|
|
17
15
|
from .types import Coro, HasSignature
|
|
18
16
|
|
|
19
17
|
|
|
20
|
-
def _dummy_scope() -> Scope:
|
|
21
|
-
return {
|
|
22
|
-
"type": "http",
|
|
23
|
-
"http_version": "1.1",
|
|
24
|
-
"query_string": b"",
|
|
25
|
-
"headers": [],
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def _dummy_request(
|
|
30
|
-
*,
|
|
31
|
-
extra_scope: Scope | None = None,
|
|
32
|
-
) -> Request:
|
|
33
|
-
async def _dummy_receive() -> Message:
|
|
34
|
-
return {
|
|
35
|
-
"type": "http.request",
|
|
36
|
-
"body": b"",
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async def _dummy_send(_: Message, /) -> None:
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
|
-
scope = _dummy_scope()
|
|
43
|
-
if extra_scope:
|
|
44
|
-
scope.update(extra_scope)
|
|
45
|
-
|
|
46
|
-
return Request(
|
|
47
|
-
scope,
|
|
48
|
-
receive=_dummy_receive,
|
|
49
|
-
send=_dummy_send,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
53
18
|
@lru_cache(maxsize=1024)
|
|
54
19
|
def create_dependant[**P, R](func: Callable[P, Coro[R]], /) -> Dependant:
|
|
55
20
|
@wraps(func)
|
|
@@ -90,7 +55,7 @@ def create_single_dependant[**P, R](func: Callable[P, R], /) -> Dependant:
|
|
|
90
55
|
|
|
91
56
|
|
|
92
57
|
@overload
|
|
93
|
-
async def
|
|
58
|
+
async def resolve_dependencies(
|
|
94
59
|
dependant: Dependant,
|
|
95
60
|
scope: InjectScope,
|
|
96
61
|
*,
|
|
@@ -99,7 +64,7 @@ async def solve_dependencies(
|
|
|
99
64
|
|
|
100
65
|
|
|
101
66
|
@overload
|
|
102
|
-
async def
|
|
67
|
+
async def resolve_dependencies(
|
|
103
68
|
dependant: Dependant,
|
|
104
69
|
scope: InjectScope,
|
|
105
70
|
*,
|
|
@@ -107,23 +72,19 @@ async def solve_dependencies(
|
|
|
107
72
|
) -> Any: ...
|
|
108
73
|
|
|
109
74
|
|
|
110
|
-
async def
|
|
75
|
+
async def resolve_dependencies(
|
|
111
76
|
dependant: Dependant,
|
|
112
77
|
scope: InjectScope,
|
|
113
78
|
*,
|
|
114
79
|
single: bool = False,
|
|
115
80
|
) -> dict[str, Any]:
|
|
116
|
-
solved = await
|
|
117
|
-
request=
|
|
118
|
-
extra_scope={
|
|
119
|
-
"fastapi_inner_astack": scope.request_astack,
|
|
120
|
-
"fastapi_function_astack": scope.func_astack,
|
|
121
|
-
},
|
|
122
|
-
),
|
|
81
|
+
solved = await solve_dependencies(
|
|
82
|
+
request=scope.request,
|
|
123
83
|
dependant=dependant,
|
|
124
|
-
async_exit_stack=scope.request_astack,
|
|
125
84
|
dependency_cache=copy(scope.dependency_cache),
|
|
126
85
|
dependency_overrides_provider=_dependency_override_provider.get(),
|
|
86
|
+
# this parameter is deprecated and not used
|
|
87
|
+
async_exit_stack=cast(AsyncExitStack, None),
|
|
127
88
|
embed_body_fields=False,
|
|
128
89
|
)
|
|
129
90
|
|
|
@@ -153,6 +114,6 @@ def set_inject_dependency_override_provider(provider: Any, /) -> None:
|
|
|
153
114
|
|
|
154
115
|
__all__ = [
|
|
155
116
|
"create_dependant",
|
|
117
|
+
"resolve_dependencies",
|
|
156
118
|
"set_inject_dependency_override_provider",
|
|
157
|
-
"solve_dependencies",
|
|
158
119
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from functools import partial, wraps
|
|
2
2
|
from typing import overload
|
|
3
3
|
|
|
4
|
-
from .deps import create_dependant,
|
|
4
|
+
from .deps import create_dependant, resolve_dependencies
|
|
5
5
|
from .scope import inside_inject_scope
|
|
6
6
|
from .sign import strip_sign
|
|
7
7
|
from .types import AsyncFunc, Decorator
|
|
@@ -40,7 +40,7 @@ def inject[**P, R](
|
|
|
40
40
|
async with inside_inject_scope(
|
|
41
41
|
new_scope=new_scope,
|
|
42
42
|
) as inject_scope:
|
|
43
|
-
solved = await
|
|
43
|
+
solved = await resolve_dependencies(dependant, inject_scope)
|
|
44
44
|
|
|
45
45
|
for key, value in solved.items():
|
|
46
46
|
kwargs.setdefault(key, value)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from typing_extensions import TypeForm
|
|
2
2
|
|
|
3
|
-
from .deps import create_single_dependant,
|
|
3
|
+
from .deps import create_single_dependant, resolve_dependencies
|
|
4
4
|
from .scope import inside_inject_scope
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
async def
|
|
7
|
+
async def resolve[R](
|
|
8
8
|
tp: TypeForm[R],
|
|
9
9
|
*,
|
|
10
10
|
new_scope: bool = False,
|
|
@@ -14,7 +14,7 @@ async def solve[R](
|
|
|
14
14
|
async with inside_inject_scope(
|
|
15
15
|
new_scope=new_scope,
|
|
16
16
|
) as inject_scope:
|
|
17
|
-
return await
|
|
17
|
+
return await resolve_dependencies(
|
|
18
18
|
dependant,
|
|
19
19
|
inject_scope,
|
|
20
20
|
single=True,
|
|
@@ -22,5 +22,5 @@ async def solve[R](
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
__all__ = [
|
|
25
|
-
"
|
|
25
|
+
"resolve",
|
|
26
26
|
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from collections.abc import AsyncIterator
|
|
2
|
+
from contextlib import (
|
|
3
|
+
AsyncExitStack,
|
|
4
|
+
asynccontextmanager,
|
|
5
|
+
)
|
|
6
|
+
from contextvars import ContextVar
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from fastapi import Request
|
|
10
|
+
from starlette.types import Message, Scope
|
|
11
|
+
|
|
12
|
+
from .types import DependencyCache
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _dummy_scope() -> Scope:
|
|
16
|
+
return {
|
|
17
|
+
"type": "http",
|
|
18
|
+
"http_version": "1.1",
|
|
19
|
+
"query_string": b"",
|
|
20
|
+
"headers": [],
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _dummy_request(
|
|
25
|
+
*,
|
|
26
|
+
extra_scope: Scope | None = None,
|
|
27
|
+
) -> Request:
|
|
28
|
+
async def _dummy_receive() -> Message:
|
|
29
|
+
return {
|
|
30
|
+
"type": "http.request",
|
|
31
|
+
"body": b"",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async def _dummy_send(_: Message, /) -> None:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
scope = _dummy_scope()
|
|
38
|
+
if extra_scope:
|
|
39
|
+
scope.update(extra_scope)
|
|
40
|
+
|
|
41
|
+
return Request(
|
|
42
|
+
scope,
|
|
43
|
+
receive=_dummy_receive,
|
|
44
|
+
send=_dummy_send,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class InjectScope:
|
|
50
|
+
dependency_cache: DependencyCache
|
|
51
|
+
request: Request
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_inject_scope: ContextVar[InjectScope | None] = ContextVar(
|
|
55
|
+
"_inject_scope",
|
|
56
|
+
default=None,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@asynccontextmanager
|
|
61
|
+
async def push_inject_scope(
|
|
62
|
+
*,
|
|
63
|
+
dependency_cache: DependencyCache | None = None,
|
|
64
|
+
request: Request | None = None,
|
|
65
|
+
) -> AsyncIterator[InjectScope]:
|
|
66
|
+
if dependency_cache is None:
|
|
67
|
+
dependency_cache = {}
|
|
68
|
+
|
|
69
|
+
async with AsyncExitStack() as stack:
|
|
70
|
+
request = request or _dummy_request(
|
|
71
|
+
extra_scope={
|
|
72
|
+
"fastapi_inner_astack": stack,
|
|
73
|
+
"fastapi_function_astack": stack,
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
scope = InjectScope(dependency_cache, request)
|
|
78
|
+
token = _inject_scope.set(scope)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
yield scope
|
|
82
|
+
finally:
|
|
83
|
+
_inject_scope.reset(token)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def current_inject_scope() -> InjectScope | None:
|
|
87
|
+
return _inject_scope.get()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@asynccontextmanager
|
|
91
|
+
async def inside_inject_scope(
|
|
92
|
+
*,
|
|
93
|
+
new_scope: bool = False,
|
|
94
|
+
) -> AsyncIterator[InjectScope]:
|
|
95
|
+
scope = current_inject_scope()
|
|
96
|
+
|
|
97
|
+
async with AsyncExitStack() as stack:
|
|
98
|
+
if scope is None or new_scope:
|
|
99
|
+
scope = await stack.enter_async_context(push_inject_scope())
|
|
100
|
+
|
|
101
|
+
yield scope
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
__all__ = [
|
|
105
|
+
"InjectScope",
|
|
106
|
+
"current_inject_scope",
|
|
107
|
+
"inside_inject_scope",
|
|
108
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastapi-injected"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
description = "Yet another library to reuse fastapi dependency injection"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Yurii Karabas", email = "1998uriyyo@gmail.com" },
|
|
@@ -19,6 +19,7 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
dependencies = [
|
|
21
21
|
"fastapi>=0.139.2",
|
|
22
|
+
"typing-extensions>=4.16.0",
|
|
22
23
|
]
|
|
23
24
|
|
|
24
25
|
[dependency-groups]
|
|
@@ -30,6 +31,7 @@ dev = [
|
|
|
30
31
|
"ty>=0.0.62",
|
|
31
32
|
"pre-commit>=4.6.1",
|
|
32
33
|
"pydantic-ai-slim>=2.15.0",
|
|
34
|
+
"httpx>=0.28.1",
|
|
33
35
|
]
|
|
34
36
|
|
|
35
37
|
[project.urls]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from fastapi import Depends, FastAPI, status
|
|
2
|
+
from fastapi.testclient import TestClient
|
|
3
|
+
|
|
4
|
+
from fastapi_injected import Dep, Injected, init_inject_scope, inject, resolve
|
|
5
|
+
|
|
6
|
+
from .deps import Container
|
|
7
|
+
|
|
8
|
+
app = FastAPI(
|
|
9
|
+
dependencies=[
|
|
10
|
+
Depends(init_inject_scope),
|
|
11
|
+
],
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
client = TestClient(app)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@inject
|
|
18
|
+
async def _func(
|
|
19
|
+
*,
|
|
20
|
+
container: Dep[Container] = Injected,
|
|
21
|
+
) -> Container:
|
|
22
|
+
return container
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.get("/")
|
|
26
|
+
async def route(container: Dep[Container]) -> str:
|
|
27
|
+
assert await resolve(Container) is container
|
|
28
|
+
assert await _func() is container
|
|
29
|
+
|
|
30
|
+
return ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_cache_is_working() -> None:
|
|
34
|
+
result = client.get("/")
|
|
35
|
+
assert result.status_code == status.HTTP_200_OK
|
|
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from fastapi_injected import Dep, push_inject_scope,
|
|
5
|
+
from fastapi_injected import Dep, push_inject_scope, resolve
|
|
6
6
|
|
|
7
7
|
pytestmark = pytest.mark.asyncio
|
|
8
8
|
|
|
@@ -17,16 +17,16 @@ class Bar:
|
|
|
17
17
|
foo: Dep[Foo]
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
async def
|
|
21
|
-
bar = await
|
|
20
|
+
async def test_resolve():
|
|
21
|
+
bar = await resolve(Bar)
|
|
22
22
|
|
|
23
23
|
assert isinstance(bar, Bar)
|
|
24
24
|
assert isinstance(bar.foo, Foo)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
async def
|
|
27
|
+
async def test_resolve_reuse_cache():
|
|
28
28
|
async with push_inject_scope():
|
|
29
|
-
b1 = await
|
|
30
|
-
b2 = await
|
|
29
|
+
b1 = await resolve(Bar)
|
|
30
|
+
b2 = await resolve(Bar)
|
|
31
31
|
|
|
32
32
|
assert b1 is b2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING
|
|
2
2
|
|
|
3
|
-
from fastapi_injected import Dep, DepFactory, Injected, inject,
|
|
3
|
+
from fastapi_injected import Dep, DepFactory, Injected, inject, resolve
|
|
4
4
|
|
|
5
5
|
from .deps import Child, Container, ContextState, ctx_dep
|
|
6
6
|
|
|
@@ -22,8 +22,8 @@ async def func(
|
|
|
22
22
|
return a + 10
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
async def
|
|
26
|
-
container = await
|
|
25
|
+
async def resolve_test() -> None:
|
|
26
|
+
container = await resolve(Container)
|
|
27
27
|
|
|
28
28
|
static_assert(is_equivalent_to(TypeOf[container], Container))
|
|
29
29
|
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
version = 1
|
|
2
2
|
revision = 3
|
|
3
3
|
requires-python = ">=3.12"
|
|
4
|
-
resolution-markers = [
|
|
5
|
-
"python_full_version >= '3.14'",
|
|
6
|
-
"python_full_version < '3.14'",
|
|
7
|
-
]
|
|
8
4
|
|
|
9
5
|
[[package]]
|
|
10
6
|
name = "annotated-doc"
|
|
@@ -160,14 +156,16 @@ wheels = [
|
|
|
160
156
|
|
|
161
157
|
[[package]]
|
|
162
158
|
name = "fastapi-injected"
|
|
163
|
-
version = "0.1.
|
|
159
|
+
version = "0.1.4"
|
|
164
160
|
source = { editable = "." }
|
|
165
161
|
dependencies = [
|
|
166
162
|
{ name = "fastapi" },
|
|
163
|
+
{ name = "typing-extensions" },
|
|
167
164
|
]
|
|
168
165
|
|
|
169
166
|
[package.dev-dependencies]
|
|
170
167
|
dev = [
|
|
168
|
+
{ name = "httpx" },
|
|
171
169
|
{ name = "pre-commit" },
|
|
172
170
|
{ name = "pydantic-ai-slim" },
|
|
173
171
|
{ name = "pytest" },
|
|
@@ -178,10 +176,14 @@ dev = [
|
|
|
178
176
|
]
|
|
179
177
|
|
|
180
178
|
[package.metadata]
|
|
181
|
-
requires-dist = [
|
|
179
|
+
requires-dist = [
|
|
180
|
+
{ name = "fastapi", specifier = ">=0.139.2" },
|
|
181
|
+
{ name = "typing-extensions", specifier = ">=4.16.0" },
|
|
182
|
+
]
|
|
182
183
|
|
|
183
184
|
[package.metadata.requires-dev]
|
|
184
185
|
dev = [
|
|
186
|
+
{ name = "httpx", specifier = ">=0.28.1" },
|
|
185
187
|
{ name = "pre-commit", specifier = ">=4.6.1" },
|
|
186
188
|
{ name = "pydantic-ai-slim", specifier = ">=2.15.0" },
|
|
187
189
|
{ name = "pytest", specifier = ">=9.1.1" },
|
|
@@ -202,15 +204,15 @@ wheels = [
|
|
|
202
204
|
|
|
203
205
|
[[package]]
|
|
204
206
|
name = "genai-prices"
|
|
205
|
-
version = "0.0.
|
|
207
|
+
version = "0.0.72"
|
|
206
208
|
source = { registry = "https://pypi.org/simple" }
|
|
207
209
|
dependencies = [
|
|
208
210
|
{ name = "httpx2" },
|
|
209
211
|
{ name = "pydantic" },
|
|
210
212
|
]
|
|
211
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
213
|
+
sdist = { url = "https://files.pythonhosted.org/packages/6f/c8/2549fa8ceaaf0bd61114cf392250a107be657233723bc4e65cb91dba934f/genai_prices-0.0.72.tar.gz", hash = "sha256:a7e481d0ea85922fcf48df6864f2491fc81a4f03dcf0ecbd9aa8c2c6d9fcdbe8", size = 82753, upload-time = "2026-07-22T21:11:03.997Z" }
|
|
212
214
|
wheels = [
|
|
213
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
215
|
+
{ url = "https://files.pythonhosted.org/packages/22/2e/1e2c31666f7e4e2d0327a80f1c920fd8764ccd27672c4bc389d79219ece6/genai_prices-0.0.72-py3-none-any.whl", hash = "sha256:21281d8df34d9bfbb736e0763a60ceda02226938cd47379ae437a44fcf8ba23d", size = 85238, upload-time = "2026-07-22T21:11:02.911Z" },
|
|
214
216
|
]
|
|
215
217
|
|
|
216
218
|
[[package]]
|
|
@@ -246,15 +248,15 @@ wheels = [
|
|
|
246
248
|
|
|
247
249
|
[[package]]
|
|
248
250
|
name = "httpcore2"
|
|
249
|
-
version = "2.
|
|
251
|
+
version = "2.9.0"
|
|
250
252
|
source = { registry = "https://pypi.org/simple" }
|
|
251
253
|
dependencies = [
|
|
252
254
|
{ name = "h11" },
|
|
253
255
|
{ name = "truststore" },
|
|
254
256
|
]
|
|
255
|
-
sdist = { url = "https://files.pythonhosted.org/packages/d5/
|
|
257
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d5/4e/fc534e5c8e9d6df4598cda50bd3df936ba9af495f932dc93376597b2f6db/httpcore2-2.9.0.tar.gz", hash = "sha256:03077a578e26d6166d831f68319326bf1d92cb851946292d2158a85e9da1896a", size = 67072, upload-time = "2026-07-23T14:09:49.949Z" }
|
|
256
258
|
wheels = [
|
|
257
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
259
|
+
{ url = "https://files.pythonhosted.org/packages/d7/00/bf7357a92f1807f654c3bb303b3e158294ec6a1313c79511fc9f1a45de2b/httpcore2-2.9.0-py3-none-any.whl", hash = "sha256:e51e54521809628d2014da8d67eecd4de738d54f556d646069fa9cb63fc9a9dc", size = 82800, upload-time = "2026-07-23T14:09:46.868Z" },
|
|
258
260
|
]
|
|
259
261
|
|
|
260
262
|
[[package]]
|
|
@@ -274,7 +276,7 @@ wheels = [
|
|
|
274
276
|
|
|
275
277
|
[[package]]
|
|
276
278
|
name = "httpx2"
|
|
277
|
-
version = "2.
|
|
279
|
+
version = "2.9.0"
|
|
278
280
|
source = { registry = "https://pypi.org/simple" }
|
|
279
281
|
dependencies = [
|
|
280
282
|
{ name = "anyio" },
|
|
@@ -283,9 +285,9 @@ dependencies = [
|
|
|
283
285
|
{ name = "truststore" },
|
|
284
286
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
285
287
|
]
|
|
286
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
288
|
+
sdist = { url = "https://files.pythonhosted.org/packages/92/18/b79756b348664b13253a5811230daa1f33b3f0e86df4409b26199f5e9c3e/httpx2-2.9.0.tar.gz", hash = "sha256:85e33a04e7b0ad24044a62affa916140f363f19ba38e15fbc7e6aba1f1fb2775", size = 95340, upload-time = "2026-07-23T14:09:51.094Z" }
|
|
287
289
|
wheels = [
|
|
288
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
290
|
+
{ url = "https://files.pythonhosted.org/packages/82/7d/354002b85409c6fe6f3f6b97624f1d11a4ddf701cd9a7ed2e161c73f0d86/httpx2-2.9.0-py3-none-any.whl", hash = "sha256:cc8e82fa3dc02fa91586dc6ccb3c9bd802a5a48adf4626d66553e793de67cbb4", size = 91107, upload-time = "2026-07-23T14:09:48.434Z" },
|
|
289
291
|
]
|
|
290
292
|
|
|
291
293
|
[[package]]
|
|
@@ -335,14 +337,14 @@ wheels = [
|
|
|
335
337
|
|
|
336
338
|
[[package]]
|
|
337
339
|
name = "opentelemetry-api"
|
|
338
|
-
version = "1.
|
|
340
|
+
version = "1.44.0"
|
|
339
341
|
source = { registry = "https://pypi.org/simple" }
|
|
340
342
|
dependencies = [
|
|
341
343
|
{ name = "typing-extensions" },
|
|
342
344
|
]
|
|
343
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
345
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" }
|
|
344
346
|
wheels = [
|
|
345
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
347
|
+
{ url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" },
|
|
346
348
|
]
|
|
347
349
|
|
|
348
350
|
[[package]]
|
|
@@ -405,7 +407,7 @@ wheels = [
|
|
|
405
407
|
|
|
406
408
|
[[package]]
|
|
407
409
|
name = "pydantic-ai-slim"
|
|
408
|
-
version = "2.
|
|
410
|
+
version = "2.16.0"
|
|
409
411
|
source = { registry = "https://pypi.org/simple" }
|
|
410
412
|
dependencies = [
|
|
411
413
|
{ name = "genai-prices" },
|
|
@@ -416,9 +418,9 @@ dependencies = [
|
|
|
416
418
|
{ name = "pydantic-graph" },
|
|
417
419
|
{ name = "typing-inspection" },
|
|
418
420
|
]
|
|
419
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
421
|
+
sdist = { url = "https://files.pythonhosted.org/packages/76/6a/3048579c646f4cea7966889009a1d7eef1365f9126393945d39cefad9e83/pydantic_ai_slim-2.16.0.tar.gz", hash = "sha256:36d17cb12edd72ffc62f9e06cc49ac5f23cb77cf6b665c4b1fd11c00dbe6a852", size = 889343, upload-time = "2026-07-23T02:47:20.696Z" }
|
|
420
422
|
wheels = [
|
|
421
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
423
|
+
{ url = "https://files.pythonhosted.org/packages/2e/95/2c79b9f8e875562bae8141078af27428120e8415d493bb07ea36ca5905f9/pydantic_ai_slim-2.16.0-py3-none-any.whl", hash = "sha256:7cad27fb8f45ce4af4e8da83d7f206a3e1038dbc7535625c0ce5518fe378a55d", size = 1077057, upload-time = "2026-07-23T02:47:12.633Z" },
|
|
422
424
|
]
|
|
423
425
|
|
|
424
426
|
[[package]]
|
|
@@ -498,7 +500,7 @@ wheels = [
|
|
|
498
500
|
|
|
499
501
|
[[package]]
|
|
500
502
|
name = "pydantic-graph"
|
|
501
|
-
version = "2.
|
|
503
|
+
version = "2.16.0"
|
|
502
504
|
source = { registry = "https://pypi.org/simple" }
|
|
503
505
|
dependencies = [
|
|
504
506
|
{ name = "httpx" },
|
|
@@ -506,9 +508,9 @@ dependencies = [
|
|
|
506
508
|
{ name = "pydantic" },
|
|
507
509
|
{ name = "typing-inspection" },
|
|
508
510
|
]
|
|
509
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
511
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/22/6b6426e14607275f6b15f2a0c4530514c48ff01f86c53bbaac4041d09df9/pydantic_graph-2.16.0.tar.gz", hash = "sha256:f71e5c8e78a4ce56bc044861178e506ebd99881717ae0993924c457f5de6230e", size = 43979, upload-time = "2026-07-23T02:47:23.328Z" }
|
|
510
512
|
wheels = [
|
|
511
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
513
|
+
{ url = "https://files.pythonhosted.org/packages/80/c0/362a7fb50562d7b51e3d02d454826e0a7b5652f27e5be8e835ddfaf84da6/pydantic_graph-2.16.0-py3-none-any.whl", hash = "sha256:99c25852c436d4d510d1ecdcb53ce4a0b11aa4d1d9d81472727587f2b5a3bc13", size = 51661, upload-time = "2026-07-23T02:47:15.921Z" },
|
|
512
514
|
]
|
|
513
515
|
|
|
514
516
|
[[package]]
|
|
@@ -671,27 +673,27 @@ wheels = [
|
|
|
671
673
|
|
|
672
674
|
[[package]]
|
|
673
675
|
name = "ty"
|
|
674
|
-
version = "0.0.
|
|
675
|
-
source = { registry = "https://pypi.org/simple" }
|
|
676
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
677
|
-
wheels = [
|
|
678
|
-
{ url = "https://files.pythonhosted.org/packages/e4/
|
|
679
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
680
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
681
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
682
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
683
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
684
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
685
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
686
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
687
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
688
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
689
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
690
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
691
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
692
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
693
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
694
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
676
|
+
version = "0.0.63"
|
|
677
|
+
source = { registry = "https://pypi.org/simple" }
|
|
678
|
+
sdist = { url = "https://files.pythonhosted.org/packages/ba/ce/cbeaa5c7576fec643609dfbf200d59493523b1cc0481d4e7a5effcbf0630/ty-0.0.63.tar.gz", hash = "sha256:c2f66439393b3acac69306c117d4ae44638ce5fffa4a20c21046e85bd473359f", size = 6280695, upload-time = "2026-07-23T11:41:39.845Z" }
|
|
679
|
+
wheels = [
|
|
680
|
+
{ url = "https://files.pythonhosted.org/packages/1a/e4/d17a8e113ab15c692fe6fb9c422112d4bee7e829d80ced35826b45d98d98/ty-0.0.63-py3-none-linux_armv6l.whl", hash = "sha256:9a4ef7782e3af314fb63d006bec5ac3025bd70fee774b4dcfb5e44e8564f1994", size = 12056302, upload-time = "2026-07-23T11:41:03.5Z" },
|
|
681
|
+
{ url = "https://files.pythonhosted.org/packages/be/0b/357234c815dc4bfcc88c3f860aa0983fe4228c8aa2a5bb16c35ee08a94af/ty-0.0.63-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:01eab0ab70d51ad10298aa2d4b058b387a1fe93e5ee52d2a1ee23e9c69ba8354", size = 11737674, upload-time = "2026-07-23T11:41:05.862Z" },
|
|
682
|
+
{ url = "https://files.pythonhosted.org/packages/1c/13/193d9aeeb6774690351cff9fafabd3ae9b54cc225d125e07fa004ce23bdc/ty-0.0.63-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a671b61eaad16178389e05b9c108c9cb75ae8d84968fe55906484f55d6268338", size = 11264191, upload-time = "2026-07-23T11:41:07.963Z" },
|
|
683
|
+
{ url = "https://files.pythonhosted.org/packages/4f/b7/c5843e16759a2b25fc8a7197473474038e585ac9fdaa6fa16aeb6384bf6a/ty-0.0.63-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b255cc83d95c51bb9ee4931303fdbece8cb1d6d7c655eb77a3f2c8f349fb64d6", size = 11818890, upload-time = "2026-07-23T11:41:09.885Z" },
|
|
684
|
+
{ url = "https://files.pythonhosted.org/packages/06/20/adf83ae1fc570d9bb449605e1eeeaa523ad2329ca838a636698b5c135d11/ty-0.0.63-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0da1e8183aa4f893421a173904478021498f542ee41273660538da6649a9631c", size = 11853141, upload-time = "2026-07-23T11:41:12.151Z" },
|
|
685
|
+
{ url = "https://files.pythonhosted.org/packages/ea/90/f8effd846e3ee13486ea08257c13094d58b9f188c5641bf609d6a7d5c09f/ty-0.0.63-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128f38eb5a67199e3811426386f7ec96f41251ddf45e04ddc0bcbb29d4853245", size = 12545184, upload-time = "2026-07-23T11:41:14.4Z" },
|
|
686
|
+
{ url = "https://files.pythonhosted.org/packages/b8/76/aac3a30d40431eb1d329acea016b248f5b6255e30ef3d28e19447e344be2/ty-0.0.63-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bedf34aff8b0557f2a7119b314a68a9c9e58c1d21554d0e2748f2c5fe6a1f638", size = 13062375, upload-time = "2026-07-23T11:41:16.441Z" },
|
|
687
|
+
{ url = "https://files.pythonhosted.org/packages/62/3d/0158733932893e17f6008dadd74c8d7aed422fefc66e47fe77888014fe8c/ty-0.0.63-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7328d63c34587606dce02935a25404f54cd0161bfe413b8f7fc21d174aead612", size = 12619461, upload-time = "2026-07-23T11:41:18.538Z" },
|
|
688
|
+
{ url = "https://files.pythonhosted.org/packages/f5/be/e280ad095b050778f16f493d49a073fa5f6d8f301d3e2e59be6a672ba05c/ty-0.0.63-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504c4457f3a62afe836c1f26a2c9a12549299095f9cc4146778558df51a7515c", size = 12376402, upload-time = "2026-07-23T11:41:20.482Z" },
|
|
689
|
+
{ url = "https://files.pythonhosted.org/packages/57/57/619788bf335cb86b090470b557ae1eed33613dc24e12142505d32b462e58/ty-0.0.63-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7394ed39424b027c89d5f0c818871c791e33958b88f5bb13e14bd4a12a3ca631", size = 12671377, upload-time = "2026-07-23T11:41:22.489Z" },
|
|
690
|
+
{ url = "https://files.pythonhosted.org/packages/07/a2/0aff2cdd3c98e2c4729337542b8da0ce1980790e1600e0c4a717a1f46293/ty-0.0.63-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:94c3f155230490f8fb505911655e940c630c25cc0c35a76690cb413254fb4437", size = 11768090, upload-time = "2026-07-23T11:41:24.558Z" },
|
|
691
|
+
{ url = "https://files.pythonhosted.org/packages/43/4a/cdb5f1d26154144dfee08e1bd671daf827238170f7cee9dad43990bb2016/ty-0.0.63-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c16e1d8b4f0ae99106d5f13a894034a202baf6cdab61e2bb1a239de82904f839", size = 11867747, upload-time = "2026-07-23T11:41:26.794Z" },
|
|
692
|
+
{ url = "https://files.pythonhosted.org/packages/7e/26/ecc09ecb70bc9fbfdf42fa57ff29568f173e8200df575a0d72dc2b8486f9/ty-0.0.63-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b66293dad89eaed4b9fbf3b661614dbeb85b59ba037178a3f9a0adedf264da5c", size = 12120000, upload-time = "2026-07-23T11:41:28.731Z" },
|
|
693
|
+
{ url = "https://files.pythonhosted.org/packages/14/07/862822f9c2c397785b69b25cf79b4dfc3c0d55684b9adf11ac194450f8e1/ty-0.0.63-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8b29cc832e2ec73502c97dd7325d6ae0e085b34a33529a0f785192317d9862fc", size = 12477862, upload-time = "2026-07-23T11:41:30.847Z" },
|
|
694
|
+
{ url = "https://files.pythonhosted.org/packages/d0/c2/50b08b641578d6f48e8aa28a91d0de32c91aa24dd926ed199e8afd2d37ea/ty-0.0.63-py3-none-win32.whl", hash = "sha256:f0a8fbfd1f990c0c5d85cce018d438969ac79e49247e2192c9745394bb7d17ab", size = 11433155, upload-time = "2026-07-23T11:41:33.28Z" },
|
|
695
|
+
{ url = "https://files.pythonhosted.org/packages/92/2d/d422a5568f0d1f317186be22241288dc6e08b71dc24aca223f22038ac2d2/ty-0.0.63-py3-none-win_amd64.whl", hash = "sha256:2aa2370bdd6f42e9f37518c812379bef70b9162f9e5aad511495229b0b71cb93", size = 12450036, upload-time = "2026-07-23T11:41:35.559Z" },
|
|
696
|
+
{ url = "https://files.pythonhosted.org/packages/35/97/2c9748e28ead0650c7ad3e5f74f178832ceabd7cb5c272a882f29eb32ee4/ty-0.0.63-py3-none-win_arm64.whl", hash = "sha256:95ac1a62162c3c7ac204731e95ebf766d62a46a7bfa238a6acbe923fcb772cb1", size = 11826243, upload-time = "2026-07-23T11:41:37.749Z" },
|
|
695
697
|
]
|
|
696
698
|
|
|
697
699
|
[[package]]
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
from collections.abc import AsyncIterator
|
|
2
|
-
from contextlib import (
|
|
3
|
-
AbstractAsyncContextManager,
|
|
4
|
-
AsyncExitStack,
|
|
5
|
-
asynccontextmanager,
|
|
6
|
-
nullcontext,
|
|
7
|
-
)
|
|
8
|
-
from contextvars import ContextVar
|
|
9
|
-
from dataclasses import dataclass
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from .types import DependencyCache
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class InjectScope:
|
|
17
|
-
dependency_cache: DependencyCache
|
|
18
|
-
func_astack: AsyncExitStack
|
|
19
|
-
request_astack: AsyncExitStack
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
_inject_scope: ContextVar[InjectScope | None] = ContextVar(
|
|
23
|
-
"_inject_scope",
|
|
24
|
-
default=None,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@asynccontextmanager
|
|
29
|
-
async def push_inject_scope(
|
|
30
|
-
dependency_cache: DependencyCache | None = None,
|
|
31
|
-
) -> AsyncIterator[InjectScope]:
|
|
32
|
-
async with AsyncExitStack() as stack:
|
|
33
|
-
scope = InjectScope(
|
|
34
|
-
dependency_cache or {},
|
|
35
|
-
stack,
|
|
36
|
-
stack,
|
|
37
|
-
)
|
|
38
|
-
token = _inject_scope.set(scope)
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
yield scope
|
|
42
|
-
finally:
|
|
43
|
-
_inject_scope.reset(token)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def current_inject_scope() -> InjectScope | None:
|
|
47
|
-
return _inject_scope.get()
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@asynccontextmanager
|
|
51
|
-
async def inside_inject_scope(
|
|
52
|
-
*,
|
|
53
|
-
new_scope: bool = False,
|
|
54
|
-
) -> AsyncIterator[InjectScope]:
|
|
55
|
-
scope = current_inject_scope()
|
|
56
|
-
|
|
57
|
-
_ctx: AbstractAsyncContextManager[Any]
|
|
58
|
-
|
|
59
|
-
if scope is None or new_scope:
|
|
60
|
-
stack = AsyncExitStack()
|
|
61
|
-
scope = await stack.enter_async_context(push_inject_scope())
|
|
62
|
-
|
|
63
|
-
_ctx = stack
|
|
64
|
-
else:
|
|
65
|
-
_ctx = nullcontext()
|
|
66
|
-
|
|
67
|
-
async with _ctx:
|
|
68
|
-
yield scope
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
__all__ = [
|
|
72
|
-
"InjectScope",
|
|
73
|
-
"current_inject_scope",
|
|
74
|
-
"inside_inject_scope",
|
|
75
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|