Skip to main content

apiwrappers is a library for building API wrappers that work both with regular and async code

Project description

Build Status Coverage Status Checked with mypy PyPI Package latest release Supported versions MIT License

apiwrappers is a library for building API wrappers that work both with regular and async code.

Features

  • Fast to code - bootstrap API wrappers with minimal efforts and declarative style

  • No code duplication - support both sync and async implementations with one wrapper

  • Unified interface - work with different python HTTP client libraries in the same way. Currently it supports:

  • Customizable - middleware mechanism to customize request/response

  • Typed - library is fully typed and it is relatively easy to get fully type annotated wrapper

Installation

pip install apiwrappers[requests,aiohttp]

Note: extras are optional and mainly needed for the final user of your future API wrapper

Quickstart

Each wrapper needs a HTTP client to make request to the API.

apiwrappers provides common interface (drivers) for the most popular HTTP clients. Let’s learn how to make a simple request:

>>> from apiwrappers import Method, Request, make_driver
>>> request = Request(Method.GET, "https://example.org", "/")
>>> driver = make_driver("requests")
>>> response = driver.fetch(request)
>>> response.status_code
200
>>> response.headers["content-type"]
'text/html; charset=UTF-8'
>>> response.text()
'<!doctype html>\n<html>\n<head>\n<title>Example Domain</title>...'

Or using asynchronous driver:

Use IPython or Python 3.8+ with python -m asyncio to try this code interactively

>>> driver = make_driver("aiohttp")
>>> response = await driver.fetch(request)
>>> response.status_code
200

Writing a simple API wrapper

Now, that we learned how to make HTTP requests, let’s build our first API wrapper:

from typing import Awaitable, Generic, List, Mapping, TypeVar, overload

from apiwrappers import AsyncDriver, Driver, Method, Request, Response, make_driver

T = TypeVar("T", Driver, AsyncDriver)


class Github(Generic[T]):
    def __init__(self, host: str, driver: T):
        self.host = host
        self.driver: T = driver

    @overload
    def get_repos(self: "Github[Driver]", username: str) -> Response:
        ...

    @overload
    def get_repos(self: "Github[AsyncDriver]", username: str) -> Awaitable[Response]:
        ...

    def get_repos(self, username: str):
        request = Request(Method.GET, self.host, f"/users/{username}/repos")
        return self.driver.fetch(request)

Here we defined one method of the api.github.com to get all user repos by username.

However wrapper has some flaws:

  • get_repos method returns Response object, but it would be nice to know what data we expect from response, and not deal with a json

  • we had to use overload twice to set correct response type based on driver type

  • it’s hard to test, because get_repos method has side-effect and we need either mock self.driver.fetch call or use third party libraries such as responses, aioresponses, etc…

Let’s improve our wrapper:

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Generic, List, Mapping, TypeVar

from apiwrappers import AsyncDriver, Driver, Fetch, Method, Request, make_driver

T = TypeVar("T", Driver, AsyncDriver)


@dataclass
class Repo:
    id: int
    name: str

    @classmethod
    def from_dict(cls, item: Mapping[str, Any]) -> Repo:
        return cls(id=item["id"], name=item["name"])

    @classmethod
    def from_list(cls, items: List[Mapping[str, Any]]) -> List[Repo]:
        return [cls.from_dict(item) for item in items]


class Github(Generic[T]):
    get_repos = Fetch(Repo.from_list)

    def __init__(self, host: str, driver: T):
        self.host = host
        self.driver: T = driver

    @get_repos.request
    def get_repos_request(self, username: str) -> Request:
        return Request(Method.GET, self.host, f"/users/{username}/repos")

Here we did the following:

  1. First, we defined Repo dataclass that describes what we want to get from response

  2. Next, we used Fetch descriptor to declare API method

  3. Each Fetch object also needs a so-called request factory. We provide one by using get_repos.request decorator on the get_repos_request

  4. get_repos_request is a pure function and easy to test

Now, our API wrapper is ready for use:

>>> driver = make_driver("requests")
>>> github = Github("https://api.github.com", driver=driver)
>>> github.get_repos("unmade")
[Repo(id=47463599, name='am-date-picker'),
 Repo(id=231653904, name='apiwrappers'),
 Repo(id=144204778, name='conway'),
 ...
]

To use it with asyncio all we need to do is provide a proper driver and don’t forget to await method call:

>>> driver = make_driver("aiohttp")
>>> github = Github("https://api.github.com", driver=driver)
>>> await github.get_repos("unmade")
[Repo(id=47463599, name='am-date-picker'),
 Repo(id=231653904, name='apiwrappers'),
 Repo(id=144204778, name='conway'),
 ...
]

In the example above only return type will be annotated and checked by mypy. Method arguments will not be checked by mypy, since it has some limitations on defining generic callable args. If you want to have fully type annotated wrapper, then you still have to use overload decorator.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

apiwrappers-0.1.0a0.tar.gz (11.9 kB view details)

Uploaded Source

Built Distribution

apiwrappers-0.1.0a0-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

File details

Details for the file apiwrappers-0.1.0a0.tar.gz.

File metadata

  • Download URL: apiwrappers-0.1.0a0.tar.gz
  • Upload date:
  • Size: 11.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.0.2 CPython/3.8.0 Linux/5.0.0-1027-azure

File hashes

Hashes for apiwrappers-0.1.0a0.tar.gz
Algorithm Hash digest
SHA256 812dbab35cfb9dd4db59d4040c04b4cac7c6481edc4672e7b486c89cd006f246
MD5 80170c194e10d8197c74be4e804a13fc
BLAKE2b-256 55c8ca52e29c05e26ba87ba934e319f92c7da695ede8af6d5d38d869172f5214

See more details on using hashes here.

File details

Details for the file apiwrappers-0.1.0a0-py3-none-any.whl.

File metadata

  • Download URL: apiwrappers-0.1.0a0-py3-none-any.whl
  • Upload date:
  • Size: 13.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.0.2 CPython/3.8.0 Linux/5.0.0-1027-azure

File hashes

Hashes for apiwrappers-0.1.0a0-py3-none-any.whl
Algorithm Hash digest
SHA256 1b7c234eb420d5069bddb9bd544304d0d2e0103cd0c3980a4da1c663665884da
MD5 9e2e53e05e628cd20131769f7542a57b
BLAKE2b-256 a3ea0f0953ebc04a5537103822992f6da70e9581832bb1c9e65c682772738202

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page