Skip to content

Commit

Permalink
Add: coverage 100%!
Browse files Browse the repository at this point in the history
  • Loading branch information
CrazyProger1 committed Apr 26, 2024
1 parent 944bac4 commit a3752a3
Show file tree
Hide file tree
Showing 9 changed files with 479 additions and 15 deletions.
106 changes: 106 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,112 @@ poetry add resty-client

## Getting-Started

See [examples](examples) for more.

### Schemas

```python
from resty.types import Schema


class UserCreateSchema(Schema):
username: str
email: str
password: str
age: int


class UserReadSchema(Schema):
id: int
username: str
email: str
age: int


class UserUpdateSchema(Schema):
username: str = None
email: str = None
```

### Manager

```python
from resty.managers import Manager
from resty.enums import Endpoint, Field


class UserManager(Manager):
endpoints = {
Endpoint.CREATE: "users/",
Endpoint.READ: "users/",
Endpoint.READ_ONE: "users/{pk}",
Endpoint.UPDATE: "users/{pk}",
Endpoint.DELETE: "users/{pk}",
}
fields = {
Field.PRIMARY: "id",
}
```

### CRUD

```python
import asyncio

import httpx

from resty.clients.httpx import RESTClient


async def main():
client = RESTClient(httpx.AsyncClient(base_url="https://localhost:8000"))

response = await UserManager.create(
client=client,
obj=UserCreateSchema(
username="admin",
email="[email protected]",
password="admin",
age=19,
),
response_type=UserReadSchema,
)
print(response) # id=1 username='admin' email='[email protected]' age=19

response = await UserManager.read(
client=client,
response_type=UserReadSchema,
)

for obj in response:
print(obj) # id=1 username='admin' email='[email protected]' age=19

response = await UserManager.read_one(
client=client,
obj_or_pk=1,
response_type=UserReadSchema,
)

print(response) # id=1 username='admin' email='[email protected]' age=19

response = await UserManager.update(
client=client,
obj=UserUpdateSchema(id=1, username="admin123", ),
response_type=UserReadSchema,
)

print(response) # id=1 username='admin123' email='[email protected]' age=19

await UserManager.delete(
client=client,
obj_or_pk=1,
expected_status=204,
)


if __name__ == "__main__":
asyncio.run(main())
```

## Status

Expand Down
2 changes: 1 addition & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@

- Improved test coverage to 100%
- Improved architecture
- Expanded Readme docs & examples
- Added examples
90 changes: 90 additions & 0 deletions examples/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import asyncio

import httpx

from resty.enums import Endpoint, Field
from resty.types import Schema
from resty.managers import Manager
from resty.clients.httpx import RESTClient


class UserCreateSchema(Schema):
username: str
email: str
password: str
age: int


class UserReadSchema(Schema):
id: int
username: str
email: str
age: int


class UserUpdateSchema(Schema):
username: str = None
email: str = None


class UserManager(Manager):
endpoints = {
Endpoint.CREATE: "users/",
Endpoint.READ: "users/",
Endpoint.READ_ONE: "users/{pk}",
Endpoint.UPDATE: "users/{pk}",
Endpoint.DELETE: "users/{pk}",
}
fields = {
Field.PRIMARY: "id",
}


async def main():
client = RESTClient(httpx.AsyncClient(base_url="https://localhost:8000"))

response = await UserManager.create(
client=client,
obj=UserCreateSchema(
username="admin",
email="[email protected]",
password="admin",
age=19,
),
response_type=UserReadSchema,
)
print(response) # id=1 username='admin' email='[email protected]' age=19

response = await UserManager.read(
client=client,
response_type=UserReadSchema,
)

for obj in response:
print(obj) # id=1 username='admin' email='[email protected]' age=19

response = await UserManager.read_one(
client=client,
obj_or_pk=1,
response_type=UserReadSchema,
)

print(response) # id=1 username='admin' email='[email protected]' age=19

response = await UserManager.update(
client=client,
obj=UserUpdateSchema(id=1, username="admin123", ),
response_type=UserReadSchema,
)

print(response) # id=1 username='admin123' email='[email protected]' age=19

await UserManager.delete(
client=client,
obj_or_pk=1,
expected_status=204,
)


if __name__ == "__main__":
asyncio.run(main())
16 changes: 13 additions & 3 deletions resty/managers/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ def get_pk(cls, obj: Schema | Mapping) -> any:

return getattr(obj, field, None)

@classmethod
def _get_pk(cls, obj_or_pk: any) -> any:
if isinstance(obj_or_pk, Mapping | Schema):
return cls.get_pk(obj=obj_or_pk)
return obj_or_pk

