Skip to content

Commit

Permalink
Feature/datetime conversion fix (#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
Oracen committed Nov 7, 2022
1 parent 65c4025 commit a75256e
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 15 deletions.
39 changes: 27 additions & 12 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Expand Up @@ -30,6 +30,7 @@ opentelemetry-api = { version = "^1.11.1", optional = true }
opentelemetry-sdk = { version = "^1.11.1", optional = true }
protobuf = "~4.21"
python = "^3.7"
python-dateutil = { version = "^2.8.2", python = "<3.11" }
types-protobuf = "~3.20"
typing-extensions = "^4.2.0"

Expand Down Expand Up @@ -121,7 +122,7 @@ ignore_missing_imports = true
exclude = [
# Ignore generated code
'temporalio/api',
'temporalio/bridge/proto',
'temporalio/bridge/proto'
]

[tool.pydocstyle]
Expand Down
20 changes: 19 additions & 1 deletion temporalio/converter.py
Expand Up @@ -14,6 +14,7 @@
from enum import IntEnum
from typing import (
Any,
Callable,
Dict,
List,
Mapping,
Expand All @@ -34,6 +35,9 @@
import temporalio.api.common.v1
import temporalio.common

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


def _get_iso_datetime_parser() -> Callable[[str], datetime]:
"""Isolates system version check and returns relevant datetime passer
Returns:
A callable to parse date strings into datetimes.
"""
if sys.version_info >= (3, 11):
return datetime.fromisoformat # noqa
else:
# Isolate import for py > 3.11, as dependency only installed for < 3.11
return parser.isoparse


def decode_search_attributes(
api: temporalio.api.common.v1.SearchAttributes,
) -> temporalio.common.SearchAttributes:
Expand All @@ -702,7 +719,8 @@ def decode_search_attributes(
val = [val]
# Convert each item to datetime if necessary
if v.metadata.get("type") == b"Datetime":
val = [datetime.fromisoformat(v) for v in val]
parser = _get_iso_datetime_parser()
val = [parser(v) for v in val]
ret[k] = val
return ret

Expand Down
39 changes: 38 additions & 1 deletion tests/test_converter.py
Expand Up @@ -4,7 +4,7 @@
import sys
from collections import deque
from dataclasses import dataclass
from datetime import datetime
from datetime import datetime, timezone
from enum import Enum, IntEnum
from typing import (
Any,
Expand Down Expand Up @@ -174,6 +174,43 @@ def test_encode_search_attribute_values():
temporalio.converter.encode_search_attribute_values(["foo", 123])


def test_decode_search_attributes():
"""Tests decode from protobuf for python types"""

def payload(key, dtype, data, encoding=None):
if encoding is None:
encoding = {"encoding": b"json/plain"}
check = temporalio.api.common.v1.Payload(
data=bytes(data, encoding="utf-8"),
metadata={"type": bytes(dtype, encoding="utf-8"), **encoding},
)
return temporalio.api.common.v1.SearchAttributes(indexed_fields={key: check})

# Check basic keyword parsing works
kw_check = temporalio.converter.decode_search_attributes(
payload("kw", "Keyword", '"test-id"')
)
assert kw_check["kw"][0] == "test-id"

# Ensure original DT functionality works
dt_check = temporalio.converter.decode_search_attributes(
payload("dt", "Datetime", '"2020-01-01T00:00:00"')
)
assert dt_check["dt"][0] == datetime(2020, 1, 1, 0, 0, 0)

# Check timezone aware works as server is using ISO 8601
dttz_check = temporalio.converter.decode_search_attributes(
payload("dt", "Datetime", '"2020-01-01T00:00:00Z"')
)
assert dttz_check["dt"][0] == datetime(2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc)

# Check timezone aware, hour offset
dttz_check = temporalio.converter.decode_search_attributes(
payload("dt", "Datetime", '"2020-01-01T00:00:00+00:00"')
)
assert dttz_check["dt"][0] == datetime(2020, 1, 1, 0, 0, 0, tzinfo=timezone.utc)


NewIntType = NewType("NewIntType", int)
MyDataClassAlias = MyDataClass

Expand Down

0 comments on commit a75256e

Please sign in to comment.