@typespec/http-client-python 0.13.0-dev.1 → 0.13.0-dev.3

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 (128) hide show
  1. package/dist/emitter/types.d.ts.map +1 -1
  2. package/dist/emitter/types.js +12 -5
  3. package/dist/emitter/types.js.map +1 -1
  4. package/emitter/src/types.ts +14 -7
  5. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  6. package/eng/scripts/setup/__pycache__/package_manager.cpython-39.pyc +0 -0
  7. package/eng/scripts/setup/__pycache__/venvtools.cpython-39.pyc +0 -0
  8. package/eng/scripts/setup/build_pygen_wheel.py +9 -10
  9. package/eng/scripts/setup/install.py +13 -17
  10. package/eng/scripts/setup/package_manager.py +139 -0
  11. package/eng/scripts/setup/prepare.py +4 -12
  12. package/eng/scripts/setup/venvtools.py +1 -42
  13. package/generator/build/lib/pygen/__init__.py +176 -2
  14. package/generator/build/lib/pygen/black.py +1 -1
  15. package/generator/build/lib/pygen/codegen/__init__.py +4 -256
  16. package/generator/build/lib/pygen/codegen/_utils.py +0 -3
  17. package/generator/build/lib/pygen/codegen/models/__init__.py +1 -1
  18. package/generator/build/lib/pygen/codegen/models/base_builder.py +1 -1
  19. package/generator/build/lib/pygen/codegen/models/client.py +12 -12
  20. package/generator/build/lib/pygen/codegen/models/code_model.py +10 -9
  21. package/generator/build/lib/pygen/codegen/models/enum_type.py +4 -4
  22. package/generator/build/lib/pygen/codegen/models/imports.py +1 -1
  23. package/generator/build/lib/pygen/codegen/models/list_type.py +6 -6
  24. package/generator/build/lib/pygen/codegen/models/lro_operation.py +1 -1
  25. package/generator/build/lib/pygen/codegen/models/operation.py +16 -16
  26. package/generator/build/lib/pygen/codegen/models/operation_group.py +4 -4
  27. package/generator/build/lib/pygen/codegen/models/paging_operation.py +4 -4
  28. package/generator/build/lib/pygen/codegen/models/parameter.py +8 -8
  29. package/generator/build/lib/pygen/codegen/models/property.py +7 -1
  30. package/generator/build/lib/pygen/codegen/models/request_builder.py +2 -2
  31. package/generator/build/lib/pygen/codegen/models/request_builder_parameter.py +2 -2
  32. package/generator/build/lib/pygen/codegen/models/response.py +3 -3
  33. package/generator/build/lib/pygen/codegen/serializers/__init__.py +27 -28
  34. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +31 -31
  35. package/generator/build/lib/pygen/codegen/serializers/client_serializer.py +4 -4
  36. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +4 -4
  37. package/generator/build/lib/pygen/codegen/serializers/metadata_serializer.py +1 -1
  38. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +1 -1
  39. package/generator/build/lib/pygen/codegen/serializers/operations_init_serializer.py +1 -1
  40. package/generator/build/lib/pygen/codegen/serializers/sample_serializer.py +1 -1
  41. package/generator/build/lib/pygen/codegen/serializers/test_serializer.py +5 -5
  42. package/generator/build/lib/pygen/codegen/templates/config.py.jinja2 +1 -1
  43. package/generator/build/lib/pygen/codegen/templates/config_container.py.jinja2 +1 -1
  44. package/generator/build/lib/pygen/codegen/templates/conftest.py.jinja2 +1 -1
  45. package/generator/build/lib/pygen/codegen/templates/init.py.jinja2 +1 -1
  46. package/generator/build/lib/pygen/codegen/templates/metadata.json.jinja2 +2 -2
  47. package/generator/build/lib/pygen/codegen/templates/model_dpg.py.jinja2 +2 -2
  48. package/generator/build/lib/pygen/codegen/templates/operation_group.py.jinja2 +1 -1
  49. package/generator/build/lib/pygen/codegen/templates/operation_groups_container.py.jinja2 +1 -1
  50. package/generator/build/lib/pygen/codegen/templates/operation_tools.jinja2 +1 -1
  51. package/generator/build/lib/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 +1 -1
  52. package/generator/build/lib/pygen/codegen/templates/packaging_templates/README.md.jinja2 +38 -7
  53. package/generator/build/lib/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 +1 -1
  54. package/generator/build/lib/pygen/codegen/templates/packaging_templates/setup.py.jinja2 +9 -9
  55. package/generator/build/lib/pygen/codegen/templates/request_builder.py.jinja2 +1 -1
  56. package/generator/build/lib/pygen/codegen/templates/sample.py.jinja2 +1 -1
  57. package/generator/build/lib/pygen/codegen/templates/test.py.jinja2 +6 -6
  58. package/generator/build/lib/pygen/codegen/templates/version.py.jinja2 +1 -1
  59. package/generator/build/lib/pygen/preprocess/__init__.py +1 -1
  60. package/generator/build/lib/pygen/utils.py +4 -0
  61. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  62. package/generator/pygen/__init__.py +176 -2
  63. package/generator/pygen/black.py +1 -1
  64. package/generator/pygen/codegen/__init__.py +4 -256
  65. package/generator/pygen/codegen/_utils.py +0 -3
  66. package/generator/pygen/codegen/models/__init__.py +1 -1
  67. package/generator/pygen/codegen/models/base_builder.py +1 -1
  68. package/generator/pygen/codegen/models/client.py +12 -12
  69. package/generator/pygen/codegen/models/code_model.py +10 -9
  70. package/generator/pygen/codegen/models/enum_type.py +4 -4
  71. package/generator/pygen/codegen/models/imports.py +1 -1
  72. package/generator/pygen/codegen/models/list_type.py +6 -6
  73. package/generator/pygen/codegen/models/lro_operation.py +1 -1
  74. package/generator/pygen/codegen/models/operation.py +16 -16
  75. package/generator/pygen/codegen/models/operation_group.py +4 -4
  76. package/generator/pygen/codegen/models/paging_operation.py +4 -4
  77. package/generator/pygen/codegen/models/parameter.py +8 -8
  78. package/generator/pygen/codegen/models/property.py +7 -1
  79. package/generator/pygen/codegen/models/request_builder.py +2 -2
  80. package/generator/pygen/codegen/models/request_builder_parameter.py +2 -2
  81. package/generator/pygen/codegen/models/response.py +3 -3
  82. package/generator/pygen/codegen/serializers/__init__.py +27 -28
  83. package/generator/pygen/codegen/serializers/builder_serializer.py +31 -31
  84. package/generator/pygen/codegen/serializers/client_serializer.py +4 -4
  85. package/generator/pygen/codegen/serializers/general_serializer.py +4 -4
  86. package/generator/pygen/codegen/serializers/metadata_serializer.py +1 -1
  87. package/generator/pygen/codegen/serializers/model_serializer.py +1 -1
  88. package/generator/pygen/codegen/serializers/operations_init_serializer.py +1 -1
  89. package/generator/pygen/codegen/serializers/sample_serializer.py +1 -1
  90. package/generator/pygen/codegen/serializers/test_serializer.py +5 -5
  91. package/generator/pygen/codegen/templates/config.py.jinja2 +1 -1
  92. package/generator/pygen/codegen/templates/config_container.py.jinja2 +1 -1
  93. package/generator/pygen/codegen/templates/conftest.py.jinja2 +1 -1
  94. package/generator/pygen/codegen/templates/init.py.jinja2 +1 -1
  95. package/generator/pygen/codegen/templates/metadata.json.jinja2 +2 -2
  96. package/generator/pygen/codegen/templates/model_dpg.py.jinja2 +2 -2
  97. package/generator/pygen/codegen/templates/operation_group.py.jinja2 +1 -1
  98. package/generator/pygen/codegen/templates/operation_groups_container.py.jinja2 +1 -1
  99. package/generator/pygen/codegen/templates/operation_tools.jinja2 +1 -1
  100. package/generator/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 +1 -1
  101. package/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 +38 -7
  102. package/generator/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 +1 -1
  103. package/generator/pygen/codegen/templates/packaging_templates/setup.py.jinja2 +9 -9
  104. package/generator/pygen/codegen/templates/request_builder.py.jinja2 +1 -1
  105. package/generator/pygen/codegen/templates/sample.py.jinja2 +1 -1
  106. package/generator/pygen/codegen/templates/test.py.jinja2 +6 -6
  107. package/generator/pygen/codegen/templates/version.py.jinja2 +1 -1
  108. package/generator/pygen/preprocess/__init__.py +1 -1
  109. package/generator/pygen/utils.py +4 -0
  110. package/generator/test/azure/mock_api_tests/asynctests/test_azure_arm_commonproperties_async.py +31 -0
  111. package/generator/test/azure/mock_api_tests/asynctests/test_azure_arm_largeheader_async.py +30 -0
  112. package/generator/test/azure/mock_api_tests/asynctests/test_azure_arm_nonresource_async.py +36 -0
  113. package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_client_initialization_async.py +9 -0
  114. package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_deserialize_empty_string_as_null_async.py +20 -0
  115. package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_usage_async.py +7 -0
  116. package/generator/test/azure/mock_api_tests/asynctests/test_azure_encode_duration_async.py +19 -0
  117. package/generator/test/azure/mock_api_tests/test_azure_arm_commonproperties.py +29 -0
  118. package/generator/test/azure/mock_api_tests/test_azure_arm_largeheader.py +27 -0
  119. package/generator/test/azure/mock_api_tests/test_azure_arm_nonresource.py +34 -0
  120. package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_client_initialization.py +8 -0
  121. package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_deserialize_empty_string_as_null.py +19 -0
  122. package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_usage.py +4 -0
  123. package/generator/test/azure/mock_api_tests/test_azure_encode_duration.py +18 -0
  124. package/generator/test/azure/requirements.txt +4 -0
  125. package/generator/test/unittests/test_optional_return_type.py +5 -5
  126. package/generator/test/unittests/test_parameter_ordering.py +5 -5
  127. package/generator/test/unittests/test_sort_schema.py +4 -4
  128. package/package.json +1 -1
