From 0ddc63b240340a952692b11dfe0810973393ed11 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Sat, 16 Apr 2022 19:37:58 +0200 Subject: gh-86178: Add wsgiref.types (GH-32335) --- Doc/library/wsgiref.rst | 68 +++++++++++++++++++--- Doc/whatsnew/3.11.rst | 4 ++ Lib/wsgiref/__init__.py | 2 + Lib/wsgiref/types.py | 54 +++++++++++++++++ .../2022-04-05-17-18-13.bpo-42012.zMocQz.rst | 2 + 5 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 Lib/wsgiref/types.py create mode 100644 Misc/NEWS.d/next/Library/2022-04-05-17-18-13.bpo-42012.zMocQz.rst diff --git a/Doc/library/wsgiref.rst b/Doc/library/wsgiref.rst index e924448..0ca7b00 100644 --- a/Doc/library/wsgiref.rst +++ b/Doc/library/wsgiref.rst @@ -23,6 +23,7 @@ an existing framework. be used to add WSGI support to a web server or framework. It provides utilities for manipulating WSGI environment variables and response headers, base classes for implementing WSGI servers, a demo HTTP server that serves WSGI applications, +types for static type checking, and a validation tool that checks WSGI servers and applications for conformance to the WSGI specification (:pep:`3333`). @@ -43,7 +44,9 @@ This module provides a variety of utility functions for working with WSGI environments. A WSGI environment is a dictionary containing HTTP request variables as described in :pep:`3333`. All of the functions taking an *environ* parameter expect a WSGI-compliant dictionary to be supplied; please see -:pep:`3333` for a detailed specification. +:pep:`3333` for a detailed specification and +:data:`~wsgiref.types.WSGIEnvironment` for a type alias that can be used +in type annotations. .. function:: guess_scheme(environ) @@ -150,7 +153,9 @@ also provides these miscellaneous utilities: .. class:: FileWrapper(filelike, blksize=8192) - A wrapper to convert a file-like object to an :term:`iterator`. The resulting objects + A concrete implementation of the :class:`wsgiref.types.FileWrapper` + protocol used to convert a file-like object to an :term:`iterator`. + The resulting objects are :term:`iterable`\ s. As the object is iterated over, the optional *blksize* parameter will be repeatedly passed to the *filelike* object's :meth:`read` method to obtain bytestrings to yield. When :meth:`read` @@ -349,7 +354,8 @@ request. (E.g., using the :func:`shift_path_info` function from .. method:: WSGIRequestHandler.get_environ() - Returns a dictionary containing the WSGI environment for a request. The default + Return a :data:`~wsgiref.types.WSGIEnvironment` dictionary for a + request. The default implementation copies the contents of the :class:`WSGIServer` object's :attr:`base_environ` dictionary attribute and then adds various headers derived from the HTTP request. Each call to this method should return a new dictionary @@ -558,13 +564,15 @@ input, output, and error streams. .. method:: BaseHandler.get_stdin() - Return an input stream object suitable for use as the ``wsgi.input`` of the + Return an object compatible with :class:`~wsgiref.types.InputStream` + suitable for use as the ``wsgi.input`` of the request currently being processed. .. method:: BaseHandler.get_stderr() - Return an output stream object suitable for use as the ``wsgi.errors`` of the + Return an object compatible with :class:`~wsgiref.types.ErrorStream` + suitable for use as the ``wsgi.errors`` of the request currently being processed. @@ -703,8 +711,9 @@ input, output, and error streams. .. attribute:: BaseHandler.wsgi_file_wrapper - A ``wsgi.file_wrapper`` factory, or ``None``. The default value of this - attribute is the :class:`wsgiref.util.FileWrapper` class. + A ``wsgi.file_wrapper`` factory, compatible with + :class:`wsgiref.types.FileWrapper`, or ``None``. The default value + of this attribute is the :class:`wsgiref.util.FileWrapper` class. .. method:: BaseHandler.sendfile() @@ -754,6 +763,51 @@ input, output, and error streams. .. versionadded:: 3.2 +:mod:`wsgiref.types` -- WSGI types for static type checking +----------------------------------------------------------- + +.. module:: wsgiref.types + :synopsis: WSGI types for static type checking + + +This module provides various types for static type checking as described +in :pep:`3333`. + +.. versionadded:: 3.11 + + +.. class:: StartResponse() + + A :class:`typing.Protocol` describing `start_response() + `_ + callables (:pep:`3333`). + +.. data:: WSGIEnvironment + + A type alias describing a WSGI environment dictionary. + +.. data:: WSGIApplication + + A type alias describing a WSGI application callable. + +.. class:: InputStream() + + A :class:`typing.Protocol` describing a `WSGI Input Stream + `_. + +.. class:: ErrorStream() + + A :class:`typing.Protocol` describing a `WSGI Error Stream + `_. + +.. class:: FileWrapper() + + A :class:`typing.Protocol` describing a `file wrapper + `_. + See :class:`wsgiref.util.FileWrapper` for a concrete implementation of this + protocol. + + Examples -------- diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index b5b2a76..19e3d2f 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -233,6 +233,10 @@ New Modules * A new module, :mod:`tomllib`, was added for parsing TOML. (Contributed by Taneli Hukkinen in :issue:`40059`.) +* :mod:`wsgiref.types`, containing WSGI-specific types for static type + checking, was added. + (Contributed by Sebastian Rittau in :issue:`42012`.) + Improved Modules ================ diff --git a/Lib/wsgiref/__init__.py b/Lib/wsgiref/__init__.py index 1efbba0..59ee48f 100644 --- a/Lib/wsgiref/__init__.py +++ b/Lib/wsgiref/__init__.py @@ -13,6 +13,8 @@ Current Contents: * validate -- validation wrapper that sits between an app and a server to detect errors in either +* types -- collection of WSGI-related types for static type checking + To-Do: * cgi_gateway -- Run WSGI apps under CGI (pending a deployment standard) diff --git a/Lib/wsgiref/types.py b/Lib/wsgiref/types.py new file mode 100644 index 0000000..4a519e5 --- /dev/null +++ b/Lib/wsgiref/types.py @@ -0,0 +1,54 @@ +"""WSGI-related types for static type checking""" + +from collections.abc import Callable, Iterable +from types import TracebackType +from typing import Any, Protocol, TypeAlias + +__all__ = [ + "StartResponse", + "WSGIEnvironment", + "WSGIApplication", + "InputStream", + "ErrorStream", + "FileWrapper", +] + +_ExcInfo = tuple[type[BaseException], BaseException, TracebackType] +_OptExcInfo = _ExcInfo | tuple[None, None, None] + +class StartResponse(Protocol): + """start_response() callable as defined in PEP 3333""" + def __call__( + self, + status: str, + headers: list[tuple[str, str]], + exc_info: _OptExcInfo | None = ..., + /, + ) -> Callable[[bytes], object]: ... + +WSGIEnvironment: TypeAlias = dict[str, Any] +WSGIApplication: TypeAlias = Callable[[WSGIEnvironment, StartResponse], + Iterable[bytes]] + +class InputStream(Protocol): + """WSGI input stream as defined in PEP 3333""" + def read(self, size: int = ..., /) -> bytes: ... + def readline(self, size: int = ..., /) -> bytes: ... + def readlines(self, hint: int = ..., /) -> list[bytes]: ... + def __iter__(self) -> Iterable[bytes]: ... + +class ErrorStream(Protocol): + """WSGI error stream as defined in PEP 3333""" + def flush(self) -> object: ... + def write(self, s: str, /) -> object: ... + def writelines(self, seq: list[str], /) -> object: ... + +class _Readable(Protocol): + def read(self, size: int = ..., /) -> bytes: ... + # Optional: def close(self) -> object: ... + +class FileWrapper(Protocol): + """WSGI file wrapper as defined in PEP 3333""" + def __call__( + self, file: _Readable, block_size: int = ..., /, + ) -> Iterable[bytes]: ... diff --git a/Misc/NEWS.d/next/Library/2022-04-05-17-18-13.bpo-42012.zMocQz.rst b/Misc/NEWS.d/next/Library/2022-04-05-17-18-13.bpo-42012.zMocQz.rst new file mode 100644 index 0000000..ba84041 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-04-05-17-18-13.bpo-42012.zMocQz.rst @@ -0,0 +1,2 @@ +Add :mod:`wsgiref.types`, containing WSGI-specific types for static type +checking. -- cgit v0.12