Custom Classes
Writing custom classes using SANE workflows will mostly depend on which type of
workflow object you wish to customize. However, each class is always based off
of the sane.options.OptionLoader base class. If you plan on modifying
the class to expose configurable options with the idea of allowing both Python
and JSON interfacing then you should update the load_extra_options()
function for that class.
As the principle is the same for any derivable workflow option, the main concepts are presented here before the specifics of any individual class.
Custom Option Loading
The key idea behind sane.options.OptionLoader is that it provides a
consistent and auditable contract for loading configuration dictionaries into
an object. This is the same pattern used by sane.Action,
sane.Host, sane.Environment, and other workflow objects.
The primary benefits to using the provided functions:
It standardizes option loading across workflow objects.
It encourages safe handling of configuration dictionaries by consuming supported keys and warning about unused keys.
It preserves the ability to trace where options were loaded from with
origins.
Primary User Interface Function
A custom sane.options.OptionLoader class (beyond the workflow objects
in SANE) is expected to implement or extend load_extra_options()
Quick Reference (click to open/close)
- OptionLoader.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.
This function is always called after load_core_options()
during the processing of load_options():
Quick Reference (click to open/close)
- OptionLoader.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.
Warning
When using ANY load_*_options() method, the most important rule that
any key that your implementation handles should be removed from the options dict.
This is because at the end of the call, any remaining keys will be recognized as unused and will be logged.
Python Example
The following simple example defines a custom Action object, and demonstrates
some common use patterns for option loading. Use in Python is purely for convenience
if configuring from a dict works best for your workflow.
import sane
class MyAction( sane.Action ):
def __init__( self, id ):
super().__init__( id )
self.max_items = 20
self.prefix = "memo"
def load_extra_options( self, options, origin ):
# Pop from the dictionary using a default value if not found allows this
# key to remain optional
self.max_items = options.pop( "max_items", self.max_items )
self.prefix = options.pop( "prefix", self.prefix )
# Continue to load any extra options - good practice for inheritance
super().load_extra_options(options, origin)
@sane.register
def workflow( orch ):
action = MyAction( "foo" )
action.load_options( { "max_items" : 10 } )
orch.add_action( action )
Note
When the workflow is loaded, action.load_options(...) is the only call
to be called as it will under the hood call the derived methods.
JSON Interfacing
When a JSON file declares an object with a "type" field, the
sane.Orchestrator will resolve that type using
sane.options.OptionLoader.search_type().
Quick Reference (click to open/close)
- OptionLoader.search_type(type_str, noexcept=False)[source]
Match a type (as an input string) to an actualy python
typeIf at any point a search is successful, the function immediately returns the found
type.Search priority:
type_strusingpydoclocate()(effectively search current context for type of that fully qualified name )Split
type_stron last.in name and search any user-loaded module that contains the prefix for an attribute matching the suffix. If no split occurs all user modules are searched.
Valid type examples:
import sane import user_mod.nested.foo # module foo has CustomType # ... in the context of this class ... self.search_type( "sane.Action" ) self.search_type( "sane.host.Host" ) self.search_type( "user_mod.nested.foo.CustomType" ) # Using search method (2) if foo was loaded into the user modules by the workflow # since "foo" is a substring of "user_mod.nested.foo" self.search_type( "foo.CustomType" )
This means that your custom Python module must be available to the workflow
before the JSON file is interpreted. When using the sane_runner entry point,
this is done for you by default - loading your Python files first before any
JSON files. The exact order of operations is outlined in Orchestrator.load_paths():
Quick Reference (click to open/close)
- Orchestrator.load_paths()[source]
Load workflow definitions from current search paths and filters
This is the primary load call after all necessary paths and filters have been set. The order of operations is as follows:
Add all search paths to
sys.pathAll valid files matching at least one search filter across all paths are gathered.
Files are sorted based on file extension into
.pyand.json[c]All
.pyfiles are loaded viaload_py_files()All registered calls (via
@sane.register) are invoked in priority order viaprocess_registered()All
.json[c]files are then loaded viaload_config_files()(.jsonfirst, then.jsonc)All patches are processed in priority order via
process_patches()
- Return type:
None
The JSON type can be the Python fully qualified name or a shortened type name that uniquely identifies the type from the loaded Python modules.
{
"actions":
{
"bar":
{
"type": "where_MyAction_is.MyAction",
"max_items": 50,
}
}
}
Hint
Recall from the Tutorial Structure that the path we provide
to the runner functions as the root namespace package so our type will be
named based off of that. If MyAction is found in .sane/proj/custom_actions/acts.py
and run with sane_runner -p .sane/ ... then "type" : "proj.custom_actions.acts.MyAction"
is the full type name.
Summary
Setting up the load_extra_options() is a good
idea for custom workflow classes that plan to use load_options()
either directly from Python or via JSON configs. When adding your own options keep in mind
to:
Only override
load_extra_options()(core options are reserved for internal classes)Always remove each processed key from the
optionsdictionaryAlways call
super.load_extra_options()at some point in your derived method to ensure proper inheritance setup