labtasker 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- labtasker-0.1.3/.gitmodules +4 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/MANIFEST.in +2 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/PKG-INFO +3 -1
- labtasker-0.1.3/demo/advanced/custom_resolver/submit_job.py +11 -0
- labtasker-0.1.3/demo/advanced/custom_resolver/w.py +38 -0
- labtasker-0.1.3/demo/advanced/custom_resolver/wo.py +29 -0
- labtasker-0.1.3/demo/basic/python_demo/run_job.py +25 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/__init__.py +4 -2
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/api_models.py +89 -22
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/cli/cli.py +2 -1
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/cli/loop.py +4 -4
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/cli/queue.py +9 -1
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/cli/task.py +4 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/cli/worker.py +6 -0
- labtasker-0.1.3/labtasker/client/client_api.py +118 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/api.py +35 -22
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/cli_utils.py +1 -1
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/config.py +6 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/heartbeat.py +20 -5
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/job_runner.py +7 -10
- labtasker-0.1.3/labtasker/client/core/resolver/__init__.py +6 -0
- labtasker-0.1.3/labtasker/client/core/resolver/models.py +37 -0
- labtasker-0.1.3/labtasker/client/core/resolver/utils.py +316 -0
- labtasker-0.1.3/labtasker/client/core/utils.py +80 -0
- labtasker-0.1.3/labtasker/client/core/version_checker.py +137 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/templates/labtasker_root/.gitignore +1 -1
- labtasker-0.1.3/labtasker/client/templates/labtasker_root/client.toml +20 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/constants.py +4 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/server/database.py +30 -8
- labtasker-0.1.3/labtasker/server/db_utils.py +249 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/utils.py +24 -118
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker.egg-info/SOURCES.txt +37 -24
- {labtasker-0.1.2 → labtasker-0.1.3}/pyproject.toml +5 -1
- labtasker-0.1.3/tests/test_api_models.py +19 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_cli/test_loop.py +1 -1
- {labtasker-0.1.2/tests → labtasker-0.1.3/tests/test_client/test_core}/test_cli_utils.py +1 -1
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_core/test_heartbeat.py +23 -9
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_core/test_job_runner.py +5 -4
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_core/test_job_runner_timeout.py +4 -3
- labtasker-0.1.3/tests/test_client/test_core/test_job_runner_with_resolver_timeout.py +128 -0
- labtasker-0.1.3/tests/test_client/test_core/test_resolver.py +405 -0
- labtasker-0.1.3/tests/test_client/test_core/test_runner_with_resolver.py +598 -0
- labtasker-0.1.3/tests/test_client/test_core/test_server_notification_and_client_version.py +70 -0
- labtasker-0.1.3/tests/test_client/test_core/test_version_checker.py +66 -0
- {labtasker-0.1.2/tests → labtasker-0.1.3/tests/test_client}/test_parser.py +2 -5
- {labtasker-0.1.2/tests → labtasker-0.1.3/tests/test_server}/test_database/test_database.py +159 -6
- {labtasker-0.1.2/tests/test_utils → labtasker-0.1.3/tests/test_server/test_db_utils}/test_arg_match.py +31 -2
- labtasker-0.1.3/tests/test_server/test_db_utils/test_keys_to_query_dict.py +30 -0
- labtasker-0.1.2/tests/test_utils/test_keys_to_query_dict.py → labtasker-0.1.3/tests/test_server/test_db_utils/test_keys_to_query_dict_deepest.py +7 -6
- labtasker-0.1.3/tests/test_server/test_db_utils/test_keys_to_query_dict_topmost.py +44 -0
- labtasker-0.1.3/tests/test_utils/__init__.py +0 -0
- labtasker-0.1.2/demo/python_demo/run_job.py +0 -20
- labtasker-0.1.2/labtasker/client/client_api.py +0 -30
- labtasker-0.1.2/labtasker/client/templates/labtasker_root/client.toml +0 -14
- labtasker-0.1.2/labtasker/server/db_utils.py +0 -91
- labtasker-0.1.2/plugins/labtasker_plugin_task_count/.gitignore +0 -186
- labtasker-0.1.2/plugins/labtasker_plugin_task_count/README.md +0 -13
- labtasker-0.1.2/plugins/labtasker_plugin_task_count/pyproject.toml +0 -32
- labtasker-0.1.2/plugins/labtasker_plugin_task_count/src/labtasker_plugin_task_count/impl.py +0 -13
- labtasker-0.1.2/plugins/labtasker_plugin_task_count/src/labtasker_plugin_task_count/main.py +0 -30
- {labtasker-0.1.2 → labtasker-0.1.3}/.coveragerc +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/.dockerignore +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/.gitattributes +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/.gitignore +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/.pre-commit-config.yaml +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/.pytest.ini +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/.python-version +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/Dockerfile +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/PRIVACY.md +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/README.md +0 -0
- {labtasker-0.1.2/demo → labtasker-0.1.3/demo/basic}/bash_demo/job_main.py +0 -0
- {labtasker-0.1.2/demo → labtasker-0.1.3/demo/basic}/bash_demo/run_job.sh +0 -0
- {labtasker-0.1.2/demo → labtasker-0.1.3/demo/basic}/bash_demo/submit_job.sh +0 -0
- {labtasker-0.1.2/demo → labtasker-0.1.3/demo/basic}/python_demo/submit_job.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/docker/mongodb/docker-entrypoint-wrapped.sh +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/docker/mongodb/healthcheck.sh +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/docker/mongodb/init.d/init-keyfile.sh +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/docker/mongodb/post-init.d/init-mongo.sh +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/docker-compose.yml +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/__main__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/cli/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/cli/config.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/cmd_parser/LabCmd.g4 +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/cmd_parser/LabCmd.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/cmd_parser/LabCmdLexer.g4 +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/cmd_parser/LabCmdLexer.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/cmd_parser/LabCmdListener.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/cmd_parser/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/cmd_parser/parser.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/context.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/exceptions.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/logging.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/paths.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/core/plugin_utils.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/client/templates/labtasker_root/logs/.gitkeep +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/concurrent.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/filtering.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/security.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/server/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/server/config.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/server/dependencies.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/server/endpoints.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/server/fsm.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/server/logging.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/labtasker/server/run.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/scripts/check_version.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/server.example.env +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/setup.cfg +0 -0
- {labtasker-0.1.2/plugins/labtasker_plugin_task_count/src/labtasker_plugin_task_count → labtasker-0.1.3/tests}/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/conftest.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/demo_pager_iterator.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/dummy_jobs/job_1.py +0 -0
- {labtasker-0.1.2/tests → labtasker-0.1.3/tests/fixtures}/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/fixtures/database/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/fixtures/database/mock.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/fixtures/database/real.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/fixtures/logging.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/fixtures/mock_datetime_now.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/fixtures/server/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/fixtures/server/async_app.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/fixtures/server/sync_app.py +0 -0
- {labtasker-0.1.2/tests/fixtures → labtasker-0.1.3/tests/test_client}/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/conftest.py +0 -0
- {labtasker-0.1.2/tests/test_client → labtasker-0.1.3/tests/test_client/test_cli}/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_cli/conftest.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_cli/test_basic.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_cli/test_config.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_cli/test_queue.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_cli/test_task.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_cli/test_worker.py +0 -0
- {labtasker-0.1.2/tests/test_client/test_cli → labtasker-0.1.3/tests/test_client/test_core}/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_core/test_logging.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_client/test_core/test_pager_iterator.py +0 -0
- {labtasker-0.1.2/tests/test_client/test_core → labtasker-0.1.3/tests/test_filtering}/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_filtering/exception_utils.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_filtering/test_exception_filtering.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_mock_time.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_security.py +0 -0
- {labtasker-0.1.2/tests/test_database → labtasker-0.1.3/tests/test_server}/__init__.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_server/conftest.py +0 -0
- {labtasker-0.1.2/tests/test_filtering → labtasker-0.1.3/tests/test_server/test_database}/__init__.py +0 -0
- {labtasker-0.1.2/tests → labtasker-0.1.3/tests/test_server}/test_database/conftest.py +0 -0
- {labtasker-0.1.2/tests → labtasker-0.1.3/tests/test_server}/test_database/test_benchmark_threadpool.py +0 -0
- {labtasker-0.1.2/tests → labtasker-0.1.3/tests/test_server}/test_database/test_query_dict_to_mongo_filter.py +0 -0
- {labtasker-0.1.2/tests/test_server → labtasker-0.1.3/tests/test_server/test_db_utils}/__init__.py +0 -0
- {labtasker-0.1.2/tests/test_utils → labtasker-0.1.3/tests/test_server/test_endpoint}/__init__.py +0 -0
- {labtasker-0.1.2/tests/test_server → labtasker-0.1.3/tests/test_server/test_endpoint}/test_server.py +0 -0
- {labtasker-0.1.2/tests/test_server → labtasker-0.1.3/tests/test_server/test_endpoint}/test_server_async.py +0 -0
- {labtasker-0.1.2/tests/test_server → labtasker-0.1.3/tests/test_server/test_endpoint}/test_server_async_ping.py +0 -0
- {labtasker-0.1.2/tests → labtasker-0.1.3/tests/test_server}/test_fsm.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_server/test_get_verified_queue_dependency.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_threadpool.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tests/test_utils/test_utils.py +0 -0
- {labtasker-0.1.2 → labtasker-0.1.3}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: labtasker
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A task queue system for lab experiments
|
|
5
5
|
Author-email: Your Name <your.email@example.com>
|
|
6
6
|
License: MIT
|
|
@@ -54,6 +54,8 @@ Requires-Dist: pytest-dependency<0.7.0,>=0.6.0; extra == "dev"
|
|
|
54
54
|
Provides-Extra: doc
|
|
55
55
|
Requires-Dist: mkdocs-material<9.7.0,>=9.6.5; extra == "doc"
|
|
56
56
|
Requires-Dist: mike<2.2.0,>=2.1.3; extra == "doc"
|
|
57
|
+
Provides-Extra: plugins
|
|
58
|
+
Requires-Dist: labtasker-plugin-task-count; extra == "plugins"
|
|
57
59
|
|
|
58
60
|
# Labtasker
|
|
59
61
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
from typing_extensions import Annotated
|
|
6
|
+
|
|
7
|
+
import labtasker
|
|
8
|
+
from labtasker import Required
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ArgsGroupA:
|
|
13
|
+
a: int
|
|
14
|
+
b: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ArgsGroupB:
|
|
19
|
+
foo: int
|
|
20
|
+
bar: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@labtasker.loop()
|
|
24
|
+
def main(
|
|
25
|
+
# use type annotation/default values to automatically resolve the required_fields
|
|
26
|
+
# use the self-defined resolver to convert the task args into custom types
|
|
27
|
+
args_a: Annotated[
|
|
28
|
+
Dict[str, Any], Required(resolver=lambda a: ArgsGroupA(**a))
|
|
29
|
+
], # option1. use Annotated
|
|
30
|
+
args_b=Required(resolver=lambda b: ArgsGroupB(**b)), # option2. use default kwarg
|
|
31
|
+
):
|
|
32
|
+
print(f"got args_a: {args_a}")
|
|
33
|
+
print(f"got args_b: {args_b}")
|
|
34
|
+
time.sleep(0.5)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
main()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
import labtasker
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class ArgsGroupA:
|
|
9
|
+
a: int
|
|
10
|
+
b: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ArgsGroupB:
|
|
15
|
+
foo: int
|
|
16
|
+
bar: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@labtasker.loop(required_fields=["args_a", "args_b"], pass_args_dict=True)
|
|
20
|
+
def main(args):
|
|
21
|
+
args_a = ArgsGroupA(**args["args_a"])
|
|
22
|
+
args_b = ArgsGroupB(**args["args_b"])
|
|
23
|
+
print(f"got args_a: {args_a}")
|
|
24
|
+
print(f"got args_b: {args_b}")
|
|
25
|
+
time.sleep(0.5)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import labtasker
|
|
4
|
+
from labtasker import Required
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def job(arg1: int, arg2: int):
|
|
8
|
+
"""Simulate a long-running job"""
|
|
9
|
+
time.sleep(3) # simulate a long-running job
|
|
10
|
+
return arg1 + arg2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@labtasker.loop()
|
|
14
|
+
def main(arg1: int = Required(), arg2: int = Required()):
|
|
15
|
+
# The labtasker autofills the parameter specified by Required()
|
|
16
|
+
# or Annotated[Any, Required()]
|
|
17
|
+
# Alternatively, you can fill in the required fields
|
|
18
|
+
# in loop(required_fields=["arg1", "arg2"]
|
|
19
|
+
# and access it via labtasker.task_info().args
|
|
20
|
+
result = job(arg1, arg2)
|
|
21
|
+
print(f"The result is {result}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
main()
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
__version__ = "0.1.3"
|
|
2
|
+
|
|
1
3
|
from labtasker.client.client_api import *
|
|
2
4
|
from labtasker.client.core.config import get_client_config
|
|
3
5
|
from labtasker.client.core.exceptions import *
|
|
4
6
|
from labtasker.client.core.paths import get_labtasker_client_config_path
|
|
7
|
+
from labtasker.client.core.version_checker import check_pypi_status
|
|
5
8
|
from labtasker.filtering import install_traceback_filter, set_traceback_filter_hook
|
|
6
9
|
|
|
7
|
-
__version__ = "0.1.2"
|
|
8
|
-
|
|
9
10
|
install_traceback_filter()
|
|
11
|
+
check_pypi_status()
|
|
10
12
|
|
|
11
13
|
# by default, traceback filter is enabled.
|
|
12
14
|
# you may disable it via client config
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from typing import Any, Dict, List, Optional, Union
|
|
3
3
|
|
|
4
|
-
from
|
|
4
|
+
from packaging.version import Version
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field, SecretStr, field_validator
|
|
5
6
|
|
|
7
|
+
from labtasker import __version__
|
|
6
8
|
from labtasker.constants import Priority
|
|
9
|
+
from labtasker.utils import validate_dict_keys
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
class BaseApiModel(BaseModel):
|
|
@@ -14,12 +17,61 @@ class BaseApiModel(BaseModel):
|
|
|
14
17
|
model_config = ConfigDict(populate_by_name=True)
|
|
15
18
|
|
|
16
19
|
|
|
17
|
-
class
|
|
20
|
+
class BaseRequestModel(BaseApiModel):
|
|
21
|
+
client_version: str = __version__
|
|
22
|
+
|
|
23
|
+
@field_validator("client_version")
|
|
24
|
+
def validate_client_version(cls, v, field):
|
|
25
|
+
# make sure it is a valid version
|
|
26
|
+
Version(v)
|
|
27
|
+
return v
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Notification(BaseModel):
|
|
31
|
+
"""Server notification such as compatibility warning etc."""
|
|
32
|
+
|
|
33
|
+
type: str = Field(..., pattern=r"^(info|warning|error)$")
|
|
34
|
+
level: str = Field(..., pattern=r"^(low|medium|high)$")
|
|
35
|
+
details: Union[str, Dict[str, Any]]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BaseResponseModel(BaseApiModel):
|
|
39
|
+
notification: Optional[List[Notification]] = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MetadataKeyValidateMixin:
|
|
43
|
+
|
|
44
|
+
@field_validator("metadata")
|
|
45
|
+
def validate_keys(cls, v, field):
|
|
46
|
+
if v:
|
|
47
|
+
validate_dict_keys(v)
|
|
48
|
+
return v
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ArgsKeyValidateMixin:
|
|
52
|
+
|
|
53
|
+
@field_validator("args")
|
|
54
|
+
def validate_keys(cls, v, field):
|
|
55
|
+
if v:
|
|
56
|
+
validate_dict_keys(v)
|
|
57
|
+
return v
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class SummaryKeyValidateMixin:
|
|
61
|
+
|
|
62
|
+
@field_validator("summary")
|
|
63
|
+
def validate_keys(cls, v, field):
|
|
64
|
+
if v:
|
|
65
|
+
validate_dict_keys(v)
|
|
66
|
+
return v
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class HealthCheckResponse(BaseResponseModel):
|
|
18
70
|
status: str = Field(..., pattern=r"^(healthy|unhealthy)$")
|
|
19
71
|
database: str
|
|
20
72
|
|
|
21
73
|
|
|
22
|
-
class QueueCreateRequest(
|
|
74
|
+
class QueueCreateRequest(BaseRequestModel, MetadataKeyValidateMixin):
|
|
23
75
|
queue_name: str = Field(
|
|
24
76
|
..., pattern=r"^[a-zA-Z0-9_-]+$", min_length=1, max_length=100
|
|
25
77
|
)
|
|
@@ -35,11 +87,11 @@ class QueueCreateRequest(BaseApiModel):
|
|
|
35
87
|
return result
|
|
36
88
|
|
|
37
89
|
|
|
38
|
-
class QueueCreateResponse(
|
|
90
|
+
class QueueCreateResponse(BaseResponseModel):
|
|
39
91
|
queue_id: str
|
|
40
92
|
|
|
41
93
|
|
|
42
|
-
class QueueGetResponse(
|
|
94
|
+
class QueueGetResponse(BaseResponseModel, MetadataKeyValidateMixin):
|
|
43
95
|
queue_id: str = Field(alias="_id")
|
|
44
96
|
queue_name: str
|
|
45
97
|
created_at: datetime
|
|
@@ -47,7 +99,12 @@ class QueueGetResponse(BaseApiModel):
|
|
|
47
99
|
metadata: Dict[str, Any]
|
|
48
100
|
|
|
49
101
|
|
|
50
|
-
class TaskSubmitRequest(
|
|
102
|
+
class TaskSubmitRequest(
|
|
103
|
+
BaseRequestModel,
|
|
104
|
+
ArgsKeyValidateMixin,
|
|
105
|
+
MetadataKeyValidateMixin,
|
|
106
|
+
SummaryKeyValidateMixin,
|
|
107
|
+
):
|
|
51
108
|
"""Task submission request."""
|
|
52
109
|
|
|
53
110
|
task_name: Optional[str] = Field(
|
|
@@ -62,16 +119,21 @@ class TaskSubmitRequest(BaseApiModel):
|
|
|
62
119
|
priority: int = Priority.MEDIUM
|
|
63
120
|
|
|
64
121
|
|
|
65
|
-
class TaskFetchRequest(
|
|
122
|
+
class TaskFetchRequest(BaseRequestModel):
|
|
66
123
|
worker_id: Optional[str] = None
|
|
67
124
|
eta_max: Optional[str] = None
|
|
68
125
|
heartbeat_timeout: Optional[float] = None
|
|
69
126
|
start_heartbeat: bool = True
|
|
70
|
-
required_fields: Optional[
|
|
127
|
+
required_fields: Optional[List[str]] = None
|
|
71
128
|
extra_filter: Optional[Dict[str, Any]] = None
|
|
72
129
|
|
|
73
130
|
|
|
74
|
-
class Task(
|
|
131
|
+
class Task(
|
|
132
|
+
BaseApiModel,
|
|
133
|
+
ArgsKeyValidateMixin,
|
|
134
|
+
MetadataKeyValidateMixin,
|
|
135
|
+
SummaryKeyValidateMixin,
|
|
136
|
+
):
|
|
75
137
|
task_id: str = Field(alias="_id") # Accepts "_id" as an input field
|
|
76
138
|
queue_id: str
|
|
77
139
|
status: str
|
|
@@ -92,7 +154,12 @@ class Task(BaseApiModel):
|
|
|
92
154
|
worker_id: Optional[str]
|
|
93
155
|
|
|
94
156
|
|
|
95
|
-
class TaskUpdateRequest(
|
|
157
|
+
class TaskUpdateRequest(
|
|
158
|
+
BaseRequestModel,
|
|
159
|
+
ArgsKeyValidateMixin,
|
|
160
|
+
MetadataKeyValidateMixin,
|
|
161
|
+
SummaryKeyValidateMixin,
|
|
162
|
+
):
|
|
96
163
|
"""This should be consistent with Task.
|
|
97
164
|
Fields that disallow manual update are commented out.
|
|
98
165
|
"""
|
|
@@ -130,12 +197,12 @@ class TaskUpdateRequest(BaseApiModel):
|
|
|
130
197
|
# worker_id: Optional[str]
|
|
131
198
|
|
|
132
199
|
|
|
133
|
-
class TaskFetchResponse(
|
|
200
|
+
class TaskFetchResponse(BaseResponseModel):
|
|
134
201
|
found: bool = False
|
|
135
202
|
task: Optional[Task] = None
|
|
136
203
|
|
|
137
204
|
|
|
138
|
-
class TaskLsRequest(
|
|
205
|
+
class TaskLsRequest(BaseRequestModel):
|
|
139
206
|
offset: int = Field(0, ge=0)
|
|
140
207
|
limit: int = Field(100, ge=0, le=1000)
|
|
141
208
|
task_id: Optional[str] = None
|
|
@@ -143,36 +210,36 @@ class TaskLsRequest(BaseApiModel):
|
|
|
143
210
|
extra_filter: Optional[Dict[str, Any]] = None
|
|
144
211
|
|
|
145
212
|
|
|
146
|
-
class TaskLsResponse(
|
|
213
|
+
class TaskLsResponse(BaseResponseModel):
|
|
147
214
|
found: bool = False
|
|
148
215
|
content: List[Task] = Field(default_factory=list)
|
|
149
216
|
|
|
150
217
|
|
|
151
|
-
class TaskSubmitResponse(
|
|
218
|
+
class TaskSubmitResponse(BaseResponseModel):
|
|
152
219
|
task_id: str
|
|
153
220
|
|
|
154
221
|
|
|
155
|
-
class TaskStatusUpdateRequest(
|
|
222
|
+
class TaskStatusUpdateRequest(BaseRequestModel):
|
|
156
223
|
status: str = Field(..., pattern=r"^(success|failed|cancelled)$")
|
|
157
224
|
worker_id: Optional[str] = None
|
|
158
225
|
summary: Optional[Dict[str, Any]] = None
|
|
159
226
|
|
|
160
227
|
|
|
161
|
-
class WorkerCreateRequest(
|
|
228
|
+
class WorkerCreateRequest(BaseRequestModel, MetadataKeyValidateMixin):
|
|
162
229
|
worker_name: Optional[str] = None
|
|
163
230
|
metadata: Optional[Dict[str, Any]] = None
|
|
164
231
|
max_retries: Optional[int] = 3
|
|
165
232
|
|
|
166
233
|
|
|
167
|
-
class WorkerCreateResponse(
|
|
234
|
+
class WorkerCreateResponse(BaseResponseModel):
|
|
168
235
|
worker_id: str
|
|
169
236
|
|
|
170
237
|
|
|
171
|
-
class WorkerStatusUpdateRequest(
|
|
238
|
+
class WorkerStatusUpdateRequest(BaseRequestModel):
|
|
172
239
|
status: str = Field(..., pattern=r"^(active|suspended|failed)$")
|
|
173
240
|
|
|
174
241
|
|
|
175
|
-
class WorkerLsRequest(
|
|
242
|
+
class WorkerLsRequest(BaseRequestModel):
|
|
176
243
|
offset: int = Field(0, ge=0)
|
|
177
244
|
limit: int = Field(100, ge=0, le=1000)
|
|
178
245
|
worker_id: Optional[str] = None
|
|
@@ -180,7 +247,7 @@ class WorkerLsRequest(BaseApiModel):
|
|
|
180
247
|
extra_filter: Optional[Dict[str, Any]] = None
|
|
181
248
|
|
|
182
249
|
|
|
183
|
-
class Worker(BaseApiModel):
|
|
250
|
+
class Worker(BaseApiModel, MetadataKeyValidateMixin):
|
|
184
251
|
worker_id: str = Field(alias="_id")
|
|
185
252
|
queue_id: str
|
|
186
253
|
status: str
|
|
@@ -194,12 +261,12 @@ class Worker(BaseApiModel):
|
|
|
194
261
|
last_modified: datetime
|
|
195
262
|
|
|
196
263
|
|
|
197
|
-
class WorkerLsResponse(
|
|
264
|
+
class WorkerLsResponse(BaseResponseModel):
|
|
198
265
|
found: bool = False
|
|
199
266
|
content: List[Worker] = Field(default_factory=list)
|
|
200
267
|
|
|
201
268
|
|
|
202
|
-
class QueueUpdateRequest(
|
|
269
|
+
class QueueUpdateRequest(BaseRequestModel):
|
|
203
270
|
new_queue_name: Optional[str] = Field(
|
|
204
271
|
None, pattern=r"^[a-zA-Z0-9_-]+$", min_length=1, max_length=100
|
|
205
272
|
)
|
|
@@ -8,7 +8,7 @@ import httpx
|
|
|
8
8
|
import typer
|
|
9
9
|
from typing_extensions import Annotated
|
|
10
10
|
|
|
11
|
-
from labtasker import __version__
|
|
11
|
+
from labtasker import __version__, check_pypi_status
|
|
12
12
|
from labtasker.client.core.api import health_check
|
|
13
13
|
from labtasker.client.core.config import requires_client_config
|
|
14
14
|
from labtasker.client.core.logging import stderr_console, stdout_console
|
|
@@ -19,6 +19,7 @@ app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
|
|
|
19
19
|
def version_callback(value: bool):
|
|
20
20
|
if value:
|
|
21
21
|
stdout_console.print(f"Labtasker Version: {__version__}")
|
|
22
|
+
check_pypi_status(force_check=True, blocking=True)
|
|
22
23
|
raise typer.Exit()
|
|
23
24
|
|
|
24
25
|
|
|
@@ -22,10 +22,8 @@ from labtasker.client.core.cli_utils import (
|
|
|
22
22
|
from labtasker.client.core.cmd_parser import cmd_interpolate
|
|
23
23
|
from labtasker.client.core.config import get_client_config
|
|
24
24
|
from labtasker.client.core.exceptions import CmdParserError
|
|
25
|
-
from labtasker.client.core.job_runner import finish
|
|
26
|
-
from labtasker.client.core.job_runner import loop as loop_run
|
|
25
|
+
from labtasker.client.core.job_runner import finish, loop_run
|
|
27
26
|
from labtasker.client.core.logging import logger, stderr_console, stdout_console
|
|
28
|
-
from labtasker.utils import keys_to_query_dict
|
|
29
27
|
|
|
30
28
|
|
|
31
29
|
class InfiniteDefaultDict(defaultdict):
|
|
@@ -59,6 +57,8 @@ def loop(
|
|
|
59
57
|
),
|
|
60
58
|
extra_filter: Optional[str] = typer.Option(
|
|
61
59
|
None,
|
|
60
|
+
"--extra-filter",
|
|
61
|
+
"-f",
|
|
62
62
|
help='Optional mongodb filter as a dict string (e.g., \'{"key": "value"}\').',
|
|
63
63
|
),
|
|
64
64
|
worker_id: Optional[str] = typer.Option(
|
|
@@ -102,7 +102,7 @@ def loop(
|
|
|
102
102
|
except (CmdParserError, KeyError, TypeError) as e:
|
|
103
103
|
raise typer.BadParameter(f"Command error with exception {e}")
|
|
104
104
|
|
|
105
|
-
required_fields =
|
|
105
|
+
required_fields = list(queried_keys)
|
|
106
106
|
|
|
107
107
|
logger.info(f"Got command: {cmd}")
|
|
108
108
|
|
|
@@ -74,6 +74,8 @@ def create(
|
|
|
74
74
|
),
|
|
75
75
|
quiet: bool = typer.Option(
|
|
76
76
|
False,
|
|
77
|
+
"--quiet",
|
|
78
|
+
"-q",
|
|
77
79
|
help="Only show queue ID string, rather than full response. Useful when using in bash scripts.",
|
|
78
80
|
),
|
|
79
81
|
):
|
|
@@ -99,6 +101,8 @@ def create_from_config(
|
|
|
99
101
|
),
|
|
100
102
|
quiet: bool = typer.Option(
|
|
101
103
|
False,
|
|
104
|
+
"--quiet",
|
|
105
|
+
"-q",
|
|
102
106
|
help="Only show queue ID string, rather than full response. Useful when using in bash scripts.",
|
|
103
107
|
),
|
|
104
108
|
):
|
|
@@ -118,7 +122,9 @@ def create_from_config(
|
|
|
118
122
|
@app.command()
|
|
119
123
|
@cli_utils_decorator
|
|
120
124
|
def get(
|
|
121
|
-
quiet: bool = typer.Option(
|
|
125
|
+
quiet: bool = typer.Option(
|
|
126
|
+
False, "--quiet", "-q", help="Only show queue ID string."
|
|
127
|
+
),
|
|
122
128
|
):
|
|
123
129
|
"""Get current queue info."""
|
|
124
130
|
resp = get_queue()
|
|
@@ -148,6 +154,8 @@ def update(
|
|
|
148
154
|
),
|
|
149
155
|
quiet: bool = typer.Option(
|
|
150
156
|
False,
|
|
157
|
+
"--quiet",
|
|
158
|
+
"-q",
|
|
151
159
|
help="Suppress the output. Execution result is only available via status code.",
|
|
152
160
|
),
|
|
153
161
|
):
|
|
@@ -274,6 +274,8 @@ def ls(
|
|
|
274
274
|
),
|
|
275
275
|
quiet: bool = typer.Option(
|
|
276
276
|
False,
|
|
277
|
+
"--quiet",
|
|
278
|
+
"-q",
|
|
277
279
|
help="Only show task IDs that match the query, rather than full entry. "
|
|
278
280
|
"Useful when using in bash scripts.",
|
|
279
281
|
),
|
|
@@ -380,6 +382,8 @@ def update(
|
|
|
380
382
|
),
|
|
381
383
|
quiet: bool = typer.Option(
|
|
382
384
|
False,
|
|
385
|
+
"--quiet",
|
|
386
|
+
"-q",
|
|
383
387
|
help="Disable interactive mode and confirmations. Set this to true if you are using this in a bash script.",
|
|
384
388
|
),
|
|
385
389
|
editor: Optional[str] = typer.Option(
|
|
@@ -59,6 +59,8 @@ def create(
|
|
|
59
59
|
),
|
|
60
60
|
quiet: bool = typer.Option(
|
|
61
61
|
False,
|
|
62
|
+
"--quiet",
|
|
63
|
+
"-q",
|
|
62
64
|
help="Only show worker ID string, rather than full response. Useful when using in bash scripts.",
|
|
63
65
|
),
|
|
64
66
|
):
|
|
@@ -95,10 +97,14 @@ def ls(
|
|
|
95
97
|
),
|
|
96
98
|
extra_filter: Optional[str] = typer.Option(
|
|
97
99
|
None,
|
|
100
|
+
"--extra-filter",
|
|
101
|
+
"-f",
|
|
98
102
|
help='Optional mongodb filter as a dict string (e.g., \'{"key": "value"}\').',
|
|
99
103
|
),
|
|
100
104
|
quiet: bool = typer.Option(
|
|
101
105
|
False,
|
|
106
|
+
"--quiet",
|
|
107
|
+
"-q",
|
|
102
108
|
help="Only show worker IDs that match the query, rather than full entry. Useful when using in bash scripts.",
|
|
103
109
|
),
|
|
104
110
|
pager: bool = typer.Option(
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
|
2
|
+
|
|
3
|
+
from labtasker.client.core.api import *
|
|
4
|
+
from labtasker.client.core.context import current_task_id, current_worker_id, task_info
|
|
5
|
+
from labtasker.client.core.exceptions import LabtaskerTypeError, LabtaskerValueError
|
|
6
|
+
from labtasker.client.core.job_runner import finish, loop_run
|
|
7
|
+
from labtasker.client.core.resolver import (
|
|
8
|
+
Required,
|
|
9
|
+
get_params_from_function,
|
|
10
|
+
get_required_fields,
|
|
11
|
+
resolve_args_partial,
|
|
12
|
+
)
|
|
13
|
+
from labtasker.utils import validate_required_fields
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
# python job runner api
|
|
17
|
+
"loop",
|
|
18
|
+
"finish",
|
|
19
|
+
"Required",
|
|
20
|
+
# context api
|
|
21
|
+
"task_info",
|
|
22
|
+
"current_task_id",
|
|
23
|
+
"current_worker_id",
|
|
24
|
+
# http api (you should be careful with these unless you know what you are doing)
|
|
25
|
+
"close_httpx_client",
|
|
26
|
+
"health_check",
|
|
27
|
+
"submit_task",
|
|
28
|
+
"delete_worker",
|
|
29
|
+
"create_queue",
|
|
30
|
+
"create_worker",
|
|
31
|
+
"delete_queue",
|
|
32
|
+
"delete_task",
|
|
33
|
+
"delete_worker",
|
|
34
|
+
"fetch_task",
|
|
35
|
+
"get_queue",
|
|
36
|
+
"health_check",
|
|
37
|
+
"ls_tasks",
|
|
38
|
+
"ls_worker",
|
|
39
|
+
"refresh_task_heartbeat",
|
|
40
|
+
"report_task_status",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def loop(
|
|
45
|
+
required_fields: List[str] = None,
|
|
46
|
+
extra_filter: Optional[Dict[str, Any]] = None,
|
|
47
|
+
cmd: Optional[Union[str, List[str]]] = None,
|
|
48
|
+
worker_id: Optional[str] = None,
|
|
49
|
+
create_worker_kwargs: Optional[Dict[str, Any]] = None,
|
|
50
|
+
eta_max: Optional[str] = None,
|
|
51
|
+
heartbeat_timeout: Optional[float] = None,
|
|
52
|
+
pass_args_dict: bool = False,
|
|
53
|
+
):
|
|
54
|
+
"""Run the wrapped job function in loop.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
required_fields: Fields (or extra fields other than specified using Required(...)) required for task execution in a dot-separated manner. E.g. ["arg1.arg11", "arg2.arg22"]
|
|
58
|
+
extra_filter: Additional filtering criteria for tasks
|
|
59
|
+
cmd: Command line arguments that runs current process. Default to sys.argv
|
|
60
|
+
worker_id: Specific worker ID to use
|
|
61
|
+
create_worker_kwargs: Arguments for default worker creation
|
|
62
|
+
eta_max: Maximum ETA for task execution.
|
|
63
|
+
heartbeat_timeout: Heartbeat timeout in seconds. Default to 3 times the send interval.
|
|
64
|
+
pass_args_dict: If True, passes task_info().args as first argument
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The decorated function
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
if required_fields is not None:
|
|
72
|
+
validate_required_fields(required_fields)
|
|
73
|
+
except ValueError as e:
|
|
74
|
+
raise LabtaskerValueError(str(e)) from e
|
|
75
|
+
except TypeError as e:
|
|
76
|
+
raise LabtaskerTypeError(str(e)) from e
|
|
77
|
+
|
|
78
|
+
def decorator(func):
|
|
79
|
+
"""
|
|
80
|
+
Steps:
|
|
81
|
+
1. Try get required fields from type annotations.
|
|
82
|
+
2. Wrap the job function with an args resolver wrapper
|
|
83
|
+
3. Wrap the resolver-wrapped job function with loop_run
|
|
84
|
+
4. Return the wrapped function
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
func:
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
param_metas = get_params_from_function(func)
|
|
93
|
+
|
|
94
|
+
# if required_fields is provided, merge them with the ones specified in param_metas
|
|
95
|
+
all_required_fields = get_required_fields(
|
|
96
|
+
param_metas=param_metas, extra_required_fields=required_fields
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# wrap the job function with args resolver
|
|
100
|
+
# that is, fill in the positional and keyword arguments for the required fields, type cast them
|
|
101
|
+
# into custom types (e.g. dataclasses) if the type_caster is provided
|
|
102
|
+
func = resolve_args_partial(
|
|
103
|
+
func, param_metas=param_metas, pass_args_dict=pass_args_dict
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# return the decorated function
|
|
107
|
+
return loop_run(
|
|
108
|
+
required_fields=all_required_fields,
|
|
109
|
+
extra_filter=extra_filter,
|
|
110
|
+
cmd=cmd,
|
|
111
|
+
worker_id=worker_id,
|
|
112
|
+
create_worker_kwargs=create_worker_kwargs,
|
|
113
|
+
eta_max=eta_max,
|
|
114
|
+
heartbeat_timeout=heartbeat_timeout,
|
|
115
|
+
pass_args_dict=True,
|
|
116
|
+
)(func)
|
|
117
|
+
|
|
118
|
+
return decorator
|