13from functools
import partial
14from collections
import defaultdict
20from pathlib
import Path
24from basf2
import create_path
25from basf2
import B2DEBUG, B2ERROR, B2INFO, B2WARNING
26from basf2
import conditions
as b2conditions
29from ROOT
import Belle2
30from ROOT.Belle2
import CalibrationAlgorithm
32from caf.utils
import create_directories
33from caf.utils
import method_dispatch
34from caf.utils
import iov_from_runs
35from caf.utils
import IoV_Result
36from caf.utils
import get_iov_from_file
37from caf.backends
import Job
38from caf.runners
import AlgorithmsRunner
43 Basic State object that can take enter and exit state methods and records
44 the state of a machine.
46 You should assign the self.on_enter or self.on_exit attributes to callback functions
47 or lists of them, if you need them.
50 def __init__(self, name, enter=None, exit=None):
52 Initialise State with a name and optional lists of callbacks.
64 Runs callbacks when a state is entered.
80 Runs callbacks when a state is exited.
96 Adds callback to a property.
98 if callable(callback):
99 attribute.append(callback)
101 B2ERROR(f
"Something other than a function (callable) passed into State {self.name}.")
106 @_add_callbacks.register(tuple)
107 @_add_callbacks.register(list)
108 def _(self, callbacks, attribute):
110 Alternate method for lists and tuples of function objects.
113 for function
in callbacks:
114 if callable(function):
115 attribute.append(function)
117 B2ERROR(f
"Something other than a function (callable) passed into State {self.name}.")
128 return f
"State(name={self.name})"
133 if isinstance(other, str):
134 return self.
name == other
136 return self.
name == other.name
141 return hash(self.
name)
147 states (list[str]): A list of possible states of the machine.
150 Base class for a final state machine wrapper.
151 Implements the framework that a more complex machine can inherit from.
153 The `transitions` attribute is a dictionary of trigger name keys, each value of
154 which is another dictionary of 'source' states, 'dest' states, and 'conditions'
155 methods. 'conditions' should be a list of callables or a single one. A transition is
156 valid if it goes from an allowed state to an allowed state.
157 Conditions are optional but must be a callable that returns True or False based
158 on some state of the machine. They cannot have input arguments currently.
160 Every condition/before/after callback function MUST take ``**kwargs`` as the only
161 argument (except ``self`` if it's a class method). This is because it's basically
162 impossible to determine which arguments to pass to which functions for a transition.
163 Therefore this machine just enforces that every function should simply take ``**kwargs``
164 and use the dictionary of arguments (even if it doesn't need any arguments).
166 This also means that if you call a trigger with arguments e.g. ``machine.walk(speed=5)``
167 you MUST use the keyword arguments rather than positional ones. So ``machine.walk(5)``
171 def __init__(self, states=None, initial_state="default_initial"):
173 Basic Setup of states and initial_state.
180 if initial_state !=
"default_initial":
195 Adds a single state to the list of possible ones.
196 Should be a unique string or a State object with a unique name.
198 if isinstance(state, str):
200 elif isinstance(state, State):
201 if state.name
not in self.
states.keys():
202 self.
states[state.name] = state
204 B2WARNING(f
"You asked to add a state {state} but it was already in the machine states.")
206 B2WARNING(f
"You asked to add a state {state} but it wasn't a State or str object")
211 The initial state of the machine. Needs a special property to prevent trying to run on_enter callbacks when set.
215 @initial_state.setter
219 if state
in self.
states.keys():
224 raise KeyError(f
"Attempted to set state to '{state}' which is not in the 'states' attribute!")
229 The current state of the machine. Actually a `property` decorator. It will call the exit method of the
230 current state and enter method of the new one. To get around the behaviour e.g. for setting initial states,
231 either use the `initial_state` property or directly set the _state attribute itself (at your own risk!).
239 if isinstance(state, str):
242 state_name = state.name
245 state = self.
states[state_name]
247 for callback
in self.
state.on_exit:
248 callback(prior_state=self.
state, new_state=state)
250 for callback
in state.on_enter:
251 callback(prior_state=self.
state, new_state=state)
255 raise MachineError(f
"Attempted to set state to '{state}' which not in the 'states' attribute!")
260 Method to always return True.
264 def add_transition(self, trigger, source, dest, conditions=None, before=None, after=None):
266 Adds a single transition to the dictionary of possible ones.
267 Trigger is the method name that begins the transition between the
268 source state and the destination state.
270 The condition is an optional function that returns True or False
271 depending on the current state/input.
275 source = self.
states[source]
277 transition_dict[
"source"] = source
278 transition_dict[
"dest"] = dest
279 except KeyError
as err:
280 B2WARNING(
"Tried to add a transition where the source or dest isn't in the list of states")
283 if isinstance(conditions, (list, tuple, set)):
284 transition_dict[
"conditions"] = list(conditions)
286 transition_dict[
"conditions"] = [conditions]
288 transition_dict[
"conditions"] = [Machine.default_condition]
292 if isinstance(before, (list, tuple, set)):
293 transition_dict[
"before"] = list(before)
295 transition_dict[
"before"] = [before]
299 if isinstance(after, (list, tuple, set)):
300 transition_dict[
"after"] = list(after)
302 transition_dict[
"after"] = [after]
308 Allows us to create a new method for each trigger on the fly.
309 If there is no trigger name in the machine to match, then the normal
310 AttributeError is called.
313 if name
not in possible_transitions:
314 raise AttributeError(f
"{name} does not exist in transitions for state {self.state}.")
317 return partial(self.
_trigger, name, transition_dict, **kwargs)
320 def _trigger(self, transition_name, transition_dict, **kwargs):
322 Runs the transition logic. Callbacks are evaluated in the order:
323 conditions -> before -> <new state set here> -> after.
325 dest, conditions, before_callbacks, after_callbacks = (
326 transition_dict[
"dest"],
327 transition_dict[
"conditions"],
328 transition_dict[
"before"],
329 transition_dict[
"after"]
332 if all(
map(
lambda condition: self.
_callback(condition, **kwargs), conditions)):
333 for before_func
in before_callbacks:
337 for after_func
in after_callbacks:
340 raise ConditionError(f
"Transition '{transition_name}' called for but one or more conditions "
346 Calls a condition/before/after.. function using arguments passed (or not).
348 return func(**kwargs)
352 Returns allowed transitions from a given state.
354 possible_transitions = []
355 for transition, transition_dicts
in self.
transitions.items():
356 for transition_dict
in transition_dicts:
357 if transition_dict[
"source"] == source:
358 possible_transitions.append(transition)
359 return possible_transitions
363 Returns the transition dictionary for a state and transition out of it.
366 for transition_dict
in transition_dicts:
367 if transition_dict[
"source"] == state:
368 return transition_dict
370 raise KeyError(f
"No transition from state {state} with the name {transition}.")
374 Does a simple dot file creation to visualise states and transiitons.
376 with open(filename,
"w")
as dotfile:
377 dotfile.write(
"digraph " + graphname +
" {\n")
378 for state
in self.
states.keys():
379 dotfile.write(
'"' + state +
'" [shape=ellipse, color=black]\n')
380 for trigger, transition_dicts
in self.
transitions.items():
381 for transition
in transition_dicts:
382 dotfile.write(
'"' + transition[
"source"].name +
'" -> "' +
383 transition[
"dest"].name +
'" [label="' + trigger +
'"]\n')
389 A state machine to handle `Calibration` objects and the flow of
394 collector_input_dir =
'collector_input'
396 collector_output_dir =
'collector_output'
398 algorithm_output_dir =
'algorithm_output'
400 def __init__(self, calibration, iov_to_calibrate=None, initial_state="init", iteration=0):
402 Takes a Calibration object from the caf framework and lets you
403 set the initial state.
444 self.
root_dir = Path(os.getcwd(), calibration.name)
454 self.
add_transition(
"submit_collector",
"init",
"running_collector",
462 self.
add_transition(
"fail",
"running_collector",
"collector_failed",
464 self.
add_transition(
"complete",
"running_collector",
"collector_completed",
466 self.
add_transition(
"run_algorithms",
"collector_completed",
"running_algorithms",
470 self.
add_transition(
"complete",
"running_algorithms",
"algorithms_completed",
473 self.
add_transition(
"fail",
"running_algorithms",
"algorithms_failed",
479 self.
add_transition(
"finish",
"algorithms_completed",
"completed",
486 """update calibration state"""
491 Lookup function that returns all files from the file_paths that
492 overlap with this IoV.
495 overlapping_files = set()
497 for file_path, file_iov
in files_to_iovs.items():
498 if file_iov.overlaps(iov)
and (file_path
in file_paths):
499 overlapping_files.add(file_path)
500 return overlapping_files
504 Dumps the `Job` object for the collections to JSON files so that it's configuration can be recovered
505 later in case of failure.
510 B2DEBUG(29,
"Some Collector Jobs still in 'init' state. Waiting...")
514 collector_job_output_file_name = self.
calibration.collections[collection_name].job_config
516 collection_name, collector_job_output_file_name)
517 job.dump_to_json(output_file)
521 Recovers the `Job` object for the collector from a JSON file in the event that we are starting from a reset.
523 for collection_name, collection
in self.
calibration.collections.items():
527 collection.job_config)
534 B2DEBUG(20, f
"Overall IoV {self.iov_to_calibrate} requested for calibration: {self.calibration.name}.")
537 B2DEBUG(20, f
"No overall IoV requested for calibration: {self.calibration.name}.")
547 Build IoV file dictionary for each collection if required.
549 iov_requested = self._iov_requested()
550 if iov_requested
or self.calibration.ignored_runs:
551 for coll_name, collection
in self.calibration.collections.items():
552 if not collection.files_to_iovs:
553 B2INFO(
"Creating IoV dictionaries to map files to (Exp,Run) ranges for"
554 f
" Calibration '{self.calibration.name} and Collection '{coll_name}'."
555 " Filling dictionary from input file metadata."
556 " If this is slow, set the 'files_to_iovs' attribute of each Collection before running.")
559 for file_path
in collection.input_files:
560 files_to_iovs[file_path] = get_iov_from_file(file_path)
561 collection.files_to_iovs = files_to_iovs
563 B2INFO(
"Using File to IoV mapping from 'files_to_iovs' attribute for "
564 f
"Calibration '{self.calibration.name}' and Collection '{coll_name}'.")
566 B2INFO(
"No File to IoV mapping required.")
581 Did all the collections succeed?
583 B2DEBUG(29,
"Checking for failed collector job.")
585 return all([job.status ==
"completed" for job
in self.
_collector_jobs.values()])
589 Did any of the collections fail?
591 B2DEBUG(29,
"Checking for failed collector job.")
593 return any([job.status ==
"failed" for job
in self.
_collector_jobs.values()])
598 bool: If AlgorithmsRunner succeeded return True.
605 bool: If AlgorithmsRunner failed return True.
616 if since_last_update > self.
calibration.collector_full_update_interval:
617 B2INFO(
"Updating full collector job statuses.")
622 num_completed = sum((subjob.status
in subjob.exit_statuses)
for subjob
in job.subjobs.values())
623 total_subjobs = len(job.subjobs)
624 B2INFO(f
"{num_completed}/{total_subjobs} Collector SubJobs finished in"
625 f
" Calibration {self.calibration.name} Job {job.name}.")
641 B2INFO(f
"Reached maximum number of iterations ({self.calibration.max_iterations}), will complete now.")
649 iteration_called =
False
651 for result
in results:
652 if result.result == CalibrationAlgorithm.c_Iterate:
653 iteration_called =
True
657 return iteration_called
662 B2INFO(f
"Calibration Machine {self.calibration.name} moved to state {kwargs['new_state'].name}.")
666 Condition function to check that the dependencies of our calibration are in the 'completed' state.
667 Technically only need to check explicit dependencies.
670 if not calibration.state == calibration.end_state:
677 Automatically try all transitions out of this state once. Tries fail last.
680 for transition
in possible_transitions:
682 if transition !=
"fail":
683 getattr(self, transition)()
685 except ConditionError:
688 if "fail" in possible_transitions:
689 getattr(self,
"fail")()
691 raise MachineError(f
"Failed to automatically transition out of {self.state} state.")
695 Creates the overall root directory of the Calibration. Will not overwrite if it already exists.
698 create_directories(self.
root_dir, overwrite=
False)
702 Creates a basf2 path for the correct collector and serializes it in the
703 self.output_dir/<calibration_name>/<iteration>/paths directory
707 create_directories(path_output_dir)
708 path_file_name = collection.collector.name() +
'.path'
709 path_file_name = path_output_dir / path_file_name
711 coll_path = create_path()
712 coll_path.add_module(collection.collector)
714 with open(path_file_name,
'bw')
as serialized_path_file:
715 pickle.dump(serialize_path(coll_path), serialized_path_file)
717 return str(path_file_name.absolute())
721 Creates a basf2 path for the collectors setup path (Collection.pre_collector_path) and serializes it in the
722 self.output_dir/<calibration_name>/<iteration>/<colector_output>/<name> directory.
725 coll_path = collection.pre_collector_path
726 path_file_name =
'pre_collector.path'
727 path_file_name = os.path.join(path_output_dir, path_file_name)
729 with open(path_file_name,
'bw')
as serialized_path_file:
730 pickle.dump(serialize_path(coll_path), serialized_path_file)
732 return path_file_name
736 Creates a Job object for the collections of this iteration, ready for submission
739 for collection_name, collection
in self.
calibration.collections.items():
745 if job.output_dir.exists():
746 B2INFO(f
"Previous output directory for {self.calibration.name} collector {collection_name} exists."
747 f
"Deleting {job.output_dir} before re-submitting.")
748 shutil.rmtree(job.output_dir)
749 job.cmd = collection.job_cmd
750 job.append_current_basf2_setup_cmds()
751 job.input_sandbox_files.append(collection.job_script)
753 job.input_sandbox_files.append(collector_path_file)
754 if collection.pre_collector_path:
756 job.input_sandbox_files.append(pre_collector_path_file)
759 list_dependent_databases = []
764 database_dir = os.path.join(os.getcwd(), dependency.name,
'outputdb')
765 B2INFO(f
"Adding local database from {dependency.name} for use by {self.calibration.name}.")
766 list_dependent_databases.append((os.path.join(database_dir,
'database.txt'), database_dir))
771 database_dir = os.path.join(previous_iteration_dir, self.
calibration.alg_output_dir,
'outputdb')
772 list_dependent_databases.append((os.path.join(database_dir,
'database.txt'), database_dir))
773 B2INFO(f
"Adding local database from previous iteration of {self.calibration.name}.")
784 for database
in collection.database_chain:
785 if database.db_type ==
'local':
786 json_db_chain.append((
'local', (database.filepath.as_posix(), database.payload_dir.as_posix())))
787 elif database.db_type ==
'central':
788 json_db_chain.append((
'central', database.global_tag))
790 raise ValueError(f
"Unknown database type {database.db_type}.")
792 for database
in list_dependent_databases:
793 json_db_chain.append((
'local', database))
794 job_config[
'database_chain'] = json_db_chain
796 job_config_file_path = input_data_directory.joinpath(
'collector_config.json').absolute()
797 with open(job_config_file_path,
'w')
as job_config_file:
798 json.dump(job_config, job_config_file, indent=2)
799 job.input_sandbox_files.append(job_config_file_path)
802 input_data_files = set(collection.input_files)
808 collection.files_to_iovs,
811 files_to_ignore = set()
813 for input_file
in input_data_files:
814 file_iov = self.
calibration.files_to_iovs[input_file]
815 if file_iov == exprun.make_iov():
816 B2INFO(f
"You have asked for {exprun} to be ignored for Calibration '{self.calibration.name}'. "
817 f
"Therefore the input file '{input_file}' from Collection '{collection_name}' "
818 "is being removed from input files list.")
819 files_to_ignore.add(input_file)
820 input_data_files.difference_update(files_to_ignore)
822 if not input_data_files:
823 raise MachineError(f
"No valid input files for Calibration '{self.calibration.name}' "
824 f
" and Collection '{collection_name}'.")
825 job.input_files = list(input_data_files)
827 job.splitter = collection.splitter
828 job.backend_args = collection.backend_args
830 job.output_patterns = collection.output_patterns
831 B2DEBUG(20, f
"Collector job for {self.calibration.name}:{collection_name}:\n{job}")
835 """check that collector output is valid"""
836 B2INFO(
"Checking that Collector output exists for all collector jobs "
837 f
"using {self.calibration.name}.output_patterns.")
839 B2INFO(
"We're restarting so we'll recreate the collector Job object.")
845 for pattern
in job.output_patterns:
846 output_files.extend(glob.glob(os.path.join(job.output_dir, pattern)))
848 raise MachineError(
"No output files from Collector Job")
850 for subjob
in job.subjobs.values():
852 for pattern
in subjob.output_patterns:
853 output_files.extend(glob.glob(os.path.join(subjob.output_dir, pattern)))
855 raise MachineError(f
"No output files from Collector {subjob}")
859 Runs the Calibration Algorithms for this calibration machine.
861 Will run them sequentially locally (possible benefits to using a
862 processing pool for low memory algorithms later on.)
866 algs_runner.algorithms = self.
calibration.algorithms
868 output_database_dir = algorithm_output_dir.joinpath(
"outputdb")
870 if algorithm_output_dir.exists():
871 B2INFO(f
"Output directory for {self.calibration.name} already exists from a previous CAF attempt. "
872 f
"Deleting and recreating {algorithm_output_dir}.")
873 create_directories(algorithm_output_dir)
874 B2INFO(f
"Output local database for {self.calibration.name} will be stored at {output_database_dir}.")
875 algs_runner.output_database_dir = output_database_dir
881 for subjob
in job.subjobs.values():
882 for pattern
in subjob.output_patterns:
883 input_files.extend(glob.glob(os.path.join(subjob.output_dir, pattern)))
885 for pattern
in job.output_patterns:
886 input_files.extend(glob.glob(os.path.join(job.output_dir, pattern)))
888 algs_runner.input_files = input_files
891 algs_runner.database_chain = self.
calibration.database_chain
895 list_dependent_databases = []
897 database_dir = os.path.join(os.getcwd(), dependency.name,
'outputdb')
898 B2INFO(f
"Adding local database from {dependency.name} for use by {self.calibration.name}.")
899 list_dependent_databases.append((os.path.join(database_dir,
'database.txt'), database_dir))
904 database_dir = os.path.join(previous_iteration_dir, self.
calibration.alg_output_dir,
'outputdb')
905 list_dependent_databases.append((os.path.join(database_dir,
'database.txt'), database_dir))
906 B2INFO(f
"Adding local database from previous iteration of {self.calibration.name}.")
907 algs_runner.dependent_databases = list_dependent_databases
909 algs_runner.ignored_runs = self.
calibration.ignored_runs
913 except Exception
as err:
923 Take the last iteration's outputdb and copy it to a more easily findable place.
928 final_database_location = self.
root_dir.joinpath(
'outputdb')
929 if final_database_location.exists():
930 B2INFO(f
"Removing previous final output database for {self.calibration.name} before copying new one.")
931 shutil.rmtree(final_database_location)
932 shutil.copytree(database_location, final_database_location)
937 A state machine to handle the logic of running the algorithm on the overall runs contained in the data.
942 required_attrs = [
"algorithm",
943 "dependent_databases",
946 "output_database_dir",
951 required_true_attrs = [
"algorithm",
953 "output_database_dir",
957 def __init__(self, algorithm=None, initial_state="init"):
959 Takes an Algorithm object from the caf framework and defines the transitions.
964 State(
"running_algorithm"),
1003 params (dict): Dictionary containing values to be assigned to the machine's attributes of the same name.
1005 for attribute_name, value
in params.items():
1006 setattr(self, attribute_name, value)
1011 bool: Whether or not this machine has been set up correctly with all its necessary attributes.
1013 B2INFO(f
"Checking validity of current setup of AlgorithmMachine for {self.algorithm.name}.")
1016 if not hasattr(self, attribute_name):
1017 B2ERROR(f
"AlgorithmMachine attribute {attribute_name} doesn't exist.")
1021 if not getattr(self, attribute_name):
1022 B2ERROR(f
"AlgorithmMachine attribute {attribute_name} returned False.")
1028 Create working/output directory of algorithm. Any old directory is overwritten.
1030 create_directories(Path(self.
output_dir), overwrite=
True)
1034 Apply all databases in the correct order.
1038 b2conditions.reset()
1039 b2conditions.override_globaltags()
1044 for database
in database_chain:
1045 if database.db_type ==
'local':
1046 B2INFO(f
"Adding Local Database {database.filepath.as_posix()} to head of chain of local databases, "
1047 f
"for {self.algorithm.name}.")
1048 b2conditions.prepend_testing_payloads(database.filepath.as_posix())
1049 elif database.db_type ==
'central':
1050 B2INFO(f
"Adding Central database tag {database.global_tag} to head of GT chain, "
1051 f
"for {self.algorithm.name}.")
1052 b2conditions.prepend_globaltag(database.global_tag)
1054 raise ValueError(f
"Unknown database type {database.db_type}.")
1059 B2INFO(f
"Adding Local Database {filename} to head of chain of local databases created by"
1060 f
" a dependent calibration, for {self.algorithm.name}.")
1061 b2conditions.prepend_testing_payloads(filename)
1067 B2INFO(f
"Output local database for {self.algorithm.name} stored at {self.output_database_dir}.")
1070 b2conditions.expert_settings(save_payloads=str(self.
output_database_dir.joinpath(
"database.txt")))
1077 B2INFO(f
"Output log file at {log_file}.")
1079 basf2.set_log_level(basf2.LogLevel.INFO)
1080 basf2.log_to_file(log_file)
1085 B2INFO(f
"Changing current working directory to {self.output_dir}.")
1090 Call the user defined algorithm setup function.
1092 B2INFO(
"Running Pre-Algorithm function (if exists)")
1100 Does the actual execute of the algorithm on an IoV and records the result.
1102 B2INFO(f
"Running {self.algorithm.name} in working directory {os.getcwd()}.")
1104 runs_to_execute = kwargs[
"runs"]
1105 iov = kwargs[
"apply_iov"]
1106 iteration = kwargs[
"iteration"]
1108 iov = iov_from_runs(runs_to_execute)
1109 B2INFO(f
"Execution will use {iov} for labelling payloads by default.")
1110 alg_result = self.
algorithm.algorithm.execute(runs_to_execute, iteration, iov._cpp_iov)
1111 self.
result = IoV_Result(iov, alg_result)
1114 """set input data"""
1120 Base exception class for this module.
1124class ConditionError(MachineError):
1126 Exception for when conditions fail during a transition.
1132 Exception for when transitions fail.
list required_true_attrs
Attributes that must have a value that returns True when tested.
str output_database_dir
The output database directory for the localdb that the algorithm will commit to.
list input_files
Collector output files, will contain all files returned by the output patterns.
_setup_database_chain(self, **kwargs)
list dependent_databases
CAF created local databases from previous calibrations that this calibration/algorithm depends on.
_setup_logging(self, **kwargs)
_set_input_data(self, **kwargs)
list database_chain
Assigned database chain to the overall Calibration object, or to the 'default' Collection.
list default_states
Default states for the AlgorithmMachine.
__init__(self, algorithm=None, initial_state="init")
list required_attrs
Required attributes that must exist before the machine can run properly.
algorithm
Algorithm() object whose state we are modelling.
result
IoV_Result object for a single execution, will be reset upon a new execution.
_change_working_dir(self, **kwargs)
_pre_algorithm(self, **kwargs)
_create_output_dir(self, **kwargs)
_execute_over_iov(self, **kwargs)
str output_dir
The algorithm output directory which is mostly used to store the stdout file.
setup_from_dict(self, params)
_check_valid_collector_output(self)
_log_new_state(self, **kwargs)
root_dir
root directory for this Calibration
_below_max_iterations(self)
_make_pre_collector_path(self, name, collection)
automatic_transition(self)
dict _collector_timing
Times of various useful updates to the collector job e.g.
files_containing_iov(self, file_paths, files_to_iovs, iov)
_collection_completed(self)
_recover_collector_jobs(self)
_update_cal_state(self, **kwargs)
_make_collector_path(self, name, collection)
_collector_jobs_ready(self)
_runner_final_state
Final state of the algorithm runner for the current iteration.
list default_states
States that are defaults to the CalibrationMachine (could override later)
_submit_collections(self)
str collector_output_dir
output directory of collector
_create_collector_jobs(self)
iov_to_calibrate
IoV to be executed, currently will loop over all runs in IoV.
dict _collector_jobs
The collector jobs used for submission.
__init__(self, calibration, iov_to_calibrate=None, initial_state="init", iteration=0)
iteration
Which iteration step are we in.
_resolve_file_paths(self)
str collector_input_dir
input directory of collector
_increment_iteration(self)
collector_backend
Backend used for this calibration machine collector.
_no_require_iteration(self)
dict _algorithm_results
Results of each iteration for all algorithms of this calibration.
calibration
Calibration object whose state we are modelling.
dependencies_completed(self)
add_transition(self, trigger, source, dest, conditions=None, before=None, after=None)
_trigger(self, transition_name, transition_dict, **kwargs)
dict states
Valid states for this machine.
save_graph(self, filename, graphname)
__init__(self, states=None, initial_state="default_initial")
transitions
Allowed transitions between states.
_callback(func, **kwargs)
add_state(self, state, enter=None, exit=None)
get_transitions(self, source)
dict _state
Actual attribute holding the Current state.
initial_state
Pointless docstring since it's a property.
default_condition(**kwargs)
dict _initial_state
Actual attribute holding initial state for this machine.
state
Current State of machine.
__getattr__(self, name, **kwargs)
get_transition_dict(self, state, transition)
_add_callbacks(self, callback, attribute)
__init__(self, name, enter=None, exit=None)
on_enter
Callback list when entering state.
list _on_enter
set state as empty list when entering it
on_exit
Callback list when exiting state.
list _on_exit
set state as empty list when exiting it