rAPIdy


NamerAPIdy JSON
Version 0.2.2 PyPI version JSON
download
home_pageNone
SummaryrAPIdy - write quickly - write beautifully
upload_time2024-05-11 12:21:52
maintainerNone
docs_urlNone
authorDaniil Grois
requires_python<4.0,>=3.8
licenseMIT
keywords rapidy aiohttp pydantic api fast http server daniil grois lev zaplatin
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            <p align="center">
    <a href="https://github.com/daniil-grois/rAPIdy" target="blank">
        <img src="docs/assets/logo-teal.png" alt="rAPIdy">
    </a>
</p>

<p align="center">
    <a href="https://pypi.org/project/rapidy" target="blank">
        <img src="https://img.shields.io/pypi/dm/rapidy?style=flat&logo=rapidy&logoColor=%237e56c2&color=%237e56c2" alt="Package downloads">
    </a>
    <a href="https://pypi.org/project/rapidy" target="blank">
        <img src="https://img.shields.io/pypi/v/rapidy?style=flat&logo=rapidy&logoColor=%237e56c2&color=%237e56c2&label=pypi%20rAPIdy" alt="Package version">
    </a>
    <a href="https://pypi.org/project/rapidy" target="blank">
        <img src="https://img.shields.io/pypi/pyversions/rapidy?style=flat&logo=rapidy&logoColor=%237e56c2&color=%237e56c2" alt="Python versions">
    </a>
    <a href="https://pypi.org/project/rapidy" target="blank">
        <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v1.json&logoColor=%237e56c2&color=%237e56c2" alt="Pydantic V1">
    </a>
    <a href="https://pypi.org/project/rapidy" target="blank">
        <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json&logoColor=%237e56c2&color=%237e56c2" alt="Pydantic V2">
    </a>
</p>

<p align="center">
    <i>
        <a href="https://github.com/daniil-grois/rAPIdy" target="blank">rAPIdy</a>
        - is a minimalist web framework for those who prioritize speed and convenience.<br/>
        Built upon <a href="https://github.com/aio-libs/aiohttp" target="blank">aiohttp</a> and <a href="https://github.com/pydantic/pydantic" target="blank">pydantic</a>,
        allowing you to fully leverage the advantages of this stack.
    </i>
</p>


# Key features

* ✏️ **Minimalism**: Retrieve and check data effortlessly using just a single line of code
* 🐍 **Native Python Support**: Offers seamless compatibility with Python native types
* 📔 **Pydantic Integration**: Fully integrated with <a href="https://github.com/pydantic/pydantic">pydantic</a> for robust data validation
* 🚀 **Powered by aiohttp**: Utilizes <a href="https://github.com/aio-libs/aiohttp">aiohttp</a> to leverage its powerful asynchronous features
* 📤 **Efficient Data Handling**: Simplifies the extraction of basic types from incoming data in Python


---
# Documentation
> [!TIP]
> Coming soon: 2024.06
---


# Installation
> [!NOTE]
> ```bash
> pip install rapidy
> ```
---

# Server
## Quickstart
### Handlers
```python
from rapidy import web

routes = web.RouteTableDef()

@routes.post('/')
async def handler(
        auth_token: str = web.Header(alias='Authorization'),
        username: str = web.JsonBody(),
        password: str = web.JsonBody(min_length=8),
) -> web.Response:
    print({'auth_token': auth_token, 'username': username, 'password': password})
    return web.json_response({'data': 'success'})

app = web.Application()
app.add_routes(routes)

if __name__ == '__main__':
    web.run_app(app, host='127.0.0.1', port=8080)
```

> [!NOTE]
> `rAPIdy` allows you to define handlers just like in <a href="https://docs.aiohttp.org/en/stable/web_quickstart.html" target="blank">aiohttp-quickstart</a>,
> but with a key improvement:
> the `request` parameter is no longer required for functional handlers
>
> If you need to access the current `request` within a handler,
> simply declare an attribute with any name of your choosing and specify its type as `web.Request`. rAPIdy will automatically replace this attribute with the current instance of `web.Request`.
```python
from typing import Annotated
from rapidy import web

routes = web.RouteTableDef()

@routes.get('/default_handler_example')
async def default_handler_example(
        request: str = web.Request,
) -> web.Response:
    print({'request': request})
    return web.json_response({'data': 'success'})

@routes.get('/handler_without_request_example')
async def handler_without_request_example() -> web.Response:
    return web.json_response({'data': 'success'})

@routes.get('/handler_request_as_snd_attr_example')
async def handler_request_as_snd_attr_example(
        host: Annotated[str, web.Header(alias='Host')],
        request: web.Request,
) -> web.Response:
    print({'host': host, 'request': request})
    return web.json_response({'data': 'success'})

app = web.Application()
app.add_routes(routes)

if __name__ == '__main__':
   web.run_app(app, host='127.0.0.1', port=8080)
```

### Middlewares
Processing an Authorization Token in Middleware

```python
from rapidy import web
from rapidy.typedefs import HandlerType

@web.middleware
async def hello_middleware(
        request: web.Request,
        handler: HandlerType,
        bearer_token: str = web.Header(alias='Authorization'),
) -> web.StreamResponse:
    request['token'] = bearer_token
    return await handler(request)

async def handler(
        request: web.Request,
        host: str = web.Header(alias='Host'),
        username: str = web.JsonBody(),
) -> web.Response:
    example_data = {'token': request['token'], 'host': host, 'username': username}
    return web.json_response(example_data)

app = web.Application(middlewares=[hello_middleware])
app.add_routes([web.post('/', handler)])

if __name__ == '__main__':
    web.run_app(app, host='127.0.0.1', port=8080)
```

