diff --git a/CHANGELOG.md b/CHANGELOG.md index cc5c9a0..91db238 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,11 +22,23 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - fixed the stale link to the context object documentation - the plugin talks to the deployment through `cmem-client` only - the deprecated `cmem-cmempy` dependency and all `setup_cmempy_user_access` calls are gone + - **breaking:** `setup_cmempy_user_access` also set `OAUTH_GRANT_TYPE` and + `OAUTH_ACCESS_TOKEN` in the environment of the whole process, which task code + calling `cmem.cmempy.*` could rely on without setting up access itself. That side + effect is gone, so such code now has to authenticate on its own - the documented + way is `get_client(context)` ### Fixed - the **Install missing dependencies** action reports the installed version of an already installed package instead of repeating its name +- the **Validate execution phase** action no longer fails with an `AttributeError` + when the execution code assigns `result = None`, which is the documented way of + handing nothing to the next task +- the **Validate execution phase** action starts from the `data` its initialization + code builds, instead of carrying the mutations of earlier action runs +- the documented behavior of a failed dependency installation: it stops the task + before the execution code runs, rather than letting the import fail later ## [1.2.1] 2025-09-18 diff --git a/README.md b/README.md index 05ab9ee..899b382 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Do not use in production! ``` eval $(cmemc -c my-cmem config eval) -task clean check plugin:install +task clean check install ``` 😈 diff --git a/cmem_plugin_python/workflow_task.py b/cmem_plugin_python/workflow_task.py index caa9e38..a9049ba 100644 --- a/cmem_plugin_python/workflow_task.py +++ b/cmem_plugin_python/workflow_task.py @@ -249,8 +249,11 @@ Dependencies are matched by package name only. A package which is already installed is left at the version which is there, and a version specifier is not understood and leads to a fresh installation attempt on every run. -A failed installation does not stop the task: execution continues and the code fails later, at -the import. + +An installation which fails stops the task before the execution code runs at all, because the +deployment rejects the request and the error is not caught. +A misspelled dependency therefore fails the workflow run rather than the import, and the +**Install missing dependencies** action reports the error instead of its per package output. """ @@ -376,9 +379,11 @@ def validate_execute_action(self) -> str: """Run the execute code and report results.""" init_scope = self.do_init() test_inputs = init_scope.get("test_inputs", []) - scope = self.do_execute(test_inputs, None, self.data) - if "result" in scope: - result: Entities = scope["result"] + # the data of the fresh init scope, not self.data: the constructor's dict is mutated by + # every run of the execution code, so re-using it accumulates across action calls + scope = self.do_execute(test_inputs, None, init_scope.get("data", {})) + result: Entities | None = scope.get("result") + if result is not None: output = "# Schema\n" output += "``` python\n" output += f"{result.schema!r}\n" diff --git a/tests/test_workflow_task.py b/tests/test_workflow_task.py index bbea2e3..4b0b3e9 100644 --- a/tests/test_workflow_task.py +++ b/tests/test_workflow_task.py @@ -173,6 +173,36 @@ def test_validate_execute_action() -> None: ) +def test_validate_execute_action_with_none_result() -> None: + """Test Validate execute action with an explicit result of None""" + assert ( + "No result provided" + in PythonCodeWorkflowPlugin( + init_code=PythonCode(""), + execute_code=PythonCode("result = None"), + ).validate_execute_action() + ) + + +def test_validate_execute_action_resets_data() -> None: + """Test that the action starts from the data of a fresh initialization run""" + counting_code = PythonCode( + """from cmem_plugin_base.dataintegration import entity +data["count"] += 1 +result = entity.Entities( + entities=[entity.Entity(uri="urn:x-example:run", values=[[str(data["count"])]])], + schema=entity.EntitySchema( + type_uri="urn:x-example:count", paths=[entity.EntityPath("count")] + ), +)""" + ) + plugin = PythonCodeWorkflowPlugin( + init_code=PythonCode('data["count"] = 0'), execute_code=counting_code + ) + for _ in range(3): + assert "['1']" in plugin.validate_execute_action() + + def test_validate_execute_action_fail() -> None: """Test Validate execute action fails""" with pytest.raises(SyntaxError, match=r"'\[' was never closed"):