Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v0.9.6 #250

Merged
merged 9 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading