openapi-python-client 0.29.0__py3-none-any.whl → 0.29.1__py3-none-any.whl

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 (90) hide show
  1. openapi_python_client/__init__.py +26 -17
  2. openapi_python_client/cli.py +1 -2
  3. openapi_python_client/config.py +3 -1
  4. openapi_python_client/parser/bodies.py +8 -7
  5. openapi_python_client/parser/openapi.py +58 -48
  6. openapi_python_client/parser/properties/__init__.py +34 -33
  7. openapi_python_client/parser/properties/any.py +11 -10
  8. openapi_python_client/parser/properties/boolean.py +13 -13
  9. openapi_python_client/parser/properties/const.py +11 -10
  10. openapi_python_client/parser/properties/date.py +12 -11
  11. openapi_python_client/parser/properties/datetime.py +12 -11
  12. openapi_python_client/parser/properties/enum_property.py +23 -20
  13. openapi_python_client/parser/properties/file.py +11 -10
  14. openapi_python_client/parser/properties/float.py +13 -13
  15. openapi_python_client/parser/properties/int.py +11 -11
  16. openapi_python_client/parser/properties/list_property.py +22 -22
  17. openapi_python_client/parser/properties/literal_enum_property.py +20 -20
  18. openapi_python_client/parser/properties/merge_properties.py +9 -6
  19. openapi_python_client/parser/properties/model_property.py +34 -33
  20. openapi_python_client/parser/properties/none.py +11 -11
  21. openapi_python_client/parser/properties/protocol.py +42 -32
  22. openapi_python_client/parser/properties/schemas.py +30 -23
  23. openapi_python_client/parser/properties/string.py +10 -11
  24. openapi_python_client/parser/properties/union.py +24 -22
  25. openapi_python_client/parser/properties/uuid.py +11 -11
  26. openapi_python_client/parser/responses.py +30 -23
  27. openapi_python_client/schema/__init__.py +2 -1
  28. openapi_python_client/schema/openapi_schema_pydantic/callback.py +3 -1
  29. openapi_python_client/schema/openapi_schema_pydantic/components.py +10 -9
  30. openapi_python_client/schema/openapi_schema_pydantic/contact.py +5 -3
  31. openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +4 -2
  32. openapi_python_client/schema/openapi_schema_pydantic/encoding.py +4 -3
  33. openapi_python_client/schema/openapi_schema_pydantic/example.py +5 -3
  34. openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +4 -2
  35. openapi_python_client/schema/openapi_schema_pydantic/header.py +20 -6
  36. openapi_python_client/schema/openapi_schema_pydantic/info.py +5 -4
  37. openapi_python_client/schema/openapi_schema_pydantic/license.py +4 -2
  38. openapi_python_client/schema/openapi_schema_pydantic/link.py +5 -4
  39. openapi_python_client/schema/openapi_schema_pydantic/media_type.py +3 -2
  40. openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +6 -4
  41. openapi_python_client/schema/openapi_schema_pydantic/open_api.py +5 -3
  42. openapi_python_client/schema/openapi_schema_pydantic/operation.py +6 -5
  43. openapi_python_client/schema/openapi_schema_pydantic/parameter.py +6 -5
  44. openapi_python_client/schema/openapi_schema_pydantic/path_item.py +5 -3
  45. openapi_python_client/schema/openapi_schema_pydantic/paths.py +2 -1
  46. openapi_python_client/schema/openapi_schema_pydantic/reference.py +3 -1
  47. openapi_python_client/schema/openapi_schema_pydantic/request_body.py +3 -2
  48. openapi_python_client/schema/openapi_schema_pydantic/response.py +5 -4
  49. openapi_python_client/schema/openapi_schema_pydantic/responses.py +2 -1
  50. openapi_python_client/schema/openapi_schema_pydantic/schema.py +7 -6
  51. openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py +3 -1
  52. openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +8 -7
  53. openapi_python_client/schema/openapi_schema_pydantic/server.py +4 -3
  54. openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +5 -3
  55. openapi_python_client/schema/openapi_schema_pydantic/tag.py +3 -2
  56. openapi_python_client/schema/openapi_schema_pydantic/xml.py +5 -3
  57. openapi_python_client/schema/ref.py +52 -0
  58. openapi_python_client/schema/untrusted_string.py +56 -0
  59. openapi_python_client/strings.py +245 -0
  60. openapi_python_client/templates/README.md.jinja +3 -3
  61. openapi_python_client/templates/client.py.jinja +5 -5
  62. openapi_python_client/templates/endpoint_macros.py.jinja +18 -18
  63. openapi_python_client/templates/endpoint_module.py.jinja +13 -13
  64. openapi_python_client/templates/helpers.jinja +3 -7
  65. openapi_python_client/templates/literal_enum.py.jinja +1 -1
  66. openapi_python_client/templates/model.py.jinja +18 -18
  67. openapi_python_client/templates/package_init.py.jinja +2 -2
  68. openapi_python_client/templates/property_templates/const_property.py.jinja +3 -3
  69. openapi_python_client/templates/property_templates/date_property.py.jinja +1 -1
  70. openapi_python_client/templates/property_templates/datetime_property.py.jinja +1 -1
  71. openapi_python_client/templates/property_templates/enum_property.py.jinja +1 -1
  72. openapi_python_client/templates/property_templates/file_property.py.jinja +1 -1
  73. openapi_python_client/templates/property_templates/list_property.py.jinja +3 -3
  74. openapi_python_client/templates/property_templates/literal_enum_property.py.jinja +2 -2
  75. openapi_python_client/templates/property_templates/model_property.py.jinja +1 -1
  76. openapi_python_client/templates/property_templates/property_macros.py.jinja +1 -1
  77. openapi_python_client/templates/property_templates/union_property.py.jinja +9 -9
  78. openapi_python_client/templates/property_templates/uuid_property.py.jinja +1 -1
  79. openapi_python_client/templates/pyproject_pdm.toml.jinja +2 -2
  80. openapi_python_client/templates/pyproject_poetry.toml.jinja +2 -2
  81. openapi_python_client/templates/pyproject_uv.toml.jinja +3 -3
  82. openapi_python_client/templates/setup.py.jinja +2 -2
  83. openapi_python_client/templates/str_enum.py.jinja +2 -2
  84. {openapi_python_client-0.29.0.dist-info → openapi_python_client-0.29.1.dist-info}/METADATA +4 -5
  85. openapi_python_client-0.29.1.dist-info/RECORD +115 -0
  86. {openapi_python_client-0.29.0.dist-info → openapi_python_client-0.29.1.dist-info}/WHEEL +1 -1
  87. openapi_python_client/utils.py +0 -122
  88. openapi_python_client-0.29.0.dist-info/RECORD +0 -113
  89. {openapi_python_client-0.29.0.dist-info → openapi_python_client-0.29.1.dist-info}/entry_points.txt +0 -0
  90. {openapi_python_client-0.29.0.dist-info → openapi_python_client-0.29.1.dist-info}/licenses/LICENSE +0 -0
