Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve API docs #2488

Merged
merged 1 commit into from Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/conf.py
Expand Up @@ -24,7 +24,11 @@

# -- General configuration ------------------------------------------------

extensions = ["sphinx.ext.autodoc", "m2r2"]
extensions = [
"sphinx.ext.autodoc",
"m2r2",
"enum_tools.autoenum",
]

templates_path = ["_templates"]

Expand Down
16 changes: 16 additions & 0 deletions docs/sanic/api/app.rst
Expand Up @@ -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:
8 changes: 8 additions & 0 deletions docs/sanic/api/core.rst
Expand Up @@ -17,6 +17,14 @@ sanic.handlers
:show-inheritance:


sanic.headers
--------------

.. automodule:: sanic.headers
:members:
:show-inheritance:


sanic.request
-------------

Expand Down
7 changes: 0 additions & 7 deletions docs/sanic/api/server.rst
Expand Up @@ -16,10 +16,3 @@ sanic.server
:members:
:show-inheritance:


sanic.worker
------------

.. automodule:: sanic.worker
:members:
:show-inheritance:
1 change: 1 addition & 0 deletions pyproject.toml
Expand Up @@ -19,6 +19,7 @@ profile = "black"

[[tool.mypy.overrides]]
module = [
"httptools.*",
"trustme.*",
"sanic_routing.*",
]
Expand Down
5 changes: 4 additions & 1 deletion sanic/app.py
Expand Up @@ -1369,7 +1369,10 @@ def auto_reload(self, value: bool):
self.config.AUTO_RELOAD = value

@property
def state(self):
def state(self) -> ApplicationState: # type: ignore
"""
:return: The application state
"""
return self._state

@property
Expand Down
3 changes: 2 additions & 1 deletion 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")
2 changes: 1 addition & 1 deletion sanic/http/http1.py
Expand Up @@ -30,7 +30,7 @@

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:
Expand Down
4 changes: 4 additions & 0 deletions sanic/http/http3.py
Expand Up @@ -265,6 +265,10 @@ async def run(self):


class Http3:
"""
Internal helper for managing the HTTP/3 request/response cycle
"""

HANDLER_PROPERTY_MAPPING = {
DataReceived: "stream_id",
HeadersReceived: "stream_id",
Expand Down
9 changes: 6 additions & 3 deletions sanic/log.py
Expand Up @@ -57,6 +57,9 @@
},
},
)
"""
Defult logging configuration
"""


class Colors(str, Enum): # no cov
Expand All @@ -80,22 +83,22 @@ def filter(self, record: logging.LogRecord) -> bool:
_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
Expand Down
116 changes: 103 additions & 13 deletions sanic/request.py
Expand Up @@ -34,12 +34,12 @@
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,
Expand Down Expand Up @@ -186,9 +186,27 @@ def __repr__(self):

@classmethod
def get_current(cls) -> Request:
"""
Retrieve the currrent request object

This implements `Context Variables
<https://docs.python.org/3/library/contextvars.html>`_
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
Expand All @@ -197,6 +215,12 @@ def generate_id(*_):

@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"
Expand Down Expand Up @@ -319,34 +343,67 @@ async def receive_body(self):
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::

<AppName>.[<BlueprintName>.]<HandlerName>

:return: Route name
:rtype: Optional[str]
"""
if self._name:
return self._name
elif self.route:
return self.route.name
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)

Expand Down Expand Up @@ -395,7 +452,11 @@ def generate_id(self):
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()

Expand All @@ -413,6 +474,10 @@ def load_json(self, loads=json_loads):

@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)
Expand Down Expand Up @@ -458,6 +523,15 @@ def credentials(self) -> Optional[Credentials]:
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(
Expand Down Expand Up @@ -487,13 +561,19 @@ def get_form(

@property
def form(self):
"""
:return: The request body parsed as form data
"""
if self.parsed_form is None:
self.get_form()

return self.parsed_form

@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

Expand All @@ -507,8 +587,8 @@ def get_args(
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:
Expand Down Expand Up @@ -557,6 +637,10 @@ def get_args(
]

args = property(get_args)
"""
Convenience property to access :meth:`Request.get_args` with
default values.
"""

def get_query_args(
self,
Expand Down Expand Up @@ -676,6 +760,9 @@ def port(self) -> int:

@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
Expand All @@ -688,6 +775,9 @@ def path(self) -> str:

@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)
Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -122,6 +122,7 @@ def open_local(paths, mode="r", encoding="utf8"):
"docutils",
"pygments",
"m2r2",
"enum-tools[sphinx]",
"mistune<2.0.0",
]

Expand Down