openapi-python-client 0.25.3__tar.gz → 0.26.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 (113) hide show
  1. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/PKG-INFO +1 -1
  2. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/openapi.py +12 -19
  3. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/union.py +30 -4
  4. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/responses.py +97 -7
  5. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/endpoint_macros.py.jinja +15 -0
  6. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/endpoint_module.py.jinja +19 -15
  7. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/pyproject.toml +1 -1
  8. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/.gitignore +0 -0
  9. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/LICENSE +0 -0
  10. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/README.md +0 -0
  11. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/__init__.py +0 -0
  12. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/__main__.py +0 -0
  13. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/cli.py +0 -0
  14. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/config.py +0 -0
  15. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/__init__.py +0 -0
  16. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/bodies.py +0 -0
  17. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/errors.py +0 -0
  18. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/__init__.py +0 -0
  19. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/any.py +0 -0
  20. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/boolean.py +0 -0
  21. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/const.py +0 -0
  22. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/date.py +0 -0
  23. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/datetime.py +0 -0
  24. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/enum_property.py +0 -0
  25. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/file.py +0 -0
  26. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/float.py +0 -0
  27. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/int.py +0 -0
  28. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/list_property.py +0 -0
  29. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/literal_enum_property.py +0 -0
  30. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/merge_properties.py +0 -0
  31. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/model_property.py +0 -0
  32. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/none.py +0 -0
  33. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/property.py +0 -0
  34. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/protocol.py +0 -0
  35. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/schemas.py +0 -0
  36. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/string.py +0 -0
  37. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/parser/properties/uuid.py +0 -0
  38. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/py.typed +0 -0
  39. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/3.0.3.md +0 -0
  40. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/3.1.0.md +0 -0
  41. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/__init__.py +0 -0
  42. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/data_type.py +0 -0
  43. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/LICENSE +0 -0
  44. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/README.md +0 -0
  45. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/__init__.py +0 -0
  46. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/callback.py +0 -0
  47. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/components.py +0 -0
  48. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/contact.py +0 -0
  49. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +0 -0
  50. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +0 -0
  51. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/example.py +0 -0
  52. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +0 -0
  53. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/header.py +0 -0
  54. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/info.py +0 -0
  55. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/license.py +0 -0
  56. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/link.py +0 -0
  57. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/media_type.py +0 -0
  58. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +0 -0
  59. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py +0 -0
  60. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +0 -0
  61. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/operation.py +0 -0
  62. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +0 -0
  63. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/path_item.py +0 -0
  64. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/paths.py +0 -0
  65. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/reference.py +0 -0
  66. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +0 -0
  67. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/response.py +0 -0
  68. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/responses.py +0 -0
  69. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/schema.py +0 -0
  70. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py +0 -0
  71. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +0 -0
  72. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/server.py +0 -0
  73. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +0 -0
  74. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/tag.py +0 -0
  75. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/openapi_schema_pydantic/xml.py +0 -0
  76. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/schema/parameter_location.py +0 -0
  77. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/.gitignore.jinja +0 -0
  78. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/README.md.jinja +0 -0
  79. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/api_init.py.jinja +0 -0
  80. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/client.py.jinja +0 -0
  81. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/endpoint_init.py.jinja +0 -0
  82. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/errors.py.jinja +0 -0
  83. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/helpers.jinja +0 -0
  84. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/int_enum.py.jinja +0 -0
  85. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/literal_enum.py.jinja +0 -0
  86. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/model.py.jinja +0 -0
  87. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/models_init.py.jinja +0 -0
  88. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/package_init.py.jinja +0 -0
  89. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/any_property.py.jinja +0 -0
  90. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/boolean_property.py.jinja +0 -0
  91. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/const_property.py.jinja +0 -0
  92. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/date_property.py.jinja +0 -0
  93. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/datetime_property.py.jinja +0 -0
  94. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/enum_property.py.jinja +0 -0
  95. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/file_property.py.jinja +0 -0
  96. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/float_property.py.jinja +0 -0
  97. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/helpers.jinja +0 -0
  98. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/int_property.py.jinja +0 -0
  99. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/list_property.py.jinja +0 -0
  100. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/literal_enum_property.py.jinja +0 -0
  101. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/model_property.py.jinja +0 -0
  102. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/property_macros.py.jinja +0 -0
  103. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/union_property.py.jinja +0 -0
  104. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/property_templates/uuid_property.py.jinja +0 -0
  105. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/pyproject.toml.jinja +0 -0
  106. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/pyproject_pdm.toml.jinja +0 -0
  107. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/pyproject_poetry.toml.jinja +0 -0
  108. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/pyproject_ruff.toml.jinja +0 -0
  109. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/pyproject_uv.toml.jinja +0 -0
  110. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/setup.py.jinja +0 -0
  111. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/str_enum.py.jinja +0 -0
  112. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/templates/types.py.jinja +0 -0
  113. {openapi_python_client-0.25.3 → openapi_python_client-0.26.0}/openapi_python_client/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openapi-python-client
