Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
name: Run service tests with pytest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
python-version: ['3.10', '3.11', '3.12', '3.13']
runs-on: ubuntu-22.04
steps:
- name: Checkout code
Expand Down
38 changes: 36 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Quick start guide
-----------------

HttpClient
~~~~~~~~~~~~~~
~~~~~~~~~~

Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/catfact_client.py`_

Expand Down Expand Up @@ -91,6 +91,9 @@ Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/ca
Test Async Server for client
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Example
*******

For the HTTP client, we create a server to which he will go and simulate real
responses. You can dynamically change the responses from the server in
a specific test.
Expand Down Expand Up @@ -152,12 +155,43 @@ Now we can use them in tests. See full example in `examples/test_catfact_client.
with pytest.raises(asyncio.TimeoutError):
await catfact_client.fetch_random_cat_fact(timeout=1)

Useful responses and serializers
********************************

- JsonResponse_: simple JSON response from any object.
You can setup status code and serializer for it. Using JsonSerializer_

- MsgpackResponse_: response in msgpack_ format with It's like JSON.
But fast and small. Using MsgpackSerializer_.

- SequenceResponse_: useful response if you want return different responses
on next request. Accepts BaseMockResponse_'s input.

- TimeoutResponse_: response with latency. For slow testing

- TomlResponse_: return TOML format text response. Using TomlSerializer_.

- YamlResponse_: return YAML format text response. Using YamlSerializer_.

.. _PyPI: https://pypi.org/
.. _aiohttp: https://pypi.org/project/aiohttp/
.. _msgpack: https://msgpack.org
.. _msgspec: https://github.com/jcrist/msgspec
.. _orjson: https://github.com/ijl/orjson
.. _pydantic: https://github.com/pydantic/pydantic

.. _examples/catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/catfact_client.py
.. _examples/test_catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/test_catfact_client.py
.. _examples/test_catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/test_catfact_client.py

.. _BaseMockResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/base.py
.. _JsonResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/json.py
.. _MsgpackResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/msgpack.py
.. _SequenceResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/sequence.py
.. _TimeoutResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/timeout.py
.. _TomlResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/toml.py
.. _YamlResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/yaml.py

.. _JsonSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/json.py
.. _MsgpackSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/msgpack.py
.. _TomlSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/toml.py
.. _YamlSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/yaml.py
2 changes: 1 addition & 1 deletion asyncly/client/handlers/msgspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async def _parse(response: ClientResponse) -> T:

def _choose_decoder(data_format: DataFormat) -> DataFormatDecode:
if data_format == "json":
return decode_json
return decode_json # type: ignore[return-value]
elif data_format == "msgpack":
return decode_msgpack # type: ignore[return-value]
elif data_format == "toml":
Expand Down
35 changes: 29 additions & 6 deletions examples/catfact_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,56 @@
from types import MappingProxyType

from aiohttp import ClientSession, hdrs
from msgspec import Struct
from pydantic import BaseModel

from asyncly import DEFAULT_TIMEOUT, BaseHttpClient, ResponseHandlersType
from asyncly.client.handlers.msgspec import parse_struct
from asyncly.client.handlers.pydantic import parse_model
from asyncly.client.timeout import TimeoutType


class CatfactSchema(BaseModel):
class CatfactModel(BaseModel):
fact: str
length: int


class CatfactStruct(Struct):
fact: str
length: int


