diff --git a/setup.cfg b/setup.cfg index f1a5c9929..a5e6e0e14 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,8 +5,12 @@ max-line-length = 88 [mypy] disallow_untyped_defs = True ignore_missing_imports = True +no_implicit_optional = True show_error_codes = True +[mypy-starlette.testclient] +no_implicit_optional = False + [mypy-tests.*] disallow_untyped_defs = False check_untyped_defs = True diff --git a/starlette/applications.py b/starlette/applications.py index d83f70d2f..8c5154449 100644 --- a/starlette/applications.py +++ b/starlette/applications.py @@ -41,17 +41,22 @@ class Starlette: def __init__( self, debug: bool = False, - routes: typing.Sequence[BaseRoute] = None, - middleware: typing.Sequence[Middleware] = None, - exception_handlers: typing.Mapping[ - typing.Any, - typing.Callable[ - [Request, Exception], typing.Union[Response, typing.Awaitable[Response]] - ], + routes: typing.Optional[typing.Sequence[BaseRoute]] = None, + middleware: typing.Optional[typing.Sequence[Middleware]] = None, + exception_handlers: typing.Optional[ + typing.Mapping[ + typing.Any, + typing.Callable[ + [Request, Exception], + typing.Union[Response, typing.Awaitable[Response]], + ], + ] + ] = None, + on_startup: typing.Optional[typing.Sequence[typing.Callable]] = None, + on_shutdown: typing.Optional[typing.Sequence[typing.Callable]] = None, + lifespan: typing.Optional[ + typing.Callable[["Starlette"], typing.AsyncContextManager] ] = None, - on_startup: typing.Sequence[typing.Callable] = None, - on_shutdown: typing.Sequence[typing.Callable] = None, - lifespan: typing.Callable[["Starlette"], typing.AsyncContextManager] = None, ) -> None: # The lifespan context function is a newer style that replaces # on_startup / on_shutdown handlers. Use one or the other, not both. @@ -124,7 +129,7 @@ def on_event(self, event_type: str) -> typing.Callable: # pragma: nocover return self.router.on_event(event_type) def mount( - self, path: str, app: ASGIApp, name: str = None + self, path: str, app: ASGIApp, name: typing.Optional[str] = None ) -> None: # pragma: nocover """ We no longer document this API, and its usage is discouraged. @@ -141,7 +146,7 @@ def mount( self.router.mount(path, app=app, name=name) def host( - self, host: str, app: ASGIApp, name: str = None + self, host: str, app: ASGIApp, name: typing.Optional[str] = None ) -> None: # pragma: no cover """ We no longer document this API, and its usage is discouraged. @@ -180,8 +185,8 @@ def add_route( self, path: str, route: typing.Callable, - methods: typing.List[str] = None, - name: str = None, + methods: typing.Optional[typing.List[str]] = None, + name: typing.Optional[str] = None, include_in_schema: bool = True, ) -> None: # pragma: no cover self.router.add_route( @@ -189,7 +194,7 @@ def add_route( ) def add_websocket_route( - self, path: str, route: typing.Callable, name: str = None + self, path: str, route: typing.Callable, name: typing.Optional[str] = None ) -> None: # pragma: no cover self.router.add_websocket_route(path, route, name=name) @@ -205,8 +210,8 @@ def decorator(func: typing.Callable) -> typing.Callable: def route( self, path: str, - methods: typing.List[str] = None, - name: str = None, + methods: typing.Optional[typing.List[str]] = None, + name: typing.Optional[str] = None, include_in_schema: bool = True, ) -> typing.Callable: # pragma: nocover """ @@ -234,7 +239,7 @@ def decorator(func: typing.Callable) -> typing.Callable: return decorator def websocket_route( - self, path: str, name: str = None + self, path: str, name: typing.Optional[str] = None ) -> typing.Callable: # pragma: nocover """ We no longer document this decorator style API, and its usage is discouraged. diff --git a/starlette/authentication.py b/starlette/authentication.py index 1a4cba377..17f4a5ead 100644 --- a/starlette/authentication.py +++ b/starlette/authentication.py @@ -20,7 +20,7 @@ def has_required_scope(conn: HTTPConnection, scopes: typing.Sequence[str]) -> bo def requires( scopes: typing.Union[str, typing.Sequence[str]], status_code: int = 403, - redirect: str = None, + redirect: typing.Optional[str] = None, ) -> typing.Callable: scopes_list = [scopes] if isinstance(scopes, str) else list(scopes) @@ -110,7 +110,7 @@ async def authenticate( class AuthCredentials: - def __init__(self, scopes: typing.Sequence[str] = None): + def __init__(self, scopes: typing.Optional[typing.Sequence[str]] = None): self.scopes = [] if scopes is None else list(scopes) diff --git a/starlette/background.py b/starlette/background.py index 14a4e9e1a..145324e3f 100644 --- a/starlette/background.py +++ b/starlette/background.py @@ -29,7 +29,7 @@ async def __call__(self) -> None: class BackgroundTasks(BackgroundTask): - def __init__(self, tasks: typing.Sequence[BackgroundTask] = None): + def __init__(self, tasks: typing.Optional[typing.Sequence[BackgroundTask]] = None): self.tasks = list(tasks) if tasks else [] def add_task( diff --git a/starlette/config.py b/starlette/config.py index bd809afb4..e9e809c73 100644 --- a/starlette/config.py +++ b/starlette/config.py @@ -52,7 +52,7 @@ def __len__(self) -> int: class Config: def __init__( self, - env_file: typing.Union[str, Path] = None, + env_file: typing.Optional[typing.Union[str, Path]] = None, environ: typing.Mapping[str, str] = environ, ) -> None: self.environ = environ @@ -88,12 +88,18 @@ def __call__( ... def __call__( - self, key: str, cast: typing.Callable = None, default: typing.Any = undefined + self, + key: str, + cast: typing.Optional[typing.Callable] = None, + default: typing.Any = undefined, ) -> typing.Any: return self.get(key, cast, default) def get( - self, key: str, cast: typing.Callable = None, default: typing.Any = undefined + self, + key: str, + cast: typing.Optional[typing.Callable] = None, + default: typing.Any = undefined, ) -> typing.Any: if key in self.environ: value = self.environ[key] @@ -118,7 +124,7 @@ def _read_file(self, file_name: typing.Union[str, Path]) -> typing.Dict[str, str return file_values def _perform_cast( - self, key: str, value: typing.Any, cast: typing.Callable = None + self, key: str, value: typing.Any, cast: typing.Optional[typing.Callable] = None ) -> typing.Any: if cast is None or value is None: return value diff --git a/starlette/exceptions.py b/starlette/exceptions.py index 8f28b6e2d..61039c598 100644 --- a/starlette/exceptions.py +++ b/starlette/exceptions.py @@ -10,7 +10,10 @@ class HTTPException(Exception): def __init__( - self, status_code: int, detail: str = None, headers: dict = None + self, + status_code: int, + detail: typing.Optional[str] = None, + headers: typing.Optional[dict] = None, ) -> None: if detail is None: detail = http.HTTPStatus(status_code).phrase @@ -27,8 +30,8 @@ class ExceptionMiddleware: def __init__( self, app: ASGIApp, - handlers: typing.Mapping[ - typing.Any, typing.Callable[[Request, Exception], Response] + handlers: typing.Optional[ + typing.Mapping[typing.Any, typing.Callable[[Request, Exception], Response]] ] = None, debug: bool = False, ) -> None: diff --git a/starlette/middleware/authentication.py b/starlette/middleware/authentication.py index 6e2d2dade..76e4a246d 100644 --- a/starlette/middleware/authentication.py +++ b/starlette/middleware/authentication.py @@ -16,8 +16,8 @@ def __init__( self, app: ASGIApp, backend: AuthenticationBackend, - on_error: typing.Callable[ - [HTTPConnection, AuthenticationError], Response + on_error: typing.Optional[ + typing.Callable[[HTTPConnection, AuthenticationError], Response] ] = None, ) -> None: self.app = app diff --git a/starlette/middleware/base.py b/starlette/middleware/base.py index bfb4a54a4..ca9deb7de 100644 --- a/starlette/middleware/base.py +++ b/starlette/middleware/base.py @@ -13,7 +13,9 @@ class BaseHTTPMiddleware: - def __init__(self, app: ASGIApp, dispatch: DispatchFunction = None) -> None: + def __init__( + self, app: ASGIApp, dispatch: typing.Optional[DispatchFunction] = None + ) -> None: self.app = app self.dispatch_func = self.dispatch if dispatch is None else dispatch diff --git a/starlette/middleware/cors.py b/starlette/middleware/cors.py index c850579c8..b36d155f5 100644 --- a/starlette/middleware/cors.py +++ b/starlette/middleware/cors.py @@ -18,7 +18,7 @@ def __init__( allow_methods: typing.Sequence[str] = ("GET",), allow_headers: typing.Sequence[str] = (), allow_credentials: bool = False, - allow_origin_regex: str = None, + allow_origin_regex: typing.Optional[str] = None, expose_headers: typing.Sequence[str] = (), max_age: int = 600, ) -> None: diff --git a/starlette/middleware/errors.py b/starlette/middleware/errors.py index 474c9afc0..acb1930f3 100644 --- a/starlette/middleware/errors.py +++ b/starlette/middleware/errors.py @@ -135,7 +135,10 @@ class ServerErrorMiddleware: """ def __init__( - self, app: ASGIApp, handler: typing.Callable = None, debug: bool = False + self, + app: ASGIApp, + handler: typing.Optional[typing.Callable] = None, + debug: bool = False, ) -> None: self.app = app self.handler = handler diff --git a/starlette/middleware/trustedhost.py b/starlette/middleware/trustedhost.py index 6bc4d2b5e..e84e6876a 100644 --- a/starlette/middleware/trustedhost.py +++ b/starlette/middleware/trustedhost.py @@ -11,7 +11,7 @@ class TrustedHostMiddleware: def __init__( self, app: ASGIApp, - allowed_hosts: typing.Sequence[str] = None, + allowed_hosts: typing.Optional[typing.Sequence[str]] = None, www_redirect: bool = True, ) -> None: if allowed_hosts is None: diff --git a/starlette/requests.py b/starlette/requests.py index e3c91e284..c738ebaa4 100644 --- a/starlette/requests.py +++ b/starlette/requests.py @@ -65,7 +65,7 @@ class HTTPConnection(Mapping): any functionality that is common to both `Request` and `WebSocket`. """ - def __init__(self, scope: Scope, receive: Receive = None) -> None: + def __init__(self, scope: Scope, receive: typing.Optional[Receive] = None) -> None: assert scope["type"] in ("http", "websocket") self.scope = scope diff --git a/starlette/responses.py b/starlette/responses.py index b33bdd713..bc73cb156 100644 --- a/starlette/responses.py +++ b/starlette/responses.py @@ -38,9 +38,9 @@ def __init__( self, content: typing.Any = None, status_code: int = 200, - headers: typing.Mapping[str, str] = None, - media_type: str = None, - background: BackgroundTask = None, + headers: typing.Optional[typing.Mapping[str, str]] = None, + media_type: typing.Optional[str] = None, + background: typing.Optional[BackgroundTask] = None, ) -> None: self.status_code = status_code if media_type is not None: @@ -56,7 +56,9 @@ def render(self, content: typing.Any) -> bytes: return content return content.encode(self.charset) - def init_headers(self, headers: typing.Mapping[str, str] = None) -> None: + def init_headers( + self, headers: typing.Optional[typing.Mapping[str, str]] = None + ) -> None: if headers is None: raw_headers: typing.List[typing.Tuple[bytes, bytes]] = [] populate_content_length = True @@ -97,10 +99,10 @@ def set_cookie( self, key: str, value: str = "", - max_age: int = None, - expires: int = None, + max_age: typing.Optional[int] = None, + expires: typing.Optional[int] = None, path: str = "/", - domain: str = None, + domain: typing.Optional[str] = None, secure: bool = False, httponly: bool = False, samesite: str = "lax", @@ -133,7 +135,7 @@ def delete_cookie( self, key: str, path: str = "/", - domain: str = None, + domain: typing.Optional[str] = None, secure: bool = False, httponly: bool = False, samesite: str = "lax", @@ -178,9 +180,9 @@ def __init__( self, content: typing.Any, status_code: int = 200, - headers: dict = None, - media_type: str = None, - background: BackgroundTask = None, + headers: typing.Optional[dict] = None, + media_type: typing.Optional[str] = None, + background: typing.Optional[BackgroundTask] = None, ) -> None: super().__init__(content, status_code, headers, media_type, background) @@ -199,8 +201,8 @@ def __init__( self, url: typing.Union[str, URL], status_code: int = 307, - headers: typing.Mapping[str, str] = None, - background: BackgroundTask = None, + headers: typing.Optional[typing.Mapping[str, str]] = None, + background: typing.Optional[BackgroundTask] = None, ) -> None: super().__init__( content=b"", status_code=status_code, headers=headers, background=background @@ -213,9 +215,9 @@ def __init__( self, content: typing.Any, status_code: int = 200, - headers: typing.Mapping[str, str] = None, - media_type: str = None, - background: BackgroundTask = None, + headers: typing.Optional[typing.Mapping[str, str]] = None, + media_type: typing.Optional[str] = None, + background: typing.Optional[BackgroundTask] = None, ) -> None: if isinstance(content, typing.AsyncIterable): self.body_iterator = content @@ -268,12 +270,12 @@ def __init__( self, path: typing.Union[str, "os.PathLike[str]"], status_code: int = 200, - headers: typing.Mapping[str, str] = None, - media_type: str = None, - background: BackgroundTask = None, - filename: str = None, - stat_result: os.stat_result = None, - method: str = None, + headers: typing.Optional[typing.Mapping[str, str]] = None, + media_type: typing.Optional[str] = None, + background: typing.Optional[BackgroundTask] = None, + filename: typing.Optional[str] = None, + stat_result: typing.Optional[os.stat_result] = None, + method: typing.Optional[str] = None, content_disposition_type: str = "attachment", ) -> None: self.path = path diff --git a/starlette/routing.py b/starlette/routing.py index ea6ec2117..7b7dc9b74 100644 --- a/starlette/routing.py +++ b/starlette/routing.py @@ -192,8 +192,8 @@ def __init__( path: str, endpoint: typing.Callable, *, - methods: typing.List[str] = None, - name: str = None, + methods: typing.Optional[typing.List[str]] = None, + name: typing.Optional[str] = None, include_in_schema: bool = True, ) -> None: assert path.startswith("/"), "Routed paths must start with '/'" @@ -276,7 +276,7 @@ def __eq__(self, other: typing.Any) -> bool: class WebSocketRoute(BaseRoute): def __init__( - self, path: str, endpoint: typing.Callable, *, name: str = None + self, path: str, endpoint: typing.Callable, *, name: typing.Optional[str] = None ) -> None: assert path.startswith("/"), "Routed paths must start with '/'" self.path = path @@ -336,9 +336,9 @@ class Mount(BaseRoute): def __init__( self, path: str, - app: ASGIApp = None, - routes: typing.Sequence[BaseRoute] = None, - name: str = None, + app: typing.Optional[ASGIApp] = None, + routes: typing.Optional[typing.Sequence[BaseRoute]] = None, + name: typing.Optional[str] = None, ) -> None: assert path == "" or path.startswith("/"), "Routed paths must start with '/'" assert ( @@ -426,7 +426,9 @@ def __eq__(self, other: typing.Any) -> bool: class Host(BaseRoute): - def __init__(self, host: str, app: ASGIApp, name: str = None) -> None: + def __init__( + self, host: str, app: ASGIApp, name: typing.Optional[str] = None + ) -> None: self.host = host self.app = app self.name = name @@ -537,12 +539,14 @@ def __call__(self: _T, app: object) -> _T: class Router: def __init__( self, - routes: typing.Sequence[BaseRoute] = None, + routes: typing.Optional[typing.Sequence[BaseRoute]] = None, redirect_slashes: bool = True, - default: ASGIApp = None, - on_startup: typing.Sequence[typing.Callable] = None, - on_shutdown: typing.Sequence[typing.Callable] = None, - lifespan: typing.Callable[[typing.Any], typing.AsyncContextManager] = None, + default: typing.Optional[ASGIApp] = None, + on_startup: typing.Optional[typing.Sequence[typing.Callable]] = None, + on_shutdown: typing.Optional[typing.Sequence[typing.Callable]] = None, + lifespan: typing.Optional[ + typing.Callable[[typing.Any], typing.AsyncContextManager] + ] = None, ) -> None: self.routes = [] if routes is None else list(routes) self.redirect_slashes = redirect_slashes @@ -700,7 +704,7 @@ def __eq__(self, other: typing.Any) -> bool: # The following usages are now discouraged in favour of configuration #  during Router.__init__(...) def mount( - self, path: str, app: ASGIApp, name: str = None + self, path: str, app: ASGIApp, name: typing.Optional[str] = None ) -> None: # pragma: nocover """ We no longer document this API, and its usage is discouraged. @@ -718,7 +722,7 @@ def mount( self.routes.append(route) def host( - self, host: str, app: ASGIApp, name: str = None + self, host: str, app: ASGIApp, name: typing.Optional[str] = None ) -> None: # pragma: no cover """ We no longer document this API, and its usage is discouraged. @@ -739,8 +743,8 @@ def add_route( self, path: str, endpoint: typing.Callable, - methods: typing.List[str] = None, - name: str = None, + methods: typing.Optional[typing.List[str]] = None, + name: typing.Optional[str] = None, include_in_schema: bool = True, ) -> None: # pragma: nocover route = Route( @@ -753,7 +757,7 @@ def add_route( self.routes.append(route) def add_websocket_route( - self, path: str, endpoint: typing.Callable, name: str = None + self, path: str, endpoint: typing.Callable, name: typing.Optional[str] = None ) -> None: # pragma: no cover route = WebSocketRoute(path, endpoint=endpoint, name=name) self.routes.append(route) @@ -761,8 +765,8 @@ def add_websocket_route( def route( self, path: str, - methods: typing.List[str] = None, - name: str = None, + methods: typing.Optional[typing.List[str]] = None, + name: typing.Optional[str] = None, include_in_schema: bool = True, ) -> typing.Callable: # pragma: nocover """ @@ -790,7 +794,7 @@ def decorator(func: typing.Callable) -> typing.Callable: return decorator def websocket_route( - self, path: str, name: str = None + self, path: str, name: typing.Optional[str] = None ) -> typing.Callable: # pragma: nocover """ We no longer document this decorator style API, and its usage is discouraged. diff --git a/starlette/staticfiles.py b/starlette/staticfiles.py index bd4d8bced..d09630f35 100644 --- a/starlette/staticfiles.py +++ b/starlette/staticfiles.py @@ -39,8 +39,10 @@ class StaticFiles: def __init__( self, *, - directory: PathLike = None, - packages: typing.List[typing.Union[str, typing.Tuple[str, str]]] = None, + directory: typing.Optional[PathLike] = None, + packages: typing.Optional[ + typing.List[typing.Union[str, typing.Tuple[str, str]]] + ] = None, html: bool = False, check_dir: bool = True, ) -> None: @@ -54,8 +56,10 @@ def __init__( def get_directories( self, - directory: PathLike = None, - packages: typing.List[typing.Union[str, typing.Tuple[str, str]]] = None, + directory: typing.Optional[PathLike] = None, + packages: typing.Optional[ + typing.List[typing.Union[str, typing.Tuple[str, str]]] + ] = None, ) -> typing.List[PathLike]: """ Given `directory` and `packages` arguments, return a list of all the diff --git a/starlette/templating.py b/starlette/templating.py index 27939c95e..99035837f 100644 --- a/starlette/templating.py +++ b/starlette/templating.py @@ -28,9 +28,9 @@ def __init__( template: typing.Any, context: dict, status_code: int = 200, - headers: typing.Mapping[str, str] = None, - media_type: str = None, - background: BackgroundTask = None, + headers: typing.Optional[typing.Mapping[str, str]] = None, + media_type: typing.Optional[str] = None, + background: typing.Optional[BackgroundTask] = None, ): self.template = template self.context = context @@ -88,9 +88,9 @@ def TemplateResponse( name: str, context: dict, status_code: int = 200, - headers: typing.Mapping[str, str] = None, - media_type: str = None, - background: BackgroundTask = None, + headers: typing.Optional[typing.Mapping[str, str]] = None, + media_type: typing.Optional[str] = None, + background: typing.Optional[BackgroundTask] = None, ) -> _TemplateResponse: if "request" not in context: raise ValueError('context must include a "request" key') diff --git a/starlette/websockets.py b/starlette/websockets.py index 03ed19972..afcbde7fc 100644 --- a/starlette/websockets.py +++ b/starlette/websockets.py @@ -13,7 +13,7 @@ class WebSocketState(enum.Enum): class WebSocketDisconnect(Exception): - def __init__(self, code: int = 1000, reason: str = None) -> None: + def __init__(self, code: int = 1000, reason: typing.Optional[str] = None) -> None: self.code = code self.reason = reason or "" @@ -88,8 +88,8 @@ async def send(self, message: Message) -> None: async def accept( self, - subprotocol: str = None, - headers: typing.Iterable[typing.Tuple[bytes, bytes]] = None, + subprotocol: typing.Optional[str] = None, + headers: typing.Optional[typing.Iterable[typing.Tuple[bytes, bytes]]] = None, ) -> None: headers = headers or [] @@ -174,14 +174,16 @@ async def send_json(self, data: typing.Any, mode: str = "text") -> None: else: await self.send({"type": "websocket.send", "bytes": text.encode("utf-8")}) - async def close(self, code: int = 1000, reason: str = None) -> None: + async def close( + self, code: int = 1000, reason: typing.Optional[str] = None + ) -> None: await self.send( {"type": "websocket.close", "code": code, "reason": reason or ""} ) class WebSocketClose: - def __init__(self, code: int = 1000, reason: str = None) -> None: + def __init__(self, code: int = 1000, reason: typing.Optional[str] = None) -> None: self.code = code self.reason = reason or ""