# 📦 APIQ
**APIQ** is an elegant, fully asynchronous Python toolkit for building robust API clients with minimal code and maximal
type safety.
Define endpoints with simple decorators, leverage strict Pydantic models, and enjoy integrated rate limiting and
retries—**no inheritance required**.
[](https://pypi.python.org/pypi/apiq)

[](LICENSE)



---
## Installation
```bash
pip install apiq
```
---
## Quickstart
### 1. Define your models
Use [Pydantic](https://docs.pydantic.dev/latest/) for type-safe request and response models:
```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 class
Configure all core settings via the `@apiclient` decorator:
```python
from apiq import apiclient, endpoint
@apiclient(
base_url="https://tonapi.io",
headers={"Authorization": "Bearer <YOUR_API_KEY>"},
version="v2",
rps=1,
retries=2,
)
class TONAPI:
@endpoint("GET")
async def status(self) -> dict:
"""Check API status (GET /status)"""
@endpoint("GET")
async def rates(self, tokens: str, currencies: str) -> dict:
"""Get token rates (GET /rates?tokens={tokens}¤cies={currencies})"""
```
**Note:**
No base class required. The decorator injects all async context, rate limiting, and HTTP logic automatically.
---
### 3. Group endpoints with namespaces (optional)
For logical endpoint grouping (e.g., `/accounts`, `/users`), use the `@apinamespace` decorator:
```python
from apiq import apinamespace, endpoint
@apinamespace("accounts")
class Accounts:
@endpoint("GET", path="/{account_id}", as_model=AccountInfoResponse)
async def info(self, account_id: str) -> AccountInfoResponse:
"""Retrieve account info (GET /accounts/{account_id})"""
@endpoint("POST", path="/_bulk", as_model=BulkAccountsResponse)
async def bulk_info(self, body: BulkAccountsRequest) -> BulkAccountsResponse:
"""Retrieve info for multiple accounts (POST /accounts/_bulk)"""
@endpoint("POST", path="/_bulk")
async def bulk_info_dict(self, body: dict) -> dict:
"""Retrieve info for multiple accounts (dict body) (POST /accounts/_bulk)"""
```
Then compose in your main client:
```python
@apiclient(
base_url="https://tonapi.io",
headers={"Authorization": "Bearer <YOUR_API_KEY>"},
version="v2",
rps=1,
retries=2,
)
class TONAPI:
# ... endpoints above ...
@property
def accounts(self) -> Accounts:
return Accounts(self)
```
**Note:**
* You can use `"accounts"` or `"/accounts"` in `@apinamespace` — leading slash is optional and combined automatically
with the endpoint path.
---
### 4. Usage
```python
async def main():
tonapi = TONAPI()
async with tonapi:
# Direct endpoint
status = await tonapi.status()
print(status)
# Namespaced endpoint
account = await tonapi.accounts.info("UQCDrgGaI6gWK-qlyw69xWZosurGxrpRgIgSkVsgahUtxZR0")
print(account)
```
---
## API Configuration
All settings are passed to the `@apiclient` decorator:
| Name | Type | Description | Default |
|------------|-------|----------------------------------------------------|---------|
| `base_url` | str | Base URL for your API (must start with http/https) | — |
| `headers` | dict | Default headers (e.g. Authorization) | None |
| `timeout` | float | Default timeout (seconds) | None |
| `rps` | int | Max requests per second (rate limit) | 1 |
| `retries` | int | Max retries for 429 (Too Many Requests) | 3 |
| `cookies` | dict | Cookies to send with every request | None |
---
## Endpoints
* Use `@endpoint` to mark methods as API endpoints.
* All method arguments are automatically mapped to path or query parameters.
* For `POST` and `PUT` requests, the `body` can be a Pydantic model or a dict.
* The return type depends on `response_type` and the `as_model` argument.
---
### Notes
* All endpoints and clients are **fully async**; always use `async with` for resource cleanup.
* You may use flat client classes or split logic into namespaces as needed.
* If `as_model` is not set in `@endpoint`, the raw dict (parsed JSON) is returned.
* If `path` is omitted, the method name is used as the endpoint path (e.g., `status` → `/status`).
---
## Contribution
We welcome your contributions!
If you have ideas for improvement or find a bug, please create an issue or submit a pull request.
---
## License
Distributed under the [MIT License](LICENSE).
Feel free to use, modify, and distribute in accordance with the license.
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 http aiohttp pydantic",
"author": "nessshon",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/45/0f/c049ba61a394749baf4cf19478ff7ee438c9847eb2dfe6fe1a91f1293132/apiq-0.0.2.tar.gz",
"platform": null,
"description": "# \ud83d\udce6 APIQ\n\n**APIQ** is an elegant, fully asynchronous Python toolkit for building robust API clients with minimal code and maximal\ntype safety.\nDefine endpoints with simple decorators, leverage strict Pydantic models, and enjoy integrated rate limiting and\nretries\u2014**no inheritance required**.\n\n[](https://pypi.python.org/pypi/apiq)\n\n[](LICENSE)\n\n\n\n\n\n---\n\n## Installation\n\n```bash\npip install apiq\n```\n\n---\n\n## Quickstart\n\n### 1. Define your models\n\nUse [Pydantic](https://docs.pydantic.dev/latest/) for type-safe request and response models:\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---\n\n### 2. Define your client class\n\nConfigure all core settings via the `@apiclient` decorator:\n\n```python\nfrom apiq import apiclient, endpoint\n\n\n@apiclient(\n base_url=\"https://tonapi.io\",\n headers={\"Authorization\": \"Bearer <YOUR_API_KEY>\"},\n version=\"v2\",\n rps=1,\n retries=2,\n)\nclass TONAPI:\n @endpoint(\"GET\")\n async def status(self) -> dict:\n \"\"\"Check API status (GET /status)\"\"\"\n\n @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**Note:**\nNo base class required. The decorator injects all async context, rate limiting, and HTTP logic automatically.\n\n---\n\n### 3. Group endpoints with namespaces (optional)\n\nFor logical endpoint grouping (e.g., `/accounts`, `/users`), use the `@apinamespace` decorator:\n\n```python\nfrom apiq import apinamespace, endpoint\n\n\n@apinamespace(\"accounts\")\nclass Accounts:\n\n @endpoint(\"GET\", path=\"/{account_id}\", as_model=AccountInfoResponse)\n async def info(self, account_id: str) -> AccountInfoResponse:\n \"\"\"Retrieve account info (GET /accounts/{account_id})\"\"\"\n\n @endpoint(\"POST\", path=\"/_bulk\", as_model=BulkAccountsResponse)\n async def bulk_info(self, body: BulkAccountsRequest) -> BulkAccountsResponse:\n \"\"\"Retrieve info for multiple accounts (POST /accounts/_bulk)\"\"\"\n\n @endpoint(\"POST\", path=\"/_bulk\")\n async def bulk_info_dict(self, body: dict) -> dict:\n \"\"\"Retrieve info for multiple accounts (dict body) (POST /accounts/_bulk)\"\"\"\n```\n\nThen compose in your main client:\n\n```python\n@apiclient(\n base_url=\"https://tonapi.io\",\n headers={\"Authorization\": \"Bearer <YOUR_API_KEY>\"},\n version=\"v2\",\n rps=1,\n retries=2,\n)\nclass TONAPI:\n # ... endpoints above ...\n\n @property\n def accounts(self) -> Accounts:\n return Accounts(self)\n```\n\n**Note:**\n\n* You can use `\"accounts\"` or `\"/accounts\"` in `@apinamespace` \u2014 leading slash is optional and combined automatically\n with the endpoint path.\n\n---\n\n### 4. Usage\n\n```python\nasync def main():\n tonapi = TONAPI()\n\n async with tonapi:\n # Direct endpoint\n status = await tonapi.status()\n print(status)\n # Namespaced endpoint\n account = await tonapi.accounts.info(\"UQCDrgGaI6gWK-qlyw69xWZosurGxrpRgIgSkVsgahUtxZR0\")\n print(account)\n```\n\n---\n\n## API Configuration\n\nAll settings are passed to the `@apiclient` decorator:\n\n| Name | Type | Description | Default |\n|------------|-------|----------------------------------------------------|---------|\n| `base_url` | str | Base URL for your API (must start with http/https) | \u2014 |\n| `headers` | dict | Default headers (e.g. Authorization) | None |\n| `timeout` | float | Default timeout (seconds) | None |\n| `rps` | int | Max requests per second (rate limit) | 1 |\n| `retries` | int | Max retries for 429 (Too Many Requests) | 3 |\n| `cookies` | dict | Cookies to send with every request | None |\n\n---\n\n## Endpoints\n\n* Use `@endpoint` to mark methods as API endpoints.\n* All method arguments are automatically mapped to path or query parameters.\n* For `POST` and `PUT` requests, the `body` can be a Pydantic model or a dict.\n* The return type depends on `response_type` and the `as_model` argument.\n\n---\n\n### Notes\n\n* All endpoints and clients are **fully async**; always use `async with` for resource cleanup.\n* You may use flat client classes or split logic into namespaces as needed.\n* If `as_model` is not set in `@endpoint`, the raw dict (parsed JSON) is returned.\n* If `path` is omitted, the method name is used as the endpoint path (e.g., `status` \u2192 `/status`).\n\n---\n\n## Contribution\n\nWe welcome your contributions!\nIf you have ideas for improvement or find a bug, please create an issue or submit a pull request.\n\n---\n\n## License\n\nDistributed under the [MIT License](LICENSE).\nFeel free to use, modify, and distribute in accordance with the license.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Elegant async API client framework with intuitive decorators for quick integration",
"version": "0.0.2",
"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",
"http",
"aiohttp",
"pydantic"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "361a5e5163215ffc492e3c957030fbce31adca0fb58dc149d25957bae9d869b9",
"md5": "281e6579e3d023ced4d2ff5eeb8fc695",
"sha256": "2349dd4ed354f4863bc3753aa740a431e4cea6416fd1bf4210cd0da0a1da1441"
},
"downloads": -1,
"filename": "apiq-0.0.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "281e6579e3d023ced4d2ff5eeb8fc695",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 13952,
"upload_time": "2025-07-08T18:50:45",
"upload_time_iso_8601": "2025-07-08T18:50:45.961748Z",
"url": "https://files.pythonhosted.org/packages/36/1a/5e5163215ffc492e3c957030fbce31adca0fb58dc149d25957bae9d869b9/apiq-0.0.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "450fc049ba61a394749baf4cf19478ff7ee438c9847eb2dfe6fe1a91f1293132",
"md5": "a32e12e28f218e978f6f9f3ac3540d38",
"sha256": "326cb83668b8ac5c377e28c87d8411bffc7f26d3fff590f91cd993599d7773d9"
},
"downloads": -1,
"filename": "apiq-0.0.2.tar.gz",
"has_sig": false,
"md5_digest": "a32e12e28f218e978f6f9f3ac3540d38",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 13372,
"upload_time": "2025-07-08T18:50:47",
"upload_time_iso_8601": "2025-07-08T18:50:47.525594Z",
"url": "https://files.pythonhosted.org/packages/45/0f/c049ba61a394749baf4cf19478ff7ee438c9847eb2dfe6fe1a91f1293132/apiq-0.0.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-08 18:50:47",
"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.7.0"
],
[
"<=",
"3.12.2"
]
]
},
{
"name": "aiolimiter",
"specs": [
[
"~=",
"1.2.1"
]
]
},
{
"name": "pydantic",
"specs": [
[
"<",
"3.0"
],
[
">=",
"2.0"
]
]
}
],
"lcname": "apiq"
}