asyncly 0.3.4__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {asyncly-0.3.4 → asyncly-0.4.0}/.gitignore +2 -1
  2. {asyncly-0.3.4 → asyncly-0.4.0}/PKG-INFO +38 -4
  3. {asyncly-0.3.4 → asyncly-0.4.0}/README.rst +36 -2
  4. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/client/base.py +9 -1
  5. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/client/handlers/base.py +1 -1
  6. asyncly-0.4.0/asyncly/client/handlers/msgspec.py +49 -0
  7. asyncly-0.4.0/asyncly/srvmocker/__init__.py +14 -0
  8. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/srvmocker/models.py +2 -8
  9. asyncly-0.4.0/asyncly/srvmocker/responses/__init__.py +0 -0
  10. asyncly-0.4.0/asyncly/srvmocker/responses/base.py +10 -0
  11. asyncly-0.4.0/asyncly/srvmocker/responses/content.py +36 -0
  12. asyncly-0.4.0/asyncly/srvmocker/responses/json.py +30 -0
  13. asyncly-0.4.0/asyncly/srvmocker/responses/msgpack.py +30 -0
  14. asyncly-0.4.0/asyncly/srvmocker/responses/sequence.py +17 -0
  15. asyncly-0.4.0/asyncly/srvmocker/responses/timeout.py +19 -0
  16. asyncly-0.4.0/asyncly/srvmocker/responses/toml.py +30 -0
  17. asyncly-0.4.0/asyncly/srvmocker/responses/yaml.py +30 -0
  18. asyncly-0.4.0/asyncly/srvmocker/serialization/__init__.py +0 -0
  19. asyncly-0.4.0/asyncly/srvmocker/serialization/base.py +9 -0
  20. asyncly-0.4.0/asyncly/srvmocker/serialization/json.py +9 -0
  21. asyncly-0.4.0/asyncly/srvmocker/serialization/msgpack.py +10 -0
  22. asyncly-0.4.0/asyncly/srvmocker/serialization/toml.py +13 -0
  23. asyncly-0.4.0/asyncly/srvmocker/serialization/yaml.py +10 -0
  24. {asyncly-0.3.4 → asyncly-0.4.0}/pyproject.toml +6 -3
  25. asyncly-0.3.4/asyncly/client/handlers/msgspec.py +0 -15
  26. asyncly-0.3.4/asyncly/srvmocker/__init__.py +0 -10
  27. asyncly-0.3.4/asyncly/srvmocker/responses.py +0 -80
  28. asyncly-0.3.4/asyncly/srvmocker/serialization.py +0 -16
  29. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/__init__.py +0 -0
  30. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/client/__init__.py +0 -0
  31. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/client/handlers/__init__.py +0 -0
  32. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/client/handlers/exceptions.py +0 -0
  33. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/client/handlers/json.py +0 -0
  34. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/client/handlers/pydantic.py +0 -0
  35. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/client/timeout.py +0 -0
  36. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/py.typed +0 -0
  37. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/srvmocker/constants.py +0 -0
  38. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/srvmocker/handlers.py +0 -0
  39. {asyncly-0.3.4 → asyncly-0.4.0}/asyncly/srvmocker/service.py +0 -0
@@ -161,4 +161,5 @@ cython_debug/
161
161
  # and can be added to the global gitignore or merged into this file. For a more nuclear
162
162
  # option (not recommended) you can uncomment the following to ignore the entire idea folder.
163
163
  .idea/
164
- .vscode/
164
+ .vscode/
165
+ .DS_Store
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asyncly
3
- Version: 0.3.4
3
+ Version: 0.4.0
4
4
  Summary: Simple HTTP client and server for your integrations based on aiohttp
5
5
  Project-URL: Homepage, https://github.com/andy-takker/asyncly
6
6
  Project-URL: Source, https://github.com/andy-takker/asyncly
@@ -30,7 +30,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
30
  Requires-Python: <4,>=3.10
31
31
  Requires-Dist: aiohttp<4,>=3.9.5
32
32
  Provides-Extra: msgspec
33
- Requires-Dist: msgspec<0.19,>=0.18.6; extra == 'msgspec'
33
+ Requires-Dist: msgspec<0.20,>=0.19.0; extra == 'msgspec'
34
34
  Provides-Extra: orjson
