openapi-python-client 0.19.1__tar.gz → 0.21.0__tar.gz

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 (109) hide show
  1. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/PKG-INFO +4 -4
  2. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/__init__.py +32 -52
  3. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/cli.py +42 -51
  4. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/config.py +13 -3
  5. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/const.py +3 -2
  6. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/model_property.py +22 -8
  7. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/protocol.py +2 -0
  8. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/endpoint_macros.py.jinja +2 -2
  9. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/model.py.jinja +10 -16
  10. openapi_python_client-0.21.0/openapi_python_client/templates/property_templates/any_property.py.jinja +7 -0
  11. openapi_python_client-0.21.0/openapi_python_client/templates/property_templates/boolean_property.py.jinja +11 -0
  12. openapi_python_client-0.21.0/openapi_python_client/templates/property_templates/const_property.py.jinja +5 -0
  13. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/property_templates/date_property.py.jinja +13 -5
  14. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/property_templates/datetime_property.py.jinja +13 -5
  15. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/property_templates/enum_property.py.jinja +12 -4
  16. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/property_templates/file_property.py.jinja +11 -1
  17. openapi_python_client-0.21.0/openapi_python_client/templates/property_templates/float_property.py.jinja +11 -0
  18. openapi_python_client-0.21.0/openapi_python_client/templates/property_templates/int_property.py.jinja +11 -0
  19. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/property_templates/list_property.py.jinja +25 -11
  20. openapi_python_client-0.21.0/openapi_python_client/templates/property_templates/model_property.py.jinja +47 -0
  21. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/property_templates/union_property.py.jinja +27 -3
  22. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/pyproject.toml +7 -7
  23. openapi_python_client-0.19.1/openapi_python_client/templates/property_templates/any_property.py.jinja +0 -0
  24. openapi_python_client-0.19.1/openapi_python_client/templates/property_templates/boolean_property.py.jinja +0 -3
  25. openapi_python_client-0.19.1/openapi_python_client/templates/property_templates/float_property.py.jinja +0 -3
  26. openapi_python_client-0.19.1/openapi_python_client/templates/property_templates/int_property.py.jinja +0 -3
  27. openapi_python_client-0.19.1/openapi_python_client/templates/property_templates/model_property.py.jinja +0 -32
  28. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/.gitignore +0 -0
  29. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/LICENSE +0 -0
  30. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/README.md +0 -0
  31. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/__main__.py +0 -0
  32. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/__init__.py +0 -0
  33. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/bodies.py +0 -0
  34. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/errors.py +0 -0
  35. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/openapi.py +0 -0
  36. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/__init__.py +0 -0
  37. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/any.py +0 -0
  38. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/boolean.py +0 -0
  39. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/date.py +0 -0
  40. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/datetime.py +0 -0
  41. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/enum_property.py +0 -0
  42. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/file.py +0 -0
  43. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/float.py +0 -0
  44. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/int.py +0 -0
  45. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/list_property.py +0 -0
  46. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/none.py +0 -0
  47. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/property.py +0 -0
  48. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/schemas.py +0 -0
  49. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/string.py +0 -0
  50. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/properties/union.py +0 -0
  51. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/parser/responses.py +0 -0
  52. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/py.typed +0 -0
  53. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/3.0.3.md +0 -0
  54. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/3.1.0.md +0 -0
  55. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/__init__.py +0 -0
  56. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/data_type.py +0 -0
  57. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/LICENSE +0 -0
  58. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/README.md +0 -0
  59. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/__init__.py +0 -0
  60. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/callback.py +0 -0
  61. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/components.py +0 -0
  62. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/contact.py +0 -0
  63. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +0 -0
  64. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +0 -0
  65. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/example.py +0 -0
  66. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +0 -0
  67. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/header.py +0 -0
  68. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/info.py +0 -0
  69. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/license.py +0 -0
  70. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/link.py +0 -0
  71. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/media_type.py +0 -0
  72. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +0 -0
  73. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py +0 -0
  74. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +0 -0
  75. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/operation.py +0 -0
  76. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +0 -0
  77. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/path_item.py +0 -0
  78. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/paths.py +0 -0
  79. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/reference.py +0 -0
  80. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +0 -0
  81. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/response.py +0 -0
  82. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/responses.py +0 -0
  83. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/schema.py +0 -0
  84. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py +0 -0
  85. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +0 -0
  86. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/server.py +0 -0
  87. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +0 -0
  88. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/tag.py +0 -0
  89. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/openapi_schema_pydantic/xml.py +0 -0
  90. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/schema/parameter_location.py +0 -0
  91. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/.gitignore.jinja +0 -0
  92. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/README.md.jinja +0 -0
  93. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/api_init.py.jinja +0 -0
  94. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/client.py.jinja +0 -0
  95. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/endpoint_init.py.jinja +0 -0
  96. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/endpoint_module.py.jinja +0 -0
  97. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/errors.py.jinja +0 -0
  98. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/helpers.jinja +0 -0
  99. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/int_enum.py.jinja +0 -0
  100. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/models_init.py.jinja +0 -0
  101. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/package_init.py.jinja +0 -0
  102. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/property_templates/helpers.jinja +0 -0
  103. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/property_templates/property_macros.py.jinja +0 -0
  104. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/pyproject.toml.jinja +0 -0
  105. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/pyproject_ruff.toml.jinja +0 -0
  106. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/setup.py.jinja +0 -0
  107. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/str_enum.py.jinja +0 -0
  108. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/templates/types.py.jinja +0 -0
  109. {openapi_python_client-0.19.1 → openapi_python_client-0.21.0}/openapi_python_client/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openapi-python-client
