Coverage for src/shephex/executor/executor.py: 98%
48 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-06-20 14:13 +0200
« prev ^ index » next coverage.py v7.6.1, created at 2025-06-20 14:13 +0200
1"""
2Executor base class.
3"""
4from abc import ABC, abstractmethod
5from pathlib import Path
6from typing import List, Optional, Sequence, Union
8from shephex.decorators import disable_decorators
9from shephex.experiment import DryResult, Experiment, ExperimentResult
10from shephex.experiment.chain_iterator import ChainableExperimentIterator
11from shephex.experiment.status import Pending, Status
14class Executor(ABC):
15 """
16 Executor base class.
17 """
18 def __init__(self) -> None:
19 pass
21 def execute(
22 self,
23 experiments: Union[Experiment, Sequence[Experiment]],
24 dry: bool = False,
25 execution_directory: Optional[Union[Path, str]] = None,
26 valid_statuses: Optional[Sequence[Status]] = None,
27 ) -> Union[ExperimentResult, List[ExperimentResult]]:
28 """
29 Execute a set of experiments.
31 Parameters
32 -----------
33 experiments: Experiment or Sequence[Experiment]
34 The experiments to be executed.
35 dry: bool
36 If True, the experiments will not be executed, only information about
37 them will be printed.
38 """
39 if isinstance(experiments, Experiment):
40 experiments = [experiments]
41 elif isinstance(experiments, ChainableExperimentIterator):
42 experiments = list(experiments)
44 if valid_statuses is None:
45 valid_statuses = [Pending()]
47 valid_experiments = []
48 for experiment in experiments:
49 if experiment.status in valid_statuses:
50 valid_experiments.append(experiment)
51 else:
52 print(f"Experiment {experiment.identifier} has status {experiment.status}, skipping.")
54 for experiment in valid_experiments:
55 if not experiment.shephex_directory.exists():
56 experiment.dump()
58 results = self._sequence_execute(
59 valid_experiments, dry=dry, execution_directory=execution_directory
60 )
62 return results
64 def _sequence_execute(
65 self,
66 experiments: Sequence[Experiment],
67 dry: bool = False,
68 execution_directory: Optional[Union[Path, str]] = None,
69 ) -> Sequence[ExperimentResult]:
70 results = []
71 for experiment in experiments:
72 result = self._execute(
73 experiment, dry=dry, execution_directory=execution_directory
74 )
75 results.append(result)
76 return results
78 @abstractmethod
79 def _single_execute(
80 self,
81 experiment: Experiment,
82 dry: bool = False,
83 execution_directory: Optional[Union[Path, str]] = None,
84 ) -> ExperimentResult:
85 raise NotImplementedError # pragma: no cover
87 def _execute(
88 self,
89 experiment: Experiment,
90 dry: bool = False,
91 execution_directory: Optional[Union[Path, str]] = None,
92 ) -> ExperimentResult:
93 result = self._single_execute(
94 experiment, dry=dry, execution_directory=execution_directory
95 )
96 return result
99class LocalExecutor(Executor):
100 """
101 Executor that runs the experiment locally, in the current process (or subprocess),
102 without any parallelization.
103 """
105 def __init__(self) -> None:
106 super().__init__()
108 def _single_execute(
109 self,
110 experiment: Experiment,
111 dry: bool = False,
112 execution_directory: Optional[Union[Path, str]] = None,
113 ) -> ExperimentResult:
115 if dry:
116 print(f'Experiment {experiment.identifier} to be executed locally.')
117 return DryResult()
119 with disable_decorators():
120 result = experiment._execute(execution_directory=execution_directory)
122 return result