Skip to content

Commit

Permalink
Merge pull request #5 from CrazyProger1/dev
Browse files Browse the repository at this point in the history
V0.0.3
  • Loading branch information
CrazyProger1 authored Apr 23, 2024
2 parents 692066b + 5621113 commit f96862f
Show file tree
Hide file tree
Showing 25 changed files with 395 additions and 144 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ async def main():

## Status

``0.0.2`` - **RELEASED**
``0.0.3`` - **RELEASED**

## Licence

Expand Down
11 changes: 10 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,14 @@

## v0.0.2

- Serializer many schemas support
- Added serializer many schemas support
- Added get_schema serializer method

## v0.0.3

- Refactored manager & httpx client
- Added more examples
- Added serializer tests
- Added URL injecting mechanism (allows to perform many-layer requests: `api/v1/users/123/product/321`).
See [example](../examples/crud_many_layers)
- Added deserialize_many serializer method
4 changes: 2 additions & 2 deletions examples/crud/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from resty.ext.django.middlewares import DjangoPagePaginationMiddleware

from managers import ProductManager
from schemas import Product
from schemas import ProductSchema


async def main():
Expand All @@ -15,7 +15,7 @@ async def main():

rest_client.add_middleware(DjangoPagePaginationMiddleware())

product = Product(
product = ProductSchema(
name='My Product',
description='My Desc',
code='123W31QQW'
Expand Down
2 changes: 1 addition & 1 deletion examples/crud/schemas.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pydantic import BaseModel


class Product(BaseModel):
class ProductSchema(BaseModel):
id: int | None = None
name: str
description: str
Expand Down
4 changes: 2 additions & 2 deletions examples/crud/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from resty.serializers import Serializer

from schemas import Product
from schemas import ProductSchema


class ProductSerializer(Serializer):
schema = Product
schema = ProductSchema
21 changes: 21 additions & 0 deletions examples/crud_many_layers/managers.py
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 UserProductManager(Manager):
serializer = ProductSerializer
endpoints = {
Endpoint.CREATE: 'users/{user_pk}/products/',
Endpoint.READ: 'users/{user_pk}/products/',
Endpoint.READ_ONE: 'users/{user_pk}/products/{pk}/',
Endpoint.UPDATE: 'users/{user_pk}/products/{pk}/',
Endpoint.DELETE: 'users/{user_pk}/products/{pk}/',
}
fields = {
Field.PRIMARY: 'id',
}
9 changes: 9 additions & 0 deletions examples/crud_many_layers/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pydantic import BaseModel


class ProductSchema(BaseModel):
id: int | None = None
name: str
description: str
code: str

7 changes: 7 additions & 0 deletions examples/crud_many_layers/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from resty.serializers import Serializer

from schemas import ProductSchema


class ProductSerializer(Serializer):
schema = ProductSchema
21 changes: 21 additions & 0 deletions examples/serializer_schemas/managers.py
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',
}
18 changes: 18 additions & 0 deletions examples/serializer_schemas/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from pydantic import BaseModel


class ProductCreateSchema(BaseModel):
name: str
description: str


class ProductReadSchema(BaseModel):
id: int | None = None
name: str
description: str
code: str


class ProductUpdateSchema(BaseModel):
name: str = None
description: str = None
17 changes: 17 additions & 0 deletions examples/serializer_schemas/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from resty.serializers import Serializer
from resty.enums import Endpoint

from schemas import (
ProductCreateSchema,
ProductReadSchema,
ProductUpdateSchema
)


class ProductSerializer(Serializer):
schemas = {
Endpoint.CREATE: ProductCreateSchema,
Endpoint.READ: ProductReadSchema,
Endpoint.READ_ONE: ProductReadSchema,
Endpoint.UPDATE: ProductUpdateSchema
}
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "resty-client"
version = "0.0.2"
version = "0.0.3"
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"
Expand All @@ -18,6 +18,7 @@ mypy = "^1.8.0"
pytest = "^7.4.4"
httpx = "^0.26.0"
black = "^24.4.0"
coverage = "^7.5.0"

[tool.ruff]
exclude = [
Expand Down
61 changes: 34 additions & 27 deletions resty/clients/httpx/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@
class RESTClient(BaseRESTClient):

def __init__(
self,
xclient: httpx.AsyncClient,
middleware_manager: BaseMiddlewareManager = None,
self,
xclient: httpx.AsyncClient = None,
middleware_manager: BaseMiddlewareManager = None,
):
self._xclient = xclient
self._middleware_manager = middleware_manager or MiddlewareManager(
default_middlewares=None,
)

self._xclient = xclient or httpx.AsyncClient()
self._middleware_manager = middleware_manager or MiddlewareManager()

@staticmethod
def _parse_xresponse(xresponse: httpx.Response) -> dict | list | None:
Expand All @@ -38,8 +37,11 @@ def _parse_xresponse(xresponse: httpx.Response) -> dict | list | None:

@staticmethod
def _check_status(
status: int, expected_status: int | Container[int], request: Request, url: str,
data: dict = None
status: int,
expected_status: int | Container[int],
request: Request,
url: str,
data: dict = None,
):
if status != expected_status:
if isinstance(expected_status, Container) and status in expected_status:
Expand All @@ -48,39 +50,44 @@ def _check_status(
exc: type[HTTPError] = STATUS_ERRORS.get(status, HTTPError)
raise exc(request=request, status=status, url=url, data=data)

async def _make_xrequest(self, request: Request):
return await self._xclient.request(
method=request.method.value,
url=request.url,
headers=request.headers,
json=request.json,
data=request.data,
params=request.params,
cookies=request.cookies,
follow_redirects=request.redirects,
timeout=request.timeout,
)

def add_middleware(self, middleware: BaseMiddleware):
self._middleware_manager.add_middleware(middleware=middleware)

async def request(self, request: Request, **kwargs) -> Response:
async def request(self, request: Request, **context) -> Response:
if not isinstance(request, Request):
raise TypeError("request is not of type Request")

expected_status: int = kwargs.pop(
expected_status: int = context.pop(
"expected_status", DEFAULT_CODES.get(request.method)
)
check_status: bool = kwargs.pop("check_status", True)
check_status: bool = context.pop("check_status", True)

if not isinstance(expected_status, (int, Container)):
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,
json=request.json,
data=request.data,
params=request.params,
cookies=request.cookies,
follow_redirects=request.redirects,
timeout=request.timeout,
await self._middleware_manager.call_request_middlewares(
request=request, **context
)

status = xresponse.status_code
xresponse = await self._make_xrequest(request=request)

data = self._parse_xresponse(xresponse=xresponse)

status = xresponse.status_code

if check_status:
self._check_status(
status=status,
Expand All @@ -95,8 +102,8 @@ async def request(self, request: Request, **kwargs) -> Response:
data=data,
)

await self._middleware_manager.call_post_middlewares(
response=response, **kwargs
await self._middleware_manager.call_response_middlewares(
response=response, **context
)

return response
10 changes: 9 additions & 1 deletion resty/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from resty.types import Request


class HTTPError(Exception):
class RestyError(Exception):
pass


class URLFormattingError(RestyError):
pass


class HTTPError(RestyError):
def __init__(self, request: Request, status: int, url: str, data: dict):
self.request = request
self.status = status
Expand Down
14 changes: 7 additions & 7 deletions resty/ext/django/middlewares/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@


class DjangoPagePaginationMiddleware(BasePaginationMiddleware):
async def handle_request(self, request: Request, **kwargs):
async def handle_request(self, request: Request, **context):
if request.method in {
Method.GET,
}:
page = kwargs.pop("page", 1)
page = context.pop("page", 1)

request.params.update(
{
"page": page,
}
)

async def handle_response(self, response: Response, **kwargs):
async def handle_response(self, response: Response, **context):
if response.request.method in {
Method.GET,
}:
Expand All @@ -29,12 +29,12 @@ class DjangoLimitOffsetPaginationMiddleware(DjangoPagePaginationMiddleware):
def __init__(self, page_size: int = 100):
self._limit = page_size

async def handle_request(self, request: Request, **kwargs):
async def handle_request(self, request: Request, **context):
if request.method in {
Method.GET,
}:
page = kwargs.pop("page", 1) - 1
limit = kwargs.pop("limit", self._limit)
offset = kwargs.pop("offset", page * self._limit)
page = context.pop("page", 1) - 1
limit = context.pop("limit", self._limit)
offset = context.pop("offset", page * self._limit)

request.params.update({"limit": limit, "offset": offset})
2 changes: 1 addition & 1 deletion resty/managers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .manager import Manager
from resty.managers.manager import Manager

__all__ = ["Manager"]
Loading

0 comments on commit f96862f

Please sign in to comment.