From 84f1eb1d62a9a4e476b18ac1a34c28103239c71f Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Mon, 2 Oct 2017 19:04:47 -0700 Subject: [PATCH 01/18] Update documentation --- README.rst | 5 +++-- docs/source/index.rst | 40 ++++++++++++++++++++-------------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/README.rst b/README.rst index 42f929e4..c81d20f8 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,8 @@ Python HTTP Made Expressive. Inspired by `Retrofit `__, the public + API outlined in this documentation should not be considered stable until the + release of :code:`v1.0.0`. + + However, while **Uplink** is under construction, we invite eager users + to install early and provide open feedback, which can be as simple as + opening a GitHub issue when you notice a missing feature, latent defect, + documentation oversight, etc. + + Moreover, for those interested in contributing, I plan to publish a + contribution guide soon, but in the meantime, please feel free to fork + the `repository on GitHub `__ and open + a pull request ('tis + `Hacktoberfest `__, after all)! + ---- A Quick Walkthrough, with GitHub API v3: @@ -44,7 +63,7 @@ To construct a consumer instance, use the helper function :py:func:`uplink.build To access the GitHub API with this instance, we simply invoke any of the methods that we defined in the interface above. To illustrate, let's update my GitHub -user's bio: +profile's bio: .. code-block:: python @@ -66,25 +85,6 @@ reusable, and fairly compact, with minimal user effort. ---- -.. note:: - - **Uplink** is currently in initial development and, therefore, not - production ready at the moment. Furthermore, as the package follows the - `Semantic Versioning Specification `__, the public - API outlined in this documentation should not be considered stable until the - release of :code:`v1.0.0`. - - However, while **Uplink** is under construction, we invite eager users - to install early and provide open feedback, which can be as simple as - opening a GitHub issue when you notice a missing feature, latent defect, - documentation oversight, etc. - - Moreover, for those interested in contributing, I plan to publish a - contribution guide soon, but in the meantime, please feel free to fork - the `repository on GitHub `__ and open - a pull request ('tis - `Hacktoberfest `__, after all)! - The User Manual --------------- From 46e6596d2236e76d87df8b006122574d674735fa Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Tue, 3 Oct 2017 11:18:46 -0700 Subject: [PATCH 02/18] Update documentation --- docs/source/index.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index af0128e2..cb84caa9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -13,10 +13,10 @@ A Declarative HTTP Client for Python, inspired by `Retrofit .. note:: **Uplink** is currently in initial development and, therefore, not - production ready at the moment. Furthermore, as the package follows the - `Semantic Versioning Specification `__, the public - API outlined in this documentation should not be considered stable until the - release of :code:`v1.0.0`. + production ready at the moment. Furthermore, as the package follows a + `semantic versioning `__ + scheme, the public API outlined in this documentation should not be + considered stable until the release of :code:`v1.0.0`. However, while **Uplink** is under construction, we invite eager users to install early and provide open feedback, which can be as simple as From 10ca957e4995267540838913ceee92643ae933e5 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Tue, 3 Oct 2017 11:20:51 -0700 Subject: [PATCH 03/18] Update deploy settings --- .travis.yml | 1 + uplink/__about__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2b16f13a..4bcb0d07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,4 +21,5 @@ deploy: secure: 2Gbn5BKdn4VovYg/iQTvVWndfzKm8941aF7mPcZ+Ped4Y1asDW8EEqhBP3Ocknive4HOwe22B4phIqnZ31/g2p/20lo5z/ywULwOCCuoRGTz5lMCFQt4MkJp3fvJwVUShOPJHPW4450UUOqmCoylaXFZsgq5+HLuplCMUgWro7ZiM8mfq6X45iCrHRGXSUh1SSmgSMLYZ7cM80bjvGjP0SlSsh+5ZUS6srDlUxFilH6Cc7+y0CjrnOxk1YIEhk+usLccaewpn0tpdhQf5gLQ6Q+3hj/o/ovnUiPyy4kYeCHjOgcv50JKPWNzM8Ie+9iWNZycs3tvwwZyWRueRMGAjnIO+AigQjuzIoaN8QEt3GyU0Rxxt1qU+KMgBvgSXV66l6w8Q5htpCD1fxIQxdElx+7gZQ1FUm9sHP7BVNVHgKPDP0etO+k5nx9d/1dXVw1CxSO/4zwfJ6VfxFIkIzOleoT7lydzdKsFY/5PlCD2WjErQfbdO8HIqinBwTFG+gfqZnblp64sRNMAcmzkqxN9GocltctRwmPHRbhUP960akeqbKlfQsAXrB2cxMIwYsX8AG21MQ+4hAOgQinAc5AiZ4Gmy7Nq0dxC126uAC9t8Y+4sq2Cwft7xzy/iSMM2hmPOlQx0kWbI7T1eI3FcXvz2aUdLhOz2o5L6VGfie7/RNo= on: tags: true + python: '3.6' distributions: "sdist bdist_wheel" diff --git a/uplink/__about__.py b/uplink/__about__.py index 134f79e5..430135bd 100644 --- a/uplink/__about__.py +++ b/uplink/__about__.py @@ -3,4 +3,4 @@ that is used both in distribution (i.e., setup.py) and within the codebase. """ -__version__ = "0.1.0-rc.2" +__version__ = "0.1.0rc3" From 58150770be02e63a07f15175d68a6857b83cd5d1 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Wed, 4 Oct 2017 19:55:42 -0700 Subject: [PATCH 04/18] Update `uplink.Path` documentation. --- uplink/types.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/uplink/types.py b/uplink/types.py index 7f0147a6..245ec66d 100644 --- a/uplink/types.py +++ b/uplink/types.py @@ -222,6 +222,8 @@ class Path(NamedArgument): """ Substitution of a URL path parameter. + Uplink supports `URI templates `__. + Here's a simple example: .. code-block:: python @@ -229,8 +231,14 @@ class Path(NamedArgument): @get("todos{/id}") def get_todo(self, todo_id: Path("id")): pass - Then, calling :code:`todo_service.get_todo(100)` would produce the - path :code:`"todos/100"`. + Then, invoking :code:`get_todo` with a consumer instance: + + .. code-block:: python + + todo_service.get_todo(100) + + builds an HTTP request that has a URL ending with: + :code:`"todos/100"`. `uplink` will try to match unannotated function arguments with URL path parameters. For example, we can rewrite the previous From 278c560a7496b71919ac2b8219f9fc725a0b5d81 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Fri, 6 Oct 2017 09:19:12 -0700 Subject: [PATCH 05/18] Update documentation --- README.rst | 4 +-- docs/source/conf.py | 7 +++-- docs/source/index.rst | 10 +++---- docs/source/types.rst | 63 ++++++++++++++++++++++++++++++++++--------- setup.py | 1 + tests/test_types.py | 4 +-- uplink/types.py | 38 +++++++++++++++----------- 7 files changed, 88 insertions(+), 39 deletions(-) diff --git a/README.rst b/README.rst index c81d20f8..c50b6195 100644 --- a/README.rst +++ b/README.rst @@ -37,7 +37,7 @@ To construct a consumer instance, use the helper function ``uplink.build``: To access the GitHub API with this instance, we simply invoke any of the methods that we defined in the interface above. To illustrate, let's update my GitHub -profile's bio: +profile bio: .. code-block:: python @@ -54,7 +54,7 @@ sends that synchronously over the network. Furthermore, the returned print(response.json()) # {u'disk_usage': 216141, u'private_gists': 0, ... In essence, **Uplink** delivers API consumers that are self-describing, -reusable, and fairly compact, with minimal user effort. +reusable, and fairly compact, with minimal user pain ☺️. Installation ------------ diff --git a/docs/source/conf.py b/docs/source/conf.py index 10407be6..c65ba07a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -30,11 +30,14 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc', +extensions = [ + 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', - 'sphinx.ext.viewcode'] + 'sphinx.ext.viewcode', + 'sphinx.ext.napoleon' +] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/source/index.rst b/docs/source/index.rst index cb84caa9..2aae228e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,8 +15,8 @@ A Declarative HTTP Client for Python, inspired by `Retrofit **Uplink** is currently in initial development and, therefore, not production ready at the moment. Furthermore, as the package follows a `semantic versioning `__ - scheme, the public API outlined in this documentation should not be - considered stable until the release of :code:`v1.0.0`. + scheme, the public API outlined in this documentation should be + considered tentative until the :code:`v1.0.0` release. However, while **Uplink** is under construction, we invite eager users to install early and provide open feedback, which can be as simple as @@ -63,7 +63,7 @@ To construct a consumer instance, use the helper function :py:func:`uplink.build To access the GitHub API with this instance, we simply invoke any of the methods that we defined in the interface above. To illustrate, let's update my GitHub -profile's bio: +profile bio: .. code-block:: python @@ -81,14 +81,14 @@ the returned response :py:obj:`r` is a :py:class:`requests.Response` print(r.json()) # {u'disk_usage': 216141, u'private_gists': 0, ... In essence, **Uplink** delivers API consumers that are self-describing, -reusable, and fairly compact, with minimal user effort. +reusable, and fairly compact, with minimal user pain ☺️. ---- The User Manual --------------- -This guide describes the package's Public API. +This guide describes Uplink's public API. .. toctree:: :maxdepth: 2 diff --git a/docs/source/types.rst b/docs/source/types.rst index 928430d1..28ed318c 100644 --- a/docs/source/types.rst +++ b/docs/source/types.rst @@ -11,13 +11,12 @@ you indicate the dynamic parts of the request by appropriately annotating those arguments with the classes defined here. To illustrate, for the method :py:meth:`get_user` in the following -snippet, we have flagged the argument :py:attr:`username` as a URI path -parameter using the :py:class:`~uplink.Path` annotation: +snippet, we have flagged the argument :py:attr:`username` as a URI +placeholder replacement using the :py:class:`~uplink.Path` annotation: .. code-block:: python class GitHubService(object): - @uplink.get("users/{username}") def get_user(self, username: uplink.Path("username")): pass @@ -53,9 +52,9 @@ annotation corresponds to :py:attr:`commits_url`, and :py:class:`~uplink.Path` to :py:attr:`sha`. .. code-block:: python + :emphasize-lines: 2,3 class GitHubService(object): - @uplink.Path @uplink.Url @uplink.get @@ -69,9 +68,9 @@ The second approach involves using the method annotation their corresponding function arguments (again, ignore :py:attr:`self`): .. code-block:: python + :emphasize-lines: 2 class GitHubService(object): - @uplink.args(uplink.Url, uplink.Path) @uplink.get def get_commit(self, commits_url, sha): pass @@ -84,9 +83,9 @@ Finally, when using Python 3, you can use these classes as function annotations (:pep:`3107`): .. code-block:: python + :emphasize-lines: 3 class GitHubService(object): - @uplink.get def get_commit(self, commit_url: uplink.Url, sha: uplink.Path): pass @@ -107,10 +106,10 @@ name, :py:attr:`username`, matches the intended URI path parameter: .. code-block:: python class GitHubService(object): - @uplink.get("users/{username}") def get_user(self, username: uplink.Path): pass +.. _implicit_path_annotations: Implicit :code:`Path` Annotations ---------------------------------- @@ -127,7 +126,6 @@ for us: .. code-block:: python class GitHubService(object): - @uplink.get("user/{username}") def get_user(self, username): pass @@ -135,13 +133,54 @@ Important to note, failure to resolve all unannotated function arguments raises an :py:class:`~uplink.InvalidRequestDefinitionError`. -:code:`Path`: URL Path Variables -================================ +:code:`Path`: Leverage URI Templates +==================================== .. autoclass:: uplink.Path :members: -:code:`Query`: URL Query Parameters -=================================== +:code:`Query`: Define URL Query Parameters +========================================== .. autoclass:: uplink.Query + +.. autoclass:: uplink.QueryMap + +:code:`Header`: Add HTTP Header Fields +====================================== + +.. autoclass:: uplink.Header + :members: + +.. autoclass:: uplink.HeaderMap + :members: + +:code:`Field`: +============== + +.. autoclass:: uplink.Field + :members: + +.. autoclass:: uplink.FieldMap + :members: + +:code:`Part`: +============== + +.. autoclass:: uplink.Part + :members: + +.. autoclass:: uplink.PartMap + :members: + +:code:`Body`: +============= + +.. autoclass:: uplink.Body + :members: + +:code:`Url`: +============= + +.. autoclass:: uplink.Url + :members: \ No newline at end of file diff --git a/setup.py b/setup.py index 5b6296e2..bbe27af8 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ def read(filename): "classifiers": [ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", diff --git a/tests/test_types.py b/tests/test_types.py index 57b044dd..1e112a2e 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -247,7 +247,7 @@ def test_modify_request(self, request_builder): class TestField(ArgumentTestCase): type_cls = types.Field - expected_converter_type = converter.CONVERT_TO_REQUEST_BODY + expected_converter_type = converter.CONVERT_TO_STRING def test_modify_request(self, request_builder): types.Field("hello").modify_request(request_builder, "world") @@ -261,7 +261,7 @@ def test_modify_request_failure(self, request_builder): class TestFieldMap(ArgumentTestCase): type_cls = types.FieldMap - expected_converter_type = converter.Map(converter.CONVERT_TO_REQUEST_BODY) + expected_converter_type = converter.Map(converter.CONVERT_TO_STRING) def test_modify_request(self, request_builder): types.FieldMap().modify_request(request_builder, {"hello": "world"}) diff --git a/uplink/types.py b/uplink/types.py index 245ec66d..2381a949 100644 --- a/uplink/types.py +++ b/uplink/types.py @@ -220,16 +220,18 @@ def modify_request(self, request_builder, value): class Path(NamedArgument): """ - Substitution of a URL path parameter. + Substitution of a path variable in a `URI template + `__. - Uplink supports `URI templates `__. - - Here's a simple example: + URI template parameters are enclosed in braces (e.g., + :code:`{name}`). To map an argument to a declared URI parameter, use + the :py:class:`Path` annotation: .. code-block:: python - @get("todos{/id}") - def get_todo(self, todo_id: Path("id")): pass + class TodoService(object): + @get("todos{/id}") + def get_todo(self, todo_id: Path("id")): pass Then, invoking :code:`get_todo` with a consumer instance: @@ -237,17 +239,20 @@ def get_todo(self, todo_id: Path("id")): pass todo_service.get_todo(100) - builds an HTTP request that has a URL ending with: - :code:`"todos/100"`. + creates an HTTP request with a URL ending in :code:`todos/100`. - `uplink` will try to match unannotated function arguments with - URL path parameters. For example, we can rewrite the previous - example as: + Note: + When building the consumer instance, :py:func:`uplink.build` will try + match unannotated function arguments with URL path parameters. See + :ref:`implicit_path_annotations` for details. - .. code-block:: python + For example, we could rewrite the method from the previous + example as: - @get("todos{/todo_id}") - def get_todo(self, todo_id): pass + .. code-block:: python + + @get("todos{/todo_id}") + def get_todo(self, todo_id): pass """ @property @@ -323,6 +328,7 @@ def modify_request(cls, request_builder, value): class Field(NamedArgument): + """Key-value pair for a form-encoded request.""" class FieldAssignmentFailed(exceptions.AnnotationError): message = ( @@ -335,7 +341,7 @@ def __init__(self, field): @property def converter_type(self): - return converter.CONVERT_TO_REQUEST_BODY + return converter.CONVERT_TO_STRING def modify_request(self, request_builder, value): try: @@ -356,7 +362,7 @@ class FieldMapUpdateFailed(exceptions.AnnotationError): @property def converter_type(self): - return converter.Map(converter.CONVERT_TO_REQUEST_BODY) + return converter.Map(converter.CONVERT_TO_STRING) def modify_request(self, request_builder, value): try: From 3bf30edbf917653394d2518e1152a07d283f22ce Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Mon, 9 Oct 2017 09:05:04 -0700 Subject: [PATCH 06/18] Update documentation --- docs/source/decorators.rst | 16 +++ docs/source/getting_started.rst | 56 ++++++++++ docs/source/index.rst | 13 ++- docs/source/introduction.rst | 174 ++++++++++++++++++++++++++++++++ docs/source/types.rst | 161 +++-------------------------- uplink/types.py | 13 +-- 6 files changed, 273 insertions(+), 160 deletions(-) create mode 100644 docs/source/decorators.rst create mode 100644 docs/source/getting_started.rst create mode 100644 docs/source/introduction.rst diff --git a/docs/source/decorators.rst b/docs/source/decorators.rst new file mode 100644 index 00000000..f17d912b --- /dev/null +++ b/docs/source/decorators.rst @@ -0,0 +1,16 @@ +Method Annotations +****************** + +Assign HTTP :code:`headers` +=========================== + +Format Body for POST/PUT/PATCH Requests +======================================= + +Apply :code:`timeout` on Request +================================ + +Annotate Arguments with :code:`args` +==================================== + + diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst new file mode 100644 index 00000000..be74bc9f --- /dev/null +++ b/docs/source/getting_started.rst @@ -0,0 +1,56 @@ +Getting Started +*************** + +Send a Request +============== + +Sending a simple + +Setting the URL +=============== + +Path Variables +============== + +Implicit :code:`Path` Annotations +---------------------------------- + +When building the consumer instance, :py:func:`uplink.build` will try to resolve +unannotated method arguments by matching their names with URI path parameters. + +For example, consider the consumer defined below, in which the method +:py:meth:`get_user` has an unannotated argument, :py:attr:`username`. +Since its name matches the URI path parameter ``{username}``, +:py:mod:`uplink` will auto-annotate the argument with :py:class:`Path` +for us: + +.. code-block:: python + + class GitHubService(object): + @uplink.get("user/{username}") + def get_user(self, username): pass + +Important to note, failure to resolve all unannotated function arguments +raises an :py:class:`~uplink.InvalidRequestDefinitionError`. + +Query Parameters +================ + +HTTP Headers +============ + +URL-Encoded Request Body +======================== + +Send Multipart Form Data +======================== + +Other Content Types +=================== + + + + + + + diff --git a/docs/source/index.rst b/docs/source/index.rst index 2aae228e..b5fd4ab8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -83,17 +83,24 @@ the returned response :py:obj:`r` is a :py:class:`requests.Response` In essence, **Uplink** delivers API consumers that are self-describing, reusable, and fairly compact, with minimal user pain ☺️. ----- - The User Manual --------------- -This guide describes Uplink's public API. +Showcasing Uplink's public API, this guide helps you get started .. toctree:: :maxdepth: 2 install.rst + introduction.rst + getting_started.rst + +The Public API +-------------- + +.. toctree:: + :maxdepth: 2 + types.rst .. |Coverage Status| image:: https://coveralls.io/repos/github/prkumar/uplink/badge.svg?branch=master diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst new file mode 100644 index 00000000..96f2610d --- /dev/null +++ b/docs/source/introduction.rst @@ -0,0 +1,174 @@ +Introduction +************ + +Method Annotations +================== + +Essentially, method annotations describe Request properties that are relevant +to all invocations of a consumer method. + +For instance, consider the following GitHub API consumer: + +.. code-block:: python + :emphasize-lines: 2 + + class GitHubService(object): + @uplink.timeout(60) + @uplink.get("/repositories") + def get_repos(self): + """Dump every public repository.""" + + + +Annotated with :py:class:`timeout`, the method :py:meth:`get_repos` will build +HTTP requests that wait an allotted number of seconds -- 60, in this case -- +for the server to respond before giving up. + +As method annotations are simply decorators, you can stack one on top of another +to chain them. + + +Annotating all API Methods in a Class +------------------------------------- + +To apply an annotation across all methods in a class, you can simply +annotate the class rather than each method individually: + +.. code-block:: python + :emphasize-lines: 1,2 + + @uplink.timeout(60) + class GitHubService(object): + @uplink.get("/repositories") + def get_repos(self): + """Dump every public repository.""" + + @uplink.get("/organizations") + def get_organizations(self): + """List all organizations.""" + + +To drive this point home, the consumer defined above is equivalent to +the following, slightly more verbose one: + +.. code-block:: python + + class GitHubService(object): + @uplink.timeout(60) + @uplink.get("/repositories") + def get_repos(self): + """Dump every public repository.""" + + @uplink.timeout(60) + @uplink.get("/organizations") + def get_organizations(self): + """List all organizations.""" + + + +Argument Annotations +===================== + +In programming, parametrization drives a function's dynamic behavior; a +function's output depends normally on its inputs. With +:py:mod:`uplink`, function arguments parametrize an HTTP request, and +you indicate the dynamic parts of the request by appropriately +annotating those arguments. + +To illustrate, for the method :py:meth:`get_user` in the following +snippet, we have flagged the argument :py:attr:`username` as a URI +placeholder replacement using the :py:class:`~uplink.Path` annotation: + +.. code-block:: python + + class GitHubService(object): + @uplink.get("users/{username}") + def get_user(self, username: uplink.Path("username")): pass + +Invoking this method on a consumer instance, like so: + +.. code-block:: python + + github.get_user(username="prkumar") + +Builds an HTTP request that has a URL ending with ``users/prkumar``. + +.. note:: + As you probably took away from the above example, :py:mod:`uplink` + ignores the instance reference argument (e.g., :py:attr:`self`), with + respect to argument annotations. + +Adopting the Argument's Name +---------------------------- + +When you initialize a named annotation, such as a +:py:class:`~uplink.Path` or :py:class:`~Field`, without a name (by +omitting the :py:attr:`name` parameter), it adopts the name of its +corresponding method argument. + +For example, in the snippet below, we can omit naming the +:py:class:`~uplink.Path` annotation since the corresponding argument's +name, :py:attr:`username`, matches the intended URI path parameter: + +.. code-block:: python + + class GitHubService(object): + @uplink.get("users/{username}") + def get_user(self, username: uplink.Path): pass + +Annotating Your Arguments +------------------------- + +There are several ways to annotate arguments. Most examples in this +documentation use function annotations, but this approach is unavailable +for Python 2.7 users. Instead, you can use argument annotations as decorators +or utilize the method annotation :py:class:`~uplink.args`. + +Argument Annotations as Decorators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For one, annotations can work as function decorators. With this approach, +annotations are mapped to arguments from "bottom-up". + +For instance, in the below definition, the :py:class:`~uplink.Url` +annotation corresponds to :py:attr:`commits_url`, and +:py:class:`~uplink.Path` to :py:attr:`sha`. + +.. code-block:: python + :emphasize-lines: 2,3 + + class GitHubService(object): + @uplink.Path + @uplink.Url + @uplink.get + def get_commit(self, commits_url, sha): pass + +Using :py:class:`uplink.args` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The second approach involves using the method annotation +:py:class:`~uplink.args`, arranging annotations in the same order as +their corresponding function arguments (again, ignore :py:attr:`self`): + +.. code-block:: python + :emphasize-lines: 2 + + class GitHubService(object): + @uplink.args(uplink.Url, uplink.Path) + @uplink.get + def get_commit(self, commits_url, sha): pass + + +Function Annotations (Python 3 only) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Finally, when using Python 3, you can use these classes as function +annotations (:pep:`3107`): + +.. code-block:: python + :emphasize-lines: 3 + + class GitHubService(object): + @uplink.get + def get_commit(self, commit_url: uplink.Url, sha: uplink.Path): + pass \ No newline at end of file diff --git a/docs/source/types.rst b/docs/source/types.rst index 28ed318c..559956a9 100644 --- a/docs/source/types.rst +++ b/docs/source/types.rst @@ -1,153 +1,22 @@ Argument Annotations -******************** +****************** -Overview -======== -In programming, parametrization drives a function's dynamic behavior; a -function's output depends normally on its inputs. With -:py:mod:`uplink`, function arguments parametrize an HTTP request, and -you indicate the dynamic parts of the request by appropriately -annotating those arguments with the classes defined here. - -To illustrate, for the method :py:meth:`get_user` in the following -snippet, we have flagged the argument :py:attr:`username` as a URI -placeholder replacement using the :py:class:`~uplink.Path` annotation: - -.. code-block:: python - - class GitHubService(object): - @uplink.get("users/{username}") - def get_user(self, username: uplink.Path("username")): pass - -Invoking this method on a consumer instance, like so: - -.. code-block:: python - - github.get_user(username="prkumar") - -Builds an HTTP request that has a URL ending with ``users/prkumar``. - -.. note:: - As you probably took away from the above example, :py:mod:`uplink` - ignores the instance reference argument (e.g., :py:attr:`self`), with - respect to argument annotations. - -Annotating Your Arguments -------------------------- - -There are several ways to annotate arguments. Most examples in this -documentation use function annotations, but this approach is unavailable -for Python 2.7 users. Instead, you can use argument annotations as decorators -or utilize the method annotation :py:class:`~uplink.args`. - -Argument Annotations as Decorators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -For one, annotations can work as function decorators. With this approach, -annotations are mapped to arguments from "bottom-up". - -For instance, in the below definition, the :py:class:`~uplink.Url` -annotation corresponds to :py:attr:`commits_url`, and -:py:class:`~uplink.Path` to :py:attr:`sha`. - -.. code-block:: python - :emphasize-lines: 2,3 - - class GitHubService(object): - @uplink.Path - @uplink.Url - @uplink.get - def get_commit(self, commits_url, sha): pass - -Using :py:class:`uplink.args` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The second approach involves using the method annotation -:py:class:`~uplink.args`, arranging annotations in the same order as -their corresponding function arguments (again, ignore :py:attr:`self`): - -.. code-block:: python - :emphasize-lines: 2 - - class GitHubService(object): - @uplink.args(uplink.Url, uplink.Path) - @uplink.get - def get_commit(self, commits_url, sha): pass - - -Function Annotations (Python 3 only) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Finally, when using Python 3, you can use these classes as function -annotations (:pep:`3107`): - -.. code-block:: python - :emphasize-lines: 3 - - class GitHubService(object): - @uplink.get - def get_commit(self, commit_url: uplink.Url, sha: uplink.Path): - pass - - -Function Argument Name Adoption -------------------------------- - -When you initialize a named annotation, such as a -:py:class:`~uplink.Path` or :py:class:`~Field`, without a name (by -omitting the :py:attr:`name` parameter), it adopts the name of its -corresponding method argument. - -For example, in the snippet below, we can omit naming the -:py:class:`~uplink.Path` annotation since the corresponding argument's -name, :py:attr:`username`, matches the intended URI path parameter: - -.. code-block:: python - - class GitHubService(object): - @uplink.get("users/{username}") - def get_user(self, username: uplink.Path): pass - -.. _implicit_path_annotations: - -Implicit :code:`Path` Annotations ----------------------------------- - -When building the consumer instance, :py:mod:`uplink` will try to resolve -unannotated method arguments by matching their names with URI path parameters. - -For example, consider the consumer defined below, in which the method -:py:meth:`get_user` has an unannotated argument, :py:attr:`username`. -Since its name matches the URI path parameter ``{username}``, -:py:mod:`uplink` will auto-annotate the argument with :py:class:`Path` -for us: - -.. code-block:: python - - class GitHubService(object): - @uplink.get("user/{username}") - def get_user(self, username): pass - -Important to note, failure to resolve all unannotated function arguments -raises an :py:class:`~uplink.InvalidRequestDefinitionError`. - - -:code:`Path`: Leverage URI Templates -==================================== +Replace a URI :code:`Path` Variable +=================================== .. autoclass:: uplink.Path :members: -:code:`Query`: Define URL Query Parameters -========================================== +Append a URL :code:`Query` Parameter +==================================== .. autoclass:: uplink.Query .. autoclass:: uplink.QueryMap -:code:`Header`: Add HTTP Header Fields -====================================== +Set a HTTP :code:`Header` Field +=============================== .. autoclass:: uplink.Header :members: @@ -155,8 +24,8 @@ raises an :py:class:`~uplink.InvalidRequestDefinitionError`. .. autoclass:: uplink.HeaderMap :members: -:code:`Field`: -============== +Add a URL Encoded Form :code:`Field` +==================================== .. autoclass:: uplink.Field :members: @@ -164,8 +33,8 @@ raises an :py:class:`~uplink.InvalidRequestDefinitionError`. .. autoclass:: uplink.FieldMap :members: -:code:`Part`: -============== +Submit a Form Data :code:`Part` +=============================== .. autoclass:: uplink.Part :members: @@ -173,14 +42,14 @@ raises an :py:class:`~uplink.InvalidRequestDefinitionError`. .. autoclass:: uplink.PartMap :members: -:code:`Body`: -============= +Control the Request :code:`Body` +================================ .. autoclass:: uplink.Body :members: -:code:`Url`: -============= +Assign a :code:`Url` at Runtime +=============================== .. autoclass:: uplink.Url :members: \ No newline at end of file diff --git a/uplink/types.py b/uplink/types.py index 2381a949..8566cc99 100644 --- a/uplink/types.py +++ b/uplink/types.py @@ -251,8 +251,8 @@ def get_todo(self, todo_id: Path("id")): pass .. code-block:: python - @get("todos{/todo_id}") - def get_todo(self, todo_id): pass + @get("todos{/id}") + def get_todo(self, id): pass """ @property @@ -267,11 +267,6 @@ def modify_request(self, request_builder, value): class Query(NamedArgument): - """ - A URL query parameter. - - - """ @staticmethod def convert_to_string(value): @@ -292,9 +287,6 @@ def modify_request(self, request_builder, value): class QueryMap(TypedArgument): - """ - Mapping of URL query parameters. - """ @property def converter_type(self): @@ -328,7 +320,6 @@ def modify_request(cls, request_builder, value): class Field(NamedArgument): - """Key-value pair for a form-encoded request.""" class FieldAssignmentFailed(exceptions.AnnotationError): message = ( From 19694d77b3c112cf91ec471b13383a3ce08e5e55 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Wed, 11 Oct 2017 09:20:43 -0700 Subject: [PATCH 07/18] Update Documentation --- docs/source/decorators.rst | 13 +++++++++++-- docs/source/getting_started.rst | 4 ++-- docs/source/index.rst | 11 ++++++++++- docs/source/introduction.rst | 14 +++++++------- docs/source/types.rst | 31 ++++++++++++++++--------------- 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/docs/source/decorators.rst b/docs/source/decorators.rst index f17d912b..100a7d06 100644 --- a/docs/source/decorators.rst +++ b/docs/source/decorators.rst @@ -4,8 +4,17 @@ Method Annotations Assign HTTP :code:`headers` =========================== -Format Body for POST/PUT/PATCH Requests -======================================= +Format POST/PUT/PATCH Request Body +================================== + +``form_url_encoded`` +-------------------- + +``multipart`` +------------- + +``json`` +-------- Apply :code:`timeout` on Request ================================ diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index be74bc9f..cf9ffa11 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -45,8 +45,8 @@ URL-Encoded Request Body Send Multipart Form Data ======================== -Other Content Types -=================== +JSON Requests, and Other Content Types +====================================== diff --git a/docs/source/index.rst b/docs/source/index.rst index b5fd4ab8..d1da09dd 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -98,11 +98,20 @@ Showcasing Uplink's public API, this guide helps you get started The Public API -------------- +.. todo:: + + Most of this guide is unfinished and completing it is a planned + deliverable for the ``v0.2.0`` release. At the least, this work will + necessitate adding docstrings to the classes enumerated below. + + .. toctree:: - :maxdepth: 2 + :maxdepth: 3 + decorators.rst types.rst + .. |Coverage Status| image:: https://coveralls.io/repos/github/prkumar/uplink/badge.svg?branch=master :target: https://coveralls.io/github/prkumar/uplink?branch=master .. |License| image:: https://img.shields.io/github/license/prkumar/uplink.svg diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 96f2610d..272662fc 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -1,8 +1,8 @@ Introduction ************ -Method Annotations -================== +**Method Annotations**: Static Request Properties +================================================= Essentially, method annotations describe Request properties that are relevant to all invocations of a consumer method. @@ -28,8 +28,8 @@ As method annotations are simply decorators, you can stack one on top of another to chain them. -Annotating all API Methods in a Class -------------------------------------- +A Shortcut for Annotating All Methods in a Class +------------------------------------------------ To apply an annotation across all methods in a class, you can simply annotate the class rather than each method individually: @@ -48,7 +48,7 @@ annotate the class rather than each method individually: """List all organizations.""" -To drive this point home, the consumer defined above is equivalent to +Let's drive this point home; the consumer defined above is equivalent to the following, slightly more verbose one: .. code-block:: python @@ -66,8 +66,8 @@ the following, slightly more verbose one: -Argument Annotations -===================== +**Arguments Annotations**: Dynamic Request Properties +===================================================== In programming, parametrization drives a function's dynamic behavior; a function's output depends normally on its inputs. With diff --git a/docs/source/types.rst b/docs/source/types.rst index 559956a9..6e6423ac 100644 --- a/docs/source/types.rst +++ b/docs/source/types.rst @@ -1,22 +1,22 @@ Argument Annotations -****************** +******************** -Replace a URI :code:`Path` Variable -=================================== +Replace URI ``Path`` Variables +============================== .. autoclass:: uplink.Path :members: -Append a URL :code:`Query` Parameter -==================================== +Append a URL ``Query`` Parameter +================================ .. autoclass:: uplink.Query .. autoclass:: uplink.QueryMap -Set a HTTP :code:`Header` Field -=============================== +Set HTTP ``Header`` Field +========================= .. autoclass:: uplink.Header :members: @@ -24,8 +24,9 @@ Set a HTTP :code:`Header` Field .. autoclass:: uplink.HeaderMap :members: -Add a URL Encoded Form :code:`Field` -==================================== + +Add URL-Encoded Form ``Field`` +============================== .. autoclass:: uplink.Field :members: @@ -33,8 +34,8 @@ Add a URL Encoded Form :code:`Field` .. autoclass:: uplink.FieldMap :members: -Submit a Form Data :code:`Part` -=============================== +Submit Form Data ``Part`` +========================= .. autoclass:: uplink.Part :members: @@ -42,14 +43,14 @@ Submit a Form Data :code:`Part` .. autoclass:: uplink.PartMap :members: -Control the Request :code:`Body` -================================ +Control the Request ``Body`` +============================ .. autoclass:: uplink.Body :members: -Assign a :code:`Url` at Runtime -=============================== +Assign a ``Url`` at Runtime +=========================== .. autoclass:: uplink.Url :members: \ No newline at end of file From 0a7ff4bb1afe0a397487c949d8283330866d6dbb Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Wed, 11 Oct 2017 20:18:00 -0700 Subject: [PATCH 08/18] Fix documentation build issues * 'feature/v0.1.0/documentation' of https://github.com/prkumar/uplink: Update Documentation Update documentation Update documentation --- docs/source/getting_started.rst | 3 +++ uplink/types.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index cf9ffa11..b6ce9f9f 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -12,6 +12,9 @@ Setting the URL Path Variables ============== +.. _implicit_path_annotations: + + Implicit :code:`Path` Annotations ---------------------------------- diff --git a/uplink/types.py b/uplink/types.py index 8566cc99..c6741541 100644 --- a/uplink/types.py +++ b/uplink/types.py @@ -19,6 +19,8 @@ "HeaderMap", "Field", "FieldMap", + "Part", + "PartMap", "Body", "Url" ] From abb3230934925ce05c72fa5280cbdc815bfe29f3 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Fri, 13 Oct 2017 18:23:56 -0700 Subject: [PATCH 09/18] Add `sphinx-autobuild` requirement for documentation --- docs/Makefile | 5 ++++- requirements.txt | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index a59bd8d6..eec3b17f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -17,4 +17,7 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +livehtml: + sphinx-autobuild -b html $(ALLSPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" diff --git a/requirements.txt b/requirements.txt index 46c68808..a030917f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ tox==2.7.0 # Documentation Sphinx==1.6.3 +sphinx-autobuild==0.7.1 From 3f23647510fa359e5935527fa298651bd7a5932a Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Fri, 13 Oct 2017 18:24:13 -0700 Subject: [PATCH 10/18] Update documentation --- docs/source/getting_started.rst | 6 ++---- docs/source/index.rst | 10 +++++----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index b6ce9f9f..fa021179 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -1,10 +1,8 @@ Getting Started *************** -Send a Request -============== - -Sending a simple +Making a Request +================ Setting the URL =============== diff --git a/docs/source/index.rst b/docs/source/index.rst index d1da09dd..2f034083 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,13 +12,13 @@ A Declarative HTTP Client for Python, inspired by `Retrofit .. note:: - **Uplink** is currently in initial development and, therefore, not + Uplink is currently in initial development and, therefore, not production ready at the moment. Furthermore, as the package follows a `semantic versioning `__ scheme, the public API outlined in this documentation should be considered tentative until the :code:`v1.0.0` release. - However, while **Uplink** is under construction, we invite eager users + However, while Uplink is under construction, we invite eager users to install early and provide open feedback, which can be as simple as opening a GitHub issue when you notice a missing feature, latent defect, documentation oversight, etc. @@ -69,10 +69,10 @@ profile bio: r = github.update_user(token, bio="Beam me up, Scotty!").execute() -*Voila*, :py:meth:`update_user` seamlessly builds the request (using the +*Voila*, :py:meth:`update_user` builds the request seamlessly (using the decorators and annotations from the method's definition), and :py:meth:`execute` sends that synchronously over the network. Furthermore, -the returned response :py:obj:`r` is a :py:class:`requests.Response` +the returned response :py:obj:`r` is simply a :py:class:`requests.Response` (`documentation `__): @@ -86,7 +86,7 @@ reusable, and fairly compact, with minimal user pain ☺️. The User Manual --------------- -Showcasing Uplink's public API, this guide helps you get started +Follow this guide to get up and running with Uplink. .. toctree:: :maxdepth: 2 From 900ee8cf588049165ee2371e5c89897f40be41dc Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Tue, 17 Oct 2017 18:05:16 -0700 Subject: [PATCH 11/18] Add CHANGELOG.rst --- CHANGELOG.rst | 27 +++++++++++++++++++++++++++ MANIFEST.in | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..92981147 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,27 @@ +Changelog +********* + +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. + +0.1.0 - 2017-10-20 +================== + +Added +----- +- Python ports for almost all method and argument annotations in `Retrofit + `__. +- Adherence to the variation of the semantic versioning scheme outlined in + the official Python package distribution tutorial. +- MIT License +- Documentation with introduction, instructions for installing, and quick + getting started guide covering the builder and all method and argument + annotations. +- README that contains GitHub API v3 example, installation instructions with + ``pip``, and link to online documentation. + + +.. _`Keep a Changelog`: +.. _`Semantic Versioning`: \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index d8afa927..e8176257 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,2 @@ -include README.rst LICENSE requirements.txt +include README.rst CHANGELOG.rst LICENSE requirements.txt recursive-include tests *.py From dcfd7c6fd0269d12fb641603627e66021dfbb37e Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Tue, 17 Oct 2017 18:05:47 -0700 Subject: [PATCH 12/18] Update documentation --- README.rst | 11 ++--- docs/source/getting_started.rst | 40 ++++++++++++++++++- docs/source/index.rst | 10 ++--- docs/source/install.rst | 13 +++--- docs/source/introduction.rst | 71 +++++++++++++++++++++++---------- 5 files changed, 106 insertions(+), 39 deletions(-) diff --git a/README.rst b/README.rst index c50b6195..d54e0bb3 100644 --- a/README.rst +++ b/README.rst @@ -9,7 +9,8 @@ A Quick Walkthrough, with GitHub API v3 --------------------------------------- Using decorators and function annotations, you can turn any plain old Python -class into an HTTP API consumer: +class into a self-describing consumer of your favorite HTTP webservice: + .. code-block:: python @@ -18,7 +19,7 @@ class into an HTTP API consumer: # To register entities that are common to all API requests, you can # decorate the enclosing class rather than each method separately: @headers({"Accept": "application/vnd.github.v3.full+json"}) - class GitHubService(object): + class GitHub(object): @get("/users/{username}") def get_user(self, username): @@ -33,7 +34,7 @@ To construct a consumer instance, use the helper function ``uplink.build``: .. code-block:: python - github = build(GitHubService, base_url="https://api.github.com/") + github = build(GitHub, base_url="https://api.github.com/") To access the GitHub API with this instance, we simply invoke any of the methods that we defined in the interface above. To illustrate, let's update my GitHub @@ -53,8 +54,8 @@ sends that synchronously over the network. Furthermore, the returned print(response.json()) # {u'disk_usage': 216141, u'private_gists': 0, ... -In essence, **Uplink** delivers API consumers that are self-describing, -reusable, and fairly compact, with minimal user pain ☺️. +In essence, Uplink delivers reusable and self-sufficient objects for +accessing HTTP webservices, with minimal code and user pain ☺️. Installation ------------ diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index fa021179..e5ced053 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -1,12 +1,50 @@ Getting Started *************** +Uplink + Making a Request ================ +An API declaration always begins with an HTTP method decorator. Uplink +currently supports :py:class:`~uplink.get`, :py:class:`~uplink.post`, +:py:class:`~uplink.put`, :py:class:`~uplink.patch`, and +:py:class:`~uplink.delete`. Moreover, an API consumer is a plain-old +Python class that contains one or more API declarations. + +For example, let's define an GitHub API consumer that can retrieve all the +public repositories hosted on the site: + +.. code-block:: python + + import uplink + + class GitHub(object): + @uplink.get("/repositories") + def get_repos(self): pass + +The :py:class:`get` decorator indicates that the :py:meth:`get_repos` method +handles an HTTP ``GET`` request targeting the :code:`/repositories` endpoint. +Now, to fetch the list of public repositories hosted on :code:`github.com`, we +create a consumer instance and invoke the method: + +.. code-block:: python + + github = uplink.build(GitHub, base_url="https://api.github.com") + print(github.get_repos().execute()) + +.. note:: + + To get the public repos hosted on a a GitHub Enterprise instance, we + could simply set the :code:`base_url` parameter appropriately. This + a simple example of how Uplink helps you build reusable consumers. + + Setting the URL =============== + + Path Variables ============== @@ -27,7 +65,7 @@ for us: .. code-block:: python - class GitHubService(object): + class GitHub(object): @uplink.get("user/{username}") def get_user(self, username): pass diff --git a/docs/source/index.rst b/docs/source/index.rst index 2f034083..d54eb161 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -35,7 +35,7 @@ A Quick Walkthrough, with GitHub API v3: ---------------------------------------- Using decorators and function annotations, you can turn any plain old Python -class into an HTTP API consumer: +class into a self-describing consumer of your favorite HTTP webservice: .. code-block:: python @@ -44,7 +44,7 @@ class into an HTTP API consumer: # To register entities that are common to all API requests, you can # decorate the enclosing class rather than each method separately: @headers({"Accept": "application/vnd.github.v3.full+json"}) - class GitHubService(object): + class GitHub(object): @get("/users/{username}") def get_user(self, username): @@ -59,7 +59,7 @@ To construct a consumer instance, use the helper function :py:func:`uplink.build .. code-block:: python - github = build(GitHubService, base_url="https://api.github.com/") + github = build(GitHub, base_url="https://api.github.com/") To access the GitHub API with this instance, we simply invoke any of the methods that we defined in the interface above. To illustrate, let's update my GitHub @@ -80,8 +80,8 @@ the returned response :py:obj:`r` is simply a :py:class:`requests.Response` print(r.json()) # {u'disk_usage': 216141, u'private_gists': 0, ... -In essence, **Uplink** delivers API consumers that are self-describing, -reusable, and fairly compact, with minimal user pain ☺️. +In essence, Uplink delivers reusable and self-sufficient objects for +accessing HTTP webservices, with minimal code and user pain ☺️. The User Manual --------------- diff --git a/docs/source/install.rst b/docs/source/install.rst index 6d54cf4f..4e784ce3 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -1,12 +1,10 @@ Installation ============ -Getting started with Uplink begins with installation. +Using :program:`pip` +-------------------- -Using ``pip`` -------------- - -With ``pip`` or ``pipenv`` installed, you can install Uplink simply by +With :program:`pip` or :program:`pipenv` installed, you can install Uplink simply by typing: :: @@ -20,13 +18,14 @@ Download the Source Code Uplink's source code is in a `public repository hosted on GitHub `__. -As an alternative to installing with ``pip``, you could clone the repository +As an alternative to installing with :program:`pip`, you could clone the +repository :: $ git clone https://github.com/prkumar/uplink.git -Then, install; e.g., with ``setup.py``: +Then, install; e.g., with :file:`setup.py`: :: diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 272662fc..4e8219a9 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -1,8 +1,15 @@ Introduction ************ -**Method Annotations**: Static Request Properties -================================================= +Uplink delivers reusable and self-sufficient objects for accessing +HTTP webservices, with minimal code and user pain. + +Defining similar objects with other Python HTTP clients, such as +:code:`requests`, often requires writing boilerplate code and layers of +abstraction. Uplink handles those tedious parts for you. + +**Method Annotations**: Static Request Handling +=============================================== Essentially, method annotations describe Request properties that are relevant to all invocations of a consumer method. @@ -12,21 +19,33 @@ For instance, consider the following GitHub API consumer: .. code-block:: python :emphasize-lines: 2 - class GitHubService(object): + class GitHub(object): @uplink.timeout(60) @uplink.get("/repositories") def get_repos(self): """Dump every public repository.""" - Annotated with :py:class:`timeout`, the method :py:meth:`get_repos` will build HTTP requests that wait an allotted number of seconds -- 60, in this case -- for the server to respond before giving up. + +Applying Multiple Method Annotations +------------------------------------ + As method annotations are simply decorators, you can stack one on top of another -to chain them. +for chaining: + +.. code-block:: python + :emphasize-lines: 2,3 + class GitHub(object): + @uplink.headers({"Accept": "application/vnd.github.v3.full+json"}) + @uplink.timeout(60) + @uplink.get("/repositories") + def get_repos(self): + """Dump every public repository.""" A Shortcut for Annotating All Methods in a Class ------------------------------------------------ @@ -38,7 +57,7 @@ annotate the class rather than each method individually: :emphasize-lines: 1,2 @uplink.timeout(60) - class GitHubService(object): + class GitHub(object): @uplink.get("/repositories") def get_repos(self): """Dump every public repository.""" @@ -48,12 +67,12 @@ annotate the class rather than each method individually: """List all organizations.""" -Let's drive this point home; the consumer defined above is equivalent to -the following, slightly more verbose one: +Hence, the consumer defined above is equivalent to the following, +slightly more verbose one: .. code-block:: python - class GitHubService(object): + class GitHub(object): @uplink.timeout(60) @uplink.get("/repositories") def get_repos(self): @@ -65,9 +84,8 @@ the following, slightly more verbose one: """List all organizations.""" - -**Arguments Annotations**: Dynamic Request Properties -===================================================== +**Arguments Annotations**: Dynamic Request Handling +=================================================== In programming, parametrization drives a function's dynamic behavior; a function's output depends normally on its inputs. With @@ -81,7 +99,7 @@ placeholder replacement using the :py:class:`~uplink.Path` annotation: .. code-block:: python - class GitHubService(object): + class GitHub(object): @uplink.get("users/{username}") def get_user(self, username: uplink.Path("username")): pass @@ -94,9 +112,11 @@ Invoking this method on a consumer instance, like so: Builds an HTTP request that has a URL ending with ``users/prkumar``. .. note:: - As you probably took away from the above example, :py:mod:`uplink` - ignores the instance reference argument (e.g., :py:attr:`self`), with - respect to argument annotations. + + As you probably took away from the above example: when parsing the + method's signature for argument annotations, :py:mod:`uplink` skips + the instance reference argument, which is the leading method + parameter and usually named :py:attr:`self`. Adopting the Argument's Name ---------------------------- @@ -112,7 +132,7 @@ name, :py:attr:`username`, matches the intended URI path parameter: .. code-block:: python - class GitHubService(object): + class GitHub(object): @uplink.get("users/{username}") def get_user(self, username: uplink.Path): pass @@ -137,7 +157,7 @@ annotation corresponds to :py:attr:`commits_url`, and .. code-block:: python :emphasize-lines: 2,3 - class GitHubService(object): + class GitHub(object): @uplink.Path @uplink.Url @uplink.get @@ -153,7 +173,7 @@ their corresponding function arguments (again, ignore :py:attr:`self`): .. code-block:: python :emphasize-lines: 2 - class GitHubService(object): + class GitHub(object): @uplink.args(uplink.Url, uplink.Path) @uplink.get def get_commit(self, commits_url, sha): pass @@ -168,7 +188,16 @@ annotations (:pep:`3107`): .. code-block:: python :emphasize-lines: 3 - class GitHubService(object): + class GitHub(object): @uplink.get def get_commit(self, commit_url: uplink.Url, sha: uplink.Path): - pass \ No newline at end of file + pass + +Integration with :code:`python-requests` +======================================== + +Experienced users of `Kenneth Reitz's `__ +well-established `Requests library `__ might be happy to read that Uplink uses +:code:`requests` behind-the-scenes and bubbles :code:`requests.Response` +instances back up to the user. From d5c9a1ba417b943b8157f4159b1168ceb1a2a664 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Tue, 17 Oct 2017 18:11:03 -0700 Subject: [PATCH 13/18] Fix links in CHANGELOG.rst --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 92981147..018a7e0b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,5 +23,5 @@ Added ``pip``, and link to online documentation. -.. _`Keep a Changelog`: -.. _`Semantic Versioning`: \ No newline at end of file +.. _`Keep a Changelog`: http://keepachangelog.com/en/1.0.0/ +.. _`Semantic Versioning`: https://packaging.python.org/tutorials/distributing-packages/#semantic-versioning-preferred \ No newline at end of file From a61aba1efca45a3b33595f906b6352cfc91ec646 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Tue, 17 Oct 2017 18:13:02 -0700 Subject: [PATCH 14/18] Clean up link for Retrofit in CHANGELOG.rst --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 018a7e0b..28ce08e7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,8 +11,7 @@ The format is based on `Keep a Changelog`_, and this project adheres to the Added ----- -- Python ports for almost all method and argument annotations in `Retrofit - `__. +- Python ports for almost all method and argument annotations in Retrofit_. - Adherence to the variation of the semantic versioning scheme outlined in the official Python package distribution tutorial. - MIT License @@ -23,5 +22,6 @@ Added ``pip``, and link to online documentation. +.. _Retrofit: http://square.github.io/retrofit/ .. _`Keep a Changelog`: http://keepachangelog.com/en/1.0.0/ .. _`Semantic Versioning`: https://packaging.python.org/tutorials/distributing-packages/#semantic-versioning-preferred \ No newline at end of file From c514eb16acfbd1138ce42d9f104cd5783e1739f4 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Wed, 18 Oct 2017 08:29:43 -0700 Subject: [PATCH 15/18] Update documentation --- docs/source/index.rst | 22 +++++++++++----------- docs/source/install.rst | 2 +- docs/source/introduction.rst | 7 +------ docs/source/types.rst | 1 - 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index d54eb161..973970a6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -95,21 +95,21 @@ Follow this guide to get up and running with Uplink. introduction.rst getting_started.rst -The Public API --------------- +.. + The Public API + -------------- -.. todo:: + .. todo:: - Most of this guide is unfinished and completing it is a planned - deliverable for the ``v0.2.0`` release. At the least, this work will - necessitate adding docstrings to the classes enumerated below. + Most of this guide is unfinished and completing it is a planned + deliverable for the ``v0.2.0`` release. At the least, this work will + necessitate adding docstrings to the classes enumerated below. + .. toctree:: + :maxdepth: 3 -.. toctree:: - :maxdepth: 3 - - decorators.rst - types.rst + decorators.rst + types.rst .. |Coverage Status| image:: https://coveralls.io/repos/github/prkumar/uplink/badge.svg?branch=master diff --git a/docs/source/install.rst b/docs/source/install.rst index 4e784ce3..0a2f4277 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -4,7 +4,7 @@ Installation Using :program:`pip` -------------------- -With :program:`pip` or :program:`pipenv` installed, you can install Uplink simply by +With :program:`pip` (or :program:`pipenv`), you can install Uplink simply by typing: :: diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 4e8219a9..7b664104 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -6,7 +6,7 @@ HTTP webservices, with minimal code and user pain. Defining similar objects with other Python HTTP clients, such as :code:`requests`, often requires writing boilerplate code and layers of -abstraction. Uplink handles those tedious parts for you. +abstraction. Uplink handles those cumbersome parts for you. **Method Annotations**: Static Request Handling =============================================== @@ -25,12 +25,10 @@ For instance, consider the following GitHub API consumer: def get_repos(self): """Dump every public repository.""" - Annotated with :py:class:`timeout`, the method :py:meth:`get_repos` will build HTTP requests that wait an allotted number of seconds -- 60, in this case -- for the server to respond before giving up. - Applying Multiple Method Annotations ------------------------------------ @@ -66,7 +64,6 @@ annotate the class rather than each method individually: def get_organizations(self): """List all organizations.""" - Hence, the consumer defined above is equivalent to the following, slightly more verbose one: @@ -83,7 +80,6 @@ slightly more verbose one: def get_organizations(self): """List all organizations.""" - **Arguments Annotations**: Dynamic Request Handling =================================================== @@ -178,7 +174,6 @@ their corresponding function arguments (again, ignore :py:attr:`self`): @uplink.get def get_commit(self, commits_url, sha): pass - Function Annotations (Python 3 only) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/source/types.rst b/docs/source/types.rst index 6e6423ac..ede98037 100644 --- a/docs/source/types.rst +++ b/docs/source/types.rst @@ -1,7 +1,6 @@ Argument Annotations ******************** - Replace URI ``Path`` Variables ============================== From 7dece43686387985ecfa7526941ab063a2252738 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Thu, 19 Oct 2017 08:09:39 -0700 Subject: [PATCH 16/18] Update documentation --- CHANGELOG.rst | 2 +- docs/source/getting_started.rst | 79 +++++++++++++++++++++++++++------ 2 files changed, 66 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 28ce08e7..4a9dd35f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,7 @@ 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. -0.1.0 - 2017-10-20 +0.1.0 - 2017-10-21 ================== Added diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index e5ced053..bf26f1b5 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -6,11 +6,7 @@ Uplink Making a Request ================ -An API declaration always begins with an HTTP method decorator. Uplink -currently supports :py:class:`~uplink.get`, :py:class:`~uplink.post`, -:py:class:`~uplink.put`, :py:class:`~uplink.patch`, and -:py:class:`~uplink.delete`. Moreover, an API consumer is a plain-old -Python class that contains one or more API declarations. +The simplest API consumer method requires only an HTTP method decorator. For example, let's define an GitHub API consumer that can retrieve all the public repositories hosted on the site: @@ -23,31 +19,86 @@ public repositories hosted on the site: @uplink.get("/repositories") def get_repos(self): pass -The :py:class:`get` decorator indicates that the :py:meth:`get_repos` method -handles an HTTP ``GET`` request targeting the :code:`/repositories` endpoint. -Now, to fetch the list of public repositories hosted on :code:`github.com`, we -create a consumer instance and invoke the method: +Now, to fetch the list of public repositories hosted on :code:`github.com`, +we simply invoke the :py:meth:`get_repos` with a consumer instance: .. code-block:: python github = uplink.build(GitHub, base_url="https://api.github.com") - print(github.get_repos().execute()) + response = github.get_repos().execute() + print(response.json()) # [{u'issues_url': u'https://api.github.com/repos/mojombo/grit/issues{/number}', ... -.. note:: +To summarize, we used the :py:class:`get` decorator to indicate that the +:py:meth:`get_repos` method handles an HTTP ``GET`` request targeting the +:code:`/repositories` endpoint. - To get the public repos hosted on a a GitHub Enterprise instance, we - could simply set the :code:`base_url` parameter appropriately. This - a simple example of how Uplink helps you build reusable consumers. +Further, Uplink currently supports :py:class:`~uplink.get`, +:py:class:`~uplink.post`, :py:class:`~uplink.put`, :py:class:`~uplink.patch`, +and :py:class:`~uplink.delete`. +Creating Consumer Instances with :code:`uplink.build` +----------------------------------------------------- + +To create consumer instances, use the :py:func:`uplink.build` function. +Notably, this helper function affords us the ability to reuse consumers in +different contexts. + +For instance, by simply changing the function's :py:attr:`base_url` +parameter, we could use the consumer defined in the previous example, +:py:attr:`GitHub`, also against any GitHub Enterprise instance, since these +services offer an API identical to that of the main website. Setting the URL =============== +To set a **static URL**, simply use the the first parameter, :py:attr:`path`, +of the HTTP method decorator: + +.. code-block:: python + :emphasize-lines: 2 + + class GitHub(object): + @uplink.get("/repositories") + def get_repos(self): pass + +Alternatively, you can allow users to pass the URL at runtime as a +method argument. To set a **dynamic URLs**, omit the decorator parameter +:code:`path` and annotate the corresponding method argument with +:py:class:`uplink.Url`: + +.. code-block:: python + :emphasize-lines: 3 + class GitHub(object); + @uplink.get + def get_commit(self, commit_url: uplink.Url): pass Path Variables ============== +For both static and dynamic URLs, Uplink supports `URI +templates `__. These +templates can contain parameters enclosed in braces (e.g., :code:`{name}`) +for method arguments to handle. + +To map a method argument to a declared URI path parameter for expansion, use +the :py:class:`uplink.Path` annotation: + +.. code-block:: python + + class GitHub(object): + @get("users/{username}") + def get_user(self, username: Path("username")): pass + +With an instance of the above defined consumer, we can invoke the +:code:`get_user` method like so + +.. code-block:: python + + github.get_user("prkumar") + +to create an HTTP request with a URL ending in :code:`users/prkumar`. + .. _implicit_path_annotations: From 873b5253b0a2e0f7f85a4ad5217982d89885f0bb Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Thu, 19 Oct 2017 19:16:37 -0700 Subject: [PATCH 17/18] Update documentation --- docs/source/getting_started.rst | 199 ++++++++++++++++++++++++++++---- docs/source/index.rst | 2 +- docs/source/introduction.rst | 6 +- 3 files changed, 184 insertions(+), 23 deletions(-) diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index bf26f1b5..3569c1f9 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -1,7 +1,10 @@ Getting Started *************** -Uplink +In this section, we'll cover the basics with Uplink. To illustrate usage +with an existent service, we mainly provide examples with GitHub's API. +To try them out yourself, you can simply copy the code snippets into a +script or the Python console. Making a Request ================ @@ -39,19 +42,19 @@ and :py:class:`~uplink.delete`. Creating Consumer Instances with :code:`uplink.build` ----------------------------------------------------- -To create consumer instances, use the :py:func:`uplink.build` function. -Notably, this helper function affords us the ability to reuse consumers in -different contexts. +As illustrated in the previous example, to create consumer instances, use the +:py:func:`uplink.build` function. Notably, this helper function affords us +the ability to reuse consumers in different contexts. For instance, by simply changing the function's :py:attr:`base_url` -parameter, we could use the consumer defined in the previous example, -:py:attr:`GitHub`, also against any GitHub Enterprise instance, since these -services offer an API identical to that of the main website. +parameter, we could use the same GitHub API consumer against the main +website, ``github.com``, and any GitHub Enterprise instance, since they +offer identical APIs. Setting the URL =============== -To set a **static URL**, simply use the the first parameter, :py:attr:`path`, +To set a **static URL**, use the the leading parameter, :py:attr:`path`, of the HTTP method decorator: .. code-block:: python @@ -61,9 +64,9 @@ of the HTTP method decorator: @uplink.get("/repositories") def get_repos(self): pass -Alternatively, you can allow users to pass the URL at runtime as a -method argument. To set a **dynamic URLs**, omit the decorator parameter -:code:`path` and annotate the corresponding method argument with +Alternatively, you can provide the URL at runtime as a method argument. +To set a **dynamic URL**, omit the decorator parameter :py:attr:`path` +and annotate the corresponding method argument with :py:class:`uplink.Url`: .. code-block:: python @@ -73,25 +76,30 @@ method argument. To set a **dynamic URLs**, omit the decorator parameter @uplink.get def get_commit(self, commit_url: uplink.Url): pass +.. _path_variables: + Path Variables ============== For both static and dynamic URLs, Uplink supports `URI templates `__. These templates can contain parameters enclosed in braces (e.g., :code:`{name}`) -for method arguments to handle. +for method arguments to handle at runtime. To map a method argument to a declared URI path parameter for expansion, use -the :py:class:`uplink.Path` annotation: +the :py:class:`uplink.Path` annotation. For instance, we can define a consumer +method to query any GitHub user's metadata by declaring the +`path segment parameter `__ +:code:`{/username}` in the method's URL. .. code-block:: python class GitHub(object): - @get("users/{username}") + @get("users{/username}") def get_user(self, username: Path("username")): pass -With an instance of the above defined consumer, we can invoke the -:code:`get_user` method like so +With an instance of this consumer, we can invoke the :code:`get_user` +method like so .. code-block:: python @@ -101,7 +109,6 @@ to create an HTTP request with a URL ending in :code:`users/prkumar`. .. _implicit_path_annotations: - Implicit :code:`Path` Annotations ---------------------------------- @@ -117,7 +124,7 @@ for us: .. code-block:: python class GitHub(object): - @uplink.get("user/{username}") + @uplink.get("users{/username}") def get_user(self, username): pass Important to note, failure to resolve all unannotated function arguments @@ -126,21 +133,173 @@ raises an :py:class:`~uplink.InvalidRequestDefinitionError`. Query Parameters ================ +To set unchanging query parameters, you can append a query string to the +static URL. For instance, GitHub offers the query parameter :code:`q` +for adding keywords to a search. With this, we can define a consumer +that queries all GitHub repositories written in Python: + +.. code-block:: python + :emphasize-lines: 2 + + class GitHub(object): + @uplink.get("/search/repositories?q=language:python") + def search_python_repos(self): pass + +Note that we have hard-coded the query parameter into the URL, so that all +requests that this method handles include that search term. + +Alternatively, we can set query parameters at runtime using method +arguments. To set dynamic query parameters, use the :py:class:`uplink.Query` and +:py:class:`uplink.QueryMap` argument annotations. + +For instance, to set the search term :code:`q` at runtime, we can +provide a method argument annotated with :py:class:`uplink.Query`: + +.. code-block:: python + :emphasize-lines: 3 + + class GitHub(object): + @uplink.get("/search/repositories") + def search_repos(self, q: uplink.Query) + +Further, the :py:class:`uplink.QueryMap` annotation indicates that an +argument handles a mapping of query parameters. For example, let's use this +annotation to transform keyword arguments into query parameters: + +.. code-block:: python + :emphasize-lines: 3 + + class GitHub(object): + @uplink.get("/search/repositories") + def search_repos(self, **params: uplink.QueryMap) + +This serves as a nice alternative to adding a :py:class:`uplink.Query` +annotated argument for each supported query parameter. For instance, +we can now optionally modify how the GitHub search results are sorted, +leveraging the :code:`sort` query parameter: + +.. code-block:: python + + # Search for Python repos and sort them by number of stars. + github.search_repos(q="language:python", sort="stars").execute() + +.. note:: + + Another approach for setting dynamic query parameters is to use `path + variables`_ in the static URL, with `"form-style query expansion" + `_. + HTTP Headers ============ +To add literal headers, use the :py:class:`uplink.headers` method annotation, +which has accepts the input parameters as :py:class:`dict`: + +.. code-block:: python + :emphasize-lines: 2,3 + + class GitHub(object): + # This header explicitly requests version v3 of the GitHub API. + @uplink.headers({"Accept": "application/vnd.github.v3.full+json"}) + @uplink.get("/repositories") + def get_repos(self): pass + +Alternatively, we can use the :py:class:`uplink.Header` argument annotation to +pass a header as a method argument at runtime: + +.. code-block:: python + :emphasize-lines: 6 + + class GitHub(object): + @uplink.get("/users{/username}") + def get_user( + self, + username, + last_modified: uplink.Header("If-Modified-Since") + ): + """Fetch a GitHub user if modified after given date.""" + +Further, you can annotate an argument with :py:class:`uplink.HeaderMap` to +accept a mapping of header fields. + URL-Encoded Request Body ======================== +For ``POST``/``PUT``/``PATCH`` requests, the format of the message body +is an important detail. A common approach is to url-encode the body and +set the header ``Content-Type: application/x-www-form-urlencoded`` +to notify the server. + +To submit a url-encoded form with Uplink, decorate the consumer method +with :py:class:`uplink.form_url_encoded` and annotate each argument +accepting a form field with :py:class:`uplink.Field`. For instance, +let's provide a method for reacting to a specific GitHub issue: + +.. code-block:: python + :emphasize-lines: 2,7 + + class GitHub(object): + @uplink.form_url_encoded + @uplink.patch("/user") + def update_blog_url( + self, + access_token: uplink.Query, + blog_url: uplink.Field + ): + """Update a user's blog URL.""" + +Further, you can annotate an argument with :py:class:`uplink.FieldMap` to +accept a mapping of form fields. + Send Multipart Form Data ======================== -JSON Requests, and Other Content Types -====================================== +`Multipart requests +`__ are commonly +used to upload files to a server. +To send a multipart message, decorate a consumer method with +:py:class:`uplink.multipart`. Moreover, use the :py:class:`uplink.Part` argument +annotation to mark a method argument as a form part. +.. todo:: + Add a code block that illustrates an example of how to define a + consumer method that sends multipart requests. +Further, you can annotate an argument with :py:class:`uplink.PartMap` to +accept a mapping of form fields to parts. +JSON Requests, and Other Content Types +====================================== + +Nowadays, many HTTP webservices nowadays accept JSON requests. (GitHub's +API is an example of such a service.) Given the format's growing +popularity, Uplink provides the decorator :py:class:`uplink.json`. + +When using this decorator, you should annotate a method argument with +:py:class:`uplink.Body`, which indicates that the argument's value +should become the request's body. Moreover, this value is expected to be +an instance of :py:class:`dict` or a subclass of +:py:class:`uplink.Mapping`. +Note that :py:class:`uplink.Body` can annotate the keyword argument, which +often enables concise method signatures: +.. code-block:: python + :emphasize-lines: 2,7 + + class GitHub(object): + @uplink.json + @uplink.patch("/user") + def update_user( + self, + access_token: uplink.Query, + **info: uplink.Body + ): + """Update an authenticated user.""" + + +Further, you may be able to send other content types by using +:py:class:`uplink.Body` and setting the ``Content-Type`` header +appropriately with the decorator :py:class:`uplink.header`. \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 973970a6..e2f3c7f9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,7 +5,7 @@ Uplink 📡 ========== -A Declarative HTTP Client for Python, inspired by `Retrofit +A Declarative HTTP Client for Python. Inspired by `Retrofit `__. |Release| |Python Version| |License| |Coverage Status| diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 7b664104..7aeed7ac 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -6,12 +6,14 @@ HTTP webservices, with minimal code and user pain. Defining similar objects with other Python HTTP clients, such as :code:`requests`, often requires writing boilerplate code and layers of -abstraction. Uplink handles those cumbersome parts for you. +abstraction. With Uplink, simply define your consumers using +decorators and function annotations, and we'll handle the REST for you! (Pun +intended, obviously.) 😎 **Method Annotations**: Static Request Handling =============================================== -Essentially, method annotations describe Request properties that are relevant +Essentially, method annotations describe request properties that are relevant to all invocations of a consumer method. For instance, consider the following GitHub API consumer: From 8ba5c46989be6ff9c3f2858a270554f0d82cb4e1 Mon Sep 17 00:00:00 2001 From: "P. Raj Kumar" Date: Thu, 19 Oct 2017 19:17:22 -0700 Subject: [PATCH 18/18] Change release date --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4a9dd35f..3836f9ae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,7 @@ 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. -0.1.0 - 2017-10-21 +0.1.0 - 2017-10-19 ================== Added