@typespec/http-client-python 0.4.4 → 0.5.0

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 (137) hide show
  1. package/dist/emitter/emitter.d.ts.map +1 -1
  2. package/dist/emitter/emitter.js +85 -24
  3. package/dist/emitter/emitter.js.map +1 -1
  4. package/dist/emitter/lib.d.ts +1 -0
  5. package/dist/emitter/lib.d.ts.map +1 -1
  6. package/dist/emitter/lib.js +1 -0
  7. package/dist/emitter/lib.js.map +1 -1
  8. package/dist/emitter/run-python3.d.ts +2 -0
  9. package/dist/emitter/run-python3.d.ts.map +1 -0
  10. package/dist/emitter/run-python3.js +19 -0
  11. package/dist/emitter/run-python3.js.map +1 -0
  12. package/dist/emitter/system-requirements.d.ts +17 -0
  13. package/dist/emitter/system-requirements.d.ts.map +1 -0
  14. package/dist/emitter/system-requirements.js +167 -0
  15. package/dist/emitter/system-requirements.js.map +1 -0
  16. package/emitter/src/emitter.ts +88 -23
  17. package/emitter/src/lib.ts +2 -0
  18. package/emitter/src/run-python3.ts +20 -0
  19. package/emitter/src/system-requirements.ts +261 -0
  20. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  21. package/eng/scripts/ci/regenerate.ts +16 -4
  22. package/eng/scripts/setup/__pycache__/venvtools.cpython-38.pyc +0 -0
  23. package/eng/scripts/setup/build.ts +16 -0
  24. package/eng/scripts/setup/build_pygen_wheel.py +40 -0
  25. package/eng/scripts/setup/install.py +9 -3
  26. package/eng/scripts/setup/install.ts +32 -0
  27. package/eng/scripts/setup/prepare.py +3 -1
  28. package/eng/scripts/setup/prepare.ts +11 -0
  29. package/eng/scripts/setup/run-python3.ts +1 -6
  30. package/generator/build/lib/pygen/__init__.py +107 -0
  31. package/generator/build/lib/pygen/_version.py +7 -0
  32. package/generator/build/lib/pygen/black.py +71 -0
  33. package/generator/build/lib/pygen/codegen/__init__.py +357 -0
  34. package/generator/build/lib/pygen/codegen/_utils.py +17 -0
  35. package/generator/build/lib/pygen/codegen/models/__init__.py +204 -0
  36. package/generator/build/lib/pygen/codegen/models/base.py +186 -0
  37. package/generator/build/lib/pygen/codegen/models/base_builder.py +118 -0
  38. package/generator/build/lib/pygen/codegen/models/client.py +435 -0
  39. package/generator/build/lib/pygen/codegen/models/code_model.py +237 -0
  40. package/generator/build/lib/pygen/codegen/models/combined_type.py +149 -0
  41. package/generator/build/lib/pygen/codegen/models/constant_type.py +129 -0
  42. package/generator/build/lib/pygen/codegen/models/credential_types.py +214 -0
  43. package/generator/build/lib/pygen/codegen/models/dictionary_type.py +127 -0
  44. package/generator/build/lib/pygen/codegen/models/enum_type.py +238 -0
  45. package/generator/build/lib/pygen/codegen/models/imports.py +291 -0
  46. package/generator/build/lib/pygen/codegen/models/list_type.py +143 -0
  47. package/generator/build/lib/pygen/codegen/models/lro_operation.py +142 -0
  48. package/generator/build/lib/pygen/codegen/models/lro_paging_operation.py +32 -0
  49. package/generator/build/lib/pygen/codegen/models/model_type.py +357 -0
  50. package/generator/build/lib/pygen/codegen/models/operation.py +509 -0
  51. package/generator/build/lib/pygen/codegen/models/operation_group.py +184 -0
  52. package/generator/build/lib/pygen/codegen/models/paging_operation.py +155 -0
  53. package/generator/build/lib/pygen/codegen/models/parameter.py +412 -0
  54. package/generator/build/lib/pygen/codegen/models/parameter_list.py +387 -0
  55. package/generator/build/lib/pygen/codegen/models/primitive_types.py +659 -0
  56. package/generator/build/lib/pygen/codegen/models/property.py +170 -0
  57. package/generator/build/lib/pygen/codegen/models/request_builder.py +189 -0
  58. package/generator/build/lib/pygen/codegen/models/request_builder_parameter.py +115 -0
  59. package/generator/build/lib/pygen/codegen/models/response.py +348 -0
  60. package/generator/build/lib/pygen/codegen/models/utils.py +21 -0
  61. package/generator/build/lib/pygen/codegen/serializers/__init__.py +574 -0
  62. package/generator/build/lib/pygen/codegen/serializers/base_serializer.py +21 -0
  63. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +1533 -0
  64. package/generator/build/lib/pygen/codegen/serializers/client_serializer.py +294 -0
  65. package/generator/build/lib/pygen/codegen/serializers/enum_serializer.py +15 -0
  66. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +213 -0
  67. package/generator/build/lib/pygen/codegen/serializers/import_serializer.py +126 -0
  68. package/generator/build/lib/pygen/codegen/serializers/metadata_serializer.py +198 -0
  69. package/generator/build/lib/pygen/codegen/serializers/model_init_serializer.py +33 -0
  70. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +335 -0
  71. package/generator/build/lib/pygen/codegen/serializers/operation_groups_serializer.py +89 -0
  72. package/generator/build/lib/pygen/codegen/serializers/operations_init_serializer.py +44 -0
  73. package/generator/build/lib/pygen/codegen/serializers/parameter_serializer.py +221 -0
  74. package/generator/build/lib/pygen/codegen/serializers/patch_serializer.py +19 -0
  75. package/generator/build/lib/pygen/codegen/serializers/request_builders_serializer.py +52 -0
  76. package/generator/build/lib/pygen/codegen/serializers/sample_serializer.py +168 -0
  77. package/generator/build/lib/pygen/codegen/serializers/test_serializer.py +292 -0
  78. package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +31 -0
  79. package/generator/build/lib/pygen/codegen/serializers/utils.py +68 -0
  80. package/generator/build/lib/pygen/codegen/templates/client.py.jinja2 +37 -0
  81. package/generator/build/lib/pygen/codegen/templates/client_container.py.jinja2 +12 -0
  82. package/generator/build/lib/pygen/codegen/templates/config.py.jinja2 +73 -0
  83. package/generator/build/lib/pygen/codegen/templates/config_container.py.jinja2 +16 -0
  84. package/generator/build/lib/pygen/codegen/templates/conftest.py.jinja2 +28 -0
  85. package/generator/build/lib/pygen/codegen/templates/enum.py.jinja2 +13 -0
  86. package/generator/build/lib/pygen/codegen/templates/enum_container.py.jinja2 +10 -0
  87. package/generator/build/lib/pygen/codegen/templates/init.py.jinja2 +24 -0
  88. package/generator/build/lib/pygen/codegen/templates/keywords.jinja2 +27 -0
  89. package/generator/build/lib/pygen/codegen/templates/lro_operation.py.jinja2 +16 -0
  90. package/generator/build/lib/pygen/codegen/templates/lro_paging_operation.py.jinja2 +18 -0
  91. package/generator/build/lib/pygen/codegen/templates/macros.jinja2 +12 -0
  92. package/generator/build/lib/pygen/codegen/templates/metadata.json.jinja2 +167 -0
  93. package/generator/build/lib/pygen/codegen/templates/model_base.py.jinja2 +1174 -0
  94. package/generator/build/lib/pygen/codegen/templates/model_container.py.jinja2 +15 -0
  95. package/generator/build/lib/pygen/codegen/templates/model_dpg.py.jinja2 +97 -0
  96. package/generator/build/lib/pygen/codegen/templates/model_init.py.jinja2 +33 -0
  97. package/generator/build/lib/pygen/codegen/templates/model_msrest.py.jinja2 +92 -0
  98. package/generator/build/lib/pygen/codegen/templates/operation.py.jinja2 +21 -0
  99. package/generator/build/lib/pygen/codegen/templates/operation_group.py.jinja2 +75 -0
  100. package/generator/build/lib/pygen/codegen/templates/operation_groups_container.py.jinja2 +19 -0
  101. package/generator/build/lib/pygen/codegen/templates/operation_tools.jinja2 +81 -0
  102. package/generator/build/lib/pygen/codegen/templates/operations_folder_init.py.jinja2 +17 -0
  103. package/generator/build/lib/pygen/codegen/templates/packaging_templates/CHANGELOG.md.jinja2 +6 -0
  104. package/generator/build/lib/pygen/codegen/templates/packaging_templates/LICENSE.jinja2 +21 -0
  105. package/generator/build/lib/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 +8 -0
  106. package/generator/build/lib/pygen/codegen/templates/packaging_templates/README.md.jinja2 +107 -0
  107. package/generator/build/lib/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 +9 -0
  108. package/generator/build/lib/pygen/codegen/templates/packaging_templates/setup.py.jinja2 +108 -0
  109. package/generator/build/lib/pygen/codegen/templates/paging_operation.py.jinja2 +21 -0
  110. package/generator/build/lib/pygen/codegen/templates/patch.py.jinja2 +19 -0
  111. package/generator/build/lib/pygen/codegen/templates/pkgutil_init.py.jinja2 +1 -0
  112. package/generator/build/lib/pygen/codegen/templates/request_builder.py.jinja2 +28 -0
  113. package/generator/build/lib/pygen/codegen/templates/request_builders.py.jinja2 +10 -0
  114. package/generator/build/lib/pygen/codegen/templates/rest_init.py.jinja2 +12 -0
  115. package/generator/build/lib/pygen/codegen/templates/sample.py.jinja2 +44 -0
  116. package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +2117 -0
  117. package/generator/build/lib/pygen/codegen/templates/test.py.jinja2 +50 -0
  118. package/generator/build/lib/pygen/codegen/templates/testpreparer.py.jinja2 +26 -0
  119. package/generator/build/lib/pygen/codegen/templates/types.py.jinja2 +7 -0
  120. package/generator/build/lib/pygen/codegen/templates/validation.py.jinja2 +38 -0
  121. package/generator/build/lib/pygen/codegen/templates/vendor.py.jinja2 +96 -0
  122. package/generator/build/lib/pygen/codegen/templates/version.py.jinja2 +4 -0
  123. package/generator/build/lib/pygen/m2r.py +65 -0
  124. package/generator/build/lib/pygen/preprocess/__init__.py +515 -0
  125. package/generator/build/lib/pygen/preprocess/helpers.py +27 -0
  126. package/generator/build/lib/pygen/preprocess/python_mappings.py +226 -0
  127. package/generator/build/lib/pygen/utils.py +163 -0
  128. package/generator/component-detection-pip-report.json +134 -0
  129. package/generator/dev_requirements.txt +0 -1
  130. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  131. package/generator/pygen.egg-info/PKG-INFO +7 -4
  132. package/generator/pygen.egg-info/requires.txt +7 -4
  133. package/generator/requirements.txt +5 -10
  134. package/generator/setup.py +7 -4
  135. package/generator/test/azure/requirements.txt +1 -1
  136. package/generator/test/unbranded/requirements.txt +1 -1
  137. package/package.json +6 -5