35
35
  Requires-Dist: orjson<4,>=3.10.6; extra == 'orjson'
36
36
  Provides-Extra: pydantic
@@ -98,7 +98,7 @@ Quick start guide
98
98
  -----------------
99
99
 
100
100
  HttpClient
101
- ~~~~~~~~~~~~~~
101
+ ~~~~~~~~~~
102
102
 
103
103
  Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/catfact_client.py`_
104
104
 
@@ -130,6 +130,9 @@ Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/ca
130
130
  Test Async Server for client
131
131
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132
132
 
133
+ Example
134
+ *******
135
+
133
136
  For the HTTP client, we create a server to which he will go and simulate real
134
137
  responses. You can dynamically change the responses from the server in
135
138
  a specific test.
@@ -191,12 +194,43 @@ Now we can use them in tests. See full example in `examples/test_catfact_client.
191
194
  with pytest.raises(asyncio.TimeoutError):
192
195
  await catfact_client.fetch_random_cat_fact(timeout=1)
193
196
 
197
+ Useful responses and serializers
198
+ ********************************
199
+
200
+ - JsonResponse_: simple JSON response from any object.
201
+ You can setup status code and serializer for it. Using JsonSerializer_
202
+
203
+ - MsgpackResponse_: response in msgpack_ format with It's like JSON.
204
+ But fast and small. Using MsgpackSerializer_.
205
+
206
+ - SequenceResponse_: useful response if you want return different responses
207
+ on next request. Accepts BaseMockResponse_'s input.
208
+
209
+ - TimeoutResponse_: response with latency. For slow testing
210
+
211
+ - TomlResponse_: return TOML format text response. Using TomlSerializer_.
212
+
213
+ - YamlResponse_: return YAML format text response. Using YamlSerializer_.
194
214
 
195
215
  .. _PyPI: https://pypi.org/
196
216
  .. _aiohttp: https://pypi.org/project/aiohttp/
217
+ .. _msgpack: https://msgpack.org
197
218
  .. _msgspec: https://github.com/jcrist/msgspec
198
219
  .. _orjson: https://github.com/ijl/orjson
199
220
  .. _pydantic: https://github.com/pydantic/pydantic
200
221
 
201
222
  .. _examples/catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/catfact_client.py
202
- .. _examples/test_catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/test_catfact_client.py
223
+ .. _examples/test_catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/test_catfact_client.py
224
+
225
+ .. _BaseMockResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/base.py
226
+ .. _JsonResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/json.py
227
+ .. _MsgpackResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/msgpack.py
228
+ .. _SequenceResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/sequence.py
229
+ .. _TimeoutResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/timeout.py
230
+ .. _TomlResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/toml.py
231
+ .. _YamlResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/yaml.py
232
+
233
+ .. _JsonSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/json.py
234
+ .. _MsgpackSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/msgpack.py
235
+ .. _TomlSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/toml.py
236
+ .. _YamlSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/yaml.py
@@ -59,7 +59,7 @@ Quick start guide
59
59
  -----------------
60
60
 
61
61
  HttpClient
62
- ~~~~~~~~~~~~~~
62
+ ~~~~~~~~~~
63
63
 
64
64
  Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/catfact_client.py`_
65
65
 
@@ -91,6 +91,9 @@ Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/ca
91
91
  Test Async Server for client
92
92
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
93
93
 
94
+ Example
95
+ *******
96
+
94
97
  For the HTTP client, we create a server to which he will go and simulate real
95
98
  responses. You can dynamically change the responses from the server in
96
99
  a specific test.
@@ -152,12 +155,43 @@ Now we can use them in tests. See full example in `examples/test_catfact_client.
152
155
  with pytest.raises(asyncio.TimeoutError):
153
156
  await catfact_client.fetch_random_cat_fact(timeout=1)
154
157
 
