Merge pull request #14 from nvllsvm/trans

Use sprockets.mixins.mediatype transcoders
This commit is contained in:
Andrew Rabert 2019-01-10 16:39:17 -05:00 committed by GitHub
commit c87f2f001a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 62 additions and 68 deletions

View file

@ -1,14 +1,10 @@
sudo: false
language: python
dist: xenial
python:
- 2.7
- pypy
- 3.4
- 3.5
- 3.6
- 3.7-dev
- 3.7
install:
- pip install -r requires/testing.txt -r docs/requirements.txt
- pip install -r requires/development.txt
script:
- nosetests
after_success:
@ -21,6 +17,6 @@ deploy:
password:
secure: "HncR2JUjQicR1czZfM2fHtlFc7rbc5DNdiBsu6uLvNVQPmdBC/fM+FAkuhXhCPrxgp8aRAKXX3mJebZK0R2NVJCui3Y20h4cmHAqTmkNchqytQVZEeDU9pxFYKZ6dhXb6dy6kq/nqxT4ZIDP6IeDaz7n03ZMlsNzMbt2Hj0b5E8BIghiaOLOIZVI3s4meOv/MGxZ5IL378ssKh/+X+mHa5JTVNoZ0gmlIM0Cq6enxQquPgPiL6c6R+EfEk7hbuIKlRwKLpves9qlAM9NCgOwGoo0lv0ashxmIGSbKQApyYtrLbGXmv1TnWAgM64uFTmR6KGKvmLnDD/avx6BCiI+0Fe1V+qDzdDaZf9A2ibPNIdSBR/bwJNueKgDm2yUvf67R0KoucsPnqxJjTObh7Uo/WShie9QjmBNh+FVeNMCPS7SumLeWK5utJx6IYlffuGaVM7VEaTuCahgLFLoYxN/Iig0REhAaG+bxDT+9jcRxgxNcUzTgrPC7TK/AwxfMIP+Ux+0j3M7bAByipek40VBFNI3VZFqfZYsgUl9/sm/ihRzENan6GeNevHx2IpvxnS9CNP0wuYBo4n8Rgvfl7ZMrUZxgl5gkXCnlAwt4Mow0/SNiuPFNqCJARt66w0KFKB5d2eLNgvowV3Mhsh+s3wWm3jxaY5KqBEtqGojQiBDYuk="
on:
python: 3.5
python: 3.7
tags: true
repo: sprockets/sprockets.mixins.http

View file

