Skip to content

Commit

Permalink
Merge pull request #250 from prkumar/master
Browse files Browse the repository at this point in the history
Release v0.9.6
  • Loading branch information
prkumar committed Jan 24, 2022
2 parents a013758 + 55989ac commit 8a369e9
Show file tree
Hide file tree
Showing 19 changed files with 791 additions and 472 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,5 @@ Pipfile.lock
# macOS
.DS_Store

# VS Code
.vscode/
26 changes: 25 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog`_, and this project adheres to the
`Semantic Versioning`_ scheme.

Unreleased
==========
Added
-----
- Add a new base class, ``uplink.retry.RetryBackoff``, which can be extended to
implement custom backoff strategies. An instance of a ``RetryBackoff`` subclass
can be provided through the ``backoff`` argument of the ``@retry`` decorator.
(`#238`_)

Changed
-------
- Bump minimum version of ``six`` to ``1.13.0``. (`#246`_)

Fixed
-----
- Fix ``@returns.json`` to cast the JSON response (or field referenced by the
``key`` argument) using the ``type`` argument when it is a callable type.
This effectively reverts a change in the decorator's behavior that was
introduced in v0.9.3. (`#215`_)


0.9.5_ - 2022-01-04
====================
Added
Expand Down Expand Up @@ -405,9 +426,12 @@ Added
.. _#204: https://github.com/prkumar/uplink/pull/204
.. _#207: https://github.com/prkumar/uplink/pull/207
.. _#209: https://github.com/prkumar/uplink/pull/209
.. _#215: https://github.com/prkumar/uplink/issues/215
.. _#217: https://github.com/prkumar/uplink/issues/217
.. _#221: https://github.com/prkumar/uplink/issues/221
.. _#237: https://github.com/prkumar/uplink/discussions/237
.. _#238: https://github.com/prkumar/uplink/issues/238
.. _#246: https://github.com/prkumar/uplink/issues/246

.. Commits
.. _3653a672ee: https://github.com/prkumar/uplink/commit/3653a672ee0703119720d0077bb450649af5459c
Expand All @@ -421,4 +445,4 @@ Added
.. _@cognifloyd: https://github.com/cognifloyd
.. _@gmcrocetti: https://github.com/gmcrocetti
.. _@leiserfg: https://github.com/leiserfg
.. _@lust4life: https://github.com/lust4life
.. _@lust4life: https://github.com/lust4life
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Extra! Extra!
-------------

Further, uplink has optional integrations and features. You can view a full list
of available extras `here <http://uplink.readthedocs.io/en/latest/install.html#extras>`_.
of available extras `here <https://uplink.readthedocs.io/en/latest/user/install.html#extras>`_.

When installing Uplink with ``pip``, you can select extras using the format:

Expand Down
17 changes: 17 additions & 0 deletions docs/source/dev/decorators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,23 @@ to specify one of the alternative approaches exposed through the
def get_user(self, user):
"""Get user by username."""
You can implement a custom backoff strategy by extending the class
:class:`uplink.retry.RetryBackoff`:

.. code-block:: python
:emphasize-lines: 3,7
from uplink.retry import RetryBackoff
class MyCustomBackoff(RetryBackoff):
...
class GitHub(uplink.Consumer):
@uplink.retry(backoff=MyCustomBackoff())
@uplink.get("/users/{user}")
def get_user(self, user):
pass
.. automodule:: uplink.retry.backoff
:members:

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def read(filename):
exec(fp.read(), about)
about = dict((k.strip("_"), about[k]) for k in about)

install_requires = ["requests>=2.18.0", "six>=1.12.0", "uritemplate>=3.0.0"]
install_requires = ["requests>=2.18.0", "six>=1.13.0", "uritemplate>=3.0.0"]

extras_require = {
"marshmallow": ["marshmallow>=2.15.0"],
Expand All @@ -29,6 +29,7 @@ def read(filename):
"twisted:python_version == '3.4'": "twisted<=19.2.1",
"typing": ["typing>=3.6.4"],
"tests": ["pytest", "pytest-mock", "pytest-cov", "pytest-twisted"],
"tests:python_version >= '3.5'": ["pytest-asyncio"],
}

