Skip to content

Commit

Permalink
Merge pull request #45 from prkumar/feature/v0.3.0/make-aiohttp-and-t…
Browse files Browse the repository at this point in the history
…wisted-optional

Make `aiohttp` and `twisted` optional extras
  • Loading branch information
prkumar committed Jan 9, 2018
2 parents 481289d + 964bdd2 commit caa696b
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 40 deletions.
27 changes: 25 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,38 @@ To install the latest stable release, you can use ``pip``:

::

$ pip install uplink

$ pip install -U uplink

If you are interested in the cutting-edge, preview the upcoming release with:

::

$ pip install https://github.com/prkumar/uplink/archive/master.zip

Extra! Extra!
-------------

Further, uplink has optional integrations and features, such as
`sending non-blocking requests with <examples/async-requests>`__
``aiohttp`` and `deserializing JSON responses using <examples/marshmallow>`__
``marshmallow``. You can view a full list of available extras `here
<http://uplink.readthedocs.io/en/latest/install.html#extras>`_.

When installing Uplink with ``pip``, you can specify any of number of extras
using the format:

::

$ pip install -U uplink[extra1, extra2, ..., extraN]


For instance, to install ``aiohttp`` and ``marshmallow`` support:

::

$ pip install -U uplink[aiohttp, marshmallow]


Documentation
=============
For more details, check out the documentation at https://uplink.readthedocs.io/.
Expand Down
40 changes: 35 additions & 5 deletions docs/source/install.rst
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Installation
============

Using :program:`pip`
--------------------
Using pip
---------

With :program:`pip` (or :program:`pipenv`), you can install Uplink simply by
typing:

::

$ pip install uplink
$ pip install -U uplink


Download the Source Code
Expand All @@ -19,16 +19,46 @@ Uplink's source code is in a `public repository hosted on GitHub
<https://github.com/prkumar/uplink>`__.

As an alternative to installing with :program:`pip`, you could clone the
repository
repository,

::

$ git clone https://github.com/prkumar/uplink.git

Then, install; e.g., with :file:`setup.py`:
then, install; e.g., with :file:`setup.py`:

::

$ cd uplink
$ python setup.py install

Extras
------

These are optional integrations and features that extend the library's core
functionality and typically require an additional dependency.

When installing Uplink with ``pip``, you can specify any of the following
extras, to add their respective dependencies to your installation:

=============== =============================================================
Extra Description
=============== =============================================================
``aiohttp`` Enables :py:class:`uplink.AiohttpClient`,
for `sending non-blocking requests <https://github.com/prkumar/uplink/tree/master/examples/async-requests>`_
and receiving awaitable responses.
``marshmallow`` Enables :py:class:`uplink.MarshmallowConverter`,
for `converting JSON responses directly into Python objects
<https://github.com/prkumar/uplink/tree/master/examples/marshmallow>`_
using :py:class:`marshmallow.Schema`.
``twisted`` Enables :py:class:`uplink.TwistedClient`,
for `sending non-blocking requests <https://github.com/prkumar/uplink/tree/master/examples/async-requests>`_ and receiving
:py:class:`~twisted.internet.defer.Deferred` responses.
=============== =============================================================

To download all available features, run

::

$ pip install -U uplink[aiohttp, marshmallow, twisted]

21 changes: 21 additions & 0 deletions examples/async-requests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@
This example details how you can use the same Uplink consumer with different
HTTP clients, with an emphasis on performing non-blocking HTTP requests.

## Requirements

Support for `twisted` and `aiohttp` are optional features. To enable these
extras, you can declare them when installing Uplink with ``pip``:

```
# Install both clients (requires Python 3.4+)
$ pip install -U uplink[twisted, aiohttp]
# Or, install support for twisted only (requires Python 2.7+):
$ pip install -U uplink[twisted]
# Or, install support for aiohttp only (requires Python 3.4+):
$ pip install -U uplink[aiohttp]
```

Notably, while `twisted` features should work on all versions of Python that
Uplink supports, the `aiohttp` library requires Python 3.4 or above.