class CatfactClient(BaseHttpClient):
RANDOM_CATFACT_HANDLERS: ResponseHandlersType = MappingProxyType(
MODEL_CATFACT_HANDLERS: ResponseHandlersType = MappingProxyType(
{
HTTPStatus.OK: parse_model(CatfactModel),
}
)
MSGPACK_CATFACT_HANDLERS: ResponseHandlersType = MappingProxyType(
{
HTTPStatus.OK: parse_model(CatfactSchema),
HTTPStatus.OK: parse_struct(CatfactStruct, data_format="msgpack"),
}
)

async def fetch_random_cat_fact(
async def fetch_catfact_model(
self,
timeout: TimeoutType = DEFAULT_TIMEOUT,
) -> CatfactSchema:
) -> CatfactModel:
return await self._make_req(
method=hdrs.METH_GET,
url=self._url / "fact/json",
handlers=self.RANDOM_CATFACT_HANDLERS,
handlers=self.MODEL_CATFACT_HANDLERS,
timeout=timeout,
)

async def fetch_catfact_msgpack(
self,
timeout: TimeoutType = DEFAULT_TIMEOUT,
) -> CatfactStruct:
return await self._make_req(
method=hdrs.METH_GET,
url=self._url / "fact/msgpack",
handlers=self.MSGPACK_CATFACT_HANDLERS,
timeout=timeout,
)

Expand Down
31 changes: 25 additions & 6 deletions examples/test_catfact_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@

from asyncly.srvmocker.models import MockRoute, MockService
from asyncly.srvmocker.responses.json import JsonResponse
from asyncly.srvmocker.responses.msgpack import MsgpackResponse
from asyncly.srvmocker.responses.timeout import LatencyResponse
from asyncly.srvmocker.service import start_service
from examples.catfact_client import CatfactClient, CatfactSchema
from examples.catfact_client import CatfactClient, CatfactModel, CatfactStruct


async def test_fetch_random_catfact(catfact_client: CatfactClient) -> None:
async def test_fetch_catfact_model(catfact_client: CatfactClient) -> None:
# use default registered handler
fact = await catfact_client.fetch_random_cat_fact()
assert fact == CatfactSchema(fact="test json", length=9)
fact = await catfact_client.fetch_catfact_model()
assert fact == CatfactModel(fact="test json", length=9)


async def test_fetch_random_catfact_timeout(
async def test_fetch_catfact_model_with_timeout(
catfact_client: CatfactClient,
catfact_service: MockService,
) -> None:
Expand All @@ -31,13 +32,31 @@ async def test_fetch_random_catfact_timeout(
),
)
with pytest.raises(asyncio.TimeoutError):
await catfact_client.fetch_random_cat_fact(timeout=0.1)
await catfact_client.fetch_catfact_model(timeout=0.1)


async def test_fetch_catfact_msgpack(
catfact_client: CatfactClient,
catfact_service: MockService,
) -> None:
catfact_service.register(
"msgpack_catfact",
MsgpackResponse(
{
"fact": "test msgpack",
"length": 9,
},
),
)
fact = await catfact_client.fetch_catfact_msgpack()
assert fact == CatfactStruct(fact="test msgpack", length=9)


@pytest.fixture
async def catfact_service() -> AsyncIterator[MockService]:
routes = [
MockRoute("GET", "/fact/json", "json_catfact"),
MockRoute("GET", "/fact/msgpack", "msgpack_catfact"),
]
async with start_service(routes) as service:
service.register(
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "asyncly"
version = "0.3.4"
version = "0.4.0"
description = "Simple HTTP client and server for your integrations based on aiohttp"
authors = [{ name = "Sergey Natalenko", email = "sergey.natalenko@mail.ru" }]
requires-python = ">=3.10, <4"
Expand Down Expand Up @@ -35,7 +35,7 @@ classifiers = [
dependencies = ["aiohttp>=3.9.5,<4"]

[project.optional-dependencies]
msgspec = ["msgspec>=0.18.6,<0.19"]
msgspec = ["msgspec>=0.19.0,<0.20"]
pydantic = ["pydantic>=2.8.2,<3"]
orjson = ["orjson>=3.10.6,<4"]

Expand All @@ -47,7 +47,7 @@ Source = "https://github.com/andy-takker/asyncly"
[dependency-groups]
dev = [
"pre-commit>=3.7.1,<5.0.0",
"mypy>=1.10.1,<2",
"mypy>=1.17.1,<2",
"ruff>=0.5.2,<0.9.0",
"restructuredtext-lint>=1.4.0,<2",
"pygments>=2.18.0,<3",
Expand Down
Loading