Skip to content

Commit

Permalink
add docs for new examples
Browse files Browse the repository at this point in the history
  • Loading branch information
essweine committed Apr 12, 2024
1 parent 18a6350 commit 3ca0a57
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 95 deletions.
5 changes: 3 additions & 2 deletions doc/bpmn/application.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ We initialize a scripting enviroment:
.. code-block:: python
script_env = TaskDataEnvironment({'datetime': datetime })
>script_engine = PythonScriptEngine(script_env)
script_engine = PythonScriptEngine(script_env)
The :code:`PythonScriptEngine` handles execution of script tasks and evaluation of gateway and DMN conditions.
We'll create the script engine based on it; execution and evaluation will occur in the context of this enviroment.
Expand Down Expand Up @@ -159,5 +159,6 @@ We then create our BPMN engine (:app:`engine/engine.py`) using each of these com
.. code-block:: python
from ..engine import BpmnEngine
engine = BpmnEngine(parser, serializer, handlers, script_env)
engine = BpmnEngine(parser, serializer, script_env)
The handlers are automatically passed to the curses UI by the main runner.
88 changes: 44 additions & 44 deletions doc/bpmn/custom_task_spec.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,27 @@ starts with a timer, the timer waits until the event occurs; this might be days
Of course, we can always check that it's waiting and serialize the workflow until that time. However, we might decide that
we don't want SpiffWorkflow to manage this at all. We could do this with a custom task spec.

First we'll create a new class
The code for this example can be found in :app:`misc/custom_start_event.py`.

There is a very simple diagram :bpmn:`timer_start.bpmn` with the process ID `timer_start` with a Start Event
with a Duration Timer of one day that can be used to illustrate how the custom task works. If you run this workflow
with any of the configurations provided, you'll see a `WAITING` Start Event; if you use the parser and serializer we
just created, you'll be propmted to complete the User Task immediately.

To run this model with the custom spec:

.. code:: python
./runner.py -e spiff_example.misc.custom_start_event add -p timer_start -b bpmn/tutorial/timer_start.bpmn
./runner.py -e spiff_example.misc.custom_start_event
First we'll create a new class.

.. note::

It might be better have the class's init method take both the event definition to use *and* the timer event
definition. Unfortunately, our parser is not terribly intuitive or easily extendable, so I've done it this
way to make this a little easier to follow.

.. code:: python
Expand All @@ -27,7 +47,7 @@ First we'll create a new class
super().__init__(wf_spec, bpmn_id, event_definition, **kwargs)
self.timer_event = None
When we create our custom event, we'll check to see if we're creating a Start Event with a :code:`TimerEventDefinition`, and
When we create our custom spec, we'll check to see if we're creating a Start Event with a :code:`TimerEventDefinition`, and
if so, we'll replace it with a :code:`NoneEventDefinition`. There are three different types of Timer Events, so we'll use
the base class for all three to make sure we account for TimeDate, Duration, and Cycle.

Expand All @@ -47,57 +67,44 @@ Whenever we create a custom task spec, we'll need to create a converter for it s
.. code:: python
from SpiffWorkflow.bpmn.serializer import BpmnWorkflowSerializer
from SpiffWorkflow.bpmn.serializer.default import EventConverter
from SpiffWorkflow.spiff.serializer.task_spec import SpiffBpmnTaskConverter
from SpiffWorkflow.spiff.serializer import DEFAULT_CONFIG
class CustomStartEventConverter(SpiffBpmnTaskConverter):
def __init__(self, registry):
super().__init__(CustomStartEvent, registry)
def to_dict(self, spec):
dct = super().to_dict(spec)
if spec.timer_event is not None:
dct['event_definition'] = self.registry.convert(spec.timer_event)
else:
dct['event_definition'] = self.registry.convert(spec.event_definition)
dct['event_definition'] = self.registry.convert(spec.event_definition)
dct['timer_event'] = self.registry.convert(spec.timer_event)
return dct
DEFAULT_CONFIG['task_specs'].remove(StartEventConverter)
DEFAULT_CONFIG['task_specs'].append(CustomStartEventConverter)
registry = BpmnWorkflowSerializer.configure(DEFAULT_CONFIG)
serializer = BpmnWorkflowSerializer(registry)
def from_dict(self, dct):
spec = super().from_dict(dct)
spec.event_definition = self.registry.restore(dct['event_definition'])
spec.timer_event = self.registry.restore(dct['timer_event'])
return spec
Our converter will inherit from the :code:`SpiffBpmnTaskConverter`, since that's our base generic BPMN mixin class.
The parent converter will handle serializing the standard BPMN attributes, as well as attributes added in the
:code:`spiff` package. There is a similar base converter in the :code:`bpmn.serializer.helpers` package.

