A library for creating fully typed declarative API clients quickly and easily.
Project description
Quick Api Client
A library for creating fully typed declarative API clients quickly and easily.
- Github repository: https://github.com/martinn/quickapiclient/
- Documentation https://martinn.github.io/quickapiclient/
A basic example
An API definition for a simple service could look like this:
from dataclasses import dataclass
import quickapi
# An example type that will be part of the API response
@dataclass
class Fact:
fact: str
length: int
# What the API response should look like
@dataclass
class ResponseBody:
current_page: int
data: list[Fact]
# We define an API endpoint
class GetFactsApi(quickapi.BaseApi[ResponseBody]):
url = "/facts"
response_body = ResponseBody
# And now our API client
class ExampleClient(quickapi.BaseClient):
base_url = "https://example.com"
get_facts = quickapi.ApiEndpoint(GetFactsApi)
# Other endpoints would follow here:
# submit_fact = quickapi.ApiEndpoint(SubmitFactApi)
And you would use it like this:
client = ExampleClient()
response = client.get_facts()
# `response` is fully typed and conforms to our `ResponseBody` definition
assert isinstance(response.body, ResponseBody)
assert isinstance(response.body.data[0], Fact)
# `reveal_type(response.body)` returns `Revealed type is 'ResponseBody'` too,
# which means full typing and IDE support.
There's also support for attrs
, pydantic
and msgspec
for more complex modeling, validation or serialization support.
Scroll down here for examples using those.
Features
It's still early development but so far we have support for:
- Write fully typed declarative API clients quickly and easily
- Fully typed request params / body
- Fully typed response body
- Serialization/deserialization support
- Basic error and serialization handling
- Fully typed HTTP status error codes handling
- Nested/inner class definitions
- Generate API boilerplate from OpenAPI specs
- HTTP client libraries
- httpx
- requests
- aiohttp
- Authentication mechanisms
- Basic Auth
- Token / JWT
- Digest
- NetRC
- Any auth supported by
httpx
or httpx_auth orrequests
, including custom schemes
- Serialization/deserialization
- attrs
- dataclasses
- pydantic
- msgspec
- API support
- REST
- GraphQL
- Others?
- Response types supported
- JSON
- XML
- Others?
Installation
You can easily install this using pip
:
pip install quickapiclient
# Or if you want to use attrs over dataclasses:
pip install quickapiclient[attrs]
# Or if you want to use pydantic over dataclasses:
pip install quickapiclient[pydantic]
# Or if you want to use msgspec over dataclasses:
pip install quickapiclient[msgspec]
# Or if you want to use requests over httpx:
pip install quickapiclient[requests]
Or if using poetry
:
poetry add quickapiclient
# Or if you want to use attrs over dataclasses:
poetry add quickapiclient[attrs]
# Or if you want to use pydantic over dataclasses:
poetry add quickapiclient[pydantic]
# Or if you want to use msgspec over dataclasses:
poetry add quickapiclient[msgspec]
# Or if you want to use requests over httpx:
poetry add quickapiclient[requests]
More examples
A GET request with query params
An example of a GET request with query parameters with overridable default values.
Click to expand
# ...
@dataclass
class RequestParams:
max_length: int = 100
limit: int = 10
class GetFactsApi(quickapi.BaseApi[ResponseBody]):
url = "/facts"
request_params = RequestParams
response_body = ResponseBody
class ExampleClient(quickapi.BaseClient):
base_url = "https://example.com"
get_facts = quickapi.ApiEndpoint(GetFactsApi)
And to use it:
client = ExampleClient()
# Using default request param values
response = client.get_facts()
# Using custom request param values
request_params = RequestParams(max_length=5, limit=10)
response = client.get_facts(request_params=request_params)
A POST request
An example of a POST request with some optional and required data.
Click to expand
# ...
@dataclass
class RequestBody:
required_input: str
optional_input: str | None = None
@dataclass
class SubmitResponseBody:
success: bool
message: str
class SubmitFactApi(quickapi.BaseApi[SubmitResponseBody]):
url = "/facts/new"
method = quickapi.BaseHttpMethod.POST
request_body = RequestBody
response_body = SubmitResponseBody
class ExampleClient(quickapi.BaseClient):
base_url = "https://example.com"
get_facts = quickapi.ApiEndpoint(GetFactsApi)
submit_fact = quickapi.ApiEndpoint(SubmitFactApi)
And to use it:
client = ExampleClient()
request_body = RequestBody(required_input="dummy")
response = client.submit_fact(request_body=request_body)
A POST request with authentication
An example of a POST request with HTTP header API key.
Click to expand
import httpx_auth
# ...
class SubmitFactApi(quickapi.BaseApi[SubmitResponseBody]):
url = "/facts/new"
method = quickapi.BaseHttpMethod.POST
# Specify it here if you want all requests to this endpoint to have auth
# auth = httpx_auth.HeaderApiKey(header_name="X-Api-Key", api_key="secret_api_key")
request_body = RequestBody
response_body = SubmitResponseBody
class ExampleClient(quickapi.BaseClient):
base_url = "https://example.com"
# Specify it here if you want requests to all of this clients' endpoints to have auth
# auth = httpx_auth.HeaderApiKey(header_name="X-Api-Key", api_key="secret_api_key")
get_facts = quickapi.ApiEndpoint(GetFactsApi)
submit_fact = quickapi.ApiEndpoint(SubmitFactApi)
And to use it:
auth = httpx_auth.HeaderApiKey(header_name="X-Api-Key", api_key="secret_api_key")
client = ExampleClient(
# Specify it here to have auth for all apis on this client instance only
# auth=auth
)
request_body = RequestBody(required_input="dummy")
response = client.submit_fact(
request_body=request_body,
# Or here to have auth just for this api request
auth=auth
)
A POST request with error handling
An example of a POST request that handles HTTP error codes too.
Click to expand
# ...
@dataclass
class ResponseError401:
status: str
message: str
class SubmitFactApi(quickapi.BaseApi[SubmitResponseBody]):
url = "/facts/new"
method = quickapi.BaseHttpMethod.POST
response_body = ResponseBody
# Add more types for each HTTP status code you wish to handle
response_errors = {401: ResponseError401}
class ExampleClient(quickapi.BaseClient):
base_url = "https://example.com"
submit_fact = quickapi.ApiEndpoint(SubmitFactApi)
And to use it:
client = ExampleClient()
request_body = RequestBody(required_input="dummy")
try:
response = client.submit_fact(request_body=request_body)
except quickapi.HTTPError as e:
match e.value.status_code:
case 401:
assert isinstance(e.value.body, ResponseError401)
print(f"Received {e.value.body.status} with {e.value.body.message}")
case _:
print("Unhandled error occured.")
A POST request with validation and conversion (Using attrs
)
An example of a POST request with custom validators and converters (using attrs
instead).
Click to expand
import attrs
import quickapi
import enum
class State(enum.Enum):
ON = "on"
OFF = "off"
@attrs.define
class RequestBody:
state: State = attrs.field(validator=attrs.validators.in_(State))
email: str = attrs.field(
validator=attrs.validators.matches_re(
r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
)
)
@attrs.define
class ResponseBody:
success: bool = attrs.field(converter=attrs.converters.to_bool)
class SubmitApi(quickapi.BaseApi[ResponseBody]):
url = "/submit"
method = quickapi.BaseHttpMethod.POST
request_body = RequestBody
response_body = ResponseBody
class ExampleClient(quickapi.BaseClient):
base_url = "https://example.com"
submit = quickapi.ApiEndpoint(SubmitApi)
And to use it:
client = ExampleClient()
request_body = RequestBody(email="invalid_email", state="on") # Will raise an error
response = client.submit(request_body=request_body)
Check out attrs for full configuration.
A POST request with validation and conversion (Using pydantic
)
An example of a POST request with custom validators and converters (using pydantic
instead).
Click to expand
import enum
import pydantic
import quickapi
class State(enum.Enum):
ON = "on"
OFF = "off"
class RequestBody(pydantic.BaseModel):
state: State
email: pydantic.EmailStr
class ResponseBody(pydantic.BaseModel):
success: bool
class SubmitApi(quickapi.BaseApi[ResponseBody]):
url = "/submit"
method = quickapi.BaseHttpMethod.POST
request_body = RequestBody
response_body = ResponseBody
class ExampleClient(quickapi.BaseClient):
base_url = "https://example.com"
submit = quickapi.ApiEndpoint(SubmitApi)
And to use it:
client = ExampleClient()
request_body = RequestBody(email="invalid_email", state="on") # Will raise an error
response = client.submit(request_body=request_body)
Check out pydantic for full configuration.
A POST request with validation and conversion (Using msgspec
)
An example of a POST request with custom validators and converters (using msgspec
instead).
Click to expand
import enum
from typing import Annotated
import msgspec
import quickapi
class State(enum.Enum):
ON = "on"
OFF = "off"
class RequestBody(msgspec.Struct):
state: State
email: str = Annotated[str, msgspec.Meta(pattern=r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$')]
class ResponseBody(msgspec.Struct):
success: bool
class SubmitApi(quickapi.BaseApi[ResponseBody]):
url = "/submit"
method = quickapi.BaseHttpMethod.POST
request_body = RequestBody
response_body = ResponseBody
class ExampleClient(quickapi.BaseClient):
base_url = "https://example.com"
submit = quickapi.ApiEndpoint(SubmitApi)
And to use it:
client = ExampleClient()
request_body = RequestBody(email="invalid_email", state="on") # Will raise an error
response = client.submit(request_body=request_body)
Using requests
library
An example of a GET request using the requests
HTTP library instead of HTTPx
.
Click to expand
from dataclasses import dataclass
import quickapi
@dataclass
class ResponseBody:
current_page: int
data: list[Fact]
class GetFactsApi(quickapi.BaseApi[ResponseBody]):
url = "/facts"
response_body = ResponseBody
class ExampleClient(quickapi.BaseClient)
base_url = "https://example.com"
http_client = quickapi.RequestsClient()
get_facts = quickapi.ApiEndpoint(GetFactsApi)
And to use it:
client = ExampleClient()
response = client.get_facts()
Goal
Eventually, I would like for the API client definition to end up looking more like this:
Click to expand
import quickapi
@quickapi.api
class FetchApi:
url = "/fetch"
method = quickapi.BaseHttpMethod.GET
class ResponseBody:
current_page: int
data: list[Fact]
@quickapi.api
class SubmitApi:
url = "/submit"
method = quickapi.BaseHttpMethod.POST
class RequestBody:
required_input: str
optional_input: str | None = None
class ResponseBody:
success: bool
message: str
@quickapi.client
class MyClient:
base_url = "https://example.com"
fetch = quickapi.ApiEndpoint(FetchApi)
submit = quickapi.ApiEndpoint(SubmitApi)
client = MyClient(auth=...)
response = client.fetch()
response = client.submit(request_body=RequestBody(...))
Contributing
Contributions are welcomed, and greatly appreciated!
The easiest way to contribute, if you found this useful or interesting, is by giving it a star! 🌟
Otherwise, check out the contributing guide for how else to help and get started.
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 quickapiclient-0.5.0.tar.gz
.
File metadata
- Download URL: quickapiclient-0.5.0.tar.gz
- Upload date:
- Size: 18.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.4 CPython/3.11.10 Linux/6.5.0-1025-azure
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ac33957dc6999fe35dd6e40db5d240a26fd1716609f42148285b4e45bd4a033e |
|
MD5 | 5184e9e9ace1d73cf9ffaa92a686a59d |
|
BLAKE2b-256 | 3780f2ff5296b58fa035cb96468afbe4cd466943fbc92088e0ff3cf033763250 |
File details
Details for the file quickapiclient-0.5.0-py3-none-any.whl
.
File metadata
- Download URL: quickapiclient-0.5.0-py3-none-any.whl
- Upload date:
- Size: 20.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.4 CPython/3.11.10 Linux/6.5.0-1025-azure
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 64a469416cd951d0139a9b511c630ef5cc354c9236020626a609988b0f542e8a |
|
MD5 | cc84d16b619b62d7446c854630b47fb4 |
|
BLAKE2b-256 | 366ce48a3236a16375c69de1e952a955e280a8bb58ea7c2333d5b15611bd3dcf |