# 📦 APIQ
[](https://pypi.python.org/pypi/apiq)

[](LICENSE)
**APIQ** is a modern Python toolkit for building both **synchronous** and **asynchronous** API clients with clean,
minimal code and full type safety.
Define endpoints using decorators like `@sync_endpoint` or `@async_endpoint`, structure logic with optional namespaces,
and leverage Pydantic models for strict request/response validation — all with built-in rate limiting and retries.



## Installation
```bash
pip install apiq
```
## Quickstart
### 1. Define your models
Use [Pydantic](https://docs.pydantic.dev/latest/) to define request and response schemas with full type safety:
```python
from typing import List
from pydantic import BaseModel
class BulkAccountsRequest(BaseModel):
account_ids: List[str]
class AccountInfoResponse(BaseModel):
address: str
balance: int
status: str
class BulkAccountsResponse(BaseModel):
accounts: List[AccountInfoResponse]
```
### 2. Define your client
Declare your API client by subclassing `AsyncClientAPI` and annotating endpoints with `@async_endpoint`:
```python
from apiq import AsyncClientAPI, async_endpoint
class AsyncTONAPI(AsyncClientAPI):
base_url = "https://tonapi.io"
headers = {"Authorization": "Bearer <YOUR_API_KEY>"}
version = "v2"
rps = 1
max_retries = 2
@async_endpoint("GET")
async def status(self) -> dict:
"""Check API status (GET /status)"""
@async_endpoint("GET")
async def rates(self, tokens: str, currencies: str) -> dict:
"""Get token rates (GET /rates?tokens={tokens}¤cies={currencies})"""
```
**Notes:**
* If you prefer synchronous clients, simply use `SyncClientAPI` with `@sync_endpoint` instead.
* For synchronous clients, use `SyncClientAPI` and `@sync_endpoint` — interface is fully symmetrical.
* Method arguments are automatically mapped to path and query parameters. The return value is parsed from JSON and
returned as a `dict`, unless a `return_as=Model` is specified.
### 3. Group endpoints with namespaces (optional)
Use `AsyncAPINamespace` to logically organize endpoints under a common prefix (e.g., `/accounts`):
```python
from apiq import AsyncAPINamespace, async_endpoint
class Accounts(AsyncAPINamespace):
namespace = "accounts"
@async_endpoint("GET", path="/{account_id}", return_as=AccountInfoResponse)
async def info(self, account_id: str) -> AccountInfoResponse:
"""Retrieve account information by account_id (GET /accounts/{account_id})"""
@async_endpoint("POST", path="/_bulk", return_as=BulkAccountsResponse)
async def bulk_info(self, payload: BulkAccountsRequest) -> BulkAccountsResponse:
"""Retrieve info for multiple accounts with a Pydantic model (POST /accounts/_bulk)"""
@async_endpoint("POST", path="/_bulk")
async def bulk_info_dict(self, payload: dict) -> dict:
"""Retrieve info for multiple accounts with a dict payload (POST /accounts/_bulk)"""
```
Then include the namespace in your main client:
```python
class AsyncTONAPI(AsyncClientAPI):
base_url = "https://tonapi.io"
headers = {"Authorization": "Bearer <YOUR_API_KEY>"}
version = "v2"
rps = 1
max_retries = 2
# ... endpoints above ...
@property
def accounts(self) -> Accounts:
return Accounts(self)
```
**Note:**
The `namespace` can be defined with or without a leading slash. It will be joined correctly with endpoint paths. Each
namespace instance receives the parent client instance automatically.
### 4. Usage
```python
async def main():
tonapi = AsyncTONAPI()
async with tonapi:
# GET /status
status = await tonapi.status()
print(status)
```
**Note:**
Always use `async with` to open and close the session properly. All retries, throttling, and connection reuse are
handled under the hood.
## API Configuration
All settings are defined as class attributes on your client class:
| Name | Type | Description | Default |
|---------------|-------|-------------------------------------------------------------------|---------|
| `base_url` | str | Base URL of the API (must start with `http://` or `https://`) | — |
| `version` | str | Optional API version prefix (e.g. `"v1"` → `/v1/...` in requests) | None |
| `rps` | int | Max requests per second (client-side rate limit) | 1 |
| `max_retries` | int | Max automatic retries for HTTP 429 (Too Many Requests) | 3 |
| `headers` | dict | Default headers to send with each request | None |
| `cookies` | dict | Default cookies to send with each request | None |
| `timeout` | float | Default request timeout in seconds | None |
**Note:**
The `version` field is automatically prefixed to all endpoint paths (e.g. `/v2/accounts/...`).
Rate limiting and retries are handled transparently and apply only per-client instance.
## Endpoints
* Use `@async_endpoint(method, path=..., return_as=...)` to declare each endpoint.
* All method arguments are automatically mapped:
* Scalars → query/path parameters
* dict or Pydantic models → request body
* `return_as=Model` lets you parse responses into Pydantic models.
* If omitted, response is returned as `dict`.
**Note:**
If `path` is omitted, the method name becomes the endpoint path (e.g. `rates` → `/rates`). You can define both flat and
namespaced methods together.
## Notes
* Fully asynchronous: all clients and endpoints require `async with`.
* Zero boilerplate: request building, error handling, retries, and throttling are automatic.
* Namespaces help organize large APIs, but are optional.
* Both dicts and Pydantic models are supported in request payloads.
* Great for building typed SDKs or internal tools.
## Contribution
We welcome your contributions!
If you find a bug or have an idea, please open an issue or submit a pull request.
## License
Distributed under the [MIT License](LICENSE).
Use freely for commercial or personal projects.
Raw data
{
"_id": null,
"home_page": "https://github.com/nessshon/apiq",
"name": "apiq",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "api client async sync http aiohttp requests pydantic",
"author": "nessshon",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/62/27/ac0aa1cfd2018b5a219cfae0619342b7585c315c2fcc17ac2b93cb054498/apiq-0.0.5.tar.gz",
"platform": null,
"description": "# \ud83d\udce6 APIQ\n\n[](https://pypi.python.org/pypi/apiq)\n\n[](LICENSE)\n\n**APIQ** is a modern Python toolkit for building both **synchronous** and **asynchronous** API clients with clean,\nminimal code and full type safety.\n\nDefine endpoints using decorators like `@sync_endpoint` or `@async_endpoint`, structure logic with optional namespaces,\nand leverage Pydantic models for strict request/response validation \u2014 all with built-in rate limiting and retries.\n\n\n\n\n\n## Installation\n\n```bash\npip install apiq\n```\n\n## Quickstart\n\n### 1. Define your models\n\nUse [Pydantic](https://docs.pydantic.dev/latest/) to define request and response schemas with full type safety:\n\n```python\nfrom typing import List\nfrom pydantic import BaseModel\n\n\nclass BulkAccountsRequest(BaseModel):\n account_ids: List[str]\n\n\nclass AccountInfoResponse(BaseModel):\n address: str\n balance: int\n status: str\n\n\nclass BulkAccountsResponse(BaseModel):\n accounts: List[AccountInfoResponse]\n```\n\n### 2. Define your client\n\nDeclare your API client by subclassing `AsyncClientAPI` and annotating endpoints with `@async_endpoint`:\n\n```python\nfrom apiq import AsyncClientAPI, async_endpoint\n\n\nclass AsyncTONAPI(AsyncClientAPI):\n base_url = \"https://tonapi.io\"\n headers = {\"Authorization\": \"Bearer <YOUR_API_KEY>\"}\n version = \"v2\"\n rps = 1\n max_retries = 2\n\n @async_endpoint(\"GET\")\n async def status(self) -> dict:\n \"\"\"Check API status (GET /status)\"\"\"\n\n @async_endpoint(\"GET\")\n async def rates(self, tokens: str, currencies: str) -> dict:\n \"\"\"Get token rates (GET /rates?tokens={tokens}¤cies={currencies})\"\"\"\n```\n\n**Notes:**\n* If you prefer synchronous clients, simply use `SyncClientAPI` with `@sync_endpoint` instead.\n* For synchronous clients, use `SyncClientAPI` and `@sync_endpoint` \u2014 interface is fully symmetrical.\n* Method arguments are automatically mapped to path and query parameters. The return value is parsed from JSON and\n returned as a `dict`, unless a `return_as=Model` is specified.\n\n### 3. Group endpoints with namespaces (optional)\n\nUse `AsyncAPINamespace` to logically organize endpoints under a common prefix (e.g., `/accounts`):\n\n```python\nfrom apiq import AsyncAPINamespace, async_endpoint\n\n\nclass Accounts(AsyncAPINamespace):\n namespace = \"accounts\"\n\n @async_endpoint(\"GET\", path=\"/{account_id}\", return_as=AccountInfoResponse)\n async def info(self, account_id: str) -> AccountInfoResponse:\n \"\"\"Retrieve account information by account_id (GET /accounts/{account_id})\"\"\"\n\n @async_endpoint(\"POST\", path=\"/_bulk\", return_as=BulkAccountsResponse)\n async def bulk_info(self, payload: BulkAccountsRequest) -> BulkAccountsResponse:\n \"\"\"Retrieve info for multiple accounts with a Pydantic model (POST /accounts/_bulk)\"\"\"\n\n @async_endpoint(\"POST\", path=\"/_bulk\")\n async def bulk_info_dict(self, payload: dict) -> dict:\n \"\"\"Retrieve info for multiple accounts with a dict payload (POST /accounts/_bulk)\"\"\"\n```\n\nThen include the namespace in your main client:\n\n```python\nclass AsyncTONAPI(AsyncClientAPI):\n base_url = \"https://tonapi.io\"\n headers = {\"Authorization\": \"Bearer <YOUR_API_KEY>\"}\n version = \"v2\"\n rps = 1\n max_retries = 2\n\n # ... endpoints above ...\n\n @property\n def accounts(self) -> Accounts:\n return Accounts(self)\n```\n\n**Note:**\nThe `namespace` can be defined with or without a leading slash. It will be joined correctly with endpoint paths. Each\nnamespace instance receives the parent client instance automatically.\n\n### 4. Usage\n\n```python\nasync def main():\n tonapi = AsyncTONAPI()\n\n async with tonapi:\n # GET /status\n status = await tonapi.status()\n print(status)\n```\n\n**Note:**\nAlways use `async with` to open and close the session properly. All retries, throttling, and connection reuse are\nhandled under the hood.\n\n## API Configuration\n\nAll settings are defined as class attributes on your client class:\n\n| Name | Type | Description | Default |\n|---------------|-------|-------------------------------------------------------------------|---------|\n| `base_url` | str | Base URL of the API (must start with `http://` or `https://`) | \u2014 |\n| `version` | str | Optional API version prefix (e.g. `\"v1\"` \u2192 `/v1/...` in requests) | None |\n| `rps` | int | Max requests per second (client-side rate limit) | 1 |\n| `max_retries` | int | Max automatic retries for HTTP 429 (Too Many Requests) | 3 |\n| `headers` | dict | Default headers to send with each request | None |\n| `cookies` | dict | Default cookies to send with each request | None |\n| `timeout` | float | Default request timeout in seconds | None |\n\n**Note:**\nThe `version` field is automatically prefixed to all endpoint paths (e.g. `/v2/accounts/...`).\nRate limiting and retries are handled transparently and apply only per-client instance.\n\n## Endpoints\n\n* Use `@async_endpoint(method, path=..., return_as=...)` to declare each endpoint.\n* All method arguments are automatically mapped:\n\n * Scalars \u2192 query/path parameters\n * dict or Pydantic models \u2192 request body\n* `return_as=Model` lets you parse responses into Pydantic models.\n* If omitted, response is returned as `dict`.\n\n**Note:**\nIf `path` is omitted, the method name becomes the endpoint path (e.g. `rates` \u2192 `/rates`). You can define both flat and\nnamespaced methods together.\n\n## Notes\n\n* Fully asynchronous: all clients and endpoints require `async with`.\n* Zero boilerplate: request building, error handling, retries, and throttling are automatic.\n* Namespaces help organize large APIs, but are optional.\n* Both dicts and Pydantic models are supported in request payloads.\n* Great for building typed SDKs or internal tools.\n\n## Contribution\n\nWe welcome your contributions!\nIf you find a bug or have an idea, please open an issue or submit a pull request.\n\n## License\n\nDistributed under the [MIT License](LICENSE).\nUse freely for commercial or personal projects.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "APIQ is a modern Python toolkit for building both synchronous and asynchronous API clients with clean, minimal code and full type safety.",
"version": "0.0.5",
"project_urls": {
"Homepage": "https://github.com/nessshon/apiq",
"Issues": "https://github.com/nessshon/apiq/issues",
"Source": "https://github.com/nessshon/apiq"
},
"split_keywords": [
"api",
"client",
"async",
"sync",
"http",
"aiohttp",
"requests",
"pydantic"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "0a94230a313662b880ec1711227b165a738070e0bce9fa34d38411755ec05aef",
"md5": "7d2a253aece172c684f59604a7854651",
"sha256": "51cd09d1ef5409bd2331c0be6f7f596fe7f777fc706bb8ba56f5f76c3786edf6"
},
"downloads": -1,
"filename": "apiq-0.0.5-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7d2a253aece172c684f59604a7854651",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 17435,
"upload_time": "2025-08-01T00:31:44",
"upload_time_iso_8601": "2025-08-01T00:31:44.329247Z",
"url": "https://files.pythonhosted.org/packages/0a/94/230a313662b880ec1711227b165a738070e0bce9fa34d38411755ec05aef/apiq-0.0.5-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "6227ac0aa1cfd2018b5a219cfae0619342b7585c315c2fcc17ac2b93cb054498",
"md5": "215935ffaf30d713908f6f56b0ba77cf",
"sha256": "67ebd22216998d7fae9841759f5cb0f9287df34ad1b15c726e12e86776609ac2"
},
"downloads": -1,
"filename": "apiq-0.0.5.tar.gz",
"has_sig": false,
"md5_digest": "215935ffaf30d713908f6f56b0ba77cf",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 14877,
"upload_time": "2025-08-01T00:31:45",
"upload_time_iso_8601": "2025-08-01T00:31:45.366690Z",
"url": "https://files.pythonhosted.org/packages/62/27/ac0aa1cfd2018b5a219cfae0619342b7585c315c2fcc17ac2b93cb054498/apiq-0.0.5.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-01 00:31:45",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "nessshon",
"github_project": "apiq",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "aiohttp",
"specs": [
[
"<=",
"3.12.2"
],
[
">=",
"3.7.0"
]
]
},
{
"name": "aiolimiter",
"specs": [
[
"~=",
"1.2.1"
]
]
},
{
"name": "pydantic",
"specs": [
[
">=",
"2.0"
],
[
"<",
"3.0"
]
]
},
{
"name": "requests",
"specs": [
[
"~=",
"2.32.4"
]
]
},
{
"name": "ratelimiter",
"specs": [
[
"~=",
"1.2.0.post0"
]
]
}
],
"lcname": "apiq"
}