3
- Version: 0.25.3
3
+ Version: 0.26.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>
@@ -2,7 +2,6 @@ import re
2
2
  from collections.abc import Iterator
3
3
  from copy import deepcopy
4
4
  from dataclasses import dataclass, field
5
- from http import HTTPStatus
6
5
  from typing import Any, Optional, Protocol, Union
7
6
 
8
7
  from pydantic import ValidationError
@@ -26,7 +25,7 @@ from .properties import (
26
25
  property_from_data,
27
26
  )
28
27
  from .properties.schemas import parameter_from_reference
29
- from .responses import Response, response_from_data
28
+ from .responses import HTTPStatusPattern, Responses, response_from_data
30
29
 
31
30
  _PATH_PARAM_REGEX = re.compile("{([a-zA-Z_-][a-zA-Z0-9_-]*)}")
32
31
 
@@ -147,7 +146,7 @@ class Endpoint:
147
146
  path_parameters: list[Property] = field(default_factory=list)
148
147
  header_parameters: list[Property] = field(default_factory=list)
149
148
  cookie_parameters: list[Property] = field(default_factory=list)
150
- responses: list[Response] = field(default_factory=list)
149
+ responses: Responses = field(default_factory=lambda: Responses(patterns=[], default=None))
151
150
  bodies: list[Body] = field(default_factory=list)
152
151
  errors: list[ParseError] = field(default_factory=list)
153
152
 
@@ -162,19 +161,9 @@ class Endpoint:
162
161
  ) -> tuple["Endpoint", Schemas]:
163
162
  endpoint = deepcopy(endpoint)
164
163
  for code, response_data in data.items():
165
- status_code: HTTPStatus
166
- try:
167
- status_code = HTTPStatus(int(code))
168
- except ValueError:
169
- endpoint.errors.append(
170
- ParseError(
171
- detail=(
172
- f"Invalid response status code {code} (not a valid HTTP "
173
- f"status code), response will be omitted from generated "
174
- f"client"
175
- )
176
- )
177
- )
164
+ status_code = HTTPStatusPattern.parse(code)
165
+ if isinstance(status_code, ParseError):
166
+ endpoint.errors.append(status_code)
178
167
  continue
179
168
 