158
+ Useful responses and serializers
159
+ ********************************
160
+
161
+ - JsonResponse_: simple JSON response from any object.
162
+ You can setup status code and serializer for it. Using JsonSerializer_
163
+
164
+ - MsgpackResponse_: response in msgpack_ format with It's like JSON.
165
+ But fast and small. Using MsgpackSerializer_.
166
+
167
+ - SequenceResponse_: useful response if you want return different responses
168
+ on next request. Accepts BaseMockResponse_'s input.
169
+
170
+ - TimeoutResponse_: response with latency. For slow testing
171
+
172
+ - TomlResponse_: return TOML format text response. Using TomlSerializer_.
173
+
174
+ - YamlResponse_: return YAML format text response. Using YamlSerializer_.
155
175
 
156
176
  .. _PyPI: https://pypi.org/
157
177
  .. _aiohttp: https://pypi.org/project/aiohttp/
178
+ .. _msgpack: https://msgpack.org
158
179
  .. _msgspec: https://github.com/jcrist/msgspec
159
180
  .. _orjson: https://github.com/ijl/orjson
160
181
  .. _pydantic: https://github.com/pydantic/pydantic
161
182
 
162
183
  .. _examples/catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/catfact_client.py
163
- .. _examples/test_catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/test_catfact_client.py
184
+ .. _examples/test_catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/test_catfact_client.py
185
+
186
+ .. _BaseMockResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/base.py
187
+ .. _JsonResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/json.py
188
+ .. _MsgpackResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/msgpack.py
189
+ .. _SequenceResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/sequence.py
190
+ .. _TimeoutResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/timeout.py
191
+ .. _TomlResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/toml.py
192
+ .. _YamlResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/yaml.py
193
+
194
+ .. _JsonSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/json.py
195
+ .. _MsgpackSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/msgpack.py
196
+ .. _TomlSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/toml.py
197
+ .. _YamlSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/yaml.py
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  from typing import Any, Literal
2
3
 
3
4
  from aiohttp import ClientSession
@@ -10,6 +11,13 @@ from asyncly.client.handlers.base import (
10
11
  )
11
12
  from asyncly.client.timeout import TimeoutType, get_timeout
12
13
 
14
+ if sys.version_info >= (3, 11):
15
+ from http import HTTPMethod
16
+
17
+ MethodType = HTTPMethod | Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"]
18
+ else:
19
+ MethodType = Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"]
20
+
13
21
 
14
22
  class BaseHttpClient:
15
23
  __slots__ = ("_url", "_session", "_client_name")
@@ -31,7 +39,7 @@ class BaseHttpClient:
31
39
 