> [!IMPORTANT]
> The first two attributes in a middleware are mandatory and must always represent the `request` and the `handler` respectively. These attributes are essential for the correct functioning of the middleware
---

## Request validation
`rAPIdy` utilizes custom parameters to validate incoming HTTP request data, ensuring integrity and compliance with expected formats

> [!TIP]
> A parameter in `rAPIdy` represents an object that mcontains meta-information about the type of data it retrieves

A parameter in `rAPIdy` offers all the functionalities of `pydantic.Field`, and even more. This means `rAPIdy-parameter` support every type of validation that `pydantic` provides, ensuring comprehensive data integrity and conformity

```python
from decimal import Decimal
from pydantic import BaseModel, Field
from rapidy import web

routes = web.RouteTableDef()

class Schema(BaseModel):
    positive: int = Field(gt=0)
    non_negative: int = Field(ge=0)
    negative: int = Field(lt=0)
    non_positive: int = Field(le=0)
    even: int = Field(multiple_of=2)
    love_for_pydantic: float = Field(allow_inf_nan=True)
    short: str = Field(min_length=3)
    long: str = Field(max_length=10)
    regex: str = Field(pattern=r'^\d*$')
    precise: Decimal = Field(max_digits=5, decimal_places=2)

@routes.get('/')
async def handler(
    positive: int = web.JsonBody(gt=0),
    non_negative: int = web.JsonBody(ge=0),
    negative: int = web.JsonBody(lt=0),
    non_positive: int = web.JsonBody(le=0),
    even: int = web.JsonBody(multiple_of=2),
    love_for_pydantic: float = web.JsonBody(allow_inf_nan=True),
    short: str = web.JsonBody(min_length=3),
    long: str = web.JsonBody(max_length=10),
    regex: str = web.JsonBody(pattern=r'^\d*$'),
    precise: Decimal = web.JsonBody(max_digits=5, decimal_places=2),
) -> web.Response:
    return web.Response()

@routes.get('/schema')
async def handler_schema(
    body: Schema = web.JsonBodySchema(),
) -> web.Response:
    return web.Response()

app = web.Application()
app.add_routes(routes)

if __name__ == '__main__':
    web.run_app(app, host='127.0.0.1', port=8080)
```

### Validation Example
```python
from rapidy import web
from pydantic import BaseModel, Field

routes = web.RouteTableDef()

class BodyRequestSchema(BaseModel):
    username: str = Field(min_length=3, max_length=20)
    password: str = Field(min_length=8, max_length=40)

@routes.post('/api/{user_id}')
async def handler(
        user_id: str = web.Path(),
        host: str = web.Header(alias='Host'),
        body: BodyRequestSchema = web.JsonBodySchema(),
) -> web.Response:
    return web.json_response({'data': 'success'})

app = web.Application()
app.add_routes(routes)

if __name__ == '__main__':
    web.run_app(app, host='127.0.0.1', port=8080)
```

#### Success request validation
```
curl -X POST \
-H "Content-Type: application/json" -d '{"username": "User", "password": "myAwesomePass"}' -v http://127.0.0.1:8080/api/1

< HTTP/1.1 200 OK ... {"data": "success"}
```

#### Failed request validation

```
curl -X POST \
-H "Content-Type: application/json" -d '{"username": "U", "password": "m"}' -v http://127.0.0.1:8080/api/1

< HTTP/1.1 422 Unprocessable Entity ...
{
    "errors": [
        {
            "loc": ["body", "username"],
            "type": "string_too_short",
            "msg": "String should have at least 3 characters",
            "ctx": {"min_length": 3}
        },
        {
            "type": "string_too_short",
            "loc": ["body", "password"],
            "msg": "String should have at least 8 characters",
            "ctx": {"min_length": 8}
        }
    ]
}
```

### Types of request parameters
`rAPIdy` supports 3 basic types for defining incoming parameters:
* Param
* Schema
* Raw data

#### Single parameter
`Single` parameter, used when you need to spot-retrieve incoming data.

```python
from rapidy import web

async def handler(
        path_param: str = web.Path(),
        # headers
        host: str = web.Header(alias='Host'),
        user_agent: str = web.Header(alias='User-Agent'),
        # cookie
        user_cookie1: str = web.Cookie(alias='UserCookie1'),
        user_cookie2: str = web.Cookie(alias='UserCookie2'),
        # query params
        user_param1: str = web.Query(alias='UserQueryParam1'),
        user_param2: str = web.Cookie(alias='UserQueryParam2'),
        # body
        username: str = web.JsonBody(min_length=3, max_length=20),
        password: str = web.JsonBody(min_length=8, max_length=40),
) -> web.Response:
    # write your code here
    # ...
    return web.Response()

app = web.Application()
app.add_routes([web.post('/api/{path_param}', handler)])
```
> [!NOTE]
> All single parameters
> * Path
> * Header
> * Cookie
> * Query
> * BodyJson
> * FormDataBody
> * MultipartBody

#### Schema
`Schema-parameter` is useful when you want to extract a large amount of data.

