Repackage sprockets.mixins.mediatype into a package.

Distributing a raw python module into a namespace package seems to be
somewhat unreliable though I haven't proven it yet...  In any case,
installing a package inside of a namespace package behaves itself.
This commit is contained in:
Dave Shawley 2015-11-03 08:10:08 -05:00
parent ee26168e25
commit ce54332cab
5 changed files with 179 additions and 42 deletions

View file

@ -1,6 +1,11 @@
Version History
===============
`Next Release`_
---------------
- Repackage from a module into a package. Distributing raw modules inside
of a namespace package is unreliable and questionably correct.
`1.0.4`_ (14 Sep 2015)
----------------------
- Support using the default_content_type in the settings if request does not
@ -22,6 +27,7 @@ Version History
---------------------
- Initial Release
.. _Next Release: https://github.com/sprockets/sprockets.http/compare/1.0.4...HEAD
.. _1.0.4: https://github.com/sprockets/sprockets.http/compare/1.0.3...1.0.4
.. _1.0.3: https://github.com/sprockets/sprockets.http/compare/1.0.2...1.0.3
.. _1.0.2: https://github.com/sprockets/sprockets.http/compare/1.0.1...1.0.2

View file

@ -4,6 +4,8 @@
import os
import setuptools
from sprockets.mixins import mediatype
def read_requirements(file_name):
requirements = []
@ -28,7 +30,7 @@ tests_require = read_requirements('testing.txt')
setuptools.setup(
name='sprockets.mixins.mediatype',
version='1.0.4',
version=mediatype.__version__,
description='A mixin for reporting handling content-type/accept headers',
long_description='\n' + open('README.rst').read(),
url='https://github.com/sprockets/sprockets.mixins.media_type',
@ -36,7 +38,7 @@ setuptools.setup(
author_email='api@aweber.com',
license='BSD',
classifiers=[
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',

View file

@ -0,0 +1,24 @@
"""
sprockets.mixins.media_type
"""
try:
from .content import (ContentMixin, ContentSettings,
add_binary_content_type, add_text_content_type,
set_default_content_type)
except ImportError as error: # pragma no cover
def _error_closure(*args, **kwargs):
raise error
ContentMixin = _error_closure
ContentSettings = _error_closure
add_binary_content_type = _error_closure
add_text_content_type = _error_closure
set_default_content_type = _error_closure
version_info = (1, 0, 4)
__version__ = '.'.join(str(v) for v in version_info)
__all__ = ('ContentMixin', 'ContentSettings', 'add_binary_content_type',
'add_text_content_type', 'set_default_content_type',
'version_info', '__version__')

View file

@ -1,16 +1,33 @@
"""
sprockets.mixins.media_type
===========================
Content handling for Tornado.
- :func:`.set_default_content_type` sets the content type that is
used when an ``Accept`` or ``Content-Type`` header is omitted.
- :func:`.add_binary_content_type` register transcoders for a binary
content type
- :func:`.add_text_content_type` register transcoders for a textual
content type
- :class:`.ContentSettings` an instance of this is attached to
:class:`tornado.web.Application` to hold the content mapping
information for the application
- :class:`.ContentMixin` attaches a :class:`.ContentSettings`
instance to the application and implements request decoding &
response encoding methods
This module is the primary interface for this library. It exposes
functions for registering new content handlers and a mix-in that
adds content handling methods to :class:`~tornado.web.RequestHandler`
instances.
"""
import logging
from ietfparse import algorithms, errors, headers
from tornado import escape, web
from tornado import web
from . import handlers
version_info = (1, 0, 4)
__version__ = '.'.join(str(v) for v in version_info)
logger = logging.getLogger(__name__)
@ -110,7 +127,8 @@ def add_binary_content_type(application, content_type, pack, unpack):
"""
settings = ContentSettings.from_application(application)
settings[content_type] = _BinaryContentHandler(content_type, pack, unpack)
settings[content_type] = handlers.BinaryContentHandler(
content_type, pack, unpack)
def add_text_content_type(application, content_type, default_encoding,
@ -128,8 +146,8 @@ def add_text_content_type(application, content_type, default_encoding,
"""
settings = ContentSettings.from_application(application)
settings[content_type] = _TextContentHandler(content_type, dumps, loads,
default_encoding)
settings[content_type] = handlers.TextContentHandler(
content_type, dumps, loads, default_encoding)
def set_default_content_type(application, content_type, encoding=None):
@ -232,35 +250,3 @@ class ContentMixin(object):
if set_content_type:
self.set_header('Content-Type', content_type)
self.write(data_bytes)
class _BinaryContentHandler(object):
def __init__(self, content_type, pack, unpack):
self._pack = pack
self._unpack = unpack
self.content_type = content_type
def to_bytes(self, data_dict, encoding=None):
return self.content_type, self._pack(data_dict)
def from_bytes(self, data, encoding=None):
return self._unpack(data)
class _TextContentHandler(object):
def __init__(self, content_type, dumps, loads, default_encoding):
self._dumps = dumps
self._loads = loads
self.content_type = content_type
self.default_encoding = default_encoding
def to_bytes(self, data_dict, encoding=None):
selected = encoding or self.default_encoding
content_type = '{0}; charset="{1}"'.format(self.content_type, selected)
dumped = self._dumps(escape.recursive_unicode(data_dict))
return content_type, dumped.encode(selected)
def from_bytes(self, data, encoding=None):
return self._loads(data.decode(encoding or self.default_encoding))

View file

@ -0,0 +1,119 @@
"""
Basic content handlers.
- :class:`BinaryContentHandler` basic transcoder for binary types that
simply calls functions for encoding and decoding
- :class:`TextContentHandler` transcoder that translates binary bodies
to text before calling functions that encode & decode text
"""
from tornado import escape
class BinaryContentHandler(object):
"""
Pack and unpack binary types.
:param str content_type: registered content type
:param pack: function that transforms an object instance
into :class:`bytes`
:param unpack: function that transforms :class:`bytes`
into an object instance
This transcoder is a thin veneer around a pair of packing
and unpacking functions.
"""
def __init__(self, content_type, pack, unpack):
self._pack = pack
self._unpack = unpack
self.content_type = content_type
def to_bytes(self, inst_data, encoding=None):
"""
Transform an object into :class:`bytes`.
:param object inst_data: object to encode
:param str encoding: ignored
:returns: :class:`tuple` of the selected content
type and the :class:`bytes` representation of
`inst_data`
"""
return self.content_type, self._pack(inst_data)
def from_bytes(self, data_bytes, encoding=None):
"""
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
:returns: decoded :class:`object` instance
"""
return self._unpack(data_bytes)
class TextContentHandler(object):
"""
Transcodes between textual and object representations.
:param str 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
transcoding from the underlying body :class:`byte`
instance
This transcoder wraps functions that transcode between :class:`str`
and :class:`object` instances. In particular, it handles the
additional step of transcoding into the :class:`byte` instances
that tornado expects.
"""
def __init__(self, content_type, dumps, loads, default_encoding):
self._dumps = dumps
self._loads = loads
self.content_type = content_type
self.default_encoding = default_encoding
def to_bytes(self, inst_data, encoding=None):
"""
Transform an object into :class:`bytes`.
:param object inst_data: object to encode
:param str 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
type and the :class:`bytes` representation of
`inst_data`
"""
selected = encoding or self.default_encoding
content_type = '{0}; charset="{1}"'.format(self.content_type, selected)
dumped = self._dumps(escape.recursive_unicode(inst_data))
return content_type, dumped.encode(selected)
def from_bytes(self, data, encoding=None):
"""
Get an object from :class:`bytes`
:param bytes data: stream of bytes to decode
:param str 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
"""
return self._loads(data.decode(encoding or self.default_encoding))