pyapiq


Namepyapiq JSON
Version 0.1.0 PyPI version JSON
download
home_pagehttps://github.com/nessshon/pyapiq
SummaryPyAPIq is a modern Python toolkit for building both synchronous and asynchronous API clients with clean, minimal code and full type safety.
upload_time2025-08-22 16:19:35
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.
            # 📦 PyAPIq

[![PyPI](https://img.shields.io/pypi/v/pyapiq.svg?color=FFE873\&labelColor=3776AB)](https://pypi.python.org/pypi/pyapiq)
![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/pyapiq)](LICENSE)

**PyAPIq** 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/pyapiq)
![Downloads](https://pepy.tech/badge/pyapiq/month)
![Downloads](https://pepy.tech/badge/pyapiq/week)

## Installation

```bash
pip install pyapiq
```

## 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 pyapiq import AsyncClientAPI, async_endpoint
from pyapiq.types import HTTPMethod


class AsyncTONAPI(AsyncClientAPI):
    base_url = "https://tonapi.io"
    headers = {"Authorization": "Bearer <YOUR_API_KEY>"}
    version = "v2"
    rps = 1
    max_retries = 2

    @async_endpoint(HTTPMethod.GET)
    async def status(self) -> dict:
        """Check API status (GET /status)"""

    @async_endpoint(HTTPMethod.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 pyapiq import AsyncAPINamespace, async_endpoint
from pyapiq.types import HTTPMethod


class Accounts(AsyncAPINamespace):
    namespace = "accounts"

    @async_endpoint(HTTPMethod.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(HTTPMethod.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(HTTPMethod.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/pyapiq",
    "name": "pyapiq",
    "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/b6/0d/9c9c52ba91ddb7b21cabeec06713111de11c47ac6c0e8549506debcf8008/pyapiq-0.1.0.tar.gz",
    "platform": null,
    "description": "# \ud83d\udce6 PyAPIq\n\n[![PyPI](https://img.shields.io/pypi/v/pyapiq.svg?color=FFE873\\&labelColor=3776AB)](https://pypi.python.org/pypi/pyapiq)\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/pyapiq)](LICENSE)\n\n**PyAPIq** 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/pyapiq)\n![Downloads](https://pepy.tech/badge/pyapiq/month)\n![Downloads](https://pepy.tech/badge/pyapiq/week)\n\n## Installation\n\n```bash\npip install pyapiq\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 pyapiq import AsyncClientAPI, async_endpoint\nfrom pyapiq.types import HTTPMethod\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(HTTPMethod.GET)\n    async def status(self) -> dict:\n        \"\"\"Check API status (GET /status)\"\"\"\n\n    @async_endpoint(HTTPMethod.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\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 pyapiq import AsyncAPINamespace, async_endpoint\nfrom pyapiq.types import HTTPMethod\n\n\nclass Accounts(AsyncAPINamespace):\n    namespace = \"accounts\"\n\n    @async_endpoint(HTTPMethod.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(HTTPMethod.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(HTTPMethod.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": "PyAPIq is a modern Python toolkit for building both synchronous and asynchronous API clients with clean, minimal code and full type safety.",
    "version": "0.1.0",
    "project_urls": {
        "Homepage": "https://github.com/nessshon/pyapiq",
        "Issues": "https://github.com/nessshon/pyapiq/issues",
        "Source": "https://github.com/nessshon/pyapiq"
    },
    "split_keywords": [
        "api",
        "client",
        "async",
        "sync",
        "http",
        "aiohttp",
        "requests",
        "pydantic"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d1857207e9b50639f1a276325b8196c3c40405778b04759a534e7f67baefe655",
                "md5": "389ed94526ddb53e42b4ca3d89c050de",
                "sha256": "be4f8fed3a8ef5a0b04a46c9e0c93b12c082d757b4eab17f26188e5a3528232a"
            },
            "downloads": -1,
            "filename": "pyapiq-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "389ed94526ddb53e42b4ca3d89c050de",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 17520,
            "upload_time": "2025-08-22T16:19:34",
            "upload_time_iso_8601": "2025-08-22T16:19:34.206824Z",
            "url": "https://files.pythonhosted.org/packages/d1/85/7207e9b50639f1a276325b8196c3c40405778b04759a534e7f67baefe655/pyapiq-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "b60d9c9c52ba91ddb7b21cabeec06713111de11c47ac6c0e8549506debcf8008",
                "md5": "cc6ed6fc970e23e8f65c94ecefe6793c",
                "sha256": "99455d94ea4c5c9c83c305a651da2b9019d80643dd20d9aa8393ea0635a70b26"
            },
            "downloads": -1,
            "filename": "pyapiq-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "cc6ed6fc970e23e8f65c94ecefe6793c",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 14939,
            "upload_time": "2025-08-22T16:19:35",
            "upload_time_iso_8601": "2025-08-22T16:19:35.770124Z",
            "url": "https://files.pythonhosted.org/packages/b6/0d/9c9c52ba91ddb7b21cabeec06713111de11c47ac6c0e8549506debcf8008/pyapiq-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-22 16:19:35",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "nessshon",
    "github_project": "pyapiq",
    "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": [
                [
                    "<",
                    "3.0"
                ],
                [
                    ">=",
                    "2.0"
                ]
            ]
        },
        {
            "name": "requests",
            "specs": [
                [
                    "~=",
                    "2.32.4"
                ]
            ]
        },
        {
            "name": "ratelimiter",
            "specs": [
                [
                    "~=",
                    "1.2.0.post0"
                ]
            ]
        }
    ],
    "lcname": "pyapiq"
}
        
Elapsed time: 1.67209s