asyncly 0.3.3__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.
- asyncly-0.4.0/.gitignore +165 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/PKG-INFO +48 -16
- {asyncly-0.3.3 → asyncly-0.4.0}/README.rst +36 -2
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/client/base.py +9 -1
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/client/handlers/base.py +1 -1
- asyncly-0.4.0/asyncly/client/handlers/msgspec.py +49 -0
- asyncly-0.4.0/asyncly/srvmocker/__init__.py +14 -0
- asyncly-0.4.0/asyncly/srvmocker/constants.py +5 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/srvmocker/handlers.py +2 -1
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/srvmocker/models.py +5 -8
- asyncly-0.4.0/asyncly/srvmocker/responses/__init__.py +0 -0
- asyncly-0.4.0/asyncly/srvmocker/responses/base.py +10 -0
- asyncly-0.4.0/asyncly/srvmocker/responses/content.py +36 -0
- asyncly-0.4.0/asyncly/srvmocker/responses/json.py +30 -0
- asyncly-0.4.0/asyncly/srvmocker/responses/msgpack.py +30 -0
- asyncly-0.4.0/asyncly/srvmocker/responses/sequence.py +17 -0
- asyncly-0.4.0/asyncly/srvmocker/responses/timeout.py +19 -0
- asyncly-0.4.0/asyncly/srvmocker/responses/toml.py +30 -0
- asyncly-0.4.0/asyncly/srvmocker/responses/yaml.py +30 -0
- asyncly-0.4.0/asyncly/srvmocker/serialization/__init__.py +0 -0
- asyncly-0.4.0/asyncly/srvmocker/serialization/base.py +9 -0
- asyncly-0.4.0/asyncly/srvmocker/serialization/json.py +9 -0
- asyncly-0.4.0/asyncly/srvmocker/serialization/msgpack.py +10 -0
- asyncly-0.4.0/asyncly/srvmocker/serialization/toml.py +13 -0
- asyncly-0.4.0/asyncly/srvmocker/serialization/yaml.py +10 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/srvmocker/service.py +12 -7
- {asyncly-0.3.3 → asyncly-0.4.0}/pyproject.toml +42 -35
- asyncly-0.3.3/asyncly/client/handlers/msgspec.py +0 -15
- asyncly-0.3.3/asyncly/srvmocker/__init__.py +0 -10
- asyncly-0.3.3/asyncly/srvmocker/responses.py +0 -80
- asyncly-0.3.3/asyncly/srvmocker/serialization.py +0 -16
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/__init__.py +0 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/client/__init__.py +0 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/client/handlers/__init__.py +0 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/client/handlers/exceptions.py +0 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/client/handlers/json.py +0 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/client/handlers/pydantic.py +0 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/client/timeout.py +0 -0
- {asyncly-0.3.3 → asyncly-0.4.0}/asyncly/py.typed +0 -0
asyncly-0.4.0/.gitignore
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
junit.xml
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
#Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# poetry
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
103
|
+
#poetry.lock
|
|
104
|
+
|
|
105
|
+
# pdm
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
107
|
+
#pdm.lock
|
|
108
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
109
|
+
# in version control.
|
|
110
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
111
|
+
.pdm.toml
|
|
112
|
+
.pdm-python
|
|
113
|
+
.pdm-build/
|
|
114
|
+
|
|
115
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
116
|
+
__pypackages__/
|
|
117
|
+
|
|
118
|
+
# Celery stuff
|
|
119
|
+
celerybeat-schedule
|
|
120
|
+
celerybeat.pid
|
|
121
|
+
|
|
122
|
+
# SageMath parsed files
|
|
123
|
+
*.sage.py
|
|
124
|
+
|
|
125
|
+
# Environments
|
|
126
|
+
.env
|
|
127
|
+
.venv
|
|
128
|
+
env/
|
|
129
|
+
venv/
|
|
130
|
+
ENV/
|
|
131
|
+
env.bak/
|
|
132
|
+
venv.bak/
|
|
133
|
+
|
|
134
|
+
# Spyder project settings
|
|
135
|
+
.spyderproject
|
|
136
|
+
.spyproject
|
|
137
|
+
|
|
138
|
+
# Rope project settings
|
|
139
|
+
.ropeproject
|
|
140
|
+
|
|
141
|
+
# mkdocs documentation
|
|
142
|
+
/site
|
|
143
|
+
|
|
144
|
+
# mypy
|
|
145
|
+
.mypy_cache/
|
|
146
|
+
.dmypy.json
|
|
147
|
+
dmypy.json
|
|
148
|
+
|
|
149
|
+
# Pyre type checker
|
|
150
|
+
.pyre/
|
|
151
|
+
|
|
152
|
+
# pytype static type analyzer
|
|
153
|
+
.pytype/
|
|
154
|
+
|
|
155
|
+
# Cython debug symbols
|
|
156
|
+
cython_debug/
|
|
157
|
+
|
|
158
|
+
# PyCharm
|
|
159
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
160
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
161
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
162
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
163
|
+
.idea/
|
|
164
|
+
.vscode/
|
|
165
|
+
.DS_Store
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: asyncly
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Simple HTTP client and server for your integrations based on aiohttp
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Author: Sergey Natalenko
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
Project-URL: Homepage, https://github.com/andy-takker/asyncly
|
|
6
|
+
Project-URL: Source, https://github.com/andy-takker/asyncly
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/andy-takker/asyncly/issues
|
|
8
|
+
Author-email: Sergey Natalenko <sergey.natalenko@mail.ru>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: aiohttp,client,http
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Framework :: AsyncIO
|
|
13
13
|
Classifier: Framework :: Pytest
|
|
@@ -22,21 +22,19 @@ 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
|
|
26
25
|
Classifier: Topic :: Internet
|
|
27
26
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
28
27
|
Classifier: Topic :: Software Development
|
|
29
28
|
Classifier: Topic :: Software Development :: Libraries
|
|
30
29
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
|
+
Requires-Python: <4,>=3.10
|
|
31
|
+
Requires-Dist: aiohttp<4,>=3.9.5
|
|
31
32
|
Provides-Extra: msgspec
|
|
33
|
+
Requires-Dist: msgspec<0.20,>=0.19.0; extra == 'msgspec'
|
|
32
34
|
Provides-Extra: orjson
|
|
35
|
+
Requires-Dist: orjson<4,>=3.10.6; extra == 'orjson'
|
|
33
36
|
Provides-Extra: pydantic
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist: msgspec (>=0.18.6,<0.19.0) ; extra == "msgspec"
|
|
36
|
-
Requires-Dist: orjson (>=3.10.6,<4.0.0) ; extra == "orjson"
|
|
37
|
-
Requires-Dist: pydantic (>=2.8.2,<3.0.0) ; extra == "pydantic"
|
|
38
|
-
Project-URL: Bug Tracker, https://github.com/andy-takker/asyncly/issues
|
|
39
|
-
Project-URL: Source, https://github.com/andy-takker/asyncly
|
|
37
|
+
Requires-Dist: pydantic<3,>=2.8.2; extra == 'pydantic'
|
|
40
38
|
Description-Content-Type: text/x-rst
|
|
41
39
|
|
|
42
40
|
Asyncly
|
|
@@ -100,7 +98,7 @@ Quick start guide
|
|
|
100
98
|
-----------------
|
|
101
99
|
|
|
102
100
|
HttpClient
|
|
103
|
-
|
|
101
|
+
~~~~~~~~~~
|
|
104
102
|
|
|
105
103
|
Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/catfact_client.py`_
|
|
106
104
|
|
|
@@ -132,6 +130,9 @@ Simple HTTP Client for `https://catfact.ninja`. See full example in `examples/ca
|
|
|
132
130
|
Test Async Server for client
|
|
133
131
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
134
132
|
|
|
133
|
+
Example
|
|
134
|
+
*******
|
|
135
|
+
|
|
135
136
|
For the HTTP client, we create a server to which he will go and simulate real
|
|
136
137
|
responses. You can dynamically change the responses from the server in
|
|
137
138
|
a specific test.
|
|
@@ -193,12 +194,43 @@ Now we can use them in tests. See full example in `examples/test_catfact_client.
|
|
|
193
194
|
with pytest.raises(asyncio.TimeoutError):
|
|
194
195
|
await catfact_client.fetch_random_cat_fact(timeout=1)
|
|
195
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_.
|
|
196
214
|
|
|
197
215
|
.. _PyPI: https://pypi.org/
|
|
198
216
|
.. _aiohttp: https://pypi.org/project/aiohttp/
|
|
217
|
+
.. _msgpack: https://msgpack.org
|
|
199
218
|
.. _msgspec: https://github.com/jcrist/msgspec
|
|
200
219
|
.. _orjson: https://github.com/ijl/orjson
|
|
201
220
|
.. _pydantic: https://github.com/pydantic/pydantic
|
|
202
221
|
|
|
203
222
|
.. _examples/catfact_client.py: https://github.com/andy-takker/asyncly/blob/master/examples/catfact_client.py
|
|
204
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:
|
|
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
|
+
)
|
|
@@ -3,6 +3,7 @@ from collections.abc import Awaitable, Callable
|
|
|
3
3
|
from aiohttp.web_request import Request
|
|
4
4
|
from aiohttp.web_response import Response
|
|
5
5
|
|
|
6
|
+
from asyncly.srvmocker.constants import SERVICE_KEY
|
|
6
7
|
from asyncly.srvmocker.models import MockService, RequestHistory
|
|
7
8
|
|
|
8
9
|
|
|
@@ -12,7 +13,7 @@ def get_default_handler(handler_name: str) -> Callable[[Request], Awaitable[Resp
|
|
|
12
13
|
request=request,
|
|
13
14
|
body=await request.read(),
|
|
14
15
|
)
|
|
15
|
-
context: MockService = request.app[
|
|
16
|
+
context: MockService = request.app[SERVICE_KEY]
|
|
16
17
|
context.history.append(history)
|
|
17
18
|
context.history_map[handler_name].append(history)
|
|
18
19
|
handler = context.handlers[handler_name]
|
|
@@ -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]
|
|
@@ -35,3 +29,6 @@ class MockService:
|
|
|
35
29
|
|
|
36
30
|
def register(self, name: str, resp: BaseMockResponse) -> None:
|
|
37
31
|
self.handlers[name] = resp
|
|
32
|
+
|
|
33
|
+
def set_url(self, url: URL) -> None:
|
|
34
|
+
object.__setattr__(self, "url", url)
|
|
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
|
+
)
|
|
@@ -4,7 +4,9 @@ from contextlib import asynccontextmanager
|
|
|
4
4
|
|
|
5
5
|
from aiohttp.test_utils import TestServer
|
|
6
6
|
from aiohttp.web_app import Application
|
|
7
|
+
from yarl import URL
|
|
7
8
|
|
|
9
|
+
from asyncly.srvmocker.constants import SERVICE_KEY
|
|
8
10
|
from asyncly.srvmocker.handlers import get_default_handler
|
|
9
11
|
from asyncly.srvmocker.models import MockRoute, MockService
|
|
10
12
|
|
|
@@ -14,6 +16,13 @@ async def start_service(
|
|
|
14
16
|
routes: Iterable[MockRoute],
|
|
15
17
|
) -> AsyncGenerator[MockService, None]:
|
|
16
18
|
app = Application()
|
|
19
|
+
mock_service = MockService(
|
|
20
|
+
history=list(),
|
|
21
|
+
history_map=defaultdict(list),
|
|
22
|
+
url=URL(),
|
|
23
|
+
handlers=dict(),
|
|
24
|
+
)
|
|
25
|
+
app[SERVICE_KEY] = mock_service
|
|
17
26
|
server = TestServer(app)
|
|
18
27
|
for route in routes:
|
|
19
28
|
app.router.add_route(
|
|
@@ -22,13 +31,9 @@ async def start_service(
|
|
|
22
31
|
handler=get_default_handler(route.handler_name),
|
|
23
32
|
)
|
|
24
33
|
await server.start_server()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
url=server.make_url(""),
|
|
29
|
-
handlers=dict(),
|
|
30
|
-
)
|
|
31
|
-
app["service"] = mock_service
|
|
34
|
+
|
|
35
|
+
mock_service.set_url(server.make_url(""))
|
|
36
|
+
|
|
32
37
|
try:
|
|
33
38
|
yield mock_service
|
|
34
39
|
finally:
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
[
|
|
1
|
+
[project]
|
|
2
2
|
name = "asyncly"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Simple HTTP client and server for your integrations based on aiohttp"
|
|
5
|
-
authors = ["Sergey Natalenko
|
|
6
|
-
|
|
7
|
-
homepage = "https://github.com/andy-takker/asyncly"
|
|
5
|
+
authors = [{ name = "Sergey Natalenko", email = "sergey.natalenko@mail.ru" }]
|
|
6
|
+
requires-python = ">=3.10, <4"
|
|
8
7
|
readme = "README.rst"
|
|
9
|
-
|
|
8
|
+
license = "MIT"
|
|
9
|
+
keywords = [
|
|
10
|
+
"aiohttp",
|
|
11
|
+
"http",
|
|
12
|
+
"client",
|
|
13
|
+
]
|
|
10
14
|
classifiers = [
|
|
11
15
|
"Development Status :: 4 - Beta",
|
|
12
16
|
"Framework :: AsyncIO",
|
|
@@ -28,39 +32,42 @@ classifiers = [
|
|
|
28
32
|
"Topic :: Software Development :: Libraries",
|
|
29
33
|
"Topic :: Software Development",
|
|
30
34
|
]
|
|
31
|
-
|
|
32
|
-
{ include = "asyncly" },
|
|
33
|
-
]
|
|
35
|
+
dependencies = ["aiohttp>=3.9.5,<4"]
|
|
34
36
|
|
|
35
|
-
[
|
|
36
|
-
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
msgspec = ["msgspec>=0.19.0,<0.20"]
|
|
39
|
+
pydantic = ["pydantic>=2.8.2,<3"]
|
|
40
|
+
orjson = ["orjson>=3.10.6,<4"]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/andy-takker/asyncly"
|
|
44
|
+
Source = "https://github.com/andy-takker/asyncly"
|
|
37
45
|
"Bug Tracker" = "https://github.com/andy-takker/asyncly/issues"
|
|
38
46
|
|
|
39
|
-
[
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
pytest-cov = "^6.0.0"
|
|
47
|
+
[dependency-groups]
|
|
48
|
+
dev = [
|
|
49
|
+
"pre-commit>=3.7.1,<5.0.0",
|
|
50
|
+
"mypy>=1.17.1,<2",
|
|
51
|
+
"ruff>=0.5.2,<0.9.0",
|
|
52
|
+
"restructuredtext-lint>=1.4.0,<2",
|
|
53
|
+
"pygments>=2.18.0,<3",
|
|
54
|
+
"pytest>=8.3.3,<9",
|
|
55
|
+
"pytest-asyncio>=0.24.0,<0.25",
|
|
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",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[tool.hatch.build.targets.sdist]
|
|
63
|
+
include = ["asyncly"]
|
|
64
|
+
|
|
65
|
+
[tool.hatch.build.targets.wheel]
|
|
66
|
+
include = ["asyncly"]
|
|
60
67
|
|
|
61
68
|
[build-system]
|
|
62
|
-
requires = ["
|
|
63
|
-
build-backend = "
|
|
69
|
+
requires = ["hatchling"]
|
|
70
|
+
build-backend = "hatchling.build"
|
|
64
71
|
|
|
65
72
|
[tool.pytest.ini_options]
|
|
66
73
|
asyncio_mode = "auto"
|
|
@@ -134,4 +141,4 @@ module = [
|
|
|
134
141
|
"msgspec.*",
|
|
135
142
|
"orjson.*",
|
|
136
143
|
]
|
|
137
|
-
ignore_missing_imports = true
|
|
144
|
+
ignore_missing_imports = true
|
|
@@ -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
|