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,188 @@
1
+ from human_requests.abstraction import Output as _Output
2
+
3
+ {% if has_regexes %}
4
+ {% for regex in regexes %}
5
+ from .regexes import {{ regex.class_name }} as _{{ regex.class_name }}
6
+ {% endfor %}
7
+ {% endif %}
8
+ {% if external_modules %}
9
+ from collections.abc import Mapping
10
+ from dataclasses import fields, is_dataclass
11
+ from typing import Any
12
+
13
+ from pydantic import BaseModel
14
+
15
+ {% for import_line in external_import_lines %}
16
+ {{ import_line }}
17
+ {% endfor %}
18
+ {% endif %}
19
+
20
+ Output = _Output
21
+ {% if has_regexes %}
22
+ {% for regex in regexes %}
23
+ {{ regex.class_name }} = _{{ regex.class_name }}
24
+ {% endfor %}
25
+ {% endif %}
26
+
27
+ __all__ = [
28
+ "Output",
29
+ {% for regex in regexes %}
30
+ "{{ regex.class_name }}",
31
+ {% endfor %}
32
+ {% if external_modules %}
33
+ {% for module in external_modules %}
34
+ {% for class_name in module.public_class_names %}
35
+ "{{ class_name }}",
36
+ {% endfor %}
37
+ {% endfor %}
38
+ {% endif %}
39
+ ]
40
+ {% if external_modules %}
41
+
42
+ SCALAR_TYPES = (str, int, float, bool, bytes, type(None))
43
+ CONTAINER_TYPES = (Mapping, list, tuple, set)
44
+
45
+
46
+ def normalize(value: Any) -> Any:
47
+ if isinstance(value, BaseModel):
48
+ return value.model_dump()
49
+ return value
50
+
51
+
52
+ def is_leaf(value: Any) -> bool:
53
+ value = normalize(value)
54
+ return isinstance(value, SCALAR_TYPES + CONTAINER_TYPES)
55
+
56
+
57
+ def public_children_with_names(obj: Any) -> list[tuple[str, Any]]:
58
+ result: list[tuple[str, Any]] = []
59
+
60
+ if isinstance(obj, type):
61
+ for name, value in vars(obj).items():
62
+ if name.startswith("_"):
63
+ continue
64
+ if isinstance(value, (staticmethod, classmethod)):
65
+ continue
66
+ if callable(value) and not isinstance(value, type):
67
+ continue
68
+ result.append((name, value))
69
+ return result
70
+
71
+ if isinstance(obj, BaseModel):
72
+ return []
73
+
74
+ if is_dataclass(obj) and not isinstance(obj, type):
75
+ for field in fields(obj):
76
+ if field.name.startswith("_"):
77
+ continue
78
+ result.append((field.name, getattr(obj, field.name)))
79
+
80
+ if hasattr(obj, "__dict__"):
81
+ for name, value in vars(obj).items():
82
+ if name.startswith("_"):
83
+ continue
84
+ if callable(value):
85
+ continue
86
+ result.append((name, value))
87
+
88
+ for name, descriptor in vars(type(obj)).items():
89
+ if name.startswith("_"):
90
+ continue
91
+ if isinstance(descriptor, property):
92
+ result.append((name, getattr(obj, name)))
93
+
94
+ return result
95
+
96
+
97
+ def registry_display_name(value: Any) -> str:
98
+ if isinstance(value, type):
99
+ return value.__qualname__.replace("<locals>.", "")
100
+ return type(value).__qualname__.replace("<locals>.", "")
101
+
102
+
103
+ def collect_allowed_value_paths(parent: Any) -> list[str]:
104
+ result: list[str] = []
105
+ seen: set[int] = set()
106
+
107
+ def walk(value: Any, path: list[str]) -> None:
108
+ value = normalize(value)
109
+ if is_leaf(value):
110
+ result.append(".".join(path))
111
+ return
112
+ value_id = id(value)
113
+ if value_id in seen:
114
+ return
115
+ seen.add(value_id)
116
+ children = public_children_with_names(value)
117
+ if not children:
118
+ result.append(".".join(path))
119
+ return
120
+ for child_name, child_value in children:
121
+ walk(child_value, [*path, child_name])
122
+
123
+ walk(parent, [registry_display_name(parent)])
124
+ return result
125
+
126
+
127
+ def collect_allowed_values(parent: Any) -> list[Any]:
128
+ result: list[Any] = []
129
+ seen: set[int] = set()
130
+
131
+ def walk(value: Any) -> None:
132
+ value = normalize(value)
133
+ if is_leaf(value):
134
+ result.append(value)
135
+ return
136
+ value_id = id(value)
137
+ if value_id in seen:
138
+ return
139
+ seen.add(value_id)
140
+ for _, child_value in public_children_with_names(value):
141
+ walk(child_value)
142
+
143
+ walk(parent)
144
+ return result
145
+
146
+
147
+ def is_allowed_value(value: Any, parent: Any) -> bool:
148
+ value = normalize(value)
149
+ return value in collect_allowed_values(parent)
150
+
151
+
152
+ def find_registry_path(parent: Any, target: Any) -> str | None:
153
+ seen: set[int] = set()
154
+
155
+ def walk(value: Any, path: list[str]) -> str | None:
156
+ if value is target:
157
+ return ".".join(path)
158
+ value = normalize(value)
159
+ if is_leaf(value):
160
+ return None
161
+ value_id = id(value)
162
+ if value_id in seen:
163
+ return None
164
+ seen.add(value_id)
165
+ for child_name, child_value in public_children_with_names(value):
166
+ found = walk(child_value, [*path, child_name])
167
+ if found is not None:
168
+ return found
169
+ return None
170
+
171
+ return walk(parent, [registry_display_name(parent)])
172
+
173
+
174
+ def validate_allowed_value(value: Any, parent: Any) -> Any:
175
+ value = normalize(value)
176
+ allowed = collect_allowed_values(parent)
177
+ if value in allowed:
178
+ return value
179
+ if not is_leaf(value):
180
+ value_path = find_registry_path(parent, value) or registry_display_name(value)
181
+ allowed_paths = collect_allowed_value_paths(parent)
182
+ raise ValueError(
183
+ f"{value_path} is a value registry, not a value.\n"
184
+ "Use one of:\n"
185
+ + "\n".join(f"- {path}" for path in allowed_paths)
186
+ )
187
+ raise ValueError(f"Invalid value {value!r}. Allowed values: {allowed!r}")
188
+ {% endif %}
@@ -0,0 +1,25 @@
1
+ from typing import Any
2
+ import re
3
+
4
+
5
+ class RegexBase:
6
+ REGEX = r""
7
+ ERROR: str | None = None
8
+
9
+ @classmethod
10
+ def match(cls, value: Any) -> bool:
11
+ return re.fullmatch(cls.REGEX, str(value)) is not None
12
+
13
+
14
+ {% for regex in regexes %}
15
+ class {{ regex.class_name }}(RegexBase):
16
+ {% if regex.description %}
17
+ """{{ regex.description }}"""
18
+ {% endif %}
19
+ REGEX = r"{{ regex.pattern }}"
20
+ {% if regex.raise_message is not none %}
21
+ ERROR = {{ regex.raise_message }}
22
+ {% endif %}
23
+
24
+
25
+ {% endfor %}
@@ -0,0 +1,3 @@
1
+ sphinx
2
+ furo
3
+ jsoncrack-for-sphinx
@@ -0,0 +1,20 @@
1
+ # Minimal makefile for Sphinx documentation
2
+ #
3
+
4
+ # You can set these variables from the command line, and also
5
+ # from the environment for the first two.
6
+ SPHINXOPTS ?=
7
+ SPHINXBUILD ?= sphinx-build
8
+ SOURCEDIR = .
9
+ BUILDDIR = _build
10
+
11
+ # Put it first so that "make" without argument is like "make help".
12
+ help:
13
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14
+
15
+ .PHONY: help Makefile
16
+
17
+ # Catch-all target: route all unknown targets to Sphinx using the new
18
+ # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19
+ %: Makefile
20
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@@ -0,0 +1,9 @@
1
+ API Reference
2
+ =============
3
+
4
+ .. toctree::
5
+ :maxdepth: 1
6
+
7
+ {% for docname in api_docnames %}
8
+ {{ docname }}
9
+ {% endfor %}
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ HERE = Path(__file__).resolve().parent
8
+ REPO_ROOT = HERE.parents[1]
9
+ sys.path.insert(0, str(REPO_ROOT))
10
+
11
+ project = "{{ project_name }}"
12
+ author = "Miskler"
13
+ copyright = "{{ current_year }}, Miskler"
14
+
15
+
16
+ def _get_version() -> str:
17
+ for key in ("PROJECT_VERSION", "READTHEDOCS_VERSION", "VERSION"):
18
+ if os.getenv(key):
19
+ return os.environ[key]
20
+ try:
21
+ from importlib.metadata import version
22
+
23
+ return version("{{ package_name }}")
24
+ except Exception:
25
+ return "{{ project_version }}"
26
+
27
+
28
+ release = _get_version()
29
+ version = ".".join(release.split(".")[:3])
30
+
31
+ extensions = [
32
+ "sphinx.ext.autodoc",
33
+ "sphinx.ext.autosummary",
34
+ "sphinx.ext.napoleon",
35
+ "sphinx.ext.intersphinx",
36
+ "sphinx.ext.viewcode",
37
+ "sphinx.ext.doctest",
38
+ "sphinx.ext.duration",
39
+ "jsoncrack_for_sphinx",
40
+ ]
41
+
42
+ autodoc_mock_imports = [
43
+ "aiohttp",
44
+ "aiohttp_retry",
45
+ "camoufox",
46
+ "playwright",
47
+ "playwright.async_api",
48
+ "PIL",
49
+ ]
50
+
51
+ source_suffix = {
52
+ ".rst": "restructuredtext",
53
+ }
54
+
55
+ autosummary_generate = False
56
+ autosummary_imported_members = True
57
+ autosummary_ignore_module_all = False
58
+
59
+ autodoc_default_options = {}
60
+ autodoc_typehints = "signature"
61
+ autodoc_preserve_defaults = True
62
+
63
+ add_module_names = False
64
+ python_use_unqualified_type_names = True
65
+ multi_line_parameter_list = True
66
+ python_maximum_signature_line_length = 80
67
+ default_role = "any"
68
+
69
+ intersphinx_mapping = {
70
+ "python": ("https://docs.python.org/3", None),
71
+ "human_requests": ("https://miskler.github.io/human-requests/", None),
72
+ }
73
+
74
+ json_schema_dir = str(HERE.parents[2] / "tests" / "__snapshots__")
75
+
76
+ html_theme = "furo"
77
+ html_static_path = ["_static"]
78
+ templates_path = ["_templates"]
79
+
80
+ html_theme_options = {
81
+ "sidebar_hide_name": True,
82
+ {% if logo %}
83
+ "light_logo": "{{ logo.light_name }}",
84
+ "dark_logo": "{{ logo.dark_name }}",
85
+ {% endif %}
86
+ }
87
+
88
+ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
@@ -0,0 +1,14 @@
1
+ {{ title }}
2
+ {{ title_underline }}
3
+
4
+ .. toctree::
5
+ :maxdepth: 4
6
+ :caption: Basic use
7
+
8
+ quick_start
9
+
10
+ .. toctree::
11
+ :maxdepth: 4
12
+ :caption: References
13
+
14
+ api
@@ -0,0 +1,34 @@
1
+ {{ title }}
2
+ {{ title_underline }}
3
+
4
+ {% if description %}
5
+ {{ description }}
6
+
7
+ {% endif %}
8
+ .. currentmodule:: {{ import_path }}
9
+
10
+ .. automodule:: {{ import_path }}
11
+
12
+ {% if class_names %}
13
+ .. rubric:: Classes
14
+
15
+ {% for class_name in class_names %}
16
+ .. autoclass:: {{ class_name }}
17
+ :members:
18
+ :undoc-members:
19
+ :show-inheritance:
20
+
21
+ {% endfor %}
22
+
23
+ {% endif %}
24
+ {% if child_pages %}
25
+ .. rubric:: Submodules
26
+
27
+ .. toctree::
28
+ :maxdepth: 1
29
+
30
+ {% for child_docname in child_docnames %}
31
+ {{ child_docname }}
32
+ {% endfor %}
33
+
34
+ {% endif %}
@@ -0,0 +1,19 @@
1
+ {{ quick_start.title }}
2
+ {{ quick_start.title_underline }}
3
+
4
+ Install the generated client:
5
+
6
+ .. code-block:: console
7
+
8
+ pip install {{ package_name }}
9
+ {% if quick_start.requires_camoufox %}
10
+ python -m camoufox fetch
11
+ {% endif %}
12
+
13
+ Import the client:
14
+
15
+ .. code-block:: python
16
+
17
+ {{ pipeline_script_code_rst }}
18
+
19
+ The public API is documented in :doc:`api`.
@@ -0,0 +1,15 @@
1
+ {% if top_groups %}
2
+ {% for group in top_groups %}
3
+ from . import {{ group.package_name }} as _{{ group.package_name }}
4
+ {% endfor %}
5
+
6
+ {% for group in top_groups %}
7
+ {{ group.class_name }} = _{{ group.package_name }}.{{ group.class_name }}
8
+ {% endfor %}
9
+ {% endif %}
10
+
11
+ __all__ = [
12
+ {% for group in top_groups %}
13
+ "{{ group.class_name }}",
14
+ {% endfor %}
15
+ ]
@@ -0,0 +1 @@
1
+ {{ pipeline_script_code }}