apiq


Nameapiq JSON
Version 0.0.5 PyPI version JSON
download
home_pagehttps://github.com/nessshon/apiq
SummaryAPIQ is a modern Python toolkit for building both synchronous and asynchronous API clients with clean, minimal code and full type safety.
upload_time2025-08-01 00:31:45
maintainerNone
docs_urlNone
authornessshon
requires_python>=3.10
licenseMIT
keywords api client async sync http aiohttp requests pydantic
VCS
bugtrack_url
requirements aiohttp aiolimiter pydantic requests ratelimiter
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 📦 APIQ

[![PyPI](https://img.shields.io/pypi/v/apiq.svg?color=FFE873\&labelColor=3776AB)](https://pypi.python.org/pypi/apiq)
![Python Versions](https://img.shields.io/badge/Python-3.10%20--%203.12-black?color=FFE873\&labelColor=3776AB)
[![License](https://img.shields.io/github/license/nessshon/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.

![Downloads](https://pepy.tech/badge/apiq)
![Downloads](https://pepy.tech/badge/apiq/month)
![Downloads](https://pepy.tech/badge/apiq/week)

## 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}&currencies={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[![PyPI](https://img.shields.io/pypi/v/apiq.svg?color=FFE873\\&labelColor=3776AB)](https://pypi.python.org/pypi/apiq)\n![Python Versions](https://img.shields.io/badge/Python-3.10%20--%203.12-black?color=FFE873\\&labelColor=3776AB)\n[![License](https://img.shields.io/github/license/nessshon/apiq)](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![Downloads](https://pepy.tech/badge/apiq)\n![Downloads](https://pepy.tech/badge/apiq/month)\n![Downloads](https://pepy.tech/badge/apiq/week)\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}&currencies={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"
}
        
Elapsed time: 0.48535s