@@ -11,28 +11,27 @@ if not sys.version_info >= (3, 9, 0):
11
11
  raise Exception("Autorest for Python extension requires Python 3.9 at least")
12
12
 
13
13
  try:
14
- import pip
15
- except (ImportError, ModuleNotFoundError):
16
- raise Exception("Your Python installation doesn't have pip available")
14
+ from package_manager import detect_package_manager, PackageManagerNotFoundError
15
+ detect_package_manager() # Just check if we have a package manager
16
+ except (ImportError, ModuleNotFoundError, PackageManagerNotFoundError):
17
+ raise Exception("Your Python installation doesn't have a suitable package manager (pip or uv) available")
17
18
 
18
19
 
19
- # Now we have pip and Py >= 3.9, go to work
20
+ # Now we have a package manager (pip or uv) and Py >= 3.9, go to work
20
21
 
21
22
  from pathlib import Path
22
23
 
23
- from venvtools import ExtendedEnvBuilder, python_run
24
+ from venvtools import python_run
25
+ from package_manager import install_packages, create_venv_with_package_manager
24
26
 
25
27
  _ROOT_DIR = Path(__file__).parent.parent.parent.parent
26
28
 
27
29
 
28
30
  def main():
29
31
  venv_path = _ROOT_DIR / "venv_build_wheel"