@@ -8,17 +8,23 @@
8
8
  import sys
9
9
 
10
10
  if not sys.version_info >= (3, 8, 0):
11
- raise Exception("Autorest for Python extension requires Python 3.8 at least")
11
+ raise Warning(
12
+ "Autorest for Python extension requires Python 3.8 at least. We will run your code with Pyodide since your Python version isn't adequate."
13
+ )
12
14
 
13
15
  try:
14
16
  import pip
15
17
  except ImportError:
16
- raise Exception("Your Python installation doesn't have pip available")
18
+ raise Warning(
19
+ "Your Python installation doesn't have pip available. We will run your code with Pyodide since your Python version isn't adequate."
20
+ )
17
21
 
18
22
  try:
19
23
  import venv
20
24
  except ImportError:
21
- raise Exception("Your Python installation doesn't have venv available")
25
+ raise Warning(
26
+ "Your Python installation doesn't have venv available. We will run your code with Pyodide since your Python version isn't adequate."
27
+ )
22
28
 
23
29
 
24
30
  # Now we have pip and Py >= 3.8, go to work
@@ -0,0 +1,32 @@
1
+ import fs from "fs";
2
+ import path, { dirname } from "path";
3
+ import { loadPyodide } from "pyodide";
4
+ import { fileURLToPath } from "url";
5
+ import { runPython3 } from "./run-python3.js";
6
+
7
+ async function main() {
8
+ try {
9
+ await runPython3("./eng/scripts/setup/install.py");
10
+ console.log("Found Python on your local environment and created a venv with all requirements."); // eslint-disable-line no-console
11
+ } catch (error) {
12
+ console.log("No Python found on your local environment. We will use Pyodide instead."); // eslint-disable-line no-console
13
+ } finally {
14
+ await installPyodideDeps();
15
+ console.log("Successfully installed all required Python packages in Pyodide"); // eslint-disable-line no-console
16
+ }
17
+ }
18
+
19
+ async function installPyodideDeps() {
20
+ const root = path.join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
21
+ const pyodide = await loadPyodide({
22
+ indexURL: path.dirname(fileURLToPath(import.meta.resolve("pyodide"))),
23
+ });
24
+ await pyodide.loadPackage("micropip");
25
+ const micropip = pyodide.pyimport("micropip");
26
+ const requirementsPath = path.join(root, "generator", "requirements.txt");
27
+ const requirementsText = fs.readFileSync(requirementsPath, "utf-8");
28
+ const requirementsArray = requirementsText.split("\n").filter((line) => line.trim() !== "");
29
+ await micropip.install(requirementsArray);
30
+ }
31
+
32
+ main();
@@ -10,7 +10,9 @@ import os
10
10
  import argparse
