Skip to content

Commit a75256e

Browse files
authored
Feature/datetime conversion fix (#179)
1 parent 65c4025 commit a75256e

File tree

4 files changed

+86
-15
lines changed

4 files changed

+86
-15
lines changed

poetry.lock

Lines changed: 27 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ opentelemetry-api = { version = "^1.11.1", optional = true }
3030
opentelemetry-sdk = { version = "^1.11.1", optional = true }
3131
protobuf = "~4.21"
3232
python = "^3.7"
33+
python-dateutil = { version = "^2.8.2", python = "<3.11" }
3334
types-protobuf = "~3.20"
3435
typing-extensions = "^4.2.0"
3536

@@ -121,7 +122,7 @@ ignore_missing_imports = true
121122
exclude = [
122123
# Ignore generated code
123124
'temporalio/api',
124-
'temporalio/bridge/proto',
125+
'temporalio/bridge/proto'
125126
]
126127

127128
[tool.pydocstyle]

temporalio/converter.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from enum import IntEnum
1515
from typing import (
1616
Any,
17+
Callable,
1718
Dict,
1819
List,
1920
Mapping,
@@ -34,6 +35,9 @@
3435
import temporalio.api.common.v1
3536
import temporalio.common
3637

38+
if sys.version_info < (3, 11):
39+
# Python's datetime.fromisoformat doesn't support certain formats pre-3.11
40+
from dateutil import parser # type: ignore
3741
# StrEnum is available in 3.11+
3842
if sys.version_info >= (3, 11):
3943
from enum import StrEnum
@@ -682,6 +686,19 @@ def encode_search_attribute_values(
682686
return default().payload_converter.to_payloads([safe_vals])[0]
683687

684688

689+
def _get_iso_datetime_parser() -> Callable[[str], datetime]:
690+
"""Isolates system version check and returns relevant datetime passer
691+
692+
Returns:
693+
A callable to parse date strings into datetimes.
694+
"""
695+
if sys.version_info >= (3, 11):
696+
return datetime.fromisoformat # noqa
697+
else:
698+
# Isolate import for py > 3.11, as dependency only installed for < 3.11
699+
return parser.isoparse
700+
701+
685702
def decode_search_attributes(
686703
api: temporalio.api.common.v1.SearchAttributes,
687704
) -> temporalio.common.SearchAttributes:
@@ -702,7 +719,8 @@ def decode_search_attributes(
702719
val = [val]
703720
# Convert each item to datetime if necessary
704721
if v.metadata.get("type") == b"Datetime":
705-
val = [datetime.fromisoformat(v) for v in val]
722+
parser = _get_iso_datetime_parser()
723+
val = [parser(v) for v in val]
706724
ret[k] = val
707725
return ret
708726

tests/test_converter.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import sys
55
from collections import deque
66
from dataclasses import dataclass
7-
from datetime import datetime
7+
from datetime import datetime, timezone
88
from enum import Enum, IntEnum
99
from typing import (
1010
Any,
@@ -174,6 +174,43 @@ def test_encode_search_attribute_values():
174174
temporalio.converter.encode_search_attribute_values(["foo", 123])
175175

176176

177+
def test_decode_search_attributes():
178+
"""Tests decode from protobuf for python types"""
179+
180+
def payload(key, dtype, data, encoding=None):
181+
if encoding is None:
182+
encoding = {"encoding": b"json/plain"}
183+
check = temporalio.api.common.v1.Payload(
184+
data=bytes(data, encoding="utf-8"),
185+
metadata={"type": bytes(dtype, encoding="utf-8"), **encoding},
186+
)
187+
return temporalio.api.common.v1.SearchAttributes(indexed_fields={key: check})
188+
189+
# Check basic keyword parsing works
190+
kw_check = temporalio.converter.decode_search_attributes(
191+
payload("kw", "Keyword", '"test-id"')
192+
)
193+
assert kw_check["kw"][0] == "test-id"
194+
195+
# Ensure original DT functionality works
196+
dt_check = temporalio.converter.decode_search_attributes(
197+
payload("dt", "Datetime", '"2020-01-01T00:00:00"')
198+
)
199+
assert dt_check["dt"][0] == datetime(2020, 1, 1, 0, 0, 0)
200+
201+
# Check timezone aware works as server is using ISO 8601
202+
dttz_check = temporalio.converter.decode_search_attributes(
203+
payload("dt", "Datetime", '"2020-01-01T00:00:00Z"')
204+
)
205+
assert dttz_check["dt"][0] == datetime(2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
206+
207+
# Check timezone aware, hour offset
208+
dttz_check = temporalio.converter.decode_search_attributes(
209+
payload("dt", "Datetime", '"2020-01-01T00:00:00+00:00"')
210+
)
211+
assert dttz_check["dt"][0] == datetime(2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
212+
213+
177214
NewIntType = NewType("NewIntType", int)
178215
MyDataClassAlias = MyDataClass
179216

0 commit comments

Comments
 (0)