{ "info": { "author": "Daniel Gerlag", "author_email": "daniel@gerlag.ca", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.6" ], "description": "# LiteFlow\n\nLiteFlow is a Python library for running workflows. Think: long running processes with multiple tasks that need to track state. It supports pluggable persistence and concurrency providers to allow for multi-node clusters.\n\n* [Installation](#installation)\n* [Basic Concepts](#basic-concepts)\n\t* [Steps](#steps)\n\t* [Passing data between steps](#passing-data-between-steps)\n\t* [Events](#events)\n* [Flow control](#flow-control)\n\t* [Parallel ForEach](#parallel-foreach)\n\t* [While condition](#while-condition)\n\t* [If condition](#if-condition)\n* [Host](#host)\n* [Persistence](#persistence)\n* [Multi-node clusters](#multi-node-clusters)\n* [Samples](#samples)\n\n## Installation\n\nInstall the \"liteflow.core\" package\n\n```\n> pip install liteflow.core\n```\n\n\n## Basic Concepts\n\n### Steps\n\nA workflow consists of a series of connected steps. Each step produces an outcome value and subsequent steps are triggered by subscribing to a particular outcome of a preceeding step.\nSteps are usually defined by inheriting from the StepBody abstract class and implementing the *run* method.\n\nFirst we define some steps\n\n```python\nfrom liteflow.core import *\n\n\nclass Hello(StepBody):\n def run(self, context: StepExecutionContext) -> ExecutionResult:\n print(\"Hello world\")\n\n\nclass Goodbye(StepBody):\n def run(self, context: StepExecutionContext) -> ExecutionResult:\n print(\"Goodbye\")\n return ExecutionResult.next()\n\n```\n\nThen we define the workflow structure by composing a chain of steps.\n\n```python\nclass MyWorkflow(Workflow):\n\n def id(self):\n return \"MyWorkflow\"\n\n def version(self):\n return 1\n\n def build(self, builder: WorkflowBuilder):\n builder\\\n .start_with(Hello)\\\n .then(Goodbye)\n\n```\nThe `id` and `version` properties are used by the workflow host to identify a workflow definition.\n\nEach running workflow is persisted to the chosen persistence provider between each step, where it can be picked up at a later point in time to continue execution. The outcome result of your step can instruct the workflow host to defer further execution of the workflow until a future point in time or in response to an external event.\n\nThe first time a particular step within the workflow is called, the persistenceData property on the context object is *None*. The ExecutionResult produced by the *run* method can either cause the workflow to proceed to the next step by providing an outcome value, instruct the workflow to sleep for a defined period or simply not move the workflow forward. If no outcome value is produced, then the step becomes re-entrant by setting persistenceData, so the workflow host will call this step again in the future buy will popluate the persistenceData with it's previous value.\n\n\n### Passing data between steps\n\nEach step is intended to be a black-box, therefore they support inputs and outputs. Each workflow instance carries a data property for holding 'workflow wide' data that the steps can use to communicate.\n\nThe following sample shows how to define inputs and outputs on a step, it then shows how to map the inputs and outputs to properties on the workflow data property.\n\n```python\n#Our workflow step with inputs and outputs\nclass AddNumbers(StepBody):\n def __init__(self):\n self.input1 = 0\n self.input2 = 0\n self.output = 0\n\n def run(self, context: StepExecutionContext) -> ExecutionResult:\n self.output = self.input1 + self.input2\n return ExecutionResult.next()\n\n\n#A class to hold workflow wide data\nclass MyData:\n def __init__(self):\n self.value1 = 0\n self.value2 = 0\n self.value3 = 0\n\n\n#Our workflow definition with mapped inputs & outputs\nclass MyWorkflow(Workflow):\n def build(self, builder: WorkflowBuilder):\n builder\\\n .start_with(Hello)\\\n .then(AddNumbers) \\\n .input('input1', lambda data, context: data.value1) \\\n .input('input2', lambda data, context: data.value2) \\\n .output('value3', lambda step: step.output) \\\n .then(Goodbye)\n\n```\n\n### Events\n\nA workflow can also wait for an external event before proceeding. In the following example, the workflow will wait for an event called *\"event1\"* with a key of *\"key1\"*. Once an external source has fired this event, the workflow will wake up and continue processing.\n\n```python\nclass MyWorkflow(Workflow):\n def build(self, builder: WorkflowBuilder):\n builder\\\n .start_with(Hello) \\\n .wait_for('event1', lambda data, context: 'key1') \\\n .then(Goodbye)\n\n\n#External events are published via the host\n#All workflows that have subscribed to event1, key1, will be passed \"hello\"\nhost.publish_event('event1', 'key1', 'hello')\n\n#Data from the published event can be captured and mapped to the workflow data object with an output on the WaitFor step\nclass MyWorkflow(Workflow):\n def build(self, builder: WorkflowBuilder):\n builder\\\n .start_with(Hello) \\\n .wait_for('event1', lambda data, context: 'key1') \\\n .output('captured_value', lambda step: step.event_data) \\\n .then(Goodbye)\n\n```\n\n### Flow Control\n\n#### Parallel ForEach\n\n```python\nclass DoStuff(StepBody):\n def run(self, context: StepExecutionContext) -> ExecutionResult:\n print(f\"doing stuff...{context.execution_pointer.context_item}\")\n return ExecutionResult.next()\n\n\nclass MyWorkflow(Workflow):\n\n def build(self, builder: WorkflowBuilder):\n builder\\\n .start_with(Hello)\\\n .for_each(lambda data, context: [\"abc\", \"def\", \"xyz\"])\\\n .do(lambda x:\\\n x.start_with(DoStuff))\\\n .then(Goodbye)\n\n```\n\n#### While condition\n\n```python\nclass MyWorkflow(Workflow):\n def build(self, builder: WorkflowBuilder):\n builder\\\n .start_with(Hello)\\\n .while_(lambda data, context: data.value1 < 3)\\\n .do(lambda do:\\\n do.start_with(DoStuff)\\\n .input('my_value', lambda data, context: data.value1)\\\n .output('value1', lambda step: step.your_value))\\\n .then(Goodbye)\n\n```\n\n#### If condition\n\n```python\nclass MyWorkflow(Workflow):\n\n def build(self, builder: WorkflowBuilder):\n builder\\\n .start_with(Hello)\\\n .if_(lambda data, context: data.value1 > 3)\\\n .do(lambda x:\\\n x.start_with(DoStuff))\\\n .then(Goodbye)\n\n```\n\n\n### Host\n\nThe workflow host is the service responsible for executing workflows. It does this by polling the persistence provider for workflow instances that are ready to run, executes them and then passes them back to the persistence provider to by stored for the next time they are run. It is also responsible for publishing events to any workflows that may be waiting on one.\n\n#### Usage\n\nWhen your application starts, create a WorkflowHost service using `configure_workflow_host`, call *register_workflow*, so that the workflow host knows about all your workflows, and then call *start* to fire up the event loop that executes workflows. Use the *start_workflow* method to initiate a new instance of a particular workflow.\n\n\n```python\nfrom liteflow.core import *\n\n\nhost = configure_workflow_host()\nhost.register_workflow(MyWorkflow())\nhost.start()\n\nwid = host.start_workflow(\"MyWorkflow\", 1, None)\n\n```\n\n## Persistence\n\nSince workflows are typically long running processes, they will need to be persisted to storage between steps.\nThere are several persistence providers available as separate packages.\n\n* Memory Persistence Provider *(Default provider, for demo and testing purposes)*\n* [MongoDB](https://github.com/danielgerlag/liteflow/tree/master/providers/mongodb)\n* *(more to come soon...)*\n\n## Multi-node clusters\n\nBy default, the WorkflowHost service will run as a single node using the built-in queue and locking providers for a single node configuration. Should you wish to run a multi-node cluster, you will need to configure an external queueing mechanism and a distributed lock manager to co-ordinate the cluster. These are the providers that are currently available.\n\n#### Queue Providers\n\n* SingleNodeQueueProvider *(Default built-in provider)*\n* [Azure](https://github.com/danielgerlag/liteflow/tree/master/providers/azure)\n* RabbitMQ *(coming soon...)*\n\n\n#### Distributed lock managers\n\n* LocalLockProvider *(Default built-in provider)*\n* [Azure](https://github.com/danielgerlag/liteflow/tree/master/providers/azure)\n* Redis Redlock *(coming soon...)*\n\n\n## Samples\n\n* [Hello World](https://github.com/danielgerlag/liteflow/blob/master/samples/01-hello-world.py)\n\n* [Passing Data](https://github.com/danielgerlag/liteflow/blob/master/samples/02-passing-data.py)\n\n* [Events](https://github.com/danielgerlag/liteflow/blob/master/samples/03-waiting-for-events.py)\n\n* [Parallel ForEach](https://github.com/danielgerlag/liteflow/blob/master/samples/04-foreach.py)\n\n* [If condition](https://github.com/danielgerlag/liteflow/blob/master/samples/05-if.py)\n\n* [While loop](https://github.com/danielgerlag/liteflow/blob/master/samples/06-while.py)\n\n\n## Authors\n\n* **Daniel Gerlag** - *Initial work*\n\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details\n\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/danielgerlag/liteflow", "keywords": "workflow", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "liteflow.core", "package_url": "https://pypi.org/project/liteflow.core/", "platform": "", "project_url": "https://pypi.org/project/liteflow.core/", "project_urls": { "Homepage": "https://github.com/danielgerlag/liteflow" }, "release_url": "https://pypi.org/project/liteflow.core/0.3.1/", "requires_dist": [ "python-interface (>=1.4.0)" ], "requires_python": ">=3.6", "summary": "Workflow library with pluggable persistence and scale out support", "version": "0.3.1" }, "last_serial": 3993995, "releases": { "0.2.1": [ { "comment_text": "", "digests": { "md5": "597e6a6d4542997eab6c6da1a3c94bd6", "sha256": "e02b47c07ee578aed90d78a7e5f63adf413b10179873c7ea04f2c10fd2c80bb8" }, "downloads": -1, "filename": "liteflow.core-0.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "597e6a6d4542997eab6c6da1a3c94bd6", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 31933, "upload_time": "2018-06-21T03:49:33", "url": "https://files.pythonhosted.org/packages/cc/58/41ecaea0264c5586a67c9db38ce619e5415854b7aadb490f4fb5483ca9d7/liteflow.core-0.2.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "a51a0692f130ecf5117db9ebfff0bc42", "sha256": "4d94d24b4f03c6985afdbaa6a5b2892c67736ce6debe2d498a0fddb3989e710d" }, "downloads": -1, "filename": "liteflow.core-0.2.1.tar.gz", "has_sig": false, "md5_digest": "a51a0692f130ecf5117db9ebfff0bc42", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 14097, "upload_time": "2018-06-21T03:49:35", "url": "https://files.pythonhosted.org/packages/e3/73/c2348ff3915836db72351a55260041d38c1bf40ea59efebb4142e78a8cb1/liteflow.core-0.2.1.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "24743a23825b611fd4066fb626eacea6", "sha256": "bf4101c3868bc46dfd4c7f7a592d28954f598741110cc6118644c9f49ef66f68" }, "downloads": -1, "filename": "liteflow.core-0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "24743a23825b611fd4066fb626eacea6", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 31908, "upload_time": "2018-06-21T03:56:41", "url": "https://files.pythonhosted.org/packages/cb/17/0d2fae7d31f50801f591425ac3d446f7f1f90806ace427425eeaac9a78a1/liteflow.core-0.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "fb7ddb246eef07039527a0e1474151c0", "sha256": "581d4247e450b93958877e72d58ff6cd4248a14cd9b049e027782a5c54a09915" }, "downloads": -1, "filename": "liteflow.core-0.3.tar.gz", "has_sig": false, "md5_digest": "fb7ddb246eef07039527a0e1474151c0", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 14098, "upload_time": "2018-06-21T03:56:42", "url": "https://files.pythonhosted.org/packages/60/47/11e5f6af10377f301a29b0b62400b0b25afd7a473933607013ee71445b7b/liteflow.core-0.3.tar.gz" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "ec88223b6a28fdb2ce74c5a5aa2684c2", "sha256": "a80aaf44059ab513544f04fe4e4e4f7ab9113784db87769f76ef5f7cc459ce55" }, "downloads": -1, "filename": "liteflow.core-0.3.1-py3-none-any.whl", "has_sig": false, "md5_digest": "ec88223b6a28fdb2ce74c5a5aa2684c2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 31867, "upload_time": "2018-06-24T01:42:17", "url": "https://files.pythonhosted.org/packages/90/a7/4bd1904f080cd21cca8600c1a86a34ad3875e31f3a9dd1f38b38648e7ab7/liteflow.core-0.3.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "fbd91bd5eb0b117f76dd5fe69eb9e283", "sha256": "f7ea987eb5981545cf6d3a3d72c3711218c04380cb738e722c0494431445e5cd" }, "downloads": -1, "filename": "liteflow.core-0.3.1.tar.gz", "has_sig": false, "md5_digest": "fbd91bd5eb0b117f76dd5fe69eb9e283", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 14171, "upload_time": "2018-06-24T01:42:28", "url": "https://files.pythonhosted.org/packages/59/90/624bb70c6ac70d9b3c375d7ef3236f441c30bf729cef29ea47b989098a0a/liteflow.core-0.3.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "ec88223b6a28fdb2ce74c5a5aa2684c2", "sha256": "a80aaf44059ab513544f04fe4e4e4f7ab9113784db87769f76ef5f7cc459ce55" }, "downloads": -1, "filename": "liteflow.core-0.3.1-py3-none-any.whl", "has_sig": false, "md5_digest": "ec88223b6a28fdb2ce74c5a5aa2684c2", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 31867, "upload_time": "2018-06-24T01:42:17", "url": "https://files.pythonhosted.org/packages/90/a7/4bd1904f080cd21cca8600c1a86a34ad3875e31f3a9dd1f38b38648e7ab7/liteflow.core-0.3.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "fbd91bd5eb0b117f76dd5fe69eb9e283", "sha256": "f7ea987eb5981545cf6d3a3d72c3711218c04380cb738e722c0494431445e5cd" }, "downloads": -1, "filename": "liteflow.core-0.3.1.tar.gz", "has_sig": false, "md5_digest": "fbd91bd5eb0b117f76dd5fe69eb9e283", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 14171, "upload_time": "2018-06-24T01:42:28", "url": "https://files.pythonhosted.org/packages/59/90/624bb70c6ac70d9b3c375d7ef3236f441c30bf729cef29ea47b989098a0a/liteflow.core-0.3.1.tar.gz" } ] }