Action
- class sane.DependencyType[source]
Types of dependencies between actions from child to parent
- AFTEROK = 'afterok'
after successful run (this is the default)
- AFTERNOTOK = 'afternotok'
after failure
- AFTERANY = 'afterany'
after either failure or success
- AFTER = 'after'
after the step starts
- class sane.ActionState[source]
States that an action may be in
- PENDING = 'pending'
queued for running
- RUNNING = 'running'
currently executing
- FINISHED = 'finished'
completed regardless of success
- INACTIVE = 'inactive'
never queued or run, idle
- SKIPPED = 'skipped'
never run due to dependencies missing
- ERROR = 'error'
reserved for internal use, use action status for action failure
- class sane.ActionStatus[source]
Final status of an action
Note
Note that the
SUBMITTEDstatus is considered “completed” in the context of action queueing, but finalSUCCESSorFAILUREis left to the host implementation.- SUCCESS = 'success'
completed successfully
- FAILURE = 'failure'
completed unsuccessfully
- SUBMITTED = 'submitted'
submitted to host, current context action completed but execution left to host
- NONE = 'none'
no completion status to report
- class sane.Action[source]
Bases:
SaveState,ResourceRequestorA single task
An Action is the singular unit within workflows that performs work. Actions will always be in one of a finite set of states with an associated status.
Actions can specify an
Environmentnecessary to runActions can specify resources necessry to run
Actions can specify
dependenciesnecessry to runActions must have unique IDs within a workflow
Actions will always execute under separate processes from the
sane.Orchestrator
User Interface
User Methods
- add_dependencies(*args)[source]
Add dependencies to this
ActionUse this function to properly add dependencies at any time before the workflow is run. This can be called before or after Actions are added to the workflow, but not after the workflow has started running. No checks for valid graph topology, dependency existing, and so on are performed. These checks are done by the
Orchestrator(seeOrchestrator.construct_dag())Any number of dependencies may be listed as either :
(a) a single
strcorresponding to the dependencyid(b) tuple pair of
str(dependencyid) andstrrepresentation ofDependencyTypeDependencyTypevalue
Note
If no
DependencyTypeis provided, i.e. calling in manner (a), then the default isDependencyType.AFTEROKThe following code block shows a few valid example calls:
import sane @sane.register def register_actions( orch ): a = sane.Action( "a" ) b = sane.Action( "bee" ) c = sane.Action( "c" ) orch.add_action( a ) # The provided ID string MUST match the Action.id, not the object variable name # vvv a.add_dependencies( "bee", ( "c", "afterok" ) ) b.add_dependencies( ( "c", sane.DependencyType.AFTEROK ) ) # This is a valid call but does not form a valid DAG c.add_dependencies( "a" )
- add_resource_requirements(resource_dict)[source]
Add resource requirements to this requestor
Hint
See
Resourcefor syntax on default supported valuesAdd an arbitrary dict of key-value pairs to this requestor. The key-value pairs in this dict will eventually be requested from a
ResourceRequestorfor:If the value in a key-value pair is of type
dict, it will be considered an override resource request specific to the name of the key. This overridedictwill be kept separately in an internal location to be used later. Multiple nesteddictoverrides are not allowed.See
resources()for more info.As an example:
{ "cpus" : 12, "mem" : "1gb", "slots" : 1 "specific_provider" : { "cpus" : 36, "mem" : "3gb" } }
The values of
"specific_provider"will only be used (in addition to any unmodified values in the top-level dict) if thecurrent_hostmatches this key.Note
While the
resource_dictcan be arbitrary values, it is on theResourceProvider(i.e.sane.Host) to be able to provide these resources.Notably, any resources that do not follow the
Resourcesyntax are left strictly to the provider class implementation. The default classes do not support values outside ofResourceunless specified.- Parameters:
resource_dict (dict)
User Attributes & Properties
- config: dict
A user-defined
dictthat can hold anything picklableAutomatic config
loadinganddereferencingis supported for this attribute. This is meant as a general information container without the need for defining a customActionwith custom attributes.Reserved keywords in this dict are:
keyword
usage
"command"command to execute when
Actionis run"arguments"arguments to use in command execution
If a custom
Actionoverridesrun(), then there are no reserved keywords as the above keywords are specific to the default base classrun()implementation.
- outputs: dict
A user-provided
dictof picklable output values that should be valid uponActioncompletionThese outputs will be provided to direct dependencies of this
Actionwithin theirdependenciesproperty during execution. Any further use of this attribute is left to user implementation.
- environment: str
The
Environment.nameof theEnvironmentto use that thecurrent hostshould provide.
- property host_info: dict
Info
dictprovided from thecurrent_hostusing theHost.info
- property info: dict
Actioninfodict(dereferenced) provided to direct dependencies viadependenciesat runtimeThe default info
dictprovided is:{ "config" :config, "outputs" :outputs}
- property dependencies: dict
A copy of the internal dependencies
dictwith relevant info about these direct dependenciesThe workflow uses a \(child \rightarrow N parents\) relation, where an
Actionrecords which parentActionsit is dependent on.Prior to workflow runtime, this
dictcontains parentActiondependencies byidand their correspondingDependencyTypeas"dep_type"in a sub-dict:{ "<parent-id-a>" : { "dep_type" :DependencyType}, "<parent-id-b>" : { "dep_type" :DependencyType}, ... }During runtime, this
dictis modified to include the parentAction.infowithin the sub-dict. By default this would then allow access to dependencies’outputsandconfig:{ "<parent-id-a>" : { "dep_type" :DependencyType, # The exact contents below come frominfo()"config" :config, "outputs" :outputs}, ... }
- resources(override=None)[source]
Return a copy of the current resources requested
During workflow execution, the
current_hostwill be provided as theoverridevalue when requesting resources from thesane.Hostviaresources.ResourceProvider.acquire_resources()
- local
Control if the resources requested should be provided from a local pool if a
NonLocalProvideris used. If set toNonethen resource delegation is left to the provider,Trueto force local, andFalseto force non-local. If the provider is a normalResourceProviderthis option has no effect.
Customizable Functions
ActionandHostclasses are designed to be modified by users.While any function could generally be overwritten with some care, certain functions within each class are designed specifically for user modification without the need to worry about internal logic.
Defining these functions does not require calling
superor other intrinsic Python knowledge beyond the interface of the function and any user logic.- extra_requirements_met(dependency_actions)[source]
Check if any extra user-imposed requirements are met
The return value may be
Trueto signify this Action is ready to run, orFalseto note that a requirement was not satisfied and this action would not be able to run and thus beSKIPPED. Default is to always returnTrueCaution
For delayed requirements that may be fulfllled at a later time (e.g. non-local host evaluation) refer to the source code for using
sane.action.RequirementState.PENDING. If used, an external entity (often a non-local host) MUST retriggerOrchestrator.__wake__when this requirement may be affected to ensure the workflow does not silently hang with the Action never running.
- load_extra_options(options, origin=None)[source]
Load any extra options after
load_core_options().Any processed field should be removed from the options dict, with everything else ignored.
See
load_options()for parameters.
- pre_launch()[source]
Called before
save()and execution ofaction_launcher.py. Seelaunch()- Return type:
None
- pre_run()[source]
Called just before
run()within the context of the Action subprocess- Return type:
None
- run()[source]
The main execution of the
Action, performed within an isolated subprocessThis function will be called from within
action_launcher.pyunder an isolated subprocess with the requestedenvironmentalreadysetup().Users may override this function in a derived
Actionas primary way to change how anActionruns. The default implementation of this function fullydereferencestheconfigdictand executesconfig["command"]andconfig["arguments"]in another subprocess viaexecute_subprocess().Hint
The subprocess of running
config["command"]in the default implementation of this function will inherit theEnvironment.setup()settings. Seesubprocess.Popenenv=Nonefor more info.- Returns:
The return value of running this action. Used by
action_launcher.pyas theexit()code. Thus, anything aside from0isFAILURE. The default returns the return value ofexecute_subprocess()from runningconfig["command"]- Return type:
Helper Functions
- resolve_path(input_path, base_path=None)[source]
Reslove a path using base path if input path is relative, otherwise only use input path
- resolve_path_exists(input_path, base_path=None, allow_dry_run=True)[source]
Wrapper on
resolve_path()to also check if that directory exists, throwing exception if not
- file_exists_in_path(input_path, file, allow_dry_run=True)[source]
Check if a specified file exists within a provided path
- dereference_str(input_str, log=True, noexcept=False)[source]
Dereference an input string using GitHub Actions style syntax scoped to the current object
Continuously dereferences strings within the current object until no more substitutions can be made. All attributes and properties can be referenced, but dereferencing will work best with attributes that are
dict,list,str, orintvalues.Nested referencing can be achieved with.operator (key as next field)Index referencing can be achieved with[]operator (positive integer)Valid syntax examples:
action_a = sane.Action( "action_a" ) action_b = sane.Action( "action_b" ) action_a.add_dependencies( "action_b" ) action_a.config["foo"] = "1" action_a.config["bar"] = "2" action_a.config["zoo"] = [ 3, 4 ] action_a.config["boo"] = 7 action_a.config["moo"] = { "loo" : [ { "goo" : "5", "hoo" : [ 6, "${{ config.boo" }} ] }, 0, 0, 0 ] } action_a.outputs["outfile"] = "something" action_a.add_resource_requirements( { "cpus" : 12, "mem" : "1gb" } ) action_b.outputs["some_file"] = "fill_this_in" # Within the context of action_a these are valid "${{ config.foo }}" => "1" "${{ config.bar }}" => "2" "${{ config.zoo[1] }}" => "4" "${{ resources.cpus }}" => "12" "${{ outputs.outfile }}" => "something" # At runtime this would be valid, with this value if the value in action_b has not changed "${{ dependencies.action_b.outputs.some_file }}" => "fill_this_in" # A complex dereference "${{ config.moo.loo[0].hoo[1] }}" => "7"
Attention
During the substitution, if indexing to the next attribute yields
NoneanExceptionwill be thrown. Thus, at the time of dereferencing, the string input MUST be valid.
- dereference(obj, log=True, noexcept=False)[source]
Fully dereference all strings within the
objpassed inFor
dictandlistobjects, each will be iterated over and this function will be recursively call for each iterated value (not key), and then assigned back to itself, presumably modified. Forstrobjectsdereference_str()will be called.For all other object types, the
objwill be unmodified.The
objis then returned, modified if necessary (and possible) to contain only fully dereferenced strings.- Returns:
objwith all stringsdereferenced
Internal API
The following documentation is provided for advanced use in the creation of custom Actions.
- __timestamp__
The start time of the
Action.launch()in ISO format
- __time__
- property runlog: str
Absolute path to logfile capturing this
Action.run()output
- property origins
A copy of all the workflow file paths (and line numbers if applicable) that formed this object
- property status: ActionStatus
The current
ActionStatusof thisAction.
- property state: ActionState
The current
ActionStateof thisAction.
- property results: dict
Default results to be saved in the workflow cache returned as a
dictBy default the following is returned
{ "state" :state, (string repr) "status" :status, (string repr) "origins" :origins, "outputs" :outputs, (dereferenced) "timestamp" :__timestamp__, (if available) "time" :__time__, (if available) }When set, the provided
dictshould match the above format
- load_options(options, origin=None)[source]
Base class implementation for loading of dict-based attributes into instance
Take a options dict of relevant attributes and load them via
load_core_options()thenload_extra_options(). The options dict should be modified in each call to remove processed fields so that at the very end of this method, any unused keys in the options dict may be logged.The
load_extra_options()is meant as a user-overwritable method so thatload_core_options()may retain core underlying base class implementation details without the risk of base class loading not being called.To keep track of every time this function is called and potentially modifying this instance an origin may be provided, noting where the change is coming from.
- load_core_options(options, origin)[source]
From
OptionLoader.load_core_options:Any processed field should be removed from the options dict, with everything else ignored. All listed options are cummulative and optional unless specified otherwise.
See
load_options()for parameters.From
Action.load_core_options():Load
Actionsettings from the provided options dict, all keys are optional.The following keys are loaded verbatim into their respective attribute:
"environment"=>environment"working_directory"=>working_directory
The following key is loaded and calls
recursive_update()preserve any unmodified existing values:"config"=>config
The following key is loaded directly to
add_dependencies()as key-value tuple pairs viadict.items()"dependencies"
An example options
dict{ "environment" : "gnu", # Recall thatconfigis a genericdict"config" : { "foo" : [ 1, 2, 3 ], "bar" : "file" }, # if loading via plain-text (e.g. JSON), use the text value # ofDependencyTypeas noted inadd_dependencies()"dependencies" : { "action_b" : "afterok", "action_c" : "afternotok" } }From
ResourceRequestor.load_core_options:Load
ResourceRequestorresource requirementsThe following key is loaded verbatim into
add_resource_requirements():"resources"
The following key is loaded if possible, defaulting to
None:"local"=>local
- execute_subprocess(cmd, arguments=None, logfile=None, verbose=False, dry_run=False, capture=False, shell=False, log_level=19)[source]
Execution wrapper for running a command using
subprocess.PopenNotably, this wrapper handles:
command and argument aggregation
internal, logfile, and verbose logging with flushing in realtime
wrapping of stdout using internal log levels
returning return value along with captured stdout (if
captureenabled)
Prefer using this function if stdout log wrapping is desired. If none of the features above are of use, any subprocess function call can be used, but may or may not appear in the log output as desired. All output is logged, however.
- Parameters:
cmd (str) – command to execute
arguments (list) – list of arguments, will be
strcastlogfile (str) – optional logfile to write command output to
verbose – optional output the command stdout and stderr to terminal
dry_run – optional do not call
subprocess.Popen, i.e. stub this callcapture – optional capture stderr/stdout to
strreturnshell – optional treat execution as shell command (see
subprocess.Popenfor more detail)
- Returns:
tupleof execution return value and any stderr/stdout capture- Return type:
- launch(working_directory, launch_wrapper=None)[source]
Main entry point for executing an
Actionwithin a workflowCoordinates the current state and status of the action whilst executing, prepares the
Actionfor action_launcher.py execution proxy, and adjust call toexecute_subprocess()if alaunch_wrapperis provided.__timestamp__and total execution__time__are recorded immediately at the beginning and end of this function, not just around theexecute_subprocess()call.The order of operations, as they concern the user, are:
pre_launch()(sharedActionmutex locked around this call)save()resolve internal launch command (action_launcher.py) and
launch_wrapperexecute_subprocess()of resolved command, capturing torunlogusingdry_runif setpost_launch()called with output of (4) (sharedActionmutex locked around this call)return output of (4)
If an unexpected
Exceptionoccurs at any point outside of thesubprocess.Popencall withinexecute_subprocess(), the following occurs:set_state_error()calledshared
Actionmutex force unlockedre-raise the same
Exception
Danger
Avoid modifying or overriding this function in any derived
Action- Parameters:
working_directory (str) – Base directory to evaluate
working_directoryfrom. Final resolved path is used to evaluate the internal_launch_cmdorlaunch_wrappercommand, if provided.launch_wrapper (tuple[str, list[str]]) –
tuplepair of command and list of arguments to use as a prefix toexecute_subprocess()where this command is the new command and the arguments, then previous command and arguments are provided in that order.
- Returns:
tupleofexecute_subprocess()of this Action’srun()(orlaunch_wrapperoutput if provided)- Return type:
- set_status_success()[source]
Quickset state to
ActionState.FINISHEDand status toActionStatus.SUCCESSTypically set by the
launch()afterActioncompletion.- Return type:
None
- set_status_failure()[source]
Quickset state to
ActionState.FINISHEDand status toActionStatus.FAILURETypically set by the
launch()afterActioncompletion.- Return type:
None
- set_state_pending()[source]
Quickset state to
ActionState.PENDINGand status toActionStatus.NONETypically set by the
Orchestratorduring workflow startup- Return type:
None
- set_state_skipped()[source]
Quickset state to
ActionState.SKIPPEDand status toActionStatus.NONESet by the
Orchestratorduring workflow running if deemed necessary- Return type:
None
- __orch_wake__()[source]
Wake up the
Orchestratorfrom another thread.This should be used as an event trigger to induce re-evaluation of completed
Actionsin the current workflow run. SeeOrchestrator.__wake__for more info.- Return type:
None