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

packages = \
['stirfried']

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

install_requires = \
['celery>=4.4.0,<5.0.0', 'python-socketio>=4.4.0,<5.0.0', 'redis>=3.4.1,<4.0.0']

extras_require = \
{'msgpack': ['msgpack-python>=0.5.6,<0.6.0'],
 'server': ['aioredis>=1.3.1,<2.0.0',
            'uvicorn>=0.11.3,<0.12.0',
            'starlette>=0.13.2,<0.14.0']}

setup_kwargs = {
    'name': 'stirfried',
    'version': '0.7.0',
    'description': 'Socket.IO server to schedule Celery tasks from clients in real-time.',
    'long_description': '[![PyPI version](https://badge.fury.io/py/stirfried.svg)](https://badge.fury.io/py/stirfried)\n[![Docker Image Version (latest semver)](https://img.shields.io/docker/v/korijn/stirfried?label=docker%20image)](https://hub.docker.com/r/korijn/stirfried)\n\n# Stirfried \U0001f961\n\nStirfried is an ASGI HTTP/Socket.IO server that provides both browser-based and regular clients with real-time control over Celery tasks.\n\nTasks are scheduled by name, meaning the server won\'t necessarily need an update when changes are made to the workers and tasks available to it.\n\nStirfried implements a simple-to-scale, three layered architecture: clients, servers and workers. Any layer can be scaled out by adding more instances.\n\nStirfried provides Socket.IO and HTTP APIs with three core functions:\n\n* Schedule a task\n* Revoke a task\n* Query task info\n\nWant to see Stirfried in action before digging through the README? Try running the [example](#example-code).\n\nBuilt on:\n\n* [starlette](https://www.starlette.io/) & [uvicorn](https://www.uvicorn.org/)\n* [python-socketio](https://github.com/miguelgrinberg/python-socketio)\n* [celery](http://www.celeryproject.org/)\n\n## Workers\n\nInstall Stirfried in your Celery workers via pip/pipenv/poetry:\n\n`pip install stirfried`\n\nImport the `StirfriedTask`:\n\n```python\nfrom stirfried.celery import StirfriedTask\n```\n\nConfigure the base class globally:\n\n```python\napp = Celery(..., task_cls=StirfriedTask)\n```\n\n...or per task:\n\n```python\n@app.task(base=StirfriedTask)\ndef add(x, y, room=None):\n    return x + y\n```\n\n## Servers\n\nThe server can be run by running the `korijn/stirfried` Docker container and\nexposing port 8000, or alternatively by cloning this repo, installing the dependencies\nwith poetry and starting the `uvicorn` server as demonstrated in the example code.\n\nYou can configure both the servers and workers with a `settings.py` file, via the standard Celery configuration mechanism. In the docker deployment scenario, you can mount the settings file to the path `/app/settings.py`.\n\n## Clients\n\nClients can connect to the Socket.IO API using standard [Socket.IO](https://socket.io/) libraries, and to the HTTP API using plain `window.fetch`.\n\n## Task object schema\n\nTasks are scheduled by submitting the following task object to either of the APIs:\n\n```javascript\n{\n    "task_name": "",  // (required) task name\n    "args": [],       // (optional) task arguments\n    "kwargs": {},     // (optional) task keyword arguments\n    "room": "",       // (optional) custom room override, only processed if\n                      //            `custom_rooms` is enabled\n                      //            NOTE: can also be used to disable server events\n                      //                  for this task by passing the sentinel room\n                      //                  ("NO_EMIT" by default)\n    "chain": []       // (optional) array of task objects to chain onto the main task\n                      //            task objects use the same schema, except for\n                      //            the `chain` property which cannot be nested further\n                      //            NOTE: chained tasks are applied in reverse order\n}\n```\n\n## Socket.IO API\n\nEvents are described in the following format: `name(args[, optional]) -> callback_args`\n\nClients can **emit** any of the following events that servers are listening for:\n\n| Event | Description |\n| ----- | ----------- |\n| `send_task({task_name[, args][, kwargs][, room][, chain]}) -> {status, data}` | Schedule a task. Use a callback to receive the reply in the client. `status` indicates if scheduling succeeded and `data` contains the task id or error message in case of failure. The client can use the task id as reference when processing subsequent server-emitted events. Reference the [task object schema](#task-object-schema) for more details. |\n| `revoke_task(task_id)` | Revoke a task. Will not fail if the task does not exist, and won\'t do anything if the task is already running. |\n| `task_info(task_id) -> {id, state, result}` | Query task info. Use a callback to receive the reply in the client. Only works if a Celery result backend is configured. |\n\nClients can **listen** for the following server-emitted events, which directly hook into the Celery Task class callbacks, except for `on_progress` which is a Stirfried addition and may be implemented by tasks to support progress events:\n\n| Event | Description |\n| ----- | ----------- |\n| `on_progress({current, total, info, task_id, task_name})` | Emitted on task progress updates. This event will only be emitted if tasks call `emit_progress`. |\n| `on_retry({task_id, task_name[, einfo]})` | Emitted automatically on task retries. `einfo` is only included if `stirfried_error_info=True`. |\n| `on_failure({task_id, task_name[, einfo]})` | Emitted automatically on task failure. `einfo` is only included if `stirfried_error_info=True`. |\n| `on_success({retval, task_id, task_name})` | Emitted automatically on task success. |\n| `on_return({status, retval, task_id, task_name})` | Emitted automatically on task success and failure. |\n\n## HTTP API\n\n| Endpoint | Description |\n| -------- | ----------- |\n| `POST /task` | Schedule a task. Submit the Task object as JSON the body of the request. Reference the [task object schema](#task-object-schema) for more details. |\n| `DELETE /task/{id}` | Revoke a task. Will not fail if the task does not exist, and won\'t do anything if the task is already running. |\n| `GET /task/{id}` | Query task info. Only works if a Celery result backend is configured. |\n\n## Settings\n\nYou can configure Celery, Socket.IO and Stirfried all from the same `settings.py` file. Stirfried settings are prefixed with `stirfried_`, Socket.IO settings are prefixed with `socketio_`, and Celery settings are used as-is (not prefixed).\n\nSocket.IO server settings are passed on directly (but without the prefix) to the `AsyncServer` constructor of the python-socketio library, see their [documentation](https://python-socketio.readthedocs.io/en/latest/api.html#asyncserver-class) for the options that are available. See the Celery [documentation](https://docs.celeryproject.org/en/stable/userguide/configuration.html) for the options there.\n\nThe following options are additionally available for configuring Stirfried **servers and workers**:\n\n| Key | Type | Default | Description |\n| --- | ---- | ------- | ----------- |\n| `stirfried_enable_http` | `bool` | `True` | Set to `False` to disable the HTTP API. |\n| `stirfried_enable_socketio` | `bool` | `True` | Set to `False` to disable the Socket.IO API. |\n| `stirfried_enable_task_info` | `bool` | `True` | Set to `False` to disable the `task_info` event and `GET /task/{id}` endpoint. |\n| `stirfried_enable_revoke_task` | `bool` | `True` | Set to `False` to disable the `revoke_task` event and `DELETE /task/{id}` endpoint. |\n| `stirfried_redis_url` | `str` | `""` | Connection string for the Socket.IO API server-to-server communication over Redis pubsub. Required if you want workers to be able to emit events. |\n| `stirfried_available_tasks` | `List[str]` | `[]` | If non-empty, `send_task` and `POST /task` will fail if a task name is not contained in the list. |\n| `stirfried_error_info` | `bool` | `False` | Set to `True` to include error messages and tracebacks in events, event callbacks and HTTP responses. |\n| `stirfried_sentinel_room` | `str` | `"NO_EMIT"` | A magic string value that can be passed to the `room` argument to prevent workers from emitting events for a task. This is the default `room` value for task sent to the HTTP API since there is no Socket.IO client. |\n| `stirfried_custom_rooms` | `bool` | `False` | Set to `True` to allow clients to override the default `room` for server-emitted events. |\n| `stirfried_header_task_map` | `Dict[str, Dict[str, str]` | `{}` | Configure to map headers to keyword arguments for specific tasks. For example, `{"send_email": {"Date": "date"}}`, would cause the `Date` header\'s value to be injected into the keyword argument `date` whenever a `send_email` task is scheduled. This can be used in concert with Socket.IO\'s `extraHeaders` feature to implement authorization and validation.\n\n## Rooms\n\nFor Socket.IO API scheduled tasks, server-emitted events are sent to the client that scheduled the task by default. For HTTP API scheduled tasks, server-emitted events are not emitted by default. The server accomplishes this by injecting a value into\nthe `room` keyword argument of Stirfried Celery tasks.\n\nThe `StirfriedTask` base class depends on the presence of this keyword argument.\n\nThis means you are required to add the keyword argument `room=None` to your\ntask definitions in order to receive it.\n\nIf `custom_rooms` is enabled, clients can override the value by sending along a custom `room` value (though not via the keyword arguments of the task, see the API schema documentation).\n\n## Progress events\n\nYou can emit progress events from workers by calling `self.emit_progress(current, total, info=None)` in a task.\n\nYou can use the optional `info` keyword argument to send along arbitrary metadata, such as a progress message or early results.\n\nNote that you are required to pass `bind=True` to the `celery.task` decorator\nin order to get access to the `self` instance variable.\n\n```python\n@celery.task(bind=True)\ndef add(self, x, y, room=None):\n    s = x\n    self.emit_progress(50, 100)  # 50%\n    s += y\n    return s\n```\n\n## Binary/big data\n\nSocket.IO clients, servers and Celery workers support the `msgpack`\ntransport, allowing you to use binary data directly (without needing to manually convert\nto and from base64 encoded strings and suffering the according performance penalty).\n\nYou should also be aware of [limitations in Redis](http://redis.io/topics/clients#output-buffers-limits)\non client output buffers. This means that you cannot emit events greater than a certain\nvalue (32mb by default). You can override this setting in various ways, here\'s how to do\nit via the Redis server CLI:\n\n```bash\n# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>\nredis-server --client-output-buffer-limit pubsub 256mb 128mb 30\n```\n\n## Testing\n\nWhen unit testing a Stirfried Celery worker, the recommended approach is to disable the\nRedis connection by simply leaving out `stirfried_redis_url` from your settings, and to\ndirectly call the task functions in unit tests. The lack of a Redis connection will\nshort-circuit any events that would normally be emitted. This setup will allow you to\ntreat tasks as regular functions and perform unit testing as usual.\n\nOptionally, you can patch/mock any calls to `self.emit_progress` using standard Python\ntesting utilities to test those too.\n\n## Example code\n\nThe repo includes an example demonstrating all of the functionality provided by Stirfried.\n\nYou can run the example as follows:\n\n* Clone the repository\n* `cd` into the `example` directory\n* Run `docker-compose build`\n* Then `docker-compose up`\n* Open your browser and go to `http://localhost:8080/`\n* You should see the following interface and are ready to give Stirfried a try:\n\n![Stirfried \U0001f961 test client](https://user-images.githubusercontent.com/1882046/76843175-b2c78200-683b-11ea-92df-b2169a7ce9ce.png)\n',
    'author': 'Korijn van Golen',
    'author_email': 'korijn@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': None,
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'extras_require': extras_require,
    'python_requires': '>=3.6,<4.0',
}


setup(**setup_kwargs)