11
11
 
12
12
  if not sys.version_info >= (3, 8, 0):
13
- raise Exception("Autorest for Python extension requires Python 3.8 at least")
13
+ raise Warning(
14
+ "Autorest for Python extension requires Python 3.8 at least. We will run your code with Pyodide since your Python version isn't adequate."
15
+ )
14
16
 
15
17
  from pathlib import Path
16
18
  import venv
@@ -0,0 +1,11 @@
1
+ import { runPython3 } from "./run-python3.js";
2
+
3
+ async function main() {
4
+ try {
5
+ await runPython3("./eng/scripts/setup/prepare.py");
6
+ } catch (error) {
7
+ console.log("No Python found on your local environment. We will use Pyodide instead."); // eslint-disable-line no-console
8
+ }
9
+ }
10
+
11
+ main();
@@ -9,7 +9,7 @@
9
9
  import cp from "child_process";
10
10
  import { patchPythonPath } from "./system-requirements.js";
11
11
 
12
- async function runPython3(...args: string[]) {
12
+ export async function runPython3(...args: string[]) {
13
13
  const command = await patchPythonPath(["python", ...args], {
14
14
  version: ">=3.8",
15
15
  environmentVariable: "AUTOREST_PYTHON_EXE",
@@ -18,8 +18,3 @@ async function runPython3(...args: string[]) {
18
18
  stdio: [0, 1, 2],
19
19
  });
20
20
  }
21
-
22
- runPython3(...process.argv.slice(2)).catch((err) => {
23
- console.error(err.toString()); // eslint-disable-line no-console
24
- process.exit(1);
25
- });
@@ -0,0 +1,107 @@
1
+ # -------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # --------------------------------------------------------------------------
6
+ import logging
7
+ from pathlib import Path
8
+ import json
9
+ from abc import ABC, abstractmethod
10
+ from typing import Any, Dict, Union, List
11
+
12
+ import yaml
13
+
14
+ from ._version import VERSION
15
+
16
+
17
+ __version__ = VERSION
18
+ _LOGGER = logging.getLogger(__name__)
19
+
20
+
21
+ class ReaderAndWriter:
22
+ def __init__(self, *, output_folder: Union[str, Path], **kwargs: Any) -> None:
23
+ self.output_folder = Path(output_folder)
24
+ self._list_file: List[str] = []
25
+ try:
26
+ with open(
27
+ Path(self.output_folder) / Path("..") / Path("python.json"),
28
+ "r",
29
+ encoding="utf-8-sig",
30
+ ) as fd:
31
+ python_json = json.load(fd)
32
+ except Exception: # pylint: disable=broad-except
33
+ python_json = {}
34
+ self.options = kwargs
35
+ if python_json:
36
+ _LOGGER.warning("Loading python.json file. This behavior will be depreacted")
37
+ self.options.update(python_json)
38
+
39
+ def read_file(self, path: Union[str, Path]) -> str:
40
+ """Directly reading from disk"""
41
+ # make path relative to output folder
42
+ try:
43
+ with open(self.output_folder / Path(path), "r", encoding="utf-8-sig") as fd:
44
+ return fd.read()
45
+ except FileNotFoundError:
46
+ return ""
47
+
48
+ def write_file(self, filename: Union[str, Path], file_content: str) -> None:
49
+ """Directly writing to disk"""
50
+ file_folder = Path(filename).parent
51
+ if not Path.is_dir(self.output_folder / file_folder):
52
+ Path.mkdir(self.output_folder / file_folder, parents=True)
53
+ with open(self.output_folder / Path(filename), "w", encoding="utf-8") as fd:
54
+ fd.write(file_content)
55
+
56
+ def list_file(self) -> List[str]:
57
+ return [str(f.relative_to(self.output_folder)) for f in self.output_folder.glob("**/*") if f.is_file()]
58
+
59
+
60
+ class Plugin(ReaderAndWriter, ABC):
61
+ """A base class for autorest plugin.
62
+
63
+ :param autorestapi: An autorest API instance
64
+ """
65
+
66
+ @abstractmethod
67
+ def process(self) -> bool:
68
+ """The plugin process.
69
+
70
+ :rtype: bool
71
+ :returns: True if everything's ok, False optherwise
72
+ :raises Exception: Could raise any exception, stacktrace will be sent to autorest API
73
+ """
74
+ raise NotImplementedError()
75
+
76
+
77
+ class YamlUpdatePlugin(Plugin):
78
+ """A plugin that update the YAML as input."""
79
+
80
+ def get_yaml(self) -> Dict[str, Any]:
81
+ # cadl file doesn't have to be relative to output folder
82
+ with open(self.options["cadl_file"], "r", encoding="utf-8-sig") as fd:
83
+ return yaml.safe_load(fd.read())
84
+
85
+ def write_yaml(self, yaml_string: str) -> None:
86
+ with open(self.options["cadl_file"], "w", encoding="utf-8-sig") as fd:
87
+ fd.write(yaml_string)
88
+
89
+ def process(self) -> bool:
90
+ # List the input file, should be only one
91
+ yaml_data = self.get_yaml()
92
+
93
+ self.update_yaml(yaml_data)
94
+
95
+ yaml_string = yaml.safe_dump(yaml_data)
96
+
97
+ self.write_yaml(yaml_string)
98
+ return True
99
+
100
+ @abstractmethod
101
+ def update_yaml(self, yaml_data: Dict[str, Any]) -> None:
102
+ """The code-model-v4-no-tags yaml model tree.
103
+
104
+ :rtype: updated yaml
105
+ :raises Exception: Could raise any exception, stacktrace will be sent to autorest API
106
+ """
107
+ raise NotImplementedError()
@@ -0,0 +1,7 @@
1
+ # -------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # --------------------------------------------------------------------------
6
+
7
+ VERSION = "0.1.0"
@@ -0,0 +1,71 @@
1
+ # -------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # --------------------------------------------------------------------------
6
+ import logging
7
+ from pathlib import Path
8
+ import os
9
+ import black
10
+ from black.report import NothingChanged
11
+
12
+ from . import Plugin
13
+ from .utils import parse_args
14
+
15
+ _LOGGER = logging.getLogger("blib2to3")
16
+
17
+ _BLACK_MODE = black.Mode() # pyright: ignore [reportPrivateImportUsage]
18
+ _BLACK_MODE.line_length = 120
19
+
20
+
21
+ class BlackScriptPlugin(Plugin):
22
+ def __init__(self, **kwargs):
23
+ super().__init__(**kwargs)
24
+ output_folder = self.options.get("output_folder", str(self.output_folder))
25
+ if output_folder.startswith("file:"):
26
+ output_folder = output_folder[5:]
27
+ if os.name == "nt" and output_folder.startswith("///"):
28
+ output_folder = output_folder[3:]
29
+ self.output_folder = Path(output_folder)
30
+
31
+ def process(self) -> bool:
32
+ # apply format_file on every .py file in the output folder
33
+ list(
34
+ map(
35
+ self.format_file,
36
+ [
37
+ Path(f)
38
+ for f in self.list_file()
39
+ if Path(f).parts[0]
40
+ not in (
41
+ "__pycache__",
42
+ "node_modules",
43
+ "venv",
44
+ "env",
45
+ )
46
+ and not Path(f).parts[0].startswith(".")
47
+ and Path(f).suffix == ".py"
48
+ ],
49
+ )
50
+ )
51
+ return True
52
+
53
+ def format_file(self, file: Path) -> None:
54
+ file_content = ""
55
+ try:
56
+ file_content = self.read_file(file)
57
+ file_content = black.format_file_contents(file_content, fast=True, mode=_BLACK_MODE)
58
+ except NothingChanged:
59
+ pass
60
+ except:
61
+ _LOGGER.error("Error: failed to format %s", file)
62
+ raise
63
+ if len(file_content.splitlines()) > 1000:
64
+ file_content = "# pylint: disable=too-many-lines\n" + file_content
65
+ self.write_file(file, file_content)
66
+
67
+
68
+ if __name__ == "__main__":
69
+ # CADL pipeline will call this
70
+ args, unknown_args = parse_args(need_cadl_file=False)
71
+ BlackScriptPlugin(output_folder=args.output_folder, **unknown_args).process()
@@ -0,0 +1,357 @@
1
+ # -------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # --------------------------------------------------------------------------
6
+ import logging
7
+ from typing import Dict, Any, Union, Optional
8
+ from pathlib import Path
9
+ import yaml
10
+
11
+
12
+ from .. import Plugin
13
+ from ..utils import parse_args
14
+ from .models.code_model import CodeModel
15
+ from .serializers import JinjaSerializer
16
+ from ._utils import DEFAULT_HEADER_TEXT, VALID_PACKAGE_MODE, TYPESPEC_PACKAGE_MODE
17
+
18
+
19
+ def _default_pprint(package_name: str) -> str:
20
+ return " ".join([i.capitalize() for i in package_name.split("-")])
21
+
22
+
23
+ _LOGGER = logging.getLogger(__name__)
24
+
25
+
26
+ class OptionsRetriever:
27
+ OPTIONS_TO_DEFAULT = {
28
+ "azure-arm": False,
29
+ "flavor": "azure", # need to default to azure in shared code so we don't break swagger generation
30
+ "no-async": False,
31
+ "low-level-client": False,
32
+ "version-tolerant": True,
33
+ "keep-version-file": False,
34
+ "no-namespace-folders": False,
35
+ "basic-setup-py": False,
36
+ "client-side-validation": False,
37
+ "multiapi": False,
38
+ "polymorphic-examples": 5,
39
+ "generate-sample": False,
40
+ "generate-test": False,
41
+ "from-typespec": False,
42
+ "emit-cross-language-definition-file": False,
43
+ }
44
+
45
+ @property
46
+ def is_azure_flavor(self) -> bool:
47
+ return self.flavor == "azure"
48
+
49
+ def __init__(self, options: Dict[str, Any]) -> None:
50
+ self.options = options
51
+
52
+ def __getattr__(self, prop: str) -> Any:
53
+ key = prop.replace("_", "-")
54
+ return self.options.get(key, self.OPTIONS_TO_DEFAULT.get(key))
55
+
56
+ @property
57
+ def company_name(self) -> str:
58
+ return self.options.get("company-name", "Microsoft" if self.is_azure_flavor else "")
59
+
60
+ @property
61
+ def license_header(self) -> str:
62
+ license_header = self.options.get(
63
+ "header-text",
64
+ (DEFAULT_HEADER_TEXT.format(company_name=self.company_name) if self.company_name else ""),
65
+ )
66
+ if license_header:
67
+ license_header = license_header.replace("\n", "\n# ")
68
+ license_header = (
69
+ "# --------------------------------------------------------------------------\n# " + license_header
70
+ )
71
+ license_header += "\n# --------------------------------------------------------------------------"
72
+ return license_header
73
+
74
+ @property
75
+ def show_operations(self) -> bool:
76
+ return self.options.get("show-operations", not self.low_level_client)
77
+
78
+ @property
79
+ def _models_mode_default(self) -> str:
80
+ models_mode_default = "none" if self.low_level_client or self.version_tolerant else "msrest"
81
+ if self.options.get("cadl_file") is not None:
82
+ models_mode_default = "dpg"
83
+ return models_mode_default
84
+
85
+ @property
86
+ def original_models_mode(self) -> str:
87
+ return self.options.get("models-mode", self._models_mode_default)
88
+
89
+ @property
90
+ def models_mode(self) -> Union[str, bool]:
91
+ # switch to falsy value for easier code writing
92
+ return False if self.original_models_mode == "none" else self.original_models_mode
93
+
94
+ @property
95
+ def tracing(self) -> bool:
96
+ return self.options.get(
97
+ "tracing",
98
+ self.show_operations and self.is_azure_flavor,
99
+ )
100
+
101
+ @property
102
+ def show_send_request(self) -> bool:
103
+ return self.options.get(
104
+ "show-send-request",
105
+ self._low_level_or_version_tolerant,
106
+ )
107
+
108
+ @property
109
+ def _low_level_or_version_tolerant(self) -> bool:
110
+ return self.low_level_client or self.version_tolerant
111
+
112
+ @property
113
+ def only_path_and_body_params_positional(self) -> bool:
114
+ return self.options.get(
115
+ "only-path-and-body-params-positional",
116
+ self._low_level_or_version_tolerant,
117
+ )
118
+
119
+ @property
120
+ def combine_operation_files(self) -> bool:
121
+ return self.options.get(
122
+ "combine-operation-files",
123
+ self.version_tolerant,
124
+ )
125
+
126
+ @property
127
+ def package_pprint_name(self) -> str:
128
+ return self.options.get("package-pprint-name") or _default_pprint(str(self.package_name))
129
+
130
+ @property
131
+ def default_optional_constants_to_none(self) -> bool:
132
+ return self.options.get(
133
+ "default-optional-constants-to-none",
134
+ self._low_level_or_version_tolerant,
135
+ )
136
+
137
+ @property
138
+ def builders_visibility(self) -> str:
139
+ builders_visibility = self.options.get("builders-visibility")
140
+ if builders_visibility is None:
141
+ return "public" if self.low_level_client else "embedded"
142
+ return builders_visibility.lower()
143
+
144
+ @property
145
+ def head_as_boolean(self) -> bool:
146
+ head_as_boolean = self.options.get("head-as-boolean", True)
147
+ # Force some options in ARM MODE
148
+ return True if self.azure_arm else head_as_boolean
149
+
150
+ @property
151
+ def package_mode(self) -> str:
152
+ return self.options.get("packaging-files-dir") or self.options.get("package-mode", "")
153
+
154
+ @property
155
+ def packaging_files_config(self) -> Optional[Dict[str, Any]]:
156
+ packaging_files_config = self.options.get("packaging-files-config")
157
+ if packaging_files_config is None:
158
+ return None
159
+ # packaging-files-config is either a string or a dict
160
+ # if it's a string, we can split on the comma to get the dict
161
+ # otherwise we just return
162
+ try:
163
+ return {k.strip(): v.strip() for k, v in [i.split(":") for i in packaging_files_config.split("|")]}
164
+ except AttributeError:
165
+ return packaging_files_config
166
+
167
+ @property
168
+ def package_version(self) -> Optional[str]:
169
+ return str(self.options.get("package-version", ""))
170
+
171
+
172
+ class CodeGenerator(Plugin):
173
+ def __init__(self, *args, **kwargs: Any) -> None:
174
+ super().__init__(*args, **kwargs)
175
+ self.options_retriever = OptionsRetriever(self.options)
176
+
177
+ def _validate_code_model_options(self) -> None:
178
+ if self.options_retriever.builders_visibility not in [
179
+ "public",
180
+ "hidden",
181
+ "embedded",
182
+ ]:
183
+ raise ValueError("The value of --builders-visibility must be either 'public', 'hidden', or 'embedded'")
184
+
185
+ if self.options_retriever.original_models_mode not in ["msrest", "dpg", "none"]:
186
+ raise ValueError(
187
+ "--models-mode can only be 'msrest', 'dpg' or 'none'. "
188
+ "Pass in 'msrest' if you want msrest models, or "
189
+ "'none' if you don't want any."
190
+ )
191
+
192
+ if not self.options_retriever.show_operations and self.options_retriever.builders_visibility == "embedded":
193
+ raise ValueError(
194
+ "Can not embed builders without operations. "
195
+ "Either set --show-operations to True, or change the value of --builders-visibility "
196
+ "to 'public' or 'hidden'."
197
+ )
198
+
199
+ if self.options_retriever.basic_setup_py and not self.options_retriever.package_version:
200
+ raise ValueError("--basic-setup-py must be used with --package-version")
201
+
202
+ if self.options_retriever.package_mode and not self.options_retriever.package_version:
203
+ raise ValueError("--package-mode must be used with --package-version")
204
+
205
+ if not self.options_retriever.show_operations and self.options_retriever.combine_operation_files:
206
+ raise ValueError(
207
+ "Can not combine operation files if you are not showing operations. "
208
+ "If you want operation files, pass in flag --show-operations"
209
+ )
210
+
211
+ if self.options_retriever.package_mode:
212
+ if (
213
+ (
214
+ self.options_retriever.package_mode not in TYPESPEC_PACKAGE_MODE
215
+ and self.options_retriever.from_typespec
216
+ )
217
+ or (
218
+ self.options_retriever.package_mode not in VALID_PACKAGE_MODE
219
+ and not self.options_retriever.from_typespec
220
+ )
221
+ ) and not Path(self.options_retriever.package_mode).exists():
222
+ raise ValueError(
223
+ f"--package-mode can only be {' or '.join(TYPESPEC_PACKAGE_MODE)} or directory which contains template files" # pylint: disable=line-too-long
224
+ )
225
+
226
+ if self.options_retriever.multiapi and self.options_retriever.version_tolerant:
227
+ raise ValueError(
228
+ "Can not currently generate version tolerant multiapi SDKs. "
229
+ "We are working on creating a new multiapi SDK for version tolerant and it is not available yet."
230
+ )
231
+
232
+ if self.options_retriever.client_side_validation and self.options_retriever.version_tolerant:
233
+ raise ValueError("Can not generate version tolerant with --client-side-validation. ")
234
+
235
+ if not (self.options_retriever.azure_arm or self.options_retriever.version_tolerant):
236
+ _LOGGER.warning(
237
+ "You are generating with options that would not allow the SDK to be shipped as an official Azure SDK. "
238
+ "Please read https://aka.ms/azsdk/dpcodegen for more details."
239
+ )
240
+
241
+ if not self.options_retriever.is_azure_flavor and self.options_retriever.tracing:
242
+ raise ValueError("Can only have tracing turned on for Azure SDKs.")
243
+
244
+ @staticmethod
245
+ def sort_exceptions(yaml_data: Dict[str, Any]) -> None:
246
+ for client in yaml_data["clients"]:
247
+ for group in client["operationGroups"]:
248
+ for operation in group["operations"]:
249
+ if not operation.get("exceptions"):
250
+ continue
251
+ # sort exceptions by status code, first single status code, then range, then default
252
+ operation["exceptions"] = sorted(
253
+ operation["exceptions"],
254
+ key=lambda x: (
255
+ 3
256
+ if x["statusCodes"][0] == "default"
257
+ else (1 if isinstance(x["statusCodes"][0], int) else 2)
258
+ ),
259
+ )
260
+
261
+ @staticmethod
262
+ def remove_cloud_errors(yaml_data: Dict[str, Any]) -> None:
263
+ for client in yaml_data["clients"]:
264
+ for group in client["operationGroups"]:
265
+ for operation in group["operations"]:
266
+ if not operation.get("exceptions"):
267
+ continue
268
+ i = 0
269
+ while i < len(operation["exceptions"]):
270
+ exception = operation["exceptions"][i]
271
+ if (
272
+ exception.get("schema")
273
+ and exception["schema"]["language"]["default"]["name"] == "CloudError"
274
+ ):
275
+ del operation["exceptions"][i]
276
+ i -= 1
277
+ i += 1
278
+ if yaml_data.get("schemas") and yaml_data["schemas"].get("objects"):
279
+ for i in range(len(yaml_data["schemas"]["objects"])):
280
+ obj_schema = yaml_data["schemas"]["objects"][i]
281
+ if obj_schema["language"]["default"]["name"] == "CloudError":
282
+ del yaml_data["schemas"]["objects"][i]
283
+ break
284
+
285
+ def _build_code_model_options(self) -> Dict[str, Any]:
286
+ flags = [
287
+ "azure_arm",
288
+ "head_as_boolean",
289
+ "license_header",
290
+ "keep_version_file",
291
+ "no_async",
292
+ "no_namespace_folders",
293
+ "basic_setup_py",
294
+ "package_name",
295
+ "package_version",
296
+ "client_side_validation",
297
+ "tracing",
298
+ "multiapi",
299
+ "polymorphic_examples",
300
+ "models_mode",
301
+ "builders_visibility",
302
+ "show_operations",
303
+ "show_send_request",
304
+ "only_path_and_body_params_positional",
305
+ "version_tolerant",
306
+ "low_level_client",
307
+ "combine_operation_files",
308
+ "package_mode",
309
+ "package_pprint_name",
310
+ "packaging_files_config",
311
+ "default_optional_constants_to_none",
312
+ "generate_sample",
313
+ "generate_test",
314
+ "default_api_version",
315
+ "from_typespec",
316
+ "flavor",
317
+ "company_name",
318
+ "emit_cross_language_definition_file",
319
+ ]
320
+ return {f: getattr(self.options_retriever, f) for f in flags}
321
+
322
+ def get_yaml(self) -> Dict[str, Any]:
323
+ # cadl file doesn't have to be relative to output folder
324
+ with open(self.options["cadl_file"], "r", encoding="utf-8-sig") as fd:
325
+ return yaml.safe_load(fd.read())
326
+
327
+ def get_serializer(self, code_model: CodeModel):
328
+ return JinjaSerializer(code_model, output_folder=self.output_folder)
329
+
330
+ def process(self) -> bool:
331
+ # List the input file, should be only one
332
+ self._validate_code_model_options()
333
+ options = self._build_code_model_options()
334
+ yaml_data = self.get_yaml()
335
+
336
+ self.sort_exceptions(yaml_data)
337
+
338
+ if self.options_retriever.azure_arm:
339
+ self.remove_cloud_errors(yaml_data)
340
+
341
+ code_model = CodeModel(yaml_data=yaml_data, options=options)
342
+ if not self.options_retriever.is_azure_flavor and any(client.lro_operations for client in code_model.clients):
343
+ raise ValueError("Only support LROs for Azure SDKs")
344
+ serializer = self.get_serializer(code_model)
345
+ serializer.serialize()
346
+
347
+ return True
348
+
349
+
350
+ if __name__ == "__main__":
351
+ # CADL pipeline will call this
352
+ parsed_args, unknown_args = parse_args()
353
+ CodeGenerator(
354
+ output_folder=parsed_args.output_folder,
355
+ cadl_file=parsed_args.cadl_file,
356
+ **unknown_args,
357
+ ).process()
@@ -0,0 +1,17 @@
1
+ # -------------------------------------------------------------------------
2
+ # Copyright (c) Microsoft Corporation. All rights reserved.
3
+ # Licensed under the MIT License. See License.txt in the project root for
4
+ # license information.
5
+ # --------------------------------------------------------------------------
6
+
7
+ DEFAULT_HEADER_TEXT = (
8
+ "Copyright (c) {company_name} Corporation. All rights reserved.\n"
9
+ "Licensed under the MIT License. See License.txt in the project root for license information.\n"
10
+ "Code generated by {company_name} (R) Python Code Generator.\n"
11
+ "Changes may cause incorrect behavior and will be lost if the code is regenerated."
12
+ )
13
+
14
+ SWAGGER_PACKAGE_MODE = ["mgmtplane", "dataplane"] # for backward compatibility
15
+ TYPESPEC_PACKAGE_MODE = ["azure-mgmt", "azure-dataplane", "generic"]
16
+ VALID_PACKAGE_MODE = SWAGGER_PACKAGE_MODE + TYPESPEC_PACKAGE_MODE
17
+ NAME_LENGTH_LIMIT = 40