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 quick 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]
# Now we can define our API
class MyApi(quickapi.BaseApi[ResponseBody]):
url = "https://catfact.ninja/facts"
response_body = ResponseBody
And you would use it like this:
api_client = MyApi()
response = api_client.execute()
# That's it! Now `response` is fully typed and conforms to our `ResponseBody` definition
assert isinstance(response.body, ResponseBody)
assert isinstance(response.body.data[0], Fact)
There's also support for attrs
or pydantic
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
- Nested/inner class definitions
- Fully typed HTTP status error codes handling
- Sessions support and/or allow building several related APIs through a single interface
- Generate API boilerplate from OpenAPI specs
- Full async support
- 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
- 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 `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 `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
from dataclasses import dataclass
import quickapi
@dataclass
class RequestParams:
max_length: int = 100
limit: int = 10
@dataclass
class Fact:
fact: str
length: int
@dataclass
class ResponseBody:
current_page: int
data: list[Fact]
class MyApi(quickapi.BaseApi[ResponseBody]):
url = "https://catfact.ninja/facts"
request_params = RequestParams
response_body = ResponseBody
And to use it:
client = MyApi()
# Using default request param values
response = client.execute()
# Using custom request param values
request_params = RequestParams(max_length=5, limit=10)
response = client.execute(request_params=request_params)
A POST request
An example of a POST request with some optional and required data.
Click to expand
from dataclasses import dataclass
import quickapi
@dataclass
class RequestBody:
required_input: str
optional_input: str | None = None
@dataclass
class Fact:
fact: str
length: int
@dataclass
class ResponseBody:
current_page: int
data: list[Fact]
class MyApi(quickapi.BaseApi[ResponseBody]):
url = "https://catfact.ninja/facts"
method = quickapi.BaseHttpMethod.POST
request_body = RequestBody
response_body = ResponseBody
And to use it:
client = MyApi()
request_body = RequestBody(required_input="dummy")
response = client.execute(request_body=request_body)
A POST request with authentication
An example of a POST request with HTTP header API key.
Click to expand
from dataclasses import dataclass
import httpx_auth
import quickapi
@dataclass
class RequestBody:
required_input: str
optional_input: str | None = None
@dataclass
class Fact:
fact: str
length: int
@dataclass
class AuthResponseBody:
authenticated: bool
user: str
class MyApi(quickapi.BaseApi[AuthResponseBody]):
url = "https://httpbin.org/bearer"
method = quickapi.BaseHttpMethod.POST
# You could specify it here if you wanted
# auth = httpx_auth.HeaderApiKey(header_name="X-Api-Key", api_key="secret_api_key")
response_body = AuthResponseBody
And to use it:
client = MyApi()
request_body = RequestBody(required_input="dummy")
auth = httpx_auth.HeaderApiKey(header_name="X-Api-Key", api_key="secret_api_key")
response = client.execute(request_body=request_body, auth=auth)
A POST request with error handling
An example of a POST request with HTTP header API key that handles HTTP error codes too.
Click to expand
from dataclasses import dataclass
import httpx_auth
import quickapi
@dataclass
class RequestBody:
required_input: str
optional_input: str | None = None
@dataclass
class Fact:
fact: str
length: int
@dataclass
class AuthResponseBody:
authenticated: bool
user: str
@dataclass
class ResponseError401:
status: str
message: str
class MyApi(quickapi.BaseApi[AuthResponseBody]):
url = "https://httpbin.org/bearer"
method = quickapi.BaseHttpMethod.POST
response_body = AuthResponseBody
# Add more types for each HTTP status code you wish to handle
response_errors = {401: ResponseError401}
And to use it:
client = MyApi()
request_body = RequestBody(required_input="dummy")
auth = httpx_auth.HeaderApiKey(header_name="X-Api-Key", api_key="incorrect_api_key")
try:
response = client.execute(request_body=request_body, auth=auth)
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 MyApi(quickapi.BaseApi[ResponseBody]):
url = "https://example.com/"
method = quickapi.BaseHttpMethod.POST
request_body = RequestBody
response_body = ResponseBody
And to use it:
client = MyApi()
request_body = RequestBody(email="invalid_email", state="on") # Will raise an error
response = client.execute(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 MyApi(quickapi.BaseApi[ResponseBody]):
url = "https://example.com/"
method = quickapi.BaseHttpMethod.POST
request_body = RequestBody
response_body = ResponseBody
And to use it:
client = MyApi()
request_body = RequestBody(email="invalid_email", state="on") # Will raise an error
response = client.execute(request_body=request_body)
Check out pydantic for full configuration.
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 MyApi(quickapi.BaseApi[ResponseBody]):
url = "https://catfact.ninja/facts"
response_body = ResponseBody
http_client = quickapi.RequestsClient()
And to use it:
client = MyApi()
response = client.execute()
Multiple API endpoints sharing state
You can easily create a client to manage related endpoints, and even share things like auth. This is done through pure Python at this stage, though we aim to make this a lot easier and streamlined in the future.
Click to expand
... [Assuming GetApi and SubmitApi have been already defined]
class ExampleClient:
fetch = GetApi
submit = SubmitApi
And to use it:
client = ExampleClient()
auth = httpx_auth.HeaderApiKey(header_name="X-Api-Key", api_key="secret_api_key")
http_client = httpx.Client()
# Calling the GetApi endpoint
response = client.fetch(auth=auth, http_client=http_client).execute()
# Calling the SubmitApi endpoint
response = client.submit(auth=auth, http_client=http_client).execute()
Goal
Eventually, I would like for the API definition to end up looking more like this (though the current approach will still be supported):
Click to expand
import quickapi
@quickapi.define
class SubmitApi:
url = "/submit"
method = quickapi.BaseHttpMethod.POST
class RequestBody:
required_input: str
optional_input: str | None = None
class ResponseBody:
current_page: int
data: list[Fact]
And if you had multiple related endpoints that could share HTTP session or auth:
@quickapi.define
class FetchApi:
url = "/fetch"
method = quickapi.BaseHttpMethod.GET
class ResponseBody:
current_page: int
data: list[Fact]
@quickapi.define_client
class MyClient:
base_url = "https://catfact.ninja"
fetch = FetchApi
submit = SubmitApi
client = MyClient(auth=...)
response = client.fetch()
response = client.submit(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
Hashes for quickapiclient-0.0.14-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 28e09194450ea04e4fd405dec5e42eb0063428158df7ebb55f10065389ee9fbc |
|
MD5 | 7e3c5bf7685f73147fa2cb321679a5f6 |
|
BLAKE2b-256 | a68be85e1372c70b27097641abc3689772f894260730fe8b00d0bebbf4d14ef9 |