Skip to content

Commit 8c8c7ed

Browse files
authored
Fix: Handle dots in title when using --use-title-as-name option (#2598)
* Sanitize titles with dots to use as names in model generation * Refactor test_jsonschema_title_with_dots docstring for clarity
1 parent 05e8cf5 commit 8c8c7ed

6 files changed

Lines changed: 59 additions & 7 deletions

File tree

src/datamodel_code_generator/parser/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,7 @@ def __init__( # noqa: PLR0913, PLR0915
578578
capitalise_enum_members=capitalise_enum_members,
579579
no_alias=no_alias,
580580
parent_scoped_naming=parent_scoped_naming,
581+
treat_dot_as_module=treat_dot_as_module,
581582
)
582583
self.class_name: str | None = class_name
583584
self.wrap_string_literal: bool | None = wrap_string_literal

src/datamodel_code_generator/parser/jsonschema.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
)
4141
from datamodel_code_generator.model import DataModel, DataModelFieldBase
4242
from datamodel_code_generator.model import pydantic as pydantic_model
43-
from datamodel_code_generator.model.base import UNDEFINED, get_module_name
43+
from datamodel_code_generator.model.base import UNDEFINED, get_module_name, sanitize_module_name
4444
from datamodel_code_generator.model.dataclass import DataClass
4545
from datamodel_code_generator.model.enum import (
4646
SPECIALIZED_ENUM_TYPE_MATCH,
@@ -967,7 +967,7 @@ def _parse_object_common_part( # noqa: PLR0913, PLR0917
967967
self.data_model_field_type(required=True, original_name=required_, data_type=DataType())
968968
)
969969
if self.use_title_as_name and obj.title: # pragma: no cover
970-
name = obj.title
970+
name = sanitize_module_name(obj.title, treat_dot_as_module=self.treat_dot_as_module)
971971
reference = self.model_resolver.add(path, name, class_name=True, loaded=True)
972972
self.set_additional_properties(reference.path, obj)
973973

@@ -1184,7 +1184,7 @@ def parse_object(
11841184
stacklevel=2,
11851185
)
11861186
if self.use_title_as_name and obj.title:
1187-
name = obj.title
1187+
name = sanitize_module_name(obj.title, treat_dot_as_module=self.treat_dot_as_module)
11881188
reference = self.model_resolver.add(
11891189
path,
11901190
name,
@@ -1281,7 +1281,7 @@ def parse_item( # noqa: PLR0911, PLR0912
12811281
) -> DataType:
12821282
"""Parse a single JSON Schema item into a data type."""
12831283
if self.use_title_as_name and item.title:
1284-
name = item.title
1284+
name = sanitize_module_name(item.title, treat_dot_as_module=self.treat_dot_as_module)
12851285
singular_name = False
12861286
if parent and not item.enum and item.has_constraint and (parent.has_constraint or self.field_constraints):
12871287
root_type_path = get_special_path("array", path)
@@ -1441,7 +1441,7 @@ def parse_array(
14411441
) -> DataType:
14421442
"""Parse array schema into a root model with array type."""
14431443
if self.use_title_as_name and obj.title:
1444-
name = obj.title
1444+
name = sanitize_module_name(obj.title, treat_dot_as_module=self.treat_dot_as_module)
14451445
reference = self.model_resolver.add(path, name, loaded=True, class_name=True)
14461446
field = self.parse_array_fields(original_name or name, obj, [*path, name])
14471447

@@ -1530,7 +1530,7 @@ def parse_root_type( # noqa: PLR0912
15301530
else:
15311531
required = not obj.nullable and not (obj.has_default and self.apply_default_values_for_required_fields)
15321532
if self.use_title_as_name and obj.title:
1533-
name = obj.title
1533+
name = sanitize_module_name(obj.title, treat_dot_as_module=self.treat_dot_as_module)
15341534
if not reference:
15351535
reference = self.model_resolver.add(path, name, loaded=True, class_name=True)
15361536
self.set_title(reference.path, obj)
@@ -1729,7 +1729,7 @@ def create_enum(reference_: Reference) -> DataType:
17291729
return self.data_type(reference=reference_)
17301730

17311731
if self.use_title_as_name and obj.title:
1732-
name = obj.title
1732+
name = sanitize_module_name(obj.title, treat_dot_as_module=self.treat_dot_as_module)
17331733
reference = self.model_resolver.add(
17341734
path,
17351735
name,

src/datamodel_code_generator/reference.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ def __init__( # noqa: PLR0913, PLR0917
412412
no_alias: bool = False, # noqa: FBT001, FBT002
413413
remove_suffix_number: bool = False, # noqa: FBT001, FBT002
414414
parent_scoped_naming: bool = False, # noqa: FBT001, FBT002
415+
treat_dot_as_module: bool = False, # noqa: FBT001, FBT002
415416
) -> None:
416417
"""Initialize model resolver with naming and resolution options."""
417418
self.references: dict[str, Reference] = {}
@@ -447,6 +448,7 @@ def __init__( # noqa: PLR0913, PLR0917
447448
self._current_base_path: Path | None = self._base_path
448449
self.remove_suffix_number: bool = remove_suffix_number
449450
self.parent_scoped_naming = parent_scoped_naming
451+
self.treat_dot_as_module = treat_dot_as_module
450452

451453
@property
452454
def current_base_path(self) -> Path | None:
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# generated by datamodel-codegen:
2+
# filename: title_with_dots.json
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import Optional
8+
9+
from pydantic import BaseModel, Field
10+
11+
12+
class Nested15(BaseModel):
13+
value: Optional[int] = None
14+
15+
16+
class GenomeStudio20MethylationModule(BaseModel):
17+
version: Optional[str] = None
18+
nested: Optional[Nested15] = Field(None, title='Nested 1.5')
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "GenomeStudio 2.0 - Methylation Module",
4+
"type": "object",
5+
"properties": {
6+
"version": {
7+
"type": "string"
8+
},
9+
"nested": {
10+
"title": "Nested 1.5",
11+
"type": "object",
12+
"properties": {
13+
"value": {
14+
"type": "integer"
15+
}
16+
}
17+
}
18+
}
19+
}

tests/main/jsonschema/test_main_jsonschema.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,6 +1735,18 @@ def test_jsonschema_without_titles_use_title_as_name(output_file: Path) -> None:
17351735
)
17361736

17371737

1738+
def test_jsonschema_title_with_dots(output_file: Path) -> None:
1739+
"""Test using title as name when title contains dots (e.g., version numbers)."""
1740+
run_main_and_assert(
1741+
input_path=JSON_SCHEMA_DATA_PATH / "title_with_dots.json",
1742+
output_path=output_file,
1743+
input_file_type="jsonschema",
1744+
assert_func=assert_file_content,
1745+
expected_file="title_with_dots.py",
1746+
extra_args=["--use-title-as-name"],
1747+
)
1748+
1749+
17381750
def test_main_jsonschema_has_default_value(output_file: Path) -> None:
17391751
"""Test default value handling."""
17401752
run_main_and_assert(

0 commit comments

Comments
 (0)