Documentation updates for typing.

The changes to `api.rst` are particularly important since that is where
I describe the externally available type annotations.
This commit is contained in:
Dave Shawley 2021-10-01 08:35:10 -04:00
parent acc0a1db14
commit 4a0ca5e390
No known key found for this signature in database
GPG key ID: F41A8A99298F8EED
6 changed files with 157 additions and 79 deletions

View file

@ -1,7 +1,27 @@
API Documentation
=================
.. currentmodule:: sprockets.mixins.mediatype.content
The easiest way to use this library is to:
#. call :func:`.install` when you create your application instance and specify a
default content type
#. call :func:`.add_transcoder` to install transcoders for the content types that
you support -- use :func:`.add_binary_content_type` and/or
:func:`.add_text_content_type` if you don't want to define a
:class:`~sprockets.mixins.mediatype.type_info.Transcoder` class.
#. include :class:`.ContentMixin` in your handler's inheritance chain
#. call :meth:`~.ContentMixin.get_request_body` to retrieve a request body
sent in any of the supported content types
#. call :meth:`~.ContentMixin.send_response` to send a response in any of the
supported content types
The :class:`.ContentMixin` will take care of inspecting the :http:header:`Content-Type`
header and deserialize the request as well as implementing the
:rfc:`proactive content negotiation algorithm <7231#section-3.4.1>` described in
:rfc:`7231` to serialize a response object appropriately.
Content Type Handling
---------------------
.. autoclass:: ContentMixin
@ -11,7 +31,7 @@ Content Type Registration
-------------------------
.. autofunction:: install
.. autofunction:: get_settings
.. autofunction:: add_transcoder
.. autofunction:: set_default_content_type
@ -19,7 +39,7 @@ Content Type Registration
.. autofunction:: add_text_content_type
.. autofunction:: add_transcoder
.. autofunction:: get_settings
.. autoclass:: ContentSettings
:members:
@ -33,3 +53,43 @@ Bundled Transcoders
.. autoclass:: MsgPackTranscoder
:members:
.. _type-info:
Python Type Information
-----------------------
The ``sprockets.mixins.mediatype.type_info`` module contains a number of
convenience type definitions for those you you who take advantage of type
annotations.
.. currentmodule:: sprockets.mixins.mediatype.type_info
Interface Types
~~~~~~~~~~~~~~~
.. autoclass:: Transcoder
:members:
.. autodata:: Serializable
.. autodata:: Deserialized
Convenience Types
~~~~~~~~~~~~~~~~~
.. autodata:: PackBFunction
.. autodata:: UnpackBFunction
.. autodata:: DumpSFunction
.. autodata:: LoadSFunction
Contract Types
~~~~~~~~~~~~~~
.. autoclass:: HasSettings
:members:
.. autoclass:: DefinesIsoFormat
:members:

View file