3
- Version: 0.19.1
3
+ Version: 0.21.0
4
4
  Summary: Generate modern Python clients from OpenAPI
5
5
  Project-URL: repository, https://github.com/openapi-generators/openapi-python-client
6
6
  Author-email: Dylan Anthony <contact@dylananthony.com>
@@ -25,10 +25,10 @@ Requires-Dist: httpx<0.28.0,>=0.20.0
25
25
  Requires-Dist: jinja2<4.0.0,>=3.0.0
26
26
  Requires-Dist: pydantic<3.0.0,>=2.1.1
27
27
  Requires-Dist: python-dateutil<3.0.0,>=2.8.1
28
- Requires-Dist: pyyaml<7.0,>=6.0
29
- Requires-Dist: ruff<0.4,>=0.2
28
+ Requires-Dist: ruamel-yaml<0.19.0,>=0.18.6
29
+ Requires-Dist: ruff<0.5,>=0.2
30
30
  Requires-Dist: shellingham<2.0.0,>=1.3.2
31
- Requires-Dist: typer<0.12,>0.6
31
+ Requires-Dist: typer<0.13,>0.6
32
32
  Requires-Dist: typing-extensions<5.0.0,>=4.8.0
33
33
  Description-Content-Type: text/markdown
34
34
 
@@ -11,8 +11,9 @@ from typing import Any, Dict, List, Optional, Sequence, Union
11
11
 
12
12
  import httpcore
13
13
  import httpx
14
- import yaml
15
14
  from jinja2 import BaseLoader, ChoiceLoader, Environment, FileSystemLoader, PackageLoader
15
+ from ruamel.yaml import YAML
16
+ from ruamel.yaml.error import YAMLError
16
17
 
17
18
  from openapi_python_client import utils
18
19
 
@@ -64,12 +65,22 @@ class Project:
64
65
  )
65
66
 
66
67
  self.project_name: str = config.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client"
67
- self.project_dir: Path = Path.cwd()
68
- if config.meta_type != MetaType.NONE:
69
- self.project_dir /= self.project_name
70
-
71
68
  self.package_name: str = config.package_name_override or self.project_name.replace("-", "_")
72
- self.package_dir: Path = self.project_dir / self.package_name
69
+ self.project_dir: Path # Where the generated code will be placed
70
+ self.package_dir: Path # Where the generated Python module will be placed (same as project_dir if no meta)
71
+
72
+ if config.output_path is not None:
73
+ self.project_dir = config.output_path
74
+ elif config.meta_type == MetaType.NONE:
75
+ self.project_dir = Path.cwd() / self.package_name
76
+ else:
77
+ self.project_dir = Path.cwd() / self.project_name
78
+
79
+ if config.meta_type == MetaType.NONE:
80
+ self.package_dir = self.project_dir
81
+ else:
82
+ self.package_dir = self.project_dir / self.package_name
83
+
73
84
  self.package_description: str = utils.remove_string_escapes(
74
85
  f"A client library for accessing {self.openapi.title}"
75
86
  )
@@ -94,29 +105,16 @@ class Project:
94
105
  def build(self) -> Sequence[GeneratorError]:
95
106
  """Create the project from templates"""
96
107
 
