dataclass-wizard icon indicating copy to clipboard operation
dataclass-wizard copied to clipboard

Allow for arbitrary ISO8601 datetime strings to parse by using dateutil.

Open eembees opened this issue 1 year ago • 2 comments

  • Dataclass Wizard version: 0.22.1
  • Python version: 3.9
  • Operating System: Ubuntu

Description

When parsing JSON data outputs from a GQL endpoint, we sometimes encounter datetime strings that conform to ISO8601 but not to python's datetime.fromisoformat method. This is a known issue where the datetime documentation recommends using python-dateutil for this.

What I Did

Try to parse a JSON with the following datetime string:

{

"created_at": "2021-12-08T12:25:28.61+00:00",

}

Suggested solution

Replace the datetime.fromisoformat parsing with the dateutil.parser.isoparse method.

This would introduce a dependency on python-dateutil.

I would be happy to make this PR if it's 👍 'd

eembees avatar Aug 23 '22 09:08 eembees

Thanks for reporting this! I was able to look into this briefly, and implemented a working solution using the docs on patterned dates and times as a reference, and specifying a custom parse format to datetime.strptime as below.

from dataclasses import dataclass

from dataclass_wizard import JSONWizard, DateTimePattern

# as `datetime.fromisoformat()` only accepts 3 or 6 decimal places for fractional seconds
CustomISOFormat = DateTimePattern['%Y-%m-%dT%H:%M:%S.%f%z']


@dataclass
class A(JSONWizard, str=False):
    created_at: CustomISOFormat


json_dict = {
    "created_at": "2021-12-08T12:25:28.61+00:00",
}

print(A.from_dict(json_dict))
# A(created_at=datetime.datetime(2021, 12, 8, 12, 25, 28, 610000, tzinfo=datetime.timezone.utc))

I am certainly not against it we do want to continue to look into a path using dateutil.parser.isoparse. The only thought I had is, we could add the new dependency under lazy_imports.py so that the module is lazily loaded, and then maybe introduce a new class-based Meta field to use dateutil as a dependency instead of datetime for parsing an ISO format string.

Even though this might introduce a bit of undue work, I would welcome a PR that achieved this, as long as we retain the default behavior of parsing ISO strings using datetime.fromisoformat for backwards compatibility purposes.

rnag avatar Sep 20 '22 20:09 rnag

Alternatively, adding another workaround with custom type hooks, to override to use dateutil.parser.isoparse as the load function for datetime types. This approach seems a little bit faster than the one with datetime.strptime above.

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime

# pip install python-dateutil
import dateutil.parser

from dataclass_wizard import JSONWizard, LoadMixin
from dataclass_wizard.decorators import _single_arg_alias
from dataclass_wizard.type_def import N


class CustomLoader(LoadMixin):
    @staticmethod
    @_single_arg_alias(dateutil.parser.isoparse)
    def load_to_datetime(o: str | N, _: type[datetime]) -> datetime:
        # alias: isoparse(o)
        ...


@dataclass
class MyClass(JSONWizard, CustomLoader, str=False):
    created_at: datetime


print(MyClass.from_dict({"created_at": "2021-12-08T12:25:28.61+00:00"}))

rnag avatar Sep 20 '22 21:09 rnag