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.
Files changed (68) hide show
  1. msra_codegen/README.md +23 -0
  2. msra_codegen/__init__.py +6 -0
  3. msra_codegen/__main__.py +5 -0
  4. msra_codegen/bridge.py +29 -0
  5. msra_codegen/cli.py +105 -0
  6. msra_codegen/codegen_context.py +1690 -0
  7. msra_codegen/config.toml +164 -0
  8. msra_codegen/core_naming.py +155 -0
  9. msra_codegen/docs_generator.py +346 -0
  10. msra_codegen/file_utils.py +8 -0
  11. msra_codegen/funcresult.py +156 -0
  12. msra_codegen/generator.py +6 -0
  13. msra_codegen/generator_config.py +35 -0
  14. msra_codegen/github_workflows.py +129 -0
  15. msra_codegen/gitignore.py +31 -0
  16. msra_codegen/issue_templates.py +100 -0
  17. msra_codegen/logo_assets.py +99 -0
  18. msra_codegen/msra_serializer.py +205 -0
  19. msra_codegen/node_export.js +296 -0
  20. msra_codegen/package_metadata.py +306 -0
  21. msra_codegen/package_writer.py +175 -0
  22. msra_codegen/project_model.py +490 -0
  23. msra_codegen/python_formatting.py +88 -0
  24. msra_codegen/python_render.py +242 -0
  25. msra_codegen/readme_pipeline.py +519 -0
  26. msra_codegen/requirements.txt +5 -0
  27. msra_codegen/template_engine.py +26 -0
  28. msra_codegen/templates/Makefile.tpl +44 -0
  29. msra_codegen/templates/README.md.tpl +55 -0
  30. msra_codegen/templates/abstraction/__init__.py.tpl +188 -0
  31. msra_codegen/templates/abstraction/regexes.py.tpl +25 -0
  32. msra_codegen/templates/docs/requirements.txt.tpl +3 -0
  33. msra_codegen/templates/docs/source/Makefile.tpl +20 -0
  34. msra_codegen/templates/docs/source/api.rst.tpl +9 -0
  35. msra_codegen/templates/docs/source/conf.py.tpl +88 -0
  36. msra_codegen/templates/docs/source/index.rst.tpl +14 -0
  37. msra_codegen/templates/docs/source/module.rst.tpl +34 -0
  38. msra_codegen/templates/docs/source/quick_start.rst.tpl +19 -0
  39. msra_codegen/templates/endpoints_init.py.tpl +15 -0
  40. msra_codegen/templates/example.py.tpl +1 -0
  41. msra_codegen/templates/function.py.tpl +364 -0
  42. msra_codegen/templates/github/issue_templates/bug_report.yml.tpl +55 -0
  43. msra_codegen/templates/github/issue_templates/config.yml.tpl +8 -0
  44. msra_codegen/templates/github/issue_templates/documentation_issue.yml.tpl +33 -0
  45. msra_codegen/templates/github/issue_templates/feature_request.yml.tpl +36 -0
  46. msra_codegen/templates/github/workflows/publish.yml.tpl +100 -0
  47. msra_codegen/templates/github/workflows/source-sync.yml.tpl +177 -0
  48. msra_codegen/templates/github/workflows/tests.yml.tpl +69 -0
  49. msra_codegen/templates/gitignore.tpl +3 -0
  50. msra_codegen/templates/group.py.tpl +56 -0
  51. msra_codegen/templates/group_init.py.tpl +14 -0
  52. msra_codegen/templates/init.py.tpl +4 -0
  53. msra_codegen/templates/licenses/GPL-3.0-or-later.txt.tpl +674 -0
  54. msra_codegen/templates/licenses/MIT.txt.tpl +21 -0
  55. msra_codegen/templates/manager.py.tpl +257 -0
  56. msra_codegen/templates/pyproject.toml.tpl +38 -0
  57. msra_codegen/templates/tests/api_test.py.tpl +49 -0
  58. msra_codegen/templates/tests/conftest.py.tpl +21 -0
  59. msra_codegen/templates/variable.py.tpl +54 -0
  60. msra_codegen/tests_generator.py +988 -0
  61. msra_codegen/typespec.py +275 -0
  62. msra_codegen/validation.py +118 -0
  63. msra_codegen-0.1.0.dist-info/METADATA +47 -0
  64. msra_codegen-0.1.0.dist-info/RECORD +68 -0
  65. msra_codegen-0.1.0.dist-info/WHEEL +5 -0
  66. msra_codegen-0.1.0.dist-info/entry_points.txt +2 -0
  67. msra_codegen-0.1.0.dist-info/licenses/LICENSE +674 -0
  68. msra_codegen-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,364 @@
