fastapi 0.114.2__py3-none-any.whl → 0.115.0__py3-none-any.whl

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 might be problematic. Click here for more details.

fastapi/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
2
2
 
3
- __version__ = "0.114.2"
3
+ __version__ = "0.115.0"
4
4
 
5
5
  from starlette import status as status
6
6
 
@@ -201,14 +201,23 @@ def get_flat_dependant(
201
201
  return flat_dependant
202
202
 
203
203
 
204
+ def _get_flat_fields_from_params(fields: List[ModelField]) -> List[ModelField]:
205
+ if not fields:
206
+ return fields
207
+ first_field = fields[0]
208
+ if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel):
209
+ fields_to_extract = get_cached_model_fields(first_field.type_)
210
+ return fields_to_extract
211
+ return fields
212
+
213
+
204
214
  def get_flat_params(dependant: Dependant) -> List[ModelField]:
205
215
  flat_dependant = get_flat_dependant(dependant, skip_repeats=True)
206
- return (
207
- flat_dependant.path_params
208
- + flat_dependant.query_params
209
- + flat_dependant.header_params
210
- + flat_dependant.cookie_params
211
- )
216
+ path_params = _get_flat_fields_from_params(flat_dependant.path_params)
217
+ query_params = _get_flat_fields_from_params(flat_dependant.query_params)
218
+ header_params = _get_flat_fields_from_params(flat_dependant.header_params)
219
+ cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)
220
+ return path_params + query_params + header_params + cookie_params
212
221
 
213
222
 
214
223
  def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
@@ -479,7 +488,15 @@ def analyze_param(
479
488
  field=field
480
489
  ), "Path params must be of one of the supported types"
481
490
  elif isinstance(field_info, params.Query):
482
- assert is_scalar_field(field) or is_scalar_sequence_field(field)
491
+ assert (
492
+ is_scalar_field(field)
493
+ or is_scalar_sequence_field(field)
494
+ or (
495
+ lenient_issubclass(field.type_, BaseModel)
496
+ # For Pydantic v1
497
+ and getattr(field, "shape", 1) == 1
498
+ )
499
+ )
483
500
 
484
501
  return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)
485
502
 
@@ -686,11 +703,14 @@ def _validate_value_with_model_field(
686
703
  return v_, []
687
704
 
688
705
 
689
- def _get_multidict_value(field: ModelField, values: Mapping[str, Any]) -> Any:
706
+ def _get_multidict_value(
707
+ field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None
708
+ ) -> Any:
709
+ alias = alias or field.alias
690
710
  if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)):
691
- value = values.getlist(field.alias)
711
+ value = values.getlist(alias)
692
712
  else:
