Skip to main content

A library for creating 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:

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


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.0.tar.gz (18.1 kB view details)

Uploaded Source

Built Distribution

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

Uploaded Python 3

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

Hashes for quickapiclient-0.5.0.tar.gz
Algorithm Hash digest
SHA256 ac33957dc6999fe35dd6e40db5d240a26fd1716609f42148285b4e45bd4a033e
MD5 5184e9e9ace1d73cf9ffaa92a686a59d
BLAKE2b-256 3780f2ff5296b58fa035cb96468afbe4cd466943fbc92088e0ff3cf033763250

See more details on using hashes here.

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

Hashes for quickapiclient-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 64a469416cd951d0139a9b511c630ef5cc354c9236020626a609988b0f542e8a
MD5 cc84d16b619b62d7446c854630b47fb4
BLAKE2b-256 366ce48a3236a16375c69de1e952a955e280a8bb58ea7c2333d5b15611bd3dcf

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