30
- env_builder = ExtendedEnvBuilder(with_pip=True, upgrade_deps=True)
31
- env_builder.create(venv_path)
32
- venv_context = env_builder.context
32
+ venv_context = create_venv_with_package_manager(venv_path)
33
33
 
34
- python_run(venv_context, "pip", ["install", "-U", "pip"])
35
- python_run(venv_context, "pip", ["install", "build"])
34
+ install_packages(["build"], venv_context)
36
35
  python_run(venv_context, "build", ["--wheel"], additional_dir="generator")
37
36
 
38
37
 
@@ -13,10 +13,11 @@ if not sys.version_info >= (3, 9, 0):
13
13
  )
14
14
 
15
15
  try:
16
- import pip
17
- except (ImportError, ModuleNotFoundError):
16
+ from package_manager import detect_package_manager, PackageManagerNotFoundError
17
+ detect_package_manager() # Just check if we have a package manager
18
+ except (ImportError, ModuleNotFoundError, PackageManagerNotFoundError):
18
19
  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
+ "Your Python installation doesn't have a suitable package manager (pip or uv) available. We will run your code with Pyodide since your Python environment isn't adequate."
20
21
  )
21
22
 
22
23
  try:
@@ -27,28 +28,23 @@ except (ImportError, ModuleNotFoundError):
27
28
  )
