fastapi-injected 0.3.1__tar.gz → 0.3.3__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.
Potentially problematic release.
This version of fastapi-injected might be problematic. Click here for more details.
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/PKG-INFO +2 -14
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/README.md +1 -13
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/__init__.py +0 -3
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/_bind.py +9 -8
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/_given.py +2 -1
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/deps.py +1 -2
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/types.py +1 -2
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/pyproject.toml +1 -1
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_bind.py +37 -3
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/uv.lock +1 -1
- fastapi_injected-0.3.1/fastapi_injected/_params.py +0 -19
- fastapi_injected-0.3.1/tests/_typing/params.py +0 -30
- fastapi_injected-0.3.1/tests/test_params.py +0 -78
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/.github/dependabot.yml +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/.github/workflows/automerge.yml +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/.github/workflows/lint.yml +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/.github/workflows/publish.yml +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/.github/workflows/test.yml +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/.gitignore +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/.pre-commit-config.yaml +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/LICENSE +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/_cache.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/_dataclass.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/_deps_tp.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/_fastapi_lifecycle.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/_injected.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/_overrides.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/_rlock.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/inject.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/overrides.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/resolve.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/scope.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/fastapi_injected/sign.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/__init__.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/__init__.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/annotations.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/arg.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/bind.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/dataclass.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/deps.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/errors.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/given.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/inject.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/injected.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/integration.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/markers.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/overrides.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/resolve.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/_typing/scope.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/deps.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/ext/__init__.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/ext/test_pydantic_ai.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_cache.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_concurrency.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_dataclass.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_dependant_cache.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_errors.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_fastapi.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_given.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_inject.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_injected.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_overrides.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_resolve.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_rlock.py +0 -0
- {fastapi_injected-0.3.1 → fastapi_injected-0.3.3}/tests/test_scope.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fastapi-injected
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
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>
|
|
@@ -253,18 +253,6 @@ class Config(MakeDataclass, frozen=True, kw_only=True):
|
|
|
253
253
|
retries: int = 3
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
-
The same is true of what holds a dependency. FastAPI's `Depends` and `Security` are frozen dataclasses, so they hash by field — and `Security(get_user, scopes=["items:read"])`, the documented spelling, holds a list, which makes the whole annotation unhashable and fails as a bare `TypeError` from `typing`. `fastapi_injected` ships both as `MakeDataclass` subclasses, which is the same class in every way that matters to FastAPI, and one that can be cached:
|
|
257
|
-
|
|
258
|
-
```python
|
|
259
|
-
from fastapi_injected import Security, resolve
|
|
260
|
-
|
|
261
|
-
type CurrentUser = Annotated[User, Security(get_user, scopes=["items:read"])]
|
|
262
|
-
|
|
263
|
-
await resolve(CurrentUser)
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
Scopes are kept as a tuple, so the same scopes written as another list stay one dependency. Everything the package builds internally is held this way too, so a dependency object that cannot be hashed by fields no longer decides whether the annotation around it can be.
|
|
267
|
-
|
|
268
256
|
### When resolution fails
|
|
269
257
|
|
|
270
258
|
A dependency can fail to resolve for the same reasons it would in a route — a missing header, a query parameter that does not validate. `DependencyResolutionError` carries those errors in the shape pydantic produced them, and turns into the response FastAPI would have returned:
|
|
@@ -369,7 +357,7 @@ Everything the package supports is importable from `fastapi_injected` itself, an
|
|
|
369
357
|
|
|
370
358
|
| | |
|
|
371
359
|
| --- | --- |
|
|
372
|
-
| Markers | `Dep`, `DepFactory`, `DepOf`, `Arg`, `Given`, `Injected
|
|
360
|
+
| Markers | `Dep`, `DepFactory`, `DepOf`, `Arg`, `Given`, `Injected` |
|
|
373
361
|
| Resolving | `inject`, `resolve`, `bind_deps`, `signature_with_deps`, `remap_dep_args`, `clear_dependant_cache` |
|
|
374
362
|
| Scopes and overrides | `InjectScope`, `push_inject_scope`, `inside_inject_scope`, `push_overrides`, `Overrides`, `OverridesProvider`, `ValueOverride`, `FactoryOverride` |
|
|
375
363
|
| Building on top | `MakeDataclass`, `MakeInjected`, `HasDependsHook`, `ArgMarker`, `is_arg`, `is_dep`, `unwrap_dep_tp`, `unwrap_dep_dependency` |
|
|
@@ -232,18 +232,6 @@ class Config(MakeDataclass, frozen=True, kw_only=True):
|
|
|
232
232
|
retries: int = 3
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
The same is true of what holds a dependency. FastAPI's `Depends` and `Security` are frozen dataclasses, so they hash by field — and `Security(get_user, scopes=["items:read"])`, the documented spelling, holds a list, which makes the whole annotation unhashable and fails as a bare `TypeError` from `typing`. `fastapi_injected` ships both as `MakeDataclass` subclasses, which is the same class in every way that matters to FastAPI, and one that can be cached:
|
|
236
|
-
|
|
237
|
-
```python
|
|
238
|
-
from fastapi_injected import Security, resolve
|
|
239
|
-
|
|
240
|
-
type CurrentUser = Annotated[User, Security(get_user, scopes=["items:read"])]
|
|
241
|
-
|
|
242
|
-
await resolve(CurrentUser)
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Scopes are kept as a tuple, so the same scopes written as another list stay one dependency. Everything the package builds internally is held this way too, so a dependency object that cannot be hashed by fields no longer decides whether the annotation around it can be.
|
|
246
|
-
|
|
247
235
|
### When resolution fails
|
|
248
236
|
|
|
249
237
|
A dependency can fail to resolve for the same reasons it would in a route — a missing header, a query parameter that does not validate. `DependencyResolutionError` carries those errors in the shape pydantic produced them, and turns into the response FastAPI would have returned:
|
|
@@ -348,7 +336,7 @@ Everything the package supports is importable from `fastapi_injected` itself, an
|
|
|
348
336
|
|
|
349
337
|
| | |
|
|
350
338
|
| --- | --- |
|
|
351
|
-
| Markers | `Dep`, `DepFactory`, `DepOf`, `Arg`, `Given`, `Injected
|
|
339
|
+
| Markers | `Dep`, `DepFactory`, `DepOf`, `Arg`, `Given`, `Injected` |
|
|
352
340
|
| Resolving | `inject`, `resolve`, `bind_deps`, `signature_with_deps`, `remap_dep_args`, `clear_dependant_cache` |
|
|
353
341
|
| Scopes and overrides | `InjectScope`, `push_inject_scope`, `inside_inject_scope`, `push_overrides`, `Overrides`, `OverridesProvider`, `ValueOverride`, `FactoryOverride` |
|
|
354
342
|
| Building on top | `MakeDataclass`, `MakeInjected`, `HasDependsHook`, `ArgMarker`, `is_arg`, `is_dep`, `unwrap_dep_tp`, `unwrap_dep_dependency` |
|
|
@@ -4,7 +4,6 @@ from ._deps_tp import is_arg, is_dep, unwrap_dep_dependency, unwrap_dep_tp
|
|
|
4
4
|
from ._fastapi_lifecycle import MissingDependencyCacheError, add_injected_scope, init_inject_scope
|
|
5
5
|
from ._given import Given
|
|
6
6
|
from ._injected import MakeInjected
|
|
7
|
-
from ._params import Depends, Security
|
|
8
7
|
from .deps import (
|
|
9
8
|
DependencyResolutionError,
|
|
10
9
|
HasDependsHook,
|
|
@@ -25,7 +24,6 @@ __all__ = [
|
|
|
25
24
|
"DepFactory",
|
|
26
25
|
"DepOf",
|
|
27
26
|
"DependencyResolutionError",
|
|
28
|
-
"Depends",
|
|
29
27
|
"FactoryOverride",
|
|
30
28
|
"Given",
|
|
31
29
|
"HasDependsHook",
|
|
@@ -38,7 +36,6 @@ __all__ = [
|
|
|
38
36
|
"NotADependencyError",
|
|
39
37
|
"Overrides",
|
|
40
38
|
"OverridesProvider",
|
|
41
|
-
"Security",
|
|
42
39
|
"UnboundDepArgsError",
|
|
43
40
|
"UnboundScopeError",
|
|
44
41
|
"ValueOverride",
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from collections.abc import Callable, Sequence
|
|
3
3
|
from functools import wraps
|
|
4
|
-
from typing import Annotated, Any, Concatenate, overload
|
|
4
|
+
from typing import Annotated, Any, Concatenate, get_origin, overload
|
|
5
5
|
|
|
6
|
-
from fastapi import params
|
|
7
6
|
from fastapi.dependencies.utils import get_typed_signature
|
|
7
|
+
from fastapi.params import Depends
|
|
8
8
|
|
|
9
|
-
from ._deps_tp import
|
|
10
|
-
from ._params import Depends
|
|
9
|
+
from ._deps_tp import unwrap_tp
|
|
11
10
|
from .sign import update_func_sign
|
|
12
11
|
from .types import AsyncFunc, DepOf, Func
|
|
13
12
|
|
|
@@ -34,13 +33,15 @@ def _dep_arg_index(name: str, /) -> int | None:
|
|
|
34
33
|
return int(index) if index.isdigit() else None
|
|
35
34
|
|
|
36
35
|
|
|
36
|
+
def _is_annotation(tp: Any, /) -> bool:
|
|
37
|
+
return get_origin(unwrap_tp(tp)) is Annotated
|
|
38
|
+
|
|
39
|
+
|
|
37
40
|
def _dep_annotation(dep: DepOf[Any] | Callable[..., Any], /) -> Any:
|
|
38
|
-
if isinstance(dep,
|
|
41
|
+
if isinstance(dep, Depends):
|
|
39
42
|
return Annotated[Any, dep]
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
# `resolve` takes one - anything else is left as it was written
|
|
43
|
-
if is_dep(dep) or not callable(dep):
|
|
44
|
+
if _is_annotation(dep) or not callable(dep):
|
|
44
45
|
return dep
|
|
45
46
|
|
|
46
47
|
return Annotated[Any, Depends(dep)]
|
|
@@ -4,13 +4,12 @@ from contextlib import AsyncExitStack
|
|
|
4
4
|
from functools import lru_cache, wraps
|
|
5
5
|
from typing import Annotated, Any, Literal, Protocol, cast, overload, runtime_checkable
|
|
6
6
|
|
|
7
|
-
from fastapi import params
|
|
7
|
+
from fastapi import Depends, params
|
|
8
8
|
from fastapi.dependencies.models import Dependant
|
|
9
9
|
from fastapi.dependencies.utils import get_dependant, get_typed_signature, solve_dependencies
|
|
10
10
|
from fastapi.exceptions import RequestValidationError
|
|
11
11
|
|
|
12
12
|
from ._deps_tp import is_dep, unwrap_tp
|
|
13
|
-
from ._params import Depends
|
|
14
13
|
from .scope import InjectScope
|
|
15
14
|
from .sign import prepare_sign, update_func_sign
|
|
16
15
|
from .types import Coro, DependencyCache, HasSignature
|
|
@@ -2,11 +2,10 @@ import inspect
|
|
|
2
2
|
from collections.abc import AsyncIterator, Callable, Coroutine, Iterator, Mapping
|
|
3
3
|
from typing import TYPE_CHECKING, Annotated, Any, Protocol, TypeVar, runtime_checkable
|
|
4
4
|
|
|
5
|
+
from fastapi import Depends
|
|
5
6
|
from fastapi.types import DependencyCacheKey
|
|
6
7
|
from typing_extensions import TypeForm, sentinel
|
|
7
8
|
|
|
8
|
-
from ._params import Depends
|
|
9
|
-
|
|
10
9
|
if TYPE_CHECKING:
|
|
11
10
|
Injected: Any = object()
|
|
12
11
|
else:
|
|
@@ -2,9 +2,19 @@ from dataclasses import dataclass
|
|
|
2
2
|
from typing import Annotated, Any
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
-
from fastapi import Depends
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
from fastapi import Depends, FastAPI, Header
|
|
6
|
+
from fastapi.testclient import TestClient
|
|
7
|
+
|
|
8
|
+
from fastapi_injected import (
|
|
9
|
+
Given,
|
|
10
|
+
bind_deps,
|
|
11
|
+
init_inject_scope,
|
|
12
|
+
inject,
|
|
13
|
+
push_inject_scope,
|
|
14
|
+
push_overrides,
|
|
15
|
+
resolve,
|
|
16
|
+
signature_with_deps,
|
|
17
|
+
)
|
|
8
18
|
from fastapi_injected._bind import UnboundDepArgsError, dep_arg_name, remap_dep_args, take_dep_args
|
|
9
19
|
|
|
10
20
|
pytestmark = pytest.mark.asyncio
|
|
@@ -87,6 +97,30 @@ async def test_a_dependency_can_be_bound_however_it_is_written(dep):
|
|
|
87
97
|
assert await resolve(bind_deps(describe, Given(Doc()), dep)) == "readme:admin"
|
|
88
98
|
|
|
89
99
|
|
|
100
|
+
TitleHeader = Annotated[str, Header(alias="x-title")]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def titled(title: str, role: Role) -> str:
|
|
104
|
+
return f"{title}:{role.name}"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def test_a_request_marker_is_bound_as_written() -> None:
|
|
108
|
+
sign = signature_with_deps(titled, TitleHeader, RoleDep)
|
|
109
|
+
|
|
110
|
+
assert next(iter(sign.parameters.values())).annotation == TitleHeader
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def test_a_request_marker_resolves_from_the_request() -> None:
|
|
114
|
+
app = FastAPI(dependencies=[Depends(init_inject_scope)])
|
|
115
|
+
|
|
116
|
+
@app.get("/marker")
|
|
117
|
+
async def _route(described: Annotated[str, Depends(bind_deps(titled, TitleHeader, RoleDep))]) -> str:
|
|
118
|
+
return described
|
|
119
|
+
|
|
120
|
+
with TestClient(app) as client:
|
|
121
|
+
assert client.get("/marker", headers={"x-title": "readme"}).json() == "readme:admin"
|
|
122
|
+
|
|
123
|
+
|
|
90
124
|
async def test_signature_with_deps_names_the_bound_parameters() -> None:
|
|
91
125
|
sign = signature_with_deps(describe, Given(Doc()), RoleDep)
|
|
92
126
|
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from fastapi import params
|
|
2
|
-
|
|
3
|
-
from ._dataclass import MakeDataclass
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Depends(MakeDataclass, params.Depends, frozen=True):
|
|
7
|
-
pass
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Security(Depends, params.Security, frozen=True):
|
|
11
|
-
def __post_init__(self) -> None:
|
|
12
|
-
if self.scopes is not None:
|
|
13
|
-
object.__setattr__(self, "scopes", tuple(self.scopes))
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
__all__ = [
|
|
17
|
-
"Depends",
|
|
18
|
-
"Security",
|
|
19
|
-
]
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
|
|
3
|
-
from fastapi import params
|
|
4
|
-
|
|
5
|
-
from fastapi_injected import Depends, Security
|
|
6
|
-
|
|
7
|
-
from .deps import (
|
|
8
|
-
ContextState,
|
|
9
|
-
ctx_dep,
|
|
10
|
-
is_assignable_to,
|
|
11
|
-
is_subtype_of,
|
|
12
|
-
static_assert,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
# they are the fastapi params they wrap, so anything taking one takes these
|
|
16
|
-
static_assert(is_subtype_of(Depends, params.Depends))
|
|
17
|
-
static_assert(is_subtype_of(Security, params.Security))
|
|
18
|
-
static_assert(is_subtype_of(Security, Depends))
|
|
19
|
-
static_assert(is_assignable_to(Annotated[ContextState, Security(ctx_dep, scopes=["me"])], ContextState))
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _params() -> None:
|
|
23
|
-
Depends()
|
|
24
|
-
Depends(ctx_dep, use_cache=False)
|
|
25
|
-
Security(ctx_dep, scopes=["me"], use_cache=False)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _negatives() -> None:
|
|
29
|
-
Depends(use_cache="yes") # type: ignore[ty:invalid-argument-type]
|
|
30
|
-
Security(ctx_dep, scopes=1) # type: ignore[ty:invalid-argument-type]
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
from typing import Annotated
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from fastapi import params
|
|
5
|
-
from fastapi.security import SecurityScopes
|
|
6
|
-
|
|
7
|
-
from fastapi_injected import Depends, Security, is_dep, push_inject_scope, resolve
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
async def scopes_dep(scopes: SecurityScopes) -> list[str]:
|
|
11
|
-
return scopes.scopes
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Unhashable:
|
|
15
|
-
__hash__ = None # type: ignore[ty:invalid-assignment]
|
|
16
|
-
|
|
17
|
-
async def __call__(self) -> str:
|
|
18
|
-
return "value"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def test_they_are_fastapi_params():
|
|
22
|
-
assert isinstance(Depends(scopes_dep), params.Depends)
|
|
23
|
-
assert isinstance(Security(scopes_dep), params.Security)
|
|
24
|
-
assert isinstance(Security(scopes_dep), params.Depends)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def test_scopes_are_written_as_documented():
|
|
28
|
-
security = Security(scopes_dep, scopes=["items:read"])
|
|
29
|
-
|
|
30
|
-
assert security.scopes == ("items:read",)
|
|
31
|
-
assert is_dep(Annotated[list[str], security])
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def test_equal_params_hash_equally():
|
|
35
|
-
assert hash(Depends(scopes_dep)) == hash(Depends(scopes_dep))
|
|
36
|
-
assert Security(scopes_dep, scopes=["me"]) == Security(scopes_dep, scopes=["me"])
|
|
37
|
-
assert hash(Security(scopes_dep, scopes=["me"])) == hash(Security(scopes_dep, scopes=("me",)))
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def test_a_scoped_annotation_can_be_hashed():
|
|
41
|
-
# the annotation is the key a dependant is cached by, and a list of scopes used to break it
|
|
42
|
-
assert hash(Annotated[list[str], Security(scopes_dep, scopes=["items:read", "me"])])
|
|
43
|
-
|
|
44
|
-
with pytest.raises(TypeError, match="unhashable type: 'list'"):
|
|
45
|
-
hash(Annotated[list[str], params.Security(scopes_dep, scopes=["items:read", "me"])])
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def test_unhashable_dependencies_fall_back_to_identity():
|
|
49
|
-
depends = Depends(Unhashable())
|
|
50
|
-
|
|
51
|
-
assert hash(depends) == object.__hash__(depends)
|
|
52
|
-
assert hash(Annotated[str, depends])
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@pytest.mark.asyncio
|
|
56
|
-
async def test_a_scoped_dependency_resolves():
|
|
57
|
-
scoped = Annotated[list[str], Security(scopes_dep, scopes=["items:read", "me"])]
|
|
58
|
-
|
|
59
|
-
assert await resolve(scoped) == ["items:read", "me"]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@pytest.mark.asyncio
|
|
63
|
-
async def test_equal_params_are_one_dependency():
|
|
64
|
-
calls = 0
|
|
65
|
-
|
|
66
|
-
async def counted() -> int:
|
|
67
|
-
nonlocal calls
|
|
68
|
-
calls += 1
|
|
69
|
-
return calls
|
|
70
|
-
|
|
71
|
-
async with push_inject_scope():
|
|
72
|
-
assert await resolve(Annotated[int, Security(counted, scopes=["me"])]) == 1
|
|
73
|
-
# the same scopes written as another list are the same dependency, so it is cached
|
|
74
|
-
assert await resolve(Annotated[int, Security(counted, scopes=["me"])]) == 1
|
|
75
|
-
# different scopes are not
|
|
76
|
-
assert await resolve(Annotated[int, Security(counted, scopes=["other"])]) == 2
|
|
77
|
-
|
|
78
|
-
assert calls == 2
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|