datamodel-code-generator 0.27.2__py3-none-any.whl → 0.27.3__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.

Potentially problematic release.


This version of datamodel-code-generator might be problematic. Click here for more details.

Files changed (43) hide show
  1. datamodel_code_generator/__init__.py +159 -190
  2. datamodel_code_generator/__main__.py +151 -173
  3. datamodel_code_generator/arguments.py +227 -230
  4. datamodel_code_generator/format.py +77 -99
  5. datamodel_code_generator/http.py +9 -10
  6. datamodel_code_generator/imports.py +57 -64
  7. datamodel_code_generator/model/__init__.py +26 -31
  8. datamodel_code_generator/model/base.py +94 -127
  9. datamodel_code_generator/model/dataclass.py +58 -59
  10. datamodel_code_generator/model/enum.py +34 -30
  11. datamodel_code_generator/model/imports.py +13 -11
  12. datamodel_code_generator/model/msgspec.py +112 -126
  13. datamodel_code_generator/model/pydantic/__init__.py +14 -27
  14. datamodel_code_generator/model/pydantic/base_model.py +120 -139
  15. datamodel_code_generator/model/pydantic/custom_root_type.py +2 -2
  16. datamodel_code_generator/model/pydantic/dataclass.py +6 -4
  17. datamodel_code_generator/model/pydantic/imports.py +35 -33
  18. datamodel_code_generator/model/pydantic/types.py +86 -117
  19. datamodel_code_generator/model/pydantic_v2/__init__.py +17 -17
  20. datamodel_code_generator/model/pydantic_v2/base_model.py +118 -119
  21. datamodel_code_generator/model/pydantic_v2/imports.py +5 -3
  22. datamodel_code_generator/model/pydantic_v2/root_model.py +6 -6
  23. datamodel_code_generator/model/pydantic_v2/types.py +8 -7
  24. datamodel_code_generator/model/rootmodel.py +1 -1
  25. datamodel_code_generator/model/scalar.py +33 -32
  26. datamodel_code_generator/model/typed_dict.py +42 -41
  27. datamodel_code_generator/model/types.py +19 -17
  28. datamodel_code_generator/model/union.py +21 -17
  29. datamodel_code_generator/parser/__init__.py +12 -11
  30. datamodel_code_generator/parser/base.py +320 -492
  31. datamodel_code_generator/parser/graphql.py +80 -111
  32. datamodel_code_generator/parser/jsonschema.py +422 -580
  33. datamodel_code_generator/parser/openapi.py +175 -204
  34. datamodel_code_generator/pydantic_patch.py +8 -9
  35. datamodel_code_generator/reference.py +192 -274
  36. datamodel_code_generator/types.py +147 -182
  37. datamodel_code_generator/util.py +22 -26
  38. {datamodel_code_generator-0.27.2.dist-info → datamodel_code_generator-0.27.3.dist-info}/METADATA +7 -1
  39. datamodel_code_generator-0.27.3.dist-info/RECORD +59 -0
  40. datamodel_code_generator-0.27.2.dist-info/RECORD +0 -59
  41. {datamodel_code_generator-0.27.2.dist-info → datamodel_code_generator-0.27.3.dist-info}/WHEEL +0 -0
  42. {datamodel_code_generator-0.27.2.dist-info → datamodel_code_generator-0.27.3.dist-info}/entry_points.txt +0 -0
  43. {datamodel_code_generator-0.27.2.dist-info → datamodel_code_generator-0.27.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
  from collections import defaultdict
3
5
  from contextlib import contextmanager
@@ -12,8 +14,6 @@ from typing import (
12
14
  Any,
13
15
  Callable,
14
16
  ClassVar,
15
- DefaultDict,
16
- Dict,
17
17
  Generator,
18
18
  List,
19
19
  Mapping,
@@ -22,10 +22,7 @@ from typing import (
22
22
  Pattern,
23
23
  Sequence,
24
24
  Set,
25
- Tuple,
26
- Type,
27
25
  TypeVar,
28
- Union,
29
26
  )
30
27
  from urllib.parse import ParseResult, urlparse
31
28
 
@@ -46,8 +43,8 @@ if TYPE_CHECKING:
46
43
 
47
44
 
48
45
  class _BaseModel(BaseModel):
49
- _exclude_fields: ClassVar[Set[str]] = set()
50
- _pass_fields: ClassVar[Set[str]] = set()
46
+ _exclude_fields: ClassVar[Set[str]] = set() # noqa: UP006
47
+ _pass_fields: ClassVar[Set[str]] = set() # noqa: UP006
51
48
 
52
49
  if not TYPE_CHECKING:
53
50
 
@@ -60,20 +57,16 @@ class _BaseModel(BaseModel):
60
57
  if not TYPE_CHECKING:
61
58
  if PYDANTIC_V2:
62
59
 
63
- def dict(
60
+ def dict( # noqa: PLR0913
64
61
  self,
65
62
  *,
66
- include: Union[
67
- AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], None
68
- ] = None,
69
- exclude: Union[
70
- AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], None
71
- ] = None,
63
+ include: AbstractSet[int | str] | Mapping[int | str, Any] | None = None,
64
+ exclude: AbstractSet[int | str] | Mapping[int | str, Any] | None = None,
72
65
  by_alias: bool = False,
73
66
  exclude_unset: bool = False,
74
67
  exclude_defaults: bool = False,
75
68
  exclude_none: bool = False,
76
- ) -> 'DictStrAny':
69
+ ) -> DictStrAny:
77
70
  return self.model_dump(
78
71
  include=include,
79
72
  exclude=set(exclude or ()) | self._exclude_fields,
@@ -85,21 +78,17 @@ class _BaseModel(BaseModel):
85
78
 
86
79
  else:
87
80
 
88
- def dict(
81
+ def dict( # noqa: PLR0913
89
82
  self,
90
83
  *,
91
- include: Union[
92
- AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], None
93
- ] = None,
94
- exclude: Union[
95
- AbstractSet[Union[int, str]], Mapping[Union[int, str], Any], None
96
- ] = None,
84
+ include: AbstractSet[int | str] | Mapping[int | str, Any] | None = None,
85
+ exclude: AbstractSet[int | str] | Mapping[int | str, Any] | None = None,
97
86
  by_alias: bool = False,
98
- skip_defaults: Optional[bool] = None,
87
+ skip_defaults: bool | None = None,
99
88
  exclude_unset: bool = False,
100
89
  exclude_defaults: bool = False,
101
90
  exclude_none: bool = False,
102
- ) -> 'DictStrAny':
91
+ ) -> DictStrAny:
103
92
  return super().dict(
104
93
  include=include,
105
94
  exclude=set(exclude or ()) | self._exclude_fields,
@@ -113,63 +102,57 @@ class _BaseModel(BaseModel):
113
102
 
114
103
  class Reference(_BaseModel):
115
104
  path: str
116
- original_name: str = ''
105
+ original_name: str = ""
117
106
  name: str
118
- duplicate_name: Optional[str] = None
107
+ duplicate_name: Optional[str] = None # noqa: UP045
119
108
  loaded: bool = True
120
- source: Optional[Any] = None
121
- children: List[Any] = []
122
- _exclude_fields: ClassVar[Set[str]] = {'children'}
109
+ source: Optional[Any] = None # noqa: UP045
110
+ children: List[Any] = [] # noqa: UP006
111
+ _exclude_fields: ClassVar[Set[str]] = {"children"} # noqa: UP006
123
112
 
124
- @model_validator(mode='before')
125
- def validate_original_name(cls, values: Any) -> Any:
113
+ @model_validator(mode="before")
114
+ def validate_original_name(cls, values: Any) -> Any: # noqa: N805
126
115
  """
127
116
  If original_name is empty then, `original_name` is assigned `name`
128
117
  """
129
118
  if not isinstance(values, dict): # pragma: no cover
130
119
  return values
131
- original_name = values.get('original_name')
120
+ original_name = values.get("original_name")
132
121
  if original_name:
133
122
  return values
134
123
 
135
- values['original_name'] = values.get('name', original_name)
124
+ values["original_name"] = values.get("name", original_name)
136
125
  return values
137
126
 
138
127
  if PYDANTIC_V2:
139
128
  # TODO[pydantic]: The following keys were removed: `copy_on_model_validation`.
140
129
  # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information.
141
- model_config = ConfigDict( # pyright: ignore [reportAssignmentType]
130
+ model_config = ConfigDict( # pyright: ignore[reportAssignmentType]
142
131
  arbitrary_types_allowed=True,
143
132
  ignored_types=(cached_property,),
144
- revalidate_instances='never',
133
+ revalidate_instances="never",
145
134
  )
146
135
  else:
147
136
 
148
137
  class Config:
149
138
  arbitrary_types_allowed = True
150
139
  keep_untouched = (cached_property,)
151
- copy_on_model_validation = (
152
- False
153
- if version.parse(pydantic.VERSION) < version.parse('1.9.2')
154
- else 'none'
155
- )
140
+ copy_on_model_validation = False if version.parse(pydantic.VERSION) < version.parse("1.9.2") else "none"
156
141
 
157
142
  @property
158
143
  def short_name(self) -> str:
159
- return self.name.rsplit('.', 1)[-1]
144
+ return self.name.rsplit(".", 1)[-1]
160
145
 
161
146
 
162
- SINGULAR_NAME_SUFFIX: str = 'Item'
147
+ SINGULAR_NAME_SUFFIX: str = "Item"
163
148
 
164
- ID_PATTERN: Pattern[str] = re.compile(r'^#[^/].*')
149
+ ID_PATTERN: Pattern[str] = re.compile(r"^#[^/].*")
165
150
 
166
- T = TypeVar('T')
151
+ T = TypeVar("T")
167
152
 
168
153
 
169
154
  @contextmanager
170
- def context_variable(
171
- setter: Callable[[T], None], current_value: T, new_value: T
172
- ) -> Generator[None, None, None]:
155
+ def context_variable(setter: Callable[[T], None], current_value: T, new_value: T) -> Generator[None, None, None]:
173
156
  previous_value: T = current_value
174
157
  setter(new_value)
175
158
  try:
@@ -178,83 +161,75 @@ def context_variable(
178
161
  setter(previous_value)
179
162
 
180
163
 
181
- _UNDER_SCORE_1: Pattern[str] = re.compile(r'([^_])([A-Z][a-z]+)')
182
- _UNDER_SCORE_2: Pattern[str] = re.compile('([a-z0-9])([A-Z])')
164
+ _UNDER_SCORE_1: Pattern[str] = re.compile(r"([^_])([A-Z][a-z]+)")
165
+ _UNDER_SCORE_2: Pattern[str] = re.compile(r"([a-z0-9])([A-Z])")
183
166
 
184
167
 
185
168
  @lru_cache
186
169
  def camel_to_snake(string: str) -> str:
187
- subbed = _UNDER_SCORE_1.sub(r'\1_\2', string)
188
- return _UNDER_SCORE_2.sub(r'\1_\2', subbed).lower()
170
+ subbed = _UNDER_SCORE_1.sub(r"\1_\2", string)
171
+ return _UNDER_SCORE_2.sub(r"\1_\2", subbed).lower()
189
172
 
190
173
 
191
174
  class FieldNameResolver:
192
- def __init__(
175
+ def __init__( # noqa: PLR0913, PLR0917
193
176
  self,
194
- aliases: Optional[Mapping[str, str]] = None,
195
- snake_case_field: bool = False,
196
- empty_field_name: Optional[str] = None,
197
- original_delimiter: Optional[str] = None,
198
- special_field_name_prefix: Optional[str] = None,
199
- remove_special_field_name_prefix: bool = False,
200
- capitalise_enum_members: bool = False,
201
- no_alias: bool = False,
202
- ):
177
+ aliases: Mapping[str, str] | None = None,
178
+ snake_case_field: bool = False, # noqa: FBT001, FBT002
179
+ empty_field_name: str | None = None,
180
+ original_delimiter: str | None = None,
181
+ special_field_name_prefix: str | None = None,
182
+ remove_special_field_name_prefix: bool = False, # noqa: FBT001, FBT002
183
+ capitalise_enum_members: bool = False, # noqa: FBT001, FBT002
184
+ no_alias: bool = False, # noqa: FBT001, FBT002
185
+ ) -> None:
203
186
  self.aliases: Mapping[str, str] = {} if aliases is None else {**aliases}
204
- self.empty_field_name: str = empty_field_name or '_'
187
+ self.empty_field_name: str = empty_field_name or "_"
205
188
  self.snake_case_field = snake_case_field
206
- self.original_delimiter: Optional[str] = original_delimiter
207
- self.special_field_name_prefix: Optional[str] = (
208
- 'field' if special_field_name_prefix is None else special_field_name_prefix
189
+ self.original_delimiter: str | None = original_delimiter
190
+ self.special_field_name_prefix: str | None = (
191
+ "field" if special_field_name_prefix is None else special_field_name_prefix
209
192
  )
210
193
  self.remove_special_field_name_prefix: bool = remove_special_field_name_prefix
211
194
  self.capitalise_enum_members: bool = capitalise_enum_members
212
195
  self.no_alias = no_alias
213
196
 
214
197
  @classmethod
215
- def _validate_field_name(cls, field_name: str) -> bool:
198
+ def _validate_field_name(cls, field_name: str) -> bool: # noqa: ARG003
216
199
  return True
217
200
 
218
- def get_valid_name(
201
+ def get_valid_name( # noqa: PLR0912
219
202
  self,
220
203
  name: str,
221
- excludes: Optional[Set[str]] = None,
222
- ignore_snake_case_field: bool = False,
223
- upper_camel: bool = False,
204
+ excludes: set[str] | None = None,
205
+ ignore_snake_case_field: bool = False, # noqa: FBT001, FBT002
206
+ upper_camel: bool = False, # noqa: FBT001, FBT002
224
207
  ) -> str:
225
208
  if not name:
226
209
  name = self.empty_field_name
227
- if name[0] == '#':
210
+ if name[0] == "#":
228
211
  name = name[1:] or self.empty_field_name
229
212
 
230
- if (
231
- self.snake_case_field
232
- and not ignore_snake_case_field
233
- and self.original_delimiter is not None
234
- ):
213
+ if self.snake_case_field and not ignore_snake_case_field and self.original_delimiter is not None:
235
214
  name = snake_to_upper_camel(name, delimiter=self.original_delimiter)
236
215
 
237
- name = re.sub(r'[¹²³⁴⁵⁶⁷⁸⁹]|\W', '_', name)
216
+ name = re.sub(r"[¹²³⁴⁵⁶⁷⁸⁹]|\W", "_", name)
238
217
  if name[0].isnumeric():
239
- name = f'{self.special_field_name_prefix}_{name}'
218
+ name = f"{self.special_field_name_prefix}_{name}"
240
219
 
241
220
  # We should avoid having a field begin with an underscore, as it
242
221
  # causes pydantic to consider it as private
243
- while name.startswith('_'):
222
+ while name.startswith("_"):
244
223
  if self.remove_special_field_name_prefix:
245
224
  name = name[1:]
246
225
  else:
247
- name = f'{self.special_field_name_prefix}{name}'
226
+ name = f"{self.special_field_name_prefix}{name}"
248
227
  break
249
- if (
250
- self.capitalise_enum_members
251
- or self.snake_case_field
252
- and not ignore_snake_case_field
253
- ):
228
+ if self.capitalise_enum_members or (self.snake_case_field and not ignore_snake_case_field):
254
229
  name = camel_to_snake(name)
255
230
  count = 1
256
231
  if iskeyword(name) or not self._validate_field_name(name):
257
- name += '_'
232
+ name += "_"
258
233
  if upper_camel:
259
234
  new_name = snake_to_upper_camel(name)
260
235
  elif self.capitalise_enum_members:
@@ -266,13 +241,13 @@ class FieldNameResolver:
266
241
  or iskeyword(new_name)
267
242
  or (excludes and new_name in excludes)
268
243
  ):
269
- new_name = f'{name}{count}' if upper_camel else f'{name}_{count}'
244
+ new_name = f"{name}{count}" if upper_camel else f"{name}_{count}"
270
245
  count += 1
271
246
  return new_name
272
247
 
273
248
  def get_valid_field_name_and_alias(
274
- self, field_name: str, excludes: Optional[Set[str]] = None
275
- ) -> Tuple[str, Optional[str]]:
249
+ self, field_name: str, excludes: set[str] | None = None
250
+ ) -> tuple[str, str | None]:
276
251
  if field_name in self.aliases:
277
252
  return self.aliases[field_name], field_name
278
253
  valid_name = self.get_valid_name(field_name, excludes=excludes)
@@ -293,13 +268,13 @@ class EnumFieldNameResolver(FieldNameResolver):
293
268
  def get_valid_name(
294
269
  self,
295
270
  name: str,
296
- excludes: Optional[Set[str]] = None,
297
- ignore_snake_case_field: bool = False,
298
- upper_camel: bool = False,
271
+ excludes: set[str] | None = None,
272
+ ignore_snake_case_field: bool = False, # noqa: FBT001, FBT002
273
+ upper_camel: bool = False, # noqa: FBT001, FBT002
299
274
  ) -> str:
300
275
  return super().get_valid_name(
301
- name='mro_' if name == 'mro' else name,
302
- excludes={'mro'} | (excludes or set()),
276
+ name="mro_" if name == "mro" else name,
277
+ excludes={"mro"} | (excludes or set()),
303
278
  ignore_snake_case_field=ignore_snake_case_field,
304
279
  upper_camel=upper_camel,
305
280
  )
@@ -311,7 +286,7 @@ class ModelType(Enum):
311
286
  CLASS = auto()
312
287
 
313
288
 
314
- DEFAULT_FIELD_NAME_RESOLVERS: Dict[ModelType, Type[FieldNameResolver]] = {
289
+ DEFAULT_FIELD_NAME_RESOLVERS: dict[ModelType, type[FieldNameResolver]] = {
315
290
  ModelType.ENUM: EnumFieldNameResolver,
316
291
  ModelType.PYDANTIC: PydanticFieldNameResolver,
317
292
  ModelType.CLASS: FieldNameResolver,
@@ -320,16 +295,16 @@ DEFAULT_FIELD_NAME_RESOLVERS: Dict[ModelType, Type[FieldNameResolver]] = {
320
295
 
321
296
  class ClassName(NamedTuple):
322
297
  name: str
323
- duplicate_name: Optional[str]
298
+ duplicate_name: str | None
324
299
 
325
300
 
326
301
  def get_relative_path(base_path: PurePath, target_path: PurePath) -> PurePath:
327
302
  if base_path == target_path:
328
- return Path('.')
303
+ return Path()
329
304
  if not target_path.is_absolute():
330
305
  return target_path
331
306
  parent_count: int = 0
332
- children: List[str] = []
307
+ children: list[str] = []
333
308
  for base_part, target_part in zip_longest(base_path.parts, target_path.parts):
334
309
  if base_part == target_part and not parent_count:
335
310
  continue
@@ -337,48 +312,44 @@ def get_relative_path(base_path: PurePath, target_path: PurePath) -> PurePath:
337
312
  parent_count += 1
338
313
  if target_part:
339
314
  children.append(target_part)
340
- return Path(*['..' for _ in range(parent_count)], *children)
315
+ return Path(*[".." for _ in range(parent_count)], *children)
341
316
 
342
317
 
343
- class ModelResolver:
344
- def __init__(
318
+ class ModelResolver: # noqa: PLR0904
319
+ def __init__( # noqa: PLR0913, PLR0917
345
320
  self,
346
- exclude_names: Optional[Set[str]] = None,
347
- duplicate_name_suffix: Optional[str] = None,
348
- base_url: Optional[str] = None,
349
- singular_name_suffix: Optional[str] = None,
350
- aliases: Optional[Mapping[str, str]] = None,
351
- snake_case_field: bool = False,
352
- empty_field_name: Optional[str] = None,
353
- custom_class_name_generator: Optional[Callable[[str], str]] = None,
354
- base_path: Optional[Path] = None,
355
- field_name_resolver_classes: Optional[
356
- Dict[ModelType, Type[FieldNameResolver]]
357
- ] = None,
358
- original_field_name_delimiter: Optional[str] = None,
359
- special_field_name_prefix: Optional[str] = None,
360
- remove_special_field_name_prefix: bool = False,
361
- capitalise_enum_members: bool = False,
362
- no_alias: bool = False,
321
+ exclude_names: set[str] | None = None,
322
+ duplicate_name_suffix: str | None = None,
323
+ base_url: str | None = None,
324
+ singular_name_suffix: str | None = None,
325
+ aliases: Mapping[str, str] | None = None,
326
+ snake_case_field: bool = False, # noqa: FBT001, FBT002
327
+ empty_field_name: str | None = None,
328
+ custom_class_name_generator: Callable[[str], str] | None = None,
329
+ base_path: Path | None = None,
330
+ field_name_resolver_classes: dict[ModelType, type[FieldNameResolver]] | None = None,
331
+ original_field_name_delimiter: str | None = None,
332
+ special_field_name_prefix: str | None = None,
333
+ remove_special_field_name_prefix: bool = False, # noqa: FBT001, FBT002
334
+ capitalise_enum_members: bool = False, # noqa: FBT001, FBT002
335
+ no_alias: bool = False, # noqa: FBT001, FBT002
363
336
  ) -> None:
364
- self.references: Dict[str, Reference] = {}
337
+ self.references: dict[str, Reference] = {}
365
338
  self._current_root: Sequence[str] = []
366
- self._root_id: Optional[str] = None
367
- self._root_id_base_path: Optional[str] = None
368
- self.ids: DefaultDict[str, Dict[str, str]] = defaultdict(dict)
369
- self.after_load_files: Set[str] = set()
370
- self.exclude_names: Set[str] = exclude_names or set()
371
- self.duplicate_name_suffix: Optional[str] = duplicate_name_suffix
372
- self._base_url: Optional[str] = base_url
339
+ self._root_id: str | None = None
340
+ self._root_id_base_path: str | None = None
341
+ self.ids: defaultdict[str, dict[str, str]] = defaultdict(dict)
342
+ self.after_load_files: set[str] = set()
343
+ self.exclude_names: set[str] = exclude_names or set()
344
+ self.duplicate_name_suffix: str | None = duplicate_name_suffix
345
+ self._base_url: str | None = base_url
373
346
  self.singular_name_suffix: str = (
374
- singular_name_suffix
375
- if isinstance(singular_name_suffix, str)
376
- else SINGULAR_NAME_SUFFIX
347
+ singular_name_suffix if isinstance(singular_name_suffix, str) else SINGULAR_NAME_SUFFIX
377
348
  )
378
349
  merged_field_name_resolver_classes = DEFAULT_FIELD_NAME_RESOLVERS.copy()
379
350
  if field_name_resolver_classes: # pragma: no cover
380
351
  merged_field_name_resolver_classes.update(field_name_resolver_classes)
381
- self.field_name_resolvers: Dict[ModelType, FieldNameResolver] = {
352
+ self.field_name_resolvers: dict[ModelType, FieldNameResolver] = {
382
353
  k: v(
383
354
  aliases=aliases,
384
355
  snake_case_field=snake_case_field,
@@ -386,42 +357,34 @@ class ModelResolver:
386
357
  original_delimiter=original_field_name_delimiter,
387
358
  special_field_name_prefix=special_field_name_prefix,
388
359
  remove_special_field_name_prefix=remove_special_field_name_prefix,
389
- capitalise_enum_members=capitalise_enum_members
390
- if k == ModelType.ENUM
391
- else False,
360
+ capitalise_enum_members=capitalise_enum_members if k == ModelType.ENUM else False,
392
361
  no_alias=no_alias,
393
362
  )
394
363
  for k, v in merged_field_name_resolver_classes.items()
395
364
  }
396
- self.class_name_generator = (
397
- custom_class_name_generator or self.default_class_name_generator
398
- )
365
+ self.class_name_generator = custom_class_name_generator or self.default_class_name_generator
399
366
  self._base_path: Path = base_path or Path.cwd()
400
- self._current_base_path: Optional[Path] = self._base_path
367
+ self._current_base_path: Path | None = self._base_path
401
368
 
402
369
  @property
403
- def current_base_path(self) -> Optional[Path]:
370
+ def current_base_path(self) -> Path | None:
404
371
  return self._current_base_path
405
372
 
406
- def set_current_base_path(self, base_path: Optional[Path]) -> None:
373
+ def set_current_base_path(self, base_path: Path | None) -> None:
407
374
  self._current_base_path = base_path
408
375
 
409
376
  @property
410
- def base_url(self) -> Optional[str]:
377
+ def base_url(self) -> str | None:
411
378
  return self._base_url
412
379
 
413
- def set_base_url(self, base_url: Optional[str]) -> None:
380
+ def set_base_url(self, base_url: str | None) -> None:
414
381
  self._base_url = base_url
415
382
 
416
383
  @contextmanager
417
- def current_base_path_context(
418
- self, base_path: Optional[Path]
419
- ) -> Generator[None, None, None]:
384
+ def current_base_path_context(self, base_path: Path | None) -> Generator[None, None, None]:
420
385
  if base_path:
421
386
  base_path = (self._base_path / base_path).resolve()
422
- with context_variable(
423
- self.set_current_base_path, self.current_base_path, base_path
424
- ):
387
+ with context_variable(self.set_current_base_path, self.current_base_path, base_path):
425
388
  yield
426
389
 
427
390
  @contextmanager
@@ -442,80 +405,66 @@ class ModelResolver:
442
405
  self._current_root = current_root
443
406
 
444
407
  @contextmanager
445
- def current_root_context(
446
- self, current_root: Sequence[str]
447
- ) -> Generator[None, None, None]:
408
+ def current_root_context(self, current_root: Sequence[str]) -> Generator[None, None, None]:
448
409
  with context_variable(self.set_current_root, self.current_root, current_root):
449
410
  yield
450
411
 
451
412
  @property
452
- def root_id(self) -> Optional[str]:
413
+ def root_id(self) -> str | None:
453
414
  return self._root_id
454
415
 
455
416
  @property
456
- def root_id_base_path(self) -> Optional[str]:
417
+ def root_id_base_path(self) -> str | None:
457
418
  return self._root_id_base_path
458
419
 
459
- def set_root_id(self, root_id: Optional[str]) -> None:
460
- if root_id and '/' in root_id:
461
- self._root_id_base_path = root_id.rsplit('/', 1)[0]
420
+ def set_root_id(self, root_id: str | None) -> None:
421
+ if root_id and "/" in root_id:
422
+ self._root_id_base_path = root_id.rsplit("/", 1)[0]
462
423
  else:
463
424
  self._root_id_base_path = None
464
425
 
465
426
  self._root_id = root_id
466
427
 
467
428
  def add_id(self, id_: str, path: Sequence[str]) -> None:
468
- self.ids['/'.join(self.current_root)][id_] = self.resolve_ref(path)
429
+ self.ids["/".join(self.current_root)][id_] = self.resolve_ref(path)
469
430
 
470
- def resolve_ref(self, path: Union[Sequence[str], str]) -> str:
471
- if isinstance(path, str):
472
- joined_path = path
473
- else:
474
- joined_path = self.join_path(path)
475
- if joined_path == '#':
476
- return f'{"/".join(self.current_root)}#'
477
- if (
478
- self.current_base_path
479
- and not self.base_url
480
- and joined_path[0] != '#'
481
- and not is_url(joined_path)
482
- ):
431
+ def resolve_ref(self, path: Sequence[str] | str) -> str: # noqa: PLR0911, PLR0912
432
+ joined_path = path if isinstance(path, str) else self.join_path(path)
433
+ if joined_path == "#":
434
+ return f"{'/'.join(self.current_root)}#"
435
+ if self.current_base_path and not self.base_url and joined_path[0] != "#" and not is_url(joined_path):
483
436
  # resolve local file path
484
- file_path, *object_part = joined_path.split('#', 1)
437
+ file_path, *object_part = joined_path.split("#", 1)
485
438
  resolved_file_path = Path(self.current_base_path, file_path).resolve()
486
- joined_path = get_relative_path(
487
- self._base_path, resolved_file_path
488
- ).as_posix()
439
+ joined_path = get_relative_path(self._base_path, resolved_file_path).as_posix()
489
440
  if object_part:
490
- joined_path += f'#{object_part[0]}'
441
+ joined_path += f"#{object_part[0]}"
491
442
  if ID_PATTERN.match(joined_path):
492
- ref: str = self.ids['/'.join(self.current_root)][joined_path]
443
+ ref: str = self.ids["/".join(self.current_root)][joined_path]
493
444
  else:
494
- if '#' not in joined_path:
495
- joined_path += '#'
496
- elif joined_path[0] == '#':
497
- joined_path = f'{"/".join(self.current_root)}{joined_path}'
498
-
499
- delimiter = joined_path.index('#')
500
- file_path = ''.join(joined_path[:delimiter])
501
- ref = f'{"".join(joined_path[:delimiter])}#{"".join(joined_path[delimiter + 1 :])}'
502
- if self.root_id_base_path and not (
503
- is_url(joined_path) or Path(self._base_path, file_path).is_file()
504
- ):
505
- ref = f'{self.root_id_base_path}/{ref}'
445
+ if "#" not in joined_path:
446
+ joined_path += "#"
447
+ elif joined_path[0] == "#":
448
+ joined_path = f"{'/'.join(self.current_root)}{joined_path}"
449
+
450
+ delimiter = joined_path.index("#")
451
+ file_path = "".join(joined_path[:delimiter])
452
+ ref = f"{''.join(joined_path[:delimiter])}#{''.join(joined_path[delimiter + 1 :])}"
453
+ if self.root_id_base_path and not (is_url(joined_path) or Path(self._base_path, file_path).is_file()):
454
+ ref = f"{self.root_id_base_path}/{ref}"
506
455
 
507
456
  if self.base_url:
508
- from .http import join_url
457
+ from .http import join_url # noqa: PLC0415
509
458
 
510
459
  joined_url = join_url(self.base_url, ref)
511
- if '#' in joined_url:
460
+ if "#" in joined_url:
512
461
  return joined_url
513
- return f'{joined_url}#'
462
+ return f"{joined_url}#"
514
463
 
515
464
  if is_url(ref):
516
- file_part, path_part = ref.split('#', 1)
465
+ file_part, path_part = ref.split("#", 1)
517
466
  if file_part == self.root_id:
518
- return f'{"/".join(self.current_root)}#{path_part}'
467
+ return f"{'/'.join(self.current_root)}#{path_part}"
519
468
  target_url: ParseResult = urlparse(file_part)
520
469
  if not (self.root_id and self.current_base_path):
521
470
  return ref
@@ -525,64 +474,47 @@ class ModelResolver:
525
474
  root_id_url.netloc,
526
475
  ): # pragma: no cover
527
476
  target_url_path = Path(target_url.path)
528
- relative_target_base = get_relative_path(
529
- Path(root_id_url.path).parent, target_url_path.parent
530
- )
531
- target_path = (
532
- self.current_base_path / relative_target_base / target_url_path.name
533
- )
477
+ relative_target_base = get_relative_path(Path(root_id_url.path).parent, target_url_path.parent)
478
+ target_path = self.current_base_path / relative_target_base / target_url_path.name
534
479
  if target_path.exists():
535
- return f'{target_path.resolve().relative_to(self._base_path)}#{path_part}'
480
+ return f"{target_path.resolve().relative_to(self._base_path)}#{path_part}"
536
481
 
537
482
  return ref
538
483
 
539
484
  def is_after_load(self, ref: str) -> bool:
540
485
  if is_url(ref) or not self.current_base_path:
541
486
  return False
542
- file_part, *_ = ref.split('#', 1)
487
+ file_part, *_ = ref.split("#", 1)
543
488
  absolute_path = Path(self._base_path, file_part).resolve().as_posix()
544
- if self.is_external_root_ref(ref):
545
- return absolute_path in self.after_load_files
546
- elif self.is_external_ref(ref):
489
+ if self.is_external_root_ref(ref) or self.is_external_ref(ref):
547
490
  return absolute_path in self.after_load_files
548
491
  return False # pragma: no cover
549
492
 
550
493
  @staticmethod
551
494
  def is_external_ref(ref: str) -> bool:
552
- return '#' in ref and ref[0] != '#'
495
+ return "#" in ref and ref[0] != "#"
553
496
 
554
497
  @staticmethod
555
498
  def is_external_root_ref(ref: str) -> bool:
556
- return ref[-1] == '#'
499
+ return ref[-1] == "#"
557
500
 
558
501
  @staticmethod
559
502
  def join_path(path: Sequence[str]) -> str:
560
- joined_path = '/'.join(p for p in path if p).replace('/#', '#')
561
- if '#' not in joined_path:
562
- joined_path += '#'
503
+ joined_path = "/".join(p for p in path if p).replace("/#", "#")
504
+ if "#" not in joined_path:
505
+ joined_path += "#"
563
506
  return joined_path
564
507
 
565
- def add_ref(self, ref: str, resolved: bool = False) -> Reference:
566
- if not resolved:
567
- path = self.resolve_ref(ref)
568
- else:
569
- path = ref
508
+ def add_ref(self, ref: str, resolved: bool = False) -> Reference: # noqa: FBT001, FBT002
509
+ path = self.resolve_ref(ref) if not resolved else ref
570
510
  reference = self.references.get(path)
571
511
  if reference:
572
512
  return reference
573
- split_ref = ref.rsplit('/', 1)
513
+ split_ref = ref.rsplit("/", 1)
574
514
  if len(split_ref) == 1:
575
- original_name = Path(
576
- split_ref[0].rstrip('#')
577
- if self.is_external_root_ref(path)
578
- else split_ref[0]
579
- ).stem
515
+ original_name = Path(split_ref[0].rstrip("#") if self.is_external_root_ref(path) else split_ref[0]).stem
580
516
  else:
581
- original_name = (
582
- Path(split_ref[1].rstrip('#')).stem
583
- if self.is_external_root_ref(path)
584
- else split_ref[1]
585
- )
517
+ original_name = Path(split_ref[1].rstrip("#")).stem if self.is_external_root_ref(path) else split_ref[1]
586
518
  name = self.get_class_name(original_name, unique=False).name
587
519
  reference = Reference(
588
520
  path=path,
@@ -594,7 +526,7 @@ class ModelResolver:
594
526
  self.references[path] = reference
595
527
  return reference
596
528
 
597
- def add(
529
+ def add( # noqa: PLR0913
598
530
  self,
599
531
  path: Sequence[str],
600
532
  original_name: str,
@@ -602,22 +534,18 @@ class ModelResolver:
602
534
  class_name: bool = False,
603
535
  singular_name: bool = False,
604
536
  unique: bool = True,
605
- singular_name_suffix: Optional[str] = None,
537
+ singular_name_suffix: str | None = None,
606
538
  loaded: bool = False,
607
539
  ) -> Reference:
608
540
  joined_path = self.join_path(path)
609
- reference: Optional[Reference] = self.references.get(joined_path)
541
+ reference: Reference | None = self.references.get(joined_path)
610
542
  if reference:
611
543
  if loaded and not reference.loaded:
612
544
  reference.loaded = True
613
- if (
614
- not original_name
615
- or original_name == reference.original_name
616
- or original_name == reference.name
617
- ):
545
+ if not original_name or original_name in {reference.original_name, reference.name}:
618
546
  return reference
619
547
  name = original_name
620
- duplicate_name: Optional[str] = None
548
+ duplicate_name: str | None = None
621
549
  if class_name:
622
550
  name, duplicate_name = self.get_class_name(
623
551
  name=name,
@@ -630,9 +558,7 @@ class ModelResolver:
630
558
  # TODO: create a validate for module name
631
559
  name = self.get_valid_field_name(name, model_type=ModelType.CLASS)
632
560
  if singular_name: # pragma: no cover
633
- name = get_singular_name(
634
- name, singular_name_suffix or self.singular_name_suffix
635
- )
561
+ name = get_singular_name(name, singular_name_suffix or self.singular_name_suffix)
636
562
  elif unique: # pragma: no cover
637
563
  unique_name = self._get_unique_name(name)
638
564
  if unique_name == name:
@@ -654,10 +580,10 @@ class ModelResolver:
654
580
  self.references[joined_path] = reference
655
581
  return reference
656
582
 
657
- def get(self, path: Union[Sequence[str], str]) -> Optional[Reference]:
583
+ def get(self, path: Sequence[str] | str) -> Reference | None:
658
584
  return self.references.get(self.resolve_ref(path))
659
585
 
660
- def delete(self, path: Union[Sequence[str], str]) -> None:
586
+ def delete(self, path: Sequence[str] | str) -> None:
661
587
  if self.resolve_ref(path) in self.references:
662
588
  del self.references[self.resolve_ref(path)]
663
589
 
@@ -670,33 +596,29 @@ class ModelResolver:
670
596
  def get_class_name(
671
597
  self,
672
598
  name: str,
673
- unique: bool = True,
674
- reserved_name: Optional[str] = None,
675
- singular_name: bool = False,
676
- singular_name_suffix: Optional[str] = None,
599
+ unique: bool = True, # noqa: FBT001, FBT002
600
+ reserved_name: str | None = None,
601
+ singular_name: bool = False, # noqa: FBT001, FBT002
602
+ singular_name_suffix: str | None = None,
677
603
  ) -> ClassName:
678
- if '.' in name:
679
- split_name = name.split('.')
680
- prefix = '.'.join(
604
+ if "." in name:
605
+ split_name = name.split(".")
606
+ prefix = ".".join(
681
607
  # TODO: create a validate for class name
682
- self.field_name_resolvers[ModelType.CLASS].get_valid_name(
683
- n, ignore_snake_case_field=True
684
- )
608
+ self.field_name_resolvers[ModelType.CLASS].get_valid_name(n, ignore_snake_case_field=True)
685
609
  for n in split_name[:-1]
686
610
  )
687
- prefix += '.'
611
+ prefix += "."
688
612
  class_name = split_name[-1]
689
613
  else:
690
- prefix = ''
614
+ prefix = ""
691
615
  class_name = name
692
616
 
693
617
  class_name = self.class_name_generator(class_name)
694
618
 
695
619
  if singular_name:
696
- class_name = get_singular_name(
697
- class_name, singular_name_suffix or self.singular_name_suffix
698
- )
699
- duplicate_name: Optional[str] = None
620
+ class_name = get_singular_name(class_name, singular_name_suffix or self.singular_name_suffix)
621
+ duplicate_name: str | None = None
700
622
  if unique:
701
623
  if reserved_name == class_name:
702
624
  return ClassName(name=class_name, duplicate_name=duplicate_name)
@@ -705,24 +627,22 @@ class ModelResolver:
705
627
  if unique_name != class_name:
706
628
  duplicate_name = class_name
707
629
  class_name = unique_name
708
- return ClassName(name=f'{prefix}{class_name}', duplicate_name=duplicate_name)
630
+ return ClassName(name=f"{prefix}{class_name}", duplicate_name=duplicate_name)
709
631
 
710
- def _get_unique_name(self, name: str, camel: bool = False) -> str:
632
+ def _get_unique_name(self, name: str, camel: bool = False) -> str: # noqa: FBT001, FBT002
711
633
  unique_name: str = name
712
634
  count: int = 1
713
- reference_names = {
714
- r.name for r in self.references.values()
715
- } | self.exclude_names
635
+ reference_names = {r.name for r in self.references.values()} | self.exclude_names
716
636
  while unique_name in reference_names:
717
637
  if self.duplicate_name_suffix:
718
- name_parts: List[Union[str, int]] = [
638
+ name_parts: list[str | int] = [
719
639
  name,
720
640
  self.duplicate_name_suffix,
721
641
  count - 1,
722
642
  ]
723
643
  else:
724
644
  name_parts = [name, count]
725
- delimiter = '' if camel else '_'
645
+ delimiter = "" if camel else "_"
726
646
  unique_name = delimiter.join(str(p) for p in name_parts if p)
727
647
  count += 1
728
648
  return unique_name
@@ -734,7 +654,7 @@ class ModelResolver:
734
654
  def get_valid_field_name(
735
655
  self,
736
656
  name: str,
737
- excludes: Optional[Set[str]] = None,
657
+ excludes: set[str] | None = None,
738
658
  model_type: ModelType = ModelType.PYDANTIC,
739
659
  ) -> str:
740
660
  return self.field_name_resolvers[model_type].get_valid_name(name, excludes)
@@ -742,34 +662,32 @@ class ModelResolver:
742
662
  def get_valid_field_name_and_alias(
743
663
  self,
744
664
  field_name: str,
745
- excludes: Optional[Set[str]] = None,
665
+ excludes: set[str] | None = None,
746
666
  model_type: ModelType = ModelType.PYDANTIC,
747
- ) -> Tuple[str, Optional[str]]:
748
- return self.field_name_resolvers[model_type].get_valid_field_name_and_alias(
749
- field_name, excludes
750
- )
667
+ ) -> tuple[str, str | None]:
668
+ return self.field_name_resolvers[model_type].get_valid_field_name_and_alias(field_name, excludes)
751
669
 
752
670
 
753
671
  @lru_cache
754
672
  def get_singular_name(name: str, suffix: str = SINGULAR_NAME_SUFFIX) -> str:
755
673
  singular_name = inflect_engine.singular_noun(name)
756
674
  if singular_name is False:
757
- singular_name = f'{name}{suffix}'
758
- return singular_name # pyright: ignore [reportReturnType]
675
+ singular_name = f"{name}{suffix}"
676
+ return singular_name # pyright: ignore[reportReturnType]
759
677
 
760
678
 
761
679
  @lru_cache
762
- def snake_to_upper_camel(word: str, delimiter: str = '_') -> str:
763
- prefix = ''
680
+ def snake_to_upper_camel(word: str, delimiter: str = "_") -> str:
681
+ prefix = ""
764
682
  if word.startswith(delimiter):
765
- prefix = '_'
683
+ prefix = "_"
766
684
  word = word[1:]
767
685
 
768
- return prefix + ''.join(x[0].upper() + x[1:] for x in word.split(delimiter) if x)
686
+ return prefix + "".join(x[0].upper() + x[1:] for x in word.split(delimiter) if x)
769
687
 
770
688
 
771
689
  def is_url(ref: str) -> bool:
772
- return ref.startswith(('https://', 'http://'))
690
+ return ref.startswith(("https://", "http://"))
773
691
 
774
692
 
775
693
  inflect_engine = inflect.engine()