28
29
 
29
30
 
30
- # Now we have pip and Py >= 3.8, go to work
31
+ # Now we have a package manager (uv or pip) and Py >= 3.8, go to work
31
32
 
32
33
  from pathlib import Path
33
34
 
34
- from venvtools import ExtendedEnvBuilder, python_run
35
-
36
35
  _ROOT_DIR = Path(__file__).parent.parent.parent.parent
37
36
 
38
37
 
39
38
  def main():
40
39
  venv_path = _ROOT_DIR / "venv"
41
- if venv_path.exists():
42
- env_builder = venv.EnvBuilder(with_pip=True)
43
- venv_context = env_builder.ensure_directories(venv_path)
44
- else:
45
- env_builder = ExtendedEnvBuilder(with_pip=True, upgrade_deps=True)
46
- env_builder.create(venv_path)
47
- venv_context = env_builder.context
48
-
49
- python_run(venv_context, "pip", ["install", "-U", "pip"])
50
- python_run(venv_context, "pip", ["install", "-U", "black"])
51
- python_run(venv_context, "pip", ["install", "-e", f"{_ROOT_DIR}/generator"])
40
+
41
+ # Create virtual environment using package manager abstraction
42
+ from package_manager import create_venv_with_package_manager, install_packages
43
+ venv_context = create_venv_with_package_manager(venv_path)
44
+
45
+ # Install required packages - install_packages handles package manager logic
46
+ install_packages(["-U", "black"], venv_context)
47
+ install_packages(["-e", f"{_ROOT_DIR}/generator"], venv_context)
52
48
 
53
49
 
