-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from CrazyProger1/paginator
Release 0.0.1
- Loading branch information
Showing
29 changed files
with
788 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,126 @@ | ||
# RestyClient | ||
RestyClient is a simple, easy-to-use Python library for interacting with REST APIs using Pydantic's powerful data validation and deserialization tools. This library provides an intuitive API that makes it easy to make HTTP requests and handle data on the client side. | ||
|
||
<p align="center"> | ||
<img src="docs/resty-cat.png" alt="resty lib logo"> | ||
</p> | ||
|
||
<p align="center"> | ||
<a href="https://github.com/CrazyProger1/RestyClient/blob/master/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/CrazyProger1/RestyClient"></a> | ||
<a href="https://github.com/CrazyProger1/RestyClient/releases/latest"><img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/CrazyProger1/RestyClient"></a> | ||
<a href="https://pypi.org/project/resty-client/"><img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/resty-client"></a> | ||
<a><img src="https://img.shields.io/pypi/status/resty-client" alt="Status"></a> | ||
</p> | ||
|
||
RestyClient is a simple, easy-to-use Python library for interacting with REST APIs using Pydantic's powerful data | ||
validation and deserialization tools. This library provides an intuitive API that makes it easy to make HTTP requests | ||
and handle data on the client side. | ||
|
||
## Features | ||
|
||
- Middleware system, which allows you to implement any pagination, filtering or authentication. | ||
|
||
## Installation | ||
|
||
Using pip: | ||
|
||
```shell | ||
pip install resty-client | ||
``` | ||
|
||
Using Poetry: | ||
|
||
```shell | ||
poetry add resty-client | ||
``` | ||
|
||
## Getting-Started | ||
|
||
### Schema | ||
|
||
```python | ||
from pydantic import BaseModel | ||
|
||
|
||
class Product(BaseModel): | ||
id: int | None = None | ||
name: str | ||
description: str | ||
code: str | ||
``` | ||
|
||
### Serializer | ||
|
||
```python | ||
from resty.serializers import Serializer | ||
|
||
|
||
class ProductSerializer(Serializer): | ||
schema = Product | ||
``` | ||
|
||
### Manager | ||
|
||
```python | ||
from resty.enums import ( | ||
Endpoint, | ||
Field | ||
) | ||
from resty.managers import Manager | ||
|
||
|
||
class ProductManager(Manager): | ||
serializer = ProductSerializer | ||
endpoints = { | ||
Endpoint.CREATE: '/products/', | ||
Endpoint.READ: '/products/', | ||
Endpoint.READ_ONE: '/products/{pk}/', | ||
Endpoint.UPDATE: '/products/{pk}/', | ||
Endpoint.DELETE: '/products/{pk}/', | ||
} | ||
fields = { | ||
Field.PRIMARY: 'id', | ||
} | ||
``` | ||
|
||
### CRUD | ||
|
||
```python | ||
from httpx import AsyncClient | ||
|
||
from resty.clients.httpx import RESTClient | ||
|
||
|
||
async def main(): | ||
xclient = AsyncClient(base_url='http://localhost:8000/') | ||
rest_client = RESTClient(xclient=xclient) | ||
|
||
product = Product( | ||
name='First prod', | ||
description='My Desc', | ||
code='123W31Q' | ||
) | ||
|
||
# Create | ||
created = await ProductManager.create(rest_client, product) | ||
|
||
# Read | ||
my_product = await ProductManager.read_one(rest_client, created.id) | ||
|
||
for prod in await ProductManager.read(rest_client): | ||
print(prod.name) | ||
|
||
# Update | ||
my_product.description = 'QWERTY' | ||
await ProductManager.update(rest_client, my_product) | ||
|
||
# Delete | ||
await ProductManager.delete(rest_client, my_product.id) | ||
``` | ||
|
||
## Status | ||
|
||
``0.0.1`` - INDEV | ||
|
||
## Licence | ||
|
||
RestyClient is released under the MIT License. See the bundled [LICENSE](LICENSE) file for details. |
File renamed without changes.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import asyncio | ||
import httpx | ||
|
||
from resty.clients.httpx import RESTClient | ||
from resty.ext.django.middlewares import DjangoPagePaginationMiddleware | ||
|
||
from managers import ProductManager | ||
from schemas import Product | ||
|
||
|
||
async def main(): | ||
xclient = httpx.AsyncClient(base_url='http://localhost:8000/') | ||
|
||
rest_client = RESTClient(xclient=xclient) | ||
|
||
rest_client.add_middleware(DjangoPagePaginationMiddleware()) | ||
|
||
product = Product( | ||
name='My Product', | ||
description='My Desc', | ||
code='123W31QQW' | ||
) | ||
|
||
# Create | ||
created = await ProductManager.create(rest_client, product) | ||
|
||
# Read | ||
my_product = await ProductManager.read_one(rest_client, created.id) | ||
|
||
for prod in await ProductManager.read(rest_client): | ||
print(prod.name) | ||
|
||
# Update | ||
my_product.description = 'QWERTY' | ||
await ProductManager.update(rest_client, my_product) | ||
|
||
# Delete | ||
await ProductManager.delete(rest_client, my_product.id) | ||
|
||
|
||
if __name__ == '__main__': | ||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from resty.enums import ( | ||
Endpoint, | ||
Field | ||
) | ||
from resty.managers import Manager | ||
|
||
from serializers import ProductSerializer | ||
|
||
|
||
class ProductManager(Manager): | ||
serializer = ProductSerializer | ||
endpoints = { | ||
Endpoint.CREATE: '/products/', | ||
Endpoint.READ: '/products/', | ||
Endpoint.READ_ONE: '/products/{pk}/', | ||
Endpoint.UPDATE: '/products/{pk}/', | ||
Endpoint.DELETE: '/products/{pk}/', | ||
} | ||
fields = { | ||
Field.PRIMARY: 'id', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from pydantic import BaseModel | ||
|
||
|
||
class Product(BaseModel): | ||
id: int | None = None | ||
name: str | ||
description: str | ||
code: str | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from resty.serializers import Serializer | ||
|
||
from schemas import Product | ||
|
||
|
||
class ProductSerializer(Serializer): | ||
schema = Product |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,23 @@ | ||
[build-system] | ||
requires = ["setuptools"] | ||
build-backend = "setuptools.build_meta" | ||
|
||
|
||
[project] | ||
name = "RestyClient" | ||
[tool.poetry] | ||
name = "resty-client" | ||
version = "0.0.1" | ||
authors = [ | ||
{ name = "crazyproger1", email = "[email protected]" }, | ||
] | ||
description = "RestyClient is a simple, easy-to-use Python library for interacting with REST APIs usingPydantic's powerful data validation and deserialization tools." | ||
description = "RestyClient is a simple, easy-to-use Python library for interacting with REST APIs using Pydantic's powerful data validation and deserialization tools." | ||
authors = ["CrazyProger1 <[email protected]>"] | ||
license = "MIT" | ||
readme = "README.md" | ||
requires-python = ">=3.7" | ||
license = { text = "MIT" } | ||
dependencies = [ | ||
"pydantic", | ||
packages = [ | ||
{ include = "resty" }, | ||
] | ||
|
||
[tool.poetry.dependencies] | ||
python = "^3.12" | ||
pydantic = "^2.5.3" | ||
|
||
[tool.poetry.group.dev.dependencies] | ||
mypy = "^1.8.0" | ||
ruff = "^0.1.14" | ||
pytest = "^7.4.4" | ||
|
||
[tool.ruff] | ||
exclude = [ | ||
".bzr", | ||
|
@@ -47,3 +48,8 @@ files = ["resty"] | |
show_error_codes = true | ||
strict = true | ||
|
||
|
||
|
||
[build-system] | ||
requires = ["poetry-core"] | ||
build-backend = "poetry.core.masonry.api" |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
try: | ||
import httpx | ||
except ImportError: | ||
raise ImportError('You should to install httpx to use httpx rest client') | ||
|
||
from .client import RESTClient | ||
|
||
__all__ = [ | ||
'RESTClient' | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import json.decoder | ||
from typing import Container | ||
|
||
import httpx | ||
|
||
from resty.constants import ( | ||
DEFAULT_CODES, | ||
STATUS_ERRORS | ||
) | ||
from resty.types import ( | ||
BaseRESTClient, | ||
Request, | ||
Response, | ||
BaseMiddleware, | ||
BaseMiddlewareManager | ||
) | ||
from resty.exceptions import ( | ||
HTTPError | ||
) | ||
from resty.middlewares import MiddlewareManager | ||
|
||
|
||
class RESTClient(BaseRESTClient): | ||
|
||
def __init__(self, xclient: httpx.AsyncClient, middleware_manager: BaseMiddlewareManager = None): | ||
self._xclient = xclient | ||
self._middleware_manager = middleware_manager or MiddlewareManager( | ||
default_middlewares=None, | ||
) | ||
|
||
@staticmethod | ||
def _parse_xresponse(xresponse: httpx.Response) -> dict | list | None: | ||
try: | ||
data = xresponse.json() | ||
except json.decoder.JSONDecodeError: | ||
data = {} | ||
|
||
return data | ||
|
||
@staticmethod | ||
def _check_status(status: int, expected_status: int | Container[int], request: Request, url: str): | ||
if status != expected_status: | ||
if isinstance(expected_status, Container) and status in expected_status: | ||
pass | ||
else: | ||
exc: type[HTTPError] = STATUS_ERRORS.get(status, HTTPError) | ||
raise exc( | ||
request=request, | ||
status=status, | ||
url=url | ||
) | ||
|
||
def add_middleware(self, middleware: BaseMiddleware): | ||
self._middleware_manager.add_middleware(middleware=middleware) | ||
|
||
async def request(self, request: Request, **kwargs) -> Response: | ||
if not isinstance(request, Request): | ||
raise TypeError('request is not of type Request') | ||
|
||
expected_status: int = kwargs.pop('expected_status', DEFAULT_CODES.get(request.method)) | ||
check_status: bool = kwargs.pop('check_status', True) | ||
|
||
if not isinstance(expected_status, (int, Container[int])): | ||
raise TypeError('expected status should be type of int or Container[int]') | ||
|
||
await self._middleware_manager.call_pre_middlewares(request=request, **kwargs) | ||
|
||
xresponse = await self._xclient.request( | ||
method=request.method.value, | ||
url=request.url, | ||
headers=request.headers, | ||
data=request.data, | ||
params=request.params, | ||
cookies=request.cookies, | ||
follow_redirects=request.redirects, | ||
timeout=request.timeout | ||
) | ||
|
||
status = xresponse.status_code | ||
|
||
if check_status: | ||
self._check_status( | ||
status=status, | ||
expected_status=expected_status, | ||
request=request, | ||
url=str(xresponse.url) | ||
) | ||
|
||
response = Response( | ||
request=request, | ||
status=status, | ||
data=self._parse_xresponse( | ||
xresponse=xresponse | ||
) | ||
) | ||
await self._middleware_manager.call_post_middlewares(response=response, **kwargs) | ||
|
||
return response |
Oops, something went wrong.