Skip to main content

clientity

Rapid HTTP clients with operator-based endpoint definitions.

Installation

pip install clientity

With your preferred HTTP library:

pip install clientity[httpx]    # or requests, aiohttp

Quick Start

import httpx
from dataclasses import dataclass
from clientity import client, endpoint, Resource

# Define response models
@dataclass
class User:
    id: int
    name: str
    email: str

@dataclass
class Status:
    ok: bool
    version: str

# Create client
api = client(httpx.AsyncClient()) @ "https://api.example.com"

# Define endpoints with operators
api.status = endpoint.get @ "/status" >> Status
api.user = endpoint.get @ "/users/{id}" >> User
api.users = endpoint.get @ "/users" >> list[User]

# Use it
async def main():
    status = await api.status()
    user = await api.user(id=123)
    users = await api.users()

Operators

Operator Method Description
@ .at(path) Set endpoint path
% .queries(model) Set query parameter model
<< .requests(model) Set request body model
>> .responds(model) Set response model
& .prehook(fn) Add pre-request hook
| .posthook(fn) Add post-response hook
# Full example
api.search = (
    endpoint.post 
    @ "/search" 
    % SearchQuery      # query params
    << SearchBody      # request body
    >> list[Result]    # response model
    & log_request      # pre-hook [(Request) -> Request]
    | log_response     # post-hook [(Response) -> Response]
)

results = await api.search(q="python", limit=10, filters={"type": "code"})

Path Parameters

Path parameters are extracted from {param} syntax and can be passed positionally or by name:

api.user = endpoint.get @ "/users/{user_id}/posts/{post_id}"

# Both work:
post = await api.user(123, 456)
post = await api.user(user_id=123, post_id=456)

Query & Body Models

Use dataclasses or Pydantic models for query parameters and request bodies:

from dataclasses import dataclass

@dataclass
class SearchQuery:
    q: str
    limit: int = 10
    offset: int = 0

@dataclass
class CreateUser:
    name: str
    email: str

api.search = endpoint.get @ "/search" % SearchQuery
api.create_user = endpoint.post @ "/users" << CreateUser

# Query params from model fields
results = await api.search(q="python", limit=20)

# Body from model fields  
user = await api.create_user(name="Joel", email="joel@example.com")

Response Models

Single Model

@dataclass
class User:
    id: int
    name: str

api.user = endpoint.get @ "/users/{id}" >> User
user = await api.user(id=1)  # Returns User instance

List Response

api.users = endpoint.get @ "/users" >> list[User]
users = await api.users()  # Returns list[User]

Custom Response Handling

Implement __respond__ for custom single-item parsing:

@dataclass
class User:
    id: int
    name: str
    
    @classmethod
    def __respond__(cls, response) -> 'User':
        data = response.json()["data"]["user"]
        return cls(**data)

Implement __respondall__ for custom list parsing:

@dataclass  
class User:
    id: int
    name: str
    
    @classmethod
    def __respondall__(cls, response) -> list['User']:
        data = response.json()
        return [cls(**u) for u in data["results"]]

Resources

Group related endpoints under a path prefix:

from clientity import Resource

users = Resource("users")
users.list = endpoint.get @ "" >> list[User]
users.get = endpoint.get @ "{id}" >> User
users.create = endpoint.post @ "" << CreateUser >> User
users.delete = endpoint.delete @ "{id}"

api.users = users

# Usage
all_users = await api.users.list()
user = await api.users.get(id=123)
new_user = await api.users.create(name="Joel", email="joel@example.com")
await api.users.delete(id=123)

Nested Resources

api_resource = Resource("api")
v1 = Resource("v1")
v1.users = endpoint.get @ "users" >> list[User]
v1.posts = endpoint.get @ "posts" >> list[Post]
api_resource.v1 = v1

api.api = api_resource

# Resolves to /api/v1/users
users = await api.api.v1.users()

Namespaces

For endpoints with different base URLs or independent HTTP clients:

from clientity import Namespace

# Independent namespace with own client
search = Namespace(
    base="https://search.example.com",
    interface=httpx.AsyncClient()
)
search.query = endpoint.post @ "/query" >> list[Result]

api.search = search

# Uses search.example.com, not api.example.com
results = await api.search.query(q="test")

Dependent namespace (uses parent client):

auth = Namespace(name="auth")
auth.login = endpoint.post @ "/auth/login" << Credentials >> Token

api.auth = auth

# Uses api.example.com/auth/login
token = await api.auth.login(username="joel", password="secret")

Hooks

Pre-hooks modify the request before sending:

def add_auth(request):
    request.headers["Authorization"] = "Bearer token123"
    return request

api.secure = endpoint.get @ "/secure" & add_auth

Post-hooks modify the response after receiving:

def log_response(response):
    print(f"Status: {response.status_code}")
    return response

api.logged = endpoint.get @ "/data" | log_response

Async hooks work too:

async def refresh_token(request):
    token = await get_fresh_token()
    request.headers["Authorization"] = f"Bearer {token}"
    return request

api.secure = endpoint.get @ "/secure" & refresh_token

Supported HTTP Libraries

clientity adapts to your preferred HTTP library:

# httpx (sync or async)
import httpx
api = client(httpx.AsyncClient()) @ "https://api.example.com"
api = client(httpx.Client()) @ "https://api.example.com"

# requests
import requests
api = client(requests.Session()) @ "https://api.example.com"

# aiohttp
import aiohttp
api = client(aiohttp.ClientSession()) @ "https://api.example.com"

Lazy Interface

Pass a callable to defer client creation:

def make_client():
    return httpx.AsyncClient(headers={"X-API-Key": os.environ["API_KEY"]})

api = client(make_client) @ "https://api.example.com"

License

MIT

Metadata

Release files for clientity 0.1.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for clientity 0.1.6
File Size Uploaded
clientity-0.1.6.tar.gz 23.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for clientity 0.1.6
File Interpreter ABI Platform
clientity-0.1.6-py2.py3-none-any.whl Python 3, Python 2 none any Details

Total release size: 49.7 kB

Release files / clientity-0.1.6.tar.gz

Download URL clientity-0.1.6.tar.gz
Size 23.3 kB
Tags Source
SHA-256 checksum
How to use checksums
8b336e5acdf018190dde333c66748e8c17d499ee7bc34544abff05729f5dae1d
BLAKE2b-256 checksum
How to use checksums
d020b49247330687e05e74cb2b32265b328210cc5d1e2b589ff6f17ae40c577d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Release files / clientity-0.1.6-py2.py3-none-any.whl

Download URL clientity-0.1.6-py2.py3-none-any.whl
Size 26.4 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
14fe29546d5f52940766955c7a0f1b5e5da7ea2446f94f994a4660d3ab9e6e7b
BLAKE2b-256 checksum
How to use checksums
57c51b35e6b3a9b095c7bc765b42a5a84573290cc95575e632094f02ad741732
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Release history Release notifications | RSS feed

This release

0.1.6 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page