54
50
  if __name__ == "__main__":
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env python
2
+
3
+ # -------------------------------------------------------------------------
4
+ # Copyright (c) Microsoft Corporation. All rights reserved.
5
+ # Licensed under the MIT License. See License.txt in the project root for
6
+ # license information.
7
+ # --------------------------------------------------------------------------
8
+ """Package manager utilities for detecting and using pip or uv."""
9
+
10
+ import subprocess
11
+ import sys
12
+ import venv
13
+ from pathlib import Path
14
+ from venvtools import ExtendedEnvBuilder
15
+
16
+
17
+ class PackageManagerNotFoundError(Exception):
18
+ """Raised when no suitable package manager is found."""
19
+ pass
20
+
21
+
22
+ def _check_command_available(command: str) -> bool:
23
+ """Check if a command is available in the environment."""
24
+ try:
25
+ subprocess.run([command, "--version"], capture_output=True, check=True)
26
+ return True
27
+ except (subprocess.CalledProcessError, FileNotFoundError):
28
+ return False
29
+
30
+
31
+ def detect_package_manager() -> str:
32
+ """Detect the best available package manager.
33
+
34
+ Returns:
35
+ str: The package manager command ('uv' or 'pip')
36
+
37
+ Raises:
38
+ PackageManagerNotFoundError: If no suitable package manager is found
39
+ """
40
+ # Check for uv first since it's more modern and faster
41
+ if _check_command_available("uv"):
42
+ return "uv"
43
+
44
+ # Fall back to pip
45
+ if _check_command_available("pip"):
46
+ return "pip"
47
+
48
+ # As a last resort, try using python -m pip
49
+ try:
50
+ subprocess.run([sys.executable, "-m", "pip", "--version"],
51
+ capture_output=True, check=True)
52
+ return "python -m pip"
53
+ except (subprocess.CalledProcessError, FileNotFoundError):
54
+ pass
55
+
56
+ raise PackageManagerNotFoundError(
57
+ "No suitable package manager found. Please install either uv or pip."
58
+ )
59
+
60
+
61
+ def get_install_command(package_manager: str, venv_context=None) -> list:
62
+ """Get the install command for the given package manager.
63
+
64
+ Args:
65
+ package_manager: The package manager command ('uv', 'pip', or 'python -m pip')
66
+ venv_context: The virtual environment context (optional, used for pip)
67
+
68
+ Returns:
69
+ list: The base install command as a list
70
+ """
71
+ if package_manager == "uv":
72
+ cmd = ["uv", "pip", "install"]
73
+ if venv_context:
74
+ cmd.extend(["--python", venv_context.env_exe])
75
+ return cmd
76
+ elif package_manager == "pip":
77
+ if venv_context:
78
+ return [venv_context.env_exe, "-m", "pip", "install"]
79
+ else:
80
+ return ["pip", "install"]
81
+ elif package_manager == "python -m pip":
82
+ if venv_context:
83
+ return [venv_context.env_exe, "-m", "pip", "install"]
84
+ else:
85
+ return [sys.executable, "-m", "pip", "install"]
86
+ else:
87
+ raise ValueError(f"Unknown package manager: {package_manager}")
88
+
89
+
90
+ def install_packages(packages: list, venv_context=None, package_manager: str = None) -> None:
91
+ """Install packages using the available package manager.
92
+
93
+ Args:
94
+ packages: List of packages to install
95
+ venv_context: Virtual environment context (optional)
96
+ package_manager: Package manager to use (auto-detected if None)
97
+ """
98
+ if package_manager is None:
99
+ package_manager = detect_package_manager()
100
+
101
+ install_cmd = get_install_command(package_manager, venv_context)
102
+
103
+ try:
104
+ subprocess.check_call(install_cmd + packages)
105
+ except subprocess.CalledProcessError as e:
106
+ raise RuntimeError(f"Failed to install packages with {package_manager}: {e}")
107
+
108
+
109
+ def create_venv_with_package_manager(venv_path):
110
+ """Create virtual environment using the best available package manager.
111
+
112
+ Args:
113
+ venv_path: Path where to create the virtual environment
114
+
115
+ Returns:
116
+ venv_context: Virtual environment context object
117
+ """
118
+ package_manager = detect_package_manager()
119
+
120
+ if package_manager == "uv":
121
+ # Use uv to create and manage the virtual environment
122
+ if not venv_path.exists():
123
+ subprocess.check_call(["uv", "venv", str(venv_path)])
124
+
125
+ # Create a mock venv_context for compatibility
126
+ class MockVenvContext:
127
+ def __init__(self, venv_path):
128
+ self.env_exe = str(venv_path / "bin" / "python") if sys.platform != "win32" else str(venv_path / "Scripts" / "python.exe")
129
+
130
+ return MockVenvContext(venv_path)
131
+ else:
132
+ # Use standard venv for pip
133
+ if venv_path.exists():
134
+ env_builder = venv.EnvBuilder(with_pip=True)
135
+ return env_builder.ensure_directories(venv_path)
136
+ else:
137
+ env_builder = ExtendedEnvBuilder(with_pip=True, upgrade_deps=True)
138
+ env_builder.create(venv_path)
139
+ return env_builder.context
@@ -6,8 +6,6 @@
6
6
  # license information.
7
7
  # --------------------------------------------------------------------------
8
8
  import sys
9
- import os
10
- import argparse
11
9
 
12
10
  if not sys.version_info >= (3, 9, 0):
13
11
  raise Warning(
@@ -15,9 +13,7 @@ if not sys.version_info >= (3, 9, 0):
15
13
  )
16
14
 
17
15
  from pathlib import Path
18
- import venv
19
-
20
- from venvtools import python_run
16
+ from package_manager import create_venv_with_package_manager, install_packages
21
17
 
22
18
  _ROOT_DIR = Path(__file__).parent.parent.parent.parent
23
19
 
@@ -28,14 +24,10 @@ def main():
28
24
 
29
25
  assert venv_preexists # Otherwise install was not done
30
26
 
31
- env_builder = venv.EnvBuilder(with_pip=True)
32
- venv_context = env_builder.ensure_directories(venv_path)
27
+ venv_context = create_venv_with_package_manager(venv_path)
28
+
33
29
  try:
