mirror of
https://github.com/sprockets/sprockets.mixins.http.git
synced 2024-11-14 19:29:31 +00:00
Merge pull request #14 from nvllsvm/trans
Use sprockets.mixins.mediatype transcoders
This commit is contained in:
commit
c87f2f001a
8 changed files with 62 additions and 68 deletions
12
.travis.yml
12
.travis.yml
|
@ -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
|
||||
|
|
14
README.rst
14
README.rst
|
@ -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
3
requires/development.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
-e .
|
||||
-r docs.txt
|
||||
-r testing.txt
|
|
@ -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
|
||||
|
|
8
setup.py
8
setup.py
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
2
tests.py
2
tests.py
|
@ -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()
|
||||
|
||||
|
|
Loading…
Reference in a new issue