97
- if self.config.meta_type == MetaType.NONE:
98
- print(f"Generating {self.package_name}")
99
- else:
100
- print(f"Generating {self.project_name}")
101
- try:
102
- self.project_dir.mkdir()
103
- except FileExistsError:
104
- return [GeneratorError(detail="Directory already exists. Delete it or use the update command.")]
105
- self._create_package()
106
- self._build_metadata()
107
- self._build_models()
108
- self._build_api()
109
- self._run_post_hooks()
110
- return self._get_errors()
111
-
112
- def update(self) -> Sequence[GeneratorError]:
113
- """Update an existing project"""
108
+ print(f"Generating {self.project_dir}")
109
+ if self.config.overwrite:
110
+ shutil.rmtree(self.project_dir, ignore_errors=True)
114
111
 
115
- if not self.package_dir.is_dir():
116
- return [GeneratorError(detail=f"Directory {self.package_dir} not found")]
117
- print(f"Updating {self.package_name}")
118
- shutil.rmtree(self.package_dir)
112
+ try:
113
+ self.project_dir.mkdir()
114
+ except FileExistsError:
115
+ return [GeneratorError(detail="Directory already exists. Delete it or use the --overwrite option.")]
119
116
  self._create_package()
117
+ self._build_metadata()
120
118
  self._build_models()
121
119
  self._build_api()
122
120
  self._run_post_hooks()
@@ -137,7 +135,7 @@ class Project:
137
135
  )
138
136
  return
139
137
  try:
140
- cwd = self.package_dir if self.config.meta_type == MetaType.NONE else self.project_dir
138
+ cwd = self.project_dir
141
139
  subprocess.run(cmd, cwd=cwd, shell=True, capture_output=True, check=True)
142
140
  except CalledProcessError as err:
