Merge pull request #19 from nvllsvm/maintenance

Maintenance
This commit is contained in:
Gavin M. Roy 2018-11-29 14:53:05 -05:00 committed by GitHub
commit daacd40488
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 97 additions and 192 deletions

1
.gitignore vendored
View file

@ -14,6 +14,7 @@ downloads
eggs
fake-eggs
parts
.eggs
# Packages #
############

View file

@ -1,23 +1,23 @@
language: python
dist: xenial
python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7-dev
- pypy
- 3.7
install:
- pip install codecov
- pip install -r requires/installation.txt
- pip install -r requires/testing.txt
script: nosetests
- pip install codecov -r requires/development.txt
script:
- nosetests
- python setup.py build_sphinx
- python setup.py check
- flake8
after_success:
- codecov
deploy:
distributions: sdist bdist_wheel
provider: pypi
on:
python: 2.7
python: 3.7
tags: true
all_branches: true
user: sprockets

View file

@ -1,4 +1,4 @@
Copyright (c) 2015-2016 AWeber Communications
Copyright (c) 2015-2018 AWeber Communications
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,

View file

@ -89,13 +89,11 @@ request handlers.
class SomeHandler(content.ContentMixin, web.RequestHandler):
def get(self):
self.send_response({'data': 'value'})
self.finish()
def post(self):
body = self.get_request_body()
# do whatever
self.send_response({'action': 'performed'})
self.finish()
Based on the settings stored in the ``Application`` instance and the HTTP
headers, the request and response data will be handled correctly or the

View file

@ -33,6 +33,3 @@ Bundled Transcoders
.. autoclass:: MsgPackTranscoder
:members:
.. autoclass:: BinaryWrapper
:members:

View file

@ -1,23 +1,18 @@
import alabaster
from sprockets.mixins.mediatype import __version__
import pkg_resources
needs_sphinx = '1.0'
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
'sphinxcontrib.autohttp.tornado']
source_suffix = '.rst'
master_doc = 'index'
project = 'sprockets.mixins.mediatype'
copyright = '2015-2016, AWeber Communications'
release = __version__
copyright = '2015-2018, AWeber Communications'
release = pkg_resources.get_distribution('sprockets.mixins.mediatype').version
version = '.'.join(release.split('.')[0:1])
pygments_style = 'sphinx'
html_theme = 'alabaster'
html_style = 'custom.css'
html_static_path = ['static']
html_theme_path = [alabaster.get_path()]
html_sidebars = {
'**': ['about.html', 'navigation.html'],
}

View file

@ -11,7 +11,6 @@ class SimpleHandler(content.ContentMixin, web.RequestHandler):
body = self.get_request_body()
self.set_status(200)
self.send_response(body)
self.finish()
def make_application(**settings):

View file

@ -1,4 +1,3 @@
-e .[msgpack]
-r docs.txt
-r testing.txt
-r installation.txt
sphinx>=1.2,<2
sphinxcontrib-httpdomain>=1.3,<2

2
requires/docs.txt Normal file
View file

@ -0,0 +1,2 @@
sphinx==1.8.2
sphinxcontrib-httpdomain==1.7.0

View file

@ -1,2 +1,2 @@
ietfparse>=1.4,<1.5
tornado>=3.2,<6
tornado>=5,<6

View file

@ -1,4 +1,4 @@
coverage>=3.7,<3.99 # prevent installing 4.0b on ALL pip versions
mock>=1.3,<2
u-msgpack-python>=2,<3
nose>=1.3,<2
coverage==4.5.2
flake8==3.6.0
nose==1.3.7
tox==3.5.3

View file

@ -1,6 +1,13 @@
[bdist_wheel]
universal = 1
[build_sphinx]
fresh-env = 1
warning-is-error = 1
[check]
strict = 1
[nosetests]
cover-branches = 1
cover-erase = 1

View file

