Skip to content
This repository was archived by the owner on Aug 19, 2025. It is now read-only.

Commit 5249c8a

Browse files
rbwlovelydinosaur
authored andcommitted
Enable Time and DateTime serialization (#69)
* Enable Time and DateTime serialization * Fix formatting issues * Improve datetime serialization test * Use UTC Z designator in DateTime serialization * Support null in time serialization * Raise AssertionError on unexpected time-type * Allow return type None in BaseFormat.serialize
1 parent 7e8f1a8 commit 5249c8a

2 files changed

Lines changed: 65 additions & 10 deletions

File tree

tests/test_schemas.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66

77
import typesystem
8+
import typesystem.formats
89

910

1011
class Person(typesystem.Schema):
@@ -148,17 +149,54 @@ def test_schema_missing_getattr():
148149
assert tshirt["missing"]
149150

150151

151-
def test_schema_format_serialization():
152+
def test_schema_date_serialization():
152153
class BlogPost(typesystem.Schema):
153154
text = typesystem.String()
154155
created = typesystem.Date()
156+
modified = typesystem.Date(allow_null=True)
155157

156158
post = BlogPost(text="Hi", created=datetime.date.today())
157159

158160
data = dict(post)
159161

160162
assert data["text"] == "Hi"
161163
assert data["created"] == datetime.date.today().isoformat()
164+
assert data["modified"] is None
165+
166+
167+
def test_schema_time_serialization():
168+
class MealSchedule(typesystem.Schema):
169+
guest_id = typesystem.Integer()
170+
breakfast_at = typesystem.Time()
171+
dinner_at = typesystem.Time(allow_null=True)
172+
173+
guest_id = 123
174+
breakfast_at = datetime.time(hour=10, minute=30)
175+
schedule = MealSchedule(guest_id=guest_id, breakfast_at=breakfast_at)
176+
177+
assert typesystem.formats.TIME_REGEX.match(schedule["breakfast_at"])
178+
assert schedule["guest_id"] == guest_id
179+
assert schedule["breakfast_at"] == breakfast_at.isoformat()
180+
assert schedule["dinner_at"] is None
181+
182+
183+
def test_schema_datetime_serialization():
184+
class Guest(typesystem.Schema):
185+
id = typesystem.Integer()
186+
name = typesystem.String()
187+
check_in = typesystem.DateTime()
188+
check_out = typesystem.DateTime(allow_null=True)
189+
190+
guest_id = 123
191+
guest_name = "Bob"
192+
check_in = datetime.datetime.now(tz=datetime.timezone.utc)
193+
guest = Guest(id=guest_id, name=guest_name, check_in=check_in)
194+
195+
assert typesystem.formats.DATETIME_REGEX.match(guest["check_in"])
196+
assert guest["id"] == guest_id
197+
assert guest["name"] == guest_name
198+
assert guest["check_in"] == check_in.isoformat()[:-6] + "Z"
199+
assert guest["check_out"] is None
162200

163201

164202
def test_schema_decimal_serialization():

typesystem/formats.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def is_native_type(self, value: typing.Any) -> bool:
3737
def validate(self, value: typing.Any) -> typing.Union[typing.Any, ValidationError]:
3838
raise NotImplementedError() # pragma: no cover
3939

40-
def serialize(self, obj: typing.Any) -> str:
40+
def serialize(self, obj: typing.Any) -> typing.Union[str, None]:
4141
raise NotImplementedError() # pragma: no cover
4242

4343

@@ -61,7 +61,12 @@ def validate(self, value: typing.Any) -> datetime.date:
6161
except ValueError:
6262
raise self.validation_error("invalid")
6363

64-
def serialize(self, obj: typing.Any) -> str:
64+
def serialize(self, obj: typing.Any) -> typing.Union[str, None]:
65+
if obj is None:
66+
return None
67+
68+
assert isinstance(obj, datetime.date)
69+
6570
return obj.isoformat()
6671

6772

@@ -89,8 +94,13 @@ def validate(self, value: typing.Any) -> datetime.time:
8994
except ValueError:
9095
raise self.validation_error("invalid")
9196

92-
# def serialize(self, obj: typing.Any) -> str:
93-
# return obj.isoformat()
97+
def serialize(self, obj: typing.Any) -> typing.Union[str, None]:
98+
if obj is None:
99+
return None
100+
101+
assert isinstance(obj, datetime.time)
102+
103+
return obj.isoformat()
94104

95105

96106
class DateTimeFormat(BaseFormat):
@@ -130,11 +140,18 @@ def validate(self, value: typing.Any) -> datetime.datetime:
130140
except ValueError:
131141
raise self.validation_error("invalid")
132142

133-
# def serialize(self, obj: typing.Any) -> str:
134-
# value = value.isoformat()
135-
# if value.endswith('+00:00'):
136-
# value = value[:-6] + 'Z'
137-
# return value
143+
def serialize(self, obj: typing.Any) -> typing.Union[str, None]:
144+
if obj is None:
145+
return None
146+
147+
assert isinstance(obj, datetime.datetime)
148+
149+
value = obj.isoformat()
150+
151+
if value.endswith("+00:00"):
152+
value = value[:-6] + "Z"
153+
154+
return value
138155

139156

140157
class UUIDFormat(BaseFormat):

0 commit comments

Comments
 (0)