@@ -2,24 +2,24 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any, ClassVar
4
4
 
5
- from attr import define
5
+ from attr import define, field
6
6
 
7
7
  from ... import schema as oai
8
- from ...utils import PythonIdentifier
8
+ from ...strings import PythonCode, PythonIdentifier
9
9
  from ..errors import PropertyError
10
- from .protocol import PropertyProtocol, Value
10
+ from .protocol import PropertyProtocol, Value, convert_example
11
11
 
12
12
 
13
13
  @define
14
14
  class NoneProperty(PropertyProtocol):
15
15
  """A property that can only be None"""
16
16
 
17
- name: str
17
+ name: oai.UntrustedString
18
18
  required: bool
19
19
  default: Value | None
20
20
  python_name: PythonIdentifier
21
- description: str | None
22
- example: str | None
21
+ description: oai.UntrustedString | None
22
+ example: oai.UntrustedString | None = field(converter=convert_example)
23
23
 
24
24
  _allowed_locations: ClassVar[set[oai.ParameterLocation]] = {
25
25
  oai.ParameterLocation.QUERY,
@@ -32,12 +32,12 @@ class NoneProperty(PropertyProtocol):
32
32
  @classmethod
33
33
  def build(
34
34
  cls,
35
- name: str,
35
+ name: oai.UntrustedString,
36
36
  required: bool,
37
37
  default: Any,
38
38
  python_name: PythonIdentifier,
39
- description: str | None,
40
- example: str | None,
39
+ description: oai.UntrustedString | None,
40
+ example: Any,
41
41
  ) -> NoneProperty | PropertyError:
42
42
  checked_default = cls.convert_value(default)
43
43
  if isinstance(checked_default, PropertyError):
@@ -52,10 +52,10 @@ class NoneProperty(PropertyProtocol):
52
52
  )
53
53
 
