openapi-python-client 0.20.0__tar.gz → 0.21.1__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 (104) hide show
  1. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/PKG-INFO +2 -2
  2. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/__init__.py +27 -49
  3. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/cli.py +42 -51
  4. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/config.py +10 -1
  5. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/bodies.py +26 -6
  6. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/openapi.py +8 -2
  7. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/list_property.py.jinja +1 -1
  8. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/pyproject.toml +4 -6
  9. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/.gitignore +0 -0
  10. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/LICENSE +0 -0
  11. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/README.md +0 -0
  12. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/__main__.py +0 -0
  13. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/__init__.py +0 -0
  14. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/errors.py +0 -0
  15. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/__init__.py +0 -0
  16. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/any.py +0 -0
  17. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/boolean.py +0 -0
  18. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/const.py +0 -0
  19. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/date.py +0 -0
  20. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/datetime.py +0 -0
  21. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/enum_property.py +0 -0
  22. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/file.py +0 -0
  23. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/float.py +0 -0
  24. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/int.py +0 -0
  25. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/list_property.py +0 -0
  26. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/model_property.py +0 -0
  27. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/none.py +0 -0
  28. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/property.py +0 -0
  29. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/protocol.py +0 -0
  30. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/schemas.py +0 -0
  31. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/string.py +0 -0
  32. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/properties/union.py +0 -0
  33. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/parser/responses.py +0 -0
  34. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/py.typed +0 -0
  35. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/3.0.3.md +0 -0
  36. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/3.1.0.md +0 -0
  37. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/__init__.py +0 -0
  38. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/data_type.py +0 -0
  39. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/LICENSE +0 -0
  40. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/README.md +0 -0
  41. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/__init__.py +0 -0
  42. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/callback.py +0 -0
  43. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/components.py +0 -0
  44. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/contact.py +0 -0
  45. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +0 -0
  46. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +0 -0
  47. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/example.py +0 -0
  48. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +0 -0
  49. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/header.py +0 -0
  50. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/info.py +0 -0
  51. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/license.py +0 -0
  52. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/link.py +0 -0
  53. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/media_type.py +0 -0
  54. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +0 -0
  55. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py +0 -0
  56. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +0 -0
  57. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/operation.py +0 -0
  58. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +0 -0
  59. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/path_item.py +0 -0
  60. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/paths.py +0 -0
  61. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/reference.py +0 -0
  62. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +0 -0
  63. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/response.py +0 -0
  64. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/responses.py +0 -0
  65. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/schema.py +0 -0
  66. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py +0 -0
  67. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +0 -0
  68. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/server.py +0 -0
  69. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +0 -0
  70. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/tag.py +0 -0
  71. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/openapi_schema_pydantic/xml.py +0 -0
  72. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/schema/parameter_location.py +0 -0
  73. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/.gitignore.jinja +0 -0
  74. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/README.md.jinja +0 -0
  75. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/api_init.py.jinja +0 -0
  76. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/client.py.jinja +0 -0
  77. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/endpoint_init.py.jinja +0 -0
  78. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/endpoint_macros.py.jinja +0 -0
  79. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/endpoint_module.py.jinja +0 -0
  80. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/errors.py.jinja +0 -0
  81. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/helpers.jinja +0 -0
  82. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/int_enum.py.jinja +0 -0
  83. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/model.py.jinja +0 -0
  84. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/models_init.py.jinja +0 -0
  85. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/package_init.py.jinja +0 -0
  86. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/any_property.py.jinja +0 -0
  87. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/boolean_property.py.jinja +0 -0
  88. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/const_property.py.jinja +0 -0
  89. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/date_property.py.jinja +0 -0
  90. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/datetime_property.py.jinja +0 -0
  91. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/enum_property.py.jinja +0 -0
  92. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/file_property.py.jinja +0 -0
  93. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/float_property.py.jinja +0 -0
  94. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/helpers.jinja +0 -0
  95. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/int_property.py.jinja +0 -0
  96. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/model_property.py.jinja +0 -0
  97. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/property_macros.py.jinja +0 -0
  98. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/property_templates/union_property.py.jinja +0 -0
  99. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/pyproject.toml.jinja +0 -0
  100. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/pyproject_ruff.toml.jinja +0 -0
  101. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/setup.py.jinja +0 -0
  102. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/str_enum.py.jinja +0 -0
  103. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/openapi_python_client/templates/types.py.jinja +0 -0
  104. {openapi_python_client-0.20.0 → openapi_python_client-0.21.1}/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.20.0
