Skip to content

Commit 11906d0

Browse files
committed
Changed CLI logic. (#46)
Signed-off-by: Pavel Kirilin <[email protected]>
1 parent 77b3451 commit 11906d0

24 files changed

+349
-134
lines changed

docs/examples/extending/cli.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from argparse import ArgumentParser
2+
from typing import Sequence
3+
4+
from taskiq.abc.cmd import TaskiqCMD
5+
6+
7+
class MyCommand(TaskiqCMD):
8+
short_help = "Demo command"
9+
10+
def exec(self, args: Sequence[str]) -> None:
11+
parser = ArgumentParser()
12+
parser.add_argument(
13+
"--test",
14+
dest="test",
15+
default="default",
16+
help="My test parameter.",
17+
)
18+
parsed = parser.parse_args(args)
19+
print(parsed)

docs/extending-taskiq/cli.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
order: 4
3+
---
4+
5+
# CLI
6+
7+
You can easily add new subcommands to taskiq. All default subcommands also use this mechanism,
8+
since it's easy to use.
9+
10+
At first you need to add a class that implements `taskiq.abc.cmd.TaskiqCMD` abstract class.
11+
12+
@[code python](../examples/extending/cli.py)
13+
14+
In the `exec` method, you should parse incoming arguments. But since all CLI arguments to taskiq are shifted you can ignore the `args` parameter.
15+
16+
Also, you can use your favorite tool to build CLI, like [click](https://click.palletsprojects.com/) or [typer](https://typer.tiangolo.com/).
17+
18+
After you have such class, you need to add entrypoint that points to that class.
19+
20+
::: tabs
21+
22+
@tab setuptools setup.py
23+
24+
```python
25+
from setuptools import setup
26+
27+
setup(
28+
# ...,
29+
entry_points={
30+
'taskiq-cli': [
31+
'demo = my_project.cmd:MyCommand',
32+
]
33+
}
34+
)
35+
```
36+
37+
@tab setuptools pyproject.toml
38+
39+
```toml
40+
[project.entry-points.taskiq-cli]
41+
demo = "my_project.cmd:MyCommand"
42+
```
43+
44+
@tab poetry
45+
46+
```toml
47+
[tool.poetry.plugins.taskiq-cli]
48+
demo = "my_project.cmd:MyCommand"
49+
```
50+
51+
:::
52+
53+
You can read more about entry points in [python documentation](https://packaging.python.org/en/latest/specifications/entry-points/).
54+
The subcommand name is the same as the name of the entry point you've created.
55+
56+
57+
```bash
58+
$ taskiq demo --help
59+
usage: demo [-h] [--test TEST]
60+
61+
optional arguments:
62+
-h, --help show this help message and exit
63+
--test TEST My test parameter.
64+
```
65+
66+
```bash
67+
$ taskiq demo --test aaa
68+
Namespace(test='aaa')
69+
```

docs/guide/architecture-overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ You have to provide a path to your broker. As an example, if you want to start l
9999
with a broker that is stored in a variable `my broker` in the module `my_project.broker` run this in your terminal:
100100

101101
```
102-
taskiq my_project.broker:mybroker
102+
taskiq worker my_project.broker:mybroker
103103
```
104104

105105
taskiq can discover task modules to import automatically,

docs/guide/cli.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,22 @@ order: 4
44

55
# CLI
66

7-
Core library comes with CLI programm called `taskiq`, which is used to run workers.
7+
Core library comes with CLI programm called `taskiq`, which is used to run different subcommands.
88

9-
To run it you have to specify the broker you want to use and modules with defined tasks.
9+
10+
By default taskiq is shipped with only one command: `worker`. You can search for more taskiq plugins
11+
using pypi. Some plugins may add new commands to taskiq.
12+
13+
## Worker
14+
15+
To run worker process, you have to specify the broker you want to use and modules with defined tasks.
1016
Like this:
1117

1218
```bash
13-
taskiq mybroker:broker_var my_project.module1 my_project.module2
19+
taskiq worker mybroker:broker_var my_project.module1 my_project.module2
1420
```
1521

16-
## Autoimporting
22+
### Autoimporting
1723

1824
Enumerating all modules with tasks is not an option sometimes.
1925
That's why taskiq can autodiscover tasks in current directory recursively.
@@ -24,7 +30,7 @@ We have two options for this:
2430
* `--fs-discover` or `-fsd`. This option enables search of task files in current directory recursively, using the given pattern.
2531

2632

27-
## Type casts
33+
### Type casts
2834

2935
One of features taskiq have is automatic type casts. For examle you have a type-hinted task like this:
3036
```python
@@ -38,7 +44,7 @@ dataclasses as the input parameters.
3844

3945
To disable this pass the `--no-parse` option to the taskiq.
4046

41-
## Hot reload
47+
### Hot reload
4248

4349
This is annoying to restart workers every time you modify tasks. That's why taskiq supports hot-reload.
4450
To enable this option simply pass the `--reload` or `-r` option to taskiq CLI.

docs/guide/getting-started.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ docker run --rm -d ^
142142
Now we need to start worker process by running taskiq cli command. You can get more info about the CLI in the [CLI](./cli.md) section.
143143

144144
```bash:no-line-numbers
145-
taskiq broker:broker
145+
taskiq worker broker:broker
146146
```
147147

148148
After the worker is up, we can run our script as an ordinary python file and see how the worker executes tasks.
@@ -206,7 +206,7 @@ docker run --rm -d ^
206206
Let's run taskiq once again. The command is the same.
207207

208208
```bash:no-line-numbers
209-
taskiq broker:broker
209+
taskiq worker broker:broker
210210
```
211211

212212
Now, if we run this file with python, we can get the correct results with a valid execution time.

poetry.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ python = "^3.7"
2929
typing-extensions = ">=3.10.0.0"
3030
pydantic = "^1.6.2"
3131
pyzmq = { version = "^23.2.0", optional = true }
32-
uvloop = { version = "^0.16.0", optional = true }
32+
uvloop = { version = ">=0.16.0,<1", optional = true }
3333
watchdog = "^2.1.9"
34-
gitignore-parser = "^0.0.8"
34+
gitignore-parser = "^0.1.0"
35+
importlib-metadata = "<4.3"
36+
3537

3638
[tool.poetry.dev-dependencies]
3739
pytest = "^7.1.2"
@@ -47,7 +49,7 @@ coverage = "^6.4.2"
4749
pytest-cov = "^3.0.0"
4850
mock = "^4.0.3"
4951
anyio = "^3.6.1"
50-
pytest-xdist = {version = "^2.5.0", extras = ["psutil"]}
52+
pytest-xdist = { version = "^2.5.0", extras = ["psutil"] }
5153
types-mock = "^4.0.15"
5254

5355
[tool.poetry.extras]
@@ -57,6 +59,9 @@ uv = ["uvloop"]
5759
[tool.poetry.scripts]
5860
taskiq = "taskiq.__main__:main"
5961

62+
[tool.poetry.plugins.taskiq-cli]
63+
worker = "taskiq.cli.worker.cmd:WorkerCMD"
64+
6065
[tool.mypy]
6166
strict = true
6267
ignore_missing_imports = true

taskiq/__main__.py

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,75 @@
1-
from taskiq.cli.args import TaskiqArgs
2-
from taskiq.cli.worker import run_worker
1+
import argparse
2+
import sys
3+
from typing import Dict
34

5+
from importlib_metadata import entry_points, version
46

5-
def main() -> None:
6-
"""Main entrypoint for CLI."""
7-
args = TaskiqArgs.from_cli()
8-
run_worker(args)
7+
from taskiq.abc.cmd import TaskiqCMD
8+
9+
10+
def main() -> None: # noqa: C901, WPS210
11+
"""
12+
Main entrypoint of the taskiq.
13+
14+
This function collects all python entrypoints
15+
and assembles a final argument parser.
16+
17+
All found entrypoints are used as subcommands.
18+
All arguments are passed to them as it was a normal
19+
call.
20+
"""
21+
plugins = entry_points().select(group="taskiq-cli")
22+
found_plugins = len(plugins)
23+
parser = argparse.ArgumentParser(
24+
description=f"""
25+
CLI for taskiq. Distributed task queue.
26+
27+
This is a meta CLI. It searches for installed plugins
28+
using python entrypoints
29+
and passes all arguments to them.
30+
31+
We found {found_plugins} installed plugins.
32+
""",
33+
)
34+
parser.add_argument(
35+
"-V",
36+
"--version",
37+
dest="version",
38+
action="store_true",
39+
help="print current taskiq version and exit",
40+
)
41+
subcommands: Dict[str, TaskiqCMD] = {}
42+
subparsers = parser.add_subparsers(
43+
title="Available subcommands",
44+
metavar="",
45+
dest="subcommand",
46+
)
47+
for entrypoint in entry_points().select(group="taskiq-cli"):
48+
try:
49+
cmd_class = entrypoint.load()
50+
except ImportError:
51+
continue
52+
if issubclass(cmd_class, TaskiqCMD):
53+
subparsers.add_parser(
54+
entrypoint.name,
55+
help=cmd_class.short_help,
56+
add_help=False,
57+
)
58+
subcommands[entrypoint.name] = cmd_class()
59+
60+
args, _ = parser.parse_known_args()
61+
62+
if args.version:
63+
print(version("taskiq")) # noqa: WPS421
64+
return
65+
66+
if args.subcommand is None:
67+
parser.print_help()
68+
return
69+
70+
command = subcommands[args.subcommand]
71+
sys.argv.pop(0)
72+
command.exec(sys.argv[1:])
973

1074

1175
if __name__ == "__main__":

taskiq/abc/cmd.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from abc import ABC, abstractmethod
2+
from typing import Sequence
3+
4+
5+
class TaskiqCMD(ABC):
6+
"""Base class for new commands."""
7+
8+
short_help = ""
9+
10+
@abstractmethod
11+
def exec(self, args: Sequence[str]) -> None:
12+
"""
13+
Execute the command.
14+
15+
:param args: CLI args.
16+
"""

taskiq/brokers/inmemory_broker.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
from taskiq.abc.broker import AsyncBroker
66
from taskiq.abc.result_backend import AsyncResultBackend, TaskiqResult
7-
from taskiq.cli.args import TaskiqArgs
8-
from taskiq.cli.receiver import Receiver
7+
from taskiq.cli.worker.args import WorkerArgs
8+
from taskiq.cli.worker.receiver import Receiver
99
from taskiq.exceptions import TaskiqError
1010
from taskiq.message import BrokerMessage
1111

@@ -102,12 +102,12 @@ def __init__( # noqa: WPS211
102102
)
103103
self.receiver = Receiver(
104104
self,
105-
TaskiqArgs(
105+
WorkerArgs(
106106
broker="",
107107
modules=[],
108108
max_threadpool_threads=sync_tasks_pool_size,
109109
no_parse=not cast_types,
110-
log_collector_format=logs_format or TaskiqArgs.log_collector_format,
110+
log_collector_format=logs_format or WorkerArgs.log_collector_format,
111111
),
112112
)
113113

0 commit comments

Comments
 (0)