Skip to content

Commit

Permalink
Merge pull request #114 from sigmavirus24/feature/gae
Browse files Browse the repository at this point in the history
Add AppEngineAdapter for GAE users
  • Loading branch information
sigmavirus24 committed Jan 24, 2016
2 parents 935dee4 + 2815389 commit c7d1be0
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 0 deletions.
34 changes: 34 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,40 @@
History
=======

0.6.0 -- 2016-xx-yy
-------------------

More information about this release can be found on the `0.6.0 milestone`_.

New Features
~~~~~~~~~~~~

- Add ``AppEngineAdapter`` to support developers using Google's AppEngine
platform with Requests.

- Add ``GuessProxyAuth`` class to support guessing between Basic and Digest
Authentication for proxies.

Fixed Bugs
~~~~~~~~~~

- Ensure that proxies use the correct TLS version when using the
``SSLAdapter``.

- Fix an ``AttributeError`` when using the ``HTTPProxyDigestAuth`` class.

Miscellaneous
~~~~~~~~~~~~~

- Drop testing support for Python 3.2. virtualenv and pip have stopped
supporting it meaning that it is harder to test for this with our CI
infrastructure. Moving forward we will make a best-effort attempt to
support 3.2 but will not test for it.


.. _0.6.0 milestone:
https://github.com/sigmavirus24/requests-toolbelt/milestones/0.6.0

0.5.1 -- 2015-12-16
-------------------

Expand Down
36 changes: 36 additions & 0 deletions docs/adapters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ The toolbelt comes with several different transport adapters for you to use
with requests. The transport adapters are all kept in
:mod:`requests_toolbelt.adapters` and include

- :class:`requests_toolbelt.adapters.appengine.AppEngineAdapter`

- :class:`requests_toolbelt.adapters.fingerprint.FingerprintAdapter`

- :class:`requests_toolbelt.adapters.socket_options.SocketOptionsAdapter`
Expand All @@ -17,6 +19,40 @@ with requests. The transport adapters are all kept in

- :class:`requests_toolbelt.adapters.ssl.SSLAdapter`

AppEngineAdapter
----------------

.. versionadded:: 0.6.0

As of version 2.10.0, Requests will be capable of supporting Google's App
Engine platform. In order to use Requests on GAE, however, you will need a
custome adapter found here as
:class:`~requests_toolbelt.adapters.appengine.AppEngineAdapter`. There are two
ways to take advantage of this support at the moment:

#. Using the :class:`~requests_toolbelt.adapters.appengine.AppEngineAdapter`
like every other adapter, e.g.,

.. code-block:: python
import requests
from requests_toolbelt.adapters import appengine
s = requests.Session()
s.mount('http://', appengine.AppEngineAdapter())
s.mount('https://', appengine.AppEngineAdapter())
#. By monkey-patching requests to always use the provided adapter:

.. code-block:: python
import requests
from requests_toolbelt.adapters import appengine
appengine.monkeypatch()
.. autoclass:: requests_toolbelt.adapters.appengine.AppEngineAdapter

FingerprintAdapter
------------------

Expand Down
20 changes: 20 additions & 0 deletions requests_toolbelt/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from collections import Mapping, MutableMapping
import sys

import requests

try:
from requests.packages.urllib3 import connection
from requests.packages.urllib3 import fields
Expand All @@ -22,6 +24,22 @@
from urllib3 import filepost
from urllib3 import poolmanager

if requests.__build__ < 0x020300:
timeout = None
else:
try:
from requests.packages.urllib3.util import timeout
except ImportError:
from urllib3.util import timeout

if requests.__build__ < 0x021000:
gaecontrib = None
else:
try:
from requests.packages.urllib3.contrib import appengine as gaecontrib
except ImportError:
from urllib3.contrib import appengine as gaecontrib

PY3 = sys.version_info > (3, 0)

if PY3:
Expand Down Expand Up @@ -272,7 +290,9 @@ def from_httplib(cls, message): # Python 2
'fields',
'filepost',
'poolmanager',
'timeout',
'HTTPHeaderDict',
'queue',
'urlencode',
'gaecontrib',
)
139 changes: 139 additions & 0 deletions requests_toolbelt/adapters/appengine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# -*- coding: utf-8 -*-
"""The App Engine Transport Adapter for requests.
This requires a version of requests >= 2.10.0.
"""
import requests
from requests import adapters
from requests import sessions

from .. import exceptions as exc
from .._compat import gaecontrib
from .._compat import timeout

"""
.. versionadded:: 0.6.0
There are two ways to use this library.
If you're using requests directly, you can use code like:
>>> import requests
>>> import ssl
>>> from requests.packages.urllib3.contrib import appengine as ul_appengine
>>> from requests_toolbelt.adapters import appengine
>>> s = requests.Session()
>>> if ul_appengine.is_appengine_sandbox():
... s.mount('http://', appengine.AppEngineAdapter())
... s.mount('https://', appengine.AppEngineAdapter())
If you depend on external libraries which use requests, you can use code like:
>>> from requests_toolbelt.adapters import appengine
>>> appengine.monkeypatch()
which will ensure all requests.Session objects use AppEngineAdapter properly.
"""


class AppEngineAdapter(adapters.HTTPAdapter):
"""A transport adapter for Requests to use urllib3's GAE support.
Implements request's HTTPAdapter API.
When deploying to Google's App Engine service, some of Requests'
functionality is broken. There is underlying support for GAE in urllib3.
This functionality, however, is opt-in and needs to be enabled explicitly
for Requests to be able to use it.
"""

def __init__(self, validate_certificate=True, *args, **kwargs):
_check_version()
self._validate_certificate = validate_certificate
super(AppEngineAdapter, self).__init__(self, *args, **kwargs)

def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = _AppEnginePoolManager(self._validate_certificate)


class _AppEnginePoolManager(object):
"""Implements urllib3's PoolManager API expected by requests.
While a real PoolManager map hostnames to reusable Connections,
AppEngine has no concept of a reusable connection to a host.
So instead, this class constructs a small Connection per request,
that is returned to the Adapter and used to access the URL.
"""

def __init__(self, validate_certificate=True):
self.appengine_manager = gaecontrib.AppEngineManager(
validate_certificate=validate_certificate)

def connection_from_url(self, url):
return _AppEngineConnection(self.appengine_manager, url)

def clear(self):
pass


class _AppEngineConnection(object):
"""Implements urllib3's HTTPConnectionPool API's urlopen().
This Connection's urlopen() is called with a host-relative path,
so in order to properly support opening the URL, we need to store
the full URL when this Connection is constructed from the PoolManager.
This code wraps AppEngineManager.urlopen(), which exposes a different
API than in the original urllib3 urlopen(), and thus needs this adapter.
"""

def __init__(self, appengine_manager, url):
self.appengine_manager = appengine_manager
self.url = url

def urlopen(self, method, url, body=None, headers=None, retries=None,
redirect=True, assert_same_host=True,
timeout=timeout.Timeout.DEFAULT_TIMEOUT,
pool_timeout=None, release_conn=None, **response_kw):
# This function's url argument is a host-relative URL,
# but the AppEngineManager expects an absolute URL.
# So we saved out the self.url when the AppEngineConnection
# was constructed, which we then can use down below instead.
# Let's verify our assumptions here though, just in case.
assert self.url.endswith(url), (
"AppEngineConnection constructed "
"with (%s), and called urlopen with (%s). Expected the latter "
"to be the host-relative equivalent of the former." %
(self.url, url))

# Jump through the hoops necessary to call AppEngineManager's API.
return self.appengine_manager.urlopen(
method,
self.url,
body=body,
headers=headers,
retries=retries,
redirect=redirect,
timeout=timeout,
**response_kw)


def monkeypatch():
"""Sets up all Sessions to use AppEngineAdapter by default.
If you don't want to deal with configuring your own Sessions,
or if you use libraries that use requests directly (ie requests.post),
then you may prefer to monkeypatch and auto-configure all Sessions.
"""
_check_version()
# HACK: We should consider modifying urllib3 to support this cleanly,
# so that we can set a module-level variable in the sessions module,
# instead of overriding an imported HTTPAdapter as is done here.
sessions.HTTPAdapter = AppEngineAdapter


def _check_version():
if gaecontrib is None:
raise exc.VersionMismatchError(
"The toolbelt requires at least Requests 2.10.0 to be "
"installed. Version {0} was found instead.".format(
requests.__version__
)
)
9 changes: 9 additions & 0 deletions requests_toolbelt/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@
class StreamingError(Exception):
"""Used in :mod:`requests_toolbelt.downloadutils.stream`."""
pass


class VersionMismatchError(Exception):
"""Used to indicate a version mismatch in the version of requests required.
The feature in use requires a newer version of Requests to function
appropriately but the version installed is not sufficient.
"""
pass

0 comments on commit c7d1be0

Please sign in to comment.