34
- python_run(
35
- venv_context,
36
- "pip",
37
- ["install", "-r", f"{_ROOT_DIR}/generator/dev_requirements.txt"],
38
- )
30
+ install_packages(["-r", f"{_ROOT_DIR}/generator/dev_requirements.txt"], venv_context)
39
31
  except FileNotFoundError as e:
40
32
  raise ValueError(e.filename)
41
33
 
@@ -3,8 +3,6 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
- from contextlib import contextmanager
7
- import tempfile
8
6
  import subprocess
9
7
  import venv
10
8
  import sys
@@ -31,51 +29,12 @@ class ExtendedEnvBuilder(venv.EnvBuilder):
31
29
  return self.context
32
30
 
33
31
 
34
- def create(
35
- env_dir,
36
- system_site_packages=False,
37
- clear=False,
38
- symlinks=False,
39
- with_pip=False,
40
- prompt=None,
41
- upgrade_deps=False,
42
- ):
43
- """Create a virtual environment in a directory."""
44
- builder = ExtendedEnvBuilder(
45
- system_site_packages=system_site_packages,
46
- clear=clear,
47
- symlinks=symlinks,
48
- with_pip=with_pip,
49
- prompt=prompt,
50
- upgrade_deps=upgrade_deps,
51
- )
52
- builder.create(env_dir)
53
- return builder.context
54
-
55
-
56
- @contextmanager
57
- def create_venv_with_package(packages):
58
- """Create a venv with these packages in a temp dir and yield the env.
59
-
60
- packages should be an iterable of pip version instruction (e.g. package~=1.2.3)
61
- """
62
- with tempfile.TemporaryDirectory() as tempdir:
63
- my_env = create(tempdir, with_pip=True, upgrade_deps=True)
64
- pip_call = [
65
- my_env.env_exe,
66
- "-m",
67
- "pip",
68
- "install",
69
- ]
70
- subprocess.check_call(pip_call + ["-U", "pip"])
71
- if packages:
72
- subprocess.check_call(pip_call + packages)
73
- yield my_env
74
32
 
75
33
 
76
34
  def python_run(venv_context, module, command=None, *, additional_dir="."):
77
35
  try:
78
36
  cmd_line = [venv_context.env_exe, "-m", module] + (command if command else [])
37
+
79
38
  print("Executing: {}".format(" ".join(cmd_line)))