693
- value = values.get(field.alias, None)
713
+ value = values.get(alias, None)
694
714
  if (
695
715
  value is None
696
716
  or (
@@ -712,7 +732,55 @@ def request_params_to_args(
712
732
  received_params: Union[Mapping[str, Any], QueryParams, Headers],
713
733
  ) -> Tuple[Dict[str, Any], List[Any]]:
714
734
  values: Dict[str, Any] = {}
715
- errors = []
735
+ errors: List[Dict[str, Any]] = []
736
+
737
+ if not fields:
738
+ return values, errors
739
+
740
+ first_field = fields[0]
741
+ fields_to_extract = fields
742
+ single_not_embedded_field = False
743
+ if len(fields) == 1 and lenient_issubclass(first_field.type_, BaseModel):
744
+ fields_to_extract = get_cached_model_fields(first_field.type_)
745
+ single_not_embedded_field = True
746
+
747
+ params_to_process: Dict[str, Any] = {}
748
+
749
+ processed_keys = set()
750
+
751
+ for field in fields_to_extract:
752
+ alias = None
753
+ if isinstance(received_params, Headers):
754
+ # Handle fields extracted from a Pydantic Model for a header, each field
755
+ # doesn't have a FieldInfo of type Header with the default convert_underscores=True
756
+ convert_underscores = getattr(field.field_info, "convert_underscores", True)
757
+ if convert_underscores:
758
+ alias = (
759
+ field.alias
760
+ if field.alias != field.name
761
+ else field.name.replace("_", "-")
762
+ )
763
+ value = _get_multidict_value(field, received_params, alias=alias)
764
+ if value is not None:
765
+ params_to_process[field.name] = value
766
+ processed_keys.add(alias or field.alias)
767
+ processed_keys.add(field.name)
768
+
769
+ for key, value in received_params.items():
770
+ if key not in processed_keys:
771
+ params_to_process[key] = value
772
+
773
+ if single_not_embedded_field:
774
+ field_info = first_field.field_info
775
+ assert isinstance(
776
+ field_info, params.Param
777
+ ), "Params must be subclasses of Param"
778
+ loc: Tuple[str, ...] = (field_info.in_.value,)
779
+ v_, errors_ = _validate_value_with_model_field(
780
+ field=first_field, value=params_to_process, values=values, loc=loc
781
+ )
782
+ return {first_field.name: v_}, errors_
783
+
716
784
  for field in fields:
717
785
  value = _get_multidict_value(field, received_params)
718
786
  field_info = field.field_info
fastapi/openapi/utils.py CHANGED
@@ -16,11 +16,15 @@ from fastapi._compat import (
16
16
  )
17
17
  from fastapi.datastructures import DefaultPlaceholder
18
18
  from fastapi.dependencies.models import Dependant
19
- from fastapi.dependencies.utils import get_flat_dependant, get_flat_params
19
+ from fastapi.dependencies.utils import (
20
+ _get_flat_fields_from_params,
21
+ get_flat_dependant,
22
+ get_flat_params,
23
+ )
20
24
  from fastapi.encoders import jsonable_encoder
21
25
  from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX, REF_TEMPLATE
22
26
  from fastapi.openapi.models import OpenAPI
23
- from fastapi.params import Body, Param
27
+ from fastapi.params import Body, ParamTypes
24
28
  from fastapi.responses import Response
25
29
  from fastapi.types import ModelNameMap
26
30
  from fastapi.utils import (
@@ -87,9 +91,9 @@ def get_openapi_security_definitions(
87
91
  return security_definitions, operation_security
88
92
 
89
93
 
90
- def get_openapi_operation_parameters(
94
+ def _get_openapi_operation_parameters(
91
95
  *,
92
- all_route_params: Sequence[ModelField],
96
+ dependant: Dependant,
93
97
  schema_generator: GenerateJsonSchema,
94
98
  model_name_map: ModelNameMap,
95
99
  field_mapping: Dict[
@@ -98,33 +102,47 @@ def get_openapi_operation_parameters(
98
102
  separate_input_output_schemas: bool = True,
99
103
  ) -> List[Dict[str, Any]]:
100
104
  parameters = []
101
- for param in all_route_params:
102
- field_info = param.field_info
103
- field_info = cast(Param, field_info)
104
- if not field_info.include_in_schema:
105
- continue
106
- param_schema = get_schema_from_model_field(
107
- field=param,
108
- schema_generator=schema_generator,
109
- model_name_map=model_name_map,
110
- field_mapping=field_mapping,
111
- separate_input_output_schemas=separate_input_output_schemas,
112
- )
113
- parameter = {
114
- "name": param.alias,
115
- "in": field_info.in_.value,
116
- "required": param.required,
117
- "schema": param_schema,
118
- }
119
- if field_info.description:
120
- parameter["description"] = field_info.description
121
- if field_info.openapi_examples:
122
- parameter["examples"] = jsonable_encoder(field_info.openapi_examples)
123
- elif field_info.example != Undefined:
124
- parameter["example"] = jsonable_encoder(field_info.example)
125
- if field_info.deprecated:
126
- parameter["deprecated"] = True
127
- parameters.append(parameter)
105
+ flat_dependant = get_flat_dependant(dependant, skip_repeats=True)
106
+ path_params = _get_flat_fields_from_params(flat_dependant.path_params)
107
+ query_params = _get_flat_fields_from_params(flat_dependant.query_params)
108
+ header_params = _get_flat_fields_from_params(flat_dependant.header_params)
109
+ cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params)
110
+ parameter_groups = [
111
+ (ParamTypes.path, path_params),
112
+ (ParamTypes.query, query_params),
113
+ (ParamTypes.header, header_params),
114
+ (ParamTypes.cookie, cookie_params),
115
+ ]
116
+ for param_type, param_group in parameter_groups:
117
+ for param in param_group:
118
+ field_info = param.field_info
119
+ # field_info = cast(Param, field_info)
120
+ if not getattr(field_info, "include_in_schema", True):
121
+ continue
122
+ param_schema = get_schema_from_model_field(
123
+ field=param,
124
+ schema_generator=schema_generator,
125
+ model_name_map=model_name_map,
126
+ field_mapping=field_mapping,
127
+ separate_input_output_schemas=separate_input_output_schemas,
128
+ )
129
+ parameter = {
130
+ "name": param.alias,
131
+ "in": param_type.value,
132
+ "required": param.required,
133
+ "schema": param_schema,
134
+ }
135
+ if field_info.description:
136
+ parameter["description"] = field_info.description
137
+ openapi_examples = getattr(field_info, "openapi_examples", None)
138
+ example = getattr(field_info, "example", None)
139
+ if openapi_examples:
140
+ parameter["examples"] = jsonable_encoder(openapi_examples)
141
+ elif example != Undefined:
142
+ parameter["example"] = jsonable_encoder(example)
143
+ if getattr(field_info, "deprecated", None):
144
+ parameter["deprecated"] = True
145
+ parameters.append(parameter)
128
146
  return parameters
129
147
 
130
148
 
@@ -247,9 +265,8 @@ def get_openapi_path(
247
265
  operation.setdefault("security", []).extend(operation_security)
248
266
  if security_definitions:
249
267
  security_schemes.update(security_definitions)
250
- all_route_params = get_flat_params(route.dependant)
251
- operation_parameters = get_openapi_operation_parameters(
252
- all_route_params=all_route_params,
268
+ operation_parameters = _get_openapi_operation_parameters(
269
+ dependant=route.dependant,
253
270
  schema_generator=schema_generator,
254
271
  model_name_map=model_name_map,
255
272
  field_mapping=field_mapping,
@@ -379,6 +396,7 @@ def get_openapi_path(
379
396
  deep_dict_update(openapi_response, process_response)
380
397
  openapi_response["description"] = description
381
398
  http422 = str(HTTP_422_UNPROCESSABLE_ENTITY)
399
+ all_route_params = get_flat_params(route.dependant)
382
400
  if (all_route_params or route.body_field) and not any(
383
401
  status in operation["responses"]
384
402
  for status in [http422, "4XX", "default"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi
3
- Version: 0.114.2
3
+ Version: 0.115.0
4
4
  Summary: FastAPI framework, high performance, easy to learn, fast to code, ready for production
5
5
  Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
6
6
  Classifier: Intended Audience :: Information Technology
@@ -1,8 +1,8 @@
1
- fastapi-0.114.2.dist-info/METADATA,sha256=MWrFpCnfw9bj9yTHhJZ4sB6nWA9MoxTCbHwK3Zg_AG4,27227
2
- fastapi-0.114.2.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
- fastapi-0.114.2.dist-info/entry_points.txt,sha256=Nn2-rs4A5_lQZko2b9QqCKQx9Irx0agGbxq3QLgjBxQ,46
4
- fastapi-0.114.2.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
5
- fastapi/__init__.py,sha256=2VfxjcXPXxuL2LFjtc1VHbVyjpHDy_Gz3Dm28lsWC7Y,1081
1
+ fastapi-0.115.0.dist-info/METADATA,sha256=ooXztPJg9fxh1kZsyXVFKVLEyPDuMUVqS0N7t6uRi94,27227
2
+ fastapi-0.115.0.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
3
+ fastapi-0.115.0.dist-info/entry_points.txt,sha256=Nn2-rs4A5_lQZko2b9QqCKQx9Irx0agGbxq3QLgjBxQ,46
4
+ fastapi-0.115.0.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086
5
+ fastapi/__init__.py,sha256=xZXaU_wxKQRFq4Cl6laXFZnFy_nuVDvmwxHB43HlgaE,1081
6
6
  fastapi/__main__.py,sha256=bKePXLdO4SsVSM6r9SVoLickJDcR2c0cTOxZRKq26YQ,37
7
7
  fastapi/_compat.py,sha256=N4y7exHYWpWytEwsDU31YHDXoJRvHxREx2qNS3bF85o,23876
8
8
  fastapi/applications.py,sha256=Ix-o9pQAWhEDf9J0Q1hZ0nBB1uP72c-Y3oiYzvrwqiM,176316
@@ -12,7 +12,7 @@ fastapi/concurrency.py,sha256=AYLnS4judDUmXsNRICtoKSP0prfYDcS8ehBtYW9JhQQ,1403
12
12
  fastapi/datastructures.py,sha256=b2PEz77XGq-u3Ur1Inwk0AGjOsQZO49yF9C7IPJ15cY,5766
13
13
  fastapi/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  fastapi/dependencies/models.py,sha256=Pjl6vx-4nZ5Tta9kJa3-RfQKkXtCpS09-FhMgs9eWNs,1507
15
- fastapi/dependencies/utils.py,sha256=Wy64GYBlQAsYmlPQzKRKo6tWFYBGZ6aLFxnB7qZSCnY,32474
15
+ fastapi/dependencies/utils.py,sha256=EnZ_n4CEK3fTqylgr9m3skxmzvFUflleoT1ESCH865k,35172
16
16
  fastapi/encoders.py,sha256=LvwYmFeOz4tVwvgBoC5rvZnbr7hZr73KGrU8O7zSptU,11068
17
17
  fastapi/exception_handlers.py,sha256=MBrIOA-ugjJDivIi4rSsUJBdTsjuzN76q4yh0q1COKw,1332
18
18
  fastapi/exceptions.py,sha256=taNixuFEXb67lI1bnX1ubq8y8TseJ4yoPlWjyP0fTzk,4969
@@ -27,7 +27,7 @@ fastapi/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  fastapi/openapi/constants.py,sha256=adGzmis1L1HJRTE3kJ5fmHS_Noq6tIY6pWv_SFzoFDU,153
28
28
  fastapi/openapi/docs.py,sha256=XcQq-ZbQdC5sI0gIGu5MoHK1q-OFaqws7-ORTo6sjY4,10348
29
29
  fastapi/openapi/models.py,sha256=PqkxQiqcEgjKuhfUIWPZPQcyTcubtUCB3vcObLsB7VE,15397
30
- fastapi/openapi/utils.py,sha256=asSbOKDuagDfpByNQvPy7OM0sqOBdUmqh64BH-n-5f0,22286
30
+ fastapi/openapi/utils.py,sha256=vpbAzWpuNaJL_ocBxt4jp0GUUwrDKNB1anyoAx69fhA,23177
31
31
  fastapi/param_functions.py,sha256=uQzNlihlhM80u4Xbstz__D3L3yxpTqLmsC-Hra1WfqE,64018
32
32
  fastapi/params.py,sha256=XC025dCSObp7fXhOPYo-jwXQRGZ9CwlfNRq2cLh_dRk,28186
33
33
  fastapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -47,4 +47,4 @@ fastapi/testclient.py,sha256=nBvaAmX66YldReJNZXPOk1sfuo2Q6hs8bOvIaCep6LQ,66
47
47
  fastapi/types.py,sha256=nFb36sK3DSoqoyo7Miwy3meKK5UdFBgkAgLSzQlUVyI,383
48
48
  fastapi/utils.py,sha256=y8Bj5ttMaI9tS4D60OUgXqKnktBr99NdYUnHHV9LgoY,7948
49
49
  fastapi/websockets.py,sha256=419uncYObEKZG0YcrXscfQQYLSWoE10jqxVMetGdR98,222
50
- fastapi-0.114.2.dist-info/RECORD,,
50
+ fastapi-0.115.0.dist-info/RECORD,,