diff --git a/crates/target_csharp_system_text/src/lib.rs b/crates/target_csharp_system_text/src/lib.rs index 71662703..ba365be7 100644 --- a/crates/target_csharp_system_text/src/lib.rs +++ b/crates/target_csharp_system_text/src/lib.rs @@ -160,9 +160,7 @@ impl jtd_codegen::target::Target for Target { None } - target::Item::Postamble => { - None - } + target::Item::Postamble => None, target::Item::Alias { metadata, diff --git a/crates/target_go/src/lib.rs b/crates/target_go/src/lib.rs index f485f5b7..f56baeca 100644 --- a/crates/target_go/src/lib.rs +++ b/crates/target_go/src/lib.rs @@ -157,9 +157,7 @@ impl jtd_codegen::target::Target for Target { None } - target::Item::Postamble => { - None - } + target::Item::Postamble => None, target::Item::Alias { metadata, diff --git a/crates/target_java_jackson/src/lib.rs b/crates/target_java_jackson/src/lib.rs index fc4d7ad8..d8696c37 100644 --- a/crates/target_java_jackson/src/lib.rs +++ b/crates/target_java_jackson/src/lib.rs @@ -198,9 +198,7 @@ impl jtd_codegen::target::Target for Target { None } - target::Item::Postamble => { - None - } + target::Item::Postamble => None, target::Item::Alias { metadata, diff --git a/crates/target_python/docker/Dockerfile b/crates/target_python/docker/Dockerfile index e1c7e025..977620f2 100644 --- a/crates/target_python/docker/Dockerfile +++ b/crates/target_python/docker/Dockerfile @@ -2,10 +2,14 @@ FROM python:3.9 ARG MAIN +RUN pip3 install mypy + WORKDIR /work COPY /main.py /work/main.py COPY /gen /work/gen RUN sed -i -e "s/MAIN/$MAIN/g" /work/main.py +RUN mypy --strict . + ENTRYPOINT python3 -u main.py diff --git a/crates/target_python/docker/main.py b/crates/target_python/docker/main.py index 34a61cdc..9c7de40b 100644 --- a/crates/target_python/docker/main.py +++ b/crates/target_python/docker/main.py @@ -3,5 +3,5 @@ from gen import MAIN for line in sys.stdin: - value = MAIN.from_json(json.loads(line)) - print(json.dumps(value.to_json())) + value = MAIN.from_json_data(json.loads(line)) + print(json.dumps(value.to_json_data())) diff --git a/crates/target_python/output/basic_discriminator/__init__.py b/crates/target_python/output/basic_discriminator/__init__.py index 229adc3c..333633c3 100644 --- a/crates/target_python/output/basic_discriminator/__init__.py +++ b/crates/target_python/output/basic_discriminator/__init__.py @@ -1,40 +1,25 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, Type, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root: foo: 'str' @classmethod - def from_json(cls, data) -> 'Root': - return { + def from_json_data(cls, data: Any) -> 'Root': + variants: Dict[str, Type[Root]] = { "BAR_BAZ": RootBarBaz, "QUUX": RootQuux, - }[data["foo"]].from_json(data) + } - def to_json(self): + return variants[data["foo"]].from_json_data(data) + + def to_json_data(self) -> Any: pass @dataclass @@ -42,15 +27,15 @@ class RootBarBaz(Root): baz: 'str' @classmethod - def from_json(cls, data) -> 'RootBarBaz': + def from_json_data(cls, data: Any) -> 'RootBarBaz': return cls( "BAR_BAZ", - _from_json(str, data.get("baz")), + _from_json_data(str, data.get("baz")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "foo": "BAR_BAZ" } - data["baz"] = _to_json(self.baz) + data["baz"] = _to_json_data(self.baz) return data @dataclass @@ -58,13 +43,72 @@ class RootQuux(Root): quuz: 'str' @classmethod - def from_json(cls, data) -> 'RootQuux': + def from_json_data(cls, data: Any) -> 'RootQuux': return cls( "QUUX", - _from_json(str, data.get("quuz")), + _from_json_data(str, data.get("quuz")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "foo": "QUUX" } - data["quuz"] = _to_json(self.quuz) + data["quuz"] = _to_json_data(self.quuz) + return data + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/basic_enum/__init__.py b/crates/target_python/output/basic_enum/__init__.py index 64c8ddd0..ebdaf90c 100644 --- a/crates/target_python/output/basic_enum/__init__.py +++ b/crates/target_python/output/basic_enum/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re +from datetime import datetime, timedelta, timezone from enum import Enum from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +class Root(Enum): + BAR = "Bar" + BAZ = "Baz" + FOO = "Foo" + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(data) + + def to_json_data(self) -> Any: + return self.value + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -class Root(Enum): - BAR = "Bar" - BAZ = "Baz" - FOO = "Foo" - @classmethod - def from_json(cls, data) -> 'Root': - return cls(data) +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - def to_json(self): - return self.value + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/basic_properties/__init__.py b/crates/target_python/output/basic_properties/__init__.py index 5213a289..dcb8a737 100644 --- a/crates/target_python/output/basic_properties/__init__.py +++ b/crates/target_python/output/basic_properties/__init__.py @@ -1,27 +1,10 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, List, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, List, Optional, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root: @@ -31,18 +14,77 @@ class Root: quux: 'List[bool]' @classmethod - def from_json(cls, data) -> 'Root': + def from_json_data(cls, data: Any) -> 'Root': return cls( - _from_json(str, data.get("bar")), - _from_json(List[bool], data.get("baz")), - _from_json(bool, data.get("foo")), - _from_json(List[bool], data.get("quux")), + _from_json_data(str, data.get("bar")), + _from_json_data(List[bool], data.get("baz")), + _from_json_data(bool, data.get("foo")), + _from_json_data(List[bool], data.get("quux")), ) - def to_json(self): - data = {} - data["bar"] = _to_json(self.bar) - data["baz"] = _to_json(self.baz) - data["foo"] = _to_json(self.foo) - data["quux"] = _to_json(self.quux) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["bar"] = _to_json_data(self.bar) + data["baz"] = _to_json_data(self.baz) + data["foo"] = _to_json_data(self.foo) + data["quux"] = _to_json_data(self.quux) return data + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/custom_overrides/__init__.py b/crates/target_python/output/custom_overrides/__init__.py index 1ba4a9b3..261611b3 100644 --- a/crates/target_python/output/custom_overrides/__init__.py +++ b/crates/target_python/output/custom_overrides/__init__.py @@ -1,67 +1,96 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + override_elements_container: 'List[str]' + override_type_discriminator: 'object' + override_type_enum: 'object' + override_type_expr: 'object' + override_type_properties: 'object' + override_values_container: 'Dict[str, str]' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls( + _from_json_data(List[str], data.get("override_elements_container")), + _from_json_data(object, data.get("override_type_discriminator")), + _from_json_data(object, data.get("override_type_enum")), + _from_json_data(object, data.get("override_type_expr")), + _from_json_data(object, data.get("override_type_properties")), + _from_json_data(Dict[str, str], data.get("override_values_container")), + ) + + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["override_elements_container"] = _to_json_data(self.override_elements_container) + data["override_type_discriminator"] = _to_json_data(self.override_type_discriminator) + data["override_type_enum"] = _to_json_data(self.override_type_enum) + data["override_type_expr"] = _to_json_data(self.override_type_expr) + data["override_type_properties"] = _to_json_data(self.override_type_properties) + data["override_values_container"] = _to_json_data(self.override_values_container) + return data + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class RootOverrideTypeDiscriminatorBaz(object): +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'RootOverrideTypeDiscriminatorBaz': - return cls( - "baz", - ) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() - def to_json(self): - data = { "foo": "baz" } - return data + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 -@dataclass -class Root: - override_elements_container: 'List[str]' - override_type_discriminator: 'object' - override_type_enum: 'object' - override_type_expr: 'object' - override_type_properties: 'object' - override_values_container: 'Dict[str, str]' + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 - @classmethod - def from_json(cls, data) -> 'Root': - return cls( - _from_json(List[str], data.get("override_elements_container")), - _from_json(object, data.get("override_type_discriminator")), - _from_json(object, data.get("override_type_enum")), - _from_json(object, data.get("override_type_expr")), - _from_json(object, data.get("override_type_properties")), - _from_json(Dict[str, str], data.get("override_values_container")), - ) + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') - def to_json(self): - data = {} - data["override_elements_container"] = _to_json(self.override_elements_container) - data["override_type_discriminator"] = _to_json(self.override_type_discriminator) - data["override_type_enum"] = _to_json(self.override_type_enum) - data["override_type_expr"] = _to_json(self.override_type_expr) - data["override_type_properties"] = _to_json(self.override_type_properties) - data["override_values_container"] = _to_json(self.override_values_container) - return data + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/definition_name_collisions/__init__.py b/crates/target_python/output/definition_name_collisions/__init__.py index f1e030cc..0de065e5 100644 --- a/crates/target_python/output/definition_name_collisions/__init__.py +++ b/crates/target_python/output/definition_name_collisions/__init__.py @@ -1,79 +1,121 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root: value: 'Foo0' @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Foo0, data)) + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Foo0, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class Bar: value: 'Bar0' @classmethod - def from_json(cls, data) -> 'Bar': - return cls(_from_json(Bar0, data)) + def from_json_data(cls, data: Any) -> 'Bar': + return cls(_from_json_data(Bar0, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class Foo: value: 'Bar' @classmethod - def from_json(cls, data) -> 'Foo': - return cls(_from_json(Bar, data)) + def from_json_data(cls, data: Any) -> 'Foo': + return cls(_from_json_data(Bar, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class Bar0: value: 'str' @classmethod - def from_json(cls, data) -> 'Bar0': - return cls(_from_json(str, data)) + def from_json_data(cls, data: Any) -> 'Bar0': + return cls(_from_json_data(str, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class Foo0: value: 'Foo' @classmethod - def from_json(cls, data) -> 'Foo0': - return cls(_from_json(Foo, data)) + def from_json_data(cls, data: Any) -> 'Foo0': + return cls(_from_json_data(Foo, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) - def to_json(self): - return _to_json(self.value) +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/description/__init__.py b/crates/target_python/output/description/__init__.py index 4bb994ce..10072c0c 100644 --- a/crates/target_python/output/description/__init__.py +++ b/crates/target_python/output/description/__init__.py @@ -1,28 +1,11 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from enum import Enum -from typing import Any, Optional, Union, get_args, get_origin +from typing import Any, Dict, Optional, Type, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class RootDiscriminatorWithDescription: @@ -33,12 +16,14 @@ class RootDiscriminatorWithDescription: foo: 'str' @classmethod - def from_json(cls, data) -> 'RootDiscriminatorWithDescription': - return { + def from_json_data(cls, data: Any) -> 'RootDiscriminatorWithDescription': + variants: Dict[str, Type[RootDiscriminatorWithDescription]] = { "bar": RootDiscriminatorWithDescriptionBar, - }[data["foo"]].from_json(data) + } + + return variants[data["foo"]].from_json_data(data) - def to_json(self): + def to_json_data(self) -> Any: pass @dataclass @@ -49,12 +34,12 @@ class RootDiscriminatorWithDescriptionBar(RootDiscriminatorWithDescription): @classmethod - def from_json(cls, data) -> 'RootDiscriminatorWithDescriptionBar': + def from_json_data(cls, data: Any) -> 'RootDiscriminatorWithDescriptionBar': return cls( "bar", ) - def to_json(self): + def to_json_data(self) -> Any: data = { "foo": "bar" } return data @@ -79,10 +64,10 @@ class RootEnumWithDescription(Enum): """ @classmethod - def from_json(cls, data) -> 'RootEnumWithDescription': + def from_json_data(cls, data: Any) -> 'RootEnumWithDescription': return cls(data) - def to_json(self): + def to_json_data(self) -> Any: return self.value @dataclass @@ -93,12 +78,12 @@ class RootPropertiesWithDescription: @classmethod - def from_json(cls, data) -> 'RootPropertiesWithDescription': + def from_json_data(cls, data: Any) -> 'RootPropertiesWithDescription': return cls( ) - def to_json(self): - data = {} + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} return data @dataclass @@ -139,24 +124,24 @@ class Root: @classmethod - def from_json(cls, data) -> 'Root': + def from_json_data(cls, data: Any) -> 'Root': return cls( - _from_json(RootDiscriminatorWithDescription, data.get("discriminator_with_description")), - _from_json(RootEnumWithDescription, data.get("enum_with_description")), - _from_json(str, data.get("long_description")), - _from_json(RootPropertiesWithDescription, data.get("properties_with_description")), - _from_json(Baz, data.get("ref_with_description")), - _from_json(str, data.get("string_with_description")), + _from_json_data(RootDiscriminatorWithDescription, data.get("discriminator_with_description")), + _from_json_data(RootEnumWithDescription, data.get("enum_with_description")), + _from_json_data(str, data.get("long_description")), + _from_json_data(RootPropertiesWithDescription, data.get("properties_with_description")), + _from_json_data(Baz, data.get("ref_with_description")), + _from_json_data(str, data.get("string_with_description")), ) - def to_json(self): - data = {} - data["discriminator_with_description"] = _to_json(self.discriminator_with_description) - data["enum_with_description"] = _to_json(self.enum_with_description) - data["long_description"] = _to_json(self.long_description) - data["properties_with_description"] = _to_json(self.properties_with_description) - data["ref_with_description"] = _to_json(self.ref_with_description) - data["string_with_description"] = _to_json(self.string_with_description) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["discriminator_with_description"] = _to_json_data(self.discriminator_with_description) + data["enum_with_description"] = _to_json_data(self.enum_with_description) + data["long_description"] = _to_json_data(self.long_description) + data["properties_with_description"] = _to_json_data(self.properties_with_description) + data["ref_with_description"] = _to_json_data(self.ref_with_description) + data["string_with_description"] = _to_json_data(self.string_with_description) return data @dataclass @@ -168,8 +153,67 @@ class Baz: value: 'str' @classmethod - def from_json(cls, data) -> 'Baz': - return cls(_from_json(str, data)) + def from_json_data(cls, data: Any) -> 'Baz': + return cls(_from_json_data(str, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/discriminator_optional_properties/__init__.py b/crates/target_python/output/discriminator_optional_properties/__init__.py index 35ef7b27..a89bf808 100644 --- a/crates/target_python/output/discriminator_optional_properties/__init__.py +++ b/crates/target_python/output/discriminator_optional_properties/__init__.py @@ -1,39 +1,24 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, List, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, List, Optional, Type, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root: foo: 'str' @classmethod - def from_json(cls, data) -> 'Root': - return { + def from_json_data(cls, data: Any) -> 'Root': + variants: Dict[str, Type[Root]] = { "bar": RootBar, - }[data["foo"]].from_json(data) + } - def to_json(self): + return variants[data["foo"]].from_json_data(data) + + def to_json_data(self) -> Any: pass @dataclass @@ -42,17 +27,76 @@ class RootBar(Root): quux: 'Optional[bool]' @classmethod - def from_json(cls, data) -> 'RootBar': + def from_json_data(cls, data: Any) -> 'RootBar': return cls( "bar", - _from_json(Optional[List[str]], data.get("baz")), - _from_json(Optional[bool], data.get("quux")), + _from_json_data(Optional[List[str]], data.get("baz")), + _from_json_data(Optional[bool], data.get("quux")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "foo": "bar" } if self.baz is not None: - data["baz"] = _to_json(self.baz) + data["baz"] = _to_json_data(self.baz) if self.quux is not None: - data["quux"] = _to_json(self.quux) + data["quux"] = _to_json_data(self.quux) + return data + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/elements/__init__.py b/crates/target_python/output/elements/__init__.py index 281f1104..fb32aa19 100644 --- a/crates/target_python/output/elements/__init__.py +++ b/crates/target_python/output/elements/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, List, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'List[str]' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(List[str], data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'List[str]' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(List[str], data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/enum_collisions/__init__.py b/crates/target_python/output/enum_collisions/__init__.py index 597a9410..9e24ce9c 100644 --- a/crates/target_python/output/enum_collisions/__init__.py +++ b/crates/target_python/output/enum_collisions/__init__.py @@ -1,37 +1,20 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from enum import Enum -from typing import Any, Optional, Union, get_args, get_origin +from typing import Any, Dict, Optional, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() class RootFooBar(Enum): X = "x" Y = "y" @classmethod - def from_json(cls, data) -> 'RootFooBar': + def from_json_data(cls, data: Any) -> 'RootFooBar': return cls(data) - def to_json(self): + def to_json_data(self) -> Any: return self.value @dataclass @@ -39,24 +22,24 @@ class RootFoo: bar: 'RootFooBar' @classmethod - def from_json(cls, data) -> 'RootFoo': + def from_json_data(cls, data: Any) -> 'RootFoo': return cls( - _from_json(RootFooBar, data.get("bar")), + _from_json_data(RootFooBar, data.get("bar")), ) - def to_json(self): - data = {} - data["bar"] = _to_json(self.bar) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["bar"] = _to_json_data(self.bar) return data class RootFooBar0(Enum): X = "x" Y = "y" @classmethod - def from_json(cls, data) -> 'RootFooBar0': + def from_json_data(cls, data: Any) -> 'RootFooBar0': return cls(data) - def to_json(self): + def to_json_data(self) -> Any: return self.value @dataclass @@ -65,14 +48,73 @@ class Root: foo_bar: 'RootFooBar0' @classmethod - def from_json(cls, data) -> 'Root': + def from_json_data(cls, data: Any) -> 'Root': return cls( - _from_json(RootFoo, data.get("foo")), - _from_json(RootFooBar0, data.get("foo_bar")), + _from_json_data(RootFoo, data.get("foo")), + _from_json_data(RootFooBar0, data.get("foo_bar")), ) - def to_json(self): - data = {} - data["foo"] = _to_json(self.foo) - data["foo_bar"] = _to_json(self.foo_bar) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["foo"] = _to_json_data(self.foo) + data["foo_bar"] = _to_json_data(self.foo_bar) return data + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/enum_variant_collisions/__init__.py b/crates/target_python/output/enum_variant_collisions/__init__.py index d3e12abf..0e076ac9 100644 --- a/crates/target_python/output/enum_variant_collisions/__init__.py +++ b/crates/target_python/output/enum_variant_collisions/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re +from datetime import datetime, timedelta, timezone from enum import Enum from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +class Root(Enum): + FOO = "FOO" + FOO0 = "Foo" + FOO1 = "foo" + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(data) + + def to_json_data(self) -> Any: + return self.value + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -class Root(Enum): - FOO = "FOO" - FOO0 = "Foo" - FOO1 = "foo" - @classmethod - def from_json(cls, data) -> 'Root': - return cls(data) +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - def to_json(self): - return self.value + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/geojson/__init__.py b/crates/target_python/output/geojson/__init__.py index ea841451..01cb347c 100644 --- a/crates/target_python/output/geojson/__init__.py +++ b/crates/target_python/output/geojson/__init__.py @@ -1,38 +1,21 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, List, Optional, Type, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root: value: 'GeojsonObject' @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(GeojsonObject, data)) + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(GeojsonObject, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class BoundingBox: @@ -113,11 +96,11 @@ class BoundingBox: value: 'List[float]' @classmethod - def from_json(cls, data) -> 'BoundingBox': - return cls(_from_json(List[float], data)) + def from_json_data(cls, data: Any) -> 'BoundingBox': + return cls(_from_json_data(List[float], data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class GeojsonObject: @@ -140,8 +123,8 @@ class GeojsonObject: type: 'str' @classmethod - def from_json(cls, data) -> 'GeojsonObject': - return { + def from_json_data(cls, data: Any) -> 'GeojsonObject': + variants: Dict[str, Type[GeojsonObject]] = { "Feature": GeojsonObjectFeature, "FeatureCollection": GeojsonObjectFeatureCollection, "GeometryCollection": GeojsonObjectGeometryCollection, @@ -151,9 +134,11 @@ def from_json(cls, data) -> 'GeojsonObject': "MultiPolygon": GeojsonObjectMultiPolygon, "Point": GeojsonObjectPoint, "Polygon": GeojsonObjectPolygon, - }[data["type"]].from_json(data) + } + + return variants[data["type"]].from_json_data(data) - def to_json(self): + def to_json_data(self) -> Any: pass @dataclass @@ -190,20 +175,20 @@ class GeojsonObjectFeature(GeojsonObject): id: 'Any' @classmethod - def from_json(cls, data) -> 'GeojsonObjectFeature': + def from_json_data(cls, data: Any) -> 'GeojsonObjectFeature': return cls( "Feature", - _from_json(Optional[GeojsonObject], data.get("geometry")), - _from_json(Dict[str, Any], data.get("properties")), - _from_json(Any, data.get("id")), + _from_json_data(Optional[GeojsonObject], data.get("geometry")), + _from_json_data(Dict[str, Any], data.get("properties")), + _from_json_data(Any, data.get("id")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "type": "Feature" } - data["geometry"] = _to_json(self.geometry) - data["properties"] = _to_json(self.properties) + data["geometry"] = _to_json_data(self.geometry) + data["properties"] = _to_json_data(self.properties) if self.id is not None: - data["id"] = _to_json(self.id) + data["id"] = _to_json_data(self.id) return data @dataclass @@ -219,15 +204,15 @@ class GeojsonObjectFeatureCollection(GeojsonObject): features: 'List[GeojsonObject]' @classmethod - def from_json(cls, data) -> 'GeojsonObjectFeatureCollection': + def from_json_data(cls, data: Any) -> 'GeojsonObjectFeatureCollection': return cls( "FeatureCollection", - _from_json(List[GeojsonObject], data.get("features")), + _from_json_data(List[GeojsonObject], data.get("features")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "type": "FeatureCollection" } - data["features"] = _to_json(self.features) + data["features"] = _to_json_data(self.features) return data @dataclass @@ -266,18 +251,18 @@ class GeojsonObjectGeometryCollection(GeojsonObject): bbox: 'Optional[BoundingBox]' @classmethod - def from_json(cls, data) -> 'GeojsonObjectGeometryCollection': + def from_json_data(cls, data: Any) -> 'GeojsonObjectGeometryCollection': return cls( "GeometryCollection", - _from_json(List[GeojsonObject], data.get("geometries")), - _from_json(Optional[BoundingBox], data.get("bbox")), + _from_json_data(List[GeojsonObject], data.get("geometries")), + _from_json_data(Optional[BoundingBox], data.get("bbox")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "type": "GeometryCollection" } - data["geometries"] = _to_json(self.geometries) + data["geometries"] = _to_json_data(self.geometries) if self.bbox is not None: - data["bbox"] = _to_json(self.bbox) + data["bbox"] = _to_json_data(self.bbox) return data @dataclass @@ -291,18 +276,18 @@ class GeojsonObjectLineString(GeojsonObject): bbox: 'Optional[BoundingBox]' @classmethod - def from_json(cls, data) -> 'GeojsonObjectLineString': + def from_json_data(cls, data: Any) -> 'GeojsonObjectLineString': return cls( "LineString", - _from_json(List[Position], data.get("coordinates")), - _from_json(Optional[BoundingBox], data.get("bbox")), + _from_json_data(List[Position], data.get("coordinates")), + _from_json_data(Optional[BoundingBox], data.get("bbox")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "type": "LineString" } - data["coordinates"] = _to_json(self.coordinates) + data["coordinates"] = _to_json_data(self.coordinates) if self.bbox is not None: - data["bbox"] = _to_json(self.bbox) + data["bbox"] = _to_json_data(self.bbox) return data @dataclass @@ -316,18 +301,18 @@ class GeojsonObjectMultiLineString(GeojsonObject): bbox: 'Optional[BoundingBox]' @classmethod - def from_json(cls, data) -> 'GeojsonObjectMultiLineString': + def from_json_data(cls, data: Any) -> 'GeojsonObjectMultiLineString': return cls( "MultiLineString", - _from_json(List[Position], data.get("coordinates")), - _from_json(Optional[BoundingBox], data.get("bbox")), + _from_json_data(List[Position], data.get("coordinates")), + _from_json_data(Optional[BoundingBox], data.get("bbox")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "type": "MultiLineString" } - data["coordinates"] = _to_json(self.coordinates) + data["coordinates"] = _to_json_data(self.coordinates) if self.bbox is not None: - data["bbox"] = _to_json(self.bbox) + data["bbox"] = _to_json_data(self.bbox) return data @dataclass @@ -341,18 +326,18 @@ class GeojsonObjectMultiPoint(GeojsonObject): bbox: 'Optional[BoundingBox]' @classmethod - def from_json(cls, data) -> 'GeojsonObjectMultiPoint': + def from_json_data(cls, data: Any) -> 'GeojsonObjectMultiPoint': return cls( "MultiPoint", - _from_json(List[Position], data.get("coordinates")), - _from_json(Optional[BoundingBox], data.get("bbox")), + _from_json_data(List[Position], data.get("coordinates")), + _from_json_data(Optional[BoundingBox], data.get("bbox")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "type": "MultiPoint" } - data["coordinates"] = _to_json(self.coordinates) + data["coordinates"] = _to_json_data(self.coordinates) if self.bbox is not None: - data["bbox"] = _to_json(self.bbox) + data["bbox"] = _to_json_data(self.bbox) return data @dataclass @@ -366,18 +351,18 @@ class GeojsonObjectMultiPolygon(GeojsonObject): bbox: 'Optional[BoundingBox]' @classmethod - def from_json(cls, data) -> 'GeojsonObjectMultiPolygon': + def from_json_data(cls, data: Any) -> 'GeojsonObjectMultiPolygon': return cls( "MultiPolygon", - _from_json(List[LinearRing], data.get("coordinates")), - _from_json(Optional[BoundingBox], data.get("bbox")), + _from_json_data(List[LinearRing], data.get("coordinates")), + _from_json_data(Optional[BoundingBox], data.get("bbox")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "type": "MultiPolygon" } - data["coordinates"] = _to_json(self.coordinates) + data["coordinates"] = _to_json_data(self.coordinates) if self.bbox is not None: - data["bbox"] = _to_json(self.bbox) + data["bbox"] = _to_json_data(self.bbox) return data @dataclass @@ -390,18 +375,18 @@ class GeojsonObjectPoint(GeojsonObject): bbox: 'Optional[BoundingBox]' @classmethod - def from_json(cls, data) -> 'GeojsonObjectPoint': + def from_json_data(cls, data: Any) -> 'GeojsonObjectPoint': return cls( "Point", - _from_json(Position, data.get("coordinates")), - _from_json(Optional[BoundingBox], data.get("bbox")), + _from_json_data(Position, data.get("coordinates")), + _from_json_data(Optional[BoundingBox], data.get("bbox")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "type": "Point" } - data["coordinates"] = _to_json(self.coordinates) + data["coordinates"] = _to_json_data(self.coordinates) if self.bbox is not None: - data["bbox"] = _to_json(self.bbox) + data["bbox"] = _to_json_data(self.bbox) return data @dataclass @@ -415,18 +400,18 @@ class GeojsonObjectPolygon(GeojsonObject): bbox: 'Optional[BoundingBox]' @classmethod - def from_json(cls, data) -> 'GeojsonObjectPolygon': + def from_json_data(cls, data: Any) -> 'GeojsonObjectPolygon': return cls( "Polygon", - _from_json(List[LinearRing], data.get("coordinates")), - _from_json(Optional[BoundingBox], data.get("bbox")), + _from_json_data(List[LinearRing], data.get("coordinates")), + _from_json_data(Optional[BoundingBox], data.get("bbox")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "type": "Polygon" } - data["coordinates"] = _to_json(self.coordinates) + data["coordinates"] = _to_json_data(self.coordinates) if self.bbox is not None: - data["bbox"] = _to_json(self.bbox) + data["bbox"] = _to_json_data(self.bbox) return data @dataclass @@ -464,11 +449,11 @@ class LinearRing: value: 'List[Position]' @classmethod - def from_json(cls, data) -> 'LinearRing': - return cls(_from_json(List[Position], data)) + def from_json_data(cls, data: Any) -> 'LinearRing': + return cls(_from_json_data(List[Position], data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class Position: @@ -515,8 +500,67 @@ class Position: value: 'List[float]' @classmethod - def from_json(cls, data) -> 'Position': - return cls(_from_json(List[float], data)) + def from_json_data(cls, data: Any) -> 'Position': + return cls(_from_json_data(List[float], data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/initialisms/__init__.py b/crates/target_python/output/initialisms/__init__.py index 46cb8d34..0e2b0559 100644 --- a/crates/target_python/output/initialisms/__init__.py +++ b/crates/target_python/output/initialisms/__init__.py @@ -1,27 +1,10 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, Optional, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class RootNestedIDInitialism: @@ -29,16 +12,16 @@ class RootNestedIDInitialism: normalword: 'str' @classmethod - def from_json(cls, data) -> 'RootNestedIDInitialism': + def from_json_data(cls, data: Any) -> 'RootNestedIDInitialism': return cls( - _from_json(str, data.get("json")), - _from_json(str, data.get("normalword")), + _from_json_data(str, data.get("json")), + _from_json_data(str, data.get("normalword")), ) - def to_json(self): - data = {} - data["json"] = _to_json(self.json) - data["normalword"] = _to_json(self.normalword) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["json"] = _to_json_data(self.json) + data["normalword"] = _to_json_data(self.normalword) return data @dataclass @@ -51,22 +34,81 @@ class Root: word_with_trailing_initialism_id: 'str' @classmethod - def from_json(cls, data) -> 'Root': + def from_json_data(cls, data: Any) -> 'Root': return cls( - _from_json(str, data.get("http")), - _from_json(str, data.get("id")), - _from_json(RootNestedIDInitialism, data.get("nested_id_initialism")), - _from_json(str, data.get("utf8")), - _from_json(str, data.get("word_with_embedded_id_initialism")), - _from_json(str, data.get("word_with_trailing_initialism_id")), + _from_json_data(str, data.get("http")), + _from_json_data(str, data.get("id")), + _from_json_data(RootNestedIDInitialism, data.get("nested_id_initialism")), + _from_json_data(str, data.get("utf8")), + _from_json_data(str, data.get("word_with_embedded_id_initialism")), + _from_json_data(str, data.get("word_with_trailing_initialism_id")), ) - def to_json(self): - data = {} - data["http"] = _to_json(self.http) - data["id"] = _to_json(self.id) - data["nested_id_initialism"] = _to_json(self.nested_id_initialism) - data["utf8"] = _to_json(self.utf8) - data["word_with_embedded_id_initialism"] = _to_json(self.word_with_embedded_id_initialism) - data["word_with_trailing_initialism_id"] = _to_json(self.word_with_trailing_initialism_id) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["http"] = _to_json_data(self.http) + data["id"] = _to_json_data(self.id) + data["nested_id_initialism"] = _to_json_data(self.nested_id_initialism) + data["utf8"] = _to_json_data(self.utf8) + data["word_with_embedded_id_initialism"] = _to_json_data(self.word_with_embedded_id_initialism) + data["word_with_trailing_initialism_id"] = _to_json_data(self.word_with_trailing_initialism_id) return data + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/keywords/__init__.py b/crates/target_python/output/keywords/__init__.py index 29d01542..c06f7901 100644 --- a/crates/target_python/output/keywords/__init__.py +++ b/crates/target_python/output/keywords/__init__.py @@ -1,27 +1,10 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, Optional, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root: @@ -29,16 +12,16 @@ class Root: object: 'Object' @classmethod - def from_json(cls, data) -> 'Root': + def from_json_data(cls, data: Any) -> 'Root': return cls( - _from_json(For, data.get("for")), - _from_json(Object, data.get("object")), + _from_json_data(For, data.get("for")), + _from_json_data(Object, data.get("object")), ) - def to_json(self): - data = {} - data["for"] = _to_json(self.for_) - data["object"] = _to_json(self.object) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["for"] = _to_json_data(self.for_) + data["object"] = _to_json_data(self.object) return data @dataclass @@ -46,19 +29,78 @@ class For: value: 'str' @classmethod - def from_json(cls, data) -> 'For': - return cls(_from_json(str, data)) + def from_json_data(cls, data: Any) -> 'For': + return cls(_from_json_data(str, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class Object: value: 'str' @classmethod - def from_json(cls, data) -> 'Object': - return cls(_from_json(str, data)) + def from_json_data(cls, data: Any) -> 'Object': + return cls(_from_json_data(str, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/nullable_discriminator/__init__.py b/crates/target_python/output/nullable_discriminator/__init__.py index edafe01c..c232f023 100644 --- a/crates/target_python/output/nullable_discriminator/__init__.py +++ b/crates/target_python/output/nullable_discriminator/__init__.py @@ -1,40 +1,25 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, Optional, Type, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root0: foo: 'str' @classmethod - def from_json(cls, data) -> 'Root0': - return { + def from_json_data(cls, data: Any) -> 'Root0': + variants: Dict[str, Type[Root0]] = { "bar": RootBar, "quux": RootQuux, - }[data["foo"]].from_json(data) + } - def to_json(self): + return variants[data["foo"]].from_json_data(data) + + def to_json_data(self) -> Any: pass @dataclass @@ -42,15 +27,15 @@ class RootBar(Root0): baz: 'str' @classmethod - def from_json(cls, data) -> 'RootBar': + def from_json_data(cls, data: Any) -> 'RootBar': return cls( "bar", - _from_json(str, data.get("baz")), + _from_json_data(str, data.get("baz")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "foo": "bar" } - data["baz"] = _to_json(self.baz) + data["baz"] = _to_json_data(self.baz) return data @dataclass @@ -58,15 +43,15 @@ class RootQuux(Root0): quuz: 'str' @classmethod - def from_json(cls, data) -> 'RootQuux': + def from_json_data(cls, data: Any) -> 'RootQuux': return cls( "quux", - _from_json(str, data.get("quuz")), + _from_json_data(str, data.get("quuz")), ) - def to_json(self): + def to_json_data(self) -> Any: data = { "foo": "quux" } - data["quuz"] = _to_json(self.quuz) + data["quuz"] = _to_json_data(self.quuz) return data @dataclass @@ -74,8 +59,67 @@ class Root: value: 'Optional[Root0]' @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Optional[Root0], data)) + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Optional[Root0], data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/nullable_elements/__init__.py b/crates/target_python/output/nullable_elements/__init__.py index 1ea99ad3..3ff9332a 100644 --- a/crates/target_python/output/nullable_elements/__init__.py +++ b/crates/target_python/output/nullable_elements/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, List, Optional, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'Optional[List[str]]' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Optional[List[str]], data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'Optional[List[str]]' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Optional[List[str]], data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/nullable_enum/__init__.py b/crates/target_python/output/nullable_enum/__init__.py index 43961147..c92a7834 100644 --- a/crates/target_python/output/nullable_enum/__init__.py +++ b/crates/target_python/output/nullable_enum/__init__.py @@ -1,38 +1,21 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from enum import Enum from typing import Any, Optional, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() class Root0(Enum): BAR = "Bar" BAZ = "Baz" FOO = "Foo" @classmethod - def from_json(cls, data) -> 'Root0': + def from_json_data(cls, data: Any) -> 'Root0': return cls(data) - def to_json(self): + def to_json_data(self) -> Any: return self.value @dataclass @@ -40,8 +23,67 @@ class Root: value: 'Optional[Root0]' @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Optional[Root0], data)) + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Optional[Root0], data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/nullable_properties/__init__.py b/crates/target_python/output/nullable_properties/__init__.py index 9f756dba..4b1d4399 100644 --- a/crates/target_python/output/nullable_properties/__init__.py +++ b/crates/target_python/output/nullable_properties/__init__.py @@ -1,27 +1,10 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, List, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, List, Optional, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root0: @@ -31,20 +14,20 @@ class Root0: quux: 'List[bool]' @classmethod - def from_json(cls, data) -> 'Root0': + def from_json_data(cls, data: Any) -> 'Root0': return cls( - _from_json(str, data.get("bar")), - _from_json(List[bool], data.get("baz")), - _from_json(bool, data.get("foo")), - _from_json(List[bool], data.get("quux")), + _from_json_data(str, data.get("bar")), + _from_json_data(List[bool], data.get("baz")), + _from_json_data(bool, data.get("foo")), + _from_json_data(List[bool], data.get("quux")), ) - def to_json(self): - data = {} - data["bar"] = _to_json(self.bar) - data["baz"] = _to_json(self.baz) - data["foo"] = _to_json(self.foo) - data["quux"] = _to_json(self.quux) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["bar"] = _to_json_data(self.bar) + data["baz"] = _to_json_data(self.baz) + data["foo"] = _to_json_data(self.foo) + data["quux"] = _to_json_data(self.quux) return data @dataclass @@ -52,8 +35,67 @@ class Root: value: 'Optional[Root0]' @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Optional[Root0], data)) + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Optional[Root0], data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/nullable_references/__init__.py b/crates/target_python/output/nullable_references/__init__.py index 7cb265ce..9fe3f727 100644 --- a/crates/target_python/output/nullable_references/__init__.py +++ b/crates/target_python/output/nullable_references/__init__.py @@ -1,27 +1,10 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, Optional, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root: @@ -33,24 +16,24 @@ class Root: null_string: 'NullString' @classmethod - def from_json(cls, data) -> 'Root': + def from_json_data(cls, data: Any) -> 'Root': return cls( - _from_json(NotnullRefNotnullString, data.get("notnull_ref_notnull_string")), - _from_json(NotnullRefNullString, data.get("notnull_ref_null_string")), - _from_json(NotnullString, data.get("notnull_string")), - _from_json(NullRefNotnullString, data.get("null_ref_notnull_string")), - _from_json(NullRefNullString, data.get("null_ref_null_string")), - _from_json(NullString, data.get("null_string")), + _from_json_data(NotnullRefNotnullString, data.get("notnull_ref_notnull_string")), + _from_json_data(NotnullRefNullString, data.get("notnull_ref_null_string")), + _from_json_data(NotnullString, data.get("notnull_string")), + _from_json_data(NullRefNotnullString, data.get("null_ref_notnull_string")), + _from_json_data(NullRefNullString, data.get("null_ref_null_string")), + _from_json_data(NullString, data.get("null_string")), ) - def to_json(self): - data = {} - data["notnull_ref_notnull_string"] = _to_json(self.notnull_ref_notnull_string) - data["notnull_ref_null_string"] = _to_json(self.notnull_ref_null_string) - data["notnull_string"] = _to_json(self.notnull_string) - data["null_ref_notnull_string"] = _to_json(self.null_ref_notnull_string) - data["null_ref_null_string"] = _to_json(self.null_ref_null_string) - data["null_string"] = _to_json(self.null_string) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["notnull_ref_notnull_string"] = _to_json_data(self.notnull_ref_notnull_string) + data["notnull_ref_null_string"] = _to_json_data(self.notnull_ref_null_string) + data["notnull_string"] = _to_json_data(self.notnull_string) + data["null_ref_notnull_string"] = _to_json_data(self.null_ref_notnull_string) + data["null_ref_null_string"] = _to_json_data(self.null_ref_null_string) + data["null_string"] = _to_json_data(self.null_string) return data @dataclass @@ -58,63 +41,122 @@ class NotnullRefNotnullString: value: 'NotnullString' @classmethod - def from_json(cls, data) -> 'NotnullRefNotnullString': - return cls(_from_json(NotnullString, data)) + def from_json_data(cls, data: Any) -> 'NotnullRefNotnullString': + return cls(_from_json_data(NotnullString, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class NotnullRefNullString: value: 'NullString' @classmethod - def from_json(cls, data) -> 'NotnullRefNullString': - return cls(_from_json(NullString, data)) + def from_json_data(cls, data: Any) -> 'NotnullRefNullString': + return cls(_from_json_data(NullString, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class NotnullString: value: 'str' @classmethod - def from_json(cls, data) -> 'NotnullString': - return cls(_from_json(str, data)) + def from_json_data(cls, data: Any) -> 'NotnullString': + return cls(_from_json_data(str, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class NullRefNotnullString: value: 'Optional[NotnullString]' @classmethod - def from_json(cls, data) -> 'NullRefNotnullString': - return cls(_from_json(Optional[NotnullString], data)) + def from_json_data(cls, data: Any) -> 'NullRefNotnullString': + return cls(_from_json_data(Optional[NotnullString], data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class NullRefNullString: value: 'Optional[NullString]' @classmethod - def from_json(cls, data) -> 'NullRefNullString': - return cls(_from_json(Optional[NullString], data)) + def from_json_data(cls, data: Any) -> 'NullRefNullString': + return cls(_from_json_data(Optional[NullString], data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class NullString: value: 'Optional[str]' @classmethod - def from_json(cls, data) -> 'NullString': - return cls(_from_json(Optional[str], data)) + def from_json_data(cls, data: Any) -> 'NullString': + return cls(_from_json_data(Optional[str], data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) - def to_json(self): - return _to_json(self.value) +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/nullable_timestamp_property/__init__.py b/crates/target_python/output/nullable_timestamp_property/__init__.py index f8a41693..409f2804 100644 --- a/crates/target_python/output/nullable_timestamp_property/__init__.py +++ b/crates/target_python/output/nullable_timestamp_property/__init__.py @@ -1,39 +1,81 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, Optional, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + foo: 'Optional[datetime]' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls( + _from_json_data(Optional[datetime], data.get("foo")), + ) + + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["foo"] = _to_json_data(self.foo) + return data + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - foo: 'Optional[str]' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls( - _from_json(Optional[str], data.get("foo")), - ) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() - def to_json(self): - data = {} - data["foo"] = _to_json(self.foo) - return data + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/optional_properties/__init__.py b/crates/target_python/output/optional_properties/__init__.py index 4cd1a481..f3e8b0bc 100644 --- a/crates/target_python/output/optional_properties/__init__.py +++ b/crates/target_python/output/optional_properties/__init__.py @@ -1,27 +1,10 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, List, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, List, Optional, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root: @@ -30,19 +13,78 @@ class Root: foo: 'Optional[str]' @classmethod - def from_json(cls, data) -> 'Root': + def from_json_data(cls, data: Any) -> 'Root': return cls( - _from_json(Optional[List[str]], data.get("bar")), - _from_json(Optional[bool], data.get("baz")), - _from_json(Optional[str], data.get("foo")), + _from_json_data(Optional[List[str]], data.get("bar")), + _from_json_data(Optional[bool], data.get("baz")), + _from_json_data(Optional[str], data.get("foo")), ) - def to_json(self): - data = {} + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} if self.bar is not None: - data["bar"] = _to_json(self.bar) + data["bar"] = _to_json_data(self.bar) if self.baz is not None: - data["baz"] = _to_json(self.baz) + data["baz"] = _to_json_data(self.baz) if self.foo is not None: - data["foo"] = _to_json(self.foo) + data["foo"] = _to_json_data(self.foo) return data + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/property_name_collisions/__init__.py b/crates/target_python/output/property_name_collisions/__init__.py index c44afb10..5e7e0d78 100644 --- a/crates/target_python/output/property_name_collisions/__init__.py +++ b/crates/target_python/output/property_name_collisions/__init__.py @@ -1,42 +1,84 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, Optional, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + foo: 'str' + foo0: 'str' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls( + _from_json_data(str, data.get("Foo")), + _from_json_data(str, data.get("foo")), + ) + + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["Foo"] = _to_json_data(self.foo) + data["foo"] = _to_json_data(self.foo0) + return data + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - foo: 'str' - foo0: 'str' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls( - _from_json(str, data.get("Foo")), - _from_json(str, data.get("foo")), - ) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() - def to_json(self): - data = {} - data["Foo"] = _to_json(self.foo) - data["foo"] = _to_json(self.foo0) - return data + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/reference/__init__.py b/crates/target_python/output/reference/__init__.py index 0be500c9..64a6b8b9 100644 --- a/crates/target_python/output/reference/__init__.py +++ b/crates/target_python/output/reference/__init__.py @@ -1,68 +1,110 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class Root: value: 'Foo' @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Foo, data)) + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Foo, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class Bar: value: 'Baz' @classmethod - def from_json(cls, data) -> 'Bar': - return cls(_from_json(Baz, data)) + def from_json_data(cls, data: Any) -> 'Bar': + return cls(_from_json_data(Baz, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class Baz: value: 'str' @classmethod - def from_json(cls, data) -> 'Baz': - return cls(_from_json(str, data)) + def from_json_data(cls, data: Any) -> 'Baz': + return cls(_from_json_data(str, data)) - def to_json(self): - return _to_json(self.value) + def to_json_data(self) -> Any: + return _to_json_data(self.value) @dataclass class Foo: value: 'Bar' @classmethod - def from_json(cls, data) -> 'Foo': - return cls(_from_json(Bar, data)) + def from_json_data(cls, data: Any) -> 'Foo': + return cls(_from_json_data(Bar, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_boolean/__init__.py b/crates/target_python/output/root_boolean/__init__.py index bb4a1471..4689bbf4 100644 --- a/crates/target_python/output/root_boolean/__init__.py +++ b/crates/target_python/output/root_boolean/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'bool' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(bool, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'bool' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(bool, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_empty/__init__.py b/crates/target_python/output/root_empty/__init__.py index 8a7c6e0e..929ed4d2 100644 --- a/crates/target_python/output/root_empty/__init__.py +++ b/crates/target_python/output/root_empty/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'Any' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Any, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'Any' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Any, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_float32/__init__.py b/crates/target_python/output/root_float32/__init__.py index 1b04d708..d8729de2 100644 --- a/crates/target_python/output/root_float32/__init__.py +++ b/crates/target_python/output/root_float32/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'float' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(float, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'float' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(float, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_float64/__init__.py b/crates/target_python/output/root_float64/__init__.py index 1b04d708..d8729de2 100644 --- a/crates/target_python/output/root_float64/__init__.py +++ b/crates/target_python/output/root_float64/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'float' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(float, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'float' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(float, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_int16/__init__.py b/crates/target_python/output/root_int16/__init__.py index 8939a55a..7431329d 100644 --- a/crates/target_python/output/root_int16/__init__.py +++ b/crates/target_python/output/root_int16/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'int' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(int, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'int' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(int, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_int32/__init__.py b/crates/target_python/output/root_int32/__init__.py index 8939a55a..7431329d 100644 --- a/crates/target_python/output/root_int32/__init__.py +++ b/crates/target_python/output/root_int32/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'int' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(int, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'int' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(int, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_int8/__init__.py b/crates/target_python/output/root_int8/__init__.py index 8939a55a..7431329d 100644 --- a/crates/target_python/output/root_int8/__init__.py +++ b/crates/target_python/output/root_int8/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'int' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(int, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'int' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(int, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_nullable_string/__init__.py b/crates/target_python/output/root_nullable_string/__init__.py index 1a42b8ab..df0b3424 100644 --- a/crates/target_python/output/root_nullable_string/__init__.py +++ b/crates/target_python/output/root_nullable_string/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Optional, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'Optional[str]' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Optional[str], data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'Optional[str]' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Optional[str], data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_nullable_timestamp/__init__.py b/crates/target_python/output/root_nullable_timestamp/__init__.py index 1a42b8ab..95f61948 100644 --- a/crates/target_python/output/root_nullable_timestamp/__init__.py +++ b/crates/target_python/output/root_nullable_timestamp/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Optional, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'Optional[datetime]' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Optional[datetime], data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'Optional[str]' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Optional[str], data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_string/__init__.py b/crates/target_python/output/root_string/__init__.py index 2c4784aa..26cc6e6d 100644 --- a/crates/target_python/output/root_string/__init__.py +++ b/crates/target_python/output/root_string/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'str' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(str, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'str' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(str, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_timestamp/__init__.py b/crates/target_python/output/root_timestamp/__init__.py index 2c4784aa..be203c74 100644 --- a/crates/target_python/output/root_timestamp/__init__.py +++ b/crates/target_python/output/root_timestamp/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'datetime' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(datetime, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'str' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(str, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_uint16/__init__.py b/crates/target_python/output/root_uint16/__init__.py index 8939a55a..7431329d 100644 --- a/crates/target_python/output/root_uint16/__init__.py +++ b/crates/target_python/output/root_uint16/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'int' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(int, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'int' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(int, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_uint32/__init__.py b/crates/target_python/output/root_uint32/__init__.py index 8939a55a..7431329d 100644 --- a/crates/target_python/output/root_uint32/__init__.py +++ b/crates/target_python/output/root_uint32/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'int' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(int, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'int' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(int, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/root_uint8/__init__.py b/crates/target_python/output/root_uint8/__init__.py index 8939a55a..7431329d 100644 --- a/crates/target_python/output/root_uint8/__init__.py +++ b/crates/target_python/output/root_uint8/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'int' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(int, data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'int' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(int, data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/type_collisions/__init__.py b/crates/target_python/output/type_collisions/__init__.py index 322b7e67..8f250e66 100644 --- a/crates/target_python/output/type_collisions/__init__.py +++ b/crates/target_python/output/type_collisions/__init__.py @@ -1,41 +1,24 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass -from typing import Any, Optional, Union, get_args, get_origin +from datetime import datetime, timedelta, timezone +from typing import Any, Dict, Optional, Union, get_args, get_origin -def _from_json(cls, data): - if data is None or cls in [bool, int, float, str, object] or cls is Any: - return data - if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) - if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] - if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) - -def _to_json(data): - if data is None or type(data) in [bool, int, float, str, object]: - return data - if type(data) is list: - return [_to_json(d) for d in data] - if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() @dataclass class RootFooBar: x: 'bool' @classmethod - def from_json(cls, data) -> 'RootFooBar': + def from_json_data(cls, data: Any) -> 'RootFooBar': return cls( - _from_json(bool, data.get("x")), + _from_json_data(bool, data.get("x")), ) - def to_json(self): - data = {} - data["x"] = _to_json(self.x) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["x"] = _to_json_data(self.x) return data @dataclass @@ -43,14 +26,14 @@ class RootFoo: bar: 'RootFooBar' @classmethod - def from_json(cls, data) -> 'RootFoo': + def from_json_data(cls, data: Any) -> 'RootFoo': return cls( - _from_json(RootFooBar, data.get("bar")), + _from_json_data(RootFooBar, data.get("bar")), ) - def to_json(self): - data = {} - data["bar"] = _to_json(self.bar) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["bar"] = _to_json_data(self.bar) return data @dataclass @@ -58,14 +41,14 @@ class RootFooBar0: x: 'str' @classmethod - def from_json(cls, data) -> 'RootFooBar0': + def from_json_data(cls, data: Any) -> 'RootFooBar0': return cls( - _from_json(str, data.get("x")), + _from_json_data(str, data.get("x")), ) - def to_json(self): - data = {} - data["x"] = _to_json(self.x) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["x"] = _to_json_data(self.x) return data @dataclass @@ -74,14 +57,73 @@ class Root: foo_bar: 'RootFooBar0' @classmethod - def from_json(cls, data) -> 'Root': + def from_json_data(cls, data: Any) -> 'Root': return cls( - _from_json(RootFoo, data.get("foo")), - _from_json(RootFooBar0, data.get("foo_bar")), + _from_json_data(RootFoo, data.get("foo")), + _from_json_data(RootFooBar0, data.get("foo_bar")), ) - def to_json(self): - data = {} - data["foo"] = _to_json(self.foo) - data["foo_bar"] = _to_json(self.foo_bar) + def to_json_data(self) -> Any: + data: Dict[str, Any] = {} + data["foo"] = _to_json_data(self.foo) + data["foo_bar"] = _to_json_data(self.foo_bar) return data + +def _from_json_data(cls: Any, data: Any) -> Any: + if data is None or cls in [bool, int, float, str, object] or cls is Any: + return data + if cls is datetime: + return _parse_rfc3339(data) + if get_origin(cls) is Union: + return _from_json_data(get_args(cls)[0], data) + if get_origin(cls) is list: + return [_from_json_data(get_args(cls)[0], d) for d in data] + if get_origin(cls) is dict: + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) + +def _to_json_data(data: Any) -> Any: + if data is None or type(data) in [bool, int, float, str, object]: + return data + if type(data) is datetime: + return data.isoformat() + if type(data) is list: + return [_to_json_data(d) for d in data] + if type(data) is dict: + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() + +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) + + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 + + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/output/values/__init__.py b/crates/target_python/output/values/__init__.py index e8aad3ad..69278cb0 100644 --- a/crates/target_python/output/values/__init__.py +++ b/crates/target_python/output/values/__init__.py @@ -1,35 +1,77 @@ # Code generated by jtd-codegen for Python v0.2.0 +import re from dataclasses import dataclass +from datetime import datetime, timedelta, timezone from typing import Any, Dict, Union, get_args, get_origin -def _from_json(cls, data): + +@dataclass +class Root: + value: 'Dict[str, str]' + + @classmethod + def from_json_data(cls, data: Any) -> 'Root': + return cls(_from_json_data(Dict[str, str], data)) + + def to_json_data(self) -> Any: + return _to_json_data(self.value) + +def _from_json_data(cls: Any, data: Any) -> Any: if data is None or cls in [bool, int, float, str, object] or cls is Any: return data + if cls is datetime: + return _parse_rfc3339(data) if get_origin(cls) is Union: - return _from_json(get_args(cls)[0], data) + return _from_json_data(get_args(cls)[0], data) if get_origin(cls) is list: - return [_from_json(get_args(cls)[0], d) for d in data] + return [_from_json_data(get_args(cls)[0], d) for d in data] if get_origin(cls) is dict: - return { k: _from_json(get_args(cls)[1], v) for k, v in data.items() } - return cls.from_json(data) + return { k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() } + return cls.from_json_data(data) -def _to_json(data): +def _to_json_data(data: Any) -> Any: if data is None or type(data) in [bool, int, float, str, object]: return data + if type(data) is datetime: + return data.isoformat() if type(data) is list: - return [_to_json(d) for d in data] + return [_to_json_data(d) for d in data] if type(data) is dict: - return { k: _to_json(v) for k, v in data.items() } - return data.to_json() + return { k: _to_json_data(v) for k, v in data.items() } + return data.to_json_data() -@dataclass -class Root: - value: 'Dict[str, str]' +def _parse_rfc3339(s: str) -> datetime: + datetime_re = '^(\d{4})-(\d{2})-(\d{2})[tT](\d{2}):(\d{2}):(\d{2})(\.\d+)?([zZ]|((\+|-)(\d{2}):(\d{2})))$' + match = re.match(datetime_re, s) + if not match: + raise ValueError('Invalid RFC3339 date/time', s) - @classmethod - def from_json(cls, data) -> 'Root': - return cls(_from_json(Dict[str, str], data)) + (year, month, day, hour, minute, second, frac_seconds, offset, + *tz) = match.groups() + + frac_seconds_parsed = None + if frac_seconds: + frac_seconds_parsed = int(float(frac_seconds) * 1_000_000) + else: + frac_seconds_parsed = 0 + + tzinfo = None + if offset == 'Z': + tzinfo = timezone.utc + else: + hours = int(tz[2]) + minutes = int(tz[3]) + sign = 1 if tz[1] == '+' else -1 + + if minutes not in range(60): + raise ValueError('minute offset must be in 0..59') + + tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes))) + + second_parsed = int(second) + if second_parsed == 60: + second_parsed = 59 - def to_json(self): - return _to_json(self.value) + return datetime(int(year), int(month), int(day), int(hour), int(minute), + second_parsed, frac_seconds_parsed, tzinfo) diff --git a/crates/target_python/src/lib.rs b/crates/target_python/src/lib.rs index bdd84103..fa0f0473 100644 --- a/crates/target_python/src/lib.rs +++ b/crates/target_python/src/lib.rs @@ -107,7 +107,7 @@ impl jtd_codegen::target::Target for Target { target::Expr::Float32 => "float".into(), target::Expr::Float64 => "float".into(), target::Expr::String => "str".into(), - target::Expr::Timestamp => "str".into(), + target::Expr::Timestamp => "datetime".into(), target::Expr::ArrayOf(sub_expr) => { state .imports @@ -162,6 +162,16 @@ impl jtd_codegen::target::Target for Target { "get_args".into(), ]); + state + .imports + .entry("datetime".into()) + .or_default() + .extend(vec![ + "datetime".into(), + "timedelta".into(), + "timezone".into(), + ]); + writeln!( out, "# Code generated by jtd-codegen for Python v{}", @@ -169,6 +179,9 @@ impl jtd_codegen::target::Target for Target { )?; writeln!(out)?; + // To avoid having to "import match from re" (which we use in + // _parse_rfc3339), we special-case this import. + writeln!(out, "import re")?; for (module, idents) in &state.imports { writeln!( out, @@ -179,42 +192,107 @@ impl jtd_codegen::target::Target for Target { } writeln!(out)?; - writeln!(out, "def _from_json(cls, data):")?; + + None + } + + target::Item::Postamble => { + writeln!(out)?; + writeln!(out, "def _from_json_data(cls: Any, data: Any) -> Any:")?; writeln!( out, " if data is None or cls in [bool, int, float, str, object] or cls is Any:" )?; writeln!(out, " return data")?; + writeln!(out, " if cls is datetime:")?; + writeln!(out, " return _parse_rfc3339(data)")?; writeln!(out, " if get_origin(cls) is Union:")?; - writeln!(out, " return _from_json(get_args(cls)[0], data)")?; + writeln!( + out, + " return _from_json_data(get_args(cls)[0], data)" + )?; writeln!(out, " if get_origin(cls) is list:")?; writeln!( out, - " return [_from_json(get_args(cls)[0], d) for d in data]" + " return [_from_json_data(get_args(cls)[0], d) for d in data]" )?; writeln!(out, " if get_origin(cls) is dict:")?; - writeln!(out, " return {{ k: _from_json(get_args(cls)[1], v) for k, v in data.items() }}")?; - writeln!(out, " return cls.from_json(data)")?; + writeln!(out, " return {{ k: _from_json_data(get_args(cls)[1], v) for k, v in data.items() }}")?; + writeln!(out, " return cls.from_json_data(data)")?; writeln!(out)?; - writeln!(out, "def _to_json(data):")?; + writeln!(out, "def _to_json_data(data: Any) -> Any:")?; writeln!( out, " if data is None or type(data) in [bool, int, float, str, object]:" )?; writeln!(out, " return data")?; + writeln!(out, " if type(data) is datetime:")?; + writeln!(out, " return data.isoformat()")?; writeln!(out, " if type(data) is list:")?; - writeln!(out, " return [_to_json(d) for d in data]")?; + writeln!(out, " return [_to_json_data(d) for d in data]")?; writeln!(out, " if type(data) is dict:")?; writeln!( out, - " return {{ k: _to_json(v) for k, v in data.items() }}" + " return {{ k: _to_json_data(v) for k, v in data.items() }}" + )?; + writeln!(out, " return data.to_json_data()")?; + writeln!(out)?; + writeln!(out, "def _parse_rfc3339(s: str) -> datetime:")?; + writeln!(out, " datetime_re = '^(\\d{{4}})-(\\d{{2}})-(\\d{{2}})[tT](\\d{{2}}):(\\d{{2}}):(\\d{{2}})(\\.\\d+)?([zZ]|((\\+|-)(\\d{{2}}):(\\d{{2}})))$'")?; + writeln!(out, " match = re.match(datetime_re, s)")?; + writeln!(out, " if not match:")?; + writeln!( + out, + " raise ValueError('Invalid RFC3339 date/time', s)" + )?; + writeln!(out)?; + writeln!( + out, + " (year, month, day, hour, minute, second, frac_seconds, offset," + )?; + writeln!(out, " *tz) = match.groups()")?; + writeln!(out)?; + writeln!(out, " frac_seconds_parsed = None")?; + writeln!(out, " if frac_seconds:")?; + writeln!( + out, + " frac_seconds_parsed = int(float(frac_seconds) * 1_000_000)" + )?; + writeln!(out, " else:")?; + writeln!(out, " frac_seconds_parsed = 0")?; + writeln!(out)?; + writeln!(out, " tzinfo = None")?; + writeln!(out, " if offset == 'Z':")?; + writeln!(out, " tzinfo = timezone.utc")?; + writeln!(out, " else:")?; + writeln!(out, " hours = int(tz[2])")?; + writeln!(out, " minutes = int(tz[3])")?; + writeln!(out, " sign = 1 if tz[1] == '+' else -1")?; + writeln!(out)?; + writeln!(out, " if minutes not in range(60):")?; + writeln!( + out, + " raise ValueError('minute offset must be in 0..59')" + )?; + writeln!(out)?; + writeln!( + out, + " tzinfo = timezone(timedelta(minutes=sign * (60 * hours + minutes)))" + )?; + writeln!(out)?; + writeln!(out, " second_parsed = int(second)")?; + writeln!(out, " if second_parsed == 60:")?; + writeln!(out, " second_parsed = 59")?; + writeln!(out)?; + writeln!( + out, + " return datetime(int(year), int(month), int(day), int(hour), int(minute)," + )?; + writeln!( + out, + " second_parsed, frac_seconds_parsed, tzinfo) " )?; - writeln!(out, " return data.to_json()")?; - - None - } - target::Item::Postamble => { None } @@ -236,11 +314,11 @@ impl jtd_codegen::target::Target for Target { writeln!(out, " value: '{}'", type_)?; writeln!(out)?; writeln!(out, " @classmethod")?; - writeln!(out, " def from_json(cls, data) -> '{}':", name)?; - writeln!(out, " return cls(_from_json({}, data))", type_)?; + writeln!(out, " def from_json_data(cls, data: Any) -> '{}':", name)?; + writeln!(out, " return cls(_from_json_data({}, data))", type_)?; writeln!(out)?; - writeln!(out, " def to_json(self):")?; - writeln!(out, " return _to_json(self.value)")?; + writeln!(out, " def to_json_data(self) -> Any:")?; + writeln!(out, " return _to_json_data(self.value)")?; None } @@ -272,10 +350,10 @@ impl jtd_codegen::target::Target for Target { )?; } writeln!(out, " @classmethod")?; - writeln!(out, " def from_json(cls, data) -> '{}':", name)?; + writeln!(out, " def from_json_data(cls, data: Any) -> '{}':", name)?; writeln!(out, " return cls(data)")?; writeln!(out)?; - writeln!(out, " def to_json(self):")?; + writeln!(out, " def to_json_data(self) -> Any:")?; writeln!(out, " return self.value")?; None @@ -301,7 +379,7 @@ impl jtd_codegen::target::Target for Target { .imports .entry("typing".into()) .or_default() - .insert("Optional".into()); + .extend(vec!["Dict".into(), "Optional".into()]); writeln!(out)?; writeln!(out, "@dataclass")?; @@ -314,31 +392,31 @@ impl jtd_codegen::target::Target for Target { writeln!(out)?; writeln!(out, " @classmethod")?; - writeln!(out, " def from_json(cls, data) -> '{}':", name)?; + writeln!(out, " def from_json_data(cls, data: Any) -> '{}':", name)?; writeln!(out, " return cls(")?; for field in &fields { writeln!( out, - " _from_json({}, data.get({:?})),", + " _from_json_data({}, data.get({:?})),", field.type_, field.json_name )?; } writeln!(out, " )")?; writeln!(out)?; - writeln!(out, " def to_json(self):")?; - writeln!(out, " data = {{}}")?; + writeln!(out, " def to_json_data(self) -> Any:")?; + writeln!(out, " data: Dict[str, Any] = {{}}")?; for field in &fields { if field.optional { writeln!(out, " if self.{} is not None:", field.name)?; writeln!( out, - " data[{:?}] = _to_json(self.{})", + " data[{:?}] = _to_json_data(self.{})", field.json_name, field.name )?; } else { writeln!( out, - " data[{:?}] = _to_json(self.{})", + " data[{:?}] = _to_json_data(self.{})", field.json_name, field.name )?; } @@ -365,6 +443,12 @@ impl jtd_codegen::target::Target for Target { .or_default() .insert("dataclass".into()); + state + .imports + .entry("typing".into()) + .or_default() + .extend(vec!["Type".into(), "Dict".into()]); + writeln!(out)?; writeln!(out, "@dataclass")?; writeln!(out, "class {}:", name)?; @@ -372,8 +456,8 @@ impl jtd_codegen::target::Target for Target { writeln!(out, " {}: 'str'", tag_field_name)?; writeln!(out)?; writeln!(out, " @classmethod")?; - writeln!(out, " def from_json(cls, data) -> '{}':", name)?; - writeln!(out, " return {{")?; + writeln!(out, " def from_json_data(cls, data: Any) -> '{}':", name)?; + writeln!(out, " variants: Dict[str, Type[{}]] = {{", name)?; for variant in &variants { writeln!( out, @@ -381,9 +465,15 @@ impl jtd_codegen::target::Target for Target { variant.tag_value, variant.type_name )?; } - writeln!(out, " }}[data[{:?}]].from_json(data)", tag_json_name)?; + writeln!(out, " }}")?; + writeln!(out)?; + writeln!( + out, + " return variants[data[{:?}]].from_json_data(data)", + tag_json_name + )?; writeln!(out)?; - writeln!(out, " def to_json(self):")?; + writeln!(out, " def to_json_data(self) -> Any:")?; writeln!(out, " pass")?; None } @@ -418,19 +508,19 @@ impl jtd_codegen::target::Target for Target { writeln!(out)?; writeln!(out, " @classmethod")?; - writeln!(out, " def from_json(cls, data) -> '{}':", name)?; + writeln!(out, " def from_json_data(cls, data: Any) -> '{}':", name)?; writeln!(out, " return cls(")?; writeln!(out, " {:?},", tag_value)?; for field in &fields { writeln!( out, - " _from_json({}, data.get({:?})),", + " _from_json_data({}, data.get({:?})),", field.type_, field.json_name )?; } writeln!(out, " )")?; writeln!(out)?; - writeln!(out, " def to_json(self):")?; + writeln!(out, " def to_json_data(self) -> Any:")?; writeln!( out, " data = {{ {:?}: {:?} }}", @@ -441,13 +531,13 @@ impl jtd_codegen::target::Target for Target { writeln!(out, " if self.{} is not None:", field.name)?; writeln!( out, - " data[{:?}] = _to_json(self.{})", + " data[{:?}] = _to_json_data(self.{})", field.json_name, field.name )?; } else { writeln!( out, - " data[{:?}] = _to_json(self.{})", + " data[{:?}] = _to_json_data(self.{})", field.json_name, field.name )?; } diff --git a/crates/target_ruby/src/lib.rs b/crates/target_ruby/src/lib.rs index 2bee12fd..d5514074 100644 --- a/crates/target_ruby/src/lib.rs +++ b/crates/target_ruby/src/lib.rs @@ -350,7 +350,11 @@ impl jtd_codegen::target::Target for Target { variant.tag_value, variant.type_name )?; } - writeln!(out, " }}[data[{:?}]].from_json_data(data)", tag_json_name)?; + writeln!( + out, + " }}[data[{:?}]].from_json_data(data)", + tag_json_name + )?; writeln!(out, " end")?; writeln!(out, " end")?; diff --git a/crates/target_ruby_sig/src/lib.rs b/crates/target_ruby_sig/src/lib.rs index 48fb4c89..a18d0093 100644 --- a/crates/target_ruby_sig/src/lib.rs +++ b/crates/target_ruby_sig/src/lib.rs @@ -151,18 +151,17 @@ impl jtd_codegen::target::Target for Target { // https://github.com/soutaro/steep/issues/266 writeln!(out)?; - writeln!(out, " def self.from_json_data: (untyped, untyped) -> untyped")?; + writeln!( + out, + " def self.from_json_data: (untyped, untyped) -> untyped" + )?; writeln!(out, " def self.to_json_data: (untyped) -> untyped")?; writeln!(out, "end")?; None } - target::Item::Alias { - name, - type_, - .. - } => { + target::Item::Alias { name, type_, .. } => { writeln!(out)?; writeln!(out, " class {}", name)?; writeln!(out, " attr_accessor value: {}", type_)?; diff --git a/crates/target_rust/src/lib.rs b/crates/target_rust/src/lib.rs index 34f9e8e6..a6851574 100644 --- a/crates/target_rust/src/lib.rs +++ b/crates/target_rust/src/lib.rs @@ -171,9 +171,7 @@ impl jtd_codegen::target::Target for Target { None } - target::Item::Postamble => { - None - } + target::Item::Postamble => None, target::Item::Alias { metadata, diff --git a/crates/target_typescript/src/lib.rs b/crates/target_typescript/src/lib.rs index 39f1fd8b..b17f9579 100644 --- a/crates/target_typescript/src/lib.rs +++ b/crates/target_typescript/src/lib.rs @@ -116,9 +116,7 @@ impl jtd_codegen::target::Target for Target { None } - target::Item::Postamble => { - None - } + target::Item::Postamble => None, target::Item::Alias { metadata, diff --git a/crates/test/schemas/roundtrip_strict/custom_overrides.jtd.json b/crates/test/schemas/roundtrip_strict/custom_overrides.jtd.json index e9e500b2..fe4a260e 100644 --- a/crates/test/schemas/roundtrip_strict/custom_overrides.jtd.json +++ b/crates/test/schemas/roundtrip_strict/custom_overrides.jtd.json @@ -71,7 +71,10 @@ } }, "baz": { - "properties": {} + "properties": {}, + "metadata": { + "pythonType": "object" + } } }, "metadata": {