Internals¶
This document presents the various classes and components of XWorkflows.
Note
All objects defined in the base module should be considered internal API
and subject to change without notice.
Public API consists of the public methods and attributes of the following objects:
- The
transition()function; - The
before_transition(),after_transition(),transition_check(),on_enter_state()andon_leave_state()decorators; - The
WorkflowandWorkflowEnabledclasses; - The
WorkflowError,AbortTransition,InvalidTransitionErrorandForbiddenTransitionexceptions.
Exceptions¶
The xworkflows module exposes a few specific exceptions:
-
exception
xworkflows.WorkflowError[source]¶ This is the base for all exceptions from the
xworkflowsmodule.
-
exception
xworkflows.AbortTransition(WorkflowError)[source]¶ This error is raised whenever a transition call fails, either due to state validation or pre-transition checks.
-
exception
xworkflows.InvalidTransitionError(AbortTransition)[source]¶ This exception is raised when trying to perform a transition from an incompatible state.
-
exception
xworkflows.ForbiddenTransition(AbortTransition)[source]¶ This exception will be raised when the
checkparameter of thetransition()decorator returns a non-Truevalue.
States¶
States may be represented with different objects:
base.Stateis a basic state (name and title)base.StateWrapperis an enhanced wrapper around theStatewith enhanced comparison functions.base.StatePropertyis a class-level property-like wrapper around aState.
The State class¶
The StateWrapper class¶
-
class
base.StateWrapper(state, workflow)¶ Intended for use as a
WorkflowEnabledattribute, this wraps aStatewith knowledge about the relatedWorkflow.Its
__hash__is computed from the relatedname. It compares equal to:- Another
StateWrapperfor the sameState - Its
State - The
nameof itsState
-
transitions()¶ Returns: A list of Transitionwith thisStateas source
- Another
The StateProperty class¶
-
class
base.StateProperty(workflow, state_field_name)¶ Special property-like object (technically a data descriptor), this class controls access to the current
Stateof aWorkflowEnabledobject.It performs the following actions:
- Checks that any set value is a valid
Statefrom theworkflow(raisesValueErrorotherwise) - Wraps retrieved values into a
StateWrapper
-
field_name¶ The name of the attribute wrapped by this
StateProperty.
- Checks that any set value is a valid
Workflows¶
A Workflow definition is slightly different from the resulting class.
A few class-level declarations will be converted into advanced objects:
statesis defined as a list of two-tuples and converted into aStateListtransitionsis defined as a list of three-tuples and converted into aTransitionListinitial_stateis defined as thenameof the initialStateof theWorkflowand converted into thatState
Workflow definition¶
A Workflow definition must inherit from the Workflow class, or use the base.WorkflowMeta metaclass for proper setup.
Defining states¶
The list of states should be defined as a list of two-tuples of (name, title):
class MyWorkflow(xworkflows.Workflow):
states = (
('initial', "Initial"),
('middle', "Intermediary"),
('final', "Final - all is said and done."),
)
This is converted into a StateList object.
-
class
base.StateList¶ This class acts as a mixed dictionary/object container of
states.It replaces the
stateslist from theWorkflowdefinition.-
__getattr__()¶ Allows retrieving a
Statefrom its name, as an attribute of theStateList:MyWorkflow.states.initial == MyWorkflow.states['initial']
-
__iter__()¶ Iterates over the states, in the order they were defined
-
Defining transitions¶
At a Workflow level, transition are defined in a list of three-tuples:
- transition name
- list of the
namesof sourcestatesfor the transition, or name of the source state if unique nameof the targetState
class MyWorkflow(xworkflows.Workflow):
transitions = (
('advance', 'initial', 'middle'),
('end', ['initial', 'middle'], 'final'),
)
This is converted into a TransitionList object.
-
class
base.TransitionList¶ This acts as a mixed dictionary/object container of
transitions.It replaces the
transitionslist from theWorkflowdefinition.-
__getitem__()¶ Allows retrieving a
Transitionfrom its name or from an instance, in a dict-like manner
-
__getattr__()¶ Allows retrieving a
Transitionfrom its name, as an attribute of theTransitionList:MyWorkflow.transitions.accept == MyWorkflow.transitions['accept']
-
__iter__()¶ Iterates over the transitions, in the order they were defined
-
__contains__()¶ Tests whether a
Transitioninstance or itsnamebelong to theWorkflow
-
available_from(state)¶ Retrieve the list of
Transitionavailable from the givenState.
-
-
class
base.Transition¶ Container for a transition.
-
name¶ The name of the
Transition; should be a valid Python identifier
-
source¶ A list of source
statesfor thisTransition
-
target¶ The target
Statefor thisTransition
-
Workflow attributes¶
A Workflow should inherit from the Workflow base class, or use the WorkflowMeta metaclass
(that builds the states, transitions, initial_state attributes).
-
class
xworkflows.Workflow¶ This class holds the definition of a workflow.
-
transitions¶ A
TransitionListof allTransitionfor thisWorkflow
-
log_transition(transition, from_state, instance, *args, **kwargs)¶ Parameters: - transition – The
Transitionjust performed - from_state – The source
Stateof the instance (before performing a transition) - instance – The
objectundergoing a transition - args – All non-keyword arguments passed to the transition implementation
- kwargs – All keyword arguments passed to the transition implementation
This method allows logging all transitions performed by objects using a given workflow.
The default implementation logs to the logging module, in the
baselogger.- transition – The
-
implementation_class¶ The class to use when creating
ImplementationWrapperfor aWorkflowEnabledusing thisWorkflow.Defaults to
ImplementationWrapper.
-
-
class
base.WorkflowMeta¶ This metaclass will simply convert the
states,transitionsandinitial_stateclass attributes into the relatedStateList,TransitionListandStateobjects.During this process, some sanity checks are performed:
- Each source/target
Stateof aTransitionmust appear instates - The
initial_statemust appear instates.
- Each source/target
Applying workflows¶
In order to use a Workflow, related objects should inherit from the WorkflowEnabled class.
-
class
xworkflows.WorkflowEnabled¶ This class will handle all specific setup related to using
workflows:- Converting
attr = SomeWorkflow()into aStatePropertyclass attribute - Wrapping all
transition()-decorated functions intoImplementationPropertywrappers - Adding noop implementations for other transitions
-
_add_workflow(mcs, field_name, state_field, attrs)¶ Adds a workflow to the attributes dict of the future class.
Parameters: - field_name (str) – Name of the field at which the field holding the current state will live
- state_field (StateField) – The
StateFieldas returned by_find_workflows() - attrs (dict) – Attribute dict of the future class, updated with the new
StateProperty.
Note
This method is also an extension point for custom XWorkflow-related libraries.
-
_find_workflows(mcs, attrs)¶ Find all workflow definitions in a class attributes dict.
Parameters: attrs (dict) – Attribute dict of the future class Returns: A dict mapping a field name to a StateFielddescribing parameters for the workflowNote
This method is also an extension point for custom XWorkflow-related libraries.
-
_workflows¶ This class-level attribute holds a dict mapping an attribute to the related
Workflow.Note
This is a private attribute, and may change at any time in the future.
-
_xworkflows_implems¶ This class-level attribute holds a dict mapping an attribute to the related implementations.
Note
This is a private attribute, and may change at any time in the future.
- Converting
-
class
base.WorkflowEnabledMeta¶ This metaclass handles the parsing of
WorkflowEnabledand related magic.Most of the work is handled by
ImplementationList, with one instance handling eachWorkflowattached to theWorkflowEnabledobject.
Customizing transitions¶
A bare WorkflowEnabled subclass definition will be automatically modified to
include “noop” implementations for all transitions from related workflows.
In order to customize this behaviour, one should use the transition() decorator on
methods that should be called when performing transitions.
-
xworkflows.transition([trname='', field='', check=None, before=None, after=None])[source]¶ Decorates a method and uses it for a given
Transition.Parameters: - trname (str) – Name of the transition during which the decorated method should be called. If empty, the name of the decorated method is used.
- field (str) – Name of the field this transition applies to; useful when two workflows define a transition with the same name.
- check (callable) –
An optional function to call before running the transition, with the about-to-be-modified instance as single argument.
Should return
Trueif the transition can proceed.Deprecated since version 0.4.0: Will be removed in 0.5.0; use
transition_check()instead. - before (callable) –
An optional function to call after checks and before the actual implementation.
Receives the same arguments as the transition implementation.
Deprecated since version 0.4.0: Will be removed in 0.5.0; use
before_transition()instead. - after (callable) –
An optional function to call after the transition was performed and logged.
Receives the instance, the implementation return value and the implementation arguments.
Deprecated since version 0.4.0: Will be removed in 0.5.0; use
after_transition()instead.
-
class
base.TransitionWrapper¶ Actual class holding all values defined by the
transition()decorator.-
func¶ The decorated function, wrapped with a few checks and calls.
-
Hooks¶
Hooks are declared through a _HookDeclaration decorator, which attaches
a specific xworkflows_hook attribute to the decorated method.
Methods with such attribute will be collected into Hook objects containing all useful fields.
Registering hooks¶
-
xworkflows._make_hook_dict(function)¶ Ensures that the given
functionhas axworkflows_hookattributes, and returns it.The
xworkflows_hookis a dict mapping each hook kind to a list of(field, hook)pairs:function.xworkflows_hook = { HOOK_BEFORE: [('state', <Hook: ...>), ('', <Hook: ...>)], HOOK_AFTER: [], ... }
Note
Although the
xworkflows_hookis considered a private API, it may become an official extension point in future releases.
-
class
base._HookDeclaration¶ Base class for hook declaration decorators.
It accepts an (optional) list of transition/state
names, andpriority/fieldas keyword arguments:@_HookDeclaration('foo', 'bar') @_HookDeclaration(priority=42) @_HookDeclaration('foo', field='state1') @_HookDeclaration(priority=42, field='state1') def hook(self): pass
-
names¶ List of
transitionorstatenames the hook applies toType: str list
-
priority¶ The priority of the hook
Type: int
-
field¶ The name of the
StateWrapperfield whose transitions the hook applies toType: str
-
_as_hook(self, func)¶ Create a
Hookfor the given callable
-
__call__(self, func)¶ Create a
Hookfor the function, and store it in the function’sxworkflows_hookattribute.
-
-
xworkflows.before_transition(*names, priority=0, field='')[source]¶ Marks a method as a pre-transition hook. The hook will be called just before changing a
WorkflowEnabledobject state, with the same*argsand**kwargsas the actual implementation.
-
xworkflows.transition_check(*names, priority=0, field='')[source]¶ Marks a method as a transition check hook.
The hook will be called when using
is_available()and before running the implementation, without any args, and should return a boolean indicating whether the transition may proceed.
-
xworkflows.after_transition(*names, priority=0, field='')[source]¶ Marks a method as a post-transition hook
The hook will be called immediately after the state update, with:
res, return value of the actual implementation*argsand**kwargsthat were passed to the implementation
-
xworkflows.on_leave_state(*names, priority=0, field='')[source]¶ Marks a method as a pre-transition hook to call when the object leaves one of the given states.
The hook will be called with the same arguments as a
before_transition()hook.
-
xworkflows.on_enter_state(*names, priority=0, field='')[source]¶ Marks a method as a post-transition hook to call just after changing the state to one of the given states.
The hook will be called with the same arguments as a
after_transition()hook.
Calling hooks¶
-
xworkflows.HOOK_BEFORE¶ The kind of
before_transition()hooks
-
xworkflows.HOOK_CHECK¶ The kind of
transition_check()hooks
-
xworkflows.HOOK_AFTER¶ The kind of
after_transition()hooks
-
xworkflows.HOOK_ON_ENTER¶ The kind of
on_leave_state()hooks
-
xworkflows.HOOK_ON_LEAVE¶ The kind of
on_enter_state()hooks
-
class
base.Hook¶ Describes a hook, including its
kind,priorityand the list of transitions it applies to.-
kind¶ One of
HOOK_BEFORE,HOOK_AFTER,HOOK_CHECK,HOOK_ON_ENTERorHOOK_ON_LEAVE; the kind of hook.
-
priority¶ The priority of the hook, as an integer defaulting to 0. Hooks with higher priority will be executed first; hooks with the same priority will be sorted according to the
functionname.Type: int
-
function¶ The actual hook function to call. Arguments passed to that function depend on the hook’s
kind.Type: callable
-
names¶ Name of
statesortransitionsthis hook applies to; will be('*',)if the hook applies to all states/transitions.Type: str tuple
-
applies_to(self, transition[, from_state=None])¶ Check whether the hook applies to the given
Transitionand optional sourceState.If
from_stateisNone, the test means “could the hook apply to the given transition, in at least one source state”.If
from_stateis notNone, the test means “does the hook apply to the given transition for this specific source state”.Returns: bool
-
__call__(self, *args, **kwargs): Call the hook
-
__eq__(self, other)¶
-
__ne__(self, other)¶ Two hooks are “equal” if they wrap the same function, have the same kind, priority and names.
-
__cmp__(self, other)¶ Hooks are ordered by descending priority and ascending decorated function name.
-
Advanced customization¶
Once WorkflowEnabledMeta has updated the WorkflowEnabled subclass,
all transitions – initially defined and automatically added – are replaced with a base.ImplementationProperty instance.
-
class
base.ImplementationProperty¶ This class holds all objects required to instantiate a
ImplementationWrapperwhenever the attribute is accessed on an instance.Internally, it acts as a ‘non-data descriptor’, close to
property().-
__get__(self, instance, owner)¶ This method overrides the
getattr()behavior:- When called without an instance (
instance=None), returns itself - When called with an instance, this will instantiate a
ImplementationWrapperattached to that instance and return it.
- When called without an instance (
-
-
class
base.ImplementationWrapper¶ This class handles applying a
Transitionto aWorkflowEnabledobject.-
instance¶ The
WorkflowEnabledobject to modify whencallingthis wrapper.
-
field_name¶ The name of the field modified by this
ImplementationProperty(a string)Type: str
-
transition¶ The
Transitionperformed by this object.Type: Transition
-
workflow¶ The
Workflowto which thisImplementationPropertyrelates.Type: Workflow
-
implementation¶ The actual method to call when performing the transition. For undefined implementations, uses
noop().Type: callable
-
hooks¶ All hooks that may be applied when performing the related transition.
Type: dictmapping a hook kind to a list ofHook
-
current_state¶ Actually a property, retrieve the current state from the instance.
Type: StateWrapper
-
__call__()¶ This method allows the
TransitionWrapperto act as a function, performing the whole range of checks and hooks before and after calling the actualimplementation.
-
is_available()¶ Determines whether the wrapped transition implementation can be called. In details:
- it makes sure that the current state of the instance is compatible with the transition;
- it calls the
transition_check()hooks, if defined.
Return type: bool
-
-
base.noop(instance)¶ The ‘do-nothing’ function called as default implementation of transitions.
Collecting the ImplementationProperty¶
Warning
This documents private APIs. Use at your own risk.
Building the list of ImplementationProperty for a given WorkflowEnabled, and generating the missing ones, is a complex job.
-
class
base.ImplementationList¶ This class performs a few low-level operations on a
WorkflowEnabledclass:- Collecting
TransitionWrapperattributes - Converting them into
ImplementationProperty - Adding
noop()implementations for remainingTransition - Updating the class attributes with those
ImplementationProperty
-
state_field¶ The name of the attribute (from
attr = SomeWorkflow()definition) currently handled.Type: str
-
workflow¶ The
WorkflowthisImplementationListrefers to
-
implementations¶ Dict mapping a transition name to the related
ImplementationPropertyType: dict(str=>ImplementationProperty)
-
transitions_at¶ Dict mapping the name of a transition to the attribute holding its
ImplementationProperty:@transition('foo') def bar(self): pass
will translate into:
self.implementations == {'foo': <ImplementationProperty for 'foo' on 'state': <function bar at 0xdeadbeed>>} self.transitions_at == {'foo': 'bar'}
-
custom_implems¶ Set of name of implementations which were remapped within the workflow.
-
load_parent_implems(self, parent_implems)¶ Loads implementations defined in a parent
ImplementationList.Parameters: parent_implems ( ImplementationList) – TheImplementationListfrom a parent
-
get_custom_implementations(self)¶ Retrieves definition of custom (non-automatic) implementations from the current list.
Yields: (trname, attr, implem): Tuples containing the transition name, the name of the attribute its implementation is stored at, and that implementation (aImplementationProperty).
-
should_collect(self, value)¶ Whether a given attribute value should be collected in the current list.
Checks that it is a
TransitionWrapper, for aTransitionof the currentWorkflow, and relates to the currentstate_field.
-
collect(self, attrs)¶ Collects all
TransitionWrapperfrom an attribute dict if they verifyshould_collect().Raises: ValueError If two TransitionWrapperfor a sameTransitionare defined in the attributes.
-
add_missing_implementations(self)¶ Registers
noop()ImplementationPropertyfor allTransitionthat weren’t collected in thecollect()step.
-
register_hooks(self, cls)¶ Walks the class attributes and collects hooks from those with a
xworkflows_hookattribute (throughregister_function_hooks())
-
register_function_hooks(self, func)¶ Retrieves hook definitions from the given function, and registers them on the related
ImplementationProperty.
-
_may_override(self, implem, other)¶ Checks whether the
implemImplementationPropertyis a valid override for theotherImplementationProperty.Rules are:
- A
ImplementationPropertymay not override anotherImplementationPropertyfor anotherTransitionor anotherstate_field - A
ImplementationPropertymay not override aTransitionWrapperunless it was generated from thatTransitionWrapper - A
ImplementationPropertymay not override other types of previous definitions.
- A
-
fill_attrs(self, attrs)¶ Adds all
ImplementationPropertyfromimplementationsto the given attributes dict, unless_may_override()prevents the operation.
-
transform(self, attrs)¶ Parameters: attrs (dict) – Mapping holding attribute declarations from a class definition Performs the following actions, in order:
collect(): CreateImplementationPropertyfrom thetransition wrappersin theattrsdictadd_missing_implementations(): createImplementationPropertyfor the remainingtransitionsfill_attrs(): Update theattrsdict with theimplementationsdefined in the previous steps.
- Collecting