diff --git a/README.md b/README.md index 71ebaff..2245f91 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,10 @@ -[![Travis Badge](https://travis-ci.org/sendgrid/python-http-client.svg?branch=master)](https://travis-ci.org/sendgrid/python-http-client) [![Code Climate](https://codeclimate.com/github/sendgrid/python-http-client/badges/gpa.svg)](https://codeclimate.com/github/sendgrid/python-http-client) - -**Quickly and easily access any RESTful or RESTful-like API.** - -If you are looking for the SendGrid API client library, please see [this repo](https://github.com/sendgrid/sendgrid-python). - -# Announcements - -All updates to this project is documented in our [CHANGELOG](https://github.com/sendgrid/python-http-client/blob/master/CHANGELOG.md). # Installation ## Prerequisites -- Python version 2.6, 2.7, 3.4, 3.5 or 3.6 - -## Install Package - -```bash -pip install python_http_client -``` - -or +- Python version 3.6 -```bash -easy_install python_http_client -``` # Quick Start @@ -33,60 +13,16 @@ Here is a quick example: `GET /your/api/{param}/call` ```python -import python_http_client -global_headers = {"Authorization": "Basic XXXXXXX"} -client = Client(host='base_url', request_headers=global_headers) -client.your.api._(param).call.get() -print response.status_code -print response.headers -print response.body +async def test(): + import python_http_client + global_headers = {"Authorization": "Basic XXXXXXX"} + client = Client(host='base_url', request_headers=global_headers) + response = await client.your.api._(param).call.get() + print(response.status_code) + print(response.headers) + print(response.body) + +import asyncio +loop = asyncio.get_event_loop() +loop.run_until_complete(go()) ``` - -`POST /your/api/{param}/call` with headers, query parameters and a request body with versioning. - -```python -import python_http_client -global_headers = {"Authorization": "Basic XXXXXXX"} -client = Client(host='base_url', request_headers=global_headers) -query_params={"hello":0, "world":1} -request_headers={"X-Test": "test"} -data={"some": 1, "awesome": 2, "data": 3} -response = client.your.api._(param).call.post(request_body=data, - query_params=query_params, - request_headers=request_headers) -print response.status_code -print response.headers -print response.body -``` - -# Usage - -- [Example Code](https://github.com/sendgrid/python-http-client/tree/master/examples) - -## Roadmap - -If you are intersted in the future direction of this project, please take a look at our [milestones](https://github.com/sendgrid/python-http-client/milestones). We would love to hear your feedback. - -## How to Contribute - -We encourage contribution to our projects, please see our [CONTRIBUTING](https://github.com/sendgrid/python-http-client/blob/master/CONTRIBUTING.md) guide for details. - -Quick links: - -- [Feature Request](https://github.com/sendgrid/python-http-client/blob/master/CONTRIBUTING.md#feature_request) -- [Bug Reports](https://github.com/sendgrid/python-http-client/blob/master/CONTRIBUTING.md#submit_a_bug_report) -- [Sign the CLA to Create a Pull Request](https://github.com/sendgrid/python-http-client/blob/master/CONTRIBUTING.md#cla) -- [Improvements to the Codebase](https://github.com/sendgrid/python-http-client/blob/master/CONTRIBUTING.mdimprovements_to_the_codebase) - -# Thanks - -We were inspired by the work done on [birdy](https://github.com/inueni/birdy) and [universalclient](https://github.com/dgreisen/universalclient). - -# About - -python-http-client is guided and supported by the SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). - -python-http-client is maintained and funded by SendGrid, Inc. The names and logos for python-http-client are trademarks of SendGrid, Inc. - -![SendGrid Logo] -(https://uiux.s3.amazonaws.com/2016-logos/email-logo%402x.png) diff --git a/python_http_client/client.py b/python_http_client/client.py index e2fa911..94243ad 100644 --- a/python_http_client/client.py +++ b/python_http_client/client.py @@ -2,29 +2,23 @@ import json from .exceptions import handle_error -try: - # Python 3 - import urllib.request as urllib - from urllib.parse import urlencode - from urllib.error import HTTPError -except ImportError: - # Python 2 - import urllib2 as urllib - from urllib2 import HTTPError - from urllib import urlencode +# Python 3 +import aiohttp +from urllib.parse import urlencode +from urllib.error import HTTPError class Response(object): """Holds the response from an API call.""" - def __init__(self, response): + def __init__(self, code, body, headers): """ :param response: The return value from a open call on a urllib.build_opener() :type response: urllib response object """ - self._status_code = response.getcode() - self._body = response.read() - self._headers = response.info() + self._status_code = code + self._body = body + self._headers = headers @property def status_code(self): @@ -59,6 +53,7 @@ class Client(object): """Quickly and easily access any REST or REST-like API.""" def __init__(self, host, + session=None, request_headers=None, version=None, url_path=None, @@ -86,6 +81,17 @@ def __init__(self, self.methods = ['delete', 'get', 'patch', 'post', 'put'] # APPEND SLASH set self.append_slash = append_slash + # aiohttp session + if session is None: + self.session = aiohttp.ClientSession() + self.mark_session_for_deletion = True + else: + self.session = session + self.mark_session_for_deletion = False + + def __del__(self): + if self.mark_session_for_deletion: + self.session.close() def _build_versioned_url(self, url): """Subclass this function for your own needs. @@ -138,28 +144,12 @@ def _build_client(self, name=None): """ url_path = self._url_path + [name] if name else self._url_path return Client(host=self.host, + session=self.session, version=self._version, request_headers=self.request_headers, url_path=url_path, append_slash=self.append_slash) - def _make_request(self, opener, request): - """Make the API call and return the response. This is separated into - it's own function, so we can mock it easily for testing. - - :param opener: - :type opener: - :param request: url payload to request - :type request: urllib.Request object - :return: urllib response - """ - try: - return opener.open(request) - except HTTPError as err: - exc = handle_error(err) - exc.__cause__ = None - raise exc - def _(self, name): """Add variable values to the url. (e.g. /your/api/{variable_value}/call) @@ -196,7 +186,7 @@ def get_version(*args, **kwargs): if name in self.methods: method = name.upper() - def http_request(*_, **kwargs): + async def http_request(*_, **kwargs): """Make the API call :param args: unused :param kwargs: @@ -216,15 +206,16 @@ def http_request(*_, **kwargs): else: data = json.dumps(kwargs['request_body']).encode('utf-8') params = kwargs['query_params'] if 'query_params' in kwargs else None - opener = urllib.build_opener() - request = urllib.Request(self._build_url(params), data=data) - if self.request_headers: - for key, value in self.request_headers.items(): - request.add_header(key, value) + headers = dict() + headers.update(self.request_headers) if data and not ('Content-Type' in self.request_headers): - request.add_header('Content-Type', 'application/json') - request.get_method = lambda: method - return Response(self._make_request(opener, request)) + headers['Content-Type'] = 'application/json' + async with self.session.request(method, self._build_url(params), data=data, headers=headers) as resp: + return Response( + resp.status, + await resp.text(), + resp.headers + ) return http_request else: # Add a segment to the URL