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}.")
103 @_add_callbacks.register(tuple)
104 @_add_callbacks.register(list)
105 def _(self, callbacks, attribute):
107 Alternate method for lists and tuples of function objects.
110 for function
in callbacks:
111 if callable(function):
112 attribute.append(function)
114 B2ERROR(f
"Something other than a function (callable) passed into State {self.name}.")
124 return f
"State(name={self.name})"
129 if isinstance(other, str):
130 return self.
name == other
132 return self.
name == other.name
137 return hash(self.
name)
143 states (list[str]): A list of possible states of the machine.
146 Base class for a final state machine wrapper.
147 Implements the framework that a more complex machine can inherit from.
149 The `transitions` attribute is a dictionary of trigger name keys, each value of
150 which is another dictionary of 'source' states, 'dest' states, and 'conditions'
151 methods. 'conditions' should be a list of callables or a single one. A transition is
152 valid if it goes from an allowed state to an allowed state.
153 Conditions are optional but must be a callable that returns True or False based
154 on some state of the machine. They cannot have input arguments currently.
156 Every condition/before/after callback function MUST take ``**kwargs`` as the only
157 argument (except ``self`` if it's a class method). This is because it's basically
158 impossible to determine which arguments to pass to which functions for a transition.
159 Therefore this machine just enforces that every function should simply take ``**kwargs``
160 and use the dictionary of arguments (even if it doesn't need any arguments).
162 This also means that if you call a trigger with arguments e.g. ``machine.walk(speed=5)``
163 you MUST use the keyword arguments rather than positional ones. So ``machine.walk(5)``
167 def __init__(self, states=None, initial_state="default_initial"):
169 Basic Setup of states and initial_state.
176 if initial_state !=
"default_initial":
191 Adds a single state to the list of possible ones.
192 Should be a unique string or a State object with a unique name.
194 if isinstance(state, str):
196 elif isinstance(state, State):
197 if state.name
not in self.
states.keys():
198 self.
states[state.name] = state
200 B2WARNING(f
"You asked to add a state {state} but it was already in the machine states.")
202 B2WARNING(f
"You asked to add a state {state} but it wasn't a State or str object")
207 The initial state of the machine. Needs a special property to prevent trying to run on_enter callbacks when set.
211 @initial_state.setter
215 if state
in self.
states.keys():
220 raise KeyError(f
"Attempted to set state to '{state}' which is not in the 'states' attribute!")
225 The current state of the machine. Actually a `property` decorator. It will call the exit method of the
226 current state and enter method of the new one. To get around the behaviour e.g. for setting initial states,
227 either use the `initial_state` property or directly set the _state attribute itself (at your own risk!).
235 if isinstance(state, str):
238 state_name = state.name
241 state = self.
states[state_name]
243 for callback
in self.
state.on_exit:
244 callback(prior_state=self.
state, new_state=state)
246 for callback
in state.on_enter:
247 callback(prior_state=self.
state, new_state=state)
251 raise MachineError(f
"Attempted to set state to '{state}' which not in the 'states' attribute!")
256 Method to always return True.
260 def add_transition(self, trigger, source, dest, conditions=None, before=None, after=None):
262 Adds a single transition to the dictionary of possible ones.
263 Trigger is the method name that begins the transition between the
264 source state and the destination state.
266 The condition is an optional function that returns True or False
267 depending on the current state/input.
271 source = self.
states[source]
273 transition_dict[
"source"] = source
274 transition_dict[
"dest"] = dest
275 except KeyError
as err:
276 B2WARNING(
"Tried to add a transition where the source or dest isn't in the list of states")
279 if isinstance(conditions, (list, tuple, set)):
280 transition_dict[
"conditions"] = list(conditions)
282 transition_dict[
"conditions"] = [conditions]
284 transition_dict[
"conditions"] = [Machine.default_condition]
288 if isinstance(before, (list, tuple, set)):
289 transition_dict[
"before"] = list(before)
291 transition_dict[
"before"] = [before]
295 if isinstance(after, (list, tuple, set)):
296 transition_dict[
"after"] = list(after)
298 transition_dict[
"after"] = [after]
304 Allows us to create a new method for each trigger on the fly.
305 If there is no trigger name in the machine to match, then the normal
306 AttributeError is called.
309 if name
not in possible_transitions:
310 raise AttributeError(f
"{name} does not exist in transitions for state {self.state}.")
313 return partial(self.
_trigger, name, transition_dict, **kwargs)
316 def _trigger(self, transition_name, transition_dict, **kwargs):
318 Runs the transition logic. Callbacks are evaluated in the order:
319 conditions -> before -> <new state set here> -> after.
321 dest, conditions, before_callbacks, after_callbacks = (
322 transition_dict[
"dest"],
323 transition_dict[
"conditions"],
324 transition_dict[
"before"],
325 transition_dict[
"after"]
328 if all(
map(
lambda condition: self.
_callback(condition, **kwargs), conditions)):
329 for before_func
in before_callbacks:
333 for after_func
in after_callbacks:
336 raise ConditionError(f
"Transition '{transition_name}' called for but one or more conditions "
342 Calls a condition/before/after.. function using arguments passed (or not).
344 return func(**kwargs)
348 Returns allowed transitions from a given state.
350 possible_transitions = []
351 for transition, transition_dicts
in self.
transitions.items():
352 for transition_dict
in transition_dicts:
353 if transition_dict[
"source"] == source:
354 possible_transitions.append(transition)
355 return possible_transitions
359 Returns the transition dictionary for a state and transition out of it.
362 for transition_dict
in transition_dicts:
363 if transition_dict[
"source"] == state:
364 return transition_dict
366 raise KeyError(f
"No transition from state {state} with the name {transition}.")
370 Does a simple dot file creation to visualise states and transiitons.
372 with open(filename,
"w")
as dotfile:
373 dotfile.write(
"digraph " + graphname +
" {\n")
374 for state
in self.
states.keys():
375 dotfile.write(
'"' + state +
'" [shape=ellipse, color=black]\n')
376 for trigger, transition_dicts
in self.
transitions.items():
377 for transition
in transition_dicts:
378 dotfile.write(
'"' + transition[
"source"].name +
'" -> "' +
379 transition[
"dest"].name +
'" [label="' + trigger +
'"]\n')
385 A state machine to handle `Calibration` objects and the flow of
390 collector_input_dir =
'collector_input'
392 collector_output_dir =
'collector_output'
394 algorithm_output_dir =
'algorithm_output'
396 def __init__(self, calibration, iov_to_calibrate=None, initial_state="init", iteration=0):
398 Takes a Calibration object from the caf framework and lets you
399 set the initial state.
440 self.
root_dir = Path(os.getcwd(), calibration.name)
450 self.
add_transition(
"submit_collector",
"init",
"running_collector",
458 self.
add_transition(
"fail",
"running_collector",
"collector_failed",
460 self.
add_transition(
"complete",
"running_collector",
"collector_completed",
462 self.
add_transition(
"run_algorithms",
"collector_completed",
"running_algorithms",
466 self.
add_transition(
"complete",
"running_algorithms",
"algorithms_completed",
469 self.
add_transition(
"fail",
"running_algorithms",
"algorithms_failed",
475 self.
add_transition(
"finish",
"algorithms_completed",
"completed",
482 """update calibration state"""
487 Lookup function that returns all files from the file_paths that
488 overlap with this IoV.
491 overlapping_files = set()
493 for file_path, file_iov
in files_to_iovs.items():
494 if file_iov.overlaps(iov)
and (file_path
in file_paths):
495 overlapping_files.add(file_path)
496 return overlapping_files
500 Dumps the `Job` object for the collections to JSON files so that it's configuration can be recovered
501 later in case of failure.
506 B2DEBUG(29,
"Some Collector Jobs still in 'init' state. Waiting...")
510 collector_job_output_file_name = self.
calibration.collections[collection_name].job_config
512 collection_name, collector_job_output_file_name)
513 job.dump_to_json(output_file)
517 Recovers the `Job` object for the collector from a JSON file in the event that we are starting from a reset.
519 for collection_name, collection
in self.
calibration.collections.items():
523 collection.job_config)
530 B2DEBUG(20, f
"Overall IoV {self.iov_to_calibrate} requested for calibration: {self.calibration.name}.")
533 B2DEBUG(20, f
"No overall IoV requested for calibration: {self.calibration.name}.")
543 Build IoV file dictionary for each collection if required.
545 iov_requested = self._iov_requested()
546 if iov_requested
or self.calibration.ignored_runs:
547 for coll_name, collection
in self.calibration.collections.items():
548 if not collection.files_to_iovs:
549 B2INFO(
"Creating IoV dictionaries to map files to (Exp,Run) ranges for"
550 f
" Calibration '{self.calibration.name} and Collection '{coll_name}'."
551 " Filling dictionary from input file metadata."
552 " If this is slow, set the 'files_to_iovs' attribute of each Collection before running.")
555 for file_path
in collection.input_files:
556 files_to_iovs[file_path] = get_iov_from_file(file_path)
557 collection.files_to_iovs = files_to_iovs
559 B2INFO(
"Using File to IoV mapping from 'files_to_iovs' attribute for "
560 f
"Calibration '{self.calibration.name}' and Collection '{coll_name}'.")
562 B2INFO(
"No File to IoV mapping required.")
577 Did all the collections succeed?
579 B2DEBUG(29,
"Checking for failed collector job.")
581 return all([job.status ==
"completed" for job
in self.
_collector_jobs.values()])
585 Did any of the collections fail?
587 B2DEBUG(29,
"Checking for failed collector job.")
589 return any([job.status ==
"failed" for job
in self.
_collector_jobs.values()])
594 bool: If AlgorithmsRunner succeeded return True.
601 bool: If AlgorithmsRunner failed return True.
612 if since_last_update > self.
calibration.collector_full_update_interval:
613 B2INFO(
"Updating full collector job statuses.")
618 num_completed = sum((subjob.status
in subjob.exit_statuses)
for subjob
in job.subjobs.values())
619 total_subjobs = len(job.subjobs)
620 B2INFO(f
"{num_completed}/{total_subjobs} Collector SubJobs finished in"
621 f
" Calibration {self.calibration.name} Job {job.name}.")
637 B2INFO(f
"Reached maximum number of iterations ({self.calibration.max_iterations}), will complete now.")
645 iteration_called =
False
647 for result
in results:
648 if result.result == CalibrationAlgorithm.c_Iterate:
649 iteration_called =
True
653 return iteration_called
658 B2INFO(f
"Calibration Machine {self.calibration.name} moved to state {kwargs['new_state'].name}.")
662 Condition function to check that the dependencies of our calibration are in the 'completed' state.
663 Technically only need to check explicit dependencies.
666 if not calibration.state == calibration.end_state:
673 Automatically try all transitions out of this state once. Tries fail last.
676 for transition
in possible_transitions:
678 if transition !=
"fail":
679 getattr(self, transition)()
681 except ConditionError:
684 if "fail" in possible_transitions:
685 getattr(self,
"fail")()
687 raise MachineError(f
"Failed to automatically transition out of {self.state} state.")
691 Creates the overall root directory of the Calibration. Will not overwrite if it already exists.
694 create_directories(self.
root_dir, overwrite=
False)
698 Creates a basf2 path for the correct collector and serializes it in the
699 self.output_dir/<calibration_name>/<iteration>/paths directory
703 create_directories(path_output_dir)
704 path_file_name = collection.collector.name() +
'.path'
705 path_file_name = path_output_dir / path_file_name
707 coll_path = create_path()
708 coll_path.add_module(collection.collector)
710 with open(path_file_name,
'bw')
as serialized_path_file:
711 pickle.dump(serialize_path(coll_path), serialized_path_file)
713 return str(path_file_name.absolute())
717 Creates a basf2 path for the collectors setup path (Collection.pre_collector_path) and serializes it in the
718 self.output_dir/<calibration_name>/<iteration>/<colector_output>/<name> directory.
721 coll_path = collection.pre_collector_path
722 path_file_name =
'pre_collector.path'
723 path_file_name = os.path.join(path_output_dir, path_file_name)
725 with open(path_file_name,
'bw')
as serialized_path_file:
726 pickle.dump(serialize_path(coll_path), serialized_path_file)
728 return path_file_name
732 Creates a Job object for the collections of this iteration, ready for submission
735 for collection_name, collection
in self.
calibration.collections.items():
741 if job.output_dir.exists():
742 B2INFO(f
"Previous output directory for {self.calibration.name} collector {collection_name} exists."
743 f
"Deleting {job.output_dir} before re-submitting.")
744 shutil.rmtree(job.output_dir)
745 job.cmd = collection.job_cmd
746 job.append_current_basf2_setup_cmds()
747 job.input_sandbox_files.append(collection.job_script)
749 job.input_sandbox_files.append(collector_path_file)
750 if collection.pre_collector_path:
752 job.input_sandbox_files.append(pre_collector_path_file)
755 list_dependent_databases = []
760 database_dir = os.path.join(os.getcwd(), dependency.name,
'outputdb')
761 B2INFO(f
"Adding local database from {dependency.name} for use by {self.calibration.name}.")
762 list_dependent_databases.append((os.path.join(database_dir,
'database.txt'), database_dir))
767 database_dir = os.path.join(previous_iteration_dir, self.
calibration.alg_output_dir,
'outputdb')
768 list_dependent_databases.append((os.path.join(database_dir,
'database.txt'), database_dir))
769 B2INFO(f
"Adding local database from previous iteration of {self.calibration.name}.")
780 for database
in collection.database_chain:
781 if database.db_type ==
'local':
782 json_db_chain.append((
'local', (database.filepath.as_posix(), database.payload_dir.as_posix())))
783 elif database.db_type ==
'central':
784 json_db_chain.append((
'central', database.global_tag))
786 raise ValueError(f
"Unknown database type {database.db_type}.")
788 for database
in list_dependent_databases:
789 json_db_chain.append((
'local', database))
790 job_config[
'database_chain'] = json_db_chain
792 job_config_file_path = input_data_directory.joinpath(
'collector_config.json').absolute()
793 with open(job_config_file_path,
'w')
as job_config_file:
794 json.dump(job_config, job_config_file, indent=2)
795 job.input_sandbox_files.append(job_config_file_path)
798 input_data_files = set(collection.input_files)
802 collection.files_to_iovs,
805 files_to_ignore = set()
807 for input_file
in input_data_files:
808 file_iov = self.
calibration.files_to_iovs[input_file]
809 if file_iov == exprun.make_iov():
810 B2INFO(f
"You have asked for {exprun} to be ignored for Calibration '{self.calibration.name}'. "
811 f
"Therefore the input file '{input_file}' from Collection '{collection_name}' "
812 "is being removed from input files list.")
813 files_to_ignore.add(input_file)
814 input_data_files.difference_update(files_to_ignore)
816 if not input_data_files:
817 raise MachineError(f
"No valid input files for Calibration '{self.calibration.name}' "
818 f
" and Collection '{collection_name}'.")
819 job.input_files = list(input_data_files)
821 job.splitter = collection.splitter
822 job.backend_args = collection.backend_args
824 job.output_patterns = collection.output_patterns
825 B2DEBUG(20, f
"Collector job for {self.calibration.name}:{collection_name}:\n{job}")
829 """check that collector output is valid"""
830 B2INFO(
"Checking that Collector output exists for all collector jobs "
831 f
"using {self.calibration.name}.output_patterns.")
833 B2INFO(
"We're restarting so we'll recreate the collector Job object.")
839 for pattern
in job.output_patterns:
840 output_files.extend(glob.glob(os.path.join(job.output_dir, pattern)))
842 raise MachineError(
"No output files from Collector Job")
844 for subjob
in job.subjobs.values():
846 for pattern
in subjob.output_patterns:
847 output_files.extend(glob.glob(os.path.join(subjob.output_dir, pattern)))
849 raise MachineError(f
"No output files from Collector {subjob}")
853 Runs the Calibration Algorithms for this calibration machine.
855 Will run them sequentially locally (possible benefits to using a
856 processing pool for low memory algorithms later on.)
860 algs_runner.algorithms = self.
calibration.algorithms
862 output_database_dir = algorithm_output_dir.joinpath(
"outputdb")
864 if algorithm_output_dir.exists():
865 B2INFO(f
"Output directory for {self.calibration.name} already exists from a previous CAF attempt. "
866 f
"Deleting and recreating {algorithm_output_dir}.")
867 create_directories(algorithm_output_dir)
868 B2INFO(f
"Output local database for {self.calibration.name} will be stored at {output_database_dir}.")
869 algs_runner.output_database_dir = output_database_dir
875 for subjob
in job.subjobs.values():
876 for pattern
in subjob.output_patterns:
877 input_files.extend(glob.glob(os.path.join(subjob.output_dir, pattern)))
879 for pattern
in job.output_patterns:
880 input_files.extend(glob.glob(os.path.join(job.output_dir, pattern)))
882 algs_runner.input_files = input_files
885 algs_runner.database_chain = self.
calibration.database_chain
889 list_dependent_databases = []
891 database_dir = os.path.join(os.getcwd(), dependency.name,
'outputdb')
892 B2INFO(f
"Adding local database from {dependency.name} for use by {self.calibration.name}.")
893 list_dependent_databases.append((os.path.join(database_dir,
'database.txt'), database_dir))
898 database_dir = os.path.join(previous_iteration_dir, self.
calibration.alg_output_dir,
'outputdb')
899 list_dependent_databases.append((os.path.join(database_dir,
'database.txt'), database_dir))
900 B2INFO(f
"Adding local database from previous iteration of {self.calibration.name}.")
901 algs_runner.dependent_databases = list_dependent_databases
903 algs_runner.ignored_runs = self.
calibration.ignored_runs
907 except Exception
as err:
917 Take the last iteration's outputdb and copy it to a more easily findable place.
922 final_database_location = self.
root_dir.joinpath(
'outputdb')
923 if final_database_location.exists():
924 B2INFO(f
"Removing previous final output database for {self.calibration.name} before copying new one.")
925 shutil.rmtree(final_database_location)
926 shutil.copytree(database_location, final_database_location)
931 A state machine to handle the logic of running the algorithm on the overall runs contained in the data.
936 required_attrs = [
"algorithm",
937 "dependent_databases",
940 "output_database_dir",
945 required_true_attrs = [
"algorithm",
947 "output_database_dir",
951 def __init__(self, algorithm=None, initial_state="init"):
953 Takes an Algorithm object from the caf framework and defines the transitions.
958 State(
"running_algorithm"),
997 params (dict): Dictionary containing values to be assigned to the machine's attributes of the same name.
999 for attribute_name, value
in params.items():
1000 setattr(self, attribute_name, value)
1005 bool: Whether or not this machine has been set up correctly with all its necessary attributes.
1007 B2INFO(f
"Checking validity of current setup of AlgorithmMachine for {self.algorithm.name}.")
1010 if not hasattr(self, attribute_name):
1011 B2ERROR(f
"AlgorithmMachine attribute {attribute_name} doesn't exist.")
1015 if not getattr(self, attribute_name):
1016 B2ERROR(f
"AlgorithmMachine attribute {attribute_name} returned False.")
1022 Create working/output directory of algorithm. Any old directory is overwritten.
1024 create_directories(Path(self.
output_dir), overwrite=
True)
1028 Apply all databases in the correct order.
1032 b2conditions.reset()
1033 b2conditions.override_globaltags()
1037 if database.db_type ==
'local':
1038 B2INFO(f
"Adding Local Database {database.filepath.as_posix()} to head of chain of local databases, "
1039 f
"for {self.algorithm.name}.")
1040 b2conditions.prepend_testing_payloads(database.filepath.as_posix())
1041 elif database.db_type ==
'central':
1042 B2INFO(f
"Adding Central database tag {database.global_tag} to head of GT chain, "
1043 f
"for {self.algorithm.name}.")
1044 b2conditions.prepend_globaltag(database.global_tag)
1046 raise ValueError(f
"Unknown database type {database.db_type}.")
1051 B2INFO(f
"Adding Local Database {filename} to head of chain of local databases created by"
1052 f
" a dependent calibration, for {self.algorithm.name}.")
1053 b2conditions.prepend_testing_payloads(filename)
1059 B2INFO(f
"Output local database for {self.algorithm.name} stored at {self.output_database_dir}.")
1062 b2conditions.expert_settings(save_payloads=str(self.
output_database_dir.joinpath(
"database.txt")))
1069 B2INFO(f
"Output log file at {log_file}.")
1071 basf2.set_log_level(basf2.LogLevel.INFO)
1072 basf2.log_to_file(log_file)
1077 B2INFO(f
"Changing current working directory to {self.output_dir}.")
1082 Call the user defined algorithm setup function.
1084 B2INFO(
"Running Pre-Algorithm function (if exists)")
1092 Does the actual execute of the algorithm on an IoV and records the result.
1094 B2INFO(f
"Running {self.algorithm.name} in working directory {os.getcwd()}.")
1096 runs_to_execute = kwargs[
"runs"]
1097 iov = kwargs[
"apply_iov"]
1098 iteration = kwargs[
"iteration"]
1100 iov = iov_from_runs(runs_to_execute)
1101 B2INFO(f
"Execution will use {iov} for labelling payloads by default.")
1102 alg_result = self.
algorithm.algorithm.execute(runs_to_execute, iteration, iov._cpp_iov)
1103 self.
result = IoV_Result(iov, alg_result)
1106 """set input data"""
1112 Base exception class for this module.
1116class ConditionError(MachineError):
1118 Exception for when conditions fail during a transition.
1124 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
_(self, callbacks, attribute)