The :code:`SpiffBpmnTaskConverter` itself inherits from
:code:`SpiffWorkflow.bpmn.serializer.helpers.task_spec.BpmnTaskSpecConverter`. which provides some helper methods for
extracting standard attributes from tasks; the :code:`SpiffBpmnTaskConverter` does the same for extensions from the
:code:`spiff` package.

We don't have to do much -- all we do is replace the event definition with the original. The timer event will be
moved when the task is restored, and this saves us from having to write a custom parser.

.. note::

It might be better have the class's init method take both the event definition to use *and* the timer event
definition. Unfortunately, our parser is not terribly intuitive or easily extendable, so I've done it this
way to make this a little easier to follow.
A converter needs to implement two methods: :code:`to_dict` (which takes a task spec and returns a JSON-serializable
dictionary of its attributes) and :code:`from_dict` (which takes the dictionary and returns a task spec of the
appropriate type. We call the base method to do most of the work, and then update the result to reflect the changes
we made, in this case ensuring that both event definitions are handled. The parent converter also provides :code:`convert`
and :code:`restore` methods to serialize any object that Spiff's serializer knows how to handle. For more details about
the serializer, see :doc:`serialization`.

When we create our serializer, we need to tell it about this task. We'll remove the converter for the standard Start
Event and add the one we created to the configuration. We then get a registry of classes that the serializer knows
about that includes our custom spec, as well as all the other specs and initialize the serializer with it.
When we create our serializer, we need to tell it about this task. The serializer is initialized with a mapping
of object class to converter class, so we just need to add an entry for this mapping.

.. note::
.. code:: python
The reason there are two steps involved (regurning a registry and *then* passing it to the serializer) rather
that using the configuration directly is to allow further customization of the :code:`registry`. Workflows
can contain arbtrary data, we want to provide developers the ability to serialization code for any object. See
:ref:`serializing_custom_objects` for more information about how this works.
SPIFF_CONFIG[CustomStartEvent] = CustomStartEventConverter
registry = FileSerializer.configure(SPIFF_CONFIG)
serializer = FileSerializer(dirname, registry=registry)
Finally, we have to update our parser:
We also have to tell the parser to use our class instead of the standard class.

.. code:: python
Expand All @@ -114,10 +121,3 @@ will use. This is a bit unintuitive, but that's how it works.

Fortunately, we were able to reuse an existing Task Spec parser, which simplifies the process quite a bit.

Having created a parser and serializer, we could create a configuration module and instantiate an engine with these
components.

There is a very simple diagram :bpmn:`timer_start.bpmn` with the process ID `timer_start` with a Start Event
with a Duration Timer of one day that can be used to illustrate how the custom task works. If you run this workflow
with any of the configurations provided, you'll see a `WAITING` Start Event; if you use the parser and serializer we
just created, you'll be propmted to complete the User Task immediately.
2 changes: 1 addition & 1 deletion doc/bpmn/imports.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Examples
--------

- :doc:`serialization`
- :doc:`custom_task_specs`
- :doc:`custom_task_spec`

DMN
===
Expand Down
Loading

0 comments on commit 3ca0a57

Please sign in to comment.