asyncly 0.3.4__tar.gz → 0.4.1__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.
- {asyncly-0.3.4 → asyncly-0.4.1}/.gitignore +2 -1
- {asyncly-0.3.4 → asyncly-0.4.1}/PKG-INFO +39 -4
- {asyncly-0.3.4 → asyncly-0.4.1}/README.rst +36 -2
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/client/base.py +9 -1
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/client/handlers/base.py +1 -1
- asyncly-0.4.1/asyncly/client/handlers/msgspec.py +49 -0
- asyncly-0.4.1/asyncly/srvmocker/__init__.py +14 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/srvmocker/models.py +2 -8
- asyncly-0.4.1/asyncly/srvmocker/responses/__init__.py +0 -0
- asyncly-0.4.1/asyncly/srvmocker/responses/base.py +10 -0
- asyncly-0.4.1/asyncly/srvmocker/responses/content.py +36 -0
- asyncly-0.4.1/asyncly/srvmocker/responses/json.py +30 -0
- asyncly-0.4.1/asyncly/srvmocker/responses/msgpack.py +30 -0
- asyncly-0.4.1/asyncly/srvmocker/responses/sequence.py +17 -0
- asyncly-0.4.1/asyncly/srvmocker/responses/timeout.py +19 -0
- asyncly-0.4.1/asyncly/srvmocker/responses/toml.py +30 -0
- asyncly-0.4.1/asyncly/srvmocker/responses/yaml.py +30 -0
- asyncly-0.4.1/asyncly/srvmocker/serialization/__init__.py +0 -0
- asyncly-0.4.1/asyncly/srvmocker/serialization/base.py +9 -0
- asyncly-0.4.1/asyncly/srvmocker/serialization/json.py +9 -0
- asyncly-0.4.1/asyncly/srvmocker/serialization/msgpack.py +10 -0
- asyncly-0.4.1/asyncly/srvmocker/serialization/toml.py +13 -0
- asyncly-0.4.1/asyncly/srvmocker/serialization/yaml.py +10 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/pyproject.toml +7 -3
- asyncly-0.3.4/asyncly/client/handlers/msgspec.py +0 -15
- asyncly-0.3.4/asyncly/srvmocker/__init__.py +0 -10
- asyncly-0.3.4/asyncly/srvmocker/responses.py +0 -80
- asyncly-0.3.4/asyncly/srvmocker/serialization.py +0 -16
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/__init__.py +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/client/__init__.py +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/client/handlers/__init__.py +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/client/handlers/exceptions.py +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/client/handlers/json.py +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/client/handlers/pydantic.py +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/client/timeout.py +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/py.typed +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/srvmocker/constants.py +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/srvmocker/handlers.py +0 -0
- {asyncly-0.3.4 → asyncly-0.4.1}/asyncly/srvmocker/service.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncly
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
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
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.10
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.11
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.12
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
26
|
Classifier: Topic :: Internet
|
|
26
27
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
27
28
|
Classifier: Topic :: Software Development
|
|
@@ -30,7 +31,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
30
31
|
Requires-Python: <4,>=3.10
|
|
31
32
|
Requires-Dist: aiohttp<4,>=3.9.5
|
|
32
33
|
Provides-Extra: msgspec
|
|
33
|
-
Requires-Dist: msgspec<0.
|
|
34
|
+
Requires-Dist: msgspec<0.20,>=0.19.0; extra == 'msgspec'
|
|
34
35
|
Provides-Extra: orjson
|
|
35
36
|
Requires-Dist: orjson<4,>=3.10.6; extra == 'orjson'
|
|
36
37
|
Provides-Extra: pydantic
|
|
@@ -98,7 +99,7 @@ Quick start guide
|
|
|
98
99
|
-----------------
|
|
99
100
|
|
|
100
101
|
HttpClient
|
|
101
|
-
|
|
102
|
+
~~~~~~~~~~
|
|
102
103
|
|
|
103
104
|
Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/catfact_client.py`_
|
|
104
105
|
|
|
@@ -130,6 +131,9 @@ Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/ca
|
|
|
130
131
|
Test Async Server for client
|
|
131
132
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
132
133
|
|
|
134
|
+
Example
|
|
135
|
+
*******
|
|
136
|
+
|
|
133
137
|
For the HTTP client, we create a server to which he will go and simulate real
|
|
134
138
|
responses. You can dynamically change the responses from the server in
|
|
135
139
|
a specific test.
|
|
@@ -191,12 +195,43 @@ Now we can use them in tests. See full example in `examples/test_catfact_client.
|
|
|
191
195
|
with pytest.raises(asyncio.TimeoutError):
|
|
192
196
|
await catfact_client.fetch_random_cat_fact(timeout=1)
|
|
193
197
|
|
|
198
|
+
Useful responses and serializers
|
|
199
|
+
********************************
|
|
200
|
+
|
|
201
|
+
- JsonResponse_: simple JSON response from any object.
|
|
202
|
+
You can setup status code and serializer for it. Using JsonSerializer_
|
|
203
|
+
|
|
204
|
+
- MsgpackResponse_: response in msgpack_ format with It's like JSON.
|
|
205
|
+
But fast and small. Using MsgpackSerializer_.
|
|
206
|
+
|
|
207
|
+
- SequenceResponse_: useful response if you want return different responses
|
|
208
|
+
on next request. Accepts BaseMockResponse_'s input.
|
|
209
|
+
|
|
210
|
+
- TimeoutResponse_: response with latency. For slow testing
|
|
211
|
+
|
|
212
|
+
- TomlResponse_: return TOML format text response. Using TomlSerializer_.
|
|
213
|
+
|
|
214
|
+
- YamlResponse_: return YAML format text response. Using YamlSerializer_.
|
|
194
215
|
|
|
195
216
|
.. _PyPI: https://pypi.org/
|
|
196
217
|
.. _aiohttp: https://pypi.org/project/aiohttp/
|
|
218
|
+
.. _msgpack: https://msgpack.org
|
|
197
219
|
.. _msgspec: https://github.com/jcrist/msgspec
|
|
198
220
|
.. _orjson: https://github.com/ijl/orjson
|
|
199
221
|
.. _pydantic: https://github.com/pydantic/pydantic
|
|
200
222
|
|
|
201
223
|
.. _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
|
|
224
|
+
.. _examples/test_catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/test_catfact_client.py
|
|
225
|
+
|
|
226
|
+
.. _BaseMockResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/base.py
|
|
227
|
+
.. _JsonResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/json.py
|
|
228
|
+
.. _MsgpackResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/msgpack.py
|
|
229
|
+
.. _SequenceResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/sequence.py
|
|
230
|
+
.. _TimeoutResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/timeout.py
|
|
231
|
+
.. _TomlResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/toml.py
|
|
232
|
+
.. _YamlResponse: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/responses/yaml.py
|
|
233
|
+
|
|
234
|
+
.. _JsonSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/json.py
|
|
235
|
+
.. _MsgpackSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/msgpack.py
|
|
236
|
+
.. _TomlSerializer: https://github.com/andy-takker/asyncly/blob/master/asyncly/srvmocker/serialization/toml.py
|
|
237
|
+
.. _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:
|
|
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
|
|
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,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)
|
|
File without changes
|
|
@@ -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
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "asyncly"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
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"
|
|
@@ -26,6 +26,7 @@ classifiers = [
|
|
|
26
26
|
"Programming Language :: Python :: 3.10",
|
|
27
27
|
"Programming Language :: Python :: 3.11",
|
|
28
28
|
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
29
30
|
"Topic :: Internet",
|
|
30
31
|
"Topic :: Internet :: WWW/HTTP",
|
|
31
32
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
@@ -35,7 +36,7 @@ classifiers = [
|
|
|
35
36
|
dependencies = ["aiohttp>=3.9.5,<4"]
|
|
36
37
|
|
|
37
38
|
[project.optional-dependencies]
|
|
38
|
-
msgspec = ["msgspec>=0.
|
|
39
|
+
msgspec = ["msgspec>=0.19.0,<0.20"]
|
|
39
40
|
pydantic = ["pydantic>=2.8.2,<3"]
|
|
40
41
|
orjson = ["orjson>=3.10.6,<4"]
|
|
41
42
|
|
|
@@ -47,13 +48,16 @@ Source = "https://github.com/andy-takker/asyncly"
|
|
|
47
48
|
[dependency-groups]
|
|
48
49
|
dev = [
|
|
49
50
|
"pre-commit>=3.7.1,<5.0.0",
|
|
50
|
-
"mypy>=1.
|
|
51
|
+
"mypy>=1.17.1,<2",
|
|
51
52
|
"ruff>=0.5.2,<0.9.0",
|
|
52
53
|
"restructuredtext-lint>=1.4.0,<2",
|
|
53
54
|
"pygments>=2.18.0,<3",
|
|
54
55
|
"pytest>=8.3.3,<9",
|
|
55
56
|
"pytest-asyncio>=0.24.0,<0.25",
|
|
56
57
|
"pytest-cov>=6.0.0,<7",
|
|
58
|
+
"types-pyyaml>=6.0.12.20250822",
|
|
59
|
+
"types-toml>=0.10.8.20240310",
|
|
60
|
+
"toml>=0.10.2",
|
|
57
61
|
]
|
|
58
62
|
|
|
59
63
|
[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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|