datamodel-code-generator
datamodel-code-generator copied to clipboard
Feature request: timedelta support
Is your feature request related to a problem? Please describe.
We have a lot of timedelta fields in our schemas and would like to take advantage of Pydantic's native handling of timedelta types. However, datamodel-code-generator does not appear to support outputting timedelta objects. Native timedelta support would be a very useful addition.
Pydantic handles timedeltas natively. It also appears that msgspec added timedelta support recently (see https://github.com/jcrist/msgspec/pull/475) so I don't think there would be anything blocking adding timedelta support to all datamodel-code-generator output types.
Describe the solution you'd like I've seen two common JSON schema formats to represent timedeltas:
-
This one is used by Pydantic 1 and serializes the timedeltas as floats of seconds
{ "type": "object", "properties": { "granularity": { "type": "number", "format": "time-delta" } }, "required": ["granularity" ] } -
This one is used by Pydantic 2 and serializes the timedeltas as ISO 8106 duration strings
{ "type": "object", "properties": { "granularity": { "type": "string", "format": "duration", "title": "Granularity" } }, "required": [ "granularity" ] }
Ideally, datamodel-code-generator would understand and output timedelta fields for both of those schemas.
Describe alternatives you've considered
Leaving the current default behavior and having {"type": "number", "format": "time-delta"} fall back to just a float of seconds.
also having this problem and came across this issue - I prepared this snippet which might be useful for debugging / creating a test if this feature does get implemented:
import typing as ty
import importlib
import json
from pathlib import Path
from tempfile import TemporaryDirectory
from datamodel_code_generator import InputFileType, generate, DataModelType
from pydantic import BaseModel
import sys
def pydantic_model_from_json_schema(json_schema: str) -> ty.Type[BaseModel]:
load = json_schema["title"] if "title" in json_schema else "Model"
with TemporaryDirectory() as temporary_directory_name:
temporary_directory = Path(temporary_directory_name)
file_path = "model.py"
module_name = file_path.split(".")[0]
output = Path(temporary_directory / file_path)
generate(
json.dumps(json_schema),
input_file_type=InputFileType.JsonSchema,
input_filename="example.json",
output=output,
output_model_type=DataModelType.PydanticV2BaseModel,
)
spec = importlib.util.spec_from_file_location(module_name, output)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return getattr(module, load)
schema = {
"title": "Test",
"type": "object",
"properties": {
"a_int": {
"default": 1,
"title": "A Int",
"type": "integer"
},
"i_duration": {
"default": "PT2H33M3S",
"format": "duration",
"title": "I Duration",
"type": "string"
}
},
}
Model = pydantic_model_from_json_schema(schema)
Model.model_fields
Model = pydantic_model_from_json_schema(schema)
Model.model_fields
#> {'a_int': FieldInfo(annotation=Union[int, NoneType], required=False, default=1, title='A Int'),
#> 'i_duration': FieldInfo(annotation=Union[str, NoneType], required=False, default='PT2H33M3S', title='I Duration')}
this is functionality that I require so I made this hack, to modify the generated pydantic model:
from datetime import timedelta
from pydantic import create_model
def get_timedelta_fields(schema: dict) -> list[str]:
pr = schema["properties"]
return [k for k, v in pr.items() if "format" in v and v["format"] == "duration"]
def update_timedelta_field(model: BaseModel, timedelta_fields: list[str]) -> BaseModel:
"""returns a new pydantic model where serialization validators have been added to dates,
datetimes and durations for compatibility with excel"""
get_default = lambda obj: obj.default if hasattr(obj, "default") else ...
deltas = {
k: (timedelta, get_default(v))
for k, v in model.model_fields.items()
if k in timedelta_fields
} | {"__base__": model}
return create_model(model.__name__ + "New", **deltas)
li = get_timedelta_fields(schema)
Model1 = update_timedelta_field(Model, li)
Model1.model_fields
#> {'a_int': FieldInfo(annotation=Union[int, NoneType], required=False, default=1, title='A Int'),
#> 'i_duration': FieldInfo(annotation=timedelta, required=False, default='PT2H33M3S')}
thanks for this great package!