Skip to content

Commit 42acf9d

Browse files
authored
Merge branch 'master' into fix/docstring-word-duplications
2 parents faaa946 + eb6851d commit 42acf9d

18 files changed

Lines changed: 108 additions & 63 deletions

.pre-commit-config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ repos:
3737
language: unsupported
3838
pass_filenames: false
3939

40+
- id: local-ty
41+
name: ty check
42+
entry: uv run ty check fastapi
43+
require_serial: true
44+
language: unsupported
45+
pass_filenames: false
46+
4047
- id: add-permalinks-pages
4148
language: unsupported
4249
name: add-permalinks-pages

docs/en/docs/release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ hide:
1919

2020
### Internal
2121

22+
* 👷 Add `ty` to precommit. PR [#15091](https://github.com/fastapi/fastapi/pull/15091) by [@svlandeg](https://github.com/svlandeg).
2223
* ⬆ Bump dorny/paths-filter from 3 to 4. PR [#15106](https://github.com/fastapi/fastapi/pull/15106) by [@dependabot[bot]](https://github.com/apps/dependabot).
2324
* ⬆ Bump cairosvg from 2.8.2 to 2.9.0. PR [#15108](https://github.com/fastapi/fastapi/pull/15108) by [@dependabot[bot]](https://github.com/apps/dependabot).
2425
* ⬆ Bump pyjwt from 2.11.0 to 2.12.0. PR [#15110](https://github.com/fastapi/fastapi/pull/15110) by [@dependabot[bot]](https://github.com/apps/dependabot).

fastapi/_compat/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from .v2 import Url as Url
2727
from .v2 import copy_field_info as copy_field_info
2828
from .v2 import create_body_model as create_body_model
29-
from .v2 import evaluate_forwardref as evaluate_forwardref
29+
from .v2 import evaluate_forwardref as evaluate_forwardref # ty: ignore[deprecated]
3030
from .v2 import get_cached_model_fields as get_cached_model_fields
3131
from .v2 import get_definitions as get_definitions
3232
from .v2 import get_flat_models_from_fields as get_flat_models_from_fields

fastapi/_compat/v2.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
2323
from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation
2424
from pydantic import ValidationError as ValidationError
25-
from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined]
25+
from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined] # ty: ignore[unused-ignore-comment]
2626
GetJsonSchemaHandler as GetJsonSchemaHandler,
2727
)
28-
from pydantic._internal._typing_extra import eval_type_lenient
28+
from pydantic._internal._typing_extra import eval_type_lenient # ty: ignore[deprecated]
2929
from pydantic.fields import FieldInfo as FieldInfo
3030
from pydantic.json_schema import GenerateJsonSchema as _GenerateJsonSchema
3131
from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue
@@ -38,7 +38,7 @@
3838

3939
RequiredParam = PydanticUndefined
4040
Undefined = PydanticUndefined
41-
evaluate_forwardref = eval_type_lenient
41+
evaluate_forwardref = eval_type_lenient # ty: ignore[deprecated]
4242

4343

4444
class GenerateJsonSchema(_GenerateJsonSchema):
@@ -148,7 +148,7 @@ def __post_init__(self) -> None:
148148
Field(**field_dict["attributes"]),
149149
)
150150
self._type_adapter: TypeAdapter[Any] = TypeAdapter(
151-
Annotated[annotated_args],
151+
Annotated[annotated_args], # ty: ignore[invalid-type-form]
152152
config=self.config,
153153
)
154154

@@ -438,7 +438,7 @@ def get_flat_models_from_annotation(
438438
for arg in get_args(annotation):
439439
if lenient_issubclass(arg, (BaseModel, Enum)):
440440
if arg not in known_models:
441-
known_models.add(arg) # type: ignore[arg-type]
441+
known_models.add(arg) # type: ignore[arg-type] # ty: ignore[unused-ignore-comment]
442442
if lenient_issubclass(arg, BaseModel):
443443
get_flat_models_from_model(arg, known_models=known_models)
444444
else:

fastapi/applications.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
from collections.abc import Awaitable, Callable, Coroutine, Sequence
22
from enum import Enum
3-
from typing import (
4-
Annotated,
5-
Any,
6-
TypeVar,
7-
)
3+
from typing import Annotated, Any, TypeVar
84

95
from annotated_doc import Doc
106
from fastapi import routing
@@ -1006,11 +1002,12 @@ class Item(BaseModel):
10061002
self.exception_handlers.setdefault(
10071003
RequestValidationError, request_validation_exception_handler
10081004
)
1005+
1006+
# Starlette still has incorrect type specification for the handlers
10091007
self.exception_handlers.setdefault(
10101008
WebSocketRequestValidationError,
1011-
# Starlette still has incorrect type specification for the handlers
1012-
websocket_request_validation_exception_handler, # type: ignore
1013-
)
1009+
websocket_request_validation_exception_handler, # type: ignore[arg-type] # ty: ignore[unused-ignore-comment]
1010+
) # ty: ignore[no-matching-overload]
10141011

10151012
self.user_middleware: list[Middleware] = (
10161013
[] if middleware is None else list(middleware)
@@ -1032,11 +1029,13 @@ def build_middleware_stack(self) -> ASGIApp:
10321029
exception_handlers[key] = value
10331030

10341031
middleware = (
1035-
[Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
1032+
[Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)] # ty: ignore[invalid-argument-type]
10361033
+ self.user_middleware
10371034
+ [
10381035
Middleware(
1039-
ExceptionMiddleware, handlers=exception_handlers, debug=debug
1036+
ExceptionMiddleware, # ty: ignore[invalid-argument-type]
1037+
handlers=exception_handlers,
1038+
debug=debug,
10401039
),
10411040
# Add FastAPI-specific AsyncExitStackMiddleware for closing files.
10421041
# Before this was also used for closing dependencies with yield but
@@ -1057,7 +1056,7 @@ def build_middleware_stack(self) -> ASGIApp:
10571056
# user middlewares, the same context is used.
10581057
# This is currently not needed, only for closing files, but used to be
10591058
# important when dependencies with yield were closed here.
1060-
Middleware(AsyncExitStackMiddleware),
1059+
Middleware(AsyncExitStackMiddleware), # ty: ignore[invalid-argument-type]
10611060
]
10621061
)
10631062

@@ -4596,7 +4595,7 @@ def on_event(
45964595
Read more about it in the
45974596
[FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/#alternative-events-deprecated).
45984597
"""
4599-
return self.router.on_event(event_type)
4598+
return self.router.on_event(event_type) # ty: ignore[deprecated]
46004599

46014600
def middleware(
46024601
self,
@@ -4639,7 +4638,7 @@ async def add_process_time_header(
46394638
"""
46404639

46414640
def decorator(func: DecoratedCallable) -> DecoratedCallable:
4642-
self.add_middleware(BaseHTTPMiddleware, dispatch=func)
4641+
self.add_middleware(BaseHTTPMiddleware, dispatch=func) # ty: ignore[invalid-argument-type]
46434642
return func
46444643

46454644
return decorator

fastapi/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
def main() -> None:
9-
if not cli_main: # type: ignore[truthy-function]
9+
if not cli_main: # type: ignore[truthy-function] # ty: ignore[unused-ignore-comment]
1010
message = 'To use the fastapi command, please install "fastapi[standard]":\n\n\tpip install "fastapi[standard]"\n'
1111
print(message)
1212
raise RuntimeError(message) # noqa: B904

fastapi/datastructures.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,8 @@ def Default(value: DefaultType) -> DefaultType:
179179
if the overridden default value was truthy.
180180
"""
181181
return DefaultPlaceholder(value) # type: ignore
182+
183+
184+
# Sentinel for "parameter not provided" in Param/FieldInfo.
185+
# Typed as None to satisfy ty
186+
_Unset = Default(None)

fastapi/dependencies/utils.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
Undefined,
3434
copy_field_info,
3535
create_body_model,
36-
evaluate_forwardref,
36+
evaluate_forwardref, # ty: ignore[deprecated]
3737
field_annotation_is_scalar,
3838
field_annotation_is_scalar_sequence,
3939
field_annotation_is_sequence,
@@ -100,12 +100,14 @@ def ensure_multipart_is_installed() -> None:
100100
except (ImportError, AssertionError):
101101
try:
102102
# __version__ is available in both multiparts, and can be mocked
103-
from multipart import __version__ # type: ignore[no-redef,import-untyped]
103+
from multipart import ( # type: ignore[no-redef,import-untyped] # ty: ignore[unused-ignore-comment]
104+
__version__,
105+
)
104106

105107
assert __version__
106108
try:
107109
# parse_options_header is only available in the right multipart
108-
from multipart.multipart import ( # type: ignore[import-untyped]
110+
from multipart.multipart import ( # type: ignore[import-untyped] # ty: ignore[unused-ignore-comment]
109111
parse_options_header,
110112
)
111113

@@ -243,7 +245,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
243245
def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any:
244246
if isinstance(annotation, str):
245247
annotation = ForwardRef(annotation)
246-
annotation = evaluate_forwardref(annotation, globalns, globalns)
248+
annotation = evaluate_forwardref(annotation, globalns, globalns) # ty: ignore[deprecated]
247249
if annotation is type(None):
248250
return None
249251
return annotation
@@ -320,8 +322,9 @@ def get_dependant(
320322
and param_details.depends.scope == "function"
321323
):
322324
assert dependant.call
325+
call_name = getattr(dependant.call, "__name__", "<unnamed_callable>")
323326
raise DependencyScopeError(
324-
f'The dependency "{dependant.call.__name__}" has a scope of '
327+
f'The dependency "{call_name}" has a scope of '
325328
'"request", it cannot depend on dependencies with scope "function".'
326329
)
327330
sub_own_oauth_scopes: list[str] = []
@@ -596,7 +599,7 @@ async def solve_dependencies(
596599
*,
597600
request: Request | WebSocket,
598601
dependant: Dependant,
599-
body: dict[str, Any] | FormData | None = None,
602+
body: dict[str, Any] | FormData | bytes | None = None,
600603
background_tasks: StarletteBackgroundTasks | None = None,
601604
response: Response | None = None,
602605
dependency_overrides_provider: Any | None = None,
@@ -619,7 +622,7 @@ async def solve_dependencies(
619622
if response is None:
620623
response = Response()
621624
del response.headers["content-length"]
622-
response.status_code = None # type: ignore
625+
response.status_code = None # type: ignore # ty: ignore[unused-ignore-comment]
623626
if dependency_cache is None:
624627
dependency_cache = {}
625628
for sub_dependant in dependant.dependencies:
@@ -826,7 +829,7 @@ def request_params_to_args(
826829

827830
for key in received_params.keys():
828831
if key not in processed_keys:
829-
if hasattr(received_params, "getlist"):
832+
if isinstance(received_params, (ImmutableMultiDict, Headers)):
830833
value = received_params.getlist(key)
831834
if isinstance(value, list) and (len(value) == 1):
832835
params_to_process[key] = value[0]
@@ -947,7 +950,7 @@ async def _extract_form_body(
947950

948951
async def request_body_to_args(
949952
body_fields: list[ModelField],
950-
received_body: dict[str, Any] | FormData | None,
953+
received_body: dict[str, Any] | FormData | bytes | None,
951954
embed_body_fields: bool,
952955
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
953956
values: dict[str, Any] = {}
@@ -978,7 +981,7 @@ async def request_body_to_args(
978981
for field in body_fields:
979982
loc = ("body", get_validation_alias(field))
980983
value: Any | None = None
981-
if body_to_process is not None:
984+
if body_to_process is not None and not isinstance(body_to_process, bytes):
982985
try:
983986
value = body_to_process.get(get_validation_alias(field))
984987
# If the received body is a list, not a dict

fastapi/encoders.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from fastapi.exceptions import PydanticV1NotSupportedError
2323
from fastapi.types import IncEx
2424
from pydantic import BaseModel
25-
from pydantic.color import Color
25+
from pydantic.color import Color # ty: ignore[deprecated]
2626
from pydantic.networks import AnyUrl, NameEmail
2727
from pydantic.types import SecretBytes, SecretStr
2828
from pydantic_core import PydanticUndefinedType
@@ -67,7 +67,7 @@ def decimal_encoder(dec_value: Decimal) -> int | float:
6767

6868
ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {
6969
bytes: lambda o: o.decode(),
70-
Color: str,
70+
Color: str, # ty: ignore[deprecated]
7171
datetime.date: isoformat,
7272
datetime.datetime: isoformat,
7373
datetime.time: isoformat,
@@ -220,9 +220,9 @@ def jsonable_encoder(
220220
if isinstance(obj, encoder_type):
221221
return encoder_instance(obj)
222222
if include is not None and not isinstance(include, (set, dict)):
223-
include = set(include) # type: ignore[assignment]
223+
include = set(include) # type: ignore[assignment] # ty: ignore[unused-ignore-comment]
224224
if exclude is not None and not isinstance(exclude, (set, dict)):
225-
exclude = set(exclude) # type: ignore[assignment]
225+
exclude = set(exclude) # type: ignore[assignment] # ty: ignore[unused-ignore-comment]
226226
if isinstance(obj, BaseModel):
227227
obj_dict = obj.model_dump(
228228
mode="json",

fastapi/openapi/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from pydantic import EmailStr
2121
except ImportError: # pragma: no cover
2222

23-
class EmailStr(str): # type: ignore
23+
class EmailStr(str): # type: ignore # ty: ignore[unused-ignore-comment]
2424
@classmethod
2525
def __get_validators__(cls) -> Iterable[Callable[..., Any]]:
2626
yield cls.validate

0 commit comments

Comments
 (0)