Skip to content

Commit bd6c5bd

Browse files
committed
Add caching for decorator guard lookup and optimize step count calculation
- Introduce `_decorator_guard_cache` in `TestStep` to cache decorator guard lookups, improving performance. - Add `_step_counts` to `OsmoHistory` for incremental step counting, reducing complexity in step statistics.
1 parent 24db71c commit bd6c5bd

2 files changed

Lines changed: 16 additions & 2 deletions

File tree

pyosmo/history/history.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def __init__(self) -> None:
1616
self.test_cases: list[OsmoTestCaseRecord] = []
1717
self.stop_time: datetime | None = None
1818
self.start_time: datetime = datetime.now()
19+
# Incremental step counter — avoids O(tc*s) history walks
20+
self._step_counts: dict[str, int] = {}
1921

2022
def start_new_test(self) -> None:
2123
# Stop test case timer
@@ -36,6 +38,7 @@ def add_step(self, step: TestStep, duration: timedelta, error: Exception | None
3638
if self.current_test_case is None:
3739
raise Exception('There is no active test case!!')
3840
self.current_test_case.add_step(TestStepLog(step, duration, error))
41+
self._step_counts[step.name] = self._step_counts.get(step.name, 0) + 1
3942

4043
def attach(self, name: str, data: str | bytes) -> None:
4144
"""Attach data to the last step of the current test case."""
@@ -74,7 +77,7 @@ def is_used(self, step: TestStep) -> bool:
7477

7578
def get_step_count(self, step: TestStep) -> int:
7679
"""Counts how many times the step is really called during whole history"""
77-
return sum(test_case.get_step_count(step) for test_case in self.test_cases)
80+
return self._step_counts.get(step.name, 0)
7881

7982
@property
8083
def step_stats(self) -> str:

pyosmo/model.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ def __str__(self) -> str:
3434
return f'{type(self.object_instance).__name__}.{self.function_name}()'
3535

3636

37+
_GUARD_NOT_CACHED = object()
38+
39+
3740
class TestStep(ModelFunction):
3841
def __init__(
3942
self,
@@ -47,6 +50,7 @@ def __init__(
4750
super().__init__(function_name, object_instance)
4851
self._step_name = step_name
4952
self._is_decorator_based = is_decorator_based
53+
self._decorator_guard_cache: ModelFunction | None | object = _GUARD_NOT_CACHED
5054

5155
@property
5256
def name(self) -> str:
@@ -114,7 +118,11 @@ def _find_decorator_guard(self) -> Optional['ModelFunction']:
114118
115119
Uses inspect.getmembers() for robust introspection.
116120
Supports both instance methods and static methods.
121+
Result is cached after first lookup since decorator guards are static.
117122
"""
123+
if self._decorator_guard_cache is not _GUARD_NOT_CACHED:
124+
return self._decorator_guard_cache # type: ignore[return-value]
125+
118126
for attr_name, method in inspect.getmembers(self.object_instance, predicate=callable):
119127
# Skip private/protected methods
120128
if attr_name.startswith('_'):
@@ -125,7 +133,10 @@ def _find_decorator_guard(self) -> Optional['ModelFunction']:
125133
and hasattr(method, '_osmo_guard_for')
126134
and method._osmo_guard_for == self.name
127135
):
128-
return ModelFunction(attr_name, self.object_instance)
136+
self._decorator_guard_cache = ModelFunction(attr_name, self.object_instance)
137+
return self._decorator_guard_cache
138+
139+
self._decorator_guard_cache = None
129140
return None
130141

131142
@property

0 commit comments

Comments
 (0)