```python
from rapidy import web
from pydantic import BaseModel, Field

class PathRequestSchema(BaseModel):
    path_param: str

class HeaderRequestSchema(BaseModel):
    host: str = Field(alias='Host')
    user_agent: str = Field(alias='User-Agent')

class CookieRequestSchema(BaseModel):
    user_cookie1: str = Field(alias='UserCookie1')
    user_cookie2: str = Field(alias='UserCookie2')

class QueryRequestSchema(BaseModel):
    user_cookie1: str = Field(alias='UserQueryParam1')
    user_cookie2: str = Field(alias='UserQueryParam1')

class BodyRequestSchema(BaseModel):
    username: str = Field(min_length=3, max_length=20)
    password: str = Field(min_length=8, max_length=40)

async def handler(
        path: PathRequestSchema = web.PathSchema(),
        headers: HeaderRequestSchema = web.HeaderSchema(),
        cookies: CookieRequestSchema = web.Cookie(),
        query: QueryRequestSchema = web.QuerySchema(),
        body: BodyRequestSchema = web.JsonBodySchema(),
) -> web.Response:
    # write your code here
    # ...
    return web.Response()

app = web.Application()
app.add_routes([web.post('/api/{path_param}', handler)])
```
> [!NOTE]
> All schema parameters
> * PathSchema
> * HeaderSchema
> * CookieSchema
> * QuerySchema
> * BodyJsonSchema
> * FormDataBodySchema
> * MultipartBodySchema

#### Raw
Use `Raw-parameter` when you don't need validation.

```python
from typing import Any
from rapidy import web

async def handler(
        path: dict[str, str] = web.PathRaw,
        headers: dict[str, str] = web.HeaderRaw,
        cookies: dict[str, str] =  web.CookieRaw,
        query: dict[str, str] = web.QueryRaw,
        body: dict[str, Any] = web.JsonBodyRaw,
) -> web.Response:
    # write your code here
    # ...
    return web.Response()

app = web.Application()
app.add_routes([web.post('/api/{path_param}', handler)])
```
> [!NOTE]
> All raw parameters
> * PathRaw - `dict[str, str]`
> * HeaderRaw - `dict[str, str]`
> * CookieRaw - `dict[str, str]`
> * QueryRaw - `dict[str, str]`
> * BodyJsonRaw - `dict[str, Any]`
> * FormDataBodyRaw - `dict[str, str]` or `dict[str, list[str]]`
> * MultipartBodyRaw - `dict[str, Any]` or `dict[str, list[Any]]`
> * TextBody - `str`
> * BytesBody - `bytes`
> * StreamBody - `aiohttp.streams.StreamReader`


#### Combining Different Approaches
```python
async def handler(
        path_param: str = web.Path(),
        headers: dict[str, str] = web.HeaderRaw(),
        body: BodyRequestSchema = web.JsonBodySchema(),
) -> web.Response:
```

### Ways to define metadata for a query parameter
There are a total of two ways to define query parameters.

1. **Using the Annotated Auxiliary Type**: Specify metadata directly within the Annotated type
2. **Using the Default Attribute Value**: Define the query parameter's default value along with its metadata


```python
from typing import Annotated  # use typing_extensions if py version == 3.8
from rapidy import web

async def handler(
        param_1: Annotated[str, web.JsonBody()],  # Annotated definition
        param_2: str = web.JsonBody(),  # Default definition
) -> web.Response:
```

### Special attributes

Some `rAPIdy-parameters` include additional attributes that are not found in `Pydantic`, offering extended functionality beyond standard data validation

#### Body
 Currently, only parameters of the body type include special attributes in `rAPIdy`

> [!WARNING]
> Additional attributes can only be specified for `Schema-parameters` and `Raw-parameters`.

In `rAPIdy`, the `body_max_size` attribute associated with each body parameter restricts the maximum allowable size of the request `body` for a specific handler

`body_max_size` (_int_) - indicating the maximum number of bytes the handler expects.

```python
async def handler(
        body: str = web.JsonBodySchema(body_max_size=10),
) -> web.Response:
```
```python
async def handler(
        body: str = web.JsonBodyRaw(body_max_size=10),
) -> web.Response:
```

##### Json
`json_decoder` (_typing.Callable[[], Any]_) - attribute that accepts the function to be called when decoding the body of the incoming request.

##### FormData
`attrs_case_sensitive` (_bool_) -  attribute that tells the data extractor whether the incoming key register should be considered.

`duplicated_attrs_parse_as_array` (_bool_) - attribute that tells the data extractor what to do with duplicated keys in a query.

If duplicated_attrs_parse_as_array=True, a list will be created for each key and all values will be placed in it.

> [!NOTE]
> `duplicated_attrs_parse_as_array` flat changes the type of data that the data extractor returns.
>
> If duplicated_attrs_parse_as_array=`True`, then the data
> will always be of type _dict[str, list[str]]_ (_by default, `formdata` has the extractable type dict[str, str]_)

##### Multipart

`attrs_case_sensitive` (_bool_) -  attribute that tells the data extractor whether the incoming key register should be considered.

`duplicated_attrs_parse_as_array` (_bool_) - attribute that tells the data extractor what to do with duplicated keys in a query.

> [!NOTE]
> `duplicated_attrs_parse_as_array` flat changes the type of data that the data extractor returns.
>
> If duplicated_attrs_parse_as_array=`True`, then the data
> will always be of type _dict[str, list[Any]]_ (_by default, `multipart` has the extractable type dict[str, Any]_)

---

### Catch client errors
The `HTTPValidationFailure` exception will be raised if the data in the query is incorrect.<br>
This error can be caught with a try/except block. The error values can be accessed using the `validation_errors` attribute.

This may be necessary, for example, if you need to log a customer error before giving them a response.

