from typing import Any, Optional
from ._metadata import Metadata
from ._results import Results, ResultsLock
from ._yax import TarfileSerializer
[docs]
class Base:
"""Base class for analysis pipelines.
This class provides a framework for defining and executing analysis pipelines.
Parameters
----------
parameters : Parameters
The parameters for the analysis.
Attributes
----------
results : Results
The current analysis results.
available_steps : list[str]
List all available steps for the analysis.
completed_steps : list[str]
List all completed steps for the analysis.
"""
def __init__(self, parameters: Optional[Any] = None):
with ResultsLock.allow_mutation(): # Unlock during initialization
self._results = Results()
self._completed_steps = set()
self._step_ordering = [] # Hidden attribute to store the order of performed steps
self.parameters = parameters
from ._executor import Executor # Avoid circular import
self._executor = Executor(self)
@property
def results(self) -> Results:
"""Get the current analysis results."""
return self._results
@property
def available_steps(self) -> list[str]:
"""List all available steps for the analysis."""
steps = []
for cls in self.__class__.__mro__:
for name, method in vars(cls).items():
if hasattr(method, "creates") and name not in steps:
steps.append(name)
return steps
@property
def completed_steps(self) -> list[str]:
"""List all completed steps for the analysis."""
return list(self._completed_steps)
[docs]
def get_step_info(self, step_name: str) -> dict:
"""Get information about a specific analysis step."""
method = getattr(self.__class__, step_name)
if not method or not hasattr(method, "creates"):
raise ValueError(f"No such analysis step: '{step_name}'")
return {
"name": step_name,
"creates": method.creates,
"requires": method.requires,
"completed": step_name in self._completed_steps,
}
[docs]
def get_step_results(self, step_name: str) -> Any:
"""Get the results for a specific analysis step."""
if step_name not in self._completed_steps:
raise ValueError(f"Step '{step_name}' has not been completed")
return self._results.get_step_results(step_name)
[docs]
def save(self, filepath: str, force=False):
"""Save the analysis to a file.
If the filepath ends in .yax, saves in yaflux archive format.
Parameters
----------
filepath : str
Path to save the analysis
force : bool, optional
Whether to overwrite existing file, by default False
"""
if filepath.endswith(TarfileSerializer.EXTENSION):
TarfileSerializer.save(filepath, self, force)
else:
TarfileSerializer.save(
f"{filepath}.{TarfileSerializer.EXTENSION}", self, force
)
[docs]
@classmethod
def load(
cls,
filepath: str,
*,
no_results: bool = False,
select: list[str] | str | None = None,
exclude: list[str] | str | None = None,
):
"""Load an analysis object from a file.
Parameters
----------
filepath : str
Path to the analysis file. If it ends in .yax, loads using yaflux archive format.
Otherwise attempts to load as legacy pickle format.
no_results : bool, optional
Only load metadata (yaflux archive format only), by default False
select : Optional[List[str]], optional
Only load specific results (yaflux archive format only), by default None
exclude : Optional[List[str]], optional
Skip specific results (yaflux archive format only), by default None
Returns
-------
Analysis
The loaded analysis object
Raises
------
ValueError
If selective loading is attempted with legacy pickle format
"""
from ._loaders import load
return load(
filepath, cls, no_results=no_results, select=select, exclude=exclude
)
def visualize_dependencies(self, *args, **kwargs):
"""Create a visualization of step dependencies.
This is a stub that will be replaced with the actual visualization
if graphviz is installed. Install with:
```bash
pip install yaflux[viz]
```
Raises:
ImportError: If graphviz is not installed.
"""
raise ImportError(
"graphviz package is required for visualization. "
"Install with: pip install yaflux[viz]"
)
[docs]
def execute(
self,
target_step: Optional[str] = None,
force: bool = False,
panic_on_existing: bool = False,
) -> Any:
"""Execute analysis steps in dependency order up to target_step."""
return self._executor.execute(
target_step=target_step, force=force, panic_on_existing=panic_on_existing
)
[docs]
def execute_all(self, force: bool = False, panic_on_existing: bool = False) -> None:
"""Execute all available steps in the analysis."""
self._executor.execute_all(force=force, panic_on_existing=panic_on_existing)
try:
from ._viz import _check_graphviz, visualize_dependencies
if _check_graphviz():
Base.visualize_dependencies = visualize_dependencies # type: ignore
except ImportError:
pass # Keep the stub method