Browse Source

right directions maybe

pull/9837/head
JONEMI21 8 months ago
parent
commit
62344d272d
  1. 3
      docs_src/query_params/tutorial007_py310.py
  2. 5
      fastapi/_compat/__init__.py
  3. 1
      fastapi/_compat/main.py
  4. 3
      fastapi/_compat/shared.py
  5. 1
      fastapi/_compat/v1.py
  6. 22
      fastapi/_compat/v2.py
  7. 14
      fastapi/dependencies/utils.py
  8. 1
      fastapi/params.py
  9. 2
      tests/test_tutorial/test_query_params/test_tutorial007_py310.py

3
docs_src/query_params/tutorial007_py310.py

@ -1,6 +1,7 @@
from typing import Annotated, Dict, List, Union from typing import Annotated, Dict, List, Union
from fastapi import FastAPI, Query from fastapi import FastAPI, Query
from pydantic import OnErrorOmit
app = FastAPI() app = FastAPI()
@ -9,7 +10,7 @@ app = FastAPI()
def get_mixed_mapping_mixed_type_query_params( def get_mixed_mapping_mixed_type_query_params(
query: Annotated[int, Query()] = None, query: Annotated[int, Query()] = None,
mapping_query_str_or_int: Annotated[ mapping_query_str_or_int: Annotated[
Union[Dict[str, str], Dict[str, int]], Query() Union[Dict[str, OnErrorOmit[str]], Dict[str, int]], Query()
] = None, ] = None,
mapping_query_int: Annotated[Dict[str, int], Query()] = None, mapping_query_int: Annotated[Dict[str, int], Query()] = None,
sequence_mapping_int: Annotated[Dict[str, List[int]], Query()] = None, sequence_mapping_int: Annotated[Dict[str, List[int]], Query()] = None,

5
fastapi/_compat/__init__.py

@ -21,7 +21,6 @@ from .main import get_compat_model_name_map as get_compat_model_name_map
from .main import get_definitions as get_definitions from .main import get_definitions as get_definitions
from .main import get_missing_field_error as get_missing_field_error from .main import get_missing_field_error as get_missing_field_error
from .main import get_schema_from_model_field as get_schema_from_model_field from .main import get_schema_from_model_field as get_schema_from_model_field
from .main import ignore_invalid as ignore_invalid
from .main import is_bytes_field as is_bytes_field from .main import is_bytes_field as is_bytes_field
from .main import is_bytes_sequence_field as is_bytes_sequence_field from .main import is_bytes_sequence_field as is_bytes_sequence_field
from .main import is_scalar_field as is_scalar_field from .main import is_scalar_field as is_scalar_field
@ -29,6 +28,7 @@ from .main import is_scalar_mapping_field as is_scalar_mapping_field
from .main import is_scalar_sequence_field as is_scalar_sequence_field from .main import is_scalar_sequence_field as is_scalar_sequence_field
from .main import is_scalar_sequence_mapping_field as is_scalar_sequence_mapping_field from .main import is_scalar_sequence_mapping_field as is_scalar_sequence_mapping_field
from .main import is_sequence_field as is_sequence_field from .main import is_sequence_field as is_sequence_field
from .main import omit_by_default as omit_by_default
from .main import serialize_sequence_value as serialize_sequence_value from .main import serialize_sequence_value as serialize_sequence_value
from .main import ( from .main import (
with_info_plain_validator_function as with_info_plain_validator_function, with_info_plain_validator_function as with_info_plain_validator_function,
@ -42,6 +42,9 @@ from .shared import PYDANTIC_V2 as PYDANTIC_V2
from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE
from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1 from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1
from .shared import field_annotation_is_scalar as field_annotation_is_scalar from .shared import field_annotation_is_scalar as field_annotation_is_scalar
from .shared import (
field_annotation_is_scalar_sequence_mapping as field_annotation_is_scalar_sequence_mapping,
)
from .shared import ( from .shared import (
is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation, is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation,
) )

1
fastapi/_compat/main.py

@ -28,6 +28,7 @@ if PYDANTIC_V2:
from .v2 import Validator as Validator from .v2 import Validator as Validator
from .v2 import evaluate_forwardref as evaluate_forwardref from .v2 import evaluate_forwardref as evaluate_forwardref
from .v2 import get_missing_field_error as get_missing_field_error from .v2 import get_missing_field_error as get_missing_field_error
from .v2 import omit_by_default as omit_by_default
from .v2 import ( from .v2 import (
with_info_plain_validator_function as with_info_plain_validator_function, with_info_plain_validator_function as with_info_plain_validator_function,
) )

3
fastapi/_compat/shared.py

@ -5,7 +5,6 @@ from collections import deque
from dataclasses import is_dataclass from dataclasses import is_dataclass
from typing import ( from typing import (
Any, Any,
Callable,
Deque, Deque,
FrozenSet, FrozenSet,
List, List,
@ -19,7 +18,7 @@ from typing import (
from fastapi._compat import may_v1 from fastapi._compat import may_v1
from fastapi.types import UnionType from fastapi.types import UnionType
from pydantic import BaseModel, ValidationError from pydantic import BaseModel
from pydantic.version import VERSION as PYDANTIC_VERSION from pydantic.version import VERSION as PYDANTIC_VERSION
from starlette.datastructures import UploadFile from starlette.datastructures import UploadFile
from typing_extensions import Annotated, get_args, get_origin from typing_extensions import Annotated, get_args, get_origin

1
fastapi/_compat/v1.py

@ -348,4 +348,3 @@ def create_body_model(
def get_model_fields(model: Type[BaseModel]) -> List[ModelField]: def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
return list(model.__fields__.values()) # type: ignore[attr-defined] return list(model.__fields__.values()) # type: ignore[attr-defined]

22
fastapi/_compat/v2.py

@ -17,8 +17,8 @@ from typing import (
from fastapi._compat import may_v1, shared from fastapi._compat import may_v1, shared
from fastapi.openapi.constants import REF_TEMPLATE from fastapi.openapi.constants import REF_TEMPLATE
from fastapi.types import IncEx, ModelNameMap from fastapi.types import IncEx, ModelNameMap, UnionType
from pydantic import BaseModel, TypeAdapter, WrapValidator, create_model from pydantic import BaseModel, OnErrorOmit, TypeAdapter, create_model
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation
from pydantic import ValidationError as ValidationError from pydantic import ValidationError as ValidationError
@ -488,3 +488,21 @@ def get_flat_models_from_fields(
def get_long_model_name(model: TypeModelOrEnum) -> str: def get_long_model_name(model: TypeModelOrEnum) -> str:
return f"{model.__module__}__{model.__qualname__}".replace(".", "__") return f"{model.__module__}__{model.__qualname__}".replace(".", "__")
def omit_by_default(annotation: Any) -> Any:
# Update the annotation to use OnErrorOmit for the inner type(s)
origin = get_origin(annotation)
if origin is Union or origin is UnionType:
new_args = []
for arg in get_args(annotation):
new_arg = omit_by_default(arg)
new_args.append(new_arg)
return Union[tuple(new_args)] # type: ignore[return-value]
elif origin is Annotated:
annotated_args = get_args(annotation)
base_annotation = annotated_args[0]
new_base_annotation = omit_by_default(base_annotation)
new_metadata = annotated_args[1:]
return Annotated[new_base_annotation + new_metadata] # type: ignore[return-value]
else:
return OnErrorOmit[annotation] # type: ignore[return-value]

14
fastapi/dependencies/utils.py

@ -31,6 +31,7 @@ from fastapi._compat import (
create_body_model, create_body_model,
evaluate_forwardref, evaluate_forwardref,
field_annotation_is_scalar, field_annotation_is_scalar,
field_annotation_is_scalar_sequence_mapping,
get_annotation_from_field_info, get_annotation_from_field_info,
get_cached_model_fields, get_cached_model_fields,
get_missing_field_error, get_missing_field_error,
@ -45,6 +46,7 @@ from fastapi._compat import (
is_uploadfile_sequence_annotation, is_uploadfile_sequence_annotation,
lenient_issubclass, lenient_issubclass,
may_v1, may_v1,
omit_by_default,
sequence_types, sequence_types,
serialize_sequence_value, serialize_sequence_value,
value_is_sequence, value_is_sequence,
@ -63,7 +65,7 @@ from fastapi.security.oauth2 import OAuth2, SecurityScopes
from fastapi.security.open_id_connect_url import OpenIdConnect from fastapi.security.open_id_connect_url import OpenIdConnect
from fastapi.types import DependencyCacheKey from fastapi.types import DependencyCacheKey
from fastapi.utils import create_model_field, get_path_param_names from fastapi.utils import create_model_field, get_path_param_names
from pydantic import BaseModel, TypeAdapter from pydantic import BaseModel
from pydantic.fields import FieldInfo from pydantic.fields import FieldInfo
from starlette.background import BackgroundTasks as StarletteBackgroundTasks from starlette.background import BackgroundTasks as StarletteBackgroundTasks
from starlette.concurrency import run_in_threadpool from starlette.concurrency import run_in_threadpool
@ -485,6 +487,12 @@ def analyze_param(
and getattr(field_info, "in_", None) is None and getattr(field_info, "in_", None) is None
): ):
field_info.in_ = params.ParamTypes.query field_info.in_ = params.ParamTypes.query
if isinstance(
field_info, (params.Query, temp_pydantic_v1_params.Query)
) and field_annotation_is_scalar_sequence_mapping(use_annotation):
use_annotation = omit_by_default(use_annotation)
use_annotation_from_field_info = get_annotation_from_field_info( use_annotation_from_field_info = get_annotation_from_field_info(
use_annotation, use_annotation,
field_info, field_info,
@ -523,10 +531,6 @@ def analyze_param(
and getattr(field, "shape", 1) == 1 and getattr(field, "shape", 1) == 1
) )
) )
if is_scalar_sequence_field(field) or is_scalar_sequence_mapping_field(
field
):
field._type_adapter.core_schema["on_error"] = "omit"
return ParamDetails(type_annotation=type_annotation, depends=depends, field=field) return ParamDetails(type_annotation=type_annotation, depends=depends, field=field)

1
fastapi/params.py

@ -305,6 +305,7 @@ class Query(Param): # type: ignore[misc]
json_schema_extra=json_schema_extra, json_schema_extra=json_schema_extra,
**extra, **extra,
) )
self.annotation = self.annotation
class Header(Param): # type: ignore[misc] class Header(Param): # type: ignore[misc]

2
tests/test_tutorial/test_query_params/test_tutorial007_py310.py

@ -17,7 +17,7 @@ def test_foo_needy_very(client: TestClient):
"query": 2, "query": 2,
"mapping_query_str_or_int": {"foo": "baz"}, "mapping_query_str_or_int": {"foo": "baz"},
"mapping_query_int": {}, "mapping_query_int": {},
"sequence_mapping_int": {}, "sequence_mapping_int": {"foo": []},
} }

Loading…
Cancel
Save