```python
import logging
from rapidy import web
from rapidy.typedefs import Handler
from rapidy.web_exceptions import HTTPValidationFailure

logger = logging.getLogger(__name__)

routes = web.RouteTableDef()

@routes.get('/')
async def handler(
        auth_token: str = web.Header(alias='Authorization'),
) -> web.Response:
    return web.json_response({'data': 'success'})

@web.middleware
async def error_catch_middleware(request: web.Request, handler: Handler) -> web.StreamResponse:
    try:
        return await handler(request)

    except HTTPValidationFailure as validation_failure_error:
        client_errors = validation_failure_error.validation_errors
        logger.error('Client error catch, errors: %s', client_errors)
        raise validation_failure_error

    except Exception as unhandled_error:
        logger.error('Unhandled error: %s', unhandled_error)
        return web.json_response(status=500)

app = web.Application(middlewares=[error_catch_middleware])
app.add_routes(routes)

if __name__ == '__main__':
    web.run_app(app, host='127.0.0.1', port=8080)
```

## Default values for parameters
Some <span style="color:#7e56c2">rAPIdy</span> parameters may contain default values.

```python
async def handler(
        header_param_1: str = web.Header('default'),
        header_param_2: Annotated[str, web.Header()] = 'default',

        cookie_param_1: str = web.Cookie('default'),
        cookie_param_2: Annotated[str, web.Cookie()] = 'default',

        query_param_1: str = web.Query('default'),
        query_param_2: Annotated[str, web.Query()] = 'default',

        json_param_1: str = web.JsonBody('default'),
        json_param_2: Annotated[str, web.JsonBody()] = 'default',
) -> web.Response:
```
> [!NOTE]
> Default values support some single parameters and schemes
>
> Raw and Path parameters cannot have default values.
>
> Some body types do not contain `Raw` in their name, but they are also parameters that receive raw data, such as `StreamBody` or `TextBody`.
---


# Client
> [!TIP]
> Coming soon: 2024.09
---


# OpenAPI
> [!TIP]
> Already in development
>
> Coming soon: 2024.09
---


# Mypy support
`rAPIdy` has its own plugin for <a href="https://mypy.readthedocs.io/en/stable/getting_started.html" target="blank">mypy</a>.

```toml
# example for pyproject.toml
# ...
[tool.mypy]
plugins = [
    "pydantic.mypy",
    "rapidy.mypy"
]
# ...
```
---


# Migration from aiohttp to rAPIdy
`rAPIdy` neatly extends `aiohttp` - meaning that anything already written in `aiohttp` will work as before without any modifications

`rAPIdy` has exactly the same overridden module names as `aiohttp`.

> [!WARNING]
> `rAPIdy` does not override all `aiohttp` modules, only those that are necessary for it to work, or those that will be extended in the near future.

If the `aiohttp` module you are trying to override is not found in `rAPIdy`, don't change it, everything will work as is.

> [!WARNING]
> `rAPIdy` supports defining handlers in the same way as <a href="https://docs.aiohttp.org/en/stable/web_quickstart.html" target="blank">aiohttp-quickstart</a> except that request is no longer a required parameter for functional handlers.
>
> If you need to get the current `request` in the handler,
> create an attribute with an arbitrary name and be sure to specify the `web.Request` type, and rAPIdy will substitute the current `web.Request` instance in that place.
---


