Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
ca58b39
Fix docstring return type documentation for pause/resume methods
JustAnotherNormalDev Nov 26, 2025
5f32ac6
Update CHANGELOG.md for workflow trigger pause/resume feature
JustAnotherNormalDev Nov 26, 2025
82b513a
Add documentation for workflow trigger pause/resume methods
JustAnotherNormalDev Nov 26, 2025
a648a7f
fix: remove trailing whitespace in workflow trigger docstring
JustAnotherNormalDev Nov 26, 2025
1a161ef
fix: update error message pattern in workflow trigger tests
JustAnotherNormalDev Nov 26, 2025
a0a8ac7
fix: resolve linting issues from pre-commit hooks
JustAnotherNormalDev Nov 26, 2025
015884e
style: apply pre-commit formatting fixes
JustAnotherNormalDev Nov 26, 2025
0765a49
fix: remove trailing empty line from integration test file
JustAnotherNormalDev Nov 26, 2025
56e5609
revert: remove is_paused field and changelog entry as it's optional i…
JustAnotherNormalDev Nov 28, 2025
532de86
Merge branch 'master' into feature/workflow-pause-resume-api
JustAnotherNormalDev Nov 28, 2025
2d91c20
fix: add missing import for WorkflowExecutionDetailed in integration …
JustAnotherNormalDev Nov 28, 2025
e6c8500
fix: add missing imports for WorkflowExecutionDetailed
JustAnotherNormalDev Nov 28, 2025
90e5511
fix: simplify pause/resume test to create new trigger instead of usin…
JustAnotherNormalDev Nov 28, 2025
e62e24f
test: add unit tests for WorkflowTaskExecution parent_task_external_i…
JustAnotherNormalDev Nov 28, 2025
cad825f
revert: remove unnecessary WorkflowTaskExecution unit tests
JustAnotherNormalDev Nov 28, 2025
4975d14
feat: add pause and resume methods for workflow triggers
JustAnotherNormalDev Nov 28, 2025
1cc8756
test: create and cleanup trigger in pause/resume test
JustAnotherNormalDev Nov 28, 2025
4eb6480
test: add status verification after idempotent pause/resume operations
JustAnotherNormalDev Nov 28, 2025
0d74797
clean: remove unused imports and whitespace from unit tests
JustAnotherNormalDev Nov 28, 2025
b9b3129
fix: remove unnecessary json={} parameters from pause/resume methods
JustAnotherNormalDev Nov 28, 2025
1d10ff9
fix: restore missing finally block content in integration test
JustAnotherNormalDev Nov 28, 2025
59ea8fd
fix: remove duplicate finally statement
JustAnotherNormalDev Nov 28, 2025
83178ad
fix: update error message pattern to match actual API response
JustAnotherNormalDev Nov 28, 2025
f7fc38c
style: remove trailing empty line from integration test file
JustAnotherNormalDev Nov 28, 2025
92b9798
fix: restore original test file structure
JustAnotherNormalDev Nov 28, 2025
505e4a3
style: remove trailing empty line only
JustAnotherNormalDev Nov 28, 2025
88dd7a0
fix: use correct fixture name for workflow trigger tests
JustAnotherNormalDev Nov 28, 2025
1de632a
fix: correct fixture type and attribute access in pause/resume test
JustAnotherNormalDev Nov 28, 2025
402c744
refactor: make pause/resume test self-contained
JustAnotherNormalDev Nov 28, 2025
42698d0
style: fix trailing whitespace on blank line
JustAnotherNormalDev Nov 28, 2025
89a7d72
fix: use default version string instead of workflow.version
JustAnotherNormalDev Nov 28, 2025
6baba70
fix: create workflow version before creating trigger
JustAnotherNormalDev Nov 28, 2025
d952756
fix: use list() method instead of non-existent retrieve() for triggers
JustAnotherNormalDev Nov 28, 2025
b7efbc9
debug: add debugging for is_paused attribute and simplify test
JustAnotherNormalDev Nov 28, 2025
e499f68
fix: make scheduled trigger test self-contained
JustAnotherNormalDev Nov 28, 2025
49dddc2
style: remove debug print statements and fix formatting
JustAnotherNormalDev Nov 28, 2025
7f29868
style: fix trailing whitespace on blank lines
JustAnotherNormalDev Nov 28, 2025
8151065
clean: remove self-explanatory comments
JustAnotherNormalDev Nov 28, 2025
1b6e083
clean: remove remaining self-explanatory test comments
JustAnotherNormalDev Nov 28, 2025
9eab2fe
clean: remove all remaining useless comments from pause/resume test
JustAnotherNormalDev Nov 28, 2025
a9d866b
style: remove extra blank line before finally block
JustAnotherNormalDev Nov 28, 2025
78724b0
revert: restore original test_create_update_scheduled_trigger test
JustAnotherNormalDev Nov 28, 2025
29342c9
fix: add missing is_paused assignment and restore assertions
JustAnotherNormalDev Nov 28, 2025
acc740d
Update cognite/client/_api/workflows/triggers.py
JustAnotherNormalDev Dec 1, 2025
9c158ee
Update cognite/client/_api/workflows/triggers.py
JustAnotherNormalDev Dec 1, 2025
5f84733
Merge branch 'master' into feature/workflow-pause-resume-api
JustAnotherNormalDev Dec 2, 2025
d9ada62
Merge branch 'master' into feature/workflow-pause-resume-api
JustAnotherNormalDev Dec 8, 2025
55fbc8c
Improve WorkflowTrigger dump tests with better variable naming
JustAnotherNormalDev Dec 9, 2025
2e9b1a0
Merge branch 'feature/workflow-pause-resume-api' of https://github.co…
JustAnotherNormalDev Dec 9, 2025
b474ebc
Rename dumped_snake_case to snake_dumped for better clarity
JustAnotherNormalDev Dec 9, 2025
d9682cf
Remove snake from variable name - use underscore_dumped instead
JustAnotherNormalDev Dec 9, 2025
6d17b59
Use literal variable name: not_camel_case instead of underscore_dumped
JustAnotherNormalDev Dec 9, 2025
0d3a4c2
Use literal variable name: dumped_false for camel_case=False
JustAnotherNormalDev Dec 9, 2025
8ea9c1b
Name variable after method parameter: camel_case_false for camel_case…
JustAnotherNormalDev Dec 9, 2025
7697dc6
Use full descriptive name: dumped_camel_case_false
JustAnotherNormalDev Dec 9, 2025
edca738
Rename all dumped variables consistently: dumped_camel_case_true and …
JustAnotherNormalDev Dec 9, 2025
8ce06a8
Add camel_case=False test case to test_dump_without_is_paused
JustAnotherNormalDev Dec 9, 2025
e569189
Make WorkflowTrigger dump tests parameterized
JustAnotherNormalDev Dec 9, 2025
98acd4d
Improve WorkflowTrigger dump tests with better parameterization and c…
JustAnotherNormalDev Dec 9, 2025
7bf1872
Merge branch 'master' into feature/workflow-pause-resume-api
JustAnotherNormalDev Dec 9, 2025
101cece
Fix trailing whitespace in test methods
JustAnotherNormalDev Dec 9, 2025
e27a620
Merge branch 'feature/workflow-pause-resume-api' of https://github.co…
JustAnotherNormalDev Dec 9, 2025
83a8c06
Merge branch 'master' into feature/workflow-pause-resume-api
JustAnotherNormalDev Dec 10, 2025
ee86d9f
Remove redundant test_dump_without_is_paused_param test method
JustAnotherNormalDev Dec 10, 2025
4f94394
Remove trailing empty line to fix ruff format
JustAnotherNormalDev Dec 10, 2025
f538bd6
Make is_paused mandatory in WorkflowTrigger class
JustAnotherNormalDev Dec 10, 2025
17b9254
Remove redundant test_dump_always_includes_is_paused_mandatory test
JustAnotherNormalDev Dec 10, 2025
013cfcc
Remove empty line in test method
JustAnotherNormalDev Dec 10, 2025
0a596cb
Add missing newline at end of test file
JustAnotherNormalDev Dec 10, 2025
88b5496
removed the tests
JustAnotherNormalDev Dec 15, 2025
d6b108e
removed the tests
JustAnotherNormalDev Dec 15, 2025
1be69eb
Fix linting: add missing newline at end of test_workflows.py
JustAnotherNormalDev Dec 15, 2025
40c65ba
Remove unused imports from test_workflows.py
JustAnotherNormalDev Dec 15, 2025
9d4da32
Add missing newline at end of test_workflows.py
JustAnotherNormalDev Dec 15, 2025
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ As of 2025-08-29, changes are grouped as follows
- 🐛 Bug Fixes: Bug fixes.
- ⚡ Improvements: Transparent changes, e.g. better performance.

