Skip to content

Commit 8a369e9

Browse files
authored
Merge pull request #250 from prkumar/master
Release v0.9.6
2 parents a013758 + 55989ac commit 8a369e9

19 files changed

Lines changed: 791 additions & 472 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,5 @@ Pipfile.lock
123123
# macOS
124124
.DS_Store
125125

126+
# VS Code
127+
.vscode/

CHANGELOG.rst

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,27 @@ All notable changes to this project will be documented in this file.
66
The format is based on `Keep a Changelog`_, and this project adheres to the
77
`Semantic Versioning`_ scheme.
88

9+
Unreleased
10+
==========
11+
Added
12+
-----
13+
- Add a new base class, ``uplink.retry.RetryBackoff``, which can be extended to
14+
implement custom backoff strategies. An instance of a ``RetryBackoff`` subclass
15+
can be provided through the ``backoff`` argument of the ``@retry`` decorator.
16+
(`#238`_)
17+
18+
Changed
19+
-------
20+
- Bump minimum version of ``six`` to ``1.13.0``. (`#246`_)
21+
22+
Fixed
23+
-----
24+
- Fix ``@returns.json`` to cast the JSON response (or field referenced by the
25+
``key`` argument) using the ``type`` argument when it is a callable type.
26+
This effectively reverts a change in the decorator's behavior that was
27+
introduced in v0.9.3. (`#215`_)
28+
29+
930
0.9.5_ - 2022-01-04
1031
====================
1132
Added
@@ -405,9 +426,12 @@ Added
405426
.. _#204: https://github.com/prkumar/uplink/pull/204
406427
.. _#207: https://github.com/prkumar/uplink/pull/207
407428
.. _#209: https://github.com/prkumar/uplink/pull/209
429+
.. _#215: https://github.com/prkumar/uplink/issues/215
408430
.. _#217: https://github.com/prkumar/uplink/issues/217
409431
.. _#221: https://github.com/prkumar/uplink/issues/221
410432
.. _#237: https://github.com/prkumar/uplink/discussions/237
433+
.. _#238: https://github.com/prkumar/uplink/issues/238
434+
.. _#246: https://github.com/prkumar/uplink/issues/246
411435

412436
.. Commits
413437
.. _3653a672ee: https://github.com/prkumar/uplink/commit/3653a672ee0703119720d0077bb450649af5459c
@@ -421,4 +445,4 @@ Added
421445
.. _@cognifloyd: https://github.com/cognifloyd
422446
.. _@gmcrocetti: https://github.com/gmcrocetti
423447
.. _@leiserfg: https://github.com/leiserfg
424-
.. _@lust4life: https://github.com/lust4life
448+
.. _@lust4life: https://github.com/lust4life

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ Extra! Extra!
119119
-------------
120120

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

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

docs/source/dev/decorators.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,23 @@ to specify one of the alternative approaches exposed through the
150150
def get_user(self, user):
151151
"""Get user by username."""
152152
153+
You can implement a custom backoff strategy by extending the class
154+
:class:`uplink.retry.RetryBackoff`:
155+
156+
.. code-block:: python
157+
:emphasize-lines: 3,7
158+
159+
from uplink.retry import RetryBackoff
160+
161+
class MyCustomBackoff(RetryBackoff):
162+
...
163+
164+
class GitHub(uplink.Consumer):
165+
@uplink.retry(backoff=MyCustomBackoff())
166+
@uplink.get("/users/{user}")
167+
def get_user(self, user):
168+
pass
169+
153170
.. automodule:: uplink.retry.backoff
154171
:members:
155172

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def read(filename):
1515
exec(fp.read(), about)
1616
about = dict((k.strip("_"), about[k]) for k in about)
1717

18-
install_requires = ["requests>=2.18.0", "six>=1.12.0", "uritemplate>=3.0.0"]
18+
install_requires = ["requests>=2.18.0", "six>=1.13.0", "uritemplate>=3.0.0"]
1919

2020
extras_require = {
2121
"marshmallow": ["marshmallow>=2.15.0"],
@@ -29,6 +29,7 @@ def read(filename):
2929
"twisted:python_version == '3.4'": "twisted<=19.2.1",
3030
"typing": ["typing>=3.6.4"],
3131
"tests": ["pytest", "pytest-mock", "pytest-cov", "pytest-twisted"],
32+
"tests:python_version >= '3.5'": ["pytest-asyncio"],
3233
}
3334

