# Faktory Client Python (faktory_worker_python)
This repository provides Python Client (Consumer and Producer) for the [Faktory](https://github.com/contribsys/faktory/) background job server.
```
+--------------------+
| |
| Faktory |
| Server |
+---->>>>| +>>>>----+
| | | |
| | | |
| +--------------------+ |
| |
| |
| |
| |
+-----------------------------------------------------------+
| . Faktory . |
| . Client . |
| . . |
| +-----------------+ +-----------------+ |
| | | | | |
| | Producer | | Consumer | |
| | pushes | | (Worker) | |
| | jobs | | fetches | |
| | | | jobs | |
| | | | | |
| +-----------------+ +-----------------+ |
| |
+-----------------------------------------------------------+
```
- [Server](https://github.com/contribsys/faktory/) - the Faktory daemon which stores background jobs in queues to be processed by Workers.
- Client - an entity that communicates with the Faktory server using the [FWP](https://github.com/contribsys/faktory/blob/master/docs/protocol-specification.md). A single client can act as both a consumer and a producer.
- Consumer (or Worker) - a client that fetches work units (jobs) from the server for execution.
- Producer - a client that issues new work units to the server.
This library tries to implement the [FWP](https://github.com/contribsys/faktory/blob/master/docs/protocol-specification.md) as well as possible. If you notice any inconsistencies, please report them.
## Installation
```
pip install pyfaktory
```
## Usage
### Faktory Server
If you have a Faktory server already running, make sure you have the correct url.
```python
# Default url for a Faktory server running locally
faktory_server_url = 'tcp://localhost:7419'
```
For the installation of faktory, please refer to [the official documentation](https://github.com/contribsys/faktory/wiki/Installation).
After installation, you can run it locally.
```console
$ /usr/bin/faktory
Faktory 1.8.0
```
You can use a password for the Faktory server via the environment variable `FAKTORY_PASSWORD`. Note if this value starts with a `/`, then it is considered a pointer to a file on the filesystem with the password. By default `/etc/faktory/password` is used.
The format of the Faktory URL is as follows:
```
tcp://:password@localhost:7419
```
You can access the [Fakotry GUI](http://localhost:7420/).
To run Faktory in production:
```
/usr/bin/faktory -e production
```
Faktory in production mode requires a password by default since version 0.7.0.
### Faktory Client
Import `pyfaktory`.
```python
from pyfaktory import Client, Consumer, Job, Producer
```
A single client can act as both a consumer and a producer. (You can enable IPv6 by setting `enable_ipv6` to `True`)
```python
client = Client(faktory_url='tcp://localhost:7419')
client.connect()
# Now you can use the client
# At the end, disconnect the client
client.disconnect()
```
Client is a context manager, so you can use `with` statement.
```python
with Client(faktory_url='tcp://localhost:7419') as client:
# Use client
```
Use `role` argument to say how you want to use the client. This argument has
three possible values: 'consumer', 'producer' or 'both'.
```python
# A client that acts as both a consumer and a producer.
client = Client(faktory_url='tcp://localhost:7419', role='both')
```
### Producer
Use the client to push jobs.
#### Push job
```python
with Client(faktory_url='tcp://localhost:7419', role='producer') as client:
producer = Producer(client=client)
job_1 = Job(jobtype='adder', args=(5, 4), queue='default')
producer.push(job_1)
```
#### Push bulk jobs
You can push several jobs at once. There is no limit, but 1000 at a time is recommended as a best practice.
```python
with Client(faktory_url='tcp://localhost:7419', role='producer') as client:
producer = Producer(client=client)
job_2 = Job(jobtype='adder', args=(50, 41))
job_3 = Job(jobtype='adder', args=(15, 68))
res = producer.push_bulk([job_2, job_3])
```
### Consumer (Worker)
Use a worker to pull jobs from Faktory server and execute them.
```python
def adder(x, y):
logging.info(f"{x} + {y} = {x + y}")
with Client(faktory_url='tcp://localhost:7419', role='consumer') as client:
consumer = Consumer(client=client, queues=['default'], concurrency=1)
consumer.register('adder', adder)
consumer.run()
```
Use `priority` to indicates in which queue order the jobs should be fetched
first.
```python
# With strict priority, there is a risk of starvation
consumer = Consumer(client=client, queues=['critical', 'default', 'bulk'], priority='strict')
# Each queue has an equal chance of being fetched first
consumer = Consumer(client=client, queues=['critical', 'default', 'bulk'], priority='uniform')
# Weights must be specified
consumer = Consumer(client=client, queues=['critical', 'default', 'bulk'], priority='weighted', weights=[0.6, 0.3, 0.1])
```
### Capture exceptions using Sentry
To capture exceptions using Sentry before failling jobs
set `sentry_capture_exception` argument to `True`.
```python
consumer = Consumer(client=client, sentry_capture_exception=True)
```
### Info
You can get various information about the server using `info` Client method.
```python
with Client(faktory_url='tcp://localhost:7419') as client:
server_info = client.info()
print(server_info)
```
### Mutate
A wrapper for the [Mutate API](https://github.com/contribsys/faktory/wiki/Mutate-API) to script certain repairs or migrations.
⚠️ MUTATE commands can be slow and/or resource intensive. They should not be used as part of your application logic.
```python
from pyfaktory import Client, JobFilter, MutateOperation
client = Client(faktory_url='tcp://localhost:7419')
client.connect()
# Find all scheduled jobs with type `QuickbooksSyncJob` and discard them
op = MutateOperation(
cmd='discard',
target='scheduled',
filter=JobFilter(jobtype="QuickbooksSyncJob")
)
client.mutate(op)
# Clear the Retry queue completely
op = MutateOperation(
cmd='discard',
target='retries',
filter=JobFilter(regexp="*")
)
client.mutate(op)
# Clear the Retry queue completely
op = MutateOperation(
cmd='discard',
target='retries'
)
client.mutate(op)
# Send a two specific JIDs in the Retry queue to the Dead queue
op = MutateOperation(
cmd='kill',
target='retries',
filter=JobFilter(jobtype="QuickbooksSyncJob", jids=["123456789", "abcdefgh"])
)
client.mutate(op)
# Enqueue all retries with a first argument matching "bob"
op = MutateOperation(
cmd='requeue',
target='retries',
filter=JobFilter(regexp="*\"args\":[\"bob\"*")
)
client.mutate(op)
client.disconnect()
```
### Queues
**Pausing**
Queues may be paused (no job can be fetched from them while paused) or unpaused
(resume fetching).
```python
# Pause a list of queues
client.queue_pause(queues=['bulk', 'another_queue'])
# Pause all queues
client.queue_pause(all_queues=True)
# Unpause a list of queues
client.queue_unpause(queues=['bulk'])
# Unpause all queues
client.queue_unpause(all_queues=True)
```
**Remove**
Queues can be removed which deletes all jobs within them. It does not stop
currently executing jobs from those queues.
```python
# Remove a list of queues
client.queue_remove(queues=['bulk'])
# Remove all queues
client.queue_remove(all_queues=True)
```
### Batch (untested)
Refer to [documentation](https://github.com/contribsys/faktory/wiki/Ent-Batches).
```python
from pyfaktory import Batch, Client, Job, Producer, TargetJob
client = Client(faktory_url='tcp://localhost:7419')
client.connect()
producer = Producer(client=client)
batch = Batch(
description="An optional description for the Web UI",
success=TargetJob(jobtype="MySuccessCallback", args=[123], queue="critical"),
complete=TargetJob(jobtype="MyCompleteCallback", args=['aa'], queue="critical")
)
# Create a new batch
resp = producer.batch_new(batch)
# Push as many jobs as necessary for the batch
# You may nest batches
# The initial batch data has a TTL of 30 minutes and will expire if batch is not commited
producer.push(Job(jobtype='SomeJob', args=(5, 4), custom={"bid": bid}))
producer.push(Job(jobtype='SomeOtherJob', args=(0, 15), custom={"bid": bid}))
# Commit the batch
producer.batch_commit(bid)
client.disconnect()
```
Use `batch_open` to open a created batch.
```python
producer.batch_open(bid)
```
Use `parent_bid` for child batch.
```python
child_batch = Batch(
parent_bid=bid,
description="An optional description for the Web UI",
success=TargetJob(jobtype="MySuccessCallback", args=[123], queue="critical"),
complete=TargetJob(jobtype="MyCompleteCallback", args=['aa'], queue="critical")
)
```
### Custom command
If you want to use a Faktory command that is not yet implemented in this client library, you can send custom commands.
```python
from pyfaktory import Client
my_command = 'INFO\r\n'
with Client(faktory_url='tcp://localhost:7419') as client:
resp = client._send_and_receive(my_command)
print(resp)
```
### Outer multiprocessing
You can use outer multiprocessing.
```python
def run_worker():
try:
with Client(faktory_url=faktory_server_url, role='consumer') as client:
import multiprocessing
custom_context = multiprocessing.get_context("spawn")
consumer = Consumer(client=client, queues=['t_test'], concurrency=2, context=custom_context)
consumer.register('task_001', task_001)
consumer.run()
except Exception as e:
print(f"task running error: {str(e)}")
```
## Example
Find examples in `./examples`.
- Start the Faktory server.
```
/usr/bin/faktory
```
- Launch a producer.
```
python examples/fproducer.py
```
- Launch a consumer.
```
python examples/fconsumer.py
```
- Look at what is happening in the logs and in the [Faktory Web UI](http://localhost:7420/).
## Contribute
### Issues
If you encounter a problem, please report it.
In addition to the description of your problem, report the server and client
versions.
```python
pyfaktory.__version__
```
```
/usr/bin/faktory -v
```
Reproduce your problem while increasing the level of debugging for both the
server and the client, and report the logs.
```python
import logging
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',
level=logging.DEBUG,
datefmt='%Y-%m-%d %H:%M:%S')
```
```
/usr/bin/faktory -l debug
```
### PRs
Please, feel free to create pull requests.
## License
See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).
Raw data
{
"_id": null,
"home_page": "https://github.com/ghilesmeddour/faktory_worker_python",
"name": "pyfaktory",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.9",
"maintainer_email": null,
"keywords": null,
"author": "Ghiles Meddour",
"author_email": "ghiles.meddour.b@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/95/ac/9607af2bef6e9b7ed8097bbf179b1d01fefd27e9320115c4a3daef82b89b/pyfaktory-0.2.9.tar.gz",
"platform": null,
"description": "# Faktory Client Python (faktory_worker_python)\n\nThis repository provides Python Client (Consumer and Producer) for the [Faktory](https://github.com/contribsys/faktory/) background job server.\n\n```\n +--------------------+\n | |\n | Faktory |\n | Server |\n +---->>>>| +>>>>----+\n | | | |\n | | | |\n | +--------------------+ |\n | |\n | |\n | |\n | |\n+-----------------------------------------------------------+ \n| . Faktory . | \n| . Client . | \n| . . | \n| +-----------------+ +-----------------+ |\n| | | | | |\n| | Producer | | Consumer | |\n| | pushes | | (Worker) | |\n| | jobs | | fetches | |\n| | | | jobs | |\n| | | | | |\n| +-----------------+ +-----------------+ | \n| | \n+-----------------------------------------------------------+ \n```\n\n- [Server](https://github.com/contribsys/faktory/) - the Faktory daemon which stores background jobs in queues to be processed by Workers.\n- Client - an entity that communicates with the Faktory server using the [FWP](https://github.com/contribsys/faktory/blob/master/docs/protocol-specification.md). A single client can act as both a consumer and a producer.\n- Consumer (or Worker) - a client that fetches work units (jobs) from the server for execution.\n- Producer - a client that issues new work units to the server.\n\nThis library tries to implement the [FWP](https://github.com/contribsys/faktory/blob/master/docs/protocol-specification.md) as well as possible. If you notice any inconsistencies, please report them.\n\n## Installation\n\n```\npip install pyfaktory\n```\n\n## Usage\n\n### Faktory Server\n\nIf you have a Faktory server already running, make sure you have the correct url.\n\n```python\n# Default url for a Faktory server running locally\nfaktory_server_url = 'tcp://localhost:7419'\n```\n\nFor the installation of faktory, please refer to [the official documentation](https://github.com/contribsys/faktory/wiki/Installation).\n\nAfter installation, you can run it locally.\n\n```console\n$ /usr/bin/faktory\nFaktory 1.8.0\n```\n\nYou can use a password for the Faktory server via the environment variable `FAKTORY_PASSWORD`. Note if this value starts with a `/`, then it is considered a pointer to a file on the filesystem with the password. By default `/etc/faktory/password` is used.\n\nThe format of the Faktory URL is as follows:\n```\ntcp://:password@localhost:7419\n```\n\nYou can access the [Fakotry GUI](http://localhost:7420/).\n\nTo run Faktory in production:\n```\n/usr/bin/faktory -e production\n```\n\nFaktory in production mode requires a password by default since version 0.7.0.\n\n### Faktory Client\n\nImport `pyfaktory`.\n\n```python\nfrom pyfaktory import Client, Consumer, Job, Producer\n```\n\nA single client can act as both a consumer and a producer. (You can enable IPv6 by setting `enable_ipv6` to `True`)\n\n```python\nclient = Client(faktory_url='tcp://localhost:7419')\nclient.connect()\n\n# Now you can use the client\n\n# At the end, disconnect the client\nclient.disconnect()\n```\n\nClient is a context manager, so you can use `with` statement.\n\n```python\nwith Client(faktory_url='tcp://localhost:7419') as client:\n # Use client\n```\n\nUse `role` argument to say how you want to use the client. This argument has \nthree possible values: 'consumer', 'producer' or 'both'.\n\n```python\n# A client that acts as both a consumer and a producer.\nclient = Client(faktory_url='tcp://localhost:7419', role='both')\n```\n\n### Producer\n\nUse the client to push jobs.\n\n#### Push job\n\n```python\nwith Client(faktory_url='tcp://localhost:7419', role='producer') as client:\n producer = Producer(client=client)\n job_1 = Job(jobtype='adder', args=(5, 4), queue='default')\n producer.push(job_1)\n```\n\n#### Push bulk jobs\n\nYou can push several jobs at once. There is no limit, but 1000 at a time is recommended as a best practice.\n\n```python\nwith Client(faktory_url='tcp://localhost:7419', role='producer') as client:\n producer = Producer(client=client)\n job_2 = Job(jobtype='adder', args=(50, 41))\n job_3 = Job(jobtype='adder', args=(15, 68))\n res = producer.push_bulk([job_2, job_3])\n```\n\n### Consumer (Worker)\n\nUse a worker to pull jobs from Faktory server and execute them.\n\n```python\ndef adder(x, y):\n logging.info(f\"{x} + {y} = {x + y}\")\n\nwith Client(faktory_url='tcp://localhost:7419', role='consumer') as client:\n consumer = Consumer(client=client, queues=['default'], concurrency=1)\n consumer.register('adder', adder)\n consumer.run()\n```\n\nUse `priority` to indicates in which queue order the jobs should be fetched \nfirst.\n\n```python\n# With strict priority, there is a risk of starvation\nconsumer = Consumer(client=client, queues=['critical', 'default', 'bulk'], priority='strict')\n# Each queue has an equal chance of being fetched first\nconsumer = Consumer(client=client, queues=['critical', 'default', 'bulk'], priority='uniform')\n# Weights must be specified\nconsumer = Consumer(client=client, queues=['critical', 'default', 'bulk'], priority='weighted', weights=[0.6, 0.3, 0.1])\n```\n\n### Capture exceptions using Sentry\n\nTo capture exceptions using Sentry before failling jobs \nset `sentry_capture_exception` argument to `True`.\n\n```python\nconsumer = Consumer(client=client, sentry_capture_exception=True)\n```\n\n### Info\n\nYou can get various information about the server using `info` Client method.\n\n```python\nwith Client(faktory_url='tcp://localhost:7419') as client:\n server_info = client.info()\n print(server_info)\n```\n\n### Mutate\n\nA wrapper for the [Mutate API](https://github.com/contribsys/faktory/wiki/Mutate-API) to script certain repairs or migrations.\n\n\u26a0\ufe0f MUTATE commands can be slow and/or resource intensive. They should not be used as part of your application logic.\n\n```python\nfrom pyfaktory import Client, JobFilter, MutateOperation\n\nclient = Client(faktory_url='tcp://localhost:7419')\nclient.connect()\n\n# Find all scheduled jobs with type `QuickbooksSyncJob` and discard them\nop = MutateOperation(\n cmd='discard', \n target='scheduled', \n filter=JobFilter(jobtype=\"QuickbooksSyncJob\")\n)\nclient.mutate(op)\n\n# Clear the Retry queue completely\nop = MutateOperation(\n cmd='discard', \n target='retries', \n filter=JobFilter(regexp=\"*\")\n)\nclient.mutate(op)\n\n# Clear the Retry queue completely\nop = MutateOperation(\n cmd='discard', \n target='retries'\n)\nclient.mutate(op)\n\n# Send a two specific JIDs in the Retry queue to the Dead queue\nop = MutateOperation(\n cmd='kill', \n target='retries', \n filter=JobFilter(jobtype=\"QuickbooksSyncJob\", jids=[\"123456789\", \"abcdefgh\"])\n)\nclient.mutate(op)\n\n# Enqueue all retries with a first argument matching \"bob\"\nop = MutateOperation(\n cmd='requeue', \n target='retries', \n filter=JobFilter(regexp=\"*\\\"args\\\":[\\\"bob\\\"*\")\n)\nclient.mutate(op)\n\nclient.disconnect()\n```\n\n### Queues\n\n**Pausing**\n\nQueues may be paused (no job can be fetched from them while paused) or unpaused \n(resume fetching).\n\n```python\n# Pause a list of queues\nclient.queue_pause(queues=['bulk', 'another_queue'])\n\n# Pause all queues\nclient.queue_pause(all_queues=True)\n\n\n# Unpause a list of queues\nclient.queue_unpause(queues=['bulk'])\n\n# Unpause all queues\nclient.queue_unpause(all_queues=True)\n```\n\n**Remove**\n\nQueues can be removed which deletes all jobs within them. It does not stop \ncurrently executing jobs from those queues.\n\n```python\n# Remove a list of queues\nclient.queue_remove(queues=['bulk'])\n\n# Remove all queues\nclient.queue_remove(all_queues=True)\n```\n\n### Batch (untested)\n\nRefer to [documentation](https://github.com/contribsys/faktory/wiki/Ent-Batches).\n\n```python\nfrom pyfaktory import Batch, Client, Job, Producer, TargetJob\n\nclient = Client(faktory_url='tcp://localhost:7419')\nclient.connect()\n\nproducer = Producer(client=client)\n\nbatch = Batch(\n description=\"An optional description for the Web UI\",\n success=TargetJob(jobtype=\"MySuccessCallback\", args=[123], queue=\"critical\"),\n complete=TargetJob(jobtype=\"MyCompleteCallback\", args=['aa'], queue=\"critical\")\n)\n\n# Create a new batch\nresp = producer.batch_new(batch)\n\n# Push as many jobs as necessary for the batch\n# You may nest batches\n# The initial batch data has a TTL of 30 minutes and will expire if batch is not commited\nproducer.push(Job(jobtype='SomeJob', args=(5, 4), custom={\"bid\": bid}))\nproducer.push(Job(jobtype='SomeOtherJob', args=(0, 15), custom={\"bid\": bid}))\n\n# Commit the batch\nproducer.batch_commit(bid)\n\nclient.disconnect()\n```\n\nUse `batch_open` to open a created batch.\n\n```python\nproducer.batch_open(bid)\n```\n\nUse `parent_bid` for child batch.\n\n```python\nchild_batch = Batch(\n parent_bid=bid,\n description=\"An optional description for the Web UI\",\n success=TargetJob(jobtype=\"MySuccessCallback\", args=[123], queue=\"critical\"),\n complete=TargetJob(jobtype=\"MyCompleteCallback\", args=['aa'], queue=\"critical\")\n)\n```\n\n### Custom command\n\nIf you want to use a Faktory command that is not yet implemented in this client library, you can send custom commands.\n\n```python\nfrom pyfaktory import Client\n\nmy_command = 'INFO\\r\\n'\n\nwith Client(faktory_url='tcp://localhost:7419') as client:\n resp = client._send_and_receive(my_command)\n print(resp)\n```\n\n### Outer multiprocessing\n\nYou can use outer multiprocessing.\n\n```python\ndef run_worker():\n try:\n with Client(faktory_url=faktory_server_url, role='consumer') as client:\n import multiprocessing\n custom_context = multiprocessing.get_context(\"spawn\")\n consumer = Consumer(client=client, queues=['t_test'], concurrency=2, context=custom_context)\n consumer.register('task_001', task_001)\n consumer.run()\n except Exception as e:\n print(f\"task running error: {str(e)}\")\n```\n\n## Example\n\nFind examples in `./examples`.\n\n- Start the Faktory server.\n```\n/usr/bin/faktory\n```\n\n- Launch a producer.\n```\npython examples/fproducer.py\n```\n\n- Launch a consumer.\n```\npython examples/fconsumer.py\n```\n\n- Look at what is happening in the logs and in the [Faktory Web UI](http://localhost:7420/).\n\n## Contribute\n\n### Issues\n\nIf you encounter a problem, please report it.\n\nIn addition to the description of your problem, report the server and client\nversions.\n\n```python\npyfaktory.__version__\n```\n```\n/usr/bin/faktory -v\n```\n\nReproduce your problem while increasing the level of debugging for both the\nserver and the client, and report the logs.\n```python\nimport logging\n\nlogging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',\n level=logging.DEBUG,\n datefmt='%Y-%m-%d %H:%M:%S')\n```\n```\n/usr/bin/faktory -l debug\n```\n\n### PRs\n\nPlease, feel free to create pull requests.\n\n## License\n\nSee the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Faktory Client Python (Producer and Consumer/Worker)",
"version": "0.2.9",
"project_urls": {
"Documentation": "https://github.com/ghilesmeddour/faktory_worker_python",
"Homepage": "https://github.com/ghilesmeddour/faktory_worker_python",
"Repository": "https://github.com/ghilesmeddour/faktory_worker_python"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "7afcd8b073506e12d4755c57e5d256e466115733b0575a2a98bc4740558fabc1",
"md5": "314e50c1099ce87db1d935627dcf4559",
"sha256": "e7a9a7a18038a538fa4b2de11e2472d7445b01c5c779d2679a5c4c68d8aa0872"
},
"downloads": -1,
"filename": "pyfaktory-0.2.9-py3-none-any.whl",
"has_sig": false,
"md5_digest": "314e50c1099ce87db1d935627dcf4559",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.9",
"size": 17756,
"upload_time": "2024-12-16T11:43:42",
"upload_time_iso_8601": "2024-12-16T11:43:42.993358Z",
"url": "https://files.pythonhosted.org/packages/7a/fc/d8b073506e12d4755c57e5d256e466115733b0575a2a98bc4740558fabc1/pyfaktory-0.2.9-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "95ac9607af2bef6e9b7ed8097bbf179b1d01fefd27e9320115c4a3daef82b89b",
"md5": "a5e020027920928cb6144a143b67f1ea",
"sha256": "c8bb2d862cffd7112ad088c8b4bfcb2b0d4df4cd31e519b0872501a01a6c5afb"
},
"downloads": -1,
"filename": "pyfaktory-0.2.9.tar.gz",
"has_sig": false,
"md5_digest": "a5e020027920928cb6144a143b67f1ea",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.9",
"size": 17762,
"upload_time": "2024-12-16T11:43:45",
"upload_time_iso_8601": "2024-12-16T11:43:45.610576Z",
"url": "https://files.pythonhosted.org/packages/95/ac/9607af2bef6e9b7ed8097bbf179b1d01fefd27e9320115c4a3daef82b89b/pyfaktory-0.2.9.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-16 11:43:45",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ghilesmeddour",
"github_project": "faktory_worker_python",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "pyfaktory"
}