Skip to content

Python: add datetime support + pass mypy --strict + rename JSON-related methods #14

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions crates/target_csharp_system_text/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,7 @@ impl jtd_codegen::target::Target for Target {
None
}

target::Item::Postamble => {
None
}
target::Item::Postamble => None,

target::Item::Alias {
metadata,
Expand Down
4 changes: 1 addition & 3 deletions crates/target_go/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,7 @@ impl jtd_codegen::target::Target for Target {
None
}

target::Item::Postamble => {
None
}
target::Item::Postamble => None,

target::Item::Alias {
metadata,
Expand Down
4 changes: 1 addition & 3 deletions crates/target_java_jackson/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,7 @@ impl jtd_codegen::target::Target for Target {
None
}

target::Item::Postamble => {
None
}
target::Item::Postamble => None,

target::Item::Alias {
metadata,
Expand Down
4 changes: 4 additions & 0 deletions crates/target_python/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions crates/target_python/docker/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
108 changes: 76 additions & 32 deletions crates/target_python/output/basic_discriminator/__init__.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,114 @@
# 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
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
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)
78 changes: 60 additions & 18 deletions crates/target_python/output/basic_enum/__init__.py
Original file line number Diff line number Diff line change
@@ -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)
Loading