diff --git a/.gitignore b/.gitignore index 02eac69..d9596bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,14 @@ -### AL ### -#Template for AL projects for Dynamics 365 Business Central -#launch.json folder +__pycache__/ +*.pyc +*.pyo +*.pyd +.pytest_cache/ +.coverage +htmlcov/ +.env +dist/ +build/ +*.egg-info/ .vscode/ -#Cache folder -.alcache/ -#Symbols folder -.alpackages/ -#Snapshots folder -.snapshots/ -#Testing Output folder -.output/ -#Extension App-file -*.app -#Rapid Application Development File -rad.json -#Translation Base-file -*.g.xlf -#License-file -*.flf -#Test results file -TestResults.xml \ No newline at end of file +.idea/ +*.log \ No newline at end of file diff --git a/src/alp/__init__.py b/src/alp/__init__.py new file mode 100644 index 0000000..b41df07 --- /dev/null +++ b/src/alp/__init__.py @@ -0,0 +1,3 @@ +from .iteration_tracker import IterationTracker, IterationStatus + +__all__ = ['IterationTracker', 'IterationStatus'] \ No newline at end of file diff --git a/src/alp/iteration_tracker.py b/src/alp/iteration_tracker.py new file mode 100644 index 0000000..901dd76 --- /dev/null +++ b/src/alp/iteration_tracker.py @@ -0,0 +1,155 @@ +from typing import Dict, Any, Optional +from enum import Enum, auto +import logging + +class IterationStatus(Enum): + """Enumeration representing possible iteration statuses.""" + INITIALIZED = auto() + IN_PROGRESS = auto() + COMPLETED = auto() + TERMINATED = auto() + ERROR = auto() + +class IterationTracker: + """ + A comprehensive iteration tracking mechanism for the Adaptive Learning Process. + + Manages iteration state, progression, and provides detailed tracking capabilities. + + Attributes: + _current_iteration (int): Current iteration number + _max_iterations (Optional[int]): Maximum number of iterations allowed + _status (IterationStatus): Current status of the iteration process + _metadata (Dict[str, Any]): Additional metadata for tracking + """ + + def __init__(self, max_iterations: Optional[int] = None): + """ + Initialize the IterationTracker. + + Args: + max_iterations (Optional[int], optional): Maximum number of iterations allowed. + Defaults to None (unlimited iterations). + """ + self._current_iteration = 0 + self._max_iterations = max_iterations + self._status = IterationStatus.INITIALIZED + self._metadata: Dict[str, Any] = {} + self._logger = logging.getLogger(self.__class__.__name__) + + def start(self) -> None: + """ + Start the iteration process, setting the initial state. + + Raises: + RuntimeError: If iterations have already been started. + """ + if self._status != IterationStatus.INITIALIZED: + raise RuntimeError("Iteration process has already been started.") + + self._status = IterationStatus.IN_PROGRESS + self._logger.info("Iteration process started.") + + def next_iteration(self) -> bool: + """ + Advance to the next iteration. + + Returns: + bool: True if iterations can continue, False otherwise. + + Raises: + RuntimeError: If iteration process is not in progress. + """ + if self._status != IterationStatus.IN_PROGRESS: + raise RuntimeError("Iteration process is not in progress.") + + self._current_iteration += 1 + + # Check iteration limit + if (self._max_iterations is not None and + self._current_iteration >= self._max_iterations): + self.complete() + return False + + self._logger.info(f"Starting iteration {self._current_iteration}") + return True + + def complete(self) -> None: + """ + Mark the iteration process as completed.""" + self._status = IterationStatus.COMPLETED + self._logger.info("Iteration process completed successfully.") + + def terminate(self, reason: Optional[str] = None) -> None: + """ + Terminate the iteration process prematurely. + + Args: + reason (Optional[str], optional): Reason for termination. + """ + self._status = IterationStatus.TERMINATED + self._logger.warning(f"Iteration process terminated. Reason: {reason}") + + def error(self, error_details: Optional[str] = None) -> None: + """ + Mark the iteration process as encountering an error. + + Args: + error_details (Optional[str], optional): Details of the error. + """ + self._status = IterationStatus.ERROR + self._logger.error(f"Iteration process encountered an error: {error_details}") + + @property + def current_iteration(self) -> int: + """ + Get the current iteration number. + + Returns: + int: Current iteration number. + """ + return self._current_iteration + + @property + def status(self) -> IterationStatus: + """ + Get the current iteration status. + + Returns: + IterationStatus: Current status of the iteration process. + """ + return self._status + + def add_metadata(self, key: str, value: Any) -> None: + """ + Add metadata to the iteration tracking. + + Args: + key (str): Metadata key + value (Any): Metadata value + """ + self._metadata[key] = value + + def get_metadata(self, key: str, default: Optional[Any] = None) -> Optional[Any]: + """ + Retrieve metadata by key. + + Args: + key (str): Metadata key + default (Optional[Any], optional): Default value if key is not found + + Returns: + Optional[Any]: Metadata value or default + """ + return self._metadata.get(key, default) + + def is_iteration_allowed(self) -> bool: + """ + Check if another iteration is allowed. + + Returns: + bool: True if iteration can continue, False otherwise. + """ + return (self._status == IterationStatus.IN_PROGRESS and + (self._max_iterations is None or + self._current_iteration < self._max_iterations)) \ No newline at end of file diff --git a/tests/test_iteration_tracker.py b/tests/test_iteration_tracker.py new file mode 100644 index 0000000..68d4511 --- /dev/null +++ b/tests/test_iteration_tracker.py @@ -0,0 +1,91 @@ +import pytest +from src.alp.iteration_tracker import IterationTracker, IterationStatus + +def test_iteration_tracker_initialization(): + """Test basic initialization of IterationTracker.""" + tracker = IterationTracker() + assert tracker.current_iteration == 0 + assert tracker.status == IterationStatus.INITIALIZED + +def test_iteration_tracker_start(): + """Test starting the iteration process.""" + tracker = IterationTracker() + tracker.start() + assert tracker.status == IterationStatus.IN_PROGRESS + +def test_iteration_tracker_next_iteration(): + """Test advancing to next iteration.""" + tracker = IterationTracker() + tracker.start() + assert tracker.next_iteration() is True + assert tracker.current_iteration == 1 + +def test_iteration_tracker_max_iterations(): + """Test iteration limit.""" + tracker = IterationTracker(max_iterations=3) + tracker.start() + + # Simulate iterations + for _ in range(3): + assert tracker.next_iteration() is True + + # Fourth iteration should return False + assert tracker.next_iteration() is False + assert tracker.status == IterationStatus.COMPLETED + +def test_iteration_tracker_manual_complete(): + """Test manual completion of iterations.""" + tracker = IterationTracker() + tracker.start() + tracker.complete() + assert tracker.status == IterationStatus.COMPLETED + +def test_iteration_tracker_terminate(): + """Test termination of iterations.""" + tracker = IterationTracker() + tracker.start() + tracker.terminate(reason="Test termination") + assert tracker.status == IterationStatus.TERMINATED + +def test_iteration_tracker_error(): + """Test error handling in iterations.""" + tracker = IterationTracker() + tracker.start() + tracker.error(error_details="Test error") + assert tracker.status == IterationStatus.ERROR + +def test_iteration_tracker_metadata(): + """Test metadata management.""" + tracker = IterationTracker() + tracker.add_metadata("test_key", "test_value") + assert tracker.get_metadata("test_key") == "test_value" + assert tracker.get_metadata("nonexistent_key") is None + +def test_iteration_tracker_is_iteration_allowed(): + """Test iteration allowance checks.""" + tracker = IterationTracker(max_iterations=2) + assert not tracker.is_iteration_allowed() # Not started + + tracker.start() + assert tracker.is_iteration_allowed() + + tracker.next_iteration() + assert tracker.is_iteration_allowed() + + tracker.next_iteration() + assert not tracker.is_iteration_allowed() # Max iterations reached + +def test_iteration_tracker_invalid_state_errors(): + """Test error handling for invalid state transitions.""" + tracker = IterationTracker() + + # Trying to get next iteration before start + with pytest.raises(RuntimeError): + tracker.next_iteration() + + # Start the tracker + tracker.start() + + # Trying to start again + with pytest.raises(RuntimeError): + tracker.start() \ No newline at end of file