Skip to content

Commit

Permalink
Merge pull request #206 from prkumar/master
Browse files Browse the repository at this point in the history
Release v0.9.2
  • Loading branch information
prkumar authored Oct 17, 2020
2 parents be2b3ac + b19b146 commit 7c3e030
Show file tree
Hide file tree
Showing 21 changed files with 438 additions and 58 deletions.
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
language: python
python:
- '2.7'
- '3.4'
- '3.5'
- '3.6'
- '3.7'
Expand All @@ -11,7 +9,7 @@ before_script:
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then pip install flake8 flake8-bugbear; fi
script:
- tox -e py
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then flake8 uplink tests setup.py docs/conf.py; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3.6 ]]; then flake8 uplink tests setup.py docs/source/conf.py; fi
after_success:
- pip install codecov
- codecov
Expand Down
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ Contributors
- Alexander Duryagin (`@daa <https://github.com/daa>`_)
- Sakorn Waungwiwatsin (`@SakornW <https://github.com/SakornW>`_)
- Jacob Floyd (`@cognifloyd <https://github.com/cognifloyd>`_)
- Guilherme Crocetti (`@gmcrocetti <https://github.com/gmcrocetti/>`_)
24 changes: 23 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ 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.9.2_ - 2020-10-18
====================
Added
-----
- Support for (de)serializing subclasses of `pydantic`_'s `BaseModel`
(`#200`_ by `@gmcrocetti`_)

Fixed
-----
- Using the ``@get``, ``@post``, ``@patch``, etc. decorators should retain the
docstring of the wrapped method (`#198`_)
- The ``Body`` and ``Part`` argument annotations should support uploading binary
data (`#180`_, `#183`_, `#204`_)

0.9.1_ - 2020-02-08
===================
Fixed
Expand Down Expand Up @@ -306,9 +320,11 @@ Added
.. _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
.. _pydantic: https://pydantic-docs.helpmanual.io/

.. Releases
.. _0.9.1: https://github.com/prkumar/uplink/compare/v0.9.1...HEAD
.. _0.9.2: https://github.com/prkumar/uplink/compare/v0.9.1...v0.9.2
.. _0.9.1: https://github.com/prkumar/uplink/compare/v0.9.0...v0.9.1
.. _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
Expand Down Expand Up @@ -345,7 +361,12 @@ Added
.. _#165: https://github.com/prkumar/uplink/pull/165
.. _#167: https://github.com/prkumar/uplink/issues/167
.. _#169: https://github.com/prkumar/uplink/pull/169
.. _#180: https://github.com/prkumar/uplink/pull/180
.. _#183: https://github.com/prkumar/uplink/pull/183
.. _#188: https://github.com/prkumar/uplink/pull/188
.. _#198: https://github.com/prkumar/uplink/pull/198
.. _#200: https://github.com/prkumar/uplink/pull/200
.. _#204: https://github.com/prkumar/uplink/pull/204

.. Contributors
.. _@daa: https://github.com/daa
Expand All @@ -354,3 +375,4 @@ Added
.. _@itstehkman: https://github.com/itstehkman
.. _@kadrach: https://github.com/kadrach
.. _@cognifloyd: https://github.com/cognifloyd
.. _@gmcrocetti: https://github.com/gmcrocetti
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ Sphinx = {version = "*", markers = "python_version != '3.3'"}
sphinx-autobuild = {version = "*", markers = "python_version != '3.3'"}

[packages]
"e1839a8" = {path = ".", extras = ["tests", "aiohttp", "marshmallow", "twisted", "typing"], editable = true}
"e1839a8" = {editable = true, extras = ["aiohttp", "marshmallow", "pydantic", "tests", "twisted", "typing"], path = "."}
7 changes: 4 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Features

- Define `custom converters`_ for your own objects
- Support for |marshmallow|_ schemas and `handling collections`_ (e.g., list of Users)
- Support for pydantic models and :ref:`handling collections <converting_collections>` (e.g., list of Repos)

- **Extendable**

Expand Down Expand Up @@ -114,7 +115,7 @@ If you are interested in the cutting-edge, preview the upcoming release with:
Extra! Extra!
-------------

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

When installing Uplink with ``pip``, you can select extras using the format:
Expand Down Expand Up @@ -188,8 +189,8 @@ Thank you for taking the time to improve an open source project :purple_heart:
.. |Code Style| image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black
:alt: Code style: black
.. |Coverage Status| image:: https://img.shields.io/codecov/c/github/prkumar/uplink.svg
:alt: Codecov
.. |Coverage Status| image:: https://img.shields.io/codecov/c/github/prkumar/uplink.svg
:alt: Codecov
:target: https://codecov.io/gh/prkumar/uplink
.. |Documentation Status| image:: https://readthedocs.org/projects/uplink/badge/?version=latest
:target: http://uplink.readthedocs.io/en/latest/?badge=latest
Expand Down
5 changes: 2 additions & 3 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,8 @@

# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'index': ['about.html', 'links.html', 'navigation.html', 'searchbox.html'],
'**': ["about.html", 'localtoc.html', 'relations.html',
'searchbox.html'],
'index': ['about.html', 'links.html', 'navigation.html', 'searchbox.html'],
'**': ["about.html", 'localtoc.html', 'relations.html', 'searchbox.html'],
'changes': ['about.html', 'searchbox.html']
}

Expand Down
15 changes: 15 additions & 0 deletions docs/source/dev/converters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ Uplink comes with optional support for :py:mod:`marshmallow`.
included if you have :py:mod:`marshmallow` installed, so you don't need
to provide it when constructing your consumer instances.

Pydantic
===========

.. versionadded:: v0.9.2

Uplink comes with optional support for :py:mod:`pydantic`.

.. autoclass:: uplink.converters.PydanticConverter

.. note::

Starting with version v0.9.2, this converter factory is automatically
included if you have :py:mod:`pydantic` installed, so you don't need
to provide it when constructing your consumer instances.

.. _`converting lists and mappings`:

Converting Collections
Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Features

- Define :ref:`custom converters <custom_json_deserialization>` for your own objects
- Support for |marshmallow|_ schemas and :ref:`handling collections <converting_collections>` (e.g., list of Users)
- Support for pydantic models and :ref:`handling collections <converting_collections>` (e.g., list of Repos)

- **Extendable**

Expand Down
5 changes: 4 additions & 1 deletion docs/source/user/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Extra Description
for `converting JSON responses directly into Python objects
<https://github.com/prkumar/uplink/tree/master/examples/marshmallow>`_
using :py:class:`marshmallow.Schema`.
``pydantic`` Enables :py:class:`uplink.PydanticConverter`,
for converting JSON responses directly into Python objects
using :py:class:`pydantic.BaseModel`.
``twisted`` Enables :py:class:`uplink.TwistedClient`,
for `sending non-blocking requests <https://github.com/prkumar/uplink/tree/master/examples/async-requests>`_ and receiving
:py:class:`~twisted.internet.defer.Deferred` responses.
Expand All @@ -62,5 +65,5 @@ To download all available features, run

::

$ pip install -U uplink[aiohttp, marshmallow, twisted]
$ pip install -U uplink[aiohttp, marshmallow, pydantic, twisted]

88 changes: 87 additions & 1 deletion docs/source/user/serialization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ dealing with the underlying protocol.

This document walks you through how to leverage Uplink's serialization support,
including integrations for third-party serialization libraries like
:mod:`marshmallow` and tools for writing custom conversion strategies that
:mod:`marshmallow`, :mod:`pydantic` and tools for writing custom conversion strategies that
fit your unique needs.

.. _using_marshmallow_schemas:
Expand Down Expand Up @@ -79,6 +79,71 @@ schema:
For a more complete example of Uplink's :mod:`marshmallow` support,
check out `this example on GitHub <https://github.com/prkumar/uplink/tree/master/examples/marshmallow>`_.

.. _using_pydantic_schemas:

Using Pydantic Models
=========================

:mod:`pydantic` is a framework-agnostic, object serialization library
for Python >= 3.6. Uplink comes with built-in support for Pydantic; you can
integrate your Pydantic models with Uplink for easy JSON (de)serialization.

First, create a :class:`pydantic.BaseModel`, declaring any necessary
conversions and validations. Here's a simple example:

.. code-block:: python
from typing import List
from pydantic import BaseModel, HttpUrl
class Owner(BaseModel):
id: int
avatar_url: HttpUrl
organizations_url: HttpUrl
class Repo(BaseModel):
id: int
full_name: str
owner: Owner
Then, specify the schema using the :class:`@returns <uplink.returns>` decorator:

.. code-block:: python
:emphasize-lines: 2
class GitHub(Consumer):
@returns.json(List[Repo])
@get("users/{username}/repos")
def get_repos(self, username):
"""Get the user's public repositories."""
Python 3 users can use a return type hint instead:

.. code-block:: python
:emphasize-lines: 3
class GitHub(Consumer):
@returns.json()
@get("users/{username}/repos")
def get_repos(self, username) -> List[Repo]:
"""Get the user's public repositories."""
Your consumer should now return Python objects based on your Pydantic
model:

.. code-block:: python
github = GitHub(base_url="https://api.github.com")
print(github.get_repos("octocat"))
# Output: [User(id=132935648, full_name='octocat/boysenberry-repo-1', owner=Owner(...), ...]
.. note::

You may have noticed the usage of `returns.json` in both examples. Unlike :mod:`marshmallow`, :mod:`pydantic`
has no `many` parameter to control the deserialization of multiple objects. The recommended approach
is to use `returns.json` instead of defining a new model with a `__root__` element.

Serializing Method Arguments
============================

Expand Down Expand Up @@ -111,6 +176,27 @@ Uplink's :mod:`marshmallow` integration (see
repo = Repo(name="my_favorite_new_project")
github.create_repo(repo)
The sample code above using :mod:`marshmallow` is also reproducible using :mod:`pydantic`:

.. code-block:: python
from uplink import Consumer, Body
class CreateRepo(BaseModel):
name: str
delete_branch_on_merge: bool
class GitHub(Consumer):
@post("user/repos")
def create_repo(self, repo: Body(type=CreateRepo)):
"""Creates a new repository for the authenticated user."""
Then, calling the client.

.. code-block:: python
repo = CreateRepo(name="my-new-uplink-pydantic", delete_branch_on_merge=True)
github.create_repo(repo)
.. _custom_json_deserialization:

Custom JSON Conversion
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def read(filename):

extras_require = {
"marshmallow": ["marshmallow>=2.15.0"],
"pydantic:python_version >= '3.6'": ["pydantic>=1.6.1"],
"aiohttp:python_version <= '3.4'": [],
"aiohttp:python_version >= '3.4'": "aiohttp>=2.3.0",
"twisted:python_version != '3.3' and python_version != '3.4'": "twisted>=17.1.0",
Expand Down
17 changes: 16 additions & 1 deletion tests/integration/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class GitHubService(uplink.Consumer):
@uplink.timeout(15)
@uplink.get("/users/{user}/repos")
def list_repos(self, user):
pass
"""List all public repositories for a specific user."""

@uplink.returns.json
@uplink.get("/users/{user}/repos/{repo}")
Expand All @@ -26,6 +26,21 @@ def forward(self, url):
pass


def test_list_repo_wrapper(mock_client):
"""Ensures that the consumer method looks like the original func."""
github = GitHubService(base_url=BASE_URL, client=mock_client)
assert (
github.list_repos.__doc__
== GitHubService.list_repos.__doc__
== "List all public repositories for a specific user."
)
assert (
github.list_repos.__name__
== GitHubService.list_repos.__name__
== "list_repos"
)


def test_list_repo(mock_client):
github = GitHubService(base_url=BASE_URL, client=mock_client)
github.list_repos("prkumar")
Expand Down
5 changes: 4 additions & 1 deletion tests/unit/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ def func():
http_method = commands.HttpMethod("METHOD", uri="/{hello}")
builder = http_method(func)
assert isinstance(builder, commands.RequestDefinitionBuilder)
assert builder.__name__ == func.__name__
assert builder.method == "METHOD"
assert list(builder.uri.remaining_variables) == ["hello"]

Expand Down Expand Up @@ -131,6 +130,7 @@ def test_method_handler_builder_getter(
self, annotation_handler_builder_mock
):
builder = commands.RequestDefinitionBuilder(
None,
None,
None,
type(annotation_handler_builder_mock)(),
Expand All @@ -143,6 +143,7 @@ def test_build(self, mocker, annotation_handler_builder_mock):
method_handler_builder = annotation_handler_builder_mock
uri_definition_builder = mocker.Mock(spec=commands.URIDefinitionBuilder)
builder = commands.RequestDefinitionBuilder(
None,
"method",
uri_definition_builder,
argument_handler_builder,
Expand All @@ -164,6 +165,7 @@ def test_auto_fill_when_not_done(
method_handler_builder = annotation_handler_builder_mock
uri_definition_builder = mocker.Mock(spec=commands.URIDefinitionBuilder)
builder = commands.RequestDefinitionBuilder(
None,
"method",
uri_definition_builder,
argument_handler_builder,
Expand All @@ -189,6 +191,7 @@ def test_auto_fill_when_not_done_fails(
method_handler_builder = annotation_handler_builder_mock
uri_definition_builder = mocker.Mock(spec=commands.URIDefinitionBuilder)
builder = commands.RequestDefinitionBuilder(
None,
"method",
uri_definition_builder,
argument_handler_builder,
Expand Down
Loading

0 comments on commit 7c3e030

Please sign in to comment.