Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: timedelta support #1624

Open
jesse-r-s-hines opened this issue Oct 18, 2023 · 3 comments
Open

Feature request: timedelta support #1624

jesse-r-s-hines opened this issue Oct 18, 2023 · 3 comments
Labels
enhancement New feature or request

Comments

@jesse-r-s-hines
Copy link

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 jcrist/msgspec#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.

@koxudaxi koxudaxi added the enhancement New feature or request label Oct 19, 2023
@jgunstone
Copy link

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!

@jgunstone
Copy link

can confirm that this is now working with v0.26.1

@jesse-r-s-hines
Copy link
Author

Thanks, this looks great! Can you also add support for the time-delta format? E.g.

{
    "type": "object",
    "properties": {
      "granularity": {
        "type": "number",
        "format": "time-delta"
      }
    },
    "required": ["granularity" ]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants