Skip to main content

Create fully typed declarative API clients quickly and easily.

Project description

Quick Api Client

Release Build status codecov

A library for creating fully typed declarative API clients quickly and easily.

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 or requests, 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.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


Download files

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

Source Distribution

quickapiclient-0.5.1.tar.gz (82.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

quickapiclient-0.5.1-py3-none-any.whl (20.5 kB view details)

Uploaded Python 3

File details

Details for the file quickapiclient-0.5.1.tar.gz.

File metadata

  • Download URL: quickapiclient-0.5.1.tar.gz
  • Upload date:
  • Size: 82.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.5.11

File hashes

Hashes for quickapiclient-0.5.1.tar.gz
Algorithm Hash digest
SHA256 55380b226515b57dab20e475802a8daa4623f8999453a32f8532c06ddeb7b0b0
MD5 ef2dd9391ab54b8e998f61e7c041ded5
BLAKE2b-256 a4e84d11ef76e4f7a757c583039b39e0299edeba385a1f426eea26e69d943785

See more details on using hashes here.

File details

Details for the file quickapiclient-0.5.1-py3-none-any.whl.

File metadata

File hashes

Hashes for quickapiclient-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 726a024d1afe42b361e5585087904efce6009f337e8b089c6a8d5139d4260a99
MD5 a3e62b5c268f3456eb5d9d21d6e031b0
BLAKE2b-256 cad50c42595ba45d5e6d615beb2bc6fc47f028320d9cbeb424387914e81f6589

See more details on using hashes here.

Supported by

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