From aba333bfb67d251883d8d39839b0d306a2559093 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Tue, 28 Jun 2022 10:53:03 +0300 Subject: [PATCH] Improve API docs (#2488) --- docs/conf.py | 6 +- docs/sanic/api/app.rst | 16 ++++++ docs/sanic/api/core.rst | 8 +++ docs/sanic/api/server.rst | 7 --- pyproject.toml | 1 + sanic/app.py | 5 +- sanic/http/__init__.py | 3 +- sanic/http/http1.py | 2 +- sanic/http/http3.py | 4 ++ sanic/log.py | 9 ++- sanic/request.py | 116 +++++++++++++++++++++++++++++++++----- setup.py | 1 + 12 files changed, 151 insertions(+), 27 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 30a01e4c..07bfe0f9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,11 @@ import sanic # -- General configuration ------------------------------------------------ -extensions = ["sphinx.ext.autodoc", "m2r2"] +extensions = [ + "sphinx.ext.autodoc", + "m2r2", + "enum_tools.autoenum", +] templates_path = ["_templates"] diff --git a/docs/sanic/api/app.rst b/docs/sanic/api/app.rst index 4d3c97bc..e51b3e3f 100644 --- a/docs/sanic/api/app.rst +++ b/docs/sanic/api/app.rst @@ -15,3 +15,19 @@ sanic.config .. automodule:: sanic.config :members: :show-inheritance: + +sanic.application.constants +--------------------------- + +.. automodule:: sanic.application.constants + :exclude-members: StrEnum + :members: + :show-inheritance: + :inherited-members: + +sanic.application.state +----------------------- + +.. automodule:: sanic.application.state + :members: + :show-inheritance: diff --git a/docs/sanic/api/core.rst b/docs/sanic/api/core.rst index 1ed9070a..441eb13b 100644 --- a/docs/sanic/api/core.rst +++ b/docs/sanic/api/core.rst @@ -17,6 +17,14 @@ sanic.handlers :show-inheritance: +sanic.headers +-------------- + +.. automodule:: sanic.headers + :members: + :show-inheritance: + + sanic.request ------------- diff --git a/docs/sanic/api/server.rst b/docs/sanic/api/server.rst index c84d93cb..f42aa66e 100644 --- a/docs/sanic/api/server.rst +++ b/docs/sanic/api/server.rst @@ -16,10 +16,3 @@ sanic.server :members: :show-inheritance: - -sanic.worker ------------- - -.. automodule:: sanic.worker - :members: - :show-inheritance: diff --git a/pyproject.toml b/pyproject.toml index 7c5e2960..35d82408 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ profile = "black" [[tool.mypy.overrides]] module = [ + "httptools.*", "trustme.*", "sanic_routing.*", ] diff --git a/sanic/app.py b/sanic/app.py index a58c5fe6..ec266147 100644 --- a/sanic/app.py +++ b/sanic/app.py @@ -1369,7 +1369,10 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta): self.config.AUTO_RELOAD = value @property - def state(self): + def state(self) -> ApplicationState: # type: ignore + """ + :return: The application state + """ return self._state @property diff --git a/sanic/http/__init__.py b/sanic/http/__init__.py index 8a961029..10451282 100644 --- a/sanic/http/__init__.py +++ b/sanic/http/__init__.py @@ -1,5 +1,6 @@ from .constants import Stage from .http1 import Http +from .http3 import Http3 -__all__ = ("Http", "Stage") +__all__ = ("Http", "Stage", "Http3") diff --git a/sanic/http/http1.py b/sanic/http/http1.py index 1f7870ee..fefa6eeb 100644 --- a/sanic/http/http1.py +++ b/sanic/http/http1.py @@ -30,7 +30,7 @@ HTTP_CONTINUE = b"HTTP/1.1 100 Continue\r\n\r\n" class Http(Stream, metaclass=TouchUpMeta): """ - Internal helper for managing the HTTP request/response cycle + Internal helper for managing the HTTP/1.1 request/response cycle :raises ServerError: :raises PayloadTooLarge: diff --git a/sanic/http/http3.py b/sanic/http/http3.py index 09ecad5b..f48fa7ee 100644 --- a/sanic/http/http3.py +++ b/sanic/http/http3.py @@ -265,6 +265,10 @@ class WebTransportReceiver(Receiver): # noqa class Http3: + """ + Internal helper for managing the HTTP/3 request/response cycle + """ + HANDLER_PROPERTY_MAPPING = { DataReceived: "stream_id", HeadersReceived: "stream_id", diff --git a/sanic/log.py b/sanic/log.py index 0c3ff826..d337f5d4 100644 --- a/sanic/log.py +++ b/sanic/log.py @@ -57,6 +57,9 @@ LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov }, }, ) +""" +Defult logging configuration +""" class Colors(str, Enum): # no cov @@ -80,22 +83,22 @@ class VerbosityFilter(logging.Filter): _verbosity_filter = VerbosityFilter() logger = logging.getLogger("sanic.root") # no cov -logger.addFilter(_verbosity_filter) """ General Sanic logger """ +logger.addFilter(_verbosity_filter) error_logger = logging.getLogger("sanic.error") # no cov -error_logger.addFilter(_verbosity_filter) """ Logger used by Sanic for error logging """ +error_logger.addFilter(_verbosity_filter) access_logger = logging.getLogger("sanic.access") # no cov -access_logger.addFilter(_verbosity_filter) """ Logger used by Sanic for access logging """ +access_logger.addFilter(_verbosity_filter) def deprecation(message: str, version: float): # no cov diff --git a/sanic/request.py b/sanic/request.py index c3ef5048..9c6336fe 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -34,12 +34,12 @@ from http.cookies import SimpleCookie from types import SimpleNamespace from urllib.parse import parse_qs, parse_qsl, unquote, urlunparse -from httptools import parse_url # type: ignore -from httptools.parser.errors import HttpParserInvalidURLError # type: ignore +from httptools import parse_url +from httptools.parser.errors import HttpParserInvalidURLError from sanic.compat import CancelledErrors, Header from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE -from sanic.exceptions import BadRequest, BadURL, SanicException, ServerError +from sanic.exceptions import BadRequest, BadURL, ServerError from sanic.headers import ( AcceptContainer, Options, @@ -186,9 +186,27 @@ class Request: @classmethod def get_current(cls) -> Request: + """ + Retrieve the currrent request object + + This implements `Context Variables + `_ + to allow for accessing the current request from anywhere. + + Raises :exc:`sanic.exceptions.ServerError` if it is outside of + a request lifecycle. + + .. code-block:: python + + from sanic import Request + + current_request = Request.get_current() + + :return: the current :class:`sanic.request.Request` + """ request = cls._current.get(None) if not request: - raise SanicException("No current request") + raise ServerError("No current request") return request @classmethod @@ -197,6 +215,12 @@ class Request: @property def stream_id(self): + """ + Access the HTTP/3 stream ID. + + Raises :exc:`sanic.exceptions.ServerError` if it is not an + HTTP/3 request. + """ if self.protocol.version is not HTTP.VERSION_3: raise ServerError( "Stream ID is only a property of a HTTP/3 request" @@ -319,7 +343,19 @@ class Request: self.body = b"".join([data async for data in self.stream]) @property - def name(self): + def name(self) -> Optional[str]: + """ + The route name + + In the following pattern: + + .. code-block:: + + .[.] + + :return: Route name + :rtype: Optional[str] + """ if self._name: return self._name elif self.route: @@ -327,26 +363,47 @@ class Request: return None @property - def endpoint(self): + def endpoint(self) -> Optional[str]: + """ + :return: Alias of :attr:`sanic.request.Request.name` + :rtype: Optional[str] + """ return self.name @property - def uri_template(self): - return f"/{self.route.path}" + def uri_template(self) -> Optional[str]: + """ + :return: The defined URI template + :rtype: Optional[str] + """ + if self.route: + return f"/{self.route.path}" + return None @property def protocol(self): + """ + :return: The HTTP protocol instance + """ if not self._protocol: self._protocol = self.transport.get_protocol() return self._protocol @property - def raw_headers(self): + def raw_headers(self) -> bytes: + """ + :return: The unparsed HTTP headers + :rtype: bytes + """ _, headers = self.head.split(b"\r\n", 1) return bytes(headers) @property - def request_line(self): + def request_line(self) -> bytes: + """ + :return: The first line of a HTTP request + :rtype: bytes + """ reqline, _ = self.head.split(b"\r\n", 1) return bytes(reqline) @@ -395,7 +452,11 @@ class Request: return self._id # type: ignore @property - def json(self): + def json(self) -> Any: + """ + :return: The request body parsed as JSON + :rtype: Any + """ if self.parsed_json is None: self.load_json() @@ -413,6 +474,10 @@ class Request: @property def accept(self) -> AcceptContainer: + """ + :return: The ``Accept`` header parsed + :rtype: AcceptContainer + """ if self.parsed_accept is None: accept_header = self.headers.getone("accept", "") self.parsed_accept = parse_accept(accept_header) @@ -458,6 +523,15 @@ class Request: def get_form( self, keep_blank_values: bool = False ) -> Optional[RequestParameters]: + """ + Method to extract and parse the form data from a request. + + :param keep_blank_values: + Whether to discard blank values from the form data + :type keep_blank_values: bool + :return: the parsed form data + :rtype: Optional[RequestParameters] + """ self.parsed_form = RequestParameters() self.parsed_files = RequestParameters() content_type = self.headers.getone( @@ -487,6 +561,9 @@ class Request: @property def form(self): + """ + :return: The request body parsed as form data + """ if self.parsed_form is None: self.get_form() @@ -494,6 +571,9 @@ class Request: @property def files(self): + """ + :return: The request body parsed as uploaded files + """ if self.parsed_files is None: self.form # compute form to get files @@ -507,8 +587,8 @@ class Request: errors: str = "replace", ) -> RequestParameters: """ - Method to parse `query_string` using `urllib.parse.parse_qs`. - This methods is used by `args` property. + Method to parse ``query_string`` using ``urllib.parse.parse_qs``. + This methods is used by ``args`` property. Can be used directly if you need to change default parameters. :param keep_blank_values: @@ -557,6 +637,10 @@ class Request: ] args = property(get_args) + """ + Convenience property to access :meth:`Request.get_args` with + default values. + """ def get_query_args( self, @@ -676,6 +760,9 @@ class Request: @property def socket(self): + """ + :return: Information about the connected socket if available + """ return self.conn_info.peername if self.conn_info else (None, None) @property @@ -688,6 +775,9 @@ class Request: @property def network_paths(self): + """ + Access the network paths if available + """ return self.conn_info.network_paths # Proxy properties (using SERVER_NAME/forwarded/request/transport info) diff --git a/setup.py b/setup.py index 9579ad26..f752a62e 100644 --- a/setup.py +++ b/setup.py @@ -122,6 +122,7 @@ docs_require = [ "docutils", "pygments", "m2r2", + "enum-tools[sphinx]", "mistune<2.0.0", ]