1
+ {% for overload in overloads %}
2
+ @overload
3
+ async def {{ method_name }}(self{% if overload.signature %}{% if signature_kwonly %}, *{% endif %}, {{ overload.signature }}{% endif %}) -> {{ return_annotation }}: ...
4
+
5
+ {% endfor %}
6
+ {% if autotest_enabled %}
7
+ @autotest
8
+ {% endif %}
9
+ async def {{ method_name }}(self{% if signature %}{% if signature_kwonly %}, *{% endif %}, {{ signature }}{% endif %}) -> {{ return_annotation }}:
10
+ {% if description %}
11
+ """{{ description }}"""
12
+ {% endif %}
13
+ {% if has_overloads %}
14
+ {{ overload_selection_code }}
15
+ {{ overload_specialization_code }}
16
+ {% if overload_validation_code %}
17
+ {{ overload_validation_code }}
18
+ {% endif %}
19
+ {% else %}
20
+ {% for item in validation %}
21
+ {% if item.has_checks %}
22
+ {% if item.required %}
23
+ if {{ item.name }} is None:
24
+ raise ValueError("`{{ item.name }}` is required")
25
+ {% endif %}
26
+ {% if item.is_list %}
27
+ {% if item.required %}
28
+ if not isinstance({{ item.name }}, list):
29
+ raise TypeError("`{{ item.name }}` must be list")
30
+ for __item in {{ item.name }}:
31
+ {% else %}
32
+ if {{ item.name }} is not None and not isinstance({{ item.name }}, list):
33
+ raise TypeError("`{{ item.name }}` must be list")
34
+ if {{ item.name }} is not None:
35
+ for __item in {{ item.name }}:
36
+ {% endif %}
37
+ {% if "integer" in item.item_type_names %}
38
+ {% if item.required %}
39
+ if not isinstance(__item, int) or isinstance(__item, bool):
40
+ raise TypeError("`{{ item.name }}` items must be int")
41
+ {% else %}
42
+ if not isinstance(__item, int) or isinstance(__item, bool):
43
+ raise TypeError("`{{ item.name }}` items must be int")
44
+ {% endif %}
45
+ {% elif "boolean" in item.item_type_names %}
46
+ {% if item.required %}
47
+ if not isinstance(__item, bool):
48
+ raise TypeError("`{{ item.name }}` items must be bool")
49
+ {% else %}
50
+ if not isinstance(__item, bool):
51
+ raise TypeError("`{{ item.name }}` items must be bool")
52
+ {% endif %}
53
+ {% elif "number" in item.item_type_names %}
54
+ {% if item.required %}
55
+ if not isinstance(__item, (int, float)) or isinstance(__item, bool):
56
+ raise TypeError("`{{ item.name }}` items must be number")
57
+ {% else %}
58
+ if not isinstance(__item, (int, float)) or isinstance(__item, bool):
59
+ raise TypeError("`{{ item.name }}` items must be number")
60
+ {% endif %}
61
+ {% elif "string" in item.item_type_names %}
62
+ {% if item.required %}
63
+ if not isinstance(__item, str):
64
+ raise TypeError("`{{ item.name }}` items must be str")
65
+ {% else %}
66
+ if not isinstance(__item, str):
67
+ raise TypeError("`{{ item.name }}` items must be str")
68
+ {% endif %}
69
+ {% endif %}
70
+ {% else %}
71
+ {% if "integer" in item.type_names %}
72
+ {% if item.required %}
73
+ if not isinstance({{ item.name }}, int) or isinstance({{ item.name }}, bool):
74
+ raise TypeError("`{{ item.name }}` must be int")
75
+ {% else %}
76
+ if {{ item.name }} is not None and (not isinstance({{ item.name }}, int) or isinstance({{ item.name }}, bool)):
77
+ raise TypeError("`{{ item.name }}` must be int")
78
+ {% endif %}
79
+ {% elif "boolean" in item.type_names %}
80
+ {% if item.required %}
81
+ if not isinstance({{ item.name }}, bool):
82
+ raise TypeError("`{{ item.name }}` must be bool")
83
+ {% else %}
84
+ if {{ item.name }} is not None and not isinstance({{ item.name }}, bool):
85
+ raise TypeError("`{{ item.name }}` must be bool")
86
+ {% endif %}
87
+ {% elif "number" in item.type_names %}
88
+ {% if item.required %}
89
+ if not isinstance({{ item.name }}, (int, float)) or isinstance({{ item.name }}, bool):
90
+ raise TypeError("`{{ item.name }}` must be number")
91
+ {% else %}
92
+ if {{ item.name }} is not None and (not isinstance({{ item.name }}, (int, float)) or isinstance({{ item.name }}, bool)):
93
+ raise TypeError("`{{ item.name }}` must be number")
94
+ {% endif %}
95
+ {% elif "string" in item.type_names %}
96
+ {% if item.required %}
97
+ if not isinstance({{ item.name }}, str):
98
+ raise TypeError("`{{ item.name }}` must be str")
99
+ {% else %}
100
+ if {{ item.name }} is not None and not isinstance({{ item.name }}, str):
101
+ raise TypeError("`{{ item.name }}` must be str")
102
+ {% endif %}
103
+ {% endif %}
104
+ {% endif %}
105
+ {% if item.match_check_expr and not item.is_list %}
106
+ {% if item.required %}
107
+ if not ({{ item.match_check_expr }}):
108
+ {% else %}
109
+ if {{ item.name }} is not None and not ({{ item.match_check_expr }}):
110
+ {% endif %}
111
+ {% if item.match_error %}
112
+ raise ValueError({{ item.match_error }})
113
+ {% else %}
114
+ raise ValueError("`{{ item.name }}` does not match the expected format")
115
+ {% endif %}
116
+ {% elif (item.match_range_lower is not none or item.match_range_upper is not none) and not item.is_list %}
117
+ {% if item.match_range_lower is not none and item.match_range_upper is not none %}
118
+ {% if item.required %}
119
+ if float({{ item.name }}) < {{ item.match_range_lower }} or float({{ item.name }}) > {{ item.match_range_upper }}:
120
+ raise ValueError("`{{ item.name }}` must be between {{ item.match_range_lower }} and {{ item.match_range_upper }}")
121
+ {% else %}
122
+ if {{ item.name }} is not None and (float({{ item.name }}) < {{ item.match_range_lower }} or float({{ item.name }}) > {{ item.match_range_upper }}):
123
+ raise ValueError("`{{ item.name }}` must be between {{ item.match_range_lower }} and {{ item.match_range_upper }}")
124
+ {% endif %}
125
+ {% elif item.match_range_lower is not none %}
126
+ {% if item.required %}
127
+ if float({{ item.name }}) < {{ item.match_range_lower }}:
128
+ raise ValueError("`{{ item.name }}` must be greater than or equal to {{ item.match_range_lower }}")
129
+ {% else %}
130
+ if {{ item.name }} is not None and float({{ item.name }}) < {{ item.match_range_lower }}:
131
+ raise ValueError("`{{ item.name }}` must be greater than or equal to {{ item.match_range_lower }}")
132
+ {% endif %}
133
+ {% else %}
134
+ {% if item.required %}
135
+ if float({{ item.name }}) > {{ item.match_range_upper }}:
136
+ raise ValueError("`{{ item.name }}` must be less than or equal to {{ item.match_range_upper }}")
137
+ {% else %}
138
+ if {{ item.name }} is not None and float({{ item.name }}) > {{ item.match_range_upper }}:
139
+ raise ValueError("`{{ item.name }}` must be less than or equal to {{ item.match_range_upper }}")
140
+ {% endif %}
141
+ {% endif %}
142
+ {% endif %}
143
+ {% if item.values_expr %}
144
+ {% if item.is_list %}
145
+ {% if item.required %}
146
+ for __item in {{ item.name }}:
147
+ if __item not in {{ item.values_expr }}:
148
+ raise ValueError("`{{ item.name }}` items must be one of {{ item.values_expr }}")
149
+ {% else %}
150
+ if {{ item.name }} is not None:
151
+ for __item in {{ item.name }}:
152
+ if __item not in {{ item.values_expr }}:
153
+ raise ValueError("`{{ item.name }}` items must be one of {{ item.values_expr }}")
154
+ {% endif %}
155
+ {% else %}
156
+ {% if item.required %}
157
+ if {{ item.name }} not in {{ item.values_expr }}:
158
+ raise ValueError("`{{ item.name }}` must be one of {{ item.values_expr }}")
159
+ {% else %}
160
+ if {{ item.name }} is not None and {{ item.name }} not in {{ item.values_expr }}:
161
+ raise ValueError("`{{ item.name }}` must be one of {{ item.values_expr }}")
162
+ {% endif %}
163
+ {% endif %}
164
+ {% endif %}
165
+ {% endif %}
166
+ {% endfor %}
167
+ {% endif %}
168
+
169
+ {{ request_url_code }}
170
+ {% if query_params %}
171
+ query_params: list[tuple[str, object]] = []
172
+ {% for param in query_params %}
173
+ {% if param.kind == "from" and param.has_value_map %}
174
+ {% if param.is_list %}
175
+ {{ param.temp_list_name }} = {{ param.source_expr }}
176
+ if {{ param.temp_list_name }} in (None, []):
177
+ {{ param.temp_list_name }} = {{ param.default_values_expr if param.default_values_expr is not none else "[]" }}
178
+ elif not isinstance({{ param.temp_list_name }}, list):
179
+ raise TypeError("`{{ param.source_name }}` must be list")
180
+ if {{ param.temp_list_name }} is not None:
181
+ for __item in {{ param.temp_list_name }}:
182
+ if __item not in {{ param.selectable_values_expr }}:
183
+ raise ValueError("`{{ param.source_name }}` must be one of {{ param.selectable_values_expr }}")
184
+ {{ param.temp_list_name }} = [{{ param.value_map_expr }}[__item] for __item in {{ param.temp_list_name }}]
185
+ if {{ param.temp_list_name }}:
186
+ {% if param.list_style_style == "repeat" %}
187
+ query_params.append(({{ param.name_expr }}, {{ param.temp_list_name }}))
188
+ {% elif param.list_style_style == "delimited" %}
189
+ query_params.append(({{ param.name_expr }}, {{ param.list_style_delimiter_expr }}.join(str(__item) for __item in {{ param.temp_list_name }})))
190
+ {% elif param.list_style_style == "bracket" %}
191
+ {% if param.list_style_indexed %}
192
+ for __index, __item in enumerate({{ param.temp_list_name }}):
193
+ query_params.append(({{ param.name_expr }} + "[{}]".format(__index), __item))
194
+ {% else %}
195
+ for __item in {{ param.temp_list_name }}:
196
+ query_params.append(({{ param.name_expr }} + "[]", __item))
197
+ {% endif %}
198
+ {% elif param.list_style_style == "json" %}
199
+ query_params.append(({{ param.name_expr }}, json.dumps({{ param.temp_list_name }}, ensure_ascii=False, separators=(",", ":"))))
200
+ {% else %}
201
+ query_params.append(({{ param.name_expr }}, {{ param.temp_list_name }}))
202
+ {% endif %}
203
+ {% else %}
204
+ {{ param.temp_name }} = {{ param.source_expr }}
205
+ if {{ param.temp_name }} is None:
206
+ {% if param.default_value_expr is not none %}
207
+ {{ param.temp_name }} = {{ param.default_value_expr }}
208
+ {% endif %}
209
+ if {{ param.temp_name }} is not None:
210
+ if {{ param.temp_name }} not in {{ param.selectable_values_expr }}:
211
+ raise ValueError("`{{ param.source_name }}` must be one of {{ param.selectable_values_expr }}")
212
+ query_params.append(({{ param.name_expr }}, {{ param.value_map_expr }}[{{ param.temp_name }}]))
213
+ {% endif %}
214
+ {% elif param.kind == "from" %}
215
+ {% if param.is_list %}
216
+ if {{ param.value_expr }}:
217
+ {% if param.list_style_style == "repeat" %}
218
+ query_params.append(({{ param.name_expr }}, {{ param.value_expr }}))
219
+ {% elif param.list_style_style == "delimited" %}
220
+ query_params.append(({{ param.name_expr }}, {{ param.list_style_delimiter_expr }}.join(str(__item) for __item in {{ param.value_expr }})))
221
+ {% elif param.list_style_style == "bracket" %}
222
+ {% if param.list_style_indexed %}
223
+ for __index, __item in enumerate({{ param.value_expr }}):
224
+ query_params.append(({{ param.name_expr }} + "[{}]".format(__index), __item))
225
+ {% else %}
226
+ for __item in {{ param.value_expr }}:
227
+ query_params.append(({{ param.name_expr }} + "[]", __item))
228
+ {% endif %}
229
+ {% elif param.list_style_style == "json" %}
230
+ query_params.append(({{ param.name_expr }}, json.dumps({{ param.value_expr }}, ensure_ascii=False, separators=(",", ":"))))
231
+ {% else %}
232
+ query_params.append(({{ param.name_expr }}, {{ param.value_expr }}))
233
+ {% endif %}
234
+ {% else %}
235
+ if {{ param.value_expr }} is not None:
236
+ query_params.append(({{ param.name_expr }}, {{ param.value_expr }}))
237
+ {% endif %}
238
+ {% elif param.kind == "literal" %}
239
+ {% if param.is_list %}
240
+ if {{ param.value_expr }}:
241
+ {% if param.list_style_style == "repeat" %}
242
+ query_params.append(({{ param.name_expr }}, {{ param.value_expr }}))
243
+ {% elif param.list_style_style == "delimited" %}
244
+ query_params.append(({{ param.name_expr }}, {{ param.list_style_delimiter_expr }}.join(str(__item) for __item in {{ param.value_expr }})))
245
+ {% elif param.list_style_style == "bracket" %}
246
+ {% if param.list_style_indexed %}
247
+ for __index, __item in enumerate({{ param.value_expr }}):
248
+ query_params.append(({{ param.name_expr }} + "[{}]".format(__index), __item))
249
+ {% else %}
250
+ for __item in {{ param.value_expr }}:
251
+ query_params.append(({{ param.name_expr }} + "[]", __item))
252
+ {% endif %}
253
+ {% elif param.list_style_style == "json" %}
254
+ query_params.append(({{ param.name_expr }}, json.dumps({{ param.value_expr }}, ensure_ascii=False, separators=(",", ":"))))
255
+ {% else %}
256
+ query_params.append(({{ param.name_expr }}, {{ param.value_expr }}))
257
+ {% endif %}
258
+ {% else %}
259
+ query_params.append(({{ param.name_expr }}, {{ param.value_expr }}))
260
+ {% endif %}
261
+ {% elif param.kind == "input_passthrough" %}
262
+ {% if param.is_list %}
263
+ if {{ param.input_name }}:
264
+ {% if param.list_style_style == "repeat" %}
265
+ query_params.append(({{ param.name_expr }}, {{ param.input_name }}))
266
+ {% elif param.list_style_style == "delimited" %}
267
+ query_params.append(({{ param.name_expr }}, {{ param.list_style_delimiter_expr }}.join(str(__item) for __item in {{ param.input_name }})))
268
+ {% elif param.list_style_style == "bracket" %}
269
+ {% if param.list_style_indexed %}
270
+ for __index, __item in enumerate({{ param.input_name }}):
271
+ query_params.append(({{ param.name_expr }} + "[{}]".format(__index), __item))
272
+ {% else %}
273
+ for __item in {{ param.input_name }}:
274
+ query_params.append(({{ param.name_expr }} + "[]", __item))
275
+ {% endif %}
276
+ {% elif param.list_style_style == "json" %}
277
+ query_params.append(({{ param.name_expr }}, json.dumps({{ param.input_name }}, ensure_ascii=False, separators=(",", ":"))))
278
+ {% else %}
279
+ query_params.append(({{ param.name_expr }}, {{ param.input_name }}))
280
+ {% endif %}
281
+ {% else %}
282
+ if {{ param.input_name }} is not None:
283
+ query_params.append(({{ param.name_expr }}, {{ param.input_name }}))
284
+ {% endif %}
285
+ {% endif %}
286
+ {% endfor %}
287
+ if query_params:
288
+ request_url += "?" + urlencode(query_params, doseq=True)
289
+ {% endif %}
290
+
291
+ {% if transport == "direct" %}
292
+ return await self._parent._direct_request(request_url)
293
+ {% elif transport == "goto" %}
294
+ page = await self._parent.ctx.new_page()
295
+ {% set has_goto_pipeline = extractor.goto_pipeline_module is not none and extractor.goto_pipeline_function is not none %}
296
+ pipeline_sniffer = None
297
+ try:
298
+ {% if has_goto_pipeline %}
299
+ pipeline_sniffer = await self._parent._create_pipeline_sniffer()
300
+ {% endif %}
301
+ resp = await page.goto(request_url, wait_until="domcontentloaded")
302
+ if resp is None:
303
+ raise RuntimeError("page.goto() returned None")
304
+ json_override = None
305
+ text_override = None
306
+ {% if extractor.render_html %}
307
+ await page.wait_for_load_state("networkidle")
308
+ {% endif %}
309
+ {% if has_goto_pipeline %}
310
+ warmup = self._parent._make_warmup_context(page=page, sniffer=pipeline_sniffer)
311
+ from {{ root_import_prefix }}{{ extractor.goto_pipeline_module }} import {{ extractor.goto_pipeline_function }} as goto_pipeline_runner
312
+ try:
313
+ await goto_pipeline_runner(warmup)
314
+ except MethodPipelineError:
315
+ raise
316
+ except Exception as exc:
317
+ raise MethodPipelineError(str(exc)) from exc
318
+ {% endif %}
319
+ {% if extractor.script_path_expr %}
320
+ evaluate_script = ({{ extractor.package_root_expr }} / {{ extractor.script_path_expr }}).read_text(encoding="utf-8")
321
+ evaluate_result = await page.evaluate(evaluate_script)
322
+ if isinstance(evaluate_result, dict):
323
+ result_type = str(evaluate_result.get("type", "")).lower()
324
+ if result_type in {"json", "text/json"}:
325
+ json_override = json.loads(evaluate_result.get("data", "null"))
326
+ elif result_type in {"text", "text/plain"}:
327
+ text_override = str(evaluate_result.get("data", ""))
328
+ {% endif %}
329
+ return await abstraction.Output.from_playwright_response(
330
+ resp,
331
+ page=page,
332
+ json_override=json_override,
333
+ text_override=text_override,
334
+ )
335
+ finally:
336
+ try:
337
+ if pipeline_sniffer is not None:
338
+ await pipeline_sniffer.complete()
339
+ finally:
340
+ await page.close()
341
+ {% else %}
342
+ {% if body_expr is not none %}
343
+ json_body = {{ body_expr }}
344
+ {% else %}
345
+ json_body = None
346
+ {% endif %}
347
+ return await self._parent._request(
348
+ HttpMethod.{{ method }},
349
+ url=request_url,
350
+ json_body=json_body,
351
+ {% if request.referrer_expr is not none %}
352
+ referrer={{ request.referrer_expr }},
353
+ {% endif %}
354
+ {% if request.cors_mode_expr is not none %}
355
+ mode={{ request.cors_mode_expr }},
356
+ {% endif %}
357
+ {% if request.credentials_expr is not none %}
358
+ credentials={{ request.credentials_expr }},
359
+ {% endif %}
360
+ {% if request.headers_expr is not none %}
361
+ headers={{ request.headers_expr }},
362
+ {% endif %}
363
+ )
364
+ {% endif %}
@@ -0,0 +1,55 @@
1
+ name: "🐛 Bug report"
2
+ description: Report something that isn’t working as intended
3
+ title: "[Bug] <short title>"
4
+ labels: ["bug"]
5
+ assignees: [{{ yaml_value(assignee) }}]
6
+
7
+ body:
8
+ - type: markdown
9
+ attributes:
10
+ value: |
11
+ **Thanks for taking the time to report a bug!**
12
+
13
+ - type: checkboxes
14
+ id: area
15
+ attributes:
16
+ label: Affected area(s)
17
+ description: Check all that apply.
18
+ options:
19
+ - label: core
20
+ - label: anti-bot
21
+ - label: python interface
22
+ validations:
23
+ required: true
24
+
25
+ - type: textarea
26
+ id: steps
27
+ attributes:
28
+ label: What did you do?
29
+ description: Step-by-step commands or actions to reproduce the issue.
30
+ render: plaintext
31
+ validations:
32
+ required: true
33
+
34
+ - type: textarea
35
+ id: actual
36
+ attributes:
37
+ label: What happened?
38
+ description: Paste error messages or describe the incorrect behaviour. Logs can be attached below.
39
+ render: plaintext
40
+ validations:
41
+ required: true
42
+
43
+ - type: textarea
44
+ id: expected
45
+ attributes:
46
+ label: What did you expect to happen?
47
+ render: plaintext
48
+ validations:
49
+ required: true
50
+
51
+ - type: textarea
52
+ id: logs
53
+ attributes:
54
+ label: Logs / screenshots
55
+ description: Drag & drop log files or screenshots here.
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: false
2
+
3
+ contact_links:
4
+ {% for link in contact_links %}
5
+ - name: {{ yaml_value(link.name) }}
6
+ url: {{ yaml_value(link.url) }}
7
+ about: {{ yaml_value(link.about) }}
8
+ {% endfor %}
@@ -0,0 +1,33 @@
1
+ name: "📚 Docs issue"
2
+ description: Flag inaccurate or missing documentation
3
+ title: "[Docs] <short title>"
4
+ labels: ["documentation"]
5
+ assignees: [{{ yaml_value(assignee) }}]
6
+
7
+ body:
8
+ - type: markdown
9
+ attributes:
10
+ value: |
11
+ **Help us keep the docs sharp!**
12
+
13
+ - type: input
14
+ id: url
15
+ attributes:
16
+ label: Link to the problematic page
17
+ placeholder: "https://example.com/docs/..."
18
+ validations:
19
+ required: true
20
+
21
+ - type: textarea
22
+ id: problem
23
+ attributes:
24
+ label: What’s broken or unclear?
25
+ render: markdown
26
+ validations:
27
+ required: true
28
+
29
+ - type: textarea
30
+ id: screenshots
31
+ attributes:
32
+ label: Screenshots (optional)
33
+ description: Drag & drop images if they help illustrate the issue.
@@ -0,0 +1,36 @@
1
+ name: "✨ Feature request"
2
+ description: Suggest an idea to improve the project
3
+ title: "[Feature] <short title>"
4
+ labels: ["feature", "enhancement"]
5
+ assignees: [{{ yaml_value(assignee) }}]
6
+
7
+ body:
8
+ - type: markdown
9
+ attributes:
10
+ value: |
11
+ **Thank you for helping us grow!**
12
+ Please fill out the fields below; mock-ups/screenshots are welcome.
13
+
14
+ - type: input
15
+ id: what
16
+ attributes:
17
+ label: What do you want to achieve?
18
+ placeholder: "e.g. Support dark mode in the web UI"
19
+ validations:
20
+ required: true
21
+
22
+ - type: textarea
23
+ id: how
24
+ attributes:
25
+ label: How should it look/work?
26
+ description: |
27
+ Describe the desired behaviour. Plain text is mandatory; you can drag-and-drop images below if helpful.
28
+ render: markdown
29
+ validations:
30
+ required: true
31
+
32
+ - type: textarea
33
+ id: context
34
+ attributes:
35
+ label: Additional context / attachments
36
+ description: Drag & drop any images or diagrams here.
@@ -0,0 +1,100 @@
1
+ name: {{ publish.name }}
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - {{ publish.branch }}
7
+ workflow_dispatch:
8
+ inputs:
9
+ target:
10
+ description: "Publish target"
11
+ required: true
12
+ default: "all"
13
+ type: choice
14
+ options: {{ publish.target_options | tojson }}
15
+
16
+ permissions:
17
+ contents: write
18
+ issues: write
19
+ pull-requests: write
20
+ pages: write
21
+ id-token: write # нужно для PyPI Trusted Publishing (OIDC)
22
+
23
+ concurrency:
24
+ group: {{ publish.concurrency_group }}
25
+ cancel-in-progress: true
26
+
27
+ jobs:
28
+ tests:
29
+ name: Run tests (reusable)
30
+ uses: {{ publish.workflow_tests_path }}
31
+ with:
32
+ python-version: {{ publish.python_version | tojson }}
33
+
34
+ build-docs:
35
+ if: {{ publish.docs_condition_expr }}
36
+ name: Build docs
37
+ needs: tests
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - uses: {{ publish.checkout_action }}
41
+ - uses: {{ publish.setup_python_action }}
42
+ with:
43
+ python-version: {{ publish.python_version | tojson }}
44
+
45
+ - name: Install deps (venv)
46
+ run: |
47
+ python -m venv venv
48
+ venv/bin/python -m pip install --upgrade pip
49
+ venv/bin/python -m pip install -r requirements.txt
50
+ venv/bin/python -m pip install -r docs/requirements.txt
51
+ PATH="$PWD/venv/bin:$PATH" make install-dev
52
+
53
+ - name: Build docs (venv)
54
+ run: |
55
+ PATH="$PWD/venv/bin:$PATH" make docs
56
+
57
+ - name: Upload Pages artifact
58
+ uses: {{ publish.upload_pages_action }}
59
+ with:
60
+ path: docs/_build/html
61
+
62
+ deploy-docs:
63
+ if: {{ publish.docs_condition_expr }}
64
+ name: Deploy docs to GitHub Pages
65
+ needs: build-docs
66
+ runs-on: ubuntu-latest
67
+ environment:
68
+ name: {{ publish.pages_environment_name }}
69
+ url: {{ publish.page_url_expr }}
70
+ steps:
71
+ - name: Deploy
72
+ id: deployment
73
+ uses: {{ publish.deploy_pages_action }}
74
+
75
+ pypi:
76
+ if: {{ publish.package_condition_expr }}
77
+ name: Build & publish to PyPI (Trusted Publishing)
78
+ needs: tests
79
+ runs-on: ubuntu-latest
80
+ environment:
81
+ name: {{ publish.pypi_environment_name }}
82
+ url: {{ publish.pypi_url }}
83
+ steps:
84
+ - uses: {{ publish.checkout_action }}
85
+ - uses: {{ publish.setup_python_action }}
86
+ with:
87
+ python-version: {{ publish.python_version | tojson }}
88
+ cache: {{ publish.setup_python_cache }}
89
+
90
+ - name: Build artifacts (PEP 517) (venv)
91
+ run: |
92
+ python -m venv venv
93
+ venv/bin/python -m pip install --upgrade pip
94
+ venv/bin/python -m pip install build
95
+ PATH="$PWD/venv/bin:$PATH" make build
96
+
97
+ - name: Publish to PyPI via OIDC
98
+ uses: {{ publish.pypi_action }}
99
+ with:
100
+ verbose: true