# For Developers
> [!TIP]
> Coming soon: 2024.06

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "rAPIdy",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.8",
    "maintainer_email": null,
    "keywords": "rAPIdy, aiohttp, pydantic, api, fast, http server, Daniil Grois, Lev Zaplatin",
    "author": "Daniil Grois",
    "author_email": "daniil.grois@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/52/12/25c465d6d5bef6ab2c19d57760929f448e69ecb35a4b0b12fa2e98594e90/rapidy-0.2.2.tar.gz",
    "platform": null,
    "description": "<p align=\"center\">\n    <a href=\"https://github.com/daniil-grois/rAPIdy\" target=\"blank\">\n        <img src=\"docs/assets/logo-teal.png\" alt=\"rAPIdy\">\n    </a>\n</p>\n\n<p align=\"center\">\n    <a href=\"https://pypi.org/project/rapidy\" target=\"blank\">\n        <img src=\"https://img.shields.io/pypi/dm/rapidy?style=flat&logo=rapidy&logoColor=%237e56c2&color=%237e56c2\" alt=\"Package downloads\">\n    </a>\n    <a href=\"https://pypi.org/project/rapidy\" target=\"blank\">\n        <img src=\"https://img.shields.io/pypi/v/rapidy?style=flat&logo=rapidy&logoColor=%237e56c2&color=%237e56c2&label=pypi%20rAPIdy\" alt=\"Package version\">\n    </a>\n    <a href=\"https://pypi.org/project/rapidy\" target=\"blank\">\n        <img src=\"https://img.shields.io/pypi/pyversions/rapidy?style=flat&logo=rapidy&logoColor=%237e56c2&color=%237e56c2\" alt=\"Python versions\">\n    </a>\n    <a href=\"https://pypi.org/project/rapidy\" target=\"blank\">\n        <img src=\"https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v1.json&logoColor=%237e56c2&color=%237e56c2\" alt=\"Pydantic V1\">\n    </a>\n    <a href=\"https://pypi.org/project/rapidy\" target=\"blank\">\n        <img src=\"https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json&logoColor=%237e56c2&color=%237e56c2\" alt=\"Pydantic V2\">\n    </a>\n</p>\n\n<p align=\"center\">\n    <i>\n        <a href=\"https://github.com/daniil-grois/rAPIdy\" target=\"blank\">rAPIdy</a>\n        - is a minimalist web framework for those who prioritize speed and convenience.<br/>\n        Built upon <a href=\"https://github.com/aio-libs/aiohttp\" target=\"blank\">aiohttp</a> and <a href=\"https://github.com/pydantic/pydantic\" target=\"blank\">pydantic</a>,\n        allowing you to fully leverage the advantages of this stack.\n    </i>\n</p>\n\n\n# Key features\n\n* \u270f\ufe0f **Minimalism**: Retrieve and check data effortlessly using just a single line of code\n* \ud83d\udc0d **Native Python Support**: Offers seamless compatibility with Python native types\n* \ud83d\udcd4 **Pydantic Integration**: Fully integrated with <a href=\"https://github.com/pydantic/pydantic\">pydantic</a> for robust data validation\n* \ud83d\ude80 **Powered by aiohttp**: Utilizes <a href=\"https://github.com/aio-libs/aiohttp\">aiohttp</a> to leverage its powerful asynchronous features\n* \ud83d\udce4 **Efficient Data Handling**: Simplifies the extraction of basic types from incoming data in Python\n\n\n---\n# Documentation\n> [!TIP]\n> Coming soon: 2024.06\n---\n\n\n# Installation\n> [!NOTE]\n> ```bash\n> pip install rapidy\n> ```\n---\n\n# Server\n## Quickstart\n### Handlers\n```python\nfrom rapidy import web\n\nroutes = web.RouteTableDef()\n\n@routes.post('/')\nasync def handler(\n        auth_token: str = web.Header(alias='Authorization'),\n        username: str = web.JsonBody(),\n        password: str = web.JsonBody(min_length=8),\n) -> web.Response:\n    print({'auth_token': auth_token, 'username': username, 'password': password})\n    return web.json_response({'data': 'success'})\n\napp = web.Application()\napp.add_routes(routes)\n\nif __name__ == '__main__':\n    web.run_app(app, host='127.0.0.1', port=8080)\n```\n\n> [!NOTE]\n> `rAPIdy` allows you to define handlers just like in <a href=\"https://docs.aiohttp.org/en/stable/web_quickstart.html\" target=\"blank\">aiohttp-quickstart</a>,\n> but with a key improvement:\n> the `request` parameter is no longer required for functional handlers\n>\n> If you need to access the current `request` within a handler,\n> simply declare an attribute with any name of your choosing and specify its type as `web.Request`. rAPIdy will automatically replace this attribute with the current instance of `web.Request`.\n```python\nfrom typing import Annotated\nfrom rapidy import web\n\nroutes = web.RouteTableDef()\n\n@routes.get('/default_handler_example')\nasync def default_handler_example(\n        request: str = web.Request,\n) -> web.Response:\n    print({'request': request})\n    return web.json_response({'data': 'success'})\n\n@routes.get('/handler_without_request_example')\nasync def handler_without_request_example() -> web.Response:\n    return web.json_response({'data': 'success'})\n\n@routes.get('/handler_request_as_snd_attr_example')\nasync def handler_request_as_snd_attr_example(\n        host: Annotated[str, web.Header(alias='Host')],\n        request: web.Request,\n) -> web.Response:\n    print({'host': host, 'request': request})\n    return web.json_response({'data': 'success'})\n\napp = web.Application()\napp.add_routes(routes)\n\nif __name__ == '__main__':\n   web.run_app(app, host='127.0.0.1', port=8080)\n```\n\n### Middlewares\nProcessing an Authorization Token in Middleware\n\n```python\nfrom rapidy import web\nfrom rapidy.typedefs import HandlerType\n\n@web.middleware\nasync def hello_middleware(\n        request: web.Request,\n        handler: HandlerType,\n        bearer_token: str = web.Header(alias='Authorization'),\n) -> web.StreamResponse:\n    request['token'] = bearer_token\n    return await handler(request)\n\nasync def handler(\n        request: web.Request,\n        host: str = web.Header(alias='Host'),\n        username: str = web.JsonBody(),\n) -> web.Response:\n    example_data = {'token': request['token'], 'host': host, 'username': username}\n    return web.json_response(example_data)\n\napp = web.Application(middlewares=[hello_middleware])\napp.add_routes([web.post('/', handler)])\n\nif __name__ == '__main__':\n    web.run_app(app, host='127.0.0.1', port=8080)\n```\n\n> [!IMPORTANT]\n> The first two attributes in a middleware are mandatory and must always represent the `request` and the `handler` respectively. These attributes are essential for the correct functioning of the middleware\n---\n\n## Request validation\n`rAPIdy` utilizes custom parameters to validate incoming HTTP request data, ensuring integrity and compliance with expected formats\n\n> [!TIP]\n> A parameter in `rAPIdy` represents an object that mcontains meta-information about the type of data it retrieves\n\nA parameter in `rAPIdy` offers all the functionalities of `pydantic.Field`, and even more. This means `rAPIdy-parameter` support every type of validation that `pydantic` provides, ensuring comprehensive data integrity and conformity\n\n```python\nfrom decimal import Decimal\nfrom pydantic import BaseModel, Field\nfrom rapidy import web\n\nroutes = web.RouteTableDef()\n\nclass Schema(BaseModel):\n    positive: int = Field(gt=0)\n    non_negative: int = Field(ge=0)\n    negative: int = Field(lt=0)\n    non_positive: int = Field(le=0)\n    even: int = Field(multiple_of=2)\n    love_for_pydantic: float = Field(allow_inf_nan=True)\n    short: str = Field(min_length=3)\n    long: str = Field(max_length=10)\n    regex: str = Field(pattern=r'^\\d*$')\n    precise: Decimal = Field(max_digits=5, decimal_places=2)\n\n@routes.get('/')\nasync def handler(\n    positive: int = web.JsonBody(gt=0),\n    non_negative: int = web.JsonBody(ge=0),\n    negative: int = web.JsonBody(lt=0),\n    non_positive: int = web.JsonBody(le=0),\n    even: int = web.JsonBody(multiple_of=2),\n    love_for_pydantic: float = web.JsonBody(allow_inf_nan=True),\n    short: str = web.JsonBody(min_length=3),\n    long: str = web.JsonBody(max_length=10),\n    regex: str = web.JsonBody(pattern=r'^\\d*$'),\n    precise: Decimal = web.JsonBody(max_digits=5, decimal_places=2),\n) -> web.Response:\n    return web.Response()\n\n@routes.get('/schema')\nasync def handler_schema(\n    body: Schema = web.JsonBodySchema(),\n) -> web.Response:\n    return web.Response()\n\napp = web.Application()\napp.add_routes(routes)\n\nif __name__ == '__main__':\n    web.run_app(app, host='127.0.0.1', port=8080)\n```\n\n### Validation Example\n```python\nfrom rapidy import web\nfrom pydantic import BaseModel, Field\n\nroutes = web.RouteTableDef()\n\nclass BodyRequestSchema(BaseModel):\n    username: str = Field(min_length=3, max_length=20)\n    password: str = Field(min_length=8, max_length=40)\n\n@routes.post('/api/{user_id}')\nasync def handler(\n        user_id: str = web.Path(),\n        host: str = web.Header(alias='Host'),\n        body: BodyRequestSchema = web.JsonBodySchema(),\n) -> web.Response:\n    return web.json_response({'data': 'success'})\n\napp = web.Application()\napp.add_routes(routes)\n\nif __name__ == '__main__':\n    web.run_app(app, host='127.0.0.1', port=8080)\n```\n\n#### Success request validation\n```\ncurl -X POST \\\n-H \"Content-Type: application/json\" -d '{\"username\": \"User\", \"password\": \"myAwesomePass\"}' -v http://127.0.0.1:8080/api/1\n\n< HTTP/1.1 200 OK ... {\"data\": \"success\"}\n```\n\n#### Failed request validation\n\n```\ncurl -X POST \\\n-H \"Content-Type: application/json\" -d '{\"username\": \"U\", \"password\": \"m\"}' -v http://127.0.0.1:8080/api/1\n\n< HTTP/1.1 422 Unprocessable Entity ...\n{\n    \"errors\": [\n        {\n            \"loc\": [\"body\", \"username\"],\n            \"type\": \"string_too_short\",\n            \"msg\": \"String should have at least 3 characters\",\n            \"ctx\": {\"min_length\": 3}\n        },\n        {\n            \"type\": \"string_too_short\",\n            \"loc\": [\"body\", \"password\"],\n            \"msg\": \"String should have at least 8 characters\",\n            \"ctx\": {\"min_length\": 8}\n        }\n    ]\n}\n```\n\n### Types of request parameters\n`rAPIdy` supports 3 basic types for defining incoming parameters:\n* Param\n* Schema\n* Raw data\n\n#### Single parameter\n`Single` parameter, used when you need to spot-retrieve incoming data.\n\n```python\nfrom rapidy import web\n\nasync def handler(\n        path_param: str = web.Path(),\n        # headers\n        host: str = web.Header(alias='Host'),\n        user_agent: str = web.Header(alias='User-Agent'),\n        # cookie\n        user_cookie1: str = web.Cookie(alias='UserCookie1'),\n        user_cookie2: str = web.Cookie(alias='UserCookie2'),\n        # query params\n        user_param1: str = web.Query(alias='UserQueryParam1'),\n        user_param2: str = web.Cookie(alias='UserQueryParam2'),\n        # body\n        username: str = web.JsonBody(min_length=3, max_length=20),\n        password: str = web.JsonBody(min_length=8, max_length=40),\n) -> web.Response:\n    # write your code here\n    # ...\n    return web.Response()\n\napp = web.Application()\napp.add_routes([web.post('/api/{path_param}', handler)])\n```\n> [!NOTE]\n> All single parameters\n> * Path\n> * Header\n> * Cookie\n> * Query\n> * BodyJson\n> * FormDataBody\n> * MultipartBody\n\n#### Schema\n`Schema-parameter` is useful when you want to extract a large amount of data.\n\n```python\nfrom rapidy import web\nfrom pydantic import BaseModel, Field\n\nclass PathRequestSchema(BaseModel):\n    path_param: str\n\nclass HeaderRequestSchema(BaseModel):\n    host: str = Field(alias='Host')\n    user_agent: str = Field(alias='User-Agent')\n\nclass CookieRequestSchema(BaseModel):\n    user_cookie1: str = Field(alias='UserCookie1')\n    user_cookie2: str = Field(alias='UserCookie2')\n\nclass QueryRequestSchema(BaseModel):\n    user_cookie1: str = Field(alias='UserQueryParam1')\n    user_cookie2: str = Field(alias='UserQueryParam1')\n\nclass BodyRequestSchema(BaseModel):\n    username: str = Field(min_length=3, max_length=20)\n    password: str = Field(min_length=8, max_length=40)\n\nasync def handler(\n        path: PathRequestSchema = web.PathSchema(),\n        headers: HeaderRequestSchema = web.HeaderSchema(),\n        cookies: CookieRequestSchema = web.Cookie(),\n        query: QueryRequestSchema = web.QuerySchema(),\n        body: BodyRequestSchema = web.JsonBodySchema(),\n) -> web.Response:\n    # write your code here\n    # ...\n    return web.Response()\n\napp = web.Application()\napp.add_routes([web.post('/api/{path_param}', handler)])\n```\n> [!NOTE]\n> All schema parameters\n> * PathSchema\n> * HeaderSchema\n> * CookieSchema\n> * QuerySchema\n> * BodyJsonSchema\n> * FormDataBodySchema\n> * MultipartBodySchema\n\n#### Raw\nUse `Raw-parameter` when you don't need validation.\n\n```python\nfrom typing import Any\nfrom rapidy import web\n\nasync def handler(\n        path: dict[str, str] = web.PathRaw,\n        headers: dict[str, str] = web.HeaderRaw,\n        cookies: dict[str, str] =  web.CookieRaw,\n        query: dict[str, str] = web.QueryRaw,\n        body: dict[str, Any] = web.JsonBodyRaw,\n) -> web.Response:\n    # write your code here\n    # ...\n    return web.Response()\n\napp = web.Application()\napp.add_routes([web.post('/api/{path_param}', handler)])\n```\n> [!NOTE]\n> All raw parameters\n> * PathRaw - `dict[str, str]`\n> * HeaderRaw - `dict[str, str]`\n> * CookieRaw - `dict[str, str]`\n> * QueryRaw - `dict[str, str]`\n> * BodyJsonRaw - `dict[str, Any]`\n> * FormDataBodyRaw - `dict[str, str]` or `dict[str, list[str]]`\n> * MultipartBodyRaw - `dict[str, Any]` or `dict[str, list[Any]]`\n> * TextBody - `str`\n> * BytesBody - `bytes`\n> * StreamBody - `aiohttp.streams.StreamReader`\n\n\n#### Combining Different Approaches\n```python\nasync def handler(\n        path_param: str = web.Path(),\n        headers: dict[str, str] = web.HeaderRaw(),\n        body: BodyRequestSchema = web.JsonBodySchema(),\n) -> web.Response:\n```\n\n### Ways to define metadata for a query parameter\nThere are a total of two ways to define query parameters.\n\n1. **Using the Annotated Auxiliary Type**: Specify metadata directly within the Annotated type\n2. **Using the Default Attribute Value**: Define the query parameter's default value along with its metadata\n\n\n```python\nfrom typing import Annotated  # use typing_extensions if py version == 3.8\nfrom rapidy import web\n\nasync def handler(\n        param_1: Annotated[str, web.JsonBody()],  # Annotated definition\n        param_2: str = web.JsonBody(),  # Default definition\n) -> web.Response:\n```\n\n### Special attributes\n\nSome `rAPIdy-parameters` include additional attributes that are not found in `Pydantic`, offering extended functionality beyond standard data validation\n\n#### Body\n Currently, only parameters of the body type include special attributes in `rAPIdy`\n\n> [!WARNING]\n> Additional attributes can only be specified for `Schema-parameters` and `Raw-parameters`.\n\nIn `rAPIdy`, the `body_max_size` attribute associated with each body parameter restricts the maximum allowable size of the request `body` for a specific handler\n\n`body_max_size` (_int_) - indicating the maximum number of bytes the handler expects.\n\n```python\nasync def handler(\n        body: str = web.JsonBodySchema(body_max_size=10),\n) -> web.Response:\n```\n```python\nasync def handler(\n        body: str = web.JsonBodyRaw(body_max_size=10),\n) -> web.Response:\n```\n\n##### Json\n`json_decoder` (_typing.Callable[[], Any]_) - attribute that accepts the function to be called when decoding the body of the incoming request.\n\n##### FormData\n`attrs_case_sensitive` (_bool_) -  attribute that tells the data extractor whether the incoming key register should be considered.\n\n`duplicated_attrs_parse_as_array` (_bool_) - attribute that tells the data extractor what to do with duplicated keys in a query.\n\nIf duplicated_attrs_parse_as_array=True, a list will be created for each key and all values will be placed in it.\n\n> [!NOTE]\n> `duplicated_attrs_parse_as_array` flat changes the type of data that the data extractor returns.\n>\n> If duplicated_attrs_parse_as_array=`True`, then the data\n> will always be of type _dict[str, list[str]]_ (_by default, `formdata` has the extractable type dict[str, str]_)\n\n##### Multipart\n\n`attrs_case_sensitive` (_bool_) -  attribute that tells the data extractor whether the incoming key register should be considered.\n\n`duplicated_attrs_parse_as_array` (_bool_) - attribute that tells the data extractor what to do with duplicated keys in a query.\n\n> [!NOTE]\n> `duplicated_attrs_parse_as_array` flat changes the type of data that the data extractor returns.\n>\n> If duplicated_attrs_parse_as_array=`True`, then the data\n> will always be of type _dict[str, list[Any]]_ (_by default, `multipart` has the extractable type dict[str, Any]_)\n\n---\n\n### Catch client errors\nThe `HTTPValidationFailure` exception will be raised if the data in the query is incorrect.<br>\nThis error can be caught with a try/except block. The error values can be accessed using the `validation_errors` attribute.\n\nThis may be necessary, for example, if you need to log a customer error before giving them a response.\n\n```python\nimport logging\nfrom rapidy import web\nfrom rapidy.typedefs import Handler\nfrom rapidy.web_exceptions import HTTPValidationFailure\n\nlogger = logging.getLogger(__name__)\n\nroutes = web.RouteTableDef()\n\n@routes.get('/')\nasync def handler(\n        auth_token: str = web.Header(alias='Authorization'),\n) -> web.Response:\n    return web.json_response({'data': 'success'})\n\n@web.middleware\nasync def error_catch_middleware(request: web.Request, handler: Handler) -> web.StreamResponse:\n    try:\n        return await handler(request)\n\n    except HTTPValidationFailure as validation_failure_error:\n        client_errors = validation_failure_error.validation_errors\n        logger.error('Client error catch, errors: %s', client_errors)\n        raise validation_failure_error\n\n    except Exception as unhandled_error:\n        logger.error('Unhandled error: %s', unhandled_error)\n        return web.json_response(status=500)\n\napp = web.Application(middlewares=[error_catch_middleware])\napp.add_routes(routes)\n\nif __name__ == '__main__':\n    web.run_app(app, host='127.0.0.1', port=8080)\n```\n\n## Default values for parameters\nSome <span style=\"color:#7e56c2\">rAPIdy</span> parameters may contain default values.\n\n```python\nasync def handler(\n        header_param_1: str = web.Header('default'),\n        header_param_2: Annotated[str, web.Header()] = 'default',\n\n        cookie_param_1: str = web.Cookie('default'),\n        cookie_param_2: Annotated[str, web.Cookie()] = 'default',\n\n        query_param_1: str = web.Query('default'),\n        query_param_2: Annotated[str, web.Query()] = 'default',\n\n        json_param_1: str = web.JsonBody('default'),\n        json_param_2: Annotated[str, web.JsonBody()] = 'default',\n) -> web.Response:\n```\n> [!NOTE]\n> Default values support some single parameters and schemes\n>\n> Raw and Path parameters cannot have default values.\n>\n> Some body types do not contain `Raw` in their name, but they are also parameters that receive raw data, such as `StreamBody` or `TextBody`.\n---\n\n\n# Client\n> [!TIP]\n> Coming soon: 2024.09\n---\n\n\n# OpenAPI\n> [!TIP]\n> Already in development\n>\n> Coming soon: 2024.09\n---\n\n\n# Mypy support\n`rAPIdy` has its own plugin for <a href=\"https://mypy.readthedocs.io/en/stable/getting_started.html\" target=\"blank\">mypy</a>.\n\n```toml\n# example for pyproject.toml\n# ...\n[tool.mypy]\nplugins = [\n    \"pydantic.mypy\",\n    \"rapidy.mypy\"\n]\n# ...\n```\n---\n\n\n# Migration from aiohttp to rAPIdy\n`rAPIdy` neatly extends `aiohttp` - meaning that anything already written in `aiohttp` will work as before without any modifications\n\n`rAPIdy` has exactly the same overridden module names as `aiohttp`.\n\n> [!WARNING]\n> `rAPIdy` does not override all `aiohttp` modules, only those that are necessary for it to work, or those that will be extended in the near future.\n\nIf the `aiohttp` module you are trying to override is not found in `rAPIdy`, don't change it, everything will work as is.\n\n> [!WARNING]\n> `rAPIdy` supports defining handlers in the same way as <a href=\"https://docs.aiohttp.org/en/stable/web_quickstart.html\" target=\"blank\">aiohttp-quickstart</a> except that request is no longer a required parameter for functional handlers.\n>\n> If you need to get the current `request` in the handler,\n> create an attribute with an arbitrary name and be sure to specify the `web.Request` type, and rAPIdy will substitute the current `web.Request` instance in that place.\n---\n\n\n# For Developers\n> [!TIP]\n> Coming soon: 2024.06\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "rAPIdy - write quickly - write beautifully",
    "version": "0.2.2",
    "project_urls": null,
    "split_keywords": [
        "rapidy",
        " aiohttp",
        " pydantic",
        " api",
        " fast",
        " http server",
        " daniil grois",
        " lev zaplatin"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "395a2466bf99020a7a35ccb42d7b8b02e303272328899c0022a716116c55e810",
                "md5": "3633eea91b2bb3ba6cc457a3e414f803",
                "sha256": "7be2d9a17dfd41533927b93a68ea88f4bb8028f13446fad928b12fad52112b63"
            },
            "downloads": -1,
            "filename": "rapidy-0.2.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "3633eea91b2bb3ba6cc457a3e414f803",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.8",
            "size": 40653,
            "upload_time": "2024-05-11T12:21:50",
            "upload_time_iso_8601": "2024-05-11T12:21:50.752733Z",
            "url": "https://files.pythonhosted.org/packages/39/5a/2466bf99020a7a35ccb42d7b8b02e303272328899c0022a716116c55e810/rapidy-0.2.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "521225c465d6d5bef6ab2c19d57760929f448e69ecb35a4b0b12fa2e98594e90",
                "md5": "4558c4d4bac301b21ab248fe5acb0761",
                "sha256": "d6c3adc7317256e87e96499ba86f7c6391e9d3504c9bcd971a3951908955a886"
            },
            "downloads": -1,
            "filename": "rapidy-0.2.2.tar.gz",
            "has_sig": false,
            "md5_digest": "4558c4d4bac301b21ab248fe5acb0761",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.8",
            "size": 37989,
            "upload_time": "2024-05-11T12:21:52",
            "upload_time_iso_8601": "2024-05-11T12:21:52.126741Z",
            "url": "https://files.pythonhosted.org/packages/52/12/25c465d6d5bef6ab2c19d57760929f448e69ecb35a4b0b12fa2e98594e90/rapidy-0.2.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-05-11 12:21:52",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "rapidy"
}
        
Elapsed time: 0.24653s