Skip to content
Closed
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
4 changes: 3 additions & 1 deletion dotflow/core/execution.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Execution module"""

import re
from collections.abc import Callable
from datetime import datetime
from inspect import getsourcelines
Expand Down Expand Up @@ -74,8 +75,9 @@ def _execution_orderer(
inside_code = getsourcelines(class_instance.__class__)[0]

for callable_name in callable_list:
pattern = re.compile(rf"def {re.escape(callable_name)}\s*\(")
for index, code in enumerate(inside_code):
if code.find(f"def {callable_name}") != -1:
if pattern.search(code):
ordered_list.append((index, callable_name))

ordered_list.sort()
Expand Down
30 changes: 30 additions & 0 deletions tests/core/test_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from tests.mocks import (
ActionStep,
ActionStepExecutionOrderer,
ActionStepPrefixMethods,
ActionStepWithContexts,
ActionStepWithError,
ActionStepWithInitialContext,
Expand Down Expand Up @@ -292,6 +293,35 @@ def test_execution_orderer(self):
expected_value,
)

def test_execution_orderer_prefix_methods(self):
"""_execution_orderer must not match 'run' against a 'def run_all' line."""
controller = Execution(
task=self.task,
workflow_id=self.workflow_id,
previous_context=Context(),
)

class_instance = ActionStepPrefixMethods(task=controller.task).storage
callable_list = [
func
for func in dir(class_instance)
if controller._is_action(class_instance, func)
]

result = controller._execution_orderer(
callable_list=callable_list, class_instance=class_instance
)
result_names = [name for _, name in result]

# Both methods must appear exactly once
self.assertEqual(result_names.count("run"), 1)
self.assertEqual(result_names.count("run_all"), 1)

# 'run' must appear at an earlier line than 'run_all'
line_run = next(idx for idx, name in result if name == "run")
line_run_all = next(idx for idx, name in result if name == "run_all")
self.assertLess(line_run, line_run_all)

def test_valid_objects(self):
valid_objects = [
"",
Expand Down
2 changes: 2 additions & 0 deletions tests/mocks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from tests.mocks.step_class import (
ActionStep,
ActionStepExecutionOrderer,
ActionStepPrefixMethods,
ActionStepWithContexts,
ActionStepWithError,
ActionStepWithInitialContext,
Expand Down Expand Up @@ -37,6 +38,7 @@
"ActionStepWithoutInit",
"SimpleStep",
"ActionStepExecutionOrderer",
"ActionStepPrefixMethods",
"action_step",
"action_step_valid_object",
"action_step_with_initial_context",
Expand Down
13 changes: 13 additions & 0 deletions tests/mocks/step_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,16 @@ def run() -> None:
class SimpleStep:
def run() -> None:
return {"foo": "bar"}


@action
class ActionStepPrefixMethods:
"""Methods where one name is a prefix of another (e.g. run vs run_all)."""

@action
def run() -> None:
return {"result": "run"}

@action
def run_all() -> None:
return {"result": "run_all"}
Loading