3435
metadata = {

tests/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import sys
2+
3+
collect_ignore = []
4+
if sys.version_info.major < 3:
5+
collect_ignore.extend(
6+
[
7+
"unit/test_aiohttp_client.py",
8+
"integration/test_retry_aiohttp.py",
9+
]
10+
)

tests/integration/test_retry.py

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
# Local imports.
66
from uplink import get, Consumer, retry
77
from uplink.clients import io
8-
from tests import requires_python34
98

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

32-
@retry(max_attempts=3, on_exception=retry.CONNECTION_TIMEOUT)
31+
@retry(
32+
stop=retry.stop.after_attempt(3), on_exception=retry.CONNECTION_TIMEOUT
33+
)
3334
@get("repos/{user}/{repo}/project/{project}")
3435
def get_project(self, user, repo, project):
3536
pass
@@ -123,30 +124,6 @@ def test_retry_with_status_501(mock_client, mock_response):
123124
assert len(mock_client.history) == 2
124125

125126

126-
@requires_python34
127-
def test_retry_with_asyncio(mock_client, mock_response):
128-
import asyncio
129-
130-
@asyncio.coroutine
131-
def coroutine():
132-
return mock_response
133-
134-
# Setup
135-
mock_response.with_json({"id": 123, "name": "prkumar"})
136-
mock_client.with_side_effect([Exception, coroutine()])
137-
mock_client.with_io(io.AsyncioStrategy())
138-
github = GitHub(base_url=BASE_URL, client=mock_client)
139-
140-
# Run
141-
awaitable = github.get_user("prkumar")
142-
loop = asyncio.get_event_loop()
143-
response = loop.run_until_complete(asyncio.ensure_future(awaitable))
144-
145-
# Verify
146-
assert len(mock_client.history) == 2
147-
assert response.json() == {"id": 123, "name": "prkumar"}
148-
149-
150127
@pytest_twisted.inlineCallbacks
151128
def test_retry_with_twisted(mock_client, mock_response):
152129
from twisted.internet import defer
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Third-party imports
2+
import pytest
3+
4+
# Local imports.
5+
from uplink.clients import io
6+
7+
from . import test_retry
8+
9+
10+
@pytest.mark.asyncio
11+
async def test_retry_with_asyncio(mock_client, mock_response):
12+
import asyncio
13+
14+
@asyncio.coroutine
15+
def coroutine():
16+
return mock_response
17+
18+
# Setup
19+
mock_response.with_json({"id": 123, "name": "prkumar"})
20+
mock_client.with_side_effect([Exception, coroutine()])
21+
mock_client.with_io(io.AsyncioStrategy())
22+
github = test_retry.GitHub(base_url=test_retry.BASE_URL, client=mock_client)
23+
24+
# Run
25+
response = await github.get_user("prkumar")
26+
27+
# Verify
28+
assert len(mock_client.history) == 2
29+
assert response.json() == {"id": 123, "name": "prkumar"}

tests/integration/test_returns.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,47 @@ def get_repo(self, user, repo):
4949
def get_repos(self, user):
5050
pass
5151

52+
@uplink.returns.from_json(key=("data", 0, "size"))
53+
@uplink.get("/users/{user}/repos")
54+
def get_first_repo_size(self, user):
55+
pass
56+
57+
@uplink.returns.from_json(key=("data", 0, "stars"), type=int)
58+
@uplink.get("/users/{user}/repos")
59+
def get_first_repo_stars(self, user):
60+
pass
61+
5262
@uplink.json
5363
@uplink.post("/users/{user}/repos", args={"repo": uplink.Body(Repo)})
5464
def create_repo(self, user, repo):
5565
pass
5666

67+
@uplink.returns(object)
68+
@uplink.get("/users")
69+
def list_users(self):
70+
pass
71+
5772

5873
# Tests
5974

6075

76+
def test_returns_response_when_type_has_no_converter(
77+
mock_client, mock_response
78+
):
79+
# Setup
80+
mock_response.with_json({"id": 123, "name": "prkumar"})
81+
mock_client.with_response(mock_response)
82+
github = GitHub(
83+
base_url=BASE_URL, client=mock_client, converters=user_reader
84+
)
85+
86+
# Run
87+
response = github.list_users()
88+
89+
# Verify
90+
assert response == mock_response
91+
92+
6193
def test_returns_with_type(mock_client, mock_response):
6294
# Setup
6395
mock_response.with_json({"id": 123, "name": "prkumar"})
@@ -114,6 +146,48 @@ def test_returns_json_with_list(mock_client, mock_response):
114146
] == repo
115147

116148

149+
def test_returns_json_by_key(mock_client, mock_response):
150+
# Setup
151+
mock_response.with_json(
152+
{
153+
"data": [
154+
{"owner": "prkumar", "name": "uplink", "size": 300},
155+
{"owner": "prkumar", "name": "uplink-protobuf", "size": 400},
156+
],
157+
"errors": [],
158+
}
159+
)
160+
mock_client.with_response(mock_response)
161+
github = GitHub(base_url=BASE_URL, client=mock_client)
162+
163+
# Run
164+
size = github.get_first_repo_size("prkumar")
165+
166+
# Verify
167+
assert size == 300
168+
169+
170+
def test_returns_json_with_key_and_type(mock_client, mock_response):
171+
# Setup
172+
mock_response.with_json(
173+
{
174+
"data": [
175+
{"owner": "prkumar", "name": "uplink", "stars": "300"},
176+
{"owner": "prkumar", "name": "uplink-protobuf", "stars": "400"},
177+
],
178+
"errors": [],
179+
}
180+
)
181+
mock_client.with_response(mock_response)
182+
github = GitHub(base_url=BASE_URL, client=mock_client)
183+
184+
# Run
185+
stars = github.get_first_repo_stars("prkumar")
186+
187+
# Verify
188+
assert stars == 300
189+
190+
117191
def test_post_json(mock_client):
118192
# Setup
119193
github = GitHub(

0 commit comments

Comments
 (0)