## Overview

The example includes three Python scripts:

- `github.py`: Defines a `GitHub` API with two methods:
Expand Down
4 changes: 2 additions & 2 deletions examples/marshmallow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ have your JSON API return Python objects.

Uplink's integration with `marshmallow` is an optional feature. You
can either install `marshmallow` separately or declare the extra when
installing Uplink:
installing Uplink with ``pip``:

```
$ pip install uplink[marshmallow]
$ pip install -U uplink[marshmallow]
```

This makes available the class `uplink.MarshmallowConverter`.
Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ def read(filename):
exec(fp.read(), about)
about = dict((k.strip("_"), about[k]) for k in about)

# TODO: Should Twisted and aiohttp be conditional dependencies?
install_requires = [
"requests>=2.18.0",
"uritemplate>=3.0.0",
"twisted>=17.1.0"
]

extras_require = {
":python_version>='3.4.2'": ["aiohttp>=2.3.0"],
"aiohttp": ["aiohttp>=2.3.0"],
"twisted": ["twisted>=17.1.0"],
"marshmallow": ["marshmallow>=2.15.0"]
}

Expand Down
53 changes: 32 additions & 21 deletions tests/test_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,32 @@
import pytest

# Local imports
from uplink.clients import interfaces, requests_, twisted_, register
from uplink.clients import (
AiohttpClient, interfaces, requests_, twisted_, register
)

try:
from uplink.clients import aiohttp_
except (ImportError, SyntaxError):
aiohttp_ = None


requires_aiohttp = pytest.mark.skipif(
not aiohttp_, reason="Requires Python 3.4 or above")
requires_python34 = pytest.mark.skipif(
not aiohttp_,
reason="Requires Python 3.4 or above")


def test_get_default_client_with_non_callable(mocker):
@contextlib.contextmanager
def _patch(obj, attr, value):
if obj is not None:
old_value = getattr(obj, attr)
setattr(obj, attr, value)
yield
if obj is not None:
setattr(obj, attr, old_value)


def test_get_default_client_with_non_callable():
# Setup
old_default = register.get_default_client()
register.set_default_client("client")
Expand Down Expand Up @@ -58,16 +71,8 @@ def test_create_requests(self, http_client_mock):
assert request._proxy is http_client_mock.create_request()
assert isinstance(request, twisted_.Request)

@staticmethod
@contextlib.contextmanager
def patch_threads(threads):
old_threads = twisted_.threads
twisted_.threads = threads
yield
twisted_.threads = old_threads

def test_create_requests_no_twisted(self, http_client_mock):
with self.patch_threads(None):
with _patch(twisted_, "threads", None):
with pytest.raises(NotImplementedError):
twisted_.TwistedClient(http_client_mock)

Expand Down Expand Up @@ -102,17 +107,23 @@ def aiohttp_session_mock(mocker):

class TestAiohttp(object):

@requires_aiohttp
@requires_python34
def test_init_when_aiohttp_is_not_installed(self):
with _patch(aiohttp_, "aiohttp", None):
with pytest.raises(NotImplementedError):
AiohttpClient()

@requires_python34
def test_get_client(self, aiohttp_session_mock):
client = register.get_client(aiohttp_session_mock)
assert isinstance(client, aiohttp_.AiohttpClient)

@requires_aiohttp
@requires_python34
def test_create_request(self, aiohttp_session_mock):
aiohttp = aiohttp_.AiohttpClient(aiohttp_session_mock)
assert isinstance(aiohttp.create_request(), aiohttp_.Request)

@requires_aiohttp
@requires_python34
def test_request_send(self, aiohttp_session_mock):
# Setup
import asyncio
Expand All @@ -133,7 +144,7 @@ def request(*args, **kwargs):
# Verify
assert value == 0

@requires_aiohttp
@requires_python34
def test_callback(self, aiohttp_session_mock):
# Setup
import asyncio
Expand All @@ -156,7 +167,7 @@ def request(*args, **kwargs):
# Verify
assert value == 2

