msra-codegen 0.1.0__py3-none-any.whl
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.
- msra_codegen/README.md +23 -0
- msra_codegen/__init__.py +6 -0
- msra_codegen/__main__.py +5 -0
- msra_codegen/bridge.py +29 -0
- msra_codegen/cli.py +105 -0
- msra_codegen/codegen_context.py +1690 -0
- msra_codegen/config.toml +164 -0
- msra_codegen/core_naming.py +155 -0
- msra_codegen/docs_generator.py +346 -0
- msra_codegen/file_utils.py +8 -0
- msra_codegen/funcresult.py +156 -0
- msra_codegen/generator.py +6 -0
- msra_codegen/generator_config.py +35 -0
- msra_codegen/github_workflows.py +129 -0
- msra_codegen/gitignore.py +31 -0
- msra_codegen/issue_templates.py +100 -0
- msra_codegen/logo_assets.py +99 -0
- msra_codegen/msra_serializer.py +205 -0
- msra_codegen/node_export.js +296 -0
- msra_codegen/package_metadata.py +306 -0
- msra_codegen/package_writer.py +175 -0
- msra_codegen/project_model.py +490 -0
- msra_codegen/python_formatting.py +88 -0
- msra_codegen/python_render.py +242 -0
- msra_codegen/readme_pipeline.py +519 -0
- msra_codegen/requirements.txt +5 -0
- msra_codegen/template_engine.py +26 -0
- msra_codegen/templates/Makefile.tpl +44 -0
- msra_codegen/templates/README.md.tpl +55 -0
- msra_codegen/templates/abstraction/__init__.py.tpl +188 -0
- msra_codegen/templates/abstraction/regexes.py.tpl +25 -0
- msra_codegen/templates/docs/requirements.txt.tpl +3 -0
- msra_codegen/templates/docs/source/Makefile.tpl +20 -0
- msra_codegen/templates/docs/source/api.rst.tpl +9 -0
- msra_codegen/templates/docs/source/conf.py.tpl +88 -0
- msra_codegen/templates/docs/source/index.rst.tpl +14 -0
- msra_codegen/templates/docs/source/module.rst.tpl +34 -0
- msra_codegen/templates/docs/source/quick_start.rst.tpl +19 -0
- msra_codegen/templates/endpoints_init.py.tpl +15 -0
- msra_codegen/templates/example.py.tpl +1 -0
- msra_codegen/templates/function.py.tpl +364 -0
- msra_codegen/templates/github/issue_templates/bug_report.yml.tpl +55 -0
- msra_codegen/templates/github/issue_templates/config.yml.tpl +8 -0
- msra_codegen/templates/github/issue_templates/documentation_issue.yml.tpl +33 -0
- msra_codegen/templates/github/issue_templates/feature_request.yml.tpl +36 -0
- msra_codegen/templates/github/workflows/publish.yml.tpl +100 -0
- msra_codegen/templates/github/workflows/source-sync.yml.tpl +177 -0
- msra_codegen/templates/github/workflows/tests.yml.tpl +69 -0
- msra_codegen/templates/gitignore.tpl +3 -0
- msra_codegen/templates/group.py.tpl +56 -0
- msra_codegen/templates/group_init.py.tpl +14 -0
- msra_codegen/templates/init.py.tpl +4 -0
- msra_codegen/templates/licenses/GPL-3.0-or-later.txt.tpl +674 -0
- msra_codegen/templates/licenses/MIT.txt.tpl +21 -0
- msra_codegen/templates/manager.py.tpl +257 -0
- msra_codegen/templates/pyproject.toml.tpl +38 -0
- msra_codegen/templates/tests/api_test.py.tpl +49 -0
- msra_codegen/templates/tests/conftest.py.tpl +21 -0
- msra_codegen/templates/variable.py.tpl +54 -0
- msra_codegen/tests_generator.py +988 -0
- msra_codegen/typespec.py +275 -0
- msra_codegen/validation.py +118 -0
- msra_codegen-0.1.0.dist-info/METADATA +47 -0
- msra_codegen-0.1.0.dist-info/RECORD +68 -0
- msra_codegen-0.1.0.dist-info/WHEEL +5 -0
- msra_codegen-0.1.0.dist-info/entry_points.txt +2 -0
- msra_codegen-0.1.0.dist-info/licenses/LICENSE +674 -0
- msra_codegen-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) {{ year }} {{ copyright_holders }}
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from time import perf_counter, time
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
{% if has_autotests %}
|
|
6
|
+
from human_requests import autotest
|
|
7
|
+
{% endif %}
|
|
8
|
+
{% if uses_classvar_import %}
|
|
9
|
+
from typing import ClassVar
|
|
10
|
+
{% endif %}
|
|
11
|
+
{% if uses_literal_import %}
|
|
12
|
+
from typing import Literal
|
|
13
|
+
{% endif %}
|
|
14
|
+
{% if imports.overload %}
|
|
15
|
+
from typing import overload
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% if imports.json %}
|
|
18
|
+
import json
|
|
19
|
+
{% endif %}
|
|
20
|
+
{% if imports.path %}
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
{% endif %}
|
|
23
|
+
{% if imports.re %}
|
|
24
|
+
import re
|
|
25
|
+
{% endif %}
|
|
26
|
+
{% if imports.urlencode %}
|
|
27
|
+
from urllib.parse import urlencode
|
|
28
|
+
{% endif %}
|
|
29
|
+
|
|
30
|
+
from aiohttp_retry import ExponentialRetry, RetryClient
|
|
31
|
+
from camoufox import AsyncCamoufox, DefaultAddons
|
|
32
|
+
from human_requests import HumanBrowser, HumanPage
|
|
33
|
+
from human_requests.abstraction import HttpMethod, Proxy, Warmup
|
|
34
|
+
{% if imports.method_pipeline_error %}
|
|
35
|
+
from human_requests.abstraction import MethodPipelineError
|
|
36
|
+
{% endif %}
|
|
37
|
+
{% if uses_warmup_error_import %}
|
|
38
|
+
from human_requests.abstraction import WarmupError
|
|
39
|
+
{% endif %}
|
|
40
|
+
from human_requests.network_analyzer.anomaly_sniffer import HeaderAnomalySniffer
|
|
41
|
+
|
|
42
|
+
from . import abstraction
|
|
43
|
+
{% for group in top_groups %}
|
|
44
|
+
from .endpoints.{{ group.module_name }} import {{ group.class_name }}
|
|
45
|
+
{% endfor %}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class {{ client_class_name }}:
|
|
50
|
+
{% if app_description %}
|
|
51
|
+
"""{{ app_description }}"""
|
|
52
|
+
|
|
53
|
+
{% endif %}
|
|
54
|
+
timeout_ms: int = {{ app.timeout_ms }}
|
|
55
|
+
"""Global timeout, in milliseconds, used by warmup and browser-backed requests."""
|
|
56
|
+
headless: bool = {{ "False" if app.disallow_headless else "True" }}
|
|
57
|
+
"""Whether the browser is started without a visible window."""
|
|
58
|
+
test_mode: bool = False
|
|
59
|
+
"""Enable the test-only warmup branch and its extra state."""
|
|
60
|
+
proxy: str | dict | Proxy | None = None
|
|
61
|
+
"""Proxy settings for browser startup and direct requests. When omitted or set to None, the client reads the proxy from the environment."""
|
|
62
|
+
browser_opts: dict[str, Any] | None = None
|
|
63
|
+
"""Extra keyword arguments forwarded to AsyncCamoufox during browser startup."""
|
|
64
|
+
{% if has_root_functions %}
|
|
65
|
+
_parent: Any = field(init=False, repr=False)
|
|
66
|
+
{% endif %}
|
|
67
|
+
|
|
68
|
+
{% for prefix in prefixes %}
|
|
69
|
+
{{ prefix.attr_name }}: ClassVar[str] = {{ prefix.value }}
|
|
70
|
+
{% endfor %}
|
|
71
|
+
|
|
72
|
+
{% for group in top_groups %}
|
|
73
|
+
{{ group.field_name }}: {{ group.class_name }} = field(init=False)
|
|
74
|
+
{% if group.description %}
|
|
75
|
+
"""{{ group.description }}"""
|
|
76
|
+
{% endif %}
|
|
77
|
+
{% endfor %}
|
|
78
|
+
|
|
79
|
+
def __post_init__(self):
|
|
80
|
+
self.proxy = Proxy.from_env() if self.proxy is None else self.proxy
|
|
81
|
+
browser_opts: dict[str, Any] = {} if self.browser_opts is None else dict(self.browser_opts)
|
|
82
|
+
self.browser_opts = browser_opts
|
|
83
|
+
{% if has_root_functions %}
|
|
84
|
+
self._parent = self
|
|
85
|
+
{% endif %}
|
|
86
|
+
self.session = None
|
|
87
|
+
self.ctx = None
|
|
88
|
+
self.page = None
|
|
89
|
+
self.unstandard_headers = {}
|
|
90
|
+
self.unstandard_urls = {}
|
|
91
|
+
|
|
92
|
+
{% for variable in variables %}
|
|
93
|
+
self.{{ variable.backing_name }} = None
|
|
94
|
+
{% endfor %}
|
|
95
|
+
|
|
96
|
+
{% for group in top_groups %}
|
|
97
|
+
self.{{ group.field_name }} = {{ group.class_name }}(self)
|
|
98
|
+
{% endfor %}
|
|
99
|
+
|
|
100
|
+
{% for func in functions %}
|
|
101
|
+
{{ func.code }}
|
|
102
|
+
|
|
103
|
+
{% endfor %}
|
|
104
|
+
async def __aenter__(self):
|
|
105
|
+
await self._warmup()
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
async def _warmup(self) -> None:
|
|
109
|
+
{% if app.disallow_headless %}
|
|
110
|
+
if self.headless:
|
|
111
|
+
raise ValueError("headless=True is not allowed when @DisallowHeadless is set")
|
|
112
|
+
{% endif %}
|
|
113
|
+
px = self.proxy if isinstance(self.proxy, Proxy) else Proxy(self.proxy)
|
|
114
|
+
browser_opts: dict[str, Any] = {} if self.browser_opts is None else dict(self.browser_opts)
|
|
115
|
+
br = await AsyncCamoufox(
|
|
116
|
+
headless=self.headless,
|
|
117
|
+
proxy=px.as_dict(),
|
|
118
|
+
humanize={{ app.humanize }},
|
|
119
|
+
**browser_opts,
|
|
120
|
+
block_images={{ app.block_images }},
|
|
121
|
+
i_know_what_im_doing=True,
|
|
122
|
+
exclude_addons=[DefaultAddons.UBO],
|
|
123
|
+
).start()
|
|
124
|
+
|
|
125
|
+
self.session = HumanBrowser.replace(cast(Any, br))
|
|
126
|
+
self.ctx = await self.session.new_context()
|
|
127
|
+
self.page = await self.ctx.new_page()
|
|
128
|
+
self.page.on_error_screenshot_path = {{ warmup.on_error_screenshot_path }}
|
|
129
|
+
|
|
130
|
+
{% if warmup.headers_sniffer %}
|
|
131
|
+
sniffer = HeaderAnomalySniffer(
|
|
132
|
+
include_subresources=True,
|
|
133
|
+
)
|
|
134
|
+
await sniffer.start(self.ctx)
|
|
135
|
+
|
|
136
|
+
{% else %}
|
|
137
|
+
sniffer = None
|
|
138
|
+
|
|
139
|
+
{% endif %}
|
|
140
|
+
{% if warmup.script_module and warmup.script_function and warmup.script_path_expr %}
|
|
141
|
+
warmup = self._make_warmup_context(page=self.page, sniffer=sniffer)
|
|
142
|
+
from .{{ warmup.script_module }} import {{ warmup.script_function }} as warmup_runner
|
|
143
|
+
try:
|
|
144
|
+
await warmup_runner(warmup)
|
|
145
|
+
except WarmupError:
|
|
146
|
+
raise
|
|
147
|
+
except Exception as exc:
|
|
148
|
+
raise WarmupError(str(exc)) from exc
|
|
149
|
+
{% endif %}
|
|
150
|
+
|
|
151
|
+
result_sniffer: dict[str, Any] = await sniffer.complete() if sniffer else {"request": {}}
|
|
152
|
+
|
|
153
|
+
result = defaultdict(set)
|
|
154
|
+
|
|
155
|
+
for _url, headers in result_sniffer.get("request", {}).items():
|
|
156
|
+
for header, values in headers.items():
|
|
157
|
+
result[header].update(values)
|
|
158
|
+
|
|
159
|
+
self.unstandard_headers = {k: list(v)[0] for k, v in result.items()}
|
|
160
|
+
{% for variable in variables %}
|
|
161
|
+
{{ variable.warmup_code }}
|
|
162
|
+
|
|
163
|
+
{% endfor %}
|
|
164
|
+
self.unstandard_urls = result_sniffer.get("request", {})
|
|
165
|
+
|
|
166
|
+
async def __aexit__(self, *exc):
|
|
167
|
+
await self.close()
|
|
168
|
+
|
|
169
|
+
async def close(self):
|
|
170
|
+
await self.session.close()
|
|
171
|
+
|
|
172
|
+
def _make_warmup_context(
|
|
173
|
+
self,
|
|
174
|
+
*,
|
|
175
|
+
page: HumanPage,
|
|
176
|
+
sniffer: HeaderAnomalySniffer | None,
|
|
177
|
+
) -> Warmup:
|
|
178
|
+
return Warmup(
|
|
179
|
+
browser=self.session,
|
|
180
|
+
context=self.ctx,
|
|
181
|
+
page=page,
|
|
182
|
+
sniffer=sniffer,
|
|
183
|
+
timeout_ms=self.timeout_ms,
|
|
184
|
+
test_mode=self.test_mode,
|
|
185
|
+
prefixes={
|
|
186
|
+
{% for prefix in prefixes %}
|
|
187
|
+
{{ prefix.name | tojson }}: self.{{ prefix.attr_name }},
|
|
188
|
+
{% endfor %}
|
|
189
|
+
},
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
async def _create_pipeline_sniffer(self) -> HeaderAnomalySniffer:
|
|
193
|
+
sniffer = HeaderAnomalySniffer(
|
|
194
|
+
include_subresources=True,
|
|
195
|
+
)
|
|
196
|
+
await sniffer.start(self.ctx)
|
|
197
|
+
return sniffer
|
|
198
|
+
|
|
199
|
+
{% for variable in variables %}
|
|
200
|
+
{{ variable.code }}
|
|
201
|
+
|
|
202
|
+
{% endfor %}
|
|
203
|
+
async def _request(
|
|
204
|
+
self,
|
|
205
|
+
method: HttpMethod,
|
|
206
|
+
url: str,
|
|
207
|
+
*,
|
|
208
|
+
json_body: Any | None = None,
|
|
209
|
+
mode: str | None = None,
|
|
210
|
+
credentials: str | None = None,
|
|
211
|
+
referrer: str | None = None,
|
|
212
|
+
headers: dict[str, Any] | None = None,
|
|
213
|
+
) -> abstraction.Output:
|
|
214
|
+
request_headers = headers if headers is not None else {{ request.headers_expr }}
|
|
215
|
+
fetch_kwargs: dict[str, Any] = {
|
|
216
|
+
"url": url,
|
|
217
|
+
"method": method,
|
|
218
|
+
"body": json_body,
|
|
219
|
+
"mode": mode if mode is not None else {{ request.cors_mode_expr }},
|
|
220
|
+
"credentials": credentials if credentials is not None else {{ request.credentials_expr }},
|
|
221
|
+
"timeout_ms": self.timeout_ms,
|
|
222
|
+
"headers": request_headers,
|
|
223
|
+
}
|
|
224
|
+
if referrer is not None:
|
|
225
|
+
fetch_kwargs["referrer"] = referrer
|
|
226
|
+
response = await self.page.fetch(**fetch_kwargs)
|
|
227
|
+
return abstraction.Output.from_fetch_response(response)
|
|
228
|
+
|
|
229
|
+
async def _direct_request(
|
|
230
|
+
self,
|
|
231
|
+
url: str,
|
|
232
|
+
*,
|
|
233
|
+
retry_attempts: int = 3,
|
|
234
|
+
timeout: float = 10,
|
|
235
|
+
) -> abstraction.Output:
|
|
236
|
+
start_t = perf_counter()
|
|
237
|
+
retry_options = ExponentialRetry(
|
|
238
|
+
attempts=retry_attempts, start_timeout=3.0, max_timeout=timeout
|
|
239
|
+
)
|
|
240
|
+
px = self.proxy if isinstance(self.proxy, Proxy) else Proxy(self.proxy)
|
|
241
|
+
async with (
|
|
242
|
+
RetryClient(retry_options=retry_options) as retry_client,
|
|
243
|
+
retry_client.get(url, raise_for_status=True, proxy=px.as_str()) as resp,
|
|
244
|
+
):
|
|
245
|
+
body = await resp.read()
|
|
246
|
+
return abstraction.Output.from_raw(
|
|
247
|
+
body,
|
|
248
|
+
url=str(resp.url),
|
|
249
|
+
headers=dict(resp.headers),
|
|
250
|
+
status_code=resp.status,
|
|
251
|
+
status_text=resp.reason,
|
|
252
|
+
redirected=bool(resp.history),
|
|
253
|
+
response_type="basic",
|
|
254
|
+
duration=perf_counter() - start_t,
|
|
255
|
+
end_time=time(),
|
|
256
|
+
page=self.page,
|
|
257
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "{{ package_name }}"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = {{ description }}
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = "{{ requires_python }}"
|
|
11
|
+
license = "{{ license }}"
|
|
12
|
+
{{ authors_block }}
|
|
13
|
+
{{ keywords_block }}
|
|
14
|
+
{{ classifiers_block }}
|
|
15
|
+
{{ dependencies_block }}
|
|
16
|
+
{{ mypy_block }}
|
|
17
|
+
{{ ruff_block }}
|
|
18
|
+
|
|
19
|
+
[tool.setuptools]
|
|
20
|
+
include-package-data = true
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.package-data]
|
|
23
|
+
{{ package_name }} = ["extractors/*.js", "extractors/**/*.js"]
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.dynamic]
|
|
26
|
+
version = { attr = "{{ package_name }}.__version__" }
|
|
27
|
+
|
|
28
|
+
[tool.pytest.ini_options]
|
|
29
|
+
pythonpath = ["."]
|
|
30
|
+
testpaths = ["tests"]
|
|
31
|
+
python_files = ["*_test.py", "*_tests.py"]
|
|
32
|
+
filterwarnings = [
|
|
33
|
+
"ignore::pytest.PytestUnraisableExceptionWarning",
|
|
34
|
+
"ignore:Event loop is closed:RuntimeWarning",
|
|
35
|
+
]
|
|
36
|
+
anyio_mode = "auto"
|
|
37
|
+
autotest_start_class = "{{ autotest_start_class }}"
|
|
38
|
+
addopts = "-v --tb=short --disable-warnings"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
{% if hooks or providers or data_cases %}
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
{% endif %}
|
|
6
|
+
|
|
7
|
+
{% if providers %}
|
|
8
|
+
import pytest
|
|
9
|
+
{% endif %}
|
|
10
|
+
{% if hooks or providers %}
|
|
11
|
+
from human_requests import autotest_depends_on, autotest_hook, autotest_params
|
|
12
|
+
{% endif %}
|
|
13
|
+
{% if data_cases %}
|
|
14
|
+
from human_requests import autotest_data
|
|
15
|
+
{% endif %}
|
|
16
|
+
|
|
17
|
+
{% for import in imports %}
|
|
18
|
+
from {{ import.module }} import {{ import.class_name }}
|
|
19
|
+
{% endfor %}
|
|
20
|
+
|
|
21
|
+
{% if hooks or providers or data_cases %}
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
{% if hooks %}
|
|
24
|
+
from human_requests.autotest import AutotestContext
|
|
25
|
+
{% endif %}
|
|
26
|
+
{% if providers %}
|
|
27
|
+
from human_requests.autotest import AutotestCallContext
|
|
28
|
+
{% endif %}
|
|
29
|
+
{% if data_cases %}
|
|
30
|
+
from human_requests.autotest import AutotestDataContext
|
|
31
|
+
{% endif %}
|
|
32
|
+
|
|
33
|
+
{% endif %}
|
|
34
|
+
{% for hook in hooks %}
|
|
35
|
+
{{ hook.hook_code }}
|
|
36
|
+
|
|
37
|
+
{% endfor %}
|
|
38
|
+
{% for provider in providers %}
|
|
39
|
+
{{ provider.provider_code }}
|
|
40
|
+
|
|
41
|
+
{% endfor %}
|
|
42
|
+
{% for data_case in data_cases %}
|
|
43
|
+
{{ data_case.code }}
|
|
44
|
+
|
|
45
|
+
{% endfor %}
|
|
46
|
+
{% for manual_test in manual_tests %}
|
|
47
|
+
{{ manual_test.code }}
|
|
48
|
+
|
|
49
|
+
{% endfor %}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from {{ package_name }} import {{ client_class_name }}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture(scope="session")
|
|
9
|
+
def anyio_backend():
|
|
10
|
+
return "asyncio"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture(scope="session")
|
|
14
|
+
async def api():
|
|
15
|
+
async with {{ client_class_name }}(test_mode=True) as client:
|
|
16
|
+
yield client
|
|
17
|
+
{{ "\n" if fixtures else "" }}
|
|
18
|
+
{% for fixture in fixtures %}
|
|
19
|
+
{{ fixture.code }}
|
|
20
|
+
{{ "\n" if not loop.last else "" }}
|
|
21
|
+
{% endfor %}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
@property
|
|
2
|
+
def {{ name }}(self) -> {{ getter_return }}:
|
|
3
|
+
{% if description %}
|
|
4
|
+
"""{{ description }}"""
|
|
5
|
+
{% endif %}
|
|
6
|
+
return self.{{ backing_name }}
|
|
7
|
+
|
|
8
|
+
{% if setter_enabled %}
|
|
9
|
+
@{{ name }}.setter
|
|
10
|
+
def {{ name }}(self, value: {{ getter_return }}) -> None:
|
|
11
|
+
{% if has_null %}
|
|
12
|
+
if value is None:
|
|
13
|
+
self.{{ backing_name }} = None
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% if has_integer %}
|
|
18
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
19
|
+
raise TypeError("`{{ name }}` must be int")
|
|
20
|
+
{% elif has_boolean %}
|
|
21
|
+
if not isinstance(value, bool):
|
|
22
|
+
raise TypeError("`{{ name }}` must be bool")
|
|
23
|
+
{% elif has_number %}
|
|
24
|
+
if not isinstance(value, (int, float)) or isinstance(value, bool):
|
|
25
|
+
raise TypeError("`{{ name }}` must be number")
|
|
26
|
+
{% else %}
|
|
27
|
+
if not isinstance(value, str):
|
|
28
|
+
raise TypeError("`{{ name }}` must be str")
|
|
29
|
+
{% endif %}
|
|
30
|
+
{% if match_check_expr %}
|
|
31
|
+
if not ({{ match_check_expr }}):
|
|
32
|
+
{% if match_error %}
|
|
33
|
+
raise ValueError({{ match_error }})
|
|
34
|
+
{% else %}
|
|
35
|
+
raise ValueError("`{{ name }}` does not match the expected format")
|
|
36
|
+
{% endif %}
|
|
37
|
+
{% elif match_range_lower is not none or match_range_upper is not none %}
|
|
38
|
+
{% if match_range_lower is not none and match_range_upper is not none %}
|
|
39
|
+
if float(value) < {{ match_range_lower }} or float(value) > {{ match_range_upper }}:
|
|
40
|
+
raise ValueError("`{{ name }}` must be between {{ match_range_lower }} and {{ match_range_upper }}")
|
|
41
|
+
{% elif match_range_lower is not none %}
|
|
42
|
+
if float(value) < {{ match_range_lower }}:
|
|
43
|
+
raise ValueError("`{{ name }}` must be greater than or equal to {{ match_range_lower }}")
|
|
44
|
+
{% else %}
|
|
45
|
+
if float(value) > {{ match_range_upper }}:
|
|
46
|
+
raise ValueError("`{{ name }}` must be less than or equal to {{ match_range_upper }}")
|
|
47
|
+
{% endif %}
|
|
48
|
+
{% elif match_values_expr %}
|
|
49
|
+
allowed_values = {{ match_values_expr }}
|
|
50
|
+
if value not in allowed_values:
|
|
51
|
+
raise ValueError(f"`{{ name }}` must be one of {allowed_values}")
|
|
52
|
+
{% endif %}
|
|
53
|
+
self.{{ backing_name }} = value
|
|
54
|
+
{% endif %}
|