Files
hatchet/sdks/python/tests/test_task_default_fallbacks.py
Matt Kaye 94d06a643c [Python] Fix: Task defaults, Patching fixes, Datetime conversions (#1667)
* feat: start wiring up defaults

* feat: add test coverage

* fix: test docs

* feat: expand tests

* fix: rm validators for now

* chore: minor version

* fix: skip prio tests in ci for now

* chore: docs

* debug: attempt to fix prio test by running with a single slot and no concurrency

* fix: python script to apply patches

* chore; gen

* feat: atomic patches

* fix: rm sed-based patches

* fix: use current tz

* fix: ordering
2025-05-06 17:31:36 -04:00

185 lines
5.4 KiB
Python

"""
IMPORTANT
----------
These tests are intended to prevent us from changing defaults in one place and
forgetting to change them in other places, or otherwise breaking the default handling logic.
If you get a failure here, you likely changed the default values for some of the params (below)
in one of the task decorators e.g. `Workflow.task`, etc.
The intention of these tests is to:
1. Ensure that the behavior of falling back to `TaskDefaults` works as expected, which means that
if no value for a certain parameter to one of these decorators is provided, it should fall back to the
value in `TaskDefaults` if one is set.
2. Ensure that the default values set in the rest of the codebase don't change, and are consistent with each other.
If you change the default values for any of these parameters, please update the tests accordingly.
"""
from datetime import timedelta
from typing import Any
import pytest
from hatchet_sdk import Context, DurableContext, EmptyModel, Hatchet, Task, TaskDefaults
def dummy_task(input: EmptyModel, context: Context) -> dict[str, str]:
return {"foo": "bar"}
def dummy_durable_task(input: EmptyModel, context: DurableContext) -> dict[str, str]:
return {"foo": "bar"}
DEFAULT_SCHEDULE_TIMEOUT = timedelta(minutes=5)
DEFAULT_EXECUTION_TIMEOUT = timedelta(seconds=60)
DEFAULT_RETRIES = 0
DEFAULT_BACKOFF_FACTOR = None
DEFAULT_BACKOFF_MAX_SECONDS = None
def task(
hatchet: Hatchet,
is_durable: bool,
task_defaults: TaskDefaults,
**kwargs: Any,
) -> Task[EmptyModel, dict[str, str]]:
workflow = hatchet.workflow(
name="foo",
task_defaults=task_defaults,
)
task_fn = workflow.durable_task if is_durable else workflow.task
return task_fn(**kwargs)(dummy_durable_task if is_durable else dummy_task) # type: ignore
def standalone_task(
hatchet: Hatchet,
is_durable: bool,
**kwargs: Any,
) -> Task[EmptyModel, dict[str, str]]:
task_fn = hatchet.durable_task if is_durable else hatchet.task
return task_fn(**kwargs)(dummy_durable_task if is_durable else task)._task # type: ignore
@pytest.mark.parametrize("is_durable", [False, True])
def test_task_defaults_applied_correctly(hatchet: Hatchet, is_durable: bool) -> None:
schedule_timeout = timedelta(seconds=3)
execution_timeout = timedelta(seconds=1)
retries = 4
backoff_factor = 1
backoff_max_seconds = 5
t = task(
hatchet=hatchet,
is_durable=is_durable,
task_defaults=TaskDefaults(
schedule_timeout=schedule_timeout,
execution_timeout=execution_timeout,
retries=retries,
backoff_factor=backoff_factor,
backoff_max_seconds=backoff_max_seconds,
),
)
assert t.schedule_timeout == schedule_timeout
assert t.execution_timeout == execution_timeout
assert t.retries == retries
assert t.backoff_factor == backoff_factor
assert t.backoff_max_seconds == backoff_max_seconds
@pytest.mark.parametrize(
"is_durable,is_standalone",
[
(False, False),
(True, False),
(False, True),
(True, True),
],
)
def test_fallbacking_ensure_default_unchanged(
hatchet: Hatchet, is_durable: bool, is_standalone: bool
) -> None:
t = task(
hatchet=hatchet,
is_durable=is_durable,
task_defaults=TaskDefaults(),
)
"""If this test fails, it means that you changed the default values for the params to one of the `task` or `durable_task` decorators"""
assert t.schedule_timeout == DEFAULT_SCHEDULE_TIMEOUT
assert t.execution_timeout == DEFAULT_EXECUTION_TIMEOUT
assert t.retries == DEFAULT_RETRIES
assert t.backoff_factor == DEFAULT_BACKOFF_FACTOR
assert t.backoff_max_seconds == DEFAULT_BACKOFF_MAX_SECONDS
@pytest.mark.parametrize(
"is_durable,is_standalone",
[
(False, False),
(True, False),
(False, True),
(True, True),
],
)
def test_defaults_correctly_overridden_by_params_passed_in(
hatchet: Hatchet, is_durable: bool, is_standalone: bool
) -> None:
t = task(
hatchet=hatchet,
is_durable=is_durable,
task_defaults=TaskDefaults(
schedule_timeout=timedelta(seconds=3),
execution_timeout=timedelta(seconds=1),
retries=4,
backoff_factor=1,
backoff_max_seconds=5,
),
schedule_timeout=timedelta(seconds=9),
execution_timeout=timedelta(seconds=2),
retries=6,
backoff_factor=3,
backoff_max_seconds=5,
)
assert t.schedule_timeout == timedelta(seconds=9)
assert t.execution_timeout == timedelta(seconds=2)
assert t.retries == 6
assert t.backoff_factor == 3
assert t.backoff_max_seconds == 5
@pytest.mark.parametrize(
"is_durable,is_standalone",
[
(False, False),
(True, False),
(False, True),
(True, True),
],
)
def test_params_correctly_set_with_no_defaults(
hatchet: Hatchet, is_durable: bool, is_standalone: bool
) -> None:
t = task(
hatchet=hatchet,
is_durable=is_durable,
task_defaults=TaskDefaults(),
schedule_timeout=timedelta(seconds=9),
execution_timeout=timedelta(seconds=2),
retries=6,
backoff_factor=3,
backoff_max_seconds=5,
)
assert t.schedule_timeout == timedelta(seconds=9)
assert t.execution_timeout == timedelta(seconds=2)
assert t.retries == 6
assert t.backoff_factor == 3
assert t.backoff_max_seconds == 5