@ -1,38 +1,30 @@
#!/usr/bin/env python
#
import os
import pathlib
import setuptools
from sprockets.mixins import mediatype
REPO_DIR = pathlib.Path(__name__).parent
def read_requirements(file_name):
def read_requirements(name):
requirements = []
try:
with open(os.path.join('requires', file_name)) as req_file:
for req_line in req_file:
req_line = req_line.strip()
if '#' in req_line:
req_line = req_line[0:req_line.find('#')].strip()
if req_line.startswith('-r'):
req_line = req_line[2:].strip()
requirements.extend(read_requirements(req_line))
else:
requirements.append(req_line)
except IOError:
pass
for req_line in REPO_DIR.joinpath(name).read_text().split('\n'):
req_line = req_line.strip()
if '#' in req_line:
req_line = req_line[0:req_line.find('#')].strip()
if req_line.startswith('-r'):
req_line = req_line[2:].strip()
requirements.extend(read_requirements(req_line))
else:
requirements.append(req_line)
return requirements
install_requires = read_requirements('installation.txt')
tests_require = read_requirements('testing.txt')
setuptools.setup(
name='sprockets.mixins.mediatype',
version=mediatype.__version__,
description='A mixin for reporting handling content-type/accept headers',
long_description='\n' + open('README.rst').read(),
long_description=REPO_DIR.joinpath('README.rst').read_text(),
url='https://github.com/sprockets/sprockets.mixins.media_type',
author='AWeber Communications',
author_email='api@aweber.com',
@ -43,24 +35,27 @@ setuptools.setup(
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries',
'Topic :: Software Development :: Libraries :: Python Modules'
],
packages=setuptools.find_packages(),
install_requires=install_requires,
tests_require=tests_require,
packages=[
'sprockets',
'sprockets.mixins',
'sprockets.mixins.mediatype'
],
install_requires=read_requirements('requires/installation.txt'),
tests_require=read_requirements('requires/testing.txt'),
extras_require={
'msgpack': ['u-msgpack-python>=2.5.0,<3']
},
setup_requires=['setuptools_scm'],
use_scm_version=True,
namespace_packages=['sprockets', 'sprockets.mixins'],
test_suite='nose.collector',
python_requires='>=3.5',
zip_safe=False)

View file

@ -1 +0,0 @@
__import__('pkg_resources').declare_namespace(__name__)

View file

@ -1 +0,0 @@
__import__('pkg_resources').declare_namespace(__name__)

View file

@ -3,28 +3,11 @@ 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
class ErrorClosureClass(object):
def __init__(self, *args, **kwargs):
raise error
ContentMixin = ErrorClosureClass
ContentSettings = ErrorClosureClass
add_binary_content_type = _error_closure
add_text_content_type = _error_closure
set_default_content_type = _error_closure
from .content import (ContentMixin, ContentSettings,
add_binary_content_type, add_text_content_type,
set_default_content_type)
version_info = (2, 2, 2)
__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