143
141
  self.errors.append(
@@ -157,7 +155,8 @@ class Project:
157
155
  return errors
158
156
 
159
157
  def _create_package(self) -> None:
160
- self.package_dir.mkdir()
158
+ if self.package_dir != self.project_dir:
159
+ self.package_dir.mkdir()
161
160
  # Package __init__.py
162
161
  package_init = self.package_dir / "__init__.py"
163
162
 
@@ -302,7 +301,7 @@ def _get_project_for_url_or_path(
302
301
  )
303
302
 
304
303
 
305
- def create_new_client(
304
+ def generate(
306
305
  *,
307
306
  config: Config,
308
307
  custom_template_path: Optional[Path] = None,
@@ -322,26 +321,6 @@ def create_new_client(
322
321
  return project.build()
323
322
 
324
323
 
325
- def update_existing_client(
326
- *,
327
- config: Config,
328
- custom_template_path: Optional[Path] = None,
329
- ) -> Sequence[GeneratorError]:
330
- """
331
- Update an existing client library
332
-
333
- Returns:
334
- A list containing any errors encountered when generating.
335
- """
336
- project = _get_project_for_url_or_path(
337
- custom_template_path=custom_template_path,
338
- config=config,
339
- )
340
- if isinstance(project, GeneratorError):
341
- return [project]
342
- return project.update()
343
-
344
-
345
324
  def _load_yaml_or_json(data: bytes, content_type: Optional[str]) -> Union[Dict[str, Any], GeneratorError]:
346
325
  if content_type == "application/json":
347
326
  try:
@@ -350,8 +329,9 @@ def _load_yaml_or_json(data: bytes, content_type: Optional[str]) -> Union[Dict[s
350
329
  return GeneratorError(header=f"Invalid JSON from provided source: {err}")
351
330
  else:
352
331
  try:
353
- return yaml.safe_load(data)
354
- except yaml.YAMLError as err:
332
+ yaml = YAML(typ="safe")
333
+ return yaml.load(data)
334
+ except YAMLError as err:
355
335
  return GeneratorError(header=f"Invalid YAML from provided source: {err}")
356
336
 
357
337
 
@@ -21,7 +21,14 @@ def _version_callback(value: bool) -> None:
21
21
 
22
22
 
23
23
  def _process_config(
24
- *, url: Optional[str], path: Optional[Path], config_path: Optional[Path], meta_type: MetaType, file_encoding: str
24
+ *,
25
+ url: Optional[str],
26
+ path: Optional[Path],
27
+ config_path: Optional[Path],
28
+ meta_type: MetaType,
29
+ file_encoding: str,
30
+ overwrite: bool,
31
+ output_path: Optional[Path],
25
32
  ) -> Config:
26
33
  source: Union[Path, str]
27
34
  if url and not path:
@@ -49,7 +56,7 @@ def _process_config(
49
56
  except Exception as err:
50
57
  raise typer.BadParameter("Unable to parse config") from err
51
58
 
52
- return Config.from_sources(config_file, meta_type, source, file_encoding)
59
+ return Config.from_sources(config_file, meta_type, source, file_encoding, overwrite, output_path=output_path)
53
60
 
54
61
 
55
62
  # noinspection PyUnusedLocal
@@ -117,62 +124,46 @@ def handle_errors(errors: Sequence[GeneratorError], fail_on_warning: bool = Fals
117
124
  raise typer.Exit(code=1)
118
125
 
119
126
 
120
- custom_template_path_options = {
121
- "help": "A path to a directory containing custom template(s)",
122
- "file_okay": False,
123
- "dir_okay": True,
124
- "readable": True,
125
- "resolve_path": True,
126
- }
127
-
128
- _meta_option = typer.Option(
129
- MetaType.POETRY,
130
- help="The type of metadata you want to generate.",
131
- )
132
-
133
- CONFIG_OPTION = typer.Option(None, "--config", help="Path to the config file to use")
134
-
135
-
136
127
  @app.command()
137
128
  def generate(
138
- url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"),
139
- path: Optional[Path] = typer.Option(None, help="A path to the JSON file"),
140
- custom_template_path: Optional[Path] = typer.Option(None, **custom_template_path_options), # type: ignore
141
- meta: MetaType = _meta_option,
129
+ url: Optional[str] = typer.Option(None, help="A URL to read the OpenAPI document from"),
130
+ path: Optional[Path] = typer.Option(None, help="A path to the OpenAPI document"),
131
+ custom_template_path: Optional[Path] = typer.Option(
132
+ None,
133
+ help="A path to a directory containing custom template(s)",
134
+ file_okay=False,
135
+ dir_okay=True,
136
+ readable=True,
137
+ resolve_path=True,
138
+ ), # type: ignore
139
+ meta: MetaType = typer.Option(
140
+ MetaType.POETRY,
141
+ help="The type of metadata you want to generate.",
142
+ ),
142
143
  file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"),
143
- config_path: Optional[Path] = CONFIG_OPTION,
144
+ config_path: Optional[Path] = typer.Option(None, "--config", help="Path to the config file to use"),
144
145
  fail_on_warning: bool = False,
146
+ overwrite: bool = typer.Option(False, help="Overwrite the existing client if it exists"),
147
+ output_path: Optional[Path] = typer.Option(
148
+ None,
149
+ help="Path to write the generated code to. "
150
+ "Defaults to the OpenAPI document title converted to kebab or snake case (depending on meta type). "
151
+ "Can also be overridden with `project_name_override` or `package_name_override` in config.",
152
+ ),
145
153
  ) -> None:
146
154
  """Generate a new OpenAPI Client library"""
147
- from . import create_new_client
148
-
149
- config = _process_config(url=url, path=path, config_path=config_path, meta_type=meta, file_encoding=file_encoding)
150
- errors = create_new_client(
151
- custom_template_path=custom_template_path,
152
- config=config,
155
+ from . import generate
156
+
157
+ config = _process_config(
158
+ url=url,
159
+ path=path,
160
+ config_path=config_path,
161
+ meta_type=meta,
162
+ file_encoding=file_encoding,
163
+ overwrite=overwrite,
164
+ output_path=output_path,
153
165
  )
154
- handle_errors(errors, fail_on_warning)
155
-
156
-
157
- @app.command()
158
- def update(
159
- url: Optional[str] = typer.Option(None, help="A URL to read the JSON from"),
160
- path: Optional[Path] = typer.Option(None, help="A path to the JSON file"),
161
- custom_template_path: Optional[Path] = typer.Option(None, **custom_template_path_options), # type: ignore
162
- meta: MetaType = _meta_option,
163
- file_encoding: str = typer.Option("utf-8", help="Encoding used when writing generated"),
164
- config_path: Optional[Path] = CONFIG_OPTION,
165
- fail_on_warning: bool = False,
166
- ) -> None:
167
- """Update an existing OpenAPI Client library
168
-
169
- The update command performs the same operations as generate except it does not overwrite specific metadata for the
170
- generated client such as the README.md, .gitignore, and pyproject.toml.
171
- """
172
- from . import update_existing_client
173
-
174
- config = _process_config(config_path=config_path, meta_type=meta, url=url, path=path, file_encoding=file_encoding)
175
- errors = update_existing_client(
166
+ errors = generate(
176
167
  custom_template_path=custom_template_path,
177
168
  config=config,
178
169
  )
@@ -4,9 +4,9 @@ from enum import Enum
4
4
  from pathlib import Path
5
5
  from typing import Dict, List, Optional, Union
6
6
 
7
- import yaml
8
7
  from attr import define
9
8
  from pydantic import BaseModel
9
+ from ruamel.yaml import YAML
10
10
 
11
11
 
12
12
  class ClassOverride(BaseModel):
@@ -51,7 +51,8 @@ class ConfigFile(BaseModel):
51
51
  if mime == "application/json":
52
52
  config_data = json.loads(path.read_text())
53
53
  else:
54
- config_data = yaml.safe_load(path.read_text())
54
+ yaml = YAML(typ="safe")
55
+ config_data = yaml.load(path)
55
56
  config = ConfigFile(**config_data)
56
57
  return config
57
58
 
@@ -72,10 +73,17 @@ class Config:
72
73
  document_source: Union[Path, str]
73
74
  file_encoding: str
74
75
  content_type_overrides: Dict[str, str]
76
+ overwrite: bool
77
+ output_path: Optional[Path]
75
78
 
76
79
  @staticmethod
77
80
  def from_sources(
78
- config_file: ConfigFile, meta_type: MetaType, document_source: Union[Path, str], file_encoding: str
81
+ config_file: ConfigFile,
82
+ meta_type: MetaType,
83
+ document_source: Union[Path, str],
84
+ file_encoding: str,
85
+ overwrite: bool,
86
+ output_path: Optional[Path],
79
87
  ) -> "Config":
80
88
  if config_file.post_hooks is not None:
81
89
  post_hooks = config_file.post_hooks
@@ -103,5 +111,7 @@ class Config:
103
111
  http_timeout=config_file.http_timeout,
104
112
  document_source=document_source,
105
113
  file_encoding=file_encoding,
114
+ overwrite=overwrite,
115
+ output_path=output_path,
106
116
  )
107
117
  return config
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, overload
3
+ from typing import Any, ClassVar, overload
4
4
 
5
5
  from attr import define
6
6
 
@@ -12,7 +12,7 @@ from .string import StringProperty
12
12
 
13
13
  @define
14
14
  class ConstProperty(PropertyProtocol):
15
- """A property representing a Union (anyOf) of other properties"""
15
+ """A property representing a const value"""
16
16
 
17
17
  name: str
18
18
  required: bool
@@ -21,6 +21,7 @@ class ConstProperty(PropertyProtocol):
21
21
  python_name: PythonIdentifier
22
22
  description: str | None
23
23
  example: None
24
+ template: ClassVar[str] = "const_property.py.jinja"
24
25
 
25
26
  @classmethod
26
27
  def build(
@@ -7,7 +7,9 @@ from attrs import define, evolve
7
7
 
8
8
  from ... import Config, utils
9
9
  from ... import schema as oai
10
+ from ...utils import PythonIdentifier
10
11
  from ..errors import ParseError, PropertyError
12
+ from .any import AnyProperty
11
13
  from .enum_property import EnumProperty
12
14
  from .protocol import PropertyProtocol, Value
13
15
  from .schemas import Class, ReferencePath, Schemas, parse_reference_path
@@ -30,7 +32,7 @@ class ModelProperty(PropertyProtocol):
30
32
  optional_properties: list[Property] | None
31
33
  relative_imports: set[str] | None
32
34
  lazy_imports: set[str] | None
33
- additional_properties: bool | Property | None
35
+ additional_properties: Property | None
34
36
  _json_type_string: ClassVar[str] = "Dict[str, Any]"
35
37
 
36
38
  template: ClassVar[str] = "model_property.py.jinja"
@@ -78,7 +80,7 @@ class ModelProperty(PropertyProtocol):
78
80
  optional_properties: list[Property] | None = None
79
81
  relative_imports: set[str] | None = None
80
82
  lazy_imports: set[str] | None = None
81
- additional_properties: bool | Property | None = None
83
+ additional_properties: Property | None = None
82
84
  if process_properties:
83
85
  data_or_err, schemas = _process_property_data(
84
86
  data=data, schemas=schemas, class_info=class_info, config=config, roots=model_roots
@@ -386,6 +388,16 @@ def _process_properties( # noqa: PLR0912, PLR0911
386
388
  )
387
389
 
388
390
 
391
+ ANY_ADDITIONAL_PROPERTY = AnyProperty.build(
392
+ name="additional",
393
+ required=True,
394
+ default=None,
395
+ description="",
396
+ python_name=PythonIdentifier(value="additional", prefix=""),
397
+ example=None,
398
+ )
399
+
400
+
389
401
  def _get_additional_properties(
390
402
  *,
391
403
  schema_additional: None | (bool | (oai.Reference | oai.Schema)),
@@ -393,18 +405,20 @@ def _get_additional_properties(
393
405
  class_name: utils.ClassName,
394
406
  config: Config,
395
407
  roots: set[ReferencePath | utils.ClassName],
396
- ) -> tuple[bool | (Property | PropertyError), Schemas]:
408
+ ) -> tuple[Property | None | PropertyError, Schemas]:
397
409
  from . import property_from_data
398
410
 
399
411
  if schema_additional is None:
400
- return True, schemas
412
+ return ANY_ADDITIONAL_PROPERTY, schemas
401
413
 
402
414
  if isinstance(schema_additional, bool):
403
- return schema_additional, schemas
415
+ if schema_additional:
416
+ return ANY_ADDITIONAL_PROPERTY, schemas
417
+ return None, schemas
404
418
 
405
419
  if isinstance(schema_additional, oai.Schema) and not any(schema_additional.model_dump().values()):
406
420
  # An empty schema
407
- return True, schemas
421
+ return ANY_ADDITIONAL_PROPERTY, schemas
408
422
 
409
423
  additional_properties, schemas = property_from_data(
410
424
  name="AdditionalProperty",
@@ -425,7 +439,7 @@ def _process_property_data(
425
439
  class_info: Class,
426
440
  config: Config,
427
441
  roots: set[ReferencePath | utils.ClassName],
428
- ) -> tuple[tuple[_PropertyData, bool | Property] | PropertyError, Schemas]:
442
+ ) -> tuple[tuple[_PropertyData, Property | None] | PropertyError, Schemas]:
429
443
  property_data = _process_properties(
430
444
  data=data, schemas=schemas, class_name=class_info.name, config=config, roots=roots
431
445
  )
@@ -442,7 +456,7 @@ def _process_property_data(
442
456
  )
443
457
  if isinstance(additional_properties, PropertyError):
444
458
  return additional_properties, schemas
445
- elif isinstance(additional_properties, bool):
459
+ elif additional_properties is None:
446
460
  pass
447
461
  else:
448
462
  property_data.relative_imports.update(additional_properties.get_imports(prefix=".."))
@@ -107,6 +107,8 @@ class PropertyProtocol(Protocol):
107
107
  """
108
108
  if json:
109
109
  type_string = self.get_base_json_type_string(quoted=quoted)
110
+ elif multipart:
111
+ type_string = "Tuple[None, bytes, str]"
110
112
  else:
111
113
  type_string = self.get_base_type_string(quoted=quoted)
112
114
 
@@ -83,8 +83,8 @@ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
83
83
  {% macro multipart_body(body, destination) %}
84
84
  {% set property = body.prop %}
85
85
  {% import "property_templates/" + property.template as prop_template %}
86
- {% if prop_template.transform_multipart %}
87
- {{ prop_template.transform_multipart(property, property.python_name, destination) }}
86
+ {% if prop_template.transform_multipart_body %}
87
+ {{ prop_template.transform_multipart_body(property, property.python_name, destination) }}
88
88
  {% endif %}
89
89
  {% endmacro %}
90
90
 
@@ -80,10 +80,10 @@ class {{ class_name }}:
80
80
  {% macro _to_dict(multipart=False) %}
81
81
  {% for property in model.required_properties + model.optional_properties %}
82
82
  {% import "property_templates/" + property.template as prop_template %}
83
- {% if prop_template.transform %}
84
- {{ prop_template.transform(property, "self." + property.python_name, property.python_name, multipart=multipart) }}
85
- {% elif multipart %}
86
- {{ property.python_name }} = self.{{ property.python_name }} if isinstance(self.{{ property.python_name }}, Unset) else (None, str(self.{{ property.python_name }}).encode(), "text/plain")
83
+ {% if multipart %}
84
+ {{ prop_template.transform_multipart(property, "self." + property.python_name, property.python_name) }}
85
+ {% elif prop_template.transform %}
86
+ {{ prop_template.transform(property=property, source="self." + property.python_name, destination=property.python_name) }}
87
87
  {% else %}
88
88
  {{ property.python_name }} = self.{{ property.python_name }}
89
89
  {% endif %}
@@ -92,19 +92,13 @@ class {{ class_name }}:
92
92
 
93
93
  field_dict: Dict[str, Any] = {}
94
94
  {% if model.additional_properties %}
95
- {% if model.additional_properties.template %}{# Can be a bool instead of an object #}
96
- {% import "property_templates/" + model.additional_properties.template as prop_template %}
97
- {% else %}
98
- {% set prop_template = None %}
99
- {% endif %}
100
- {% if prop_template and prop_template.transform %}
95
+ {% import "property_templates/" + model.additional_properties.template as prop_template %}
96
+ {% if multipart %}
101
97
  for prop_name, prop in self.additional_properties.items():
102
- {{ prop_template.transform(model.additional_properties, "prop", "field_dict[prop_name]", multipart=multipart, declare_type=false) | indent(4) }}
103
- {% elif multipart %}
104
- field_dict.update({
105
- key: (None, str(value).encode(), "text/plain")
106
- for key, value in self.additional_properties.items()
107
- })
98
+ {{ prop_template.transform_multipart(model.additional_properties, "prop", "field_dict[prop_name]") | indent(4) }}
99
+ {% elif prop_template.transform %}
100
+ for prop_name, prop in self.additional_properties.items():
101
+ {{ prop_template.transform(model.additional_properties, "prop", "field_dict[prop_name]", declare_type=false) | indent(4) }}
108
102
  {% else %}
109
103
  field_dict.update(self.additional_properties)
110
104
  {% endif %}
@@ -0,0 +1,7 @@
1
+ {% macro transform_multipart(property, source, destination) %}
2
+ {% if not property.required %}
3
+ {{ destination }} = {{source}} if isinstance({{source}}, Unset) else (None, str({{source}}).encode(), "text/plain")
4
+ {% else %}
5
+ {{ destination }} = (None, str({{ source }}).encode(), "text/plain")
6
+ {% endif %}
7
+ {% endmacro %}
@@ -0,0 +1,11 @@
1
+ {% macro transform_header(source) %}
2
+ "true" if {{ source }} else "false"
3
+ {% endmacro %}
4
+
5
+ {% macro transform_multipart(property, source, destination) %}
6
+ {% if not property.required %}
7
+ {{ destination }} = {{source}} if isinstance({{source}}, Unset) else (None, str({{source}}).encode(), "text/plain")
8
+ {% else %}
9
+ {{ destination }} = (None, str({{ source }}).encode(), "text/plain")
10
+ {% endif %}
11
+ {% endmacro %}
@@ -0,0 +1,5 @@
1
+ {% macro construct(property, source) %}
2
+ {{ property.python_name }} = cast({{ property.get_type_string() }} , {{ source }})
3
+ if {{ property.python_name }} != {{ property.value }}{% if not property.required %}and not isinstance({{ property.python_name }}, Unset){% endif %}:
4
+ raise ValueError(f"{{ property.name }} must match const {{ property.value }}, got '{{'{' + property.python_name + '}' }}'")
5
+ {%- endmacro %}
@@ -10,17 +10,13 @@ isoparse({{ source }}).date()
10
10
 
11
11
  {% macro check_type_for_construct(property, source) %}isinstance({{ source }}, str){% endmacro %}
12
12
 
13
- {% macro transform(property, source, destination, declare_type=True, multipart=False) %}
13
+ {% macro transform(property, source, destination, declare_type=True) %}
14
14
  {% set transformed = source + ".isoformat()" %}
15
- {% if multipart %}{# Multipart data must be bytes, not str #}
16
- {% set transformed = transformed + ".encode()" %}
17
- {% endif %}
18
15
  {% if property.required %}
19
16
  {{ destination }} = {{ transformed }}
20
17
  {%- else %}
21
18
  {% if declare_type %}
22
19
  {% set type_annotation = property.get_type_string(json=True) %}
23
- {% if multipart %}{% set type_annotation = type_annotation | replace("str", "bytes") %}{% endif %}
24
20
  {{ destination }}: {{ type_annotation }} = UNSET
25
21
  {% else %}
26
22
  {{ destination }} = UNSET
@@ -29,3 +25,15 @@ if not isinstance({{ source }}, Unset):
29
25
  {{ destination }} = {{ transformed }}
30
26
  {%- endif %}
31
27
  {% endmacro %}
28
+
29
+ {% macro transform_multipart(property, source, destination) %}
30
+ {% set transformed = source + ".isoformat().encode()" %}
31
+ {% if property.required %}
32
+ {{ destination }} = {{ transformed }}
33
+ {%- else %}
34
+ {% set type_annotation = property.get_type_string(json=True) | replace("str", "bytes") %}
35
+ {{ destination }}: {{ type_annotation }} = UNSET
36
+ if not isinstance({{ source }}, Unset):
37
+ {{ destination }} = {{ transformed }}
38
+ {%- endif %}
39
+ {% endmacro %}
@@ -10,17 +10,13 @@ isoparse({{ source }})
10
10
 
11
11
  {% macro check_type_for_construct(property, source) %}isinstance({{ source }}, str){% endmacro %}
12
12
 
13
- {% macro transform(property, source, destination, declare_type=True, multipart=False) %}
13
+ {% macro transform(property, source, destination, declare_type=True) %}
14
14
  {% set transformed = source + ".isoformat()" %}
15
- {% if multipart %}{# Multipart data must be bytes, not str #}
16
- {% set transformed = transformed + ".encode()" %}
17
- {% endif %}
18
15
  {% if property.required %}
19
16
  {{ destination }} = {{ transformed }}
20
17
  {%- else %}
21
18
  {% if declare_type %}
22
19
  {% set type_annotation = property.get_type_string(json=True) %}
23
- {% if multipart %}{% set type_annotation = type_annotation | replace("str", "bytes") %}{% endif %}
24
20
  {{ destination }}: {{ type_annotation }} = UNSET
25
21
  {% else %}
26
22
  {{ destination }} = UNSET
@@ -29,3 +25,15 @@ if not isinstance({{ source }}, Unset):
29
25
  {{ destination }} = {{ transformed }}
30
26
  {%- endif %}
31
27
  {% endmacro %}
28
+
29
+ {% macro transform_multipart(property, source, destination) %}
30
+ {% set transformed = source + ".isoformat().encode()" %}
31
+ {% if property.required %}
32
+ {{ destination }} = {{ transformed }}
33
+ {%- else %}
34
+ {% set type_annotation = property.get_type_string(json=True) | replace("str", "bytes") %}
35
+ {{ destination }}: {{ type_annotation }} = UNSET
36
+ if not isinstance({{ source }}, Unset):
37
+ {{ destination }} = {{ transformed }}
38
+ {%- endif %}
39
+ {% endmacro %}
@@ -13,10 +13,6 @@
13
13
  {% macro transform(property, source, destination, declare_type=True, multipart=False) %}
14
14
  {% set transformed = source + ".value" %}
15
15
  {% set type_string = property.get_type_string(json=True) %}
16
- {% if multipart %}
17
- {% set transformed = "(None, str(" + transformed + ").encode(), \"text/plain\")" %}
18
- {% set type_string = "Union[Unset, Tuple[None, bytes, str]]" %}
19
- {% endif %}
20
16
  {% if property.required %}
21
17
  {{ destination }} = {{ transformed }}
22
18
  {%- else %}
@@ -26,6 +22,18 @@ if not isinstance({{ source }}, Unset):
26
22
  {% endif %}
27
23
  {% endmacro %}
28
24
 
25
+ {% macro transform_multipart(property, source, destination) %}
26
+ {% set transformed = "(None, str(" + source + ".value" + ").encode(), \"text/plain\")" %}
27
+ {% set type_string = "Union[Unset, Tuple[None, bytes, str]]" %}
28
+ {% if property.required %}
29
+ {{ destination }} = {{ transformed }}
30
+ {%- else %}
31
+ {{ destination }}: {{ type_string }} = UNSET
32
+ if not isinstance({{ source }}, Unset):
33
+ {{ destination }} = {{ transformed }}
34
+ {% endif %}
35
+ {% endmacro %}
36
+
29
37
  {% macro transform_header(source) %}
30
38
  str({{ source }})
31
39
  {% endmacro %}
@@ -12,7 +12,7 @@ File(
12
12
 
13
13
  {% macro check_type_for_construct(property, source) %}isinstance({{ source }}, bytes){% endmacro %}
14
14
 
15
- {% macro transform(property, source, destination, declare_type=True, multipart=False) %}
15
+ {% macro transform(property, source, destination, declare_type=True) %}
16
16
  {% if property.required %}
17
17
  {{ destination }} = {{ source }}.to_tuple()
18
18
  {% else %}
@@ -21,3 +21,13 @@ if not isinstance({{ source }}, Unset):
21
21
  {{ destination }} = {{ source }}.to_tuple()
22
22
  {% endif %}
23
23
  {% endmacro %}
24
+
25
+ {% macro transform_multipart(property, source, destination) %}
26
+ {% if property.required %}
27
+ {{ destination }} = {{ source }}.to_tuple()
28
+ {% else %}
29
+ {{ destination }}: {{ property.get_type_string(json=True) }} = UNSET
30
+ if not isinstance({{ source }}, Unset):
31
+ {{ destination }} = {{ source }}.to_tuple()
32
+ {% endif %}
33
+ {% endmacro %}