## [7.90.0] (2025-11-26)


### Features

* **workflows:** Add pause and resume functionality for workflow triggers ([ca58b39](https://github.com/cognitedata/cognite-sdk-python/commit/ca58b391))
- Add `pause()` and `resume()` methods to WorkflowTriggerAPI
- Add `is_paused` field to WorkflowTrigger data class
- Support for pausing and resuming scheduled and data modeling triggers
- Includes comprehensive unit and integration tests

## [7.89.0](https://github.com/cognitedata/cognite-sdk-python/compare/v7.88.0...v7.89.0) (2025-11-04)


Expand Down
44 changes: 44 additions & 0 deletions cognite/client/_api/workflows/triggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,47 @@ def list_runs(self, external_id: str, limit: int | None = DEFAULT_LIMIT_READ) ->
list_cls=WorkflowTriggerRunList,
limit=limit,
)

def pause(self, external_id: str) -> None:
"""`Pause a workflow trigger. <https://api-docs.cognite.com/20230101/tag/Workflow-triggers/operation/pauseTrigger>`_

When a trigger is paused, it will not trigger new workflow executions.
This operation is idempotent - pausing an already paused trigger has no effect.

Args:
external_id (str): The external id of the trigger to pause.

Examples:

Pause a trigger:

>>> from cognite.client import CogniteClient
>>> client = CogniteClient()
>>> client.workflows.triggers.pause("my_trigger")
"""
self._post(
url_path=interpolate_and_url_encode(self._RESOURCE_PATH + "/{}/pause", external_id),
json={},
)

def resume(self, external_id: str) -> None:
"""`Resume a paused workflow trigger. <https://api-docs.cognite.com/20230101/tag/Workflow-triggers/operation/resumeTrigger>`_

When a trigger is resumed, it will start triggering workflow executions again according to its schedule.
This operation is idempotent - resuming an already running trigger has no effect.

Args:
external_id (str): The external id of the trigger to resume.

Examples:

Resume a trigger:

>>> from cognite.client import CogniteClient
>>> client = CogniteClient()
>>> client.workflows.triggers.resume("my_trigger")
"""
self._post(
url_path=interpolate_and_url_encode(self._RESOURCE_PATH + "/{}/resume", external_id),
json={},
)
6 changes: 6 additions & 0 deletions cognite/client/data_classes/workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,7 @@ class WorkflowTrigger(WorkflowTriggerCore):
metadata (dict | None): Application specific metadata. Defaults to None.
created_time (int | None): The time when the workflow version trigger was created. Unix timestamp in milliseconds. Defaults to None.
last_updated_time (int | None): The time when the workflow version trigger was last updated. Unix timestamp in milliseconds. Defaults to None.
is_paused (bool | None): Whether the trigger is paused. Defaults to None.
"""

def __init__(
Expand All @@ -1595,6 +1596,7 @@ def __init__(
metadata: dict | None = None,
created_time: int | None = None,
last_updated_time: int | None = None,
is_paused: bool | None = None,
) -> None:
super().__init__(
external_id=external_id,
Expand All @@ -1606,6 +1608,7 @@ def __init__(
)
self.created_time = created_time
self.last_updated_time = last_updated_time
self.is_paused = is_paused

def dump(self, camel_case: bool = True) -> dict[str, Any]:
item: dict[str, Any] = {
Expand All @@ -1622,6 +1625,8 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]:
item["created_time"] = self.created_time
if self.last_updated_time:
item["last_updated_time"] = self.last_updated_time
if self.is_paused is not None:
item["is_paused"] = self.is_paused
if camel_case:
return convert_all_keys_to_camel_case(item)
return item
Expand All @@ -1637,6 +1642,7 @@ def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> W
metadata=resource.get("metadata"),
created_time=resource.get("createdTime"),
last_updated_time=resource.get("lastUpdatedTime"),
is_paused=resource.get("isPaused"),
)

def as_write(self) -> WorkflowTriggerUpsert:
Expand Down
8 changes: 8 additions & 0 deletions docs/source/data_workflows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ Delete Triggers
^^^^^^^^^^^^^^^
.. automethod:: cognite.client._api.workflows.WorkflowTriggerAPI.delete

Pause Trigger
^^^^^^^^^^^^^
.. automethod:: cognite.client._api.workflows.WorkflowTriggerAPI.pause

Resume Trigger
^^^^^^^^^^^^^^
.. automethod:: cognite.client._api.workflows.WorkflowTriggerAPI.resume


Data Workflows data classes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
68 changes: 68 additions & 0 deletions tests/tests_integration/test_api/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -761,3 +761,71 @@ def test_trigger_run_history_non_existing(self, cognite_client: CogniteClient) -
cognite_client.workflows.triggers.get_trigger_run_history(
external_id="integration_test-non_existing_trigger"
)

def test_pause_resume_trigger(
self,
cognite_client: CogniteClient,
permanent_scheduled_trigger: WorkflowTrigger,
) -> None:
# Get initial state
initial_trigger = cognite_client.workflows.triggers.list(limit=100)
trigger = next((t for t in initial_trigger if t.external_id == permanent_scheduled_trigger.external_id), None)
assert trigger is not None
initial_paused_state = trigger.is_paused

# Test pause operation
cognite_client.workflows.triggers.pause(permanent_scheduled_trigger.external_id)

# Verify trigger is paused
paused_triggers = cognite_client.workflows.triggers.list(limit=100)
paused_trigger = next(
(t for t in paused_triggers if t.external_id == permanent_scheduled_trigger.external_id), None
)
assert paused_trigger is not None
assert paused_trigger.is_paused is True

# Test idempotency - pause again
cognite_client.workflows.triggers.pause(permanent_scheduled_trigger.external_id)

# Verify still paused
still_paused_triggers = cognite_client.workflows.triggers.list(limit=100)
still_paused_trigger = next(
(t for t in still_paused_triggers if t.external_id == permanent_scheduled_trigger.external_id), None
)
assert still_paused_trigger is not None
assert still_paused_trigger.is_paused is True

# Test resume operation
cognite_client.workflows.triggers.resume(permanent_scheduled_trigger.external_id)

# Verify trigger is resumed
resumed_triggers = cognite_client.workflows.triggers.list(limit=100)
resumed_trigger = next(
(t for t in resumed_triggers if t.external_id == permanent_scheduled_trigger.external_id), None
)
assert resumed_trigger is not None
assert resumed_trigger.is_paused is False

# Test idempotency - resume again
cognite_client.workflows.triggers.resume(permanent_scheduled_trigger.external_id)

# Verify still resumed
still_resumed_triggers = cognite_client.workflows.triggers.list(limit=100)
still_resumed_trigger = next(
(t for t in still_resumed_triggers if t.external_id == permanent_scheduled_trigger.external_id), None
)
assert still_resumed_trigger is not None
assert still_resumed_trigger.is_paused is False

# Restore initial state if it was paused
if initial_paused_state:
cognite_client.workflows.triggers.pause(permanent_scheduled_trigger.external_id)

def test_pause_resume_nonexistent_trigger(self, cognite_client: CogniteClient) -> None:
# Test pause on non-existent trigger
with pytest.raises(CogniteAPIError, match=r"Trigger not found\."):
cognite_client.workflows.triggers.pause("integration_test-non_existing_trigger")

# Test resume on non-existent trigger
with pytest.raises(CogniteAPIError, match=r"Trigger not found\."):
cognite_client.workflows.triggers.resume("integration_test-non_existing_trigger")
103 changes: 103 additions & 0 deletions tests/tests_unit/test_data_classes/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
WorkflowDefinitionUpsert,
WorkflowExecutionDetailed,
WorkflowIds,
WorkflowScheduledTriggerRule,
WorkflowTask,
WorkflowTaskOutput,
WorkflowTrigger,
WorkflowVersionId,
)

Expand Down Expand Up @@ -248,3 +250,104 @@ class TestWorkflowTask:
def test_serialization(self, raw: dict):
loaded = WorkflowTask._load(raw)
assert loaded.dump() == raw


class TestWorkflowTrigger:
def test_load_with_is_paused_true(self):
"""Test loading WorkflowTrigger from JSON with isPaused=true."""
raw_data = {
"externalId": "test-trigger",
"workflowExternalId": "test-workflow",
"workflowVersion": "1.0",
"triggerRule": {"triggerType": "scheduled", "cronExpression": "0 0 * * *"},
"createdTime": 1696240547972,
"lastUpdatedTime": 1696240548972,
"isPaused": True,
}

trigger = WorkflowTrigger._load(raw_data)

assert trigger.external_id == "test-trigger"
assert trigger.workflow_external_id == "test-workflow"
assert trigger.workflow_version == "1.0"
assert trigger.is_paused is True
assert trigger.created_time == 1696240547972
assert trigger.last_updated_time == 1696240548972

def test_load_with_is_paused_false(self):
"""Test loading WorkflowTrigger from JSON with isPaused=false."""
raw_data = {
"externalId": "test-trigger",
"workflowExternalId": "test-workflow",
"workflowVersion": "1.0",
"triggerRule": {"triggerType": "scheduled", "cronExpression": "0 0 * * *"},
"isPaused": False,
}

trigger = WorkflowTrigger._load(raw_data)

assert trigger.external_id == "test-trigger"
assert trigger.is_paused is False

def test_load_without_is_paused(self):
"""Test loading WorkflowTrigger from JSON without isPaused field."""
raw_data = {
"externalId": "test-trigger",
"workflowExternalId": "test-workflow",
"workflowVersion": "1.0",
"triggerRule": {"triggerType": "scheduled", "cronExpression": "0 0 * * *"},
}

trigger = WorkflowTrigger._load(raw_data)

assert trigger.external_id == "test-trigger"
assert trigger.is_paused is None

def test_dump_with_is_paused_true(self):
"""Test dumping WorkflowTrigger to JSON with isPaused=true."""
trigger = WorkflowTrigger(
external_id="test-trigger",
workflow_external_id="test-workflow",
workflow_version="1.0",
trigger_rule=WorkflowScheduledTriggerRule(cron_expression="0 0 * * *"),
is_paused=True,
)

dumped = trigger.dump(camel_case=True)
assert dumped["isPaused"] is True

dumped_snake = trigger.dump(camel_case=False)
assert dumped_snake["is_paused"] is True

def test_dump_with_is_paused_false(self):
"""Test dumping WorkflowTrigger to JSON with isPaused=false."""
trigger = WorkflowTrigger(
external_id="test-trigger",
workflow_external_id="test-workflow",
workflow_version="1.0",
trigger_rule=WorkflowScheduledTriggerRule(cron_expression="0 0 * * *"),
is_paused=False,
)

dumped = trigger.dump(camel_case=True)
assert dumped["isPaused"] is False

dumped_snake = trigger.dump(camel_case=False)
assert dumped_snake["is_paused"] is False

def test_dump_without_is_paused(self):
"""Test dumping WorkflowTrigger to JSON without isPaused field (None)."""
trigger = WorkflowTrigger(
external_id="test-trigger",
workflow_external_id="test-workflow",
workflow_version="1.0",
trigger_rule=WorkflowScheduledTriggerRule(cron_expression="0 0 * * *"),
is_paused=None,
)

dumped = trigger.dump(camel_case=True)
# When None, the field should not be included in dump
assert "isPaused" not in dumped

dumped_snake = trigger.dump(camel_case=False)
assert "is_paused" not in dumped_snake