From 66e3a5aaa5069289b0e038a7cbf8483f86afb31f Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Sat, 10 Aug 2019 01:42:10 -0700 Subject: [PATCH] Omit `Header` with `None` value from request (#169) Fixes #167. --- CHANGELOG.rst | 10 +++++++- tests/unit/test_arguments.py | 4 +++ uplink/arguments.py | 49 +++++++++++++++++++++++++----------- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d87df8e9..62d49bc0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,13 @@ 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_ +============= +Changed +------- +- Omit ``Header`` argument from request when its value is ``None``. + (`#167`_, `#169`_) + 0.9.0_ - 2019-06-05 =================== Added @@ -29,7 +36,7 @@ Fixed Changed ------- -- Renamed ``uplink.retry.stop.DISABLE`` to ``uplink.retry.stop.NEVER`` +- Rename ``uplink.retry.stop.DISABLE`` to ``uplink.retry.stop.NEVER`` 0.8.0_ - 2019-02-16 =================== @@ -294,6 +301,7 @@ Added .. _`Semantic Versioning`: https://packaging.python.org/tutorials/distributing-packages/#semantic-versioning-preferred .. Releases +.. _Unreleased: https://github.com/prkumar/uplink/compare/v0.9.0...HEAD .. _0.9.0: https://github.com/prkumar/uplink/compare/v0.8.0...v0.9.0 .. _0.8.0: https://github.com/prkumar/uplink/compare/v0.7.0...v0.8.0 .. _0.7.0: https://github.com/prkumar/uplink/compare/v0.6.1...v0.7.0 diff --git a/tests/unit/test_arguments.py b/tests/unit/test_arguments.py index c8e9be00..73581c35 100644 --- a/tests/unit/test_arguments.py +++ b/tests/unit/test_arguments.py @@ -327,6 +327,10 @@ def test_modify_request(self, request_builder): arguments.Header("hello").modify_request(request_builder, "world") assert request_builder.info["headers"] == {"hello": "world"} + def test_skip_none(self, request_builder): + arguments.Header("hello").modify_request(request_builder, None) + assert request_builder.info["headers"] == {} + class TestHeaderMap(ArgumentTestCase, FuncDecoratorTestCase): type_cls = arguments.HeaderMap diff --git a/uplink/arguments.py b/uplink/arguments.py index a888c10e..e4297801 100644 --- a/uplink/arguments.py +++ b/uplink/arguments.py @@ -225,6 +225,23 @@ def converter_key(self): # pragma: no cover raise NotImplementedError +class EncodeNoneMixin(object): + #: Identifies how a `None` value should be encoded in the request. + _encode_none = None # type: str + + def _modify_request(self, request_builder, value): # pragma: no cover + raise NotImplementedError + + def modify_request(self, request_builder, value): + if value is None: + if self._encode_none is None: + # ignore value if it is None and shouldn't be encoded + return + else: + value = self._encode_none + super(EncodeNoneMixin, self).modify_request(request_builder, value) + + class FuncDecoratorMixin(object): @classmethod def _is_static_call(cls, *args_, **kwargs): @@ -304,7 +321,7 @@ def _modify_request(self, request_builder, value): request_builder.url.set_variable({self.name: value}) -class Query(FuncDecoratorMixin, NamedArgument): +class Query(FuncDecoratorMixin, EncodeNoneMixin, NamedArgument): """ Set a dynamic query parameter. @@ -392,14 +409,6 @@ def _modify_request(self, request_builder, value): request_builder.info, {self.name: value}, self._encoded ) - def modify_request(self, request_builder, value): - if value is None: - # ignore value if it is None and shouldn't be encoded - if self._encode_none is not None: - self._modify_request(request_builder, self._encode_none) - else: - super(Query, self).modify_request(request_builder, value) - class QueryMap(FuncDecoratorMixin, TypedArgument): """ @@ -438,20 +447,30 @@ def _modify_request(self, request_builder, value): Query.update_params(request_builder.info, value, self._encoded) -class Header(FuncDecoratorMixin, NamedArgument): +class Header(FuncDecoratorMixin, EncodeNoneMixin, NamedArgument): """ Pass a header as a method argument at runtime. - While :py:class:`uplink.headers` attaches static headers - that define all requests sent from a consumer method, this - class turns a method argument into a dynamic header value. + While :py:class:`uplink.headers` attaches request headers values + that are static across all requests sent from the decorated + consumer method, this annotation turns a method argument into a + dynamic request header. Example: .. code-block:: python @get("/user") - def (self, session_id: Header("Authorization")): - \"""Get the authenticated user\""" + def me(self, session_id: Header("Authorization")): + \"""Get the authenticated user.\""" + + To define an optional header, use the default value of `None`: + + @get("/repositories") + def fetch_repos(self, auth: Header("Authorization") = None): + \"""List all public repositories.\""" + + When the argument is not used, the header will not be added + to the request. """ @property