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.
- datamodel_code_generator/__init__.py +159 -190
- datamodel_code_generator/__main__.py +151 -173
- datamodel_code_generator/arguments.py +227 -230
- datamodel_code_generator/format.py +77 -99
- datamodel_code_generator/http.py +9 -10
- datamodel_code_generator/imports.py +57 -64
- datamodel_code_generator/model/__init__.py +26 -31
- datamodel_code_generator/model/base.py +94 -127
- datamodel_code_generator/model/dataclass.py +58 -59
- datamodel_code_generator/model/enum.py +34 -30
- datamodel_code_generator/model/imports.py +13 -11
- datamodel_code_generator/model/msgspec.py +112 -126
- datamodel_code_generator/model/pydantic/__init__.py +14 -27
- datamodel_code_generator/model/pydantic/base_model.py +120 -139
- datamodel_code_generator/model/pydantic/custom_root_type.py +2 -2
- datamodel_code_generator/model/pydantic/dataclass.py +6 -4
- datamodel_code_generator/model/pydantic/imports.py +35 -33
- datamodel_code_generator/model/pydantic/types.py +86 -117
- datamodel_code_generator/model/pydantic_v2/__init__.py +17 -17
- datamodel_code_generator/model/pydantic_v2/base_model.py +118 -119
- datamodel_code_generator/model/pydantic_v2/imports.py +5 -3
- datamodel_code_generator/model/pydantic_v2/root_model.py +6 -6
- datamodel_code_generator/model/pydantic_v2/types.py +8 -7
- datamodel_code_generator/model/rootmodel.py +1 -1
- datamodel_code_generator/model/scalar.py +33 -32
- datamodel_code_generator/model/typed_dict.py +42 -41
- datamodel_code_generator/model/types.py +19 -17
- datamodel_code_generator/model/union.py +21 -17
- datamodel_code_generator/parser/__init__.py +12 -11
- datamodel_code_generator/parser/base.py +320 -492
- datamodel_code_generator/parser/graphql.py +80 -111
- datamodel_code_generator/parser/jsonschema.py +422 -580
- datamodel_code_generator/parser/openapi.py +175 -204
- datamodel_code_generator/pydantic_patch.py +8 -9
- datamodel_code_generator/reference.py +192 -274
- datamodel_code_generator/types.py +147 -182
- datamodel_code_generator/util.py +22 -26
- {datamodel_code_generator-0.27.2.dist-info → datamodel_code_generator-0.27.3.dist-info}/METADATA +7 -1
- datamodel_code_generator-0.27.3.dist-info/RECORD +59 -0
- datamodel_code_generator-0.27.2.dist-info/RECORD +0 -59
- {datamodel_code_generator-0.27.2.dist-info → datamodel_code_generator-0.27.3.dist-info}/WHEEL +0 -0
- {datamodel_code_generator-0.27.2.dist-info → datamodel_code_generator-0.27.3.dist-info}/entry_points.txt +0 -0
- {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:
|
|
67
|
-
|
|
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
|
-
) ->
|
|
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:
|
|
92
|
-
|
|
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:
|
|
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
|
-
) ->
|
|
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]] = {
|
|
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=
|
|
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(
|
|
120
|
+
original_name = values.get("original_name")
|
|
132
121
|
if original_name:
|
|
133
122
|
return values
|
|
134
123
|
|
|
135
|
-
values[
|
|
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
|
|
130
|
+
model_config = ConfigDict( # pyright: ignore[reportAssignmentType]
|
|
142
131
|
arbitrary_types_allowed=True,
|
|
143
132
|
ignored_types=(cached_property,),
|
|
144
|
-
revalidate_instances=
|
|
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(
|
|
144
|
+
return self.name.rsplit(".", 1)[-1]
|
|
160
145
|
|
|
161
146
|
|
|
162
|
-
SINGULAR_NAME_SUFFIX: str =
|
|
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(
|
|
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
|
|
182
|
-
_UNDER_SCORE_2: Pattern[str] = re.compile(
|
|
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
|
|
188
|
-
return _UNDER_SCORE_2.sub(r
|
|
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:
|
|
195
|
-
snake_case_field: bool = False,
|
|
196
|
-
empty_field_name:
|
|
197
|
-
original_delimiter:
|
|
198
|
-
special_field_name_prefix:
|
|
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:
|
|
207
|
-
self.special_field_name_prefix:
|
|
208
|
-
|
|
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:
|
|
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
|
|
216
|
+
name = re.sub(r"[¹²³⁴⁵⁶⁷⁸⁹]|\W", "_", name)
|
|
238
217
|
if name[0].isnumeric():
|
|
239
|
-
name = f
|
|
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
|
|
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
|
|
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:
|
|
275
|
-
) ->
|
|
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:
|
|
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=
|
|
302
|
-
excludes={
|
|
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:
|
|
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:
|
|
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:
|
|
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(*[
|
|
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:
|
|
347
|
-
duplicate_name_suffix:
|
|
348
|
-
base_url:
|
|
349
|
-
singular_name_suffix:
|
|
350
|
-
aliases:
|
|
351
|
-
snake_case_field: bool = False,
|
|
352
|
-
empty_field_name:
|
|
353
|
-
custom_class_name_generator:
|
|
354
|
-
base_path:
|
|
355
|
-
field_name_resolver_classes:
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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:
|
|
337
|
+
self.references: dict[str, Reference] = {}
|
|
365
338
|
self._current_root: Sequence[str] = []
|
|
366
|
-
self._root_id:
|
|
367
|
-
self._root_id_base_path:
|
|
368
|
-
self.ids:
|
|
369
|
-
self.after_load_files:
|
|
370
|
-
self.exclude_names:
|
|
371
|
-
self.duplicate_name_suffix:
|
|
372
|
-
self._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:
|
|
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:
|
|
367
|
+
self._current_base_path: Path | None = self._base_path
|
|
401
368
|
|
|
402
369
|
@property
|
|
403
|
-
def current_base_path(self) ->
|
|
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:
|
|
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) ->
|
|
377
|
+
def base_url(self) -> str | None:
|
|
411
378
|
return self._base_url
|
|
412
379
|
|
|
413
|
-
def set_base_url(self, base_url:
|
|
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) ->
|
|
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) ->
|
|
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:
|
|
460
|
-
if root_id and
|
|
461
|
-
self._root_id_base_path = root_id.rsplit(
|
|
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[
|
|
429
|
+
self.ids["/".join(self.current_root)][id_] = self.resolve_ref(path)
|
|
469
430
|
|
|
470
|
-
def resolve_ref(self, path:
|
|
471
|
-
if isinstance(path, str)
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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(
|
|
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
|
|
441
|
+
joined_path += f"#{object_part[0]}"
|
|
491
442
|
if ID_PATTERN.match(joined_path):
|
|
492
|
-
ref: str = self.ids[
|
|
443
|
+
ref: str = self.ids["/".join(self.current_root)][joined_path]
|
|
493
444
|
else:
|
|
494
|
-
if
|
|
495
|
-
joined_path +=
|
|
496
|
-
elif joined_path[0] ==
|
|
497
|
-
joined_path = f'
|
|
498
|
-
|
|
499
|
-
delimiter = joined_path.index(
|
|
500
|
-
file_path =
|
|
501
|
-
ref = f'
|
|
502
|
-
if self.root_id_base_path and not (
|
|
503
|
-
|
|
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
|
|
460
|
+
if "#" in joined_url:
|
|
512
461
|
return joined_url
|
|
513
|
-
return f
|
|
462
|
+
return f"{joined_url}#"
|
|
514
463
|
|
|
515
464
|
if is_url(ref):
|
|
516
|
-
file_part, path_part = ref.split(
|
|
465
|
+
file_part, path_part = ref.split("#", 1)
|
|
517
466
|
if file_part == self.root_id:
|
|
518
|
-
return f'
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
561
|
-
if
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
675
|
-
singular_name: bool = False,
|
|
676
|
-
singular_name_suffix:
|
|
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
|
|
679
|
-
split_name = name.split(
|
|
680
|
-
prefix =
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
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:
|
|
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:
|
|
665
|
+
excludes: set[str] | None = None,
|
|
746
666
|
model_type: ModelType = ModelType.PYDANTIC,
|
|
747
|
-
) ->
|
|
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
|
|
758
|
-
return singular_name # pyright: ignore
|
|
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 =
|
|
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 +
|
|
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((
|
|
690
|
+
return ref.startswith(("https://", "http://"))
|
|
773
691
|
|
|
774
692
|
|
|
775
693
|
inflect_engine = inflect.engine()
|