apiwrappers is a library for building API wrappers that work both with regular and async code
Project description
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:
First, we defined Repo dataclass that describes what we want to get from response
Next, we used Fetch descriptor to declare API method
Each Fetch object also needs a so-called request factory. We provide one by using get_repos.request decorator on the get_repos_request
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
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
Algorithm | Hash digest | |
---|---|---|
SHA256 | 812dbab35cfb9dd4db59d4040c04b4cac7c6481edc4672e7b486c89cd006f246 |
|
MD5 | 80170c194e10d8197c74be4e804a13fc |
|
BLAKE2b-256 | 55c8ca52e29c05e26ba87ba934e319f92c7da695ede8af6d5d38d869172f5214 |
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
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1b7c234eb420d5069bddb9bd544304d0d2e0103cd0c3980a4da1c663665884da |
|
MD5 | 9e2e53e05e628cd20131769f7542a57b |
|
BLAKE2b-256 | a3ea0f0953ebc04a5537103822992f6da70e9581832bb1c9e65c682772738202 |