diff --git a/src/data_factory_testing_framework/models/_data_factory_element.py b/src/data_factory_testing_framework/models/_data_factory_element.py index b45441f7..15131f22 100644 --- a/src/data_factory_testing_framework/models/_data_factory_element.py +++ b/src/data_factory_testing_framework/models/_data_factory_element.py @@ -1,4 +1,5 @@ import json +from functools import lru_cache from typing import Any from data_factory_testing_framework._expression_runtime.expression_runtime import ExpressionRuntime @@ -10,6 +11,13 @@ from data_factory_testing_framework.state import RunState +@lru_cache(maxsize=None) +def _get_expression_runtime() -> ExpressionRuntime: + # Building an ExpressionRuntime compiles the Lark grammar, which costs more than evaluating + # an expression. It holds no per-evaluation state, so one instance serves every element. + return ExpressionRuntime() + + class DataFactoryElement: expression: str result: DataFactoryObjectType @@ -26,8 +34,7 @@ def __init__(self, expression: str) -> None: def evaluate(self, state: RunState) -> DataFactoryObjectType: """Evaluate the expression.""" try: - expression_runtime = ExpressionRuntime() - self.result = expression_runtime.evaluate(self.expression, state) + self.result = _get_expression_runtime().evaluate(self.expression, state) return self.result except UserError as e: raise e from e diff --git a/tests/functional/datafactory_element/test_evaluate_datafactory_element.py b/tests/functional/datafactory_element/test_evaluate_datafactory_element.py index 75593495..2e5f1e54 100644 --- a/tests/functional/datafactory_element/test_evaluate_datafactory_element.py +++ b/tests/functional/datafactory_element/test_evaluate_datafactory_element.py @@ -1,10 +1,14 @@ +from unittest.mock import patch + import pytest +from data_factory_testing_framework._expression_runtime.expression_runtime import ExpressionRuntime from data_factory_testing_framework.exceptions import ( DataFactoryElementEvaluationError, ParameterNotFoundError, ) from data_factory_testing_framework.exceptions._user_error import UserError -from data_factory_testing_framework.models import DataFactoryElement +from data_factory_testing_framework.models import DataFactoryElement, _data_factory_element +from data_factory_testing_framework.models._data_factory_element import _get_expression_runtime from data_factory_testing_framework.state import PipelineRunState @@ -44,3 +48,19 @@ def test_evaluate_datafactory_element_raises_technical_errors() -> None: # Act with pytest.raises(DataFactoryElementEvaluationError): data_factory_element.evaluate(state) + + +def test_evaluate_datafactory_element_reuses_one_expression_runtime() -> None: + # Arrange + _get_expression_runtime.cache_clear() + state = PipelineRunState() + elements = [DataFactoryElement("@mul(2, 3)"), DataFactoryElement("@add(2, 3)")] + + # Act + with patch.object(_data_factory_element, "ExpressionRuntime", wraps=ExpressionRuntime) as runtime_class: + results = [element.evaluate(state) for element in elements] + results.append(elements[0].evaluate(state)) + + # Assert + assert results == [6, 5, 6] + assert runtime_class.call_count == 1