@requires_aiohttp
@requires_python34
def test_threaded_callback(self, mocker):
import asyncio

Expand All @@ -177,7 +188,7 @@ def callback(response):
response.text.assert_called_with()
assert value == response

@requires_aiohttp
@requires_python34
def test_threaded_coroutine(self):
# Setup
import asyncio
Expand All @@ -194,7 +205,7 @@ def coroutine():
# Verify
assert response == 1

@requires_aiohttp
@requires_python34
def test_threaded_response(self, mocker):
# Setup
import asyncio
Expand All @@ -215,7 +226,7 @@ def coroutine():
assert isinstance(threaded_coroutine, aiohttp_.ThreadedCoroutine)
assert return_value == 1

@requires_aiohttp
@requires_python34
def test_create(self, mocker):
# Setup
import asyncio
Expand Down
7 changes: 3 additions & 4 deletions uplink/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,14 @@ def _client_class_handler(key):

try:
from uplink.clients.aiohttp_ import AiohttpClient
except (SyntaxError, ImportError) as error: # pragma: no cover
except (ImportError, SyntaxError) as error: # pragma: no cover
from uplink.clients import interfaces

class AiohttpClient(interfaces.HttpClientAdapter):
error_message = str(error)

def __init__(self, *args, **kwargs):
raise NotImplementedError(
"Failed to load `asyncio` client: %s" % self.error_message
"Failed to load `aiohttp` client: you may be using a version "
"of Python below 3.3. `aiohttp` requires Python 3.4+."
)

def create_request(self):
Expand Down
16 changes: 14 additions & 2 deletions uplink/clients/aiohttp_.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
This module defines an :py:class:`aiohttp.ClientSession` adapter
that sends awaitable requests.
that returns awaitable responses.
"""
# Standard library imports
import atexit
Expand All @@ -11,7 +11,10 @@
from concurrent import futures

# Third party imports
import aiohttp
try:
import aiohttp
except ImportError: # pragma: no cover
aiohttp = None

# Local imports
from uplink.clients import interfaces, register
Expand All @@ -37,6 +40,12 @@ class AiohttpClient(interfaces.HttpClientAdapter):
"""
An :py:mod:`aiohttp` client that creates awaitable responses.
Note:
This client is an optional feature and requires the :py:mod:`aiohttp`
package. For example, here's how to install this extra using pip::
$ pip install uplink[aiohttp]
Args:
session (:py:class:`aiohttp.ClientSession`, optional):
The session that should handle sending requests. If this
Expand All @@ -47,6 +56,9 @@ class AiohttpClient(interfaces.HttpClientAdapter):
__ARG_SPEC = collections.namedtuple("__ARG_SPEC", "args kwargs")

def __init__(self, session=None):
if aiohttp is None:
raise NotImplementedError("aiohttp is not installed.")

if session is None:
session = self.__ARG_SPEC((), {})
self._session = session
Expand Down
13 changes: 12 additions & 1 deletion uplink/clients/twisted_.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
"""
This module defines an :py:class:`aiohttp.ClientSession` adapter that
returns :py:class:`twisted.internet.defer.Deferred` responses.
"""

# Third party imports
try:
from twisted.internet import threads
Expand All @@ -13,6 +18,12 @@ class TwistedClient(interfaces.HttpClientAdapter):
Client that returns :py:class:`twisted.internet.defer.Deferred`
responses.
Note:
This client is an optional feature and requires the :py:mod:`twisted`
package. For example, here's how to install this extra using pip::
$ pip install uplink[twisted]
Args:
session (:py:class:`requests.Session`, optional): The session
that should handle sending requests. If this argument is
Expand All @@ -22,7 +33,7 @@ class TwistedClient(interfaces.HttpClientAdapter):

def __init__(self, session=None):
if threads is None:
raise NotImplementedError("TwistedClient is not installed.")
raise NotImplementedError("twisted is not installed.")
self._requests = register.get_client(session)

def create_request(self):
Expand Down

0 comments on commit caa696b

Please sign in to comment.