# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['pyservice']

package_data = \
{'': ['*']}

setup_kwargs = {
    'name': 'pyservice',
    'version': '0.0.11',
    'description': 'A light-service like project in Python',
    'long_description': '# `pyservice`\n\n![build](https://github.com/adomokos/pyservice/workflows/Python%20Build/badge.svg)\n[![PyPI version](https://badge.fury.io/py/pyservice.svg)](https://badge.fury.io/py/pyservice)\n[![Coverage](https://raw.githubusercontent.com/adomokos/pyservice/master/coverage.svg)](https://raw.githubusercontent.com/adomokos/pyservice/master/coverage.svg)\n[![License](https://img.shields.io/badge/license-MIT-green.svg)](http://opensource.org/licenses/MIT)\n\nA [light-service](https://github.com/adomokos/light-service) influenced project in Python.\n\n## Intro\n\nAre you tired of 500 lines long Python code with conditionals, iterators, and function calls? Testing this logic is close to impossible, and with the lack of testing, you don\'t dare to touch it.\n\nAll complex logic can be decomposed into small functions, invoked sequentially. The functions should protect themselves from execution if a previous failure\xa0occurs, a more elegant solution exists: [Railway-Oriented Programming](https://fsharpforfunandprofit.com/rop/).\n\nLet\'s see how that looks with `pyservice`. There are two functions, one that adds 2 to the initial number, and one that adds 3. The data is carried over between the functions in an extended dictionary we call [Context](https://github.com/adomokos/pyservice/blob/master/pyservice/context.py), just like how a conveyor\xa0belt would be used in an assembly line:\n\n```python\nfrom pyservice import action, Context, Organizer\n\n\n@action()\ndef add_two(ctx: Context) -> Context:\n    number = ctx.get("n", 0)\n\n    ctx["result"] = number + 2\n\n    return ctx\n\n\n@action()\ndef add_three(ctx: Context) -> Context:\n    result = ctx["result"]\n\n    ctx["result"] = result + 3\n\n    return ctx\n\n\ndef test_can_run_functions():\n    ctx = Context.make({"n": 4})\n    organizer = Organizer([add_two, add_three])\n    result_ctx = organizer.run(ctx)\n\n    assert ctx.is_success\n    assert result_ctx["result"] == 9\n```\n\nThe `Context` is an extended dictionary, it stores failure and success states in it besides its key-value pairs. This is the "state" that is carried between the actions by the [Organizer](https://github.com/adomokos/pyservice/blob/master/pyservice/organizer.py). All Organizers expose a `run` function that is responsible for executing the provided actions in order.\n\nThis is the happy path, but what happens when there is a failure between the two functions? I add a `fail_context` function that will fail the context with a message:\n\n```python\n@action()\ndef fail_context(ctx: Context) -> Context:\n    ctx.fail("I don\'t like what I see here")\n    return ctx\n```\n\nThe context will be in a failure state and only the first action will be executed as processing stops after the second action (4+2=6):\n\n```python\ndef test_can_run_functions_with_failure():\n    ctx = Context.make({"n": 4})\n    organizer = Organizer([add_two, fail_context, add_three])\n    result_ctx = organizer.run(ctx)\n\n    assert ctx.is_failure\n    assert result_ctx["result"] == 6\n```\n\nLook at the actions, no conditional logic was added to them, the function wrapper protects the action from execution once it\'s in a failure state.\n\nYou can find these examples [here](https://github.com/adomokos/pyservice/blob/master/test/readme_examples/test_example_1.py).\n\nBut there is more to it!\n\n\n## Expects and Promises\n\nYou can define contracts for the actions with the `expects` and `promises` list of keys like this:\n\n```python\n@action(expects=["n"], promises=["result"])\ndef add_two(ctx: Context) -> Context:\n    number = ctx.get("n", 0)\n\n    ctx["result"] = number + 2\n\n    return ctx\n\n\n@action(expects=["result"])\ndef add_three(ctx: Context) -> Context:\n    result = ctx["result"]\n\n    ctx["result"] = result + 3\n\n    return ctx\n```\n\nThe `action` will verify - before it\'s invoked - that the expected keys are in the `Context` hash. If there are any missing, `ExpectedKeyNotFoundError` will be thrown and all of the missing keys will be listed in the exception message. Similarly, `PromisedKeyNotFoundError` is raised when the action fails to provide a value with the defined promised keys.\n\nYou can find the relevant examples [here](https://github.com/adomokos/pyservice/blob/master/test/readme_examples/test_example_2.py).\n\n## Rollback\n\nOne of your actions might fail while they have logic that permanently changes state in a data store or in an API resource. A trivial example is charging your customer while you can\'t complete the order. When that happens, you can leverage `pyservice`\'s  `rollback` functionality like this:\n\n```python\ndef add_two_rollback(ctx: Context) -> Context:\n    ctx["result"] -= 2\n    return ctx\n\n\n@action(expects=["n"], promises=["result"], rollback=add_two_rollback)\ndef add_two(ctx: Context) -> Context:\n    number = ctx.get("n", 0)\n\n    ctx["result"] = number + 2\n\n    return ctx\n\n\n@action()\ndef fail_context(ctx: Context) -> Context:\n    ctx.fail("I don\'t like what I see here")\n    raise Organizer.ContextFailed(fail_context)\n```\n\nThe action accepts a function reference to roll back its state changes when a `Context` fails. The rollback field is optional, nothing happens when you don\'t provide one.\n\nTake a look at [this](https://github.com/adomokos/pyservice/blob/master/test/readme_examples/test_example_3.py) basic example.\n',
    'author': 'Attila Domokos',
    'author_email': 'adomokos@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/adomokos/pyservice',
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.6,<4.0',
}


setup(**setup_kwargs)