54
54
  @classmethod
55
- def convert_value(cls, value: Any) -> Value | None | PropertyError:
55
+ def convert_value(cls, value: Any) -> Value | PropertyError | None:
56
56
  if value is None or isinstance(value, Value):
57
57
  return value
58
58
  if isinstance(value, str):
59
59
  if value == "None":
60
- return Value(python_code=value, raw_value=value)
60
+ return Value(python_code=PythonCode(value), raw_value=value)
61
61
  return PropertyError(f"Value {value} is not valid, only None is allowed")
@@ -1,20 +1,29 @@
1
1
  from __future__ import annotations
2
2
 
3
- __all__ = ["PropertyProtocol", "Value"]
3
+ __all__ = ["PropertyProtocol", "Value", "convert_example"]
4
4
 
5
5
  from abc import abstractmethod
6
6
  from dataclasses import dataclass
7
- from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeVar
7
+ from typing import Any, ClassVar, Protocol, TypeVar
8
8
 
9
9
  from ... import Config
10
10
  from ... import schema as oai
11
- from ...utils import PythonIdentifier
11
+ from ...strings import PythonCode, PythonIdentifier, safe_for_docstring
12
12
  from ..errors import ParseError, PropertyError
13
13
 
14
- if TYPE_CHECKING: # pragma: no cover
15
- from .model_property import ModelProperty
16
- else:
17
- ModelProperty = "ModelProperty"
14
+
15
+ def convert_example(example: Any) -> oai.UntrustedString | None:
16
+ """
17
+ Convert a raw OpenAPI `example` (which can be anything) into an `UntrustedString` (or None).
18
+
19
+ Examples only ever appear in docstrings, so non-string examples are stringified. Wrapping ensures a
20
+ template which uses an example without escaping renders a safe-but-broken value instead of an exploit.
21
+ """
22
+ if example is None or isinstance(example, oai.UntrustedString):
23
+ return example
24
+ if isinstance(example, str):
25
+ return oai.UntrustedString(example)
26
+ return oai.UntrustedString(str(example))
18
27
 
19
28
 
20
29
  @dataclass
@@ -24,7 +33,7 @@ class Value:
24
33
  (with string escaping, for example). We still keep the `raw_value` around for merging `allOf`.
25
34
  """
26
35
 
27
- python_code: str
36
+ python_code: PythonCode
28
37
  raw_value: Any
29
38
 
30
39
 
@@ -45,7 +54,7 @@ class PropertyProtocol(Protocol):
45
54
  ValidationError: Raised when the default value fails to be converted to the expected type
46
55
  """
47
56
 
48
- name: str
57
+ name: oai.UntrustedString
49
58
  required: bool
50
59
  _type_string: ClassVar[str] = ""
51
60
  _json_type_string: ClassVar[str] = "" # Type of the property after JSON serialization
@@ -56,26 +65,26 @@ class PropertyProtocol(Protocol):
56
65
  }
57
66
  default: Value | None
58
67
  python_name: PythonIdentifier
59
- description: str | None
60
- example: str | None
68
+ description: oai.UntrustedString | None
69
+ example: oai.UntrustedString | None
61
70
 
62
71
  template: ClassVar[str] = "any_property.py.jinja"
63
72
  json_is_dict: ClassVar[bool] = False
64
73
 
65
74
  @abstractmethod
66
- def convert_value(self, value: Any) -> Value | None | PropertyError:
75
+ def convert_value(self, value: Any) -> Value | PropertyError | None:
67
76
  """Convert a string value to a Value object"""
68
77
  raise NotImplementedError() # pragma: no cover
69
78
 
70
79
  def validate_location(self, location: oai.ParameterLocation) -> ParseError | None:
71
80
  """Returns an error if this type of property is not allowed in the given location"""
72
81
  if location not in self._allowed_locations:
73
- return ParseError(detail=f"{self.get_type_string()} is not allowed in {location}")
82
+ return ParseError(detail=f"{self.get_type_string().as_unembedded_code()} is not allowed in {location}")
74
83
  if location == oai.ParameterLocation.PATH and not self.required:
75
84
  return ParseError(detail="Path parameter must be required")
76
85
  return None
77
86
 