3
+ Version: 0.21.1
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>
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Topic :: Software Development :: Code Generators
20
20
  Classifier: Typing :: Typed
21
- Requires-Python: <4.0,>=3.8
21
+ Requires-Python: <4.0,>=3.8.1
22
22
  Requires-Dist: attrs>=21.3.0
23
23
  Requires-Dist: colorama>=0.4.3; sys_platform == 'win32'
24
24
  Requires-Dist: httpx<0.28.0,>=0.20.0
@@ -65,12 +65,22 @@ class Project:
65
65
  )
66
66
 
67
67
  self.project_name: str = config.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client"
68
- self.project_dir: Path = Path.cwd()
69
- if config.meta_type != MetaType.NONE:
70
- self.project_dir /= self.project_name
71
-
72
68
  self.package_name: str = config.package_name_override or self.project_name.replace("-", "_")
73
- 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
+
74
84
  self.package_description: str = utils.remove_string_escapes(
75
85
  f"A client library for accessing {self.openapi.title}"
76
86
  )
@@ -95,29 +105,16 @@ class Project:
95
105
  def build(self) -> Sequence[GeneratorError]:
96
106
  """Create the project from templates"""
97
107
 
98
- if self.config.meta_type == MetaType.NONE:
99
- print(f"Generating {self.package_name}")
100
- else:
101
- print(f"Generating {self.project_name}")
102
- try:
103
- self.project_dir.mkdir()
104
- except FileExistsError:
105
- return [GeneratorError(detail="Directory already exists. Delete it or use the update command.")]
106
- self._create_package()
107
- self._build_metadata()
108
- self._build_models()
109
- self._build_api()
110
- self._run_post_hooks()
111
- return self._get_errors()
112
-
113
- def update(self) -> Sequence[GeneratorError]:
114
- """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)
115
111
 
116
- if not self.package_dir.is_dir():
117
- return [GeneratorError(detail=f"Directory {self.package_dir} not found")]
118
- print(f"Updating {self.package_name}")
119
- 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.")]
120
116
  self._create_package()
117
+ self._build_metadata()
121
118
  self._build_models()
122
119
  self._build_api()
123
120
  self._run_post_hooks()
@@ -138,7 +135,7 @@ class Project:
138
135
  )
139
136
  return
140
137
  try:
141
- cwd = self.package_dir if self.config.meta_type == MetaType.NONE else self.project_dir
138
+ cwd = self.project_dir
142
139
  subprocess.run(cmd, cwd=cwd, shell=True, capture_output=True, check=True)
143
140
  except CalledProcessError as err:
144
141
  self.errors.append(
@@ -158,7 +155,8 @@ class Project:
158
155
  return errors
159
156
 
160
157
  def _create_package(self) -> None:
161
- self.package_dir.mkdir()
158
+ if self.package_dir != self.project_dir:
159
+ self.package_dir.mkdir()
162
160
  # Package __init__.py
163
161
  package_init = self.package_dir / "__init__.py"
164
162
 
@@ -303,7 +301,7 @@ def _get_project_for_url_or_path(
303
301
  )
304
302
 
305
303
 
306
- def create_new_client(
304
+ def generate(
307
305
  *,
308
306
  config: Config,
309
307
  custom_template_path: Optional[Path] = None,
@@ -323,26 +321,6 @@ def create_new_client(
323
321
  return project.build()
324
322
 
325
323
 
326
- def update_existing_client(
327
- *,
328
- config: Config,
329
- custom_template_path: Optional[Path] = None,
330
- ) -> Sequence[GeneratorError]:
331
- """
332
- Update an existing client library
333
-
334
- Returns:
335
- A list containing any errors encountered when generating.
336
- """
337
- project = _get_project_for_url_or_path(
338
- custom_template_path=custom_template_path,
339
- config=config,
340
- )
341
- if isinstance(project, GeneratorError):
342
- return [project]
343
- return project.update()
344
-
345
-
346
324
  def _load_yaml_or_json(data: bytes, content_type: Optional[str]) -> Union[Dict[str, Any], GeneratorError]:
347
325
  if content_type == "application/json":
348
326
  try:
@@ -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
  )
@@ -73,10 +73,17 @@ class Config:
73
73
  document_source: Union[Path, str]
74
74
  file_encoding: str
75
75
  content_type_overrides: Dict[str, str]
76
+ overwrite: bool
77
+ output_path: Optional[Path]
76
78
 
77
79
  @staticmethod
78
80
  def from_sources(
79
- 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],
80
87
  ) -> "Config":
81
88
  if config_file.post_hooks is not None:
82
89
  post_hooks = config_file.post_hooks
@@ -104,5 +111,7 @@ class Config:
104
111
  http_timeout=config_file.http_timeout,
105
112
  document_source=document_source,
106
113
  file_encoding=file_encoding,
114
+ overwrite=overwrite,
115
+ output_path=output_path,
107
116
  )
108
117
  return config
@@ -1,5 +1,5 @@
1
1
  import sys
2
- from typing import List, Tuple, Union
2
+ from typing import Dict, List, Tuple, Union
3
3
 
4
4
  import attr
5
5
 
@@ -44,15 +44,19 @@ def body_from_data(
44
44
  *,
45
45
  data: oai.Operation,
46
46
  schemas: Schemas,
47
+ request_bodies: Dict[str, Union[oai.RequestBody, oai.Reference]],
47
48
  config: Config,
48
49
  endpoint_name: str,
49
50
  ) -> Tuple[List[Union[Body, ParseError]], Schemas]:
50
51
  """Adds form or JSON body to Endpoint if included in data"""
51
- if data.request_body is None or isinstance(data.request_body, oai.Reference):
52
+ body = _resolve_reference(data.request_body, request_bodies)
53
+ if isinstance(body, ParseError):
54
+ return [body], schemas
55
+ if body is None:
52
56
  return [], schemas
53
57
 
54
58
  bodies: List[Union[Body, ParseError]] = []
55
- body_content = data.request_body.content
59
+ body_content = body.content
56
60
  prefix_type_names = len(body_content) > 1
57
61
 
58
62
  for content_type, media_type in body_content.items():
@@ -61,7 +65,7 @@ def body_from_data(
61
65
  bodies.append(
62
66
  ParseError(
63
67
  detail="Invalid content type",
64
- data=data.request_body,
68
+ data=body,
65
69
  level=ErrorLevel.WARNING,
66
70
  )
67
71
  )
@@ -71,7 +75,7 @@ def body_from_data(
71
75
  bodies.append(
72
76
  ParseError(
73
77
  detail="Missing schema",
74
- data=data.request_body,
78
+ data=body,
75
79
  level=ErrorLevel.WARNING,
76
80
  )
77
81
  )
@@ -88,7 +92,7 @@ def body_from_data(
88
92
  bodies.append(
89
93
  ParseError(
90
94
  detail=f"Unsupported content type {simplified_content_type}",
91
- data=data.request_body,
95
+ data=body,
92
96
  level=ErrorLevel.WARNING,
93
97
  )
94
98
  )
@@ -123,3 +127,19 @@ def body_from_data(
123
127
  )
124
128
 
125
129
  return bodies, schemas
130
+
131
+
132
+ def _resolve_reference(
133
+ body: Union[oai.RequestBody, oai.Reference, None], request_bodies: Dict[str, Union[oai.RequestBody, oai.Reference]]
134
+ ) -> Union[oai.RequestBody, ParseError, None]:
135
+ if body is None:
136
+ return None
137
+ references_seen = []
138
+ while isinstance(body, oai.Reference) and body.ref not in references_seen:
139
+ references_seen.append(body.ref)
140
+ body = request_bodies.get(body.ref.split("/")[-1])
141
+ if isinstance(body, oai.Reference):
142
+ return ParseError(detail="Circular $ref in request body", data=body)
143
+ if body is None and references_seen:
144
+ return ParseError(detail=f"Could not resolve $ref {references_seen[-1]} in request body")
145
+ return body
@@ -48,6 +48,7 @@ class EndpointCollection:
48
48
  data: Dict[str, oai.PathItem],
49
49
  schemas: Schemas,
50
50
  parameters: Parameters,
51
+ request_bodies: Dict[str, Union[oai.RequestBody, oai.Reference]],
51
52
  config: Config,
52
53
  ) -> Tuple[Dict[utils.PythonIdentifier, "EndpointCollection"], Schemas, Parameters]:
53
54
  """Parse the openapi paths data to get EndpointCollections by tag"""
@@ -69,6 +70,7 @@ class EndpointCollection:
69
70
  tag=tag,
70
71
  schemas=schemas,
71
72
  parameters=parameters,
73
+ request_bodies=request_bodies,
72
74
  config=config,
73
75
  )
74
76
  # Add `PathItem` parameters
@@ -392,6 +394,7 @@ class Endpoint:
392
394
  tag: str,
393
395
  schemas: Schemas,
394
396
  parameters: Parameters,
397
+ request_bodies: Dict[str, Union[oai.RequestBody, oai.Reference]],
395
398
  config: Config,
396
399
  ) -> Tuple[Union["Endpoint", ParseError], Schemas, Parameters]:
397
400
  """Construct an endpoint from the OpenAPI data"""
@@ -423,7 +426,9 @@ class Endpoint:
423
426
  result, schemas = Endpoint._add_responses(endpoint=result, data=data.responses, schemas=schemas, config=config)
424
427
  if isinstance(result, ParseError):
425
428
  return result, schemas, parameters
426
- bodies, schemas = body_from_data(data=data, schemas=schemas, config=config, endpoint_name=result.name)
429
+ bodies, schemas = body_from_data(
430
+ data=data, schemas=schemas, config=config, endpoint_name=result.name, request_bodies=request_bodies
431
+ )
427
432
  body_errors = []
428
433
  for body in bodies:
429
434
  if isinstance(body, ParseError):
@@ -507,8 +512,9 @@ class GeneratorData:
507
512
  parameters=parameters,
508
513
  config=config,
509
514
  )
515
+ request_bodies = (openapi.components and openapi.components.requestBodies) or {}
510
516
  endpoint_collections_by_tag, schemas, parameters = EndpointCollection.from_data(
511
- data=openapi.paths, schemas=schemas, parameters=parameters, config=config
517
+ data=openapi.paths, schemas=schemas, parameters=parameters, request_bodies=request_bodies, config=config
512
518
  )
513
519
 
514
520
  enums = (prop for prop in schemas.classes_by_name.values() if isinstance(prop, EnumProperty))
@@ -60,7 +60,7 @@ if not isinstance({{ source }}, Unset):
60
60
  {% else %}
61
61
  {{ destination }}: {{ type_string }} = UNSET
62
62
  if not isinstance({{ source }}, Unset):
63
- {{ _transform(property, source, destination, True, "to_dict") | indent(4)}}
63
+ {{ _transform(property, source, destination, True, "to_dict") | indent(4)}}
64
64
  {% endif %}
65
65
  {% endmacro %}
66
66
 
@@ -3,7 +3,7 @@ authors = [
3
3
  { name = "Dylan Anthony", email = "contact@dylananthony.com" },
4
4
  ]
5
5
  license = { text = "MIT" }
6
- requires-python = ">=3.8,<4.0"
6
+ requires-python = ">=3.8.1,<4.0"
7
7
  dependencies = [
8
8
  "jinja2>=3.0.0,<4.0.0",
9
9
  "typer>0.6,<0.13",
@@ -18,7 +18,7 @@ dependencies = [
18
18
  "typing-extensions>=4.8.0,<5.0.0",
19
19
  ]
20
20
  name = "openapi-python-client"
21
- version = "0.20.0"
21
+ version = "0.21.1"
22
22
  description = "Generate modern Python clients from OpenAPI"
23
23
  keywords = [
24
24
  "OpenAPI",
@@ -84,20 +84,19 @@ ignore_missing_imports = true
84
84
  [tool.pytest.ini_options]
85
85
  junit_family = "xunit2"
86
86
 
87
-
88
87
  [tool.pdm.dev-dependencies]
89
88
  dev = [
90
89
  "pytest",
91
90
  "pytest-mock",
92
91
  "mypy",
93
92
  "taskipy",
94
- "safety",
95
93
  "pytest-cov",
96
94
  "python-multipart",
97
95
  "types-PyYAML<7.0.0,>=6.0.3",
98
96
  "types-certifi<2021.10.9,>=2020.0.0",
99
97
  "types-python-dateutil<3.0.0,>=2.0.0",
100
98
  "ruamel-yaml-string>=0.1.1",
99
+ "syrupy>=4",
101
100
  ]
102
101
 
103
102
  [tool.pdm.build]
@@ -110,9 +109,8 @@ includes = [
110
109
  [tool.pdm.scripts]
111
110
  lint = "ruff check --fix ."
112
111
  format = "ruff format ."
113
- safety_check = { shell = "pdm export -o requirements.txt && safety check -r requirements.txt --bare && rm requirements.txt" }
114
112
  mypy = "mypy openapi_python_client"
115
- check = { composite = ["lint", "format", "safety_check", "mypy", "test"] }
113
+ check = { composite = ["lint", "format", "mypy", "test"] }
116
114
  regen = {composite = ["regen_e2e", "regen_integration"]}
117
115
  e2e = "pytest openapi_python_client end_to_end_tests/test_end_to_end.py"
118
116
  re = {composite = ["regen_e2e", "e2e"]}