WebOb: Complete the stubs and activate stricter pyright config (#11460)

This commit is contained in:
David Salvisberg
2024-02-29 07:57:50 +01:00
committed by GitHub
parent 3a06fc7c1a
commit fa164b2419
13 changed files with 448 additions and 113 deletions
+11
View File
@@ -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
@@ -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