78
- def set_python_name(self, new_name: str, config: Config, skip_snake_case: bool = False) -> None:
87
+ def set_python_name(self, new_name: oai.UntrustedString, config: Config, skip_snake_case: bool = False) -> None:
79
88
  """Mutates this Property to set a new python_name.
80
89
 
81
90
  Required to mutate due to how Properties are stored and the difficulty of updating them in-dict.
@@ -88,21 +97,21 @@ class PropertyProtocol(Protocol):
88
97
  PythonIdentifier(value=new_name, prefix=config.field_prefix, skip_snake_case=skip_snake_case),
89
98
  )
90
99
 
91
- def get_base_type_string(self) -> str:
92
- """Get the string describing the Python type of this property. Base types no require quoting."""
93
- return self._type_string
100
+ def get_base_type_string(self) -> PythonCode:
101
+ """Get the code describing the Python type of this property. Base types no require quoting."""
102
+ return PythonCode(self._type_string)
94
103
 
95
- def get_base_json_type_string(self) -> str:
96
- """Get the string describing the JSON type of this property. Base types no require quoting."""
97
- return self._json_type_string
104
+ def get_base_json_type_string(self) -> PythonCode:
105
+ """Get the code describing the JSON type of this property. Base types no require quoting."""
106
+ return PythonCode(self._json_type_string)
98
107
 
99
108
  def get_type_string(
100
109
  self,
101
110
  no_optional: bool = False,
102
111
  json: bool = False,
103
- ) -> str:
112
+ ) -> PythonCode:
104
113
  """
105
- Get a string representation of type that should be used when declaring this property
114
+ Get a Python code representation of type that should be used when declaring this property
106
115
 
107
116
  Args:
108
117
  no_optional: Do not include Optional or Unset even if the value is optional (needed for isinstance checks)
@@ -115,10 +124,10 @@ class PropertyProtocol(Protocol):
115
124
 
116
125
  if no_optional or self.required:
117
126
  return type_string
118
- return f"{type_string} | Unset"
127
+ return PythonCode(f"{type_string.as_unembedded_code()} | Unset")
119
128
 
120
- def get_instance_type_string(self) -> str:
121
- """Get a string representation of runtime type that should be used for `isinstance` checks"""
129
+ def get_instance_type_string(self) -> PythonCode:
130
+ """Get a Python code representation of runtime type that should be used for `isinstance` checks"""
122
131
  return self.get_type_string(no_optional=True)
123
132
 
124
133
  # noinspection PyUnusedLocal
@@ -144,25 +153,26 @@ class PropertyProtocol(Protocol):
144
153
  """
145
154
  return set()
146
155
 
147
- def to_string(self) -> str:
156
+ def to_string(self) -> PythonCode:
148
157
  """How this should be declared in a dataclass"""
149
158
  default: str | None
150
159
  if self.default is not None:
151
- default = self.default.python_code
160
+ default = self.default.python_code.as_unembedded_code()
152
161
  elif not self.required:
153
162
  default = "UNSET"
154
163
  else:
155
164
  default = None
156
165
 
166
+ declaration = f"{self.python_name}: {self.get_type_string().as_unembedded_code()}"
157
167
  if default is not None:
158
- return f"{self.python_name}: {self.get_type_string()} = {default}"
159
- return f"{self.python_name}: {self.get_type_string()}"
168
+ declaration += f" = {default}"
169
+ return PythonCode(declaration)
160
170
 
161
171
  def to_docstring(self) -> str:
162
172
  """Returns property docstring"""
163
- doc = f"{self.python_name} ({self.get_type_string()}): {self.description or ''}"
173
+ doc = f"{self.python_name} ({safe_for_docstring(self.get_type_string())}): {safe_for_docstring(self.description or '')}"
164
174
  if self.default:
165
- doc += f" Default: {self.default.python_code}."
175
+ doc += f" Default: {safe_for_docstring(self.default.python_code)}."
166
176
  if self.example:
167
- doc += f" Example: {self.example}."
177
+ doc += f" Example: {safe_for_docstring(self.example)}."
168
178
  return doc
@@ -10,7 +10,7 @@ __all__ = [
10
10
  "update_schemas_with_data",
11
11
  ]
12
12
 
13
- from typing import TYPE_CHECKING, NewType, cast
13
+ from typing import TYPE_CHECKING, NewType
14
14
  from urllib.parse import urlparse
15
15
 
16
16
  from attrs import define, evolve, field
@@ -18,7 +18,9 @@ from attrs import define, evolve, field
18
18
  from ... import Config
19
19
  from ... import schema as oai
20
20
  from ...schema.openapi_schema_pydantic import Parameter
21
- from ...utils import ClassName, PythonIdentifier
21
+ from ...schema.ref import Ref
22
+ from ...schema.untrusted_string import UntrustedString
23
+ from ...strings import ClassName, PythonIdentifier
22
24
  from ..errors import ParameterError, ParseError, PropertyError
23
25
 
24
26
  if TYPE_CHECKING: # pragma: no cover
@@ -29,10 +31,10 @@ else:
29
31
  Property = "Property"
30
32
 
31
33
 
32
- ReferencePath = NewType("ReferencePath", str)
34
+ ReferencePath = NewType("ReferencePath", UntrustedString)
33
35
 
34
36
 
35
- def parse_reference_path(ref_path_raw: str) -> ReferencePath | ParseError:
37
+ def parse_reference_path(ref_path_raw: Ref) -> ReferencePath | ParseError:
36
38
  """