80
39
  subprocess.run(
81
40
  cmd_line,
@@ -3,13 +3,15 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
+ from collections.abc import ItemsView, KeysView, MutableMapping, ValuesView
6
7
  import logging
7
8
  from pathlib import Path
8
9
  import json
9
10
  from abc import ABC, abstractmethod
10
- from typing import Any, Dict, Union, List
11
+ from typing import Any, Dict, Iterator, Optional, Union, List
11
12
 
12
13
  import yaml
14
+ from .utils import TYPESPEC_PACKAGE_MODE, VALID_PACKAGE_MODE
13
15
 
14
16
  from ._version import VERSION
15
17
 
@@ -18,6 +20,177 @@ __version__ = VERSION
18
20
  _LOGGER = logging.getLogger(__name__)
19
21
 
20
22
 
23
+ class OptionsDict(MutableMapping):
24
+ DEFAULTS = {
25
+ "azure-arm": False,
26
+ "basic-setup-py": False,
27
+ "client-side-validation": False,
28
+ "emit-cross-language-definition-file": False,
29
+ "flavor": "azure", # need to default to azure in shared code so we don't break swagger generation
30
+ "from-typespec": False,
31
+ "generate-sample": False,
32
+ "generate-test": False,
33
+ "head-as-boolean": True,
34
+ "keep-version-file": False,
35
+ "low-level-client": False,
36
+ "multiapi": False,
37
+ "no-async": False,
38
+ "no-namespace-folders": False,
39
+ "polymorphic-examples": 5,
40
+ "validate-versioning": True,
41
+ "version-tolerant": True,
42
+ }
43
+
44
+ def __init__(self, options: Optional[Dict[str, Any]] = None) -> None:
45
+ self._data = options.copy() if options else {}
46
+ self._validate_combinations()
47
+
48
+ def __getitem__(self, key: str) -> Any:
49
+ if key == "head-as-boolean" and self.get("azure-arm"):
50
+ # override to always true if azure-arm is set
51
+ return True
52
+ if key in self._data:
53
+ retval = self._data[key]
54
+ if key == "package-files-config":
55
+ try:
56
+ return {k.strip(): v.strip() for k, v in [i.split(":") for i in retval.split("|")]}
57
+ except AttributeError:
58
+ return retval
59
+ return retval
60
+ if key == "package-mode" and self._data.get("packaging-files-dir"):
61
+ # if packaging-files-dir is set, use it as package-mode
62
+ return self._data["packaging-files-dir"]
63
+ return self._get_default(key)
64
+
65
+ def __setitem__(self, key: str, value: Any) -> None:
66
+ validated_value = self._validate_and_transform(key, value)
67
+ self._data[key] = validated_value
68
+
69
+ def __delitem__(self, key: str) -> None:
70
+ if key in self._data:
71
+ del self._data[key]
72
+ else:
73
+ raise KeyError(f"Option '{key}' not found")
74
+
75
+ def __iter__(self) -> Iterator[str]:
76
+ # Return both explicitly set keys and all possible default keys
77
+ return iter(set(self.keys()))
78
+
79
+ def __len__(self) -> int:
80
+ return len(set(self._data.keys()).union(self.DEFAULTS.keys()))
81
+
82
+ def __contains__(self, key: str) -> bool: # type: ignore
83
+ return key in self._data or key in self.DEFAULTS
84
+
85
+ def __repr__(self) -> str:
86
+ """String representation."""
87
+ return f"OptionsDict({dict(self.items())})"
88
+
89
+ def _get_default(self, key: str) -> Any: # pylint: disable=too-many-return-statements
90
+ if key == "show-operations":
91
+ return not self.get("low-level-client")
92
+ if key == "tracing":
93
+ return self.get("show-operations") and self.get("flavor") == "azure"
94
+ if key in ["show-send-request", "only-path-and-body-params-positional", "default-optional-constants-to-none"]:
95
+ return self.get("low-level-client") or self.get("version-tolerant")
96
+ if key == "combine-operation-files":
97
+ return self.get("version-tolerant")
98
+ if key == "package-pprint-name":
99
+ return " ".join([i.capitalize() for i in str(self.get("package-name", "")).split("-")])
100
+ if key == "builders-visibility":
101
+ # Default to public if low-level client is not set, otherwise embedded
102
+ return "embedded" if not self.get("low-level-client") else "public"
103
+ if key == "models-mode":
104
+ models_mode_default = False if self.get("low-level-client") or self.get("version-tolerant") else "msrest"
105
+ if self.get("tsp_file") is not None:
106
+ models_mode_default = "dpg"
107
+ # switch to falsy value for easier code writing
108
+ return models_mode_default
109
+ return self.DEFAULTS[key]
110
+
111
+ def _validate_combinations(self) -> None:
112
+ if not self.get("show-operations") and self.get("builders-visibility") == "embedded":
113
+ raise ValueError(
114
+ "Can not embed builders without operations. "
115
+ "Either set --show-operations to True, or change the value of --builders-visibility "
116
+ "to 'public' or 'hidden'."
117
+ )
118
+
119
+ if self.get("basic-setup-py") and not self.get("package-version"):
120
+ raise ValueError("--basic-setup-py must be used with --package-version")
121
+
122
+ if self.get("package-mode") and not self.get("package-version"):
123
+ raise ValueError("--package-mode must be used with --package-version")
124
+
125
+ if not self.get("show-operations") and self.get("combine-operation-files"):
126
+ raise ValueError(
127
+ "Can not combine operation files if you are not showing operations. "
128
+ "If you want operation files, pass in flag --show-operations"
129
+ )
130
+
131
+ if self.get("multiapi") and self.get("version-tolerant"):
132
+ raise ValueError(
133
+ "Can not currently generate version tolerant multiapi SDKs. "
134
+ "We are working on creating a new multiapi SDK for version tolerant and it is not available yet."
135
+ )
136
+
137
+ if self.get("client-side-validation") and self.get("version-tolerant"):
138
+ raise ValueError("Can not generate version tolerant with --client-side-validation. ")
139
+
140
+ if not (self.get("azure-arm") or self.get("version-tolerant")):
141
+ _LOGGER.warning(
142
+ "You are generating with options that would not allow the SDK to be shipped as an official Azure SDK. "
143
+ "Please read https://aka.ms/azsdk/dpcodegen for more details."
144
+ )
145
+
146
+ if self.get("flavor") != "azure" and self.get("tracing"):
147
+ raise ValueError("Can only have tracing turned on for Azure SDKs.")
148
+
149
+ def _validate_and_transform(self, key: str, value: Any) -> Any:
150
+ if key == "builders-visibility" and value not in ["public", "hidden", "embedded"]:
151
+ raise ValueError("The value of --builders-visibility must be either 'public', 'hidden', or 'embedded'")
152
+
153
+ if key == "models-mode" and value == "none":
154
+ value = False # switch to falsy value for easier code writing
155
+
156
+ if key == "models-mode" and value not in ["msrest", "dpg", False]:
157
+ raise ValueError(
158
+ "--models-mode can only be 'msrest', 'dpg' or 'none'. "
159
+ "Pass in 'msrest' if you want msrest models, or "
160
+ "'none' if you don't want any."
161
+ )
162
+ if key == "package-mode":
163
+ if (
164
+ (value not in TYPESPEC_PACKAGE_MODE and self.get("from-typespec"))
165
+ or (value not in VALID_PACKAGE_MODE and not self.get("from-typespec"))
166
+ ) and not Path(value).exists():
167
+ raise ValueError(
168
+ f"--package-mode can only be {' or '.join(TYPESPEC_PACKAGE_MODE)} or directory which contains template files" # pylint: disable=line-too-long
169
+ )
170
+ return value
171
+
172
+ def setdefault(self, key: str, default: Any, /) -> Any: # type: ignore # pylint: disable=arguments-differ
173
+ """Set a default value for a key if it does not exist."""
174
+ if key not in self._data:
175
+ self[key] = default
176
+ return self[key]
177
+
178
+ def keys(self) -> KeysView[str]:
179
+ """Return all keys, including defaults."""
180
+ all_keys = set(self._data.keys())
181
+ for key in self.DEFAULTS:
182
+ if key not in all_keys:
183
+ all_keys.add(key)
184
+ all_keys.update(self.DEFAULTS.keys())
185
+ return KeysView({key: None for key in all_keys})
186
+
187
+ def values(self) -> ValuesView[Any]:
188
+ return {key: self[key] for key in self.keys()}.values() # pylint: disable=consider-using-dict-items
189
+
190
+ def items(self) -> ItemsView[str, Any]:
191
+ return {key: self[key] for key in self.keys()}.items() # pylint: disable=consider-using-dict-items
192
+
193
+
21
194
  class ReaderAndWriter:
22
195
  def __init__(self, *, output_folder: Union[str, Path], **kwargs: Any) -> None:
23
196
  self.output_folder = Path(output_folder)
@@ -31,7 +204,8 @@ class ReaderAndWriter:
31
204
  python_json = json.load(fd)
32
205
  except Exception: # pylint: disable=broad-except
33
206
  python_json = {}
34
- self.options = kwargs
207
+ kwargs["output-folder"] = str(self.output_folder)
208
+ self.options = OptionsDict(kwargs)
35
209
  if python_json:
36
210
  _LOGGER.warning("Loading python.json file. This behavior will be depreacted")
37
211
  self.options.update(python_json)
@@ -21,7 +21,7 @@ _BLACK_MODE.line_length = 120
21
21
  class BlackScriptPlugin(Plugin):
22
22
  def __init__(self, **kwargs):
23
23
  super().__init__(**kwargs)
24
- output_folder = self.options.get("output_folder", str(self.output_folder))
24
+ output_folder = self.options.get("output-folder", str(self.output_folder))
25
25
  if output_folder.startswith("file:"):
26
26
  output_folder = output_folder[5:]
27
27
  if os.name == "nt" and output_folder.startswith("///"):