32
40
  async def _make_req(
33
41
  self,
34
- method: Literal["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"],
42
+ method: MethodType,
35
43
  url: URL,
36
44
  handlers: ResponseHandlersType,
37
45
  timeout: TimeoutType = DEFAULT_TIMEOUT,
@@ -17,7 +17,7 @@ async def apply_handler(
17
17
  handler = _find_handler(handlers=handlers, status=response.status)
18
18
  if not handler:
19
19
  raise UnhandledStatusException(
20
- f"Unexpected resposne {response.status} from {response.url}",
20
+ f"Unexpected response {response.status} from {response.url}",
21
21
  status=response.status,
22
22
  url=response.url,
23
23
  client_name=client_name,
@@ -0,0 +1,49 @@
1
+ from collections.abc import Awaitable, Callable
2
+ from typing import Any, Literal, Protocol, TypeVar
3
+
4
+ from aiohttp import ClientResponse
5
+ from msgspec import Struct
6
+ from msgspec.json import decode as decode_json
7
+ from msgspec.msgpack import decode as decode_msgpack
8
+ from msgspec.toml import decode as decode_toml
9
+ from msgspec.yaml import decode as decode_yaml
10
+
11
+ T = TypeVar("T", bound=Struct)
12
+
13
+ DataFormat = Literal["json", "msgpack", "toml", "yaml"]
14
+
15
+
16
+ class DataFormatDecode(Protocol):
17
+ def __call__(
18
+ self,
19
+ buf: bytes | str,
20
+ *,
21
+ type: type[T] = ...,
22
+ strict: bool = True,
23
+ dec_hook: Callable[[type, Any], Any] | None = None,
24
+ ) -> Any: ...
25
+
26
+
27
+ def parse_struct(
28
+ struct: type[T],
29
+ data_format: DataFormat = "json",
30
+ strict: bool = True,
31
+ ) -> Callable[[ClientResponse], Awaitable[T]]:
32
+ decode = _choose_decoder(data_format)
33
+
34
+ async def _parse(response: ClientResponse) -> T:
35
+ return decode(await response.read(), type=struct, strict=strict)
36
+
37
+ return _parse
38
+
39
+
40
+ def _choose_decoder(data_format: DataFormat) -> DataFormatDecode:
41
+ if data_format == "json":
42
+ return decode_json # type: ignore[return-value]
43
+ elif data_format == "msgpack":
44
+ return decode_msgpack # type: ignore[return-value]
45
+ elif data_format == "toml":
46
+ return decode_toml
47
+ elif data_format == "yaml":
48
+ return decode_yaml
49
+ return decode_json
@@ -0,0 +1,14 @@
1
+ from asyncly.srvmocker.models import MockRoute, MockService
2
+ from asyncly.srvmocker.responses.base import BaseMockResponse
3
+ from asyncly.srvmocker.responses.content import ContentResponse
4
+ from asyncly.srvmocker.responses.json import JsonResponse
5
+ from asyncly.srvmocker.service import start_service
6
+
7
+ __all__ = (
8
+ "BaseMockResponse",
9
+ "ContentResponse",
10
+ "MockRoute",
11
+ "MockService",
12
+ "JsonResponse",
13
+ "start_service",
14
+ )
@@ -1,11 +1,11 @@
1
- from abc import ABC, abstractmethod
2
1
  from collections.abc import MutableMapping, MutableSequence
3
2
  from dataclasses import dataclass
4
3
 
5
4
  from aiohttp.web_request import Request
6
- from aiohttp.web_response import Response
7
5
  from yarl import URL
8
6
 
7
+ from asyncly.srvmocker.responses.base import BaseMockResponse
8
+
9
9
 
10
10
  @dataclass(frozen=True)
11
11
  class MockRoute:
@@ -20,12 +20,6 @@ class RequestHistory:
20
20
  body: bytes
21
21
 
22
22
 
23
- class BaseMockResponse(ABC):
24
- @abstractmethod
25
- async def response(self, request: Request) -> Response:
26
- pass
27
-
28
-
29
23
  @dataclass(frozen=True)
30
24
  class MockService:
31
25
  history: MutableSequence[RequestHistory]
File without changes
@@ -0,0 +1,10 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from aiohttp.web_request import Request
4
+ from aiohttp.web_response import Response
5
+
6
+
7
+ class BaseMockResponse(ABC):
8
+ @abstractmethod
9
+ async def response(self, request: Request) -> Response:
10
+ pass
@@ -0,0 +1,36 @@
1
+ from collections.abc import Mapping, MutableMapping
2
+ from dataclasses import dataclass
3
+ from http import HTTPStatus
4
+ from typing import Any
5
+
6
+ from aiohttp import hdrs
7
+ from aiohttp.web_request import Request
8
+ from aiohttp.web_response import Response
9
+
10
+ from asyncly.srvmocker.responses.base import BaseMockResponse
11
+ from asyncly.srvmocker.serialization.base import Serializer
12
+
13
+
14
+ @dataclass
15
+ class ContentResponse(BaseMockResponse):
16
+ body: Any = None
17
+ status: int = HTTPStatus.OK
18
+ headers: Mapping[str, str] | None = None
19
+ serializer: Serializer | None = None
20
+
21
+ async def response(self, request: Request) -> Response:
22
+ headers: MutableMapping[str, str] = dict()
23
+ if self.headers:
24
+ headers.update(self.headers)
25
+ if self.serializer:
26
+ headers[hdrs.CONTENT_TYPE] = self.serializer.content_type
27
+ return Response(
28
+ status=self.status,
29
+ body=self.serialize(),
30
+ headers=headers,
31
+ )
32
+
33
+ def serialize(self) -> Any:
34
+ if not self.serializer:
35
+ return self.body
36
+ return self.serializer.dumps(self.body)
@@ -0,0 +1,30 @@
1
+ from collections.abc import Mapping
2
+ from http import HTTPStatus
3
+ from typing import Any
4
+
5
+ from aiohttp.web_request import Request
6
+ from aiohttp.web_response import Response
7
+
8
+ from asyncly.srvmocker.responses.base import BaseMockResponse
9
+ from asyncly.srvmocker.responses.content import ContentResponse
10
+ from asyncly.srvmocker.serialization.json import JsonSerializer
11
+
12
+
13
+ class JsonResponse(BaseMockResponse):
14
+ _content: ContentResponse
15
+
16
+ def __init__(
17
+ self,
18
+ body: Any,
19
+ status: int = HTTPStatus.OK,
20
+ headers: Mapping[str, str] | None = None,
21
+ ) -> None:
22
+ self._content = ContentResponse(
23
+ body=body,
24
+ status=status,
25
+ headers=headers,
26
+ serializer=JsonSerializer,
27
+ )
28
+
29
+ async def response(self, request: Request) -> Response:
30
+ return await self._content.response(request)
@@ -0,0 +1,30 @@
1
+ from collections.abc import Mapping
2
+ from http import HTTPStatus
3
+ from typing import Any
4
+
5
+ from aiohttp.web_request import Request
6
+ from aiohttp.web_response import Response
7
+
8
+ from asyncly.srvmocker.responses.base import BaseMockResponse
9
+ from asyncly.srvmocker.responses.content import ContentResponse
10
+ from asyncly.srvmocker.serialization.msgpack import MsgpackSerializer
11
+
12
+
13
+ class MsgpackResponse(BaseMockResponse):
14
+ __content: ContentResponse
15
+
16
+ def __init__(
17
+ self,
18
+ body: Any,
19
+ status: int = HTTPStatus.OK,
20
+ headers: Mapping[str, str] | None = None,
21
+ ) -> None:
22
+ self.__content = ContentResponse(
23
+ body=body,
24
+ status=status,
25
+ headers=headers,
26
+ serializer=MsgpackSerializer,
27
+ )
28
+
29
+ async def response(self, request: Request) -> Response:
30
+ return await self.__content.response(request)
@@ -0,0 +1,17 @@
1
+ from collections.abc import Iterable, Iterator
2
+
3
+ from aiohttp.web_request import Request
4
+ from aiohttp.web_response import Response
5
+
6
+ from asyncly.srvmocker.responses.base import BaseMockResponse
7
+
8
+
9
+ class SequenceResponse(BaseMockResponse):
10
+ responses: Iterator[BaseMockResponse]
11
+
12
+ def __init__(self, responses: Iterable[BaseMockResponse]) -> None:
13
+ self.responses = iter(responses)
14
+
15
+ async def response(self, request: Request) -> Response:
16
+ resp = next(self.responses)
17
+ return await resp.response(request)
@@ -0,0 +1,19 @@
1
+ from asyncio import sleep
2
+ from dataclasses import dataclass
3
+
4
+ from aiohttp.web_request import Request
5
+ from aiohttp.web_response import Response
6
+
7
+ from asyncly.srvmocker.responses.base import BaseMockResponse
8
+
9
+ TimeoutType = int | float
10
+
11
+
12
+ @dataclass
13
+ class LatencyResponse(BaseMockResponse):
14
+ wrapped: BaseMockResponse
15
+ latency: TimeoutType
16
+
17
+ async def response(self, request: Request) -> Response:
18
+ await sleep(self.latency)
19
+ return await self.wrapped.response(request)
@@ -0,0 +1,30 @@
1
+ from collections.abc import Mapping
2
+ from http import HTTPStatus
3
+ from typing import Any
4
+
5
+ from aiohttp.web_request import Request
6
+ from aiohttp.web_response import Response
7
+
8
+ from asyncly.srvmocker.responses.base import BaseMockResponse
9
+ from asyncly.srvmocker.responses.content import ContentResponse
10
+ from asyncly.srvmocker.serialization.toml import TomlSerializer
11
+
12
+
13
+ class TomlResponse(BaseMockResponse):
14
+ _content: ContentResponse
15
+
16
+ def __init__(
17
+ self,
18
+ body: Any,
19
+ status: int = HTTPStatus.OK,
20
+ headers: Mapping[str, str] | None = None,
21
+ ) -> None:
22
+ self._content = ContentResponse(
23
+ body=body,
24
+ status=status,
25
+ headers=headers,
26
+ serializer=TomlSerializer,
27
+ )
28
+
29
+ async def response(self, request: Request) -> Response:
30
+ return await self._content.response(request)
@@ -0,0 +1,30 @@
1
+ from collections.abc import Mapping
2
+ from http import HTTPStatus
3
+ from typing import Any
4
+
5
+ from aiohttp.web_request import Request
6
+ from aiohttp.web_response import Response
7
+
8
+ from asyncly.srvmocker.responses.base import BaseMockResponse
9
+ from asyncly.srvmocker.responses.content import ContentResponse
10
+ from asyncly.srvmocker.serialization.yaml import YamlSerializer
11
+
12
+
13
+ class YamlResponse(BaseMockResponse):
14
+ _content: ContentResponse
15
+
16
+ def __init__(
17
+ self,
18
+ body: Any,
19
+ status: int = HTTPStatus.OK,
20
+ headers: Mapping[str, str] | None = None,
21
+ ) -> None:
22
+ self._content = ContentResponse(
23
+ body=body,
24
+ status=status,
25
+ headers=headers,
26
+ serializer=YamlSerializer,
27
+ )
28
+
29
+ async def response(self, request: Request) -> Response:
30
+ return await self._content.response(request)
@@ -0,0 +1,9 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class Serializer:
8
+ dumps: Callable[[Any], str | bytes]
9
+ content_type: str
@@ -0,0 +1,9 @@
1
+ import json
2
+ from typing import Final
3
+
4
+ from asyncly.srvmocker.serialization.base import Serializer
5
+
6
+ JsonSerializer: Final = Serializer(
7
+ dumps=json.dumps,
8
+ content_type="application/json",
9
+ )
@@ -0,0 +1,10 @@
1
+ from typing import Final
2
+
3
+ import msgspec
4
+
5
+ from asyncly.srvmocker.serialization.base import Serializer
6
+
7
+ MsgpackSerializer: Final = Serializer(
8
+ dumps=msgspec.msgpack.encode,
9
+ content_type="application/msgpack",
10
+ )
@@ -0,0 +1,13 @@
1
+ from typing import Final
2
+
3
+ try:
4
+ import toml
5
+ except ImportError:
6
+ raise ImportError("toml is not installed")
7
+
8
+ from asyncly.srvmocker.serialization.base import Serializer
9
+
10
+ TomlSerializer: Final = Serializer(
11
+ dumps=toml.dumps,
12
+ content_type="application/toml",
13
+ )
@@ -0,0 +1,10 @@
1
+ from typing import Final
2
+
3
+ import yaml
4
+
5
+ from asyncly.srvmocker.serialization.base import Serializer
6
+
7
+ YamlSerializer: Final = Serializer(
8
+ dumps=yaml.dump,
9
+ content_type="application/yaml",
10
+ )
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "asyncly"
3
- version = "0.3.4"
3
+ version = "0.4.0"
4
4
  description = "Simple HTTP client and server for your integrations based on aiohttp"
5
5
  authors = [{ name = "Sergey Natalenko", email = "sergey.natalenko@mail.ru" }]
6
6
  requires-python = ">=3.10, <4"
@@ -35,7 +35,7 @@ classifiers = [
35
35
  dependencies = ["aiohttp>=3.9.5,<4"]
36
36
 
37
37
  [project.optional-dependencies]
38
- msgspec = ["msgspec>=0.18.6,<0.19"]
38
+ msgspec = ["msgspec>=0.19.0,<0.20"]
39
39
  pydantic = ["pydantic>=2.8.2,<3"]
40
40
  orjson = ["orjson>=3.10.6,<4"]
41
41
 
@@ -47,13 +47,16 @@ Source = "https://github.com/andy-takker/asyncly"
47
47
  [dependency-groups]
48
48
  dev = [
49
49
  "pre-commit>=3.7.1,<5.0.0",
50
- "mypy>=1.10.1,<2",
50
+ "mypy>=1.17.1,<2",
51
51
  "ruff>=0.5.2,<0.9.0",
52
52
  "restructuredtext-lint>=1.4.0,<2",
53
53
  "pygments>=2.18.0,<3",
54
54
  "pytest>=8.3.3,<9",
55
55
  "pytest-asyncio>=0.24.0,<0.25",
56
56
  "pytest-cov>=6.0.0,<7",
57
+ "types-pyyaml>=6.0.12.20250822",
58
+ "types-toml>=0.10.8.20240310",
59
+ "toml>=0.10.2",
57
60
  ]
58
61
 
59
62
  [tool.hatch.build.targets.sdist]
@@ -1,15 +0,0 @@
1
- from collections.abc import Awaitable, Callable
2
- from typing import TypeVar
3
-
4
- from aiohttp import ClientResponse
5
- from msgspec import Struct
6
- from msgspec.json import decode
7
-
8
- T = TypeVar("T", bound=Struct)
9
-
10
-
11
- def parse_struct(struct: type[T]) -> Callable[[ClientResponse], Awaitable[T]]:
12
- async def _parse(response: ClientResponse) -> T:
13
- return decode(await response.read(), type=struct)
14
-
15
- return _parse
@@ -1,10 +0,0 @@
1
- from asyncly.srvmocker.models import MockRoute, MockService
2
- from asyncly.srvmocker.responses import JsonResponse
3
- from asyncly.srvmocker.service import start_service
4
-
5
- __all__ = (
6
- "MockRoute",
7
- "MockService",
8
- "JsonResponse",
9
- "start_service",
10
- )
@@ -1,80 +0,0 @@
1
- from asyncio import sleep
2
- from collections.abc import Iterable, Iterator, Mapping, MutableMapping
3
- from dataclasses import dataclass
4
- from http import HTTPStatus
5
- from typing import Any
6
-
7
- from aiohttp import hdrs
8
- from aiohttp.web_request import Request
9
- from aiohttp.web_response import Response
10
-
11
- from asyncly.srvmocker.models import BaseMockResponse
12
- from asyncly.srvmocker.serialization import JsonSerializer, Serializer
13
-
14
- TimeoutType = int | float
15
-
16
-
17
- @dataclass
18
- class ContentResponse(BaseMockResponse):
19
- body: Any = None
20
- status: int = HTTPStatus.OK
21
- headers: Mapping[str, str] | None = None
22
- serializer: Serializer | None = None
23
-
24
- async def response(self, request: Request) -> Response:
25
- headers: MutableMapping[str, str] = dict()
26
- if self.headers:
27
- headers.update(self.headers)
28
- if self.serializer:
29
- headers[hdrs.CONTENT_TYPE] = self.serializer.content_type
30
- return Response(
31
- status=self.status,
32
- body=self.serialize(),
33
- headers=headers,
34
- )
35
-
36
- def serialize(self) -> Any:
37
- if not self.serializer:
38
- return self.body
39
- return self.serializer.dumps(self.body)
40
-
41
-
42
- @dataclass
43
- class LatencyResponse(BaseMockResponse):
44
- wrapped: BaseMockResponse
45
- latency: TimeoutType
46
-
47
- async def response(self, request: Request) -> Response:
48
- await sleep(self.latency)
49
- return await self.wrapped.response(request)
50
-
51
-
52
- class MockSeqResponse(BaseMockResponse):
53
- responses: Iterator[BaseMockResponse]
54
-
55
- def __init__(self, responses: Iterable[BaseMockResponse]) -> None:
56
- self.responses = iter(responses)
57
-
58
- async def response(self, request: Request) -> Response:
59
- resp = next(self.responses)
60
- return await resp.response(request)
61
-
62
-
63
- class JsonResponse(BaseMockResponse):
64
- __content: ContentResponse
65
-
66
- def __init__(
67
- self,
68
- body: Any,
69
- status: int = HTTPStatus.OK,
70
- headers: Mapping[str, str] | None = None,
71
- ) -> None:
72
- self.__content = ContentResponse(
73
- body=body,
74
- status=status,
75
- headers=headers,
76
- serializer=JsonSerializer,
77
- )
78
-
79
- async def response(self, request: Request) -> Response:
80
- return await self.__content.response(request)
@@ -1,16 +0,0 @@
1
- import json
2
- from collections.abc import Callable
3
- from dataclasses import dataclass
4
- from typing import Any, Final
5
-
6
-
7
- @dataclass(frozen=True)
8
- class Serializer:
9
- dumps: Callable[[Any], str]
10
- content_type: str
11
-
12
-
13
- JsonSerializer: Final = Serializer(
14
- dumps=json.dumps,
15
- content_type="application/json",
16
- )
File without changes
File without changes