diff --git a/pyrightconfig.stricter.json b/pyrightconfig.stricter.json index c1d731bd8..a7b7e0a98 100644 --- a/pyrightconfig.stricter.json +++ b/pyrightconfig.stricter.json @@ -78,7 +78,6 @@ "stubs/tqdm", "stubs/ttkthemes", "stubs/vobject", - "stubs/WebOb", "stubs/workalendar", ], "typeCheckingMode": "strict", diff --git a/stubs/WebOb/@tests/stubtest_allowlist.txt b/stubs/WebOb/@tests/stubtest_allowlist.txt index 5d6a305f1..f20871060 100644 --- a/stubs/WebOb/@tests/stubtest_allowlist.txt +++ b/stubs/WebOb/@tests/stubtest_allowlist.txt @@ -137,6 +137,17 @@ webob.request.AdhocAttrMixin.__setattr__ # make sense to annotate them and pretend they're part of the API. webob.request.BaseRequest.__init__ +# We needed to add a dummy *_: _P.args in order to support ParamSpec +webob.dec.wsgify.middleware +webob.dec._MiddlewareFactory.__call__ + +# We renamed some of the arguments in positional only overloads for greater +# clarity about what the arguments mean, stubtest should probably be a bit +# more lenient here, since this is only unsafe if that overload accepts +# arbitrary named arguments, that could overlap with the argument in that +# specific position +webob.dec.wsgify.__call__ + # Error: is not present at runtime # ============================= # This attribute is there to help mypy type narrow NoVars based on its static diff --git a/stubs/WebOb/@tests/test_cases/check_wsgify.py b/stubs/WebOb/@tests/test_cases/check_wsgify.py new file mode 100644 index 000000000..ec4b01dd0 --- /dev/null +++ b/stubs/WebOb/@tests/test_cases/check_wsgify.py @@ -0,0 +1,121 @@ +from __future__ import annotations + +from _typeshed.wsgi import StartResponse, WSGIApplication, WSGIEnvironment +from collections.abc import Iterable # noqa: F401 +from typing_extensions import assert_type + +from webob.dec import _AnyResponse, wsgify +from webob.request import Request + + +class App: + @wsgify + def __call__(self, request: Request) -> str: + return "hello" + + +env: WSGIEnvironment = {} +start_response: StartResponse = lambda x, y, z=None: lambda b: None +application: WSGIApplication = lambda e, s: [b""] +request: Request = Request(env) + +x = App() +# since we wsgified our __call__ we should now be a valid WSGIApplication +application = x +assert_type(x(env, start_response), "Iterable[bytes]") +# currently we lose the exact response type, but that should be fine in +# most use-cases, since middlewares operate on an application level, not +# on these raw intermediary functions +assert_type(x(request), _AnyResponse) + +# accessing the method from the class should work as you expect it to +assert_type(App.__call__(x, env, start_response), "Iterable[bytes]") +assert_type(App.__call__(x, request), _AnyResponse) + + +# but we can also wrap it with a middleware that expects to deal with requests +class Middleware: + @wsgify.middleware + def restrict_ip(self, req: Request, app: WSGIApplication, ips: list[str]) -> WSGIApplication: + return app + + __call__ = restrict_ip(x, ips=["127.0.0.1"]) + + +# and we still end up with a valid WSGIApplication +m = Middleware() +application = m +assert_type(m(env, start_response), "Iterable[bytes]") +assert_type(m(request), _AnyResponse) + + +# the same should work with plain functions +@wsgify +def app(request: Request) -> str: + return "hello" + + +application = app +assert_type(app, "wsgify[Request, []]") +assert_type(app(env, start_response), "Iterable[bytes]") +assert_type(app(request), _AnyResponse) +# FIXME: For some reason pyright complains here with +# mismatch: expected "wsgify[Request, ()]" but received "wsgify[Request, ()]" +# can you spot the difference? +# assert_type(app(application), "wsgify[Request, []]") +application = app(application) + + +@wsgify.middleware +def restrict_ip(req: Request, app: WSGIApplication, ips: list[str]) -> WSGIApplication: + return app + + +@restrict_ip(ips=["127.0.0.1"]) +@wsgify +def m_app(request: Request) -> str: + return "hello" + + +application = m_app +# FIXME: same weird pyright error where it complains about the types +# being the same +# assert_type(m_app, "wsgify[Request, [WSGIApplication]]") +assert_type(m_app(env, start_response), "Iterable[bytes]") +assert_type(m_app(request), _AnyResponse) +# FIXME: and also here +# assert_type(m_app(application), "wsgify[Request, [WSGIApplication]]") +application = m_app(application) + + +# custom request +class MyRequest(Request): + pass + + +@wsgify(RequestClass=MyRequest) +def my_request_app(request: MyRequest) -> None: + pass + + +application = my_request_app +assert_type(my_request_app, "wsgify[MyRequest, []]") + + +# we are allowed to accept a less specific request class +@wsgify(RequestClass=MyRequest) +def valid_request_app(request: Request) -> None: + pass + + +# but the opposite is not allowed +@wsgify # type:ignore +def invalid_request_app(request: MyRequest) -> None: + pass + + +# we can't really make passing extra arguments directly work +# otherwise we have to give up most of our type safety for +# something that should only be used through wsgify.middleware +wsgify(args=(1,)) # type:ignore +wsgify(kwargs={"ips": ["127.0.0.1"]}) # type:ignore diff --git a/stubs/WebOb/webob/byterange.pyi b/stubs/WebOb/webob/byterange.pyi index c63cf6257..461d10783 100644 --- a/stubs/WebOb/webob/byterange.pyi +++ b/stubs/WebOb/webob/byterange.pyi @@ -6,23 +6,23 @@ class Range: start: int | None end: int | None @overload - def __init__(self, start: None, end: None): ... + def __init__(self, start: None, end: None) -> None: ... @overload - def __init__(self, start: int, end: int | None): ... + def __init__(self, start: int, end: int | None) -> None: ... def range_for_length(self, length: int | None) -> tuple[int, int] | None: ... def content_range(self, length: int | None) -> ContentRange | None: ... def __iter__(self) -> Iterator[int | None]: ... @classmethod - def parse(cls, header: str | None) -> Self: ... + def parse(cls, header: str | None) -> Self | None: ... class ContentRange: start: int | None stop: int | None length: int | None @overload - def __init__(self, start: None, stop: None, length: int | None): ... + def __init__(self, start: None, stop: None, length: int | None) -> None: ... @overload - def __init__(self, start: int, stop: int, length: int | None): ... + def __init__(self, start: int, stop: int, length: int | None) -> None: ... def __iter__(self) -> Iterator[int | None]: ... @classmethod - def parse(cls, value: str | None) -> Self: ... + def parse(cls, value: str | None) -> Self | None: ... diff --git a/stubs/WebOb/webob/cachecontrol.pyi b/stubs/WebOb/webob/cachecontrol.pyi index d38249a2b..b3130de75 100644 --- a/stubs/WebOb/webob/cachecontrol.pyi +++ b/stubs/WebOb/webob/cachecontrol.pyi @@ -18,7 +18,7 @@ class UpdateDict(dict[_KT, _VT]): class exists_property: prop: str type: str | None - def __init__(self, prop: str, type: str | None = None): ... + def __init__(self, prop: str, type: str | None = None) -> None: ... @overload def __get__(self, obj: None, type: _Type | None = None) -> Self: ... @overload @@ -32,9 +32,9 @@ class value_property(Generic[_T, _NoneLiteral]): none: _NoneLiteral type: str | None @overload - def __init__(self, prop: str, default: None = None, none: None = None, type: str | None = None): ... + def __init__(self, prop: str, default: None = None, none: None = None, type: str | None = None) -> None: ... @overload - def __init__(self, prop: str, default: _T, none: _NoneLiteral, type: str | None = None): ... + def __init__(self, prop: str, default: _T, none: _NoneLiteral, type: str | None = None) -> None: ... @overload def __get__(self, obj: None, type: _Type | None = None) -> Self: ... @overload diff --git a/stubs/WebOb/webob/client.pyi b/stubs/WebOb/webob/client.pyi index 81f54880b..869bdec63 100644 --- a/stubs/WebOb/webob/client.pyi +++ b/stubs/WebOb/webob/client.pyi @@ -1,10 +1,10 @@ from _typeshed.wsgi import StartResponse, WSGIEnvironment from collections.abc import Iterable -from http.client import HTTPMessage +from http.client import HTTPConnection, HTTPMessage, HTTPSConnection from typing import ClassVar class SendRequest: - def __init__(self, HTTPConnection=..., HTTPSConnection=...) -> None: ... + def __init__(self, HTTPConnection: type[HTTPConnection] = ..., HTTPSConnection: type[HTTPSConnection] = ...) -> None: ... def __call__(self, environ: WSGIEnvironment, start_response: StartResponse) -> Iterable[bytes]: ... filtered_headers: ClassVar[tuple[str, ...]] def parse_headers(self, message: HTTPMessage) -> list[tuple[str, str]]: ... diff --git a/stubs/WebOb/webob/datetime_utils.pyi b/stubs/WebOb/webob/datetime_utils.pyi index 6a57d83d9..a20e8a4d0 100644 --- a/stubs/WebOb/webob/datetime_utils.pyi +++ b/stubs/WebOb/webob/datetime_utils.pyi @@ -1,4 +1,5 @@ -from datetime import datetime, timedelta, tzinfo +from datetime import date, datetime, timedelta, tzinfo +from time import _TimeTuple, struct_time class _UTC(tzinfo): def dst(self, dt: datetime | None) -> timedelta: ... @@ -17,7 +18,7 @@ second: timedelta month: timedelta year: timedelta -def parse_date(value: str | bytes) -> datetime | None: ... -def serialize_date(dt) -> str: ... -def parse_date_delta(value: str | bytes) -> datetime | None: ... -def serialize_date_delta(value) -> str: ... +def parse_date(value: str | bytes | None) -> datetime | None: ... +def serialize_date(dt: datetime | date | timedelta | _TimeTuple | struct_time | float | str | bytes) -> str: ... +def parse_date_delta(value: str | bytes | None) -> datetime | None: ... +def serialize_date_delta(value: datetime | date | timedelta | _TimeTuple | struct_time | float | str | bytes) -> str: ... diff --git a/stubs/WebOb/webob/dec.pyi b/stubs/WebOb/webob/dec.pyi index a08ed4926..e9eb60049 100644 --- a/stubs/WebOb/webob/dec.pyi +++ b/stubs/WebOb/webob/dec.pyi @@ -1,49 +1,196 @@ -from _typeshed import Incomplete -from _typeshed.wsgi import WSGIApplication -from typing import ClassVar, overload -from typing_extensions import Self +from _typeshed.wsgi import StartResponse, WSGIApplication, WSGIEnvironment +from collections.abc import Callable, Iterable, Mapping +from typing import Any, Generic, TypeVar, overload +from typing_extensions import Concatenate, Never, ParamSpec, Self, TypeAlias from webob.request import Request +from webob.response import Response -class wsgify: - RequestClass: ClassVar[type[Request]] - func: Incomplete - args: Incomplete - kwargs: Incomplete - middleware_wraps: Incomplete +_AnyResponse: TypeAlias = Response | WSGIApplication | str | None +_S = TypeVar("_S") +_AppT = TypeVar("_AppT", bound=WSGIApplication) +_AppT_contra = TypeVar("_AppT_contra", bound=WSGIApplication, contravariant=True) +_RequestT = TypeVar("_RequestT", bound=Request) +_RequestT_contra = TypeVar("_RequestT_contra", bound=Request, contravariant=True) +_P = ParamSpec("_P") +_P2 = ParamSpec("_P2") + +_RequestHandlerCallable: TypeAlias = Callable[Concatenate[_RequestT_contra, _P], _AnyResponse] +_RequestHandlerMethod: TypeAlias = Callable[Concatenate[Any, _RequestT_contra, _P], _AnyResponse] +_MiddlewareCallable: TypeAlias = Callable[Concatenate[_RequestT_contra, _AppT_contra, _P], _AnyResponse] +_MiddlewareMethod: TypeAlias = Callable[Concatenate[Any, _RequestT_contra, _AppT_contra, _P], _AnyResponse] +_RequestHandler: TypeAlias = _RequestHandlerCallable[_RequestT_contra, _P] | _RequestHandlerMethod[_RequestT_contra, _P] +_Middleware: TypeAlias = ( + _MiddlewareCallable[_RequestT_contra, _AppT_contra, _P] | _MiddlewareMethod[_RequestT_contra, _AppT_contra, _P] +) + +class wsgify(Generic[_RequestT_contra, _P]): + RequestClass: type[Request] + func: _RequestHandler[_RequestT_contra, _P] | None + args: tuple[Any, ...] + kwargs: dict[str, Any] + middleware_wraps: WSGIApplication | None + # NOTE: We disallow passing args/kwargs using this direct API, because + # we can't really make it work as a decorator this way, these + # arguments should only really be used indrectly through the + # middleware decorator, where we can be more type safe + @overload def __init__( - self, - func: Incomplete | None = None, - RequestClass: Incomplete | None = None, - args=..., - kwargs: Incomplete | None = None, - middleware_wraps: Incomplete | None = None, + self: wsgify[Request, []], + func: _RequestHandler[Request, []] | None = None, + RequestClass: None = None, + args: tuple[()] = (), + kwargs: None = None, + middleware_wraps: None = None, ) -> None: ... @overload - def __get__(self, obj: None, type: type | None = None) -> Self: ... + def __init__( + self: wsgify[_RequestT_contra, []], + func: _RequestHandler[_RequestT_contra, []] | None, + RequestClass: type[_RequestT_contra], + args: tuple[()] = (), + kwargs: None = None, + middleware_wraps: None = None, + ) -> None: ... @overload - def __get__(self, obj: object, type: type | None = None) -> WSGIApplication: ... - def __call__(self, req, *args, **kw): ... - def get(self, url, **kw): ... - def post(self, url, POST: Incomplete | None = None, **kw): ... - def request(self, url, **kw): ... - def call_func(self, req, *args, **kwargs): ... - def clone(self, func: Incomplete | None = None, **kw): ... + def __init__( + self: wsgify[_RequestT_contra, []], + func: _RequestHandler[_RequestT_contra, []] | None = None, + *, + RequestClass: type[_RequestT_contra], + args: tuple[()] = (), + kwargs: None = None, + middleware_wraps: None = None, + ) -> None: ... + @overload + def __init__( + self: wsgify[Request, [_AppT_contra]], + func: _Middleware[Request, _AppT_contra, []] | None = None, + RequestClass: None = None, + args: tuple[()] = (), + kwargs: None = None, + *, + middleware_wraps: _AppT_contra, + ) -> None: ... + @overload + def __init__( + self: wsgify[_RequestT_contra, [_AppT_contra]], + func: _Middleware[_RequestT_contra, _AppT_contra, []] | None, + RequestClass: type[_RequestT_contra], + args: tuple[()] = (), + kwargs: None = None, + *, + middleware_wraps: _AppT_contra, + ) -> None: ... + @overload + def __init__( + self: wsgify[_RequestT_contra, [_AppT_contra]], + func: _Middleware[_RequestT_contra, _AppT_contra, []] | None = None, + *, + RequestClass: type[_RequestT_contra], + args: tuple[()] = (), + kwargs: None = None, + middleware_wraps: _AppT_contra, + ) -> None: ... + @overload + def __get__(self, obj: None, type: type[_S]) -> _unbound_wsgify[_RequestT_contra, _P, _S]: ... + @overload + def __get__(self, obj: object, type: type | None = None) -> Self: ... + @overload + def __call__(self, env: WSGIEnvironment, /, start_response: StartResponse) -> Iterable[bytes]: ... + @overload + def __call__(self, func: _RequestHandler[_RequestT_contra, _P], /) -> Self: ... + @overload + def __call__(self, req: _RequestT_contra) -> _AnyResponse: ... + @overload + def __call__(self, req: _RequestT_contra, *args: _P.args, **kw: _P.kwargs) -> _AnyResponse: ... + def get(self, url: str, **kw: Any) -> _AnyResponse: ... + def post( + self, url: str, POST: str | bytes | Mapping[Any, Any] | Mapping[Any, list[Any] | tuple[Any, ...]] | None = None, **kw: Any + ) -> _AnyResponse: ... + def request(self, url: str, **kw: Any) -> _AnyResponse: ... + def call_func(self, req: _RequestT_contra, *args: _P.args, **kwargs: _P.kwargs) -> _AnyResponse: ... + # technically this could bind different type vars, but we disallow it for safety + def clone(self, func: _RequestHandler[_RequestT_contra, _P] | None = None, **kw: Never) -> Self: ... @property - def undecorated(self): ... + def undecorated(self) -> _RequestHandler[_RequestT_contra, _P] | None: ... + @overload @classmethod - def middleware(cls, middle_func: Incomplete | None = None, app: Incomplete | None = None, **kw): ... + def middleware( + cls, middle_func: None = None, app: None | _AppT = None, *_: _P.args, **kw: _P.kwargs + ) -> _UnboundMiddleware[Any, _AppT, _P]: ... + @overload + @classmethod + def middleware( + cls, middle_func: _MiddlewareCallable[_RequestT, _AppT, _P2], app: None = None + ) -> _MiddlewareFactory[_RequestT, _AppT, _P2]: ... + @overload + @classmethod + def middleware( + cls, middle_func: _MiddlewareMethod[_RequestT, _AppT, _P2], app: None = None + ) -> _MiddlewareFactory[_RequestT, _AppT, _P2]: ... + @overload + @classmethod + def middleware( + cls, middle_func: _MiddlewareMethod[_RequestT, _AppT, _P2], app: None = None, *_: _P2.args, **kw: _P2.kwargs + ) -> _MiddlewareFactory[_RequestT, _AppT, _P2]: ... + @overload + @classmethod + def middleware( + cls, middle_func: _MiddlewareMethod[_RequestT, _AppT, _P2], app: _AppT + ) -> type[wsgify[_RequestT, Concatenate[_AppT, _P2]]]: ... + @overload + @classmethod + def middleware( + cls, middle_func: _MiddlewareMethod[_RequestT, _AppT, _P2], app: _AppT, *_: _P2.args, **kw: _P2.kwargs + ) -> type[wsgify[_RequestT, Concatenate[_AppT, _P2]]]: ... -class _UnboundMiddleware: - wrapper_class: Incomplete - app: Incomplete - kw: Incomplete - def __init__(self, wrapper_class, app, kw) -> None: ... - def __call__(self, func, app: Incomplete | None = None): ... +class _unbound_wsgify(wsgify[_RequestT_contra, _P], Generic[_RequestT_contra, _P, _S]): + @overload # type: ignore[override] + def __call__(self, __self: _S, env: WSGIEnvironment, /, start_response: StartResponse) -> Iterable[bytes]: ... + @overload + def __call__(self, __self: _S, func: _RequestHandler[_RequestT_contra, _P], /) -> Self: ... + @overload + def __call__(self, __self: _S, req: _RequestT_contra) -> _AnyResponse: ... + @overload + def __call__(self, __self: _S, req: _RequestT_contra, *args: _P.args, **kw: _P.kwargs) -> _AnyResponse: ... -class _MiddlewareFactory: - wrapper_class: Incomplete - middleware: Incomplete - kw: Incomplete - def __init__(self, wrapper_class, middleware, kw) -> None: ... - def __call__(self, app: Incomplete | None = None, **config): ... +class _UnboundMiddleware(Generic[_RequestT_contra, _AppT_contra, _P]): + wrapper_class: type[wsgify[_RequestT_contra, Concatenate[_AppT_contra, _P]]] + app: _AppT_contra | None + kw: dict[str, Any] + def __init__( + self, + wrapper_class: type[wsgify[_RequestT_contra, Concatenate[_AppT_contra, _P]]], + app: _AppT_contra | None, + kw: dict[str, Any], + ) -> None: ... + @overload + def __call__(self, func: None, app: _AppT_contra | None = None) -> Self: ... + @overload + def __call__( + self, func: _Middleware[_RequestT_contra, _AppT_contra, _P], app: None = None + ) -> wsgify[_RequestT_contra, Concatenate[_AppT_contra, _P]]: ... + @overload + def __call__( + self, func: _Middleware[_RequestT_contra, _AppT_contra, _P], app: _AppT_contra + ) -> wsgify[_RequestT_contra, Concatenate[_AppT_contra, _P]]: ... + +class _MiddlewareFactory(Generic[_RequestT_contra, _AppT_contra, _P]): + wrapper_class: type[wsgify[_RequestT_contra, Concatenate[_AppT_contra, _P]]] + middleware: _Middleware[_RequestT_contra, _AppT_contra, _P] + kw: dict[str, Any] + def __init__( + self, + wrapper_class: type[wsgify[_RequestT_contra, Concatenate[_AppT_contra, _P]]], + middleware: _Middleware[_RequestT_contra, _AppT_contra, _P], + kw: dict[str, Any], + ) -> None: ... + # NOTE: Technically you are not allowed to pass args, but we give up all kinds + # of other safety if we don't use ParamSpec + @overload + def __call__( + self, app: None = None, *_: _P.args, **config: _P.kwargs + ) -> _MiddlewareFactory[_RequestT_contra, _AppT_contra, []]: ... + @overload + def __call__(self, app: _AppT_contra, *_: _P.args, **config: _P.kwargs) -> wsgify[_RequestT_contra, [_AppT_contra]]: ... diff --git a/stubs/WebOb/webob/descriptors.pyi b/stubs/WebOb/webob/descriptors.pyi index 6119896b6..d76b56791 100644 --- a/stubs/WebOb/webob/descriptors.pyi +++ b/stubs/WebOb/webob/descriptors.pyi @@ -1,11 +1,30 @@ -from _typeshed import Incomplete -from collections.abc import Iterable +from collections.abc import Callable, Iterable from datetime import date, datetime, timedelta from time import _TimeTuple, struct_time from typing import Any, Generic, NamedTuple, TypeVar, overload +from typing_extensions import TypeAlias +from webob.byterange import ContentRange, Range +from webob.etag import IfRange, IfRangeDate + +_T = TypeVar("_T") +_DefaultT = TypeVar("_DefaultT") _GetterReturnType = TypeVar("_GetterReturnType") _SetterValueType = TypeVar("_SetterValueType") +_ConvertedGetterReturnType = TypeVar("_ConvertedGetterReturnType") +_ConvertedSetterValueType = TypeVar("_ConvertedSetterValueType") +_ContentRangeParams: TypeAlias = ( + ContentRange + | list[int] + | list[None] + | list[int | None] + | tuple[int, int] + | tuple[None, None] + | tuple[int, int, int | None] + | tuple[None, None, int | None] + | str + | None +) class _AsymmetricProperty(Generic[_GetterReturnType, _SetterValueType]): @overload @@ -17,36 +36,67 @@ class _AsymmetricProperty(Generic[_GetterReturnType, _SetterValueType]): class _AsymmetricPropertyWithDelete(_AsymmetricProperty[_GetterReturnType, _SetterValueType]): def __delete__(self, __obj: Any) -> None: ... -class _StringProperty(_AsymmetricPropertyWithDelete[str | None, str | None]): ... +class _SymmetricProperty(_AsymmetricProperty[_T, _T]): ... +class _SymmetricPropertyWithDelete(_AsymmetricPropertyWithDelete[_T, _T]): ... +class _StringProperty(_SymmetricPropertyWithDelete[str | None]): ... class _ListProperty(_AsymmetricPropertyWithDelete[tuple[str, ...] | None, Iterable[str] | str | None]): ... class _DateProperty( _AsymmetricPropertyWithDelete[datetime | None, date | datetime | timedelta | _TimeTuple | struct_time | float | str | None] ): ... -def environ_getter(key, default=..., rfc_section: Incomplete | None = None): ... -def environ_decoder(key, default=..., rfc_section: Incomplete | None = None, encattr: Incomplete | None = None): ... -def upath_property(key): ... -def deprecated_property(attr, name, text, version): ... +@overload +def environ_getter(key: str, default: None, rfc_section: str | None = None) -> _SymmetricPropertyWithDelete[Any | None]: ... +@overload +def environ_getter( + key: str, default: _DefaultT, rfc_section: str | None = None +) -> _AsymmetricPropertyWithDelete[Any | _DefaultT, Any | _DefaultT | None]: ... +@overload +def environ_getter(key: str, *, rfc_section: str | None = None) -> _SymmetricProperty[Any]: ... +@overload +def environ_decoder( + key: str, default: str, rfc_section: str | None = None, encattr: str | None = None +) -> _AsymmetricPropertyWithDelete[str, str | None]: ... +@overload +def environ_decoder( + key: str, default: None, rfc_section: str | None = None, encattr: str | None = None +) -> _SymmetricPropertyWithDelete[str | None]: ... +@overload +def environ_decoder(key: str, *, rfc_section: str | None = None, encattr: str | None = None) -> _SymmetricProperty[str]: ... +def upath_property(key: str) -> _SymmetricProperty[str]: ... +def deprecated_property(attr: _T, name: str, text: str, version: str) -> _T: ... def header_getter(header: str, rfc_section: str) -> _StringProperty: ... -def converter(prop, parse, serialize, convert_name: Incomplete | None = None): ... +@overload +def converter( + prop: _AsymmetricPropertyWithDelete[_GetterReturnType, _SetterValueType], + parse: Callable[[_GetterReturnType], _ConvertedGetterReturnType], + serialize: Callable[[_ConvertedSetterValueType], _SetterValueType], + convert_name: str | None = None, +) -> _AsymmetricPropertyWithDelete[_ConvertedGetterReturnType, _ConvertedSetterValueType]: ... +@overload +def converter( + prop: _AsymmetricProperty[_GetterReturnType, _SetterValueType], + parse: Callable[[_GetterReturnType], _ConvertedGetterReturnType], + serialize: Callable[[_ConvertedSetterValueType], _SetterValueType], + convert_name: str | None = None, +) -> _AsymmetricProperty[_ConvertedGetterReturnType, _ConvertedSetterValueType]: ... def list_header(header: str, rfc_section: str) -> _ListProperty: ... -def parse_list(value): ... -def serialize_list(value): ... -def converter_date(prop): ... +def parse_list(value: str | None) -> tuple[str, ...] | None: ... +def serialize_list(value: Iterable[str] | str) -> str: ... +def converter_date(prop: _StringProperty) -> _DateProperty: ... def date_header(header: str, rfc_section: str) -> _DateProperty: ... -def parse_etag_response(value, strong: bool = False): ... -def serialize_etag_response(value): ... -def serialize_if_range(value): ... -def parse_range(value): ... -def serialize_range(value): ... -def parse_int(value): ... -def parse_int_safe(value): ... +def parse_etag_response(value: str | None, strong: bool = False) -> str | None: ... +def serialize_etag_response(value: str | tuple[str, bool]) -> str: ... +def serialize_if_range(value: IfRange | IfRangeDate | datetime | date | str) -> str | None: ... +def parse_range(value: str | None) -> Range | None: ... +def serialize_range(value: tuple[int, int | None] | list[int | None] | list[int] | str | None) -> str | None: ... +def parse_int(value: str | None) -> int | None: ... +def parse_int_safe(value: str | None) -> int | None: ... -serialize_int = str +serialize_int: Callable[[int], str] -def parse_content_range(value): ... -def serialize_content_range(value): ... -def parse_auth_params(params): ... +def parse_content_range(value: str | None) -> ContentRange | None: ... +def serialize_content_range(value: _ContentRangeParams) -> str | None: ... +def parse_auth_params(params: str) -> dict[str, str]: ... known_auth_schemes: dict[str, None] @@ -54,5 +104,5 @@ class _authorization(NamedTuple): authtype: str params: dict[str, str] | str -def parse_auth(val): ... -def serialize_auth(val): ... +def parse_auth(val: str | None) -> _authorization | None: ... +def serialize_auth(val: tuple[str, dict[str, str] | str] | list[Any] | str | None) -> str | None: ... diff --git a/stubs/WebOb/webob/multidict.pyi b/stubs/WebOb/webob/multidict.pyi index d2d9a0768..c8470952d 100644 --- a/stubs/WebOb/webob/multidict.pyi +++ b/stubs/WebOb/webob/multidict.pyi @@ -11,9 +11,9 @@ _VT = TypeVar("_VT") class MultiDict(MutableMapping[_KT, _VT]): @overload - def __init__(self, __m: SupportsItems[_KT, _VT], **kwargs: _VT): ... + def __init__(self, __m: SupportsItems[_KT, _VT], **kwargs: _VT) -> None: ... @overload - def __init__(self, __m: Iterable[tuple[_KT, _VT]], **kwargs: _VT): ... + def __init__(self, __m: Iterable[tuple[_KT, _VT]], **kwargs: _VT) -> None: ... @overload def __init__(self, **kwargs: _VT) -> None: ... @classmethod diff --git a/stubs/WebOb/webob/request.pyi b/stubs/WebOb/webob/request.pyi index 4526091ce..d57118866 100644 --- a/stubs/WebOb/webob/request.pyi +++ b/stubs/WebOb/webob/request.pyi @@ -1,14 +1,6 @@ import datetime import io -from _typeshed import ( - ExcInfo, - Incomplete, - ReadableBuffer, - SupportsItems, - SupportsKeysAndGetItem, - SupportsNoArgReadline, - SupportsRead, -) +from _typeshed import ExcInfo, ReadableBuffer, SupportsItems, SupportsKeysAndGetItem, SupportsNoArgReadline, SupportsRead from _typeshed.wsgi import WSGIApplication, WSGIEnvironment from cgi import FieldStorage from collections.abc import Iterable, Mapping @@ -55,8 +47,11 @@ class BaseRequest: environ: WSGIEnvironment method: _HTTPMethod def __init__(self, environ: WSGIEnvironment, **kw: Any) -> None: ... - def encget(self, key: str, default: Any = ..., encattr: str | None = None) -> Any: ... - def encset(self, key: str, val: Any, encattr: str | None = None) -> None: ... + @overload + def encget(self, key: str, default: _T, encattr: str | None = None) -> str | _T: ... + @overload + def encget(self, key: str, *, encattr: str | None = None) -> str: ... + def encset(self, key: str, val: str, encattr: str | None = None) -> None: ... @property def charset(self) -> str | None: ... def decode(self, charset: str | None = None, errors: str = "strict") -> Self: ... @@ -96,7 +91,10 @@ class BaseRequest: @path_info.setter def path_info(self, value: str | None) -> None: ... uscript_name: str # bw compat - upath_info = path_info # bw compat + @property + def upath_info(self) -> str | None: ... # bw compat + @upath_info.setter + def upath_info(self, value: str | None) -> None: ... # bw compat content_type: str | None headers: _AsymmetricProperty[EnvironHeaders, SupportsItems[str, str] | Iterable[tuple[str, str]]] @property @@ -166,7 +164,7 @@ class BaseRequest: ] max_forwards: int | None pragma: str | None - range: _AsymmetricPropertyWithDelete[Range, tuple[int, int | None] | list[int | None] | str | None] + range: _AsymmetricPropertyWithDelete[Range | None, tuple[int, int | None] | list[int | None] | list[int] | str | None] referer: str | None referrer: str | None user_agent: str | None @@ -198,20 +196,31 @@ class BaseRequest: base_url: str | None = None, headers: Mapping[str, str] | None = None, POST: str | bytes | Mapping[Any, Any] | Mapping[Any, _ListOrTuple[Any]] | None = None, - **kw, + **kw: Any, ) -> Self: ... class LegacyRequest(BaseRequest): - uscript_name: Incomplete - upath_info: Incomplete - def encget(self, key, default=..., encattr: Incomplete | None = None): ... + @property + def uscript_name(self) -> str: ... + @uscript_name.setter + def uscript_name(self, value: str) -> None: ... + @property # type:ignore[override] + def upath_info(self) -> str: ... + @upath_info.setter + def upath_info(self, value: str) -> None: ... + def encget(self, key: str, default: Any = ..., encattr: str | None = None) -> Any: ... class AdhocAttrMixin: def __setattr__(self, attr: str, value: Any) -> None: ... def __getattr__(self, attr: str) -> Any: ... def __delattr__(self, attr: str) -> None: ... -class Request(AdhocAttrMixin, BaseRequest): ... +class Request(AdhocAttrMixin, BaseRequest): + # this is so Request doesn't count as callable, it's not very pretty + # but we run into trouble with overlapping overloads in wsgify if we + # don't exclude __call__ from arbitrary attribute access + __call__: None + class DisconnectionError(IOError): ... def environ_from_url(path: str) -> WSGIEnvironment: ... diff --git a/stubs/WebOb/webob/response.pyi b/stubs/WebOb/webob/response.pyi index c7f4e5158..afa81270a 100644 --- a/stubs/WebOb/webob/response.pyi +++ b/stubs/WebOb/webob/response.pyi @@ -8,7 +8,14 @@ from typing_extensions import TypeAlias from webob.byterange import ContentRange from webob.cachecontrol import _ResponseCacheControl from webob.cookies import _SameSitePolicy -from webob.descriptors import _AsymmetricProperty, _AsymmetricPropertyWithDelete, _authorization, _DateProperty, _ListProperty +from webob.descriptors import ( + _AsymmetricProperty, + _AsymmetricPropertyWithDelete, + _authorization, + _ContentRangeParams, + _DateProperty, + _ListProperty, +) from webob.headers import ResponseHeaders from webob.request import Request @@ -46,16 +53,6 @@ class _ResponseCacheControlDict(TypedDict, total=False): stale_if_error: int _HTTPHeader: TypeAlias = tuple[str, str] -_ContentRangeParams: TypeAlias = ( - ContentRange - | list[int | None] - | tuple[int, int] - | tuple[None, None] - | tuple[int, int, int | None] - | tuple[None, None, int | None] - | str - | None -) class Response: default_content_type: str @@ -154,7 +151,7 @@ class ResponseBodyFile: mode: Literal["wb"] closed: Literal[False] response: Response - def __init__(self, response: Response): ... + def __init__(self, response: Response) -> None: ... @property def encoding(self) -> str | None: ... def write(self, text: str | bytes) -> int: ... diff --git a/stubs/WebOb/webob/util.pyi b/stubs/WebOb/webob/util.pyi index ea8ccec42..f0354c241 100644 --- a/stubs/WebOb/webob/util.pyi +++ b/stubs/WebOb/webob/util.pyi @@ -5,8 +5,8 @@ class _HasHTML(Protocol): def __html__(self) -> str: ... def html_escape(s: str | bytes | _HasHTML) -> str: ... -def header_docstring(header, rfc_section): ... -def warn_deprecation(text, version, stacklevel) -> None: ... +def header_docstring(header: str, rfc_section: str) -> str: ... +def warn_deprecation(text: str, version: str, stacklevel: int) -> None: ... status_reasons: dict[int, str] status_generic_reasons: dict[int, str]