180
169
  response, schemas = response_from_data(
@@ -190,7 +179,7 @@ class Endpoint:
190
179
  endpoint.errors.append(
191
180
  ParseError(
192
181
  detail=(
193
- f"Cannot parse response for status code {status_code}{detail_suffix}, "
182
+ f"Cannot parse response for status code {code}{detail_suffix}, "
194
183
  f"response will be omitted from generated client"
195
184
  ),
196
185
  data=response.data,
@@ -201,7 +190,11 @@ class Endpoint:
201
190
  # No reasons to use lazy imports in endpoints, so add lazy imports to relative here.
202
191
  endpoint.relative_imports |= response.prop.get_lazy_imports(prefix=models_relative_prefix)
203
192
  endpoint.relative_imports |= response.prop.get_imports(prefix=models_relative_prefix)
204
- endpoint.responses.append(response)
193
+ if response.is_default():
194
+ endpoint.responses.default = response
195
+ else:
196
+ endpoint.responses.patterns.append(response)
197
+ endpoint.responses.patterns.sort()
205
198
  return endpoint, schemas
206
199
 
207
200
  @staticmethod
@@ -480,7 +473,7 @@ class Endpoint:
480
473
  if len(types) == 0:
481
474
  return "Any"
482
475
  if len(types) == 1:
483
- return self.responses[0].prop.get_type_string(quoted=False)
476
+ return types[0]
484
477
  return f"Union[{', '.join(types)}]"
485
478
 
486
479
  def iter_all_parameters(self) -> Iterator[tuple[oai.ParameterLocation, Property]]:
@@ -28,7 +28,14 @@ class UnionProperty(PropertyProtocol):
28
28
 
29
29
  @classmethod
30
30
  def build(
31
- cls, *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config
31
+ cls,
32
+ *,
33
+ data: oai.Schema,
34
+ name: str,
35
+ required: bool,
36
+ schemas: Schemas,
37
+ parent_name: str,
38
+ config: Config,
32
39
  ) -> tuple[UnionProperty | PropertyError, Schemas]:
33
40
  """
34
41
  Create a `UnionProperty` the right way.
@@ -55,8 +62,19 @@ class UnionProperty(PropertyProtocol):
55
62
  type_list_data.append(data.model_copy(update={"type": _type, "default": None}))
56
63
 
57
64
  for i, sub_prop_data in enumerate(chain(data.anyOf, data.oneOf, type_list_data)):
65
+ # If a schema has a unique title property, we can use that to carry forward a descriptive name instead of "type_0"
66
+ subscript: str
67
+ if (
68
+ isinstance(sub_prop_data, oai.Schema)
69
+ and sub_prop_data.title is not None
70
+ and sub_prop_data.title != data.title
71
+ ):
72
+ subscript = sub_prop_data.title
73
+ else:
74
+ subscript = f"type_{i}"
75
+
58
76
  sub_prop, schemas = property_from_data(
59
- name=f"{name}_type_{i}",
77
+ name=f"{name}_{subscript}",
60
78
  required=True,
61
79
  data=sub_prop_data,
62
80
  schemas=schemas,
@@ -64,7 +82,10 @@ class UnionProperty(PropertyProtocol):
64
82
  config=config,
65
83
  )
66
84
  if isinstance(sub_prop, PropertyError):
67
- return PropertyError(detail=f"Invalid property in union {name}", data=sub_prop_data), schemas
85
+ return (
86
+ PropertyError(detail=f"Invalid property in union {name}", data=sub_prop_data),
87
+ schemas,
88
+ )
68
89
  sub_properties.append(sub_prop)
69
90
 
70
91
  def flatten_union_properties(sub_properties: list[PropertyProtocol]) -> list[PropertyProtocol]:
@@ -108,7 +129,12 @@ class UnionProperty(PropertyProtocol):
108
129
 
109
130
  def _get_inner_type_strings(self, json: bool) -> set[str]:
110
131
  return {
111
- p.get_type_string(no_optional=True, json=json, quoted=not p.is_base_type) for p in self.inner_properties
132
+ p.get_type_string(
133
+ no_optional=True,
134
+ json=json,
135
+ quoted=not p.is_base_type,
136
+ )
137
+ for p in self.inner_properties
112
138
  }
113
139
 
114
140
  @staticmethod
@@ -1,6 +1,6 @@
1
- __all__ = ["Response", "response_from_data"]
1
+ __all__ = ["HTTPStatusPattern", "Response", "Responses", "response_from_data"]
2
2
 
3
- from http import HTTPStatus
3
+ from collections.abc import Iterator
4
4
  from typing import Optional, TypedDict, Union
5
5
 
6
6
  from attrs import define
@@ -15,6 +15,20 @@ from .errors import ParseError, PropertyError
15
15
  from .properties import AnyProperty, Property, Schemas, property_from_data
16
16
 
17
17
 
18
+ @define
19
+ class Responses:
20
+ patterns: list["Response"]
21
+ default: Optional["Response"]
22
+
23
+ def __iter__(self) -> Iterator["Response"]:
24
+ yield from self.patterns
25
+ if self.default:
26
+ yield self.default
27
+
28
+ def __len__(self) -> int:
29
+ return len(self.patterns) + (1 if self.default else 0)
30
+
31
+
18
32
  class _ResponseSource(TypedDict):
19
33
  """What data should be pulled from the httpx Response object"""
20
34
 
@@ -28,15 +42,91 @@ TEXT_SOURCE = _ResponseSource(attribute="response.text", return_type="str")
28
42
  NONE_SOURCE = _ResponseSource(attribute="None", return_type="None")
29
43
 
30
44
 
31
- @define
45
+ class HTTPStatusPattern:
46
+ """Status code patterns come in three flavors, in order of precedence:
47
+ 1. Specific status codes, such as 200. This is represented by `min` and `max` being the same.
48
+ 2. Ranges of status codes, such as 2XX. This is represented by `min` and `max` being different.
49
+ 3. The special `default` status code, which is used when no other status codes match. `range` is `None` in this case.
50
+
51
+ https://github.com/openapi-generators/openapi-python-client/blob/61b6c54994e2a6285bb422ee3b864c45b5d88c15/openapi_python_client/schema/3.1.0.md#responses-object
52
+ """
53
+
54
+ pattern: str
55
+ range: Optional[tuple[int, int]]
56
+
57
+ def __init__(self, *, pattern: str, code_range: Optional[tuple[int, int]]):
58
+ """Initialize with a range of status codes or None for the default case."""
59
+ self.pattern = pattern
60
+ self.range = code_range
61
+
62
+ @staticmethod
63
+ def parse(pattern: str) -> Union["HTTPStatusPattern", ParseError]:
64
+ """Parse a status code pattern such as 2XX or 404"""
65
+ if pattern == "default":
66
+ return HTTPStatusPattern(pattern=pattern, code_range=None)
67
+
68
+ if pattern.endswith("XX") and pattern[0].isdigit():
69
+ first_digit = int(pattern[0])
70
+ return HTTPStatusPattern(pattern=pattern, code_range=(first_digit * 100, first_digit * 100 + 99))
71
+
72
+ try:
73
+ code = int(pattern)
74
+ return HTTPStatusPattern(pattern=pattern, code_range=(code, code))
75
+ except ValueError:
76
+ return ParseError(
77
+ detail=(
78
+ f"Invalid response status code pattern: {pattern}, response will be omitted from generated client"
79
+ )
80
+ )
81
+
82
+ def is_range(self) -> bool:
83
+ """Check if this is a range of status codes, such as 2XX"""
84
+ return self.range is not None and self.range[0] != self.range[1]
85
+
86
+ def __lt__(self, other: "HTTPStatusPattern") -> bool:
87
+ """Compare two HTTPStatusPattern objects based on the order they should be applied in"""
88
+ if self.range is None:
89
+ return False # Default gets applied last
90
+ if other.range is None:
91
+ return True # Other is default, so this one gets applied first
92
+
93
+ # Specific codes appear before ranges
94
+ if self.is_range() and not other.is_range():
95
+ return False
96
+ if not self.is_range() and other.is_range():
97
+ return True
98
+
99
+ # Order specific codes numerically
100
+ return self.range[0] < other.range[0]
101
+
102
+ def __eq__(self, other: object) -> bool: # pragma: no cover
103
+ if not isinstance(other, HTTPStatusPattern):
104
+ return False
105
+ return self.range == other.range
106
+
107
+ def __hash__(self) -> int: # pragma: no cover
108
+ return hash(self.range)
109
+
110
+ def __repr__(self) -> str: # pragma: no cover
111
+ return f"<HTTPStatusPattern {self.pattern}>"
112
+
113
+
114
+ @define(order=False)
32
115
  class Response:
33
116
  """Describes a single response for an endpoint"""
34
117
 
35
- status_code: HTTPStatus
118
+ status_code: HTTPStatusPattern
36
119
  prop: Property
37
120
  source: _ResponseSource
38
121
  data: Union[oai.Response, oai.Reference] # Original data which created this response, useful for custom templates
39
122
 
123
+ def is_default(self) -> bool:
124
+ return self.status_code.range is None
125
+
126
+ def __lt__(self, other: "Response") -> bool:
127
+ """Compare two responses based on the order in which they should be applied in"""
128
+ return self.status_code < other.status_code
129
+
40
130
 
41
131
  def _source_by_content_type(content_type: str, config: Config) -> Optional[_ResponseSource]:
42
132
  parsed_content_type = utils.get_content_type(content_type, config)
@@ -59,7 +149,7 @@ def _source_by_content_type(content_type: str, config: Config) -> Optional[_Resp
59
149
 
60
150
  def empty_response(
61
151
  *,
62
- status_code: HTTPStatus,
152
+ status_code: HTTPStatusPattern,
63
153
  response_name: str,
64
154
  config: Config,
65
155
  data: Union[oai.Response, oai.Reference],
@@ -82,7 +172,7 @@ def empty_response(
82
172
 
83
173
  def response_from_data( # noqa: PLR0911
84
174
  *,
85
- status_code: HTTPStatus,
175
+ status_code: HTTPStatusPattern,
86
176
  data: Union[oai.Response, oai.Reference],
87
177
  schemas: Schemas,
88
178
  responses: dict[str, Union[oai.Response, oai.Reference]],
@@ -91,7 +181,7 @@ def response_from_data( # noqa: PLR0911
91
181
  ) -> tuple[Union[Response, ParseError], Schemas]:
92
182
  """Generate a Response from the OpenAPI dictionary representation of it"""
93
183
 
94
- response_name = f"response_{status_code}"
184
+ response_name = f"response_{status_code.pattern}"
95
185
  if isinstance(data, oai.Reference):
96
186
  ref_path = parse_reference_path(data.ref)
97
187
  if isinstance(ref_path, ParseError):
@@ -184,3 +184,18 @@ Returns:
184
184
  {% macro docstring(endpoint, return_string, is_detailed) %}
185
185
  {{ safe_docstring(docstring_content(endpoint, return_string, is_detailed)) }}
186
186
  {% endmacro %}
187
+
188
+ {% macro parse_response(parsed_responses, response) %}
189
+ {% if parsed_responses %}{% import "property_templates/" + response.prop.template as prop_template %}
190
+ {% if prop_template.construct %}
191
+ {{ prop_template.construct(response.prop, response.source.attribute) }}
192
+ {% elif response.source.return_type == response.prop.get_type_string() %}
193
+ {{ response.prop.python_name }} = {{ response.source.attribute }}
194
+ {% else %}
195
+ {{ response.prop.python_name }} = cast({{ response.prop.get_type_string() }}, {{ response.source.attribute }})
196
+ {% endif %}
197
+ return {{ response.prop.python_name }}
198
+ {% else %}
199
+ return None
200
+ {% endif %}
201
+ {% endmacro %}
@@ -64,27 +64,31 @@ def _get_kwargs(
64
64
  {% endif %}
65
65
  return _kwargs
66
66
 
67
+ {% if endpoint.responses.default %}
68
+ {% set return_type = return_string %}
69
+ {% else %}
70
+ {% set return_type = "Optional[" + return_string + "]" %}
71
+ {% endif %}
67
72
 
68
- def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[{{ return_string }}]:
69
- {% for response in endpoint.responses %}
70
- if response.status_code == {{ response.status_code.value }}:
71
- {% if parsed_responses %}{% import "property_templates/" + response.prop.template as prop_template %}
72
- {% if prop_template.construct %}
73
- {{ prop_template.construct(response.prop, response.source.attribute) | indent(8) }}
74
- {% elif response.source.return_type == response.prop.get_type_string() %}
75
- {{ response.prop.python_name }} = {{ response.source.attribute }}
76
- {% else %}
77
- {{ response.prop.python_name }} = cast({{ response.prop.get_type_string() }}, {{ response.source.attribute }})
78
- {% endif %}
79
- return {{ response.prop.python_name }}
80
- {% else %}
81
- return None
82
- {% endif %}
73
+
74
+ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> {{return_type}}:
75
+ {% for response in endpoint.responses.patterns %}
76
+ {% set code_range = response.status_code.range %}
77
+ {% if code_range[0] == code_range[1] %}
78
+ if response.status_code == {{ code_range[0] }}:
79
+ {% else %}
80
+ if {{ code_range[0] }} <= response.status_code <= {{ code_range[1] }}:
81
+ {% endif %}
82
+ {{ parse_response(parsed_responses, response) | indent(8) }}
83
83
  {% endfor %}
84
+ {% if endpoint.responses.default %}
85
+ {{ parse_response(parsed_responses, endpoint.responses.default) | indent(4) }}
86
+ {% else %}
84
87
  if client.raise_on_unexpected_status:
85
88
  raise errors.UnexpectedStatus(response.status_code, response.content)
86
89
  else:
87
90
  return None
91
+ {% endif %}
88
92
 
89
93
 
90
94
  def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[{{ return_string }}]:
@@ -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.25.3"
21
+ version = "0.26.0"
22
22
  description = "Generate modern Python clients from OpenAPI"
23
23
  keywords = [
24
24
  "OpenAPI",