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

Add timeout argument annotation #133

Merged
merged 6 commits into from
Jan 4, 2019
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
5 changes: 5 additions & 0 deletions docs/source/dev/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,8 @@ Url
===

.. autoclass:: uplink.Url

Timeout
=======

.. autoclass:: uplink.Timeout
9 changes: 9 additions & 0 deletions tests/unit/test_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,12 @@ def test_modify_request_definition_failure(
def test_modify_request(self, request_builder):
arguments.Url().modify_request(request_builder, "/some/path")
assert request_builder.url == "/some/path"


class TestTimeout(ArgumentTestCase, FuncDecoratorTestCase):
type_cls = arguments.Timeout
expected_converter_key = keys.Identity()

def test_modify_request(self, request_builder):
arguments.Timeout().modify_request(request_builder, 10)
assert request_builder.info["timeout"] == 10
31 changes: 31 additions & 0 deletions tests/unit/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,37 @@ def test_eq(self):
assert not (converters.keys.Sequence(1) == 1)


class TestIdentity(object):
_sentinel = object()

@pytest.fixture(scope="class")
def registry(self):
return converters.ConverterFactoryRegistry(
(converters.StandardConverter(),)
)

@pytest.fixture(scope="class")
def key(self):
return converters.keys.Identity()

@pytest.mark.parametrize(
"value, expected",
[
(1, 1),
("a", "a"),
(_sentinel, _sentinel),
({"a": "b"}, {"a": "b"}),
([1, 2], [1, 2]),
],
)
def test_convert(self, registry, key, value, expected):
converter = registry[key]()
assert converter(value) == expected

def test_eq(self):
assert converters.keys.Identity() == converters.keys.Identity()


class TestRegistry(object):
@pytest.mark.parametrize(
"converter",
Expand Down
2 changes: 2 additions & 0 deletions uplink/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
PartMap,
Body,
Url,
Timeout,
)
from uplink.ratelimit import ratelimit
from uplink.retry import retry
Expand Down Expand Up @@ -88,6 +89,7 @@
"PartMap",
"Body",
"Url",
"Timeout",
"retry",
"ratelimit",
]
Expand Down
33 changes: 33 additions & 0 deletions uplink/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"PartMap",
"Body",
"Url",
"Timeout",
]


Expand Down Expand Up @@ -675,3 +676,35 @@ def modify_request_definition(self, request_definition_builder):
def _modify_request(cls, request_builder, value):
"""Updates request url."""
request_builder.url = value


class Timeout(FuncDecoratorMixin, ArgumentAnnotation):
"""
Pass a timeout as a method argument at runtime.

While :py:class:`uplink.timeout` attaches static timeout to all requests
sent from a consumer method, this class turns a method argument into a
dynamic timeout value.

Example:
.. code-block:: python

@get("/user/posts")
def get_posts(self, timeout: Timeout() = 60):
\"""Fetch all posts for the current users giving up after given
number of seconds.\"""

"""

@property
def type(self):
return float

@property
def converter_key(self):
"""Do not convert passed argument."""
return keys.Identity()

def _modify_request(self, request_builder, value):
"""Modifies request timeout."""
request_builder.info["timeout"] = value
18 changes: 18 additions & 0 deletions uplink/converters/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,21 @@ def convert(self, converter, value):
return list(map(converter, value))
else:
return converter(value)


class Identity(object):
"""
Identity conversion - pass value as is
"""

def __call__(self, converter_registry):
return self._identity_factory

def __eq__(self, other):
return type(other) is type(self)

def _identity_factory(self, *args, **kwargs):
return self._identity

def _identity(self, value):
return value