fastapi-extra 0.3.3__tar.gz → 0.3.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_extra-0.3.3 → fastapi_extra-0.3.4}/PKG-INFO +1 -1
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/__init__.py +2 -1
- fastapi_extra-0.3.4/fastapi_extra/_patch.py +121 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra.egg-info/PKG-INFO +1 -1
- fastapi_extra-0.3.3/fastapi_extra/_patch.py +0 -34
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/LICENSE +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/README.rst +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/cache/__init__.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/cache/redis.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/cursor.pyi +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/database/__init__.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/database/model.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/database/service.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/database/session.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/dependency.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/form.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/native/cursor.pyx +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/native/routing.pyx +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/native/urlparse.pyx +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/py.typed +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/response.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/routing.pyi +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/settings.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/types.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/urlparse.pyi +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra/utils.py +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra.egg-info/SOURCES.txt +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra.egg-info/dependency_links.txt +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra.egg-info/requires.txt +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/fastapi_extra.egg-info/top_level.txt +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/pyproject.toml +0 -0
- {fastapi_extra-0.3.3 → fastapi_extra-0.3.4}/setup.cfg +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "0.3.
|
|
1
|
+
__version__ = "0.3.4"
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
from fastapi import FastAPI
|
|
@@ -12,6 +12,7 @@ def setup(app: FastAPI) -> None:
|
|
|
12
12
|
|
|
13
13
|
_patch.install_routes(app)
|
|
14
14
|
origin_routing.solve_dependencies.__globals__['field_annotation_is_sequence'] = _patch.is_sequence_field # type: ignore
|
|
15
|
+
origin_routing.solve_dependencies.__globals__['request_params_to_args'] = _patch.request_params_to_args # type: ignore
|
|
15
16
|
origin_utils.QueryParams.__init__ = _patch.query_params_init # type: ignore
|
|
16
17
|
except ImportError: # pragma: nocover
|
|
17
18
|
pass
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
__author__ = "ziyan.yin"
|
|
2
|
+
__date__ = "2026-01-13"
|
|
3
|
+
|
|
4
|
+
import functools
|
|
5
|
+
from typing import Any, Mapping, Sequence, Union
|
|
6
|
+
|
|
7
|
+
from fastapi import FastAPI, params
|
|
8
|
+
from fastapi._compat import (ModelField, get_cached_model_fields,
|
|
9
|
+
lenient_issubclass, shared)
|
|
10
|
+
from fastapi.dependencies import utils
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from starlette import datastructures
|
|
13
|
+
|
|
14
|
+
from fastapi_extra import routing
|
|
15
|
+
from fastapi_extra.urlparse import parse_qsl
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def install_routes(app: FastAPI) -> None:
|
|
19
|
+
routing.install(app)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@functools.lru_cache
|
|
23
|
+
def is_sequence_field(annotation: type[Any]) -> bool:
|
|
24
|
+
return shared.field_annotation_is_sequence(annotation)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def request_params_to_args(
|
|
28
|
+
fields: Sequence[ModelField],
|
|
29
|
+
received_params: Union[Mapping[str, Any], datastructures.QueryParams, datastructures.Headers],
|
|
30
|
+
) -> tuple[dict[str, Any], list[Any]]:
|
|
31
|
+
values: dict[str, Any] = {}
|
|
32
|
+
errors: list[dict[str, Any]] = []
|
|
33
|
+
|
|
34
|
+
if not fields:
|
|
35
|
+
return values, errors
|
|
36
|
+
|
|
37
|
+
first_field = fields[0]
|
|
38
|
+
fields_to_extract = fields
|
|
39
|
+
single_not_embedded_field = False
|
|
40
|
+
default_convert_underscores = True
|
|
41
|
+
if len(fields) == 1 and lenient_issubclass(
|
|
42
|
+
first_field.field_info.annotation, BaseModel
|
|
43
|
+
):
|
|
44
|
+
fields_to_extract = get_cached_model_fields(first_field.field_info.annotation)
|
|
45
|
+
single_not_embedded_field = True
|
|
46
|
+
# If headers are in a Pydantic model, the way to disable convert_underscores
|
|
47
|
+
# would be with Header(convert_underscores=False) at the Pydantic model level
|
|
48
|
+
default_convert_underscores = getattr(
|
|
49
|
+
first_field.field_info, "convert_underscores", True
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
params_to_process: dict[str, Any] = {}
|
|
53
|
+
|
|
54
|
+
processed_keys = set()
|
|
55
|
+
|
|
56
|
+
for field in fields_to_extract:
|
|
57
|
+
alias = field_alias = utils.get_validation_alias(field)
|
|
58
|
+
if isinstance(received_params, datastructures.Headers):
|
|
59
|
+
# Handle fields extracted from a Pydantic Model for a header, each field
|
|
60
|
+
# doesn't have a FieldInfo of type Header with the default convert_underscores=True
|
|
61
|
+
convert_underscores = getattr(
|
|
62
|
+
field.field_info, "convert_underscores", default_convert_underscores
|
|
63
|
+
)
|
|
64
|
+
if convert_underscores and alias == field.name:
|
|
65
|
+
alias = alias.replace("_", "-")
|
|
66
|
+
value = utils._get_multidict_value(field, received_params, alias=alias)
|
|
67
|
+
if value is not None:
|
|
68
|
+
params_to_process[field_alias] = value
|
|
69
|
+
processed_keys.add(alias)
|
|
70
|
+
|
|
71
|
+
for key in received_params.keys():
|
|
72
|
+
if key not in processed_keys:
|
|
73
|
+
if hasattr(received_params, "getlist"):
|
|
74
|
+
value = received_params.getlist(key)
|
|
75
|
+
if isinstance(value, list) and (len(value) == 1):
|
|
76
|
+
params_to_process[key] = value[0]
|
|
77
|
+
else:
|
|
78
|
+
params_to_process[key] = value
|
|
79
|
+
else:
|
|
80
|
+
params_to_process[key] = received_params.get(key)
|
|
81
|
+
|
|
82
|
+
if single_not_embedded_field:
|
|
83
|
+
field_info = first_field.field_info
|
|
84
|
+
assert isinstance(field_info, params.Param), (
|
|
85
|
+
"Params must be subclasses of Param"
|
|
86
|
+
)
|
|
87
|
+
loc: tuple[str, ...] = (field_info.in_.value,)
|
|
88
|
+
v_, errors_ = utils._validate_value_with_model_field(
|
|
89
|
+
field=first_field, value=params_to_process, values=values, loc=loc
|
|
90
|
+
)
|
|
91
|
+
return {first_field.name: v_}, errors_
|
|
92
|
+
|
|
93
|
+
for field in fields:
|
|
94
|
+
field_alias = utils.get_validation_alias(field)
|
|
95
|
+
value = params_to_process.get(field_alias, None)
|
|
96
|
+
field_info = field.field_info
|
|
97
|
+
assert isinstance(field_info, params.Param), (
|
|
98
|
+
"Params must be subclasses of Param"
|
|
99
|
+
)
|
|
100
|
+
loc = (field_info.in_.value, field_alias)
|
|
101
|
+
v_, errors_ = utils._validate_value_with_model_field(
|
|
102
|
+
field=field, value=value, values=values, loc=loc
|
|
103
|
+
)
|
|
104
|
+
if errors_:
|
|
105
|
+
errors.extend(errors_)
|
|
106
|
+
else:
|
|
107
|
+
values[field.name] = v_
|
|
108
|
+
return values, errors
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def query_params_init(obj: datastructures.QueryParams, *args, **kwargs) -> None:
|
|
112
|
+
value = args[0] if args else []
|
|
113
|
+
|
|
114
|
+
if isinstance(value, bytes):
|
|
115
|
+
super(datastructures.QueryParams, obj).__init__(parse_qsl(value, keep_blank_values=True), **kwargs)
|
|
116
|
+
elif isinstance(value, str):
|
|
117
|
+
super(datastructures.QueryParams, obj).__init__(parse_qsl(value.encode("latin-1"), keep_blank_values=True), **kwargs)
|
|
118
|
+
else:
|
|
119
|
+
super(datastructures.QueryParams, obj).__init__(*args, **kwargs) # type: ignore[arg-type]
|
|
120
|
+
obj._list = [(str(k), str(v)) for k, v in obj._list]
|
|
121
|
+
obj._dict = {str(k): str(v) for k, v in obj._dict.items()}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
__author__ = "ziyan.yin"
|
|
2
|
-
__date__ = "2026-01-13"
|
|
3
|
-
|
|
4
|
-
import functools
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from fastapi import FastAPI
|
|
8
|
-
from fastapi._compat import shared
|
|
9
|
-
from starlette import datastructures
|
|
10
|
-
|
|
11
|
-
from fastapi_extra import routing
|
|
12
|
-
from fastapi_extra.urlparse import parse_qsl
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def install_routes(app: FastAPI) -> None:
|
|
16
|
-
routing.install(app)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@functools.lru_cache
|
|
20
|
-
def is_sequence_field(annotation: type[Any]) -> bool:
|
|
21
|
-
return shared.field_annotation_is_sequence(annotation)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def query_params_init(obj: datastructures.QueryParams, *args, **kwargs) -> None:
|
|
25
|
-
value = args[0] if args else []
|
|
26
|
-
|
|
27
|
-
if isinstance(value, bytes):
|
|
28
|
-
super(datastructures.QueryParams, obj).__init__(parse_qsl(value, keep_blank_values=True), **kwargs)
|
|
29
|
-
elif isinstance(value, str):
|
|
30
|
-
super(datastructures.QueryParams, obj).__init__(parse_qsl(value.encode("latin-1"), keep_blank_values=True), **kwargs)
|
|
31
|
-
else:
|
|
32
|
-
super(datastructures.QueryParams, obj).__init__(*args, **kwargs) # type: ignore[arg-type]
|
|
33
|
-
obj._list = [(str(k), str(v)) for k, v in obj._list]
|
|
34
|
-
obj._dict = {str(k): str(v) for k, v in obj._dict.items()}
|
|
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
|