@ -38,15 +38,14 @@ This examples demonstrates the most basic usage of ``sprockets.mixins.http``
.. code:: python
from tornado import gen, ioloop, web
from tornado import ioloop, web
from sprockets.mixins import http
class RequestHandler(http.HTTPClientMixin, web.RequestHandler):
@gen.coroutine
def get(self, *args, **kwargs):
response = yield self.http_fetch('https://api.github.com')
async def get(self, *args, **kwargs):
response = await self.http_fetch('https://api.github.com')
if not response.ok:
self.set_status(response.code)
self.write(response.body)
@ -62,7 +61,7 @@ As with Tornado, to use the curl client which has numerous benefits:
.. code:: python
from tornado import gen, httpclient, ioloop, web
from tornado import httpclient, ioloop, web
from sprockets.mixins import http
httpclient.AsyncHTTPClient.configure(
@ -71,9 +70,8 @@ As with Tornado, to use the curl client which has numerous benefits:
class RequestHandler(http.HTTPClientMixin, web.RequestHandler):
@gen.coroutine
def get(self, *args, **kwargs):
response = yield self.http_fetch('https://api.github.com')
async def get(self, *args, **kwargs):
response = await self.http_fetch('https://api.github.com')
if not response.ok:
self.set_status(response.code)
self.write(response.body)

3
requires/development.txt Normal file
View file

@ -0,0 +1,3 @@
-e .
-r docs.txt
-r testing.txt

View file

@ -1,3 +1,3 @@
ietfparse>=1.4.1,<2
tornado>=4.2.0,<6
u-msgpack-python>=2.1,<3
ietfparse>=1.5.1
sprockets.mixins.mediatype[msgpack]>=3.0.0
tornado>=5

View file

@ -34,18 +34,12 @@ setuptools.setup(
'Development Status :: 3 - Alpha', 'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License', 'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 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'
],
python_requires='>=3.7',
packages=setuptools.find_packages(),
package_data={'': ['LICENSE', 'README.rst', 'requires/installation.txt']},
include_package_data=True,

View file

@ -5,16 +5,16 @@ A Tornado Request Handler Mixin that provides functions for making HTTP
requests.
"""
import asyncio
import collections
import json
import logging
import os
import socket
import time
from ietfparse import algorithms, errors, headers
from tornado import gen, httpclient
import umsgpack
from sprockets.mixins.mediatype import transcoders
from tornado import httpclient
__version__ = '1.1.1'
@ -45,7 +45,7 @@ slightly higher level of functionality than Tornado's
"""
class HTTPClientMixin(object):
class HTTPClientMixin:
"""Mixin for making http requests. Requests using the asynchronous
:meth:`HTTPClientMixin.http_fetch` method """
@ -57,22 +57,26 @@ class HTTPClientMixin(object):
MAX_HTTP_RETRIES = 3
MAX_REDIRECTS = 5
@gen.coroutine
def http_fetch(self, url,
method='GET',
request_headers=None,
body=None,
content_type=CONTENT_TYPE_MSGPACK,
follow_redirects=False,
max_redirects=MAX_REDIRECTS,
connect_timeout=DEFAULT_CONNECT_TIMEOUT,
request_timeout=DEFAULT_REQUEST_TIMEOUT,
max_http_attempts=MAX_HTTP_RETRIES,
auth_username=None,
auth_password=None,
user_agent=None,
validate_cert=True,
allow_nonstandard_methods=False):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__json_transcoder = transcoders.JSONTranscoder()
self.__msgpack_transcoder = transcoders.MsgPackTranscoder()
async def http_fetch(self, url,
method='GET',
request_headers=None,
body=None,
content_type=CONTENT_TYPE_MSGPACK,
follow_redirects=False,
max_redirects=MAX_REDIRECTS,
connect_timeout=DEFAULT_CONNECT_TIMEOUT,
request_timeout=DEFAULT_REQUEST_TIMEOUT,
max_http_attempts=MAX_HTTP_RETRIES,
auth_username=None,
auth_password=None,
user_agent=None,
validate_cert=True,
allow_nonstandard_methods=False):
"""Perform a HTTP request
Will retry up to ``self.MAX_HTTP_RETRIES`` times.
@ -123,7 +127,7 @@ class HTTPClientMixin(object):
method, url, attempt + 1, max_http_attempts,
request_headers)
try:
response = yield client.fetch(
response = await client.fetch(
url,
method=method,
headers=request_headers,
@ -151,24 +155,22 @@ class HTTPClientMixin(object):
method, url, response.code, attempt + 1,
max_http_attempts, warning_header)
if 200 <= response.code < 400:
raise gen.Return(
HTTPResponse(
return HTTPResponse(
True, response.code, dict(response.headers),
self._http_resp_deserialize(response),
response, attempt + 1, time.time() - start_time))
response, attempt + 1, time.time() - start_time)
elif response.code in {423, 429}:
yield self._http_resp_rate_limited(response)
await self._http_resp_rate_limited(response)
elif 400 <= response.code < 500:
error = self._http_resp_error_message(response)
LOGGER.debug('HTTP Response Error for %s to %s'
'attempt %i of %i (%s): %s',
method, url, response.code, attempt + 1,
max_http_attempts, error)
raise gen.Return(
HTTPResponse(
return HTTPResponse(
False, response.code, dict(response.headers),
error, response, attempt + 1,
time.time() - start_time))
time.time() - start_time)
elif response.code >= 500:
LOGGER.error('HTTP Response Error for %s to %s, '
'attempt %i of %i (%s): %s',
@ -179,16 +181,14 @@ class HTTPClientMixin(object):
LOGGER.warning('HTTP Get %s failed after %i attempts', url,
max_http_attempts)
if response:
raise gen.Return(
HTTPResponse(
return HTTPResponse(
False, response.code, dict(response.headers),
self._http_resp_error_message(response) or response.body,
response, max_http_attempts,
time.time() - start_time))
raise gen.Return(
HTTPResponse(
time.time() - start_time)
return HTTPResponse(
False, 599, None, None, None, max_http_attempts,
time.time() - start_time))
time.time() - start_time)
def _http_req_apply_default_headers(self, request_headers,
content_type, body):
@ -216,8 +216,7 @@ class HTTPClientMixin(object):
'Correlation-Id', self.request.headers['Correlation-Id'])
return request_headers
@staticmethod
def _http_req_body_serialize(body, content_type):
def _http_req_body_serialize(self, body, content_type):
"""Conditionally serialize the request body value if mime_type is set
and it's serializable.
@ -231,9 +230,9 @@ class HTTPClientMixin(object):
content_type = headers.parse_content_type(content_type)
if content_type == CONTENT_TYPE_JSON:
return json.dumps(body)
return self.__json_transcoder.dumps(body)
elif content_type == CONTENT_TYPE_MSGPACK:
return umsgpack.packb(body)
return self.__msgpack_transcoder.packb(body)
raise ValueError('Unsupported Content Type')
def _http_req_user_agent(self):
@ -289,9 +288,13 @@ class HTTPClientMixin(object):
if content_type[0] == CONTENT_TYPE_JSON:
return self._http_resp_decode(
json.loads(self._http_resp_decode(response.body)))
self.__json_transcoder.loads(
self._http_resp_decode(response.body)
)
)
elif content_type[0] == CONTENT_TYPE_MSGPACK:
return self._http_resp_decode(umsgpack.unpackb(response.body))
return self._http_resp_decode(
self.__msgpack_transcoder.unpackb(response.body))
def _http_resp_error_message(self, response):
"""Try and extract the error message from a HTTP error response.
@ -316,4 +319,4 @@ class HTTPClientMixin(object):
"""
duration = int(response.headers.get('Retry-After', 3))
LOGGER.warning('Rate Limited by, retrying in %i seconds', duration)
return gen.sleep(duration)
return asyncio.sleep(duration)

View file

@ -113,7 +113,7 @@ class TestHandler(web.RequestHandler):
class MixinTestCase(testing.AsyncHTTPTestCase):
def setUp(self):
super(MixinTestCase, self).setUp()
super().setUp()
self.correlation_id = str(uuid.uuid4())
self.mixin = self.create_mixin()