37
39
  Takes a raw string provided in a `$ref` and turns it into a validated `_ReferencePath` or a `ParseError` if
38
40
  validation fails.
@@ -40,17 +42,17 @@ def parse_reference_path(ref_path_raw: str) -> ReferencePath | ParseError:
40
42
  See Also:
41
43
  - https://swagger.io/docs/specification/using-ref/
42
44
  """
43
- parsed = urlparse(ref_path_raw)
45
+ parsed = urlparse(ref_path_raw.get_untrusted_value())
44
46
  if parsed.scheme or parsed.path:
45
- return ParseError(detail=f"Remote references such as {ref_path_raw} are not supported yet.")
46
- return cast(ReferencePath, parsed.fragment)
47
+ return ParseError(detail=f"Remote references such as {parsed} are not supported yet.")
48
+ return ReferencePath(UntrustedString(parsed.fragment))
47
49
 
48
50
 
49
- def get_reference_simple_name(ref_path: str) -> str:
51
+ def get_reference_simple_name(ref_path: Ref | UntrustedString) -> UntrustedString:
50
52
  """
51
53
  Takes a path like `/components/schemas/NameOfThing` and returns a string like `NameOfThing`.
52
54
  """
53
- return ref_path.rsplit("/", maxsplit=1)[-1]
55
+ return UntrustedString(ref_path.get_untrusted_value().rsplit("/", maxsplit=1)[-1])
54
56
 
55
57
 
56
58
  @define
@@ -61,10 +63,10 @@ class Class:
61
63
  module_name: PythonIdentifier
62
64
 
63
65
  @staticmethod
64
- def from_string(*, string: str, config: Config) -> "Class":
66
+ def from_string(*, string: str | UntrustedString, config: Config) -> "Class":
65
67
  """Get a Class from an arbitrary string"""
66
- class_name = get_reference_simple_name(string) # Get rid of ref path stuff
67
- class_name = ClassName(class_name, config.field_prefix)
68
+ untrusted_class_name = get_reference_simple_name(Ref(string)) # Get rid of ref path stuff
69
+ class_name = ClassName(untrusted_class_name, config.field_prefix)
68
70
  override = config.class_overrides.get(class_name)
69
71
 
70
72
  if override is not None and override.class_name is not None:
@@ -134,12 +136,15 @@ def update_schemas_with_data(
134
136
  )
135
137
 
136
138
  if isinstance(prop, PropertyError):
137
- prop.detail = f"{prop.header}: {prop.detail}"
138
- prop.header = f"Unable to parse schema {ref_path}"
139
- if isinstance(prop.data, oai.Reference) and prop.data.ref.endswith(ref_path): # pragma: nocover
140
- prop.detail += (
139
+ detail = f"{prop.header}: {prop.detail}" if prop.detail is not None else prop.header
140
+ prop.header = f"Unable to parse schema {ref_path.get_untrusted_value()}"
141
+ if isinstance(prop.data, oai.Reference) and prop.data.ref.endswith(
142
+ ref_path.get_untrusted_value()
143
+ ): # pragma: nocover
144
+ detail += (
141
145
  "\n\nRecursive and circular references are not supported directly in an array schema's 'items' section"
142
146
  )
147
+ prop.detail = detail
143
148
  return prop
144
149
 
145
150
  schemas = evolve(schemas, classes_by_reference={ref_path: prop, **schemas.classes_by_reference})
@@ -157,7 +162,7 @@ class Parameters:
157
162
 
158
163
  def parameter_from_data(
159
164
  *,
160
- name: str,
165
+ name: UntrustedString,
161
166
  data: oai.Reference | oai.Parameter,
162
167
  parameters: Parameters,
163
168
  config: Config,
@@ -171,10 +176,10 @@ def parameter_from_data(
171
176
  return ParameterError("Parameter has no schema"), parameters
172
177
 
173
178
  new_param = Parameter(
174
- name=name,
179
+ name=name.get_untrusted_value(),
175
180
  required=data.required,
176
181
  explode=data.explode,
177
- style=data.style,
182
+ style=data.style.get_untrusted_value() if data.style is not None else None,
178
183
  param_schema=data.param_schema,
179
184
  param_in=data.param_in,
180
185
  )
@@ -203,10 +208,12 @@ def update_parameters_with_data(
203
208
  """