@classmethod
def _deserialize(cls, schema: type[Schema], data: any, **kwargs):
serializer = cls.get_serializer(**kwargs)
Expand Down Expand Up @@ -114,6 +120,9 @@ def _prepare_request(cls, endpoint: Endpoint, **kwargs) -> Request:

@classmethod
def _handle_response(cls, response: Response, response_type: ResponseType, **kwargs) -> any:
if not response:
return

if inspect.isclass(response_type):
if issubclass(response_type, dict | list | tuple | set):
return response_type(response.json)
Expand Down Expand Up @@ -144,9 +153,10 @@ async def read[T: Schema](cls, client: BaseRESTClient, response_type: ResponseTy
@classmethod
async def read_one[T: Schema](cls, client: BaseRESTClient, obj_or_pk: Schema | Mapping | any,
response_type: ResponseType = None, **kwargs) -> T:

request = cls._prepare_request(
endpoint=Endpoint.READ_ONE,
pk=kwargs.pop('pk', cls.get_pk(obj_or_pk)),
pk=cls._get_pk(obj_or_pk=obj_or_pk),
**kwargs
)
response = await cls._make_request(client=client, request=request)
Expand All @@ -156,7 +166,7 @@ async def read_one[T: Schema](cls, client: BaseRESTClient, obj_or_pk: Schema | M
async def update[T: Schema](cls, client: BaseRESTClient, obj: Schema | Mapping, response_type: ResponseType = None,
**kwargs) -> T | None:
request = cls._prepare_request(
endpoint=Endpoint.READ_ONE,
endpoint=Endpoint.UPDATE,
pk=kwargs.pop('pk', cls.get_pk(obj)),
obj=obj,
**kwargs
Expand All @@ -170,7 +180,7 @@ async def delete[T: Schema](cls, client: BaseRESTClient, obj_or_pk: Schema | Map
**kwargs) -> T | None:
request = cls._prepare_request(
endpoint=Endpoint.DELETE,
pk=kwargs.pop('pk', cls.get_pk(obj_or_pk)),
pk=cls._get_pk(obj_or_pk=obj_or_pk),
**kwargs
)
response = await cls._make_request(client=client, request=request)
Expand Down
16 changes: 16 additions & 0 deletions resty/managers/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ class BaseManager(ABC):
serializer_class: BaseSerializer
url_builder_class: BaseURLBuilder

@classmethod
@abstractmethod
def get_serializer(cls, **kwargs) -> type[BaseSerializer]: ...

@classmethod
@abstractmethod
def get_method(cls, endpoint: Endpoint, **kwargs) -> Method: ...

@classmethod
@abstractmethod
def get_field(cls, field: Field) -> str: ...

@classmethod
@abstractmethod
def get_pk(cls, obj: Schema | Mapping) -> any: ...

@classmethod
@abstractmethod
async def create[T: Schema](
Expand Down
11 changes: 5 additions & 6 deletions resty/middlewares/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

class StatusCheckingMiddleware(BaseResponseMiddleware):
def __init__(
self,
errors: Mapping[int, type[Exception]] = None,
default_error: type[Exception] = HTTPError,
self,
errors: Mapping[int, type[Exception]] = None,
default_error: type[Exception] = HTTPError,
):
self._errors = errors or STATUS_ERRORS
self._default_error = default_error
Expand All @@ -33,10 +33,9 @@ async def __call__(self, response: Response, **kwargs):
actual_status = response.status
expected_status = kwargs.pop(
"expected_status",
{
200,
},
{200, 201},
)

check_status = kwargs.pop("check_status", True)

if check_status and not self._check_status(actual_status, expected_status):
Expand Down
6 changes: 3 additions & 3 deletions resty/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Request:
class Response:
request: Request
status: int
content: bytes
text: str
json: list | dict
content: bytes = None
text: str = None
json: list | dict = None
middleware_options: dict = field(default_factory=dict)
14 changes: 13 additions & 1 deletion tests/managers/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from resty.clients import BaseRESTClient
from resty.types import Request, Response

Expand All @@ -9,6 +11,16 @@ def __init__(self, response: Response = None, **expected):

async def request(self, request: Request) -> Response:
for key, value in self.expected.items():
assert getattr(request, key) != value
assert getattr(request, key) == value

return self.response


@pytest.fixture
def client(request): # pragma: nocover
response, expected = request.param

return RESTClientMock(
response=response,
**expected,
)
Loading

0 comments on commit a3752a3

Please sign in to comment.