@ -3,10 +3,7 @@ import os
import pkg_resources
needs_sphinx = '4.0'
extensions = [
'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx',
'sphinx.ext.extlinks', 'sphinxcontrib.httpdomain'
]
extensions = ['sphinx.ext.viewcode', 'sphinxcontrib.httpdomain']
master_doc = 'index'
project = 'sprockets.mixins.mediatype'
copyright = '2015-2021, AWeber Communications'
@ -24,14 +21,40 @@ html_sidebars = {
'**': ['about.html', 'navigation.html'],
}
# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html
extensions.append('sphinx.ext.intersphinx')
intersphinx_mapping = {
'ietfparse': ('https://ietfparse.readthedocs.io/en/latest', None),
'python': ('https://docs.python.org/3', None),
'requests': ('https://requests.readthedocs.org/en/latest/', None),
'sprockets': ('https://sprockets.readthedocs.org/en/latest/', None),
'tornado': ('https://www.tornadoweb.org/en/stable/', None),
}
# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html
# We need to define type aliases for both the simple name (e.g., Deserialized)
# and the prefixed name (e.g., type_info.Deserialized) since both forms
# appear in the typing annotations.
extensions.append('sphinx.ext.autodoc')
autodoc_type_aliases = {
alias: f'sprockets.mixins.mediatype.type_info.{alias}'
for alias in {
'DefinesIsoFormat', 'Deserialized', 'DumpSFunction', 'HasSettings',
'LoadSFunction', 'MsgPackable', 'PackBFunction', 'Serializable',
'Transcoder', 'UnpackBFunction'
}
}
autodoc_type_aliases.update({
f'type_info.{alias}': f'sprockets.mixins.mediatype.type_info.{alias}'
for alias in {
'DefinesIsoFormat', 'Deserialized', 'DumpSFunction', 'HasSettings',
'LoadSFunction', 'MsgPackable', 'PackBFunction', 'Serializable',
'Transcoder', 'UnpackBFunction'
}
})
# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
extensions.append('sphinx.ext.extlinks')
extlinks = {
'compare': ('https://github.com/sprockets/sprockets.mixins.mediatype'
'/compare/%s', '%s')

View file

@ -1,6 +1,10 @@
Version History
===============
:compare:`Next <3.0.4...master>`
--------------------------------
- Add type annotations (see :ref:`type-info`)
:compare:`3.0.4 <3.0.3...3.0.4>` (2 Nov 2020)
---------------------------------------------
- Return a "400 Bad Request" when an invalid Content-Type header is received

View file

@ -61,13 +61,9 @@ class ContentSettings:
The settings instance contains the list of available content
types and handlers associated with them. Each handler implements
a simple interface:
- ``to_bytes(dict, encoding:str) -> bytes``
- ``from_bytes(bytes, encoding:str) -> dict``
Use the :func:`add_binary_content_type` and :func:`add_text_content_type`
helper functions to modify the settings for the application.
the :class:`~sprockets.mixins.mediatype.type_info.Transcoder`
interface. Use :func:`add_transcoder` to add support for a
specific content type to the application.
This class acts as a mapping from content-type string to the
appropriate handler instance. Add new content types and find
@ -92,7 +88,7 @@ class ContentSettings:
return app
Of course, that is quite tedious, so use the :class:`.ContentMixin`
instead.
instead of using the settings directly.
"""
@ -178,12 +174,13 @@ def get_settings(
"""
Retrieve the media type settings for a application.
:param tornado.web.Application application:
:keyword bool force_instance: if :data:`True` then create the
:param application:
:param force_instance: if :data:`True` then create the
instance if it does not exist
:return: the content settings instance
:rtype: sprockets.mixins.mediatype.content.ContentSettings
:return: the content settings instance or :data:`None` if
`force_instance` is not :data:`True` and :func:`.install`
has not been called
"""
try:
@ -200,12 +197,12 @@ def add_binary_content_type(application: type_info.HasSettings,
"""
Add handler for a binary content type.
:param tornado.web.Application application: the application to modify
:param str content_type: the content type to add
:param application: the application to modify
:param content_type: the content type to add
:param pack: function that packs a dictionary to a byte string.
``pack(dict) -> bytes``
See :any:`type_info.PackBFunction`
:param unpack: function that takes a byte string and returns a
dictionary. ``unpack(bytes) -> dict``
dictionary. See :any:`type_info.UnpackBFunction`
"""
add_transcoder(application,
@ -219,13 +216,13 @@ def add_text_content_type(application: type_info.HasSettings,
"""
Add handler for a text content type.
:param tornado.web.Application application: the application to modify
:param str content_type: the content type to add
:param str default_encoding: encoding to use when one is unspecified
:param application: the application to modify
:param content_type: the content type to add
:param default_encoding: encoding to use when one is unspecified
:param dumps: function that dumps a dictionary to a string.
``dumps(dict, encoding:str) -> str``
See :any:`type_info.DumpSFunction`
:param loads: function that loads a dictionary from a string.
``loads(str, encoding:str) -> dict``
See :any:`type_info.LoadSFunction`
Note that the ``charset`` parameter is stripped from `content_type`
if it is present.
@ -246,32 +243,16 @@ def add_transcoder(application: type_info.HasSettings,
"""
Register a transcoder for a specific content type.
:param tornado.web.Application application: the application to modify
:param transcoder: object that translates between :class:`bytes` and
:class:`object` instances
:param str content_type: the content type to add. If this is
unspecified or :data:`None`, then the transcoder's ``content_type``
attribute is used.
:param application: the application to modify
:param transcoder: object that translates between :class:`bytes`
and object instances
:param content_type: the content type to add. If this is
unspecified or :data:`None`, then the transcoder's
``content_type`` attribute is used.
The `transcoder` instance is required to implement the following
simple protocol:
.. attribute:: transcoder.content_type
:class:`str` that identifies the MIME type that the transcoder
implements.
.. method:: transcoder.to_bytes(inst_data, encoding=None) -> bytes
:param object inst_data: the object to encode
:param str encoding: character encoding to apply or :data:`None`
:returns: the encoded :class:`bytes` instance
.. method:: transcoder.from_bytes(data_bytes, encoding=None) -> object
:param bytes data_bytes: the :class:`bytes` instance to decode
:param str encoding: character encoding to use or :data:`None`
:returns: the decoded :class:`object` instance
The `transcoder` instance is required to implement the
:class:`~sprockets.mixins.mediatype.type_info.Transcoder`
protocol.
"""
settings = get_settings(application, force_instance=True)
@ -284,9 +265,9 @@ def set_default_content_type(application: type_info.HasSettings,
"""
Store the default content type for an application.
:param tornado.web.Application application: the application to modify
:param str content_type: the content type to default to
:param str|None encoding: encoding to use when one is unspecified
:param application: the application to modify
:param content_type: the content type to default to
:param encoding: encoding to use when one is unspecified
"""
settings = get_settings(application, force_instance=True)
@ -322,6 +303,17 @@ class ContentMixin(web.RequestHandler):
def get_response_content_type(self) -> typing.Union[str, None]:
"""Select the content type will be used in the response.
This method implements proactive content negotiation as
described in :rfc:`7231#section-3.4.1` using the
:http:header:`Accept` request header or the configured
default content type if the header is not present. The
selected response type is cached and returned. It will
be used when :meth:`.send_response` is called.
Note that this method is called by :meth:`.send_response`
so you will seldom need to call it directly.
"""
if self._best_response_match is None:
settings = get_settings(self.application, force_instance=True)
@ -346,7 +338,7 @@ class ContentMixin(web.RequestHandler):
"""
Fetch (and cache) the request body as a dictionary.
:raise web.HTTPError:
:raise tornado.web.HTTPError:
- if the content type cannot be matched, then the status code
is set to 415 Unsupported Media Type.
- if decoding the content body fails, then the status code is
@ -390,10 +382,15 @@ class ContentMixin(web.RequestHandler):
"""
Serialize and send ``body`` in the response.
:param dict body: the body to serialize
:param bool set_content_type: should the :http:header:`Content-Type`
:param body: the body to serialize
:param set_content_type: should the :http:header:`Content-Type`
header be set? Defaults to :data:`True`
The transcoder for the response is selected by calling
:meth:`.get_response_content_type` which chooses an
appropriate transcoder based on the :http:header:`Accept`
header from the request.
"""
settings = get_settings(self.application, force_instance=True)
# TODO -- account for get_response_type returning None

View file

@ -20,7 +20,7 @@ class BinaryContentHandler:
"""
Pack and unpack binary types.
:param str content_type: registered content type
:param content_type: registered content type
:param pack: function that transforms an object instance
into :class:`bytes`
:param unpack: function that transforms :class:`bytes`
@ -43,8 +43,8 @@ class BinaryContentHandler:
"""
Transform an object into :class:`bytes`.
:param object inst_data: object to encode
:param str encoding: ignored
:param inst_data: object to encode
:param encoding: ignored
:returns: :class:`tuple` of the selected content
type and the :class:`bytes` representation of
`inst_data`
@ -59,11 +59,8 @@ class BinaryContentHandler:
"""
Get an object from :class:`bytes`
:param bytes data_bytes: stream of bytes to decode
:param str encoding: ignored
:param dict content_parameters: optional :class:`dict` of
content type parameters from the :mailheader:`Content-Type`
header
:param data_bytes: stream of bytes to decode
:param encoding: ignored
:returns: decoded :class:`object` instance
"""
@ -74,12 +71,12 @@ class TextContentHandler:
"""
Transcodes between textual and object representations.
:param str content_type: registered content type
:param content_type: registered content type
:param dumps: function that transforms an object instance
into a :class:`str`
:param loads: function that transforms a :class:`str`
into an object instance
:param str default_encoding: encoding to apply when
:param default_encoding: encoding to apply when
transcoding from the underlying body :class:`byte`
instance
@ -104,8 +101,8 @@ class TextContentHandler:
"""
Transform an object into :class:`bytes`.
:param object inst_data: object to encode
:param str encoding: character set used to encode the bytes
:param inst_data: object to encode
:param encoding: character set used to encode the bytes
returned from the ``dumps`` function. This defaults to
:attr:`default_encoding`
:returns: :class:`tuple` of the selected content
@ -125,13 +122,10 @@ class TextContentHandler:
"""
Get an object from :class:`bytes`
:param bytes data: stream of bytes to decode
:param str encoding: character set used to decode the incoming
:param data: stream of bytes to decode
:param encoding: character set used to decode the incoming
bytes before calling the ``loads`` function. This defaults
to :attr:`default_encoding`
:param dict content_parameters: optional :class:`dict` of
content type parameters from the :mailheader:`Content-Type`
header
:returns: decoded :class:`object` instance
"""

View file

@ -26,10 +26,10 @@ class JSONTranscoder(handlers.TextContentHandler):
"""
JSON transcoder instance.
:param str content_type: the content type that this encoder instance
:param content_type: the content type that this encoder instance
implements. If omitted, ``application/json`` is used. This is
passed directly to the ``TextContentHandler`` initializer.
:param str default_encoding: the encoding to use if none is specified.
:param default_encoding: the encoding to use if none is specified.
If omitted, this defaults to ``utf-8``. This is passed directly to
the ``TextContentHandler`` initializer.
@ -77,7 +77,7 @@ class JSONTranscoder(handlers.TextContentHandler):
"""
Called to encode unrecognized object.
:param object obj: the object to encode
:param obj: the object to encode
:return: the encoded object
:raises TypeError: when `obj` cannot be encoded
@ -114,7 +114,7 @@ class MsgPackTranscoder(handlers.BinaryContentHandler):
"""
Msgpack Transcoder instance.
:param str content_type: the content type that this encoder instance
:param content_type: the content type that this encoder instance
implements. If omitted, ``application/msgpack`` is used. This
is passed directly to the ``BinaryContentHandler`` initializer.