204
209
  param, parameters = parameter_from_data(data=data, name=data.name, parameters=parameters, config=config)
205
210
 
206
- if isinstance(param, ParameterError):
211
+ if isinstance(param, ParameterError): # pragma: no cover
207
212
  param.detail = f"{param.header}: {param.detail}"
208
- param.header = f"Unable to parse parameter {ref_path}"
209
- if isinstance(param.data, oai.Reference) and param.data.ref.endswith(ref_path): # pragma: nocover
213
+ param.header = f"Unable to parse parameter {ref_path.get_untrusted_value()}"
214
+ if isinstance(param.data, oai.Reference) and param.data.ref.endswith(
215
+ ref_path.get_untrusted_value()
216
+ ): # pragma: nocover
210
217
  param.detail += (
211
218
  "\n\nRecursive and circular references are not supported. "
212
219
  "See https://github.com/openapi-generators/openapi-python-client/issues/466"
@@ -245,5 +252,5 @@ def parameter_from_reference(
245
252
 
246
253
  _resolved_parameter_class = parameters.classes_by_reference.get(ref_path, None)
247
254
  if _resolved_parameter_class is None:
248
- return ParameterError(detail=f"Reference `{ref_path}` not found.")
255
+ return ParameterError(detail=f"Reference `{ref_path.get_untrusted_value()}` not found.")
249
256
  return _resolved_parameter_class
@@ -2,25 +2,24 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any, ClassVar, overload
4
4
 
5
- from attr import define
5
+ from attr import define, field
6
6
 
7
7
  from ... import schema as oai
8
- from ... import utils
9
- from ...utils import PythonIdentifier
8
+ from ...strings import PythonCode, PythonIdentifier, remove_string_escapes
10
9
  from ..errors import PropertyError
11
- from .protocol import PropertyProtocol, Value
10
+ from .protocol import PropertyProtocol, Value, convert_example
12
11
 
13
12
 
14
13
  @define
15
14
  class StringProperty(PropertyProtocol):
16
15
  """A property of type str"""
17
16
 
18
- name: str
17
+ name: oai.UntrustedString
19
18
  required: bool
20
19
  default: Value | None
21
20
  python_name: PythonIdentifier
22
- description: str | None
23
- example: str | None
21
+ description: oai.UntrustedString | None
22
+ example: oai.UntrustedString | None = field(converter=convert_example)
24
23
  _type_string: ClassVar[str] = "str"
25
24
  _json_type_string: ClassVar[str] = "str"
26
25
  _allowed_locations: ClassVar[set[oai.ParameterLocation]] = {
@@ -33,12 +32,12 @@ class StringProperty(PropertyProtocol):
33
32
  @classmethod
34
33
  def build(
35
34
  cls,
36
- name: str,
35
+ name: oai.UntrustedString,
37
36
  required: bool,
38
37
  default: Any,
39
38
  python_name: PythonIdentifier,
40
- description: str | None,
41
- example: str | None,
39
+ description: oai.UntrustedString | None,
40
+ example: Any,
42
41
  ) -> StringProperty | PropertyError:
43
42
  checked_default = cls.convert_value(default)
44
43
  return cls(
@@ -65,4 +64,4 @@ class StringProperty(PropertyProtocol):
65
64
  return value
66
65
  if not isinstance(value, str):
67
66
  value = str(value)
68
- return Value(python_code=repr(utils.remove_string_escapes(value)), raw_value=value)
67
+ return Value(python_code=PythonCode(repr(remove_string_escapes(value))), raw_value=value)
@@ -4,13 +4,13 @@ from collections.abc import Iterator
4
4
  from itertools import chain
5
5
  from typing import Any, ClassVar, cast
6
6
 
7
- from attr import define, evolve
7
+ from attr import define, evolve, field
8
8
 
9
- from ... import Config
9
+ from ... import Config, strings
10
10
  from ... import schema as oai
11
- from ...utils import PythonIdentifier
11
+ from ...strings import PythonIdentifier
12
12
  from ..errors import ParseError, PropertyError
13
- from .protocol import PropertyProtocol, Value
13
+ from .protocol import PropertyProtocol, Value, convert_example
14
14
  from .schemas import Schemas
15
15
 
16
16
 
@@ -18,12 +18,12 @@ from .schemas import Schemas
18
18
  class UnionProperty(PropertyProtocol):
19
19
  """A property representing a Union (anyOf) of other properties"""
20
20
 
21
- name: str
21
+ name: oai.UntrustedString
22
22
  required: bool
23
23
  default: Value | None
24
24
  python_name: PythonIdentifier
25
- description: str | None
26
- example: str | None
25
+ description: oai.UntrustedString | None
26
+ example: oai.UntrustedString | None = field(converter=convert_example)
27
27
  inner_properties: list[PropertyProtocol]
28
28
  template: ClassVar[str] = "union_property.py.jinja"
29
29
 
@@ -32,7 +32,7 @@ class UnionProperty(PropertyProtocol):
32
32
  cls,
33
33
  *,
34
34
  data: oai.Schema,
35
- name: str,
35
+ name: oai.UntrustedString,
36
36
  required: bool,
37
37
  schemas: Schemas,
38
38
  parent_name: str,
@@ -70,12 +70,12 @@ class UnionProperty(PropertyProtocol):
70
70
  and sub_prop_data.title is not None
71
71
  and sub_prop_data.title != data.title
72
72
  ):
73
- subscript = sub_prop_data.title
73
+ subscript = sub_prop_data.title.get_untrusted_value()
74
74
  else:
75
75
  subscript = f"type_{i}"
76
76
 
77
77
  sub_prop, schemas = property_from_data(
78
- name=f"{name}_{subscript}",
78
+ name=oai.UntrustedString(f"{name.get_untrusted_value()}_{subscript}"),
79
79
  required=True,
80
80
  data=sub_prop_data,
81
81
  schemas=schemas,
@@ -120,7 +120,7 @@ class UnionProperty(PropertyProtocol):
120
120
  prop = evolve(prop, default=default_or_error)
121
121
  return prop, schemas
122
122
 
123
- def convert_value(self, value: Any) -> Value | None | PropertyError:
123
+ def convert_value(self, value: Any) -> Value | PropertyError | None:
124
124
  if value is None or isinstance(value, Value):
125
125
  return None
126
126
  value_or_error: Value | PropertyError | None = PropertyError(
@@ -132,7 +132,7 @@ class UnionProperty(PropertyProtocol):
132
132
  return value_or_error
133
133
  return value_or_error
134
134
 
135
- def _get_inner_type_strings(self, json: bool) -> set[str]:
135
+ def _get_inner_type_strings(self, json: bool) -> set[strings.PythonCode]:
136
136
  return {
137
137
  p.get_type_string(
138
138
  no_optional=True,
@@ -142,18 +142,19 @@ class UnionProperty(PropertyProtocol):
142
142
  }
143
143
 
144
144
  @staticmethod
145
- def _get_type_string_from_inner_type_strings(inner_types: set[str]) -> str:
145
+ def _get_type_string_from_inner_type_strings(inner_types: set[strings.PythonCode]) -> strings.PythonCode:
146
146
  if len(inner_types) == 1:
147
147
  return inner_types.pop()
148
- return " | ".join(sorted(inner_types, key=lambda x: x.lower()))
148
+ joined = " | ".join(sorted((t.as_unembedded_code() for t in inner_types), key=str.lower))
149
+ return strings.PythonCode(joined)
149
150
 
150
- def get_base_type_string(self) -> str:
151
+ def get_base_type_string(self) -> strings.PythonCode:
151
152
  return self._get_type_string_from_inner_type_strings(self._get_inner_type_strings(json=False))
152
153
 
153
- def get_base_json_type_string(self) -> str:
154
+ def get_base_json_type_string(self) -> strings.PythonCode:
154
155
  return self._get_type_string_from_inner_type_strings(self._get_inner_type_strings(json=True))
155
156
 
156
- def get_type_strings_in_union(self, *, no_optional: bool = False, json: bool) -> set[str]:
157
+ def get_type_strings_in_union(self, *, no_optional: bool = False, json: bool) -> set[strings.PythonCode]:
157
158
  """
158
159
  Get the set of all the types that should appear within the `Union` representing this property.
159
160
 
@@ -164,22 +165,22 @@ class UnionProperty(PropertyProtocol):
164
165
  json: If True, this returns the JSON types, not the Python types, of this property.
165
166
 
166
167
  Returns:
167
- A set of strings containing the types that should appear within `Union`.
168
+ A set of code snippets containing the types that should appear within `Union`.
168
169
  """
169
170
  type_strings = self._get_inner_type_strings(json=json)
170
171
  if no_optional:
171
172
  return type_strings
172
173
  if not self.required:
173
- type_strings.add("Unset")
174
+ type_strings.add(strings.PythonCode("Unset"))
174
175
  return type_strings
175
176
 
176
177
  def get_type_string(
177
178
  self,
178
179
  no_optional: bool = False,
179
180
  json: bool = False,
180
- ) -> str:
181
+ ) -> strings.PythonCode:
181
182
  """
182
- Get a string representation of type that should be used when declaring this property.
183
+ Get a Python code representation of type that should be used when declaring this property.
183
184
  This implementation differs slightly from `Property.get_type_string` in order to collapse
184
185
  nested union types.
185
186
  """
@@ -212,5 +213,6 @@ class UnionProperty(PropertyProtocol):
212
213
 
213
214
  for inner_prop in self.inner_properties:
214
215
  if evolve(cast(Property, inner_prop), required=self.required).validate_location(location) is not None:
215
- return ParseError(detail=f"{self.get_type_string()} is not allowed in {location}")
216
+ type_string = self.get_type_string().as_unembedded_code()
217
+ return ParseError(detail=f"{type_string} is not allowed in {location}")
216
218
  return None
@@ -3,24 +3,24 @@ from __future__ import annotations
3
3
  from typing import Any, ClassVar
4
4
  from uuid import UUID
5
5
 
6
- from attr import define
6
+ from attr import define, field
7
7
 
8
8
  from ... import schema as oai
9
- from ...utils import PythonIdentifier
9
+ from ...strings import PythonCode, PythonIdentifier
10
10
  from ..errors import PropertyError
11
- from .protocol import PropertyProtocol, Value
11
+ from .protocol import PropertyProtocol, Value, convert_example
12
12
 
13
13
 
14
14
  @define
15
15
  class UuidProperty(PropertyProtocol):
16
16
  """A property of type uuid.UUID"""
17
17
 
18
- name: str
18
+ name: oai.UntrustedString
19
19
  required: bool
20
20
  default: Value | None
21
21
  python_name: PythonIdentifier
22
- description: str | None
23
- example: str | None
22
+ description: oai.UntrustedString | None
23
+ example: oai.UntrustedString | None = field(converter=convert_example)
24
24
 
25
25
  _type_string: ClassVar[str] = "UUID"
26
26
  _json_type_string: ClassVar[str] = "str"
@@ -35,12 +35,12 @@ class UuidProperty(PropertyProtocol):
35
35
  @classmethod
36
36
  def build(
37
37
  cls,
38
- name: str,
38
+ name: oai.UntrustedString,
39
39
  required: bool,
40
40
  default: Any,
41
41
  python_name: PythonIdentifier,
42
- description: str | None,
43
- example: str | None,
42
+ description: oai.UntrustedString | None,
43
+ example: Any,
44
44
  ) -> UuidProperty | PropertyError:
45
45
  checked_default = cls.convert_value(default)
46
46
  if isinstance(checked_default, PropertyError):
@@ -56,7 +56,7 @@ class UuidProperty(PropertyProtocol):
56
56
  )
57
57
 
58
58
  @classmethod
59
- def convert_value(cls, value: Any) -> Value | None | PropertyError:
59
+ def convert_value(cls, value: Any) -> Value | PropertyError | None:
60
60
  if value is None or isinstance(value, Value):
61
61
  return value
62
62
  if isinstance(value, str):
@@ -64,7 +64,7 @@ class UuidProperty(PropertyProtocol):
64
64
  UUID(value)
65
65
  except ValueError:
66
66
  return PropertyError(f"Invalid UUID value: {value}")
67
- return Value(python_code=f"UUID('{value}')", raw_value=value)
67
+ return Value(python_code=PythonCode(f"UUID('{value}')"), raw_value=value)
68
68
  return PropertyError(f"Invalid UUID value: {value}")
69
69
 
70
70
  def get_imports(self, *, prefix: str) -> set[str]: