Skip to content

Commit

Permalink
Merge pull request #19 from gadventures/connection-pooling
Browse files Browse the repository at this point in the history
Enable (optional) requests/urllib3 connection pooling
  • Loading branch information
jonprindiville committed Oct 13, 2015
2 parents 4f24f89 + 87a01e2 commit f010414
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 2 deletions.
5 changes: 5 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
History
-------

0.4.0 (2015-10-13)
------------------

* Added connection pooling options, see docs for details on ``connection_pool_options``.

0.3.0 (2015-09-24)
------------------

Expand Down
27 changes: 27 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,33 @@ Since the cache backend is defined by a python module path, you are free to use
a cache backend outside of this project.


Connection Pooling
------------------

We use the ``requests`` library, and you can take advantage of the provided
connection pooling options by passing in a ``'connection_pool_options'`` dict
to your client.

Values inside the ``'connection_pool_options'`` dict of interest are as
follows:

* Set ``enable`` to ``True`` to enable pooling. Defaults to ``False``.
* Use ``number`` to set the number of connection pools to cache.
Defaults to 10.
* Use ``maxsize`` to set the max number of connections in each pool.
Defaults to 10.
* Set ``block`` to ``True`` if the connection pool should block and wait
for a connection to be released when it has reached ``maxsize``. If
``False`` and the pool is already at ``maxsize`` a new connection will
be created without blocking, but it will not be saved once it is used.
Defaults to ``False``.

See also:

* http://www.python-requests.org/en/latest/api/#requests.adapters.HTTPAdapter
* http://urllib3.readthedocs.org/en/latest/pools.html#urllib3.connectionpool.HTTPConnectionPool


Dependencies
------------

Expand Down
2 changes: 1 addition & 1 deletion gapipy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

__version__ = '0.3.0'
__version__ = '0.4.0'
__title__ = 'gapipy'


Expand Down
70 changes: 70 additions & 0 deletions gapipy/client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import logging
import os
import re
import requests
from importlib import import_module

from .utils import get_available_resource_classes
Expand All @@ -18,8 +20,22 @@
'cache_backend': os.environ.get('GAPI_CACHE_BACKEND', 'gapipy.cache.NullCache'),
'cache_options': {'threshold': 500, 'default_timeout': 3600},
'debug': os.environ.get('GAPI_CLIENT_DEBUG', False),
'connection_pool_options': {
'enable': os.environ.get('GAPI_CLIENT_CONNECTION_POOL_ENABLE', False),
'block': os.environ.get('GAPI_CLIENT_CONNECTION_POOL_BLOCK', False),
'number': os.environ.get('GAPI_CLIENT_CONNECTION_POOL_NUMBER', 10),
'maxsize': os.environ.get('GAPI_CLIENT_CONNECTION_POOL_MAXSIZE', 10),
},
}

def _get_protocol_prefix(api_root):
"""
Returns the protocol plus "://" of api_root.
This is likely going to be "https://".
"""
match = re.search(r'^[^:/]*://', api_root)
return match.group(0) if match else ''

def get_config(config, name):
return config.get(name, default_config[name])
Expand All @@ -38,11 +54,18 @@ def __init__(self, **config):
self.api_language = get_config(config, 'api_language')
self.cache_backend = get_config(config, 'cache_backend')

# begin with default connection pool options and overwrite any that the
# client has specified
self.connection_pool_options = default_config['connection_pool_options']
self.connection_pool_options.update(get_config(config, 'connection_pool_options'))


log_level = 'DEBUG' if get_config(config, 'debug') else 'ERROR'
self.logger = logger
self.logger.setLevel(log_level)

self._set_cache_instance(get_config(config, 'cache_options'))
self._set_requestor(self.connection_pool_options)

# Prevent install issues where setup.py digs down the path and
# eventually fails on a missing requests requirement by importing Query
Expand All @@ -58,6 +81,53 @@ def _set_cache_instance(self, cache_options):
cache = getattr(module, class_name)(**cache_options)
self._cache = cache

def _set_requestor(self, pool_options):
"""
Set the requestor based on connection pooling options.
If connection pooling is disabled, just set `requests`. If connection
pooling is enabled, set up a `requests.Session`.
"""
if not pool_options['enable']:
self._requestor = requests
return

session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
pool_block=pool_options['block'],
pool_connections=pool_options['number'],
pool_maxsize=pool_options['maxsize'],
)
logger.info(
'Created connection pool (block={}, number={}, maxsize={})'.format(
pool_options['block'],
pool_options['number'],
pool_options['maxsize']))

prefix = _get_protocol_prefix(self.api_root)
if prefix:
session.mount(prefix, adapter)
logger.info('Mounted connection pool for "{}"'.format(prefix))
else:
session.mount('http://', adapter)
session.mount('https://', adapter)
logger.info(
'Could not find protocol prefix in API root, mounted '
'connection pool on both http and https.')

self._requestor = session

@property
def requestor(self):
"""
Return a requestor, an object we'll use to make HTTP requests.
This is either going to be `requests` (if connection pooling is
disabled), or a `requests.Session` (if connection pooling is enabled), or
an AttributeError (if `__init__` has not happened yet).
"""
return self._requestor

def query(self, resource_name):
try:
return getattr(self, resource_name)
Expand Down
2 changes: 1 addition & 1 deletion gapipy/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def _make_call(self, method, url, headers, data, params):
"""Make the actual request to the API, using the given URL, headers,
data and extra parameters.
"""
requests_call = getattr(requests, method.lower())
requests_call = getattr(self.client.requestor, method.lower())

self.client.logger.debug('Making a {0} request to {1}'.format(method, url))

Expand Down

0 comments on commit f010414

Please sign in to comment.