Skip to content
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
6 changes: 5 additions & 1 deletion azure/durable_functions/tasks/task_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from ..models.history import HistoryEventType, HistoryEvent
from ..constants import DATETIME_STRING_FORMAT
from azure.functions._durable_functions import _deserialize_custom_object
from datetime import datetime

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is the new datetime format we are using. Is there anyway for us to remove the reference to the old datetime format so it isn't accidentally used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually the same datetime format as before, I just needed to explicitly import it now, because I needed to use its datettime.strptime parser.

I'll write some more thoughts on the "two datetime variants" in a comment below.

from typing import List, Optional


Expand Down Expand Up @@ -131,10 +132,13 @@ def find_task_timer_created(state, fire_at):
if fire_at is None:
return None

# We remove the timezone metadata,
# to enable comparisons with timezone-naive datetime objects. This may be dangerous
fire_at = fire_at.replace(tzinfo=None)
tasks = []
for e in state:
if e.event_type == HistoryEventType.TIMER_CREATED and hasattr(e, "FireAt"):
if e.FireAt == fire_at.strftime(DATETIME_STRING_FORMAT):
if datetime.strptime(e.FireAt, DATETIME_STRING_FORMAT) == fire_at:
tasks.append(e)

if len(tasks) == 0:
Expand Down
66 changes: 66 additions & 0 deletions tests/orchestrator/test_create_timer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from tests.test_utils.ContextBuilder import ContextBuilder
from .orchestrator_test_utils \
import get_orchestration_state_result, assert_orchestration_state_equals, assert_valid_schema
from azure.durable_functions.models.actions.CreateTimerAction import CreateTimerAction
from azure.durable_functions.models.OrchestratorState import OrchestratorState
from azure.durable_functions.constants import DATETIME_STRING_FORMAT
from datetime import datetime, timedelta, timezone


def base_expected_state(output=None) -> OrchestratorState:
return OrchestratorState(is_done=False, actions=[], output=output)

def add_timer_fired_events(context_builder: ContextBuilder, id_: int, timestamp: str):
fire_at: str = context_builder.add_timer_created_event(id_, timestamp)
context_builder.add_orchestrator_completed_event()
context_builder.add_orchestrator_started_event()
context_builder.add_timer_fired_event(id_=id_, fire_at=fire_at)

def generator_function(context):

# Create a timezone aware datetime object, just like a normal
# call to `context.current_utc_datetime` would create
timestamp = "2020-07-23T21:56:54.936700Z"
fire_at = datetime.strptime(timestamp, DATETIME_STRING_FORMAT)
fire_at = fire_at.replace(tzinfo=timezone.utc)

yield context.create_timer(fire_at)
return "Done!"

def add_timer_action(state: OrchestratorState, fire_at: datetime):
action = CreateTimerAction(fire_at= fire_at)
state._actions.append([action]) # Todo: brackets?

def test_timers_comparison_with_relaxed_precision():
"""Test if that two `datetime` different but equivalent
serializations of timer deadlines are found to be equivalent.

The Durable Extension may sometimes drop redundant zeroes on
a datetime object. For instance, the date
2020-07-23T21:56:54.936700Z
may get transformed into
2020-07-23T21:56:54.9367Z
This test ensures that dropping redundant zeroes does not affect
our ability to recognize that a timer has been fired.
"""

# equivalent to 2020-07-23T21:56:54.936700Z
relaxed_timestamp = "2020-07-23T21:56:54.9367Z"
fire_at = datetime.strptime(relaxed_timestamp, DATETIME_STRING_FORMAT)

context_builder = ContextBuilder("relaxed precision")
add_timer_fired_events(context_builder, 0, relaxed_timestamp)

result = get_orchestration_state_result(
context_builder, generator_function)

expected_state = base_expected_state(output='Done!')
add_timer_action(expected_state, fire_at)

expected_state._is_done = True
expected = expected_state.to_json()

#assert_valid_schema(result)
# TODO: getting the following error when validating the schema
# "Additional properties are not allowed ('fireAt', 'isCanceled' were unexpected)">
assert_orchestration_state_equals(expected, result)
4 changes: 3 additions & 1 deletion tests/test_utils/ContextBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,10 @@ def add_task_failed_event(self, id_: int, reason: str, details: str):
event.TaskScheduledId = id_
self.history_events.append(event)

def add_timer_created_event(self, id_: int):
def add_timer_created_event(self, id_: int, timestamp: str = None):
fire_at = self.current_datetime.strftime(DATETIME_STRING_FORMAT)
if timestamp is not None:
fire_at = timestamp
event = self.get_base_event(HistoryEventType.TIMER_CREATED, id_=id_)
event.FireAt = fire_at
self.history_events.append(event)
Expand Down