@ -43,7 +43,7 @@ SETTINGS_KEY = 'sprockets.mixins.mediatype.ContentSettings'
_warning_issued = False
class ContentSettings(object):
class ContentSettings:
"""
Content selection settings.
@ -75,7 +75,6 @@ class ContentSettings(object):
response_body = settings['application/msgpack'].to_bytes(
response_dict, encoding='utf-8')
self.write(response_body)
self.finish()
def make_application():
app = web.Application([web.url('/', SomeHandler)])
@ -277,7 +276,7 @@ def set_default_content_type(application, content_type, encoding=None):
settings.default_encoding = encoding
class ContentMixin(object):
class ContentMixin:
"""
Mix this in to add some content handling methods.
@ -288,7 +287,6 @@ class ContentMixin(object):
body = self.get_request_body()
# do stuff --> response_dict
self.send_response(response_dict)
self.finish()
:meth:`get_request_body` will deserialize the request data into
a dictionary based on the :http:header:`Content-Type` request
@ -300,7 +298,7 @@ class ContentMixin(object):
"""
def initialize(self):
super(ContentMixin, self).initialize()
super().initialize()
self._request_body = None
self._best_response_match = None
self._logger = getattr(self, 'logger', logger)

View file

@ -10,7 +10,7 @@ Basic content handlers.
from tornado import escape
class BinaryContentHandler(object):
class BinaryContentHandler:
"""
Pack and unpack binary types.
@ -58,7 +58,7 @@ class BinaryContentHandler(object):
return self._unpack(data_bytes)
class TextContentHandler(object):
class TextContentHandler:
"""
Transcodes between textual and object representations.

View file

@ -7,7 +7,6 @@ Bundled media type transcoders.
"""
import base64
import json
import sys
import uuid
import collections
@ -20,28 +19,6 @@ except ImportError:
from sprockets.mixins.mediatype import handlers
class BinaryWrapper(bytes):
"""
Ensures that a Python 2 ``str`` is treated as binary.
Since :class:`bytes` is a synonym for :class:`str` in Python 2,
you cannot distinguish between something that should be binary
and something that should be encoded as a string. This is a
problem in formats `such as msgpack`_ where binary data and
strings are encoded differently. The :class:`MsgPackTranscoder`
accomodates this by trying to UTF-8 encode a :class:`str` instance
and falling back to binary encoding if the transcode fails.
You can avoid this by wrapping binary content in an instance of
this class. The transcoder will then treat it as a binary payload
instead of trying to detect whether it is a string or not.
.. _such as msgpack: http://msgpack.org
"""
pass
class JSONTranscoder(handlers.TextContentHandler):
"""
JSON transcoder instance.
@ -73,8 +50,8 @@ class JSONTranscoder(handlers.TextContentHandler):
def __init__(self, content_type='application/json',
default_encoding='utf-8'):
super(JSONTranscoder, self).__init__(content_type, self.dumps,
self.loads, default_encoding)
super().__init__(content_type, self.dumps, self.loads,
default_encoding)
self.dump_options = {
'default': self.dump_object,
'separators': (',', ':'),
@ -128,13 +105,6 @@ class JSONTranscoder(handlers.TextContentHandler):
| :class:`uuid.UUID` | Same as ``str(value)`` |
+----------------------------+---------------------------------------+
.. warning::
:class:`bytes` instances are treated as character strings by the
standard JSON module in Python 2.7 so the *default* object hook
is never called. In other words, :class:`bytes` values will not
be serialized as Base64 strings in Python 2.7.
"""
if isinstance(obj, uuid.UUID):
return str(obj)
@ -160,18 +130,14 @@ class MsgPackTranscoder(handlers.BinaryContentHandler):
.. _msgpack format: http://msgpack.org/index.html
"""
if sys.version_info[0] < 3:
PACKABLE_TYPES = (bool, int, float, long)
else:
PACKABLE_TYPES = (bool, int, float)
PACKABLE_TYPES = (bool, int, float)
def __init__(self, content_type='application/msgpack'):
if umsgpack is None:
raise RuntimeError('Cannot import MsgPackTranscoder, '
'umsgpack is not available')
super(MsgPackTranscoder, self).__init__(content_type, self.packb,
self.unpackb)
super().__init__(content_type, self.packb, self.unpackb)
def packb(self, data):
"""Pack `data` into a :class:`bytes` instance."""
@ -206,7 +172,7 @@ class MsgPackTranscoder(handlers.BinaryContentHandler):
+-------------------------------+-------------------------------+
| :class:`float` | `float family`_ |
+-------------------------------+-------------------------------+
| String (see note) | `str family`_ |
| String | `str family`_ |
+-------------------------------+-------------------------------+
| :class:`bytes` | `bin family`_ |
+-------------------------------+-------------------------------+
@ -214,8 +180,6 @@ class MsgPackTranscoder(handlers.BinaryContentHandler):
+-------------------------------+-------------------------------+
| :class:`memoryview` | `bin family`_ |
+-------------------------------+-------------------------------+
| :class:`.BinaryWrapper` | `bin family`_ |
+-------------------------------+-------------------------------+
| :class:`collections.Sequence` | `array family`_ |
+-------------------------------+-------------------------------+
| :class:`collections.Set` | `array family`_ |
@ -225,19 +189,6 @@ class MsgPackTranscoder(handlers.BinaryContentHandler):
| :class:`uuid.UUID` | Converted to String |
+-------------------------------+-------------------------------+
.. note::
:class:`str` and :class:`bytes` are the same before Python 3.
If you want a value to be treated as a binary value, then you
should wrap it in :class:`.BinaryWrapper` if there is any
chance of running under Python 2.7.
The processing of :class:`str` in Python 2.x attempts to
encode the string as a UTF-8 stream. If the ``encode`` succeeds,
then the string is encoded according to the `str family`_.
If ``encode`` fails, then the string is encoded according to
the `bin family`_ .
.. _nil byte: https://github.com/msgpack/msgpack/blob/
0b8f5ac67cdd130f4d4d4fe6afb839b989fdb86a/spec.md#formats-nil
.. _true byte: https://github.com/msgpack/msgpack/blob/
@ -277,16 +228,6 @@ class MsgPackTranscoder(handlers.BinaryContentHandler):
if hasattr(datum, 'isoformat'):
datum = datum.isoformat()
if sys.version_info[0] < 3 and isinstance(datum, (str, unicode)):
if isinstance(datum, str) and not isinstance(datum, BinaryWrapper):
# try to decode this into a string to make the common
# case work. If we fail, then send along the bytes.
try:
datum = datum.decode('utf-8')
except UnicodeDecodeError:
pass
return datum
if isinstance(datum, (bytes, str)):
return datum

View file

@ -4,7 +4,6 @@ import json
import os
import pickle
import struct
import sys
import unittest
import uuid
@ -28,7 +27,7 @@ class UTC(datetime.tzinfo):
return 'UTC'
class Context(object):
class Context:
"""Super simple class to call setattr on"""
def __init__(self):
self.settings = {}
@ -37,27 +36,27 @@ class Context(object):
def pack_string(obj):
"""Optimally pack a string according to msgpack format"""
payload = str(obj).encode('ASCII')
l = len(payload)
if l < (2 ** 5):
prefix = struct.pack('B', 0b10100000 | l)
elif l < (2 ** 8):
prefix = struct.pack('BB', 0xD9, l)
elif l < (2 ** 16):
prefix = struct.pack('>BH', 0xDA, l)
pl = len(payload)
if pl < (2 ** 5):
prefix = struct.pack('B', 0b10100000 | pl)
elif pl < (2 ** 8):
prefix = struct.pack('BB', 0xD9, pl)
elif pl < (2 ** 16):
prefix = struct.pack('>BH', 0xDA, pl)
else:
prefix = struct.pack('>BI', 0xDB, l)
prefix = struct.pack('>BI', 0xDB, pl)
return prefix + payload
def pack_bytes(payload):
"""Optimally pack a byte string according to msgpack format"""
l = len(payload)
if l < (2 ** 8):
prefix = struct.pack('BB', 0xC4, l)
elif l < (2 ** 16):
prefix = struct.pack('>BH', 0xC5, l)
pl = len(payload)
if pl < (2 ** 8):
prefix = struct.pack('BB', 0xC4, pl)
elif pl < (2 ** 16):
prefix = struct.pack('>BH', 0xC5, pl)
else:
prefix = struct.pack('>BI', 0xC6, l)
prefix = struct.pack('>BI', 0xC6, pl)
return prefix + payload
@ -111,16 +110,16 @@ class GetRequestBodyTests(testing.AsyncHTTPTestCase):
def test_that_request_with_unhandled_type_results_in_415(self):
response = self.fetch(
'/', method='POST', headers={'Content-Type': 'application/xml'},
body=(u'<request><name>value</name>'
u'<embedded><utf8>\u2731</utf8></embedded>'
u'</request>').encode('utf-8'))
body=('<request><name>value</name>'
'<embedded><utf8>\u2731</utf8></embedded>'
'</request>').encode('utf-8'))
self.assertEqual(response.code, 415)
def test_that_msgpack_request_returns_default_type(self):
body = {
'name': 'value',
'embedded': {
'utf8': u'\u2731'
'utf8': '\u2731'
}
}
response = self.fetch('/', method='POST', body=umsgpack.packb(body),
@ -140,7 +139,7 @@ class GetRequestBodyTests(testing.AsyncHTTPTestCase):
class JSONTranscoderTests(unittest.TestCase):
def setUp(self):
super(JSONTranscoderTests, self).setUp()
super().setUp()
self.transcoder = transcoders.JSONTranscoder()
def test_that_uuids_are_dumped_as_strings(self):
@ -161,13 +160,6 @@ class JSONTranscoderTests(unittest.TestCase):
self.assertEqual(dumped.replace(' ', ''),
'{"now":"%s"}' % obj['now'].isoformat())
@unittest.skipIf(sys.version_info[0] == 2, 'bytes unsupported on python 2')
def test_that_bytes_are_base64_encoded(self):
bin = bytes(os.urandom(127))
dumped = self.transcoder.dumps({'bin': bin})
self.assertEqual(
dumped, '{"bin":"%s"}' % base64.b64encode(bin).decode('ASCII'))
def test_that_bytearrays_are_base64_encoded(self):
bin = bytearray(os.urandom(127))
dumped = self.transcoder.dumps({'bin': bin})
@ -226,7 +218,7 @@ class ContentSettingsTests(unittest.TestCase):
class ContentFunctionTests(unittest.TestCase):
def setUp(self):
super(ContentFunctionTests, self).setUp()
super().setUp()
self.context = Context()
def test_that_add_binary_content_type_creates_binary_handler(self):
@ -281,11 +273,11 @@ class ContentFunctionTests(unittest.TestCase):
class MsgPackTranscoderTests(unittest.TestCase):
def setUp(self):
super(MsgPackTranscoderTests, self).setUp()
super().setUp()
self.transcoder = transcoders.MsgPackTranscoder()
def test_that_strings_are_dumped_as_strings(self):
dumped = self.transcoder.packb(u'foo')
dumped = self.transcoder.packb('foo')
self.assertEqual(self.transcoder.unpackb(dumped), 'foo')
self.assertEqual(dumped, pack_string('foo'))
@ -373,6 +365,6 @@ class MsgPackTranscoderTests(unittest.TestCase):
def test_that_utf8_values_can_be_forced_to_bytes(self):
data = b'a ascii value'
dumped = self.transcoder.packb(transcoders.BinaryWrapper(data))
dumped = self.transcoder.packb(data)
self.assertEqual(self.transcoder.unpackb(dumped), data)
self.assertEqual(dumped, pack_bytes(data))

View file

@ -1,5 +1,5 @@
[tox]
envlist = py27,py34,py35,pypy
envlist = py35,py36,py37
indexserver =
default = https://pypi.python.org/simple
toxworkdir = build/tox
@ -7,6 +7,6 @@ skip_missing_interpreters = true
[testenv]
deps =
-rrequires/installation.txt
-rrequires/testing.txt
-e .[msgpack]
-r requires/testing.txt
commands = nosetests []