Skip to content

Commit 114feaa

Browse files
feat: Add support for number type with time-delta format (#2689)
* feat: Add support for time-delta type in JSON schema and corresponding model generation * docs: update CLI reference documentation 🤖 Generated by GitHub Actions --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent d325cf3 commit 114feaa

8 files changed

Lines changed: 98 additions & 8 deletions

File tree

docs/cli-reference/general-options.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1896,7 +1896,9 @@ comparison, not continuous watching.
18961896

18971897
??? example "Output"
18981898

1899-
> **Error:** File not found: /home/runner/work/datamodel-code-generator/datamodel-code-generator/tests/data/expected/Error: --watch and --check cannot be used together
1899+
```
1900+
Error: --watch and --check cannot be used together
1901+
```
19001902

19011903
---
19021904

@@ -1950,7 +1952,9 @@ rapid file changes. Press Ctrl+C to stop watching.
19501952

19511953
??? example "Output"
19521954

1953-
> **Error:** File not found: /home/runner/work/datamodel-code-generator/datamodel-code-generator/tests/data/expected/Watching
1955+
```
1956+
Watching
1957+
```
19541958

19551959
---
19561960

scripts/build_cli_docs.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def safe_read_file(base_path: Path, relative_path: str, file_types: tuple[str, .
155155
raise ValueError(msg) from None
156156

157157
if not full_path.exists():
158-
msg = f"File not found: {full_path}"
158+
msg = f"File not found: {relative_path}"
159159
raise FileNotFoundError(msg)
160160

161161
if full_path.is_dir():
@@ -312,14 +312,21 @@ def generate_option_section(
312312

313313
md += '??? example "Output"\n\n'
314314
if "expected_stdout" in kwargs:
315-
try:
316-
content = read_expected_file(kwargs["expected_stdout"])
315+
stdout_value = kwargs["expected_stdout"]
316+
if "/" in stdout_value or stdout_value.endswith((".py", ".txt")):
317+
try:
318+
content = read_expected_file(stdout_value)
319+
md += " ```\n"
320+
for line in content.strip().split("\n"):
321+
md += f" {line}\n" if line else " \n"
322+
md += " ```\n\n"
323+
except (FileNotFoundError, ValueError) as e:
324+
md += f" > **Error:** {e}\n\n"
325+
else:
317326
md += " ```\n"
318-
for line in content.strip().split("\n"):
327+
for line in stdout_value.strip().split("\n"):
319328
md += f" {line}\n" if line else " \n"
320329
md += " ```\n\n"
321-
except (FileNotFoundError, ValueError) as e:
322-
md += f" > **Error:** {e}\n\n"
323330
elif "comparison_output" in kwargs and "model_outputs" in kwargs:
324331
model_labels = {
325332
"pydantic_v1": "Pydantic v1",

src/datamodel_code_generator/parser/jsonschema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ def get_model_by_path(
131131
"decimal": Types.decimal,
132132
"date-time": Types.date_time,
133133
"time": Types.time,
134+
"time-delta": Types.timedelta,
134135
"default": Types.number,
135136
},
136137
"string": {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# generated by datamodel-codegen:
2+
# filename: time_delta.json
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from datetime import timedelta
8+
from typing import Any, Union
9+
10+
from msgspec import UNSET, Struct, UnsetType
11+
from typing_extensions import TypeAlias
12+
13+
Model: TypeAlias = Any
14+
15+
16+
class Test(Struct):
17+
n_timedelta: Union[timedelta, UnsetType] = UNSET
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: time_delta.json
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from datetime import timedelta
8+
from typing import Any, Optional
9+
10+
from pydantic import BaseModel, RootModel
11+
12+
13+
class Model(RootModel[Any]):
14+
root: Any
15+
16+
17+
class Test(BaseModel):
18+
n_timedelta: Optional[timedelta] = None
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"definitions": {
4+
"Test": {
5+
"type": "object",
6+
"properties": {
7+
"n_timedelta": {
8+
"type": "number",
9+
"format": "time-delta"
10+
}
11+
}
12+
}
13+
}
14+
}

tests/main/jsonschema/test_main_jsonschema.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3135,6 +3135,33 @@ def test_main_jsonschema_duration(output_model: str, expected_output: str, min_v
31353135
)
31363136

31373137

3138+
@pytest.mark.parametrize(
3139+
("output_model", "expected_output"),
3140+
[
3141+
(
3142+
"pydantic_v2.BaseModel",
3143+
"time_delta_pydantic_v2.py",
3144+
),
3145+
(
3146+
"msgspec.Struct",
3147+
"time_delta_msgspec.py",
3148+
),
3149+
],
3150+
)
3151+
def test_main_jsonschema_time_delta(
3152+
output_model: str, expected_output: str, min_version: str, output_file: Path
3153+
) -> None:
3154+
"""Test time-delta type handling for number format."""
3155+
run_main_and_assert(
3156+
input_path=JSON_SCHEMA_DATA_PATH / "time_delta.json",
3157+
output_path=output_file,
3158+
input_file_type=None,
3159+
assert_func=assert_file_content,
3160+
expected_file=expected_output,
3161+
extra_args=["--output-model-type", output_model, "--target-python", min_version],
3162+
)
3163+
3164+
31383165
@pytest.mark.skipif(
31393166
int(black.__version__.split(".")[0]) < 24,
31403167
reason="Installed black doesn't support the new style",

tests/parser/test_jsonschema.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,8 @@ def test_parse_nested_array(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) ->
393393
("string", "date-time", "DateTime", "pendulum", "DateTime", True),
394394
("string", "duration", "timedelta", "datetime", "timedelta", False),
395395
("string", "duration", "Duration", "pendulum", "Duration", True),
396+
("number", "time-delta", "timedelta", "datetime", "timedelta", False),
397+
("number", "time-delta", "Duration", "pendulum", "Duration", True),
396398
("string", "path", "Path", "pathlib", "Path", False),
397399
("string", "password", "SecretStr", "pydantic", "SecretStr", False),
398400
("string", "email", "EmailStr", "pydantic", "EmailStr", False),

0 commit comments

Comments
 (0)