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
httpxor 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 or your favourite package manager:
pip install quickapiclient # Or with optional extras (choose from the list below) pip install quickapiclient[attrs,pydantic,msgspec,requests] # Or if using poetry poetry add quickapiclient poetry add quickapiclient[attrs,pydantic,msgspec,requests] # Or if using uv uv add quickapiclient uv add quickapiclient[attrs,pydantic,msgspec,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.HandledHTTPError 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 _: except quickapi.HTTPError as e: 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.