metadata = {
Expand Down
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import sys

collect_ignore = []
if sys.version_info.major < 3:
collect_ignore.extend(
[
"unit/test_aiohttp_client.py",
"integration/test_retry_aiohttp.py",
]
)
29 changes: 3 additions & 26 deletions tests/integration/test_retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
# Local imports.
from uplink import get, Consumer, retry
from uplink.clients import io
from tests import requires_python34

# Constants
BASE_URL = "https://api.github.com/"
Expand All @@ -29,7 +28,9 @@ def get_user(self, user):
def get_issue(self, user, repo, issue):
pass

@retry(max_attempts=3, on_exception=retry.CONNECTION_TIMEOUT)
@retry(
stop=retry.stop.after_attempt(3), on_exception=retry.CONNECTION_TIMEOUT
)
@get("repos/{user}/{repo}/project/{project}")
def get_project(self, user, repo, project):
pass
Expand Down Expand Up @@ -123,30 +124,6 @@ def test_retry_with_status_501(mock_client, mock_response):
assert len(mock_client.history) == 2


@requires_python34
def test_retry_with_asyncio(mock_client, mock_response):
import asyncio

@asyncio.coroutine
def coroutine():
return mock_response

# Setup
mock_response.with_json({"id": 123, "name": "prkumar"})
mock_client.with_side_effect([Exception, coroutine()])
mock_client.with_io(io.AsyncioStrategy())
github = GitHub(base_url=BASE_URL, client=mock_client)

# Run
awaitable = github.get_user("prkumar")
loop = asyncio.get_event_loop()
response = loop.run_until_complete(asyncio.ensure_future(awaitable))

# Verify
assert len(mock_client.history) == 2
assert response.json() == {"id": 123, "name": "prkumar"}


@pytest_twisted.inlineCallbacks
def test_retry_with_twisted(mock_client, mock_response):
from twisted.internet import defer
Expand Down
29 changes: 29 additions & 0 deletions tests/integration/test_retry_aiohttp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Third-party imports
import pytest

# Local imports.
from uplink.clients import io

from . import test_retry


@pytest.mark.asyncio
async def test_retry_with_asyncio(mock_client, mock_response):
import asyncio

@asyncio.coroutine
def coroutine():
return mock_response

# Setup
mock_response.with_json({"id": 123, "name": "prkumar"})
mock_client.with_side_effect([Exception, coroutine()])
mock_client.with_io(io.AsyncioStrategy())
github = test_retry.GitHub(base_url=test_retry.BASE_URL, client=mock_client)

# Run
response = await github.get_user("prkumar")

# Verify
assert len(mock_client.history) == 2
assert response.json() == {"id": 123, "name": "prkumar"}
74 changes: 74 additions & 0 deletions tests/integration/test_returns.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,47 @@ def get_repo(self, user, repo):
def get_repos(self, user):
pass

@uplink.returns.from_json(key=("data", 0, "size"))
@uplink.get("/users/{user}/repos")
def get_first_repo_size(self, user):
pass

@uplink.returns.from_json(key=("data", 0, "stars"), type=int)
@uplink.get("/users/{user}/repos")
def get_first_repo_stars(self, user):
pass

@uplink.json
@uplink.post("/users/{user}/repos", args={"repo": uplink.Body(Repo)})
def create_repo(self, user, repo):
pass

@uplink.returns(object)
@uplink.get("/users")
def list_users(self):
pass


# Tests


def test_returns_response_when_type_has_no_converter(
mock_client, mock_response
):
# Setup
mock_response.with_json({"id": 123, "name": "prkumar"})
mock_client.with_response(mock_response)
github = GitHub(
base_url=BASE_URL, client=mock_client, converters=user_reader
)

# Run
response = github.list_users()

# Verify
assert response == mock_response


def test_returns_with_type(mock_client, mock_response):
# Setup
mock_response.with_json({"id": 123, "name": "prkumar"})
Expand Down Expand Up @@ -114,6 +146,48 @@ def test_returns_json_with_list(mock_client, mock_response):
] == repo


def test_returns_json_by_key(mock_client, mock_response):
# Setup
mock_response.with_json(
{
"data": [
{"owner": "prkumar", "name": "uplink", "size": 300},
{"owner": "prkumar", "name": "uplink-protobuf", "size": 400},
],
"errors": [],
}
)
mock_client.with_response(mock_response)
github = GitHub(base_url=BASE_URL, client=mock_client)

# Run
size = github.get_first_repo_size("prkumar")

# Verify
assert size == 300


def test_returns_json_with_key_and_type(mock_client, mock_response):
# Setup
mock_response.with_json(
{
"data": [
{"owner": "prkumar", "name": "uplink", "stars": "300"},
{"owner": "prkumar", "name": "uplink-protobuf", "stars": "400"},
],
"errors": [],
}
)
mock_client.with_response(mock_response)
github = GitHub(base_url=BASE_URL, client=mock_client)

# Run
stars = github.get_first_repo_stars("prkumar")

# Verify
assert stars == 300


def test_post_json(mock_client):
# Setup
github = GitHub(
Expand Down

0 comments on commit 8a369e9

Please sign in to comment.