labtasker 0.2.2__tar.gz → 0.2.4__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.
Files changed (189) hide show
  1. {labtasker-0.2.2 → labtasker-0.2.4}/.gitignore +2 -0
  2. labtasker-0.2.4/.gitmodules +8 -0
  3. {labtasker-0.2.2 → labtasker-0.2.4}/.pre-commit-config.yaml +2 -2
  4. {labtasker-0.2.2 → labtasker-0.2.4}/Dockerfile +5 -2
  5. {labtasker-0.2.2 → labtasker-0.2.4}/MANIFEST.in +2 -0
  6. {labtasker-0.2.2 → labtasker-0.2.4}/PKG-INFO +37 -15
  7. {labtasker-0.2.2 → labtasker-0.2.4}/README.md +29 -10
  8. {labtasker-0.2.2 → labtasker-0.2.4}/docker-compose.yml +2 -2
  9. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/__init__.py +1 -1
  10. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/api_models.py +2 -2
  11. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/cli/init.py +5 -2
  12. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/cli/loop.py +23 -15
  13. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/cli/task.py +41 -8
  14. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/cli/worker.py +11 -5
  15. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/cli_utils.py +27 -21
  16. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/parser.py +16 -11
  17. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/config.py +2 -2
  18. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/exceptions.py +12 -0
  19. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/job_runner.py +49 -7
  20. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/templates/labtasker_root/client.toml +1 -1
  21. labtasker-0.2.4/labtasker/server/cli.py +90 -0
  22. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/server/config.py +4 -3
  23. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/server/database.py +69 -29
  24. labtasker-0.2.4/labtasker/server/embedded_db.py +420 -0
  25. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/server/endpoints.py +5 -0
  26. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker.egg-info/SOURCES.txt +3 -1
  27. {labtasker-0.2.2 → labtasker-0.2.4}/pyproject.toml +11 -7
  28. {labtasker-0.2.2 → labtasker-0.2.4}/server.example.env +1 -1
  29. {labtasker-0.2.2 → labtasker-0.2.4}/tests/conftest.py +1 -1
  30. labtasker-0.2.4/tests/fixtures/database/__init__.py +4 -0
  31. labtasker-0.2.4/tests/fixtures/database/mock.py +19 -0
  32. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/conftest.py +10 -0
  33. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/test_init.py +1 -1
  34. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/test_loop.py +22 -0
  35. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/test_task.py +18 -0
  36. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_with_resolver.py +16 -0
  37. labtasker-0.2.4/tests/test_server/test_embedded_db.py +41 -0
  38. labtasker-0.2.2/.gitmodules +0 -4
  39. labtasker-0.2.2/labtasker/server/run.py +0 -21
  40. labtasker-0.2.2/tests/fixtures/database/__init__.py +0 -2
  41. labtasker-0.2.2/tests/fixtures/database/mock.py +0 -61
  42. {labtasker-0.2.2 → labtasker-0.2.4}/.coveragerc +0 -0
  43. {labtasker-0.2.2 → labtasker-0.2.4}/.dockerignore +0 -0
  44. {labtasker-0.2.2 → labtasker-0.2.4}/.flake8 +0 -0
  45. {labtasker-0.2.2 → labtasker-0.2.4}/.gitattributes +0 -0
  46. {labtasker-0.2.2 → labtasker-0.2.4}/.pytest.ini +0 -0
  47. {labtasker-0.2.2 → labtasker-0.2.4}/.python-version +0 -0
  48. {labtasker-0.2.2 → labtasker-0.2.4}/LICENSE +0 -0
  49. {labtasker-0.2.2 → labtasker-0.2.4}/PRIVACY.md +0 -0
  50. {labtasker-0.2.2 → labtasker-0.2.4}/demo/advanced/custom_resolver/submit_job.py +0 -0
  51. {labtasker-0.2.2 → labtasker-0.2.4}/demo/advanced/custom_resolver/w.py +0 -0
  52. {labtasker-0.2.2 → labtasker-0.2.4}/demo/advanced/custom_resolver/wo.py +0 -0
  53. {labtasker-0.2.2 → labtasker-0.2.4}/demo/advanced/event_system/email_on_task_failure.py +0 -0
  54. {labtasker-0.2.2 → labtasker-0.2.4}/demo/advanced/event_system/send_email.py +0 -0
  55. {labtasker-0.2.2 → labtasker-0.2.4}/demo/advanced/event_system/sim_unstable_job.py +0 -0
  56. {labtasker-0.2.2 → labtasker-0.2.4}/demo/advanced/event_system/submit.sh +0 -0
  57. {labtasker-0.2.2 → labtasker-0.2.4}/demo/basic/bash_demo/job_main.py +0 -0
  58. {labtasker-0.2.2 → labtasker-0.2.4}/demo/basic/bash_demo/run_job.sh +0 -0
  59. {labtasker-0.2.2 → labtasker-0.2.4}/demo/basic/bash_demo/submit_job.sh +0 -0
  60. {labtasker-0.2.2 → labtasker-0.2.4}/demo/basic/python_demo/run_job.py +0 -0
  61. {labtasker-0.2.2 → labtasker-0.2.4}/demo/basic/python_demo/submit_job.py +0 -0
  62. {labtasker-0.2.2 → labtasker-0.2.4}/docker/mongodb/init.d/init-keyfile.sh +0 -0
  63. {labtasker-0.2.2 → labtasker-0.2.4}/docker/mongodb/post-init.d/init-mongo.sh +0 -0
  64. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/__main__.py +0 -0
  65. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/__init__.py +0 -0
  66. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/cli/__init__.py +0 -0
  67. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/cli/cli.py +0 -0
  68. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/cli/config.py +0 -0
  69. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/cli/event.py +0 -0
  70. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/cli/queue.py +0 -0
  71. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/client_api.py +0 -0
  72. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/__init__.py +0 -0
  73. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/api.py +0 -0
  74. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/LabCmd.g4 +0 -0
  75. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/LabCmdLexer.g4 +0 -0
  76. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/__init__.py +0 -0
  77. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/generated/LabCmd.py +0 -0
  78. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/generated/LabCmdLexer.py +0 -0
  79. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/generated/LabCmdListener.py +0 -0
  80. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/generated/__init__.py +0 -0
  81. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/context.py +0 -0
  82. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/events.py +0 -0
  83. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/heartbeat.py +0 -0
  84. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/logging.py +0 -0
  85. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/paths.py +0 -0
  86. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/plugin_utils.py +0 -0
  87. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/query_transpiler.py +0 -0
  88. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/resolver/__init__.py +0 -0
  89. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/resolver/models.py +0 -0
  90. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/resolver/utils.py +0 -0
  91. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/utils.py +0 -0
  92. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/core/version_checker.py +0 -0
  93. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/templates/labtasker_root/.gitignore +0 -0
  94. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/client/templates/labtasker_root/logs/.gitkeep +0 -0
  95. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/constants.py +0 -0
  96. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/filtering.py +0 -0
  97. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/security.py +0 -0
  98. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/server/__init__.py +0 -0
  99. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/server/db_utils.py +0 -0
  100. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/server/dependencies.py +0 -0
  101. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/server/event_manager.py +0 -0
  102. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/server/fsm.py +0 -0
  103. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/server/logging.py +0 -0
  104. {labtasker-0.2.2 → labtasker-0.2.4}/labtasker/utils.py +0 -0
  105. {labtasker-0.2.2 → labtasker-0.2.4}/script_tests/test_ban_datetime_now/allowed.py +0 -0
  106. {labtasker-0.2.2 → labtasker-0.2.4}/script_tests/test_ban_datetime_now/disallowed.py +0 -0
  107. {labtasker-0.2.2 → labtasker-0.2.4}/script_tests/test_ban_datetime_now/test_ban.py +0 -0
  108. {labtasker-0.2.2 → labtasker-0.2.4}/scripts/asciinema_adjust_timestamp.py +0 -0
  109. {labtasker-0.2.2 → labtasker-0.2.4}/scripts/check_version.py +0 -0
  110. {labtasker-0.2.2 → labtasker-0.2.4}/scripts/datetime_now_checker.py +0 -0
  111. {labtasker-0.2.2 → labtasker-0.2.4}/setup.cfg +0 -0
  112. {labtasker-0.2.2 → labtasker-0.2.4}/tests/__init__.py +0 -0
  113. {labtasker-0.2.2 → labtasker-0.2.4}/tests/demo_pager_iterator.py +0 -0
  114. {labtasker-0.2.2 → labtasker-0.2.4}/tests/dummy_jobs/job_1.py +0 -0
  115. {labtasker-0.2.2 → labtasker-0.2.4}/tests/fixtures/__init__.py +0 -0
  116. {labtasker-0.2.2 → labtasker-0.2.4}/tests/fixtures/database/real.py +0 -0
  117. {labtasker-0.2.2 → labtasker-0.2.4}/tests/fixtures/logging.py +0 -0
  118. {labtasker-0.2.2 → labtasker-0.2.4}/tests/fixtures/mock_datetime_now.py +0 -0
  119. {labtasker-0.2.2 → labtasker-0.2.4}/tests/fixtures/server/__init__.py +0 -0
  120. {labtasker-0.2.2 → labtasker-0.2.4}/tests/fixtures/server/async_app.py +0 -0
  121. {labtasker-0.2.2 → labtasker-0.2.4}/tests/fixtures/server/sync_app.py +0 -0
  122. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_api_models.py +0 -0
  123. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/__init__.py +0 -0
  124. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/__init__.py +0 -0
  125. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/conftest.py +0 -0
  126. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/test_basic.py +0 -0
  127. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/test_config.py +0 -0
  128. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/test_event.py +0 -0
  129. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/test_queue.py +0 -0
  130. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_cli/test_worker.py +0 -0
  131. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/__init__.py +0 -0
  132. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_cli_utils.py +0 -0
  133. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_event/__init__.py +0 -0
  134. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_event/test_concurrency_job_flow_event.py +0 -0
  135. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_event/test_event_listener_basic.py +0 -0
  136. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_event/test_various_actions.py +0 -0
  137. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_event/utils.py +0 -0
  138. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_heartbeat.py +0 -0
  139. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_job_runner.py +0 -0
  140. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_logging.py +0 -0
  141. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_loop_internal_error_handler.py +0 -0
  142. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_pager_iterator.py +0 -0
  143. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_parser.py +0 -0
  144. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/__init__.py +0 -0
  145. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/conftest.py +0 -0
  146. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/test_behavior.py +0 -0
  147. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/test_matching.py +0 -0
  148. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/test_utils.py +0 -0
  149. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/utils.py +0 -0
  150. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_resolver.py +0 -0
  151. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_concurrency/__init__.py +0 -0
  152. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_concurrency/run_concurrent.py +0 -0
  153. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_concurrency/test_runner_concurrency_success_failure.py +0 -0
  154. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_concurrency/test_runner_high_concurrency.py +0 -0
  155. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_timeout/__init__.py +0 -0
  156. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_timeout/conftest.py +0 -0
  157. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_timeout/test_job_runner_timeout.py +0 -0
  158. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_timeout/test_job_runner_with_resolver_timeout.py +0 -0
  159. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_server_notification_and_client_version.py +0 -0
  160. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_client/test_core/test_version_checker.py +0 -0
  161. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_filtering/__init__.py +0 -0
  162. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_filtering/exception_utils.py +0 -0
  163. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_filtering/test_exception_filtering.py +0 -0
  164. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_mock_time.py +0 -0
  165. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_security.py +0 -0
  166. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/__init__.py +0 -0
  167. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/conftest.py +0 -0
  168. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_database/__init__.py +0 -0
  169. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_database/conftest.py +0 -0
  170. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_database/test_database_basic.py +0 -0
  171. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_database/test_fetch_extra_filter.py +0 -0
  172. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_database/test_query_dict_to_mongo_filter.py +0 -0
  173. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_database/test_required_field_fetching.py +0 -0
  174. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_db_utils/__init__.py +0 -0
  175. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_db_utils/test_arg_match.py +0 -0
  176. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_db_utils/test_keys_to_query_dict.py +0 -0
  177. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_db_utils/test_keys_to_query_dict_deepest.py +0 -0
  178. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_db_utils/test_keys_to_query_dict_topmost.py +0 -0
  179. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_endpoint/__init__.py +0 -0
  180. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_endpoint/test_event_basic.py +0 -0
  181. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_endpoint/test_server.py +0 -0
  182. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_endpoint/test_server_async.py +0 -0
  183. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_endpoint/test_server_async_ping.py +0 -0
  184. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_fsm.py +0 -0
  185. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_server/test_get_verified_queue_dependency.py +0 -0
  186. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_utils/__init__.py +0 -0
  187. {labtasker-0.2.2 → labtasker-0.2.4}/tests/test_utils/test_utils.py +0 -0
  188. {labtasker-0.2.2 → labtasker-0.2.4}/tests/utils.py +0 -0
  189. {labtasker-0.2.2 → labtasker-0.2.4}/tox.ini +0 -0
@@ -1,3 +1,5 @@
1
+ labtasker_db.json
2
+
1
3
  # Created by https://www.toptal.com/developers/gitignore/api/python
2
4
  # Edit at https://www.toptal.com/developers/gitignore?templates=python
3
5
 
@@ -0,0 +1,8 @@
1
+ [submodule "plugins/labtasker_plugin_task_count"]
2
+ path = plugins/labtasker_plugin_task_count
3
+ url = https://github.com/fkcptlst/labtasker-plugin-task-count.git
4
+ branch = main
5
+ [submodule "plugins/labtasker_plugin_script_generate"]
6
+ path = plugins/labtasker_plugin_script_generate
7
+ url = https://github.com/fkcptlst/labtasker-plugin-script-generate.git
8
+ branch = main
@@ -12,11 +12,11 @@ repos:
12
12
  args: [ --fix=lf ]
13
13
 
14
14
  - repo: https://github.com/psf/black
15
- rev: 24.8.0
15
+ rev: 25.1.0
16
16
  hooks:
17
17
  - id: black
18
18
 
19
19
  - repo: https://github.com/pycqa/isort
20
- rev: 5.13.2
20
+ rev: 6.0.1
21
21
  hooks:
22
22
  - id: isort
@@ -16,7 +16,10 @@ RUN pip install --no-cache-dir "."
16
16
 
17
17
  # Health check
18
18
  HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
19
- CMD curl -f http://localhost:${API_PORT:-8080}/health || exit 1
19
+ CMD curl -f http://localhost:${API_PORT:-9321}/health || exit 1
20
+
21
+ # Set DB_MODE to external
22
+ ENV DB_MODE=${DB_MODE:-external}
20
23
 
21
24
  # Run the application
22
- CMD ["python", "-m", "labtasker.server.run"]
25
+ CMD ["labtasker-server", "serve"]
@@ -19,6 +19,8 @@ exclude coverage.xml
19
19
  exclude poetry.lock
20
20
  exclude poetry.toml
21
21
 
22
+ exclude labtasker_db.json
23
+
22
24
  prune .vscode
23
25
  prune .github
24
26
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labtasker
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A task queue system for lab experiments
5
5
  Author-email: Your Name <your.email@example.com>
6
6
  License: Apache License 2.0
@@ -40,11 +40,12 @@ Requires-Dist: sse-starlette<3.0.0,>=2.1.3
40
40
  Requires-Dist: httpx-sse<0.5.0,>=0.4.0
41
41
  Requires-Dist: stamina<26.0.0,>=25.1.0
42
42
  Requires-Dist: noneprompt<0.2.0,>=0.1.9
43
- Requires-Dist: pytest-sugar<2.0.0,>=1.0.0
43
+ Requires-Dist: mongomock<4.4.0,>=4.3.0
44
+ Requires-Dist: jsonpickle<5.0.0,>=4.0.2
45
+ Requires-Dist: mslex<2.0.0,>=1.3.0
44
46
  Provides-Extra: dev
45
47
  Requires-Dist: pytest<9.0.0,>=8.0.0; extra == "dev"
46
48
  Requires-Dist: pytest-cov<7.0.0,>=5.0.0; extra == "dev"
47
- Requires-Dist: mongomock<4.4.0,>=4.3.0; extra == "dev"
48
49
  Requires-Dist: black<26.0.0,>=24.0.0; extra == "dev"
49
50
  Requires-Dist: isort<7.0.0,>=5.13.0; extra == "dev"
50
51
  Requires-Dist: mypy<2.0.0,>=1.14.0; extra == "dev"
@@ -52,21 +53,23 @@ Requires-Dist: flake8<8.0.0,>=7.0.0; extra == "dev"
52
53
  Requires-Dist: pre-commit<5.0.0,>=3.0.0; extra == "dev"
53
54
  Requires-Dist: freezegun<2.0.0,>=1.5.0; extra == "dev"
54
55
  Requires-Dist: pytest-docker<4.0.0,>=3.0.0; extra == "dev"
55
- Requires-Dist: pytest-asyncio<0.26.0,>=0.24.0; extra == "dev"
56
+ Requires-Dist: pytest-asyncio<0.27.0,>=0.24.0; extra == "dev"
56
57
  Requires-Dist: asgi-lifespan<3.0.0,>=2.1.0; extra == "dev"
57
- Requires-Dist: tox<4.25.0,>=4.24.0; extra == "dev"
58
+ Requires-Dist: tox<4.26.0,>=4.24.0; extra == "dev"
58
59
  Requires-Dist: pytest-dependency<0.7.0,>=0.6.0; extra == "dev"
60
+ Requires-Dist: pytest-sugar<2.0.0,>=1.0.0; extra == "dev"
59
61
  Provides-Extra: doc
60
62
  Requires-Dist: mkdocs-material<9.7.0,>=9.6.5; extra == "doc"
61
63
  Requires-Dist: mkdocs-glightbox<0.5.0,>=0.4.0; extra == "doc"
62
64
  Requires-Dist: mike<2.2.0,>=2.1.3; extra == "doc"
63
65
  Provides-Extra: plugins
64
66
  Requires-Dist: labtasker-plugin-task-count; extra == "plugins"
67
+ Requires-Dist: labtasker-plugin-script-generate; extra == "plugins"
65
68
  Dynamic: license-file
66
69
 
67
70
  <p align="center"><em>Make your ML experiment wrapper scripts smarter with...</em></p>
68
71
  <h1 align="center" style="font-size: 40px;"> <a href="">Labtasker</a></h1>
69
- <p align="center"><a href="https://fkcptlst.github.io/labtasker/latest/install/install/">Install</a> • <a href="https://fkcptlst.github.io/labtasker/latest/guide/basic/">Tutorial / Demo</a> • <a href="https://fkcptlst.github.io/labtasker/latest/">Documentation</a> • <a href="https://fkcptlst.github.io/labtasker/dev/faq/">FAQs</a> • <a href="https://github.com/fkcptlst/labtasker/releases">Releases</a></p>
72
+ <p align="center"><a href="https://fkcptlst.github.io/labtasker/latest/install/install/">Install</a> • <a href="https://fkcptlst.github.io/labtasker/latest/guide/basic/">Tutorial / Demo</a> • <a href="https://fkcptlst.github.io/labtasker/latest/">Documentation</a> • <a href="https://fkcptlst.github.io/labtasker/latest/faq/">FAQs</a> • <a href="https://github.com/fkcptlst/labtasker/releases">Releases</a></p>
70
73
 
71
74
  <p align="center">
72
75
  <img src="https://github.com/fkcptlst/labtasker/actions/workflows/unit-test-matrix.yml/badge.svg" alt="unit-test-matrix" />
@@ -99,7 +102,8 @@ such as sending emails on task failure.
99
102
 
100
103
  ![demo](docs/docs/assets/gifs/demo.gif)
101
104
 
102
- For more detailed steps, please refer to the content in the [Tutorial / Demo](https://fkcptlst.github.io/labtasker/latest/guide/basic/).
105
+ For more detailed steps, please refer to the content in
106
+ the [Tutorial / Demo](https://fkcptlst.github.io/labtasker/latest/guide/basic/).
103
107
 
104
108
  ## ⚡️ Features
105
109
 
@@ -111,23 +115,31 @@ For more detailed steps, please refer to the content in the [Tutorial / Demo](ht
111
115
  ## 🔮 Supercharge Your ML Experiments with Labtasker
112
116
 
113
117
  - ⚡️ **Effortless Parallelization:** Distribute tasks across multiple GPU workers with just a few lines of code.
114
- - 🛡️ **Intelligent Failure Management:** Automatically capture exceptions, retry failed tasks, and maintain detailed error logs.
115
- - 🔄 **Seamless Recovery:** Resume failed experiments with a single command - no more scavenging through logs or directories.
116
- - 🎯 **Real-time Prioritization:** Changed your mind about experiment settings? Instantly cancel, add, or reschedule tasks without disrupting existing ones.
117
- - 🤖 **Workflow Automation:** Set up smart event triggers for email notifications or task workflow based on FSM transition events.
118
- - 📊 **Streamlined Logging:** All stdout/stderr automatically organized in `.labtasker/logs` - zero configuration required.
119
- - 🧩 **Extensible Plugin System:** Create custom command combinations or leverage community plugins to extend functionality.
118
+ - 🛡️ **Intelligent Failure Management:** Automatically capture exceptions, retry failed tasks, and maintain detailed
119
+ error logs.
120
+ - 🔄 **Seamless Recovery:** Resume failed experiments with a single command - no more scavenging through logs or
121
+ directories.
122
+ - 🎯 **Real-time Prioritization:** Changed your mind about experiment settings? Instantly cancel, add, or reschedule
123
+ tasks without disrupting existing ones.
124
+ - 🤖 **Workflow Automation:** Set up smart event triggers for email notifications or task workflow based on FSM
125
+ transition events.
126
+ - 📊 **Streamlined Logging:** All stdout/stderr automatically organized in `.labtasker/logs` - zero configuration
127
+ required.
128
+ - 🧩 **Extensible Plugin System:** Create custom command combinations or leverage community plugins to extend
129
+ functionality.
120
130
 
121
131
  ## 🛠️ Installation
122
132
 
123
133
  > [!NOTE]
124
134
  > You need a running Labtasker server to use the client tools.
135
+ > Simply use the installed Python CLI `labtasker-server serve` or use docker-compose to deploy the server.
125
136
  > See [deployment instructions](https://fkcptlst.github.io/labtasker/latest/install/deployment/).
126
137
 
127
- ### 1. Install Client Tools via PyPI
138
+ ### 1. Install via PyPI
128
139
 
129
140
  ```bash
130
- pip install labtasker
141
+ # Install with optional bundled plugins
142
+ pip install 'labtasker[plugins]'
131
143
  ```
132
144
 
133
145
  ### 2. Install the Latest Version from GitHub
@@ -138,6 +150,12 @@ pip install git+https://github.com/fkcptlst/labtasker.git
138
150
 
139
151
  ## 🚀 Quick Start
140
152
 
153
+ Use the following command to launch a labtasker server in the background:
154
+
155
+ ```bash
156
+ labtasker-server serve &
157
+ ```
158
+
141
159
  Use the following command to quickly setup a labtasker queue for your project:
142
160
 
143
161
  ```bash
@@ -146,6 +164,10 @@ labtasker init
146
164
 
147
165
  Then, use `labtasker submit` to submit tasks and use `labtasker loop` to run tasks across any number of workers.
148
166
 
167
+ > [!TIP]
168
+ > If you think manually writing 2 scripts for submit and run is laborious, you can checkout the [labtasker-plugin-script-generate](https://github.com/fkcptlst/labtasker-plugin-script-generate).
169
+ > It automatically generate 2 scripts based on the script you provided.
170
+
149
171
  ## 📚 Documentation
150
172
 
151
173
  For detailed information on demo, tutorial, deployment, usage, please refer to
@@ -1,6 +1,6 @@
1
1
  <p align="center"><em>Make your ML experiment wrapper scripts smarter with...</em></p>
2
2
  <h1 align="center" style="font-size: 40px;"> <a href="">Labtasker</a></h1>
3
- <p align="center"><a href="https://fkcptlst.github.io/labtasker/latest/install/install/">Install</a> • <a href="https://fkcptlst.github.io/labtasker/latest/guide/basic/">Tutorial / Demo</a> • <a href="https://fkcptlst.github.io/labtasker/latest/">Documentation</a> • <a href="https://fkcptlst.github.io/labtasker/dev/faq/">FAQs</a> • <a href="https://github.com/fkcptlst/labtasker/releases">Releases</a></p>
3
+ <p align="center"><a href="https://fkcptlst.github.io/labtasker/latest/install/install/">Install</a> • <a href="https://fkcptlst.github.io/labtasker/latest/guide/basic/">Tutorial / Demo</a> • <a href="https://fkcptlst.github.io/labtasker/latest/">Documentation</a> • <a href="https://fkcptlst.github.io/labtasker/latest/faq/">FAQs</a> • <a href="https://github.com/fkcptlst/labtasker/releases">Releases</a></p>
4
4
 
5
5
  <p align="center">
6
6
  <img src="https://github.com/fkcptlst/labtasker/actions/workflows/unit-test-matrix.yml/badge.svg" alt="unit-test-matrix" />
@@ -33,7 +33,8 @@ such as sending emails on task failure.
33
33
 
34
34
  ![demo](docs/docs/assets/gifs/demo.gif)
35
35
 
36
- For more detailed steps, please refer to the content in the [Tutorial / Demo](https://fkcptlst.github.io/labtasker/latest/guide/basic/).
36
+ For more detailed steps, please refer to the content in
37
+ the [Tutorial / Demo](https://fkcptlst.github.io/labtasker/latest/guide/basic/).
37
38
 
38
39
  ## ⚡️ Features
39
40
 
@@ -45,23 +46,31 @@ For more detailed steps, please refer to the content in the [Tutorial / Demo](ht
45
46
  ## 🔮 Supercharge Your ML Experiments with Labtasker
46
47
 
47
48
  - ⚡️ **Effortless Parallelization:** Distribute tasks across multiple GPU workers with just a few lines of code.
48
- - 🛡️ **Intelligent Failure Management:** Automatically capture exceptions, retry failed tasks, and maintain detailed error logs.
49
- - 🔄 **Seamless Recovery:** Resume failed experiments with a single command - no more scavenging through logs or directories.
50
- - 🎯 **Real-time Prioritization:** Changed your mind about experiment settings? Instantly cancel, add, or reschedule tasks without disrupting existing ones.
51
- - 🤖 **Workflow Automation:** Set up smart event triggers for email notifications or task workflow based on FSM transition events.
52
- - 📊 **Streamlined Logging:** All stdout/stderr automatically organized in `.labtasker/logs` - zero configuration required.
53
- - 🧩 **Extensible Plugin System:** Create custom command combinations or leverage community plugins to extend functionality.
49
+ - 🛡️ **Intelligent Failure Management:** Automatically capture exceptions, retry failed tasks, and maintain detailed
50
+ error logs.
51
+ - 🔄 **Seamless Recovery:** Resume failed experiments with a single command - no more scavenging through logs or
52
+ directories.
53
+ - 🎯 **Real-time Prioritization:** Changed your mind about experiment settings? Instantly cancel, add, or reschedule
54
+ tasks without disrupting existing ones.
55
+ - 🤖 **Workflow Automation:** Set up smart event triggers for email notifications or task workflow based on FSM
56
+ transition events.
57
+ - 📊 **Streamlined Logging:** All stdout/stderr automatically organized in `.labtasker/logs` - zero configuration
58
+ required.
59
+ - 🧩 **Extensible Plugin System:** Create custom command combinations or leverage community plugins to extend
60
+ functionality.
54
61
 
55
62
  ## 🛠️ Installation
56
63
 
57
64
  > [!NOTE]
58
65
  > You need a running Labtasker server to use the client tools.
66
+ > Simply use the installed Python CLI `labtasker-server serve` or use docker-compose to deploy the server.
59
67
  > See [deployment instructions](https://fkcptlst.github.io/labtasker/latest/install/deployment/).
60
68
 
61
- ### 1. Install Client Tools via PyPI
69
+ ### 1. Install via PyPI
62
70
 
63
71
  ```bash
64
- pip install labtasker
72
+ # Install with optional bundled plugins
73
+ pip install 'labtasker[plugins]'
65
74
  ```
66
75
 
67
76
  ### 2. Install the Latest Version from GitHub
@@ -72,6 +81,12 @@ pip install git+https://github.com/fkcptlst/labtasker.git
72
81
 
73
82
  ## 🚀 Quick Start
74
83
 
84
+ Use the following command to launch a labtasker server in the background:
85
+
86
+ ```bash
87
+ labtasker-server serve &
88
+ ```
89
+
75
90
  Use the following command to quickly setup a labtasker queue for your project:
76
91
 
77
92
  ```bash
@@ -80,6 +95,10 @@ labtasker init
80
95
 
81
96
  Then, use `labtasker submit` to submit tasks and use `labtasker loop` to run tasks across any number of workers.
82
97
 
98
+ > [!TIP]
99
+ > If you think manually writing 2 scripts for submit and run is laborious, you can checkout the [labtasker-plugin-script-generate](https://github.com/fkcptlst/labtasker-plugin-script-generate).
100
+ > It automatically generate 2 scripts based on the script you provided.
101
+
83
102
  ## 📚 Documentation
84
103
 
85
104
  For detailed information on demo, tutorial, deployment, usage, please refer to
@@ -67,10 +67,10 @@ services:
67
67
  - DB_HOST=mongodb
68
68
  - DB_PORT=${DB_PORT:-27017}
69
69
  - API_HOST=${API_HOST:-0.0.0.0}
70
- - API_PORT=${API_PORT:-8080}
70
+ - API_PORT=${API_PORT:-9321}
71
71
  - PERIODIC_TASK_INTERVAL=${PERIODIC_TASK_INTERVAL:-30}
72
72
  ports:
73
- - "${API_PORT:-8080}:${API_PORT:-8080}"
73
+ - "${API_PORT:-9321}:${API_PORT:-9321}"
74
74
  depends_on:
75
75
  mongodb:
76
76
  condition: service_healthy
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.2"
1
+ __version__ = "0.2.4"
2
2
 
3
3
  from labtasker.client.client_api import *
4
4
  from labtasker.client.core.config import get_client_config
@@ -206,7 +206,7 @@ class TaskFetchResponse(BaseResponseModel):
206
206
 
207
207
  class TaskLsRequest(BaseRequestModel):
208
208
  offset: int = Field(0, ge=0)
209
- limit: int = Field(100, ge=0, le=1000)
209
+ limit: int = Field(100, gt=0, le=1000)
210
210
  task_id: Optional[str] = None
211
211
  task_name: Optional[str] = None
212
212
  status: Optional[str] = Field(
@@ -268,7 +268,7 @@ class WorkerStatusUpdateRequest(BaseRequestModel):
268
268
 
269
269
  class WorkerLsRequest(BaseRequestModel):
270
270
  offset: int = Field(0, ge=0)
271
- limit: int = Field(100, ge=0, le=1000)
271
+ limit: int = Field(100, gt=0, le=1000)
272
272
  worker_id: Optional[str] = None
273
273
  worker_name: Optional[str] = None
274
274
  status: Optional[str] = Field(None, pattern=r"^(active|suspended|crashed)$")
@@ -83,7 +83,8 @@ def setup_endpoint_url() -> Tuple[str, bool]:
83
83
 
84
84
  # 1. Prompt
85
85
  url = InputPrompt(
86
- "URL of your Labtasker server (http://host:port or https://xxx.yyy): ",
86
+ "URL of your Labtasker server: ",
87
+ default_text="http://localhost:9321",
87
88
  validator=validator,
88
89
  ).prompt()
89
90
  url = str(HttpUrl(url))
@@ -186,7 +187,9 @@ def setup_queue(base_url, base_url_verified) -> Tuple[str, str]:
186
187
  except LabtaskerHTTPStatusError:
187
188
  pass
188
189
 
189
- yes = ConfirmPrompt(question="Would you like to create this queue?").prompt()
190
+ yes = ConfirmPrompt(
191
+ question="Would you like to create this queue?", default_choice=True
192
+ ).prompt()
190
193
  if yes:
191
194
  try:
192
195
  resp = create_queue(queue_name, password, client=client)
@@ -1,17 +1,14 @@
1
1
  """Implements `labtasker loop xxx`"""
2
2
 
3
3
  import json
4
- import os
5
- import shlex
6
4
  import subprocess
5
+ import sys
7
6
  from collections import defaultdict
8
7
  from typing import List, Optional
9
8
 
10
9
  import typer
11
10
  from typing_extensions import Annotated
12
11
 
13
- import labtasker
14
- import labtasker.client.core.context
15
12
  from labtasker.client.cli.cli import app
16
13
  from labtasker.client.core.cli_utils import (
17
14
  cli_utils_decorator,
@@ -20,8 +17,9 @@ from labtasker.client.core.cli_utils import (
20
17
  )
21
18
  from labtasker.client.core.cmd_parser import cmd_interpolate
22
19
  from labtasker.client.core.config import get_client_config
23
- from labtasker.client.core.exceptions import CmdParserError
24
- from labtasker.client.core.job_runner import finish, loop_run
20
+ from labtasker.client.core.context import task_info
21
+ from labtasker.client.core.exceptions import CmdParserError, _LabtaskerJobFailed
22
+ from labtasker.client.core.job_runner import loop_run
25
23
  from labtasker.client.core.logging import (
26
24
  logger,
27
25
  set_verbose,
@@ -56,9 +54,10 @@ def loop(
56
54
  ] = None,
57
55
  option_cmd: str = typer.Option(
58
56
  None,
59
- "--cmd",
57
+ "--command",
58
+ "--command",
60
59
  "-c",
61
- help="Alternative way to specify the command to run. Supports the same argument interpolation the same way as the positional argument. Except you need to quote the entire command.",
60
+ help="Specify the command to run with shell=True. Supports the same argument interpolation the same way as the positional argument. Except you need to quote the entire command.",
62
61
  ),
63
62
  extra_filter: Optional[str] = typer.Option(
64
63
  None,
@@ -99,13 +98,17 @@ def loop(
99
98
  """
100
99
  if cmd and option_cmd:
101
100
  raise typer.BadParameter(
102
- "Only one of [CMD] and [--cmd] can be specified. Please use one of them."
101
+ "Only one of [CMD] and [--command] can be specified. Please use one of them."
103
102
  )
104
103
 
105
- cmd = cmd if cmd else shlex.split(option_cmd, posix=(os.name == "posix"))
104
+ cmd = cmd if cmd else option_cmd
105
+ if not cmd and not sys.stdin.isatty():
106
+ # try reading multi-line cmd from stdin if shell mode
107
+ cmd = sys.stdin.read()
108
+
106
109
  if not cmd:
107
110
  raise typer.BadParameter(
108
- "Command cannot be empty. Either specify via positional argument [CMD] or `--cmd`."
111
+ "Command cannot be empty. Either specify via positional argument [CMD] or `--command`."
109
112
  )
110
113
 
111
114
  parsed_filter = parse_filter(extra_filter)
@@ -145,11 +148,16 @@ def loop(
145
148
  )
146
149
  logger.info(f"Prepared to run interpolated command: {interpolated_cmd}")
147
150
 
151
+ shell = False
152
+ if isinstance(interpolated_cmd, str):
153
+ shell = True
154
+
148
155
  with subprocess.Popen(
149
156
  args=interpolated_cmd,
150
157
  stdout=subprocess.PIPE,
151
158
  stderr=subprocess.PIPE,
152
159
  text=True,
160
+ shell=shell,
153
161
  ) as process:
154
162
  while True:
155
163
  output = process.stdout.readline()
@@ -166,11 +174,11 @@ def loop(
166
174
 
167
175
  process.wait()
168
176
  if process.returncode != 0:
169
- finish("failed")
170
- else:
171
- finish("success")
177
+ raise _LabtaskerJobFailed(
178
+ "Job process finished with non-zero exit code."
179
+ )
172
180
 
173
- logger.info(f"Task {labtasker.client.core.context.task_info().task_id} ended.")
181
+ logger.info(f"Task {task_info().task_id} ended.")
174
182
 
175
183
  run_cmd()
176
184
 
@@ -3,6 +3,7 @@
3
3
  import io
4
4
  import json
5
5
  import os
6
+ import sys
6
7
  import tempfile
7
8
  from functools import partial
8
9
  from pathlib import Path
@@ -51,6 +52,10 @@ from labtasker.constants import Priority
51
52
  app = typer.Typer()
52
53
 
53
54
 
55
+ class _ReEdit(Exception):
56
+ pass
57
+
58
+
54
59
  def commented_seq_from_dict_list(
55
60
  entries: List[Dict[str, Any]],
56
61
  ) -> ruamel.yaml.CommentedSeq:
@@ -373,6 +378,10 @@ def update(
373
378
  help="Updated values of fields. Specify multiple options via repeating `-u`. "
374
379
  "E.g. `labtasker task update -u args.arg1=foo -u metadata.tag=test`",
375
380
  ),
381
+ limit: int = typer.Option(
382
+ 1000,
383
+ help="Limit the number of tasks returned.",
384
+ ),
376
385
  offset: int = typer.Option(
377
386
  0,
378
387
  help="Initial offset for pagination (In case there are too many items for update, only 1000 results starting from offset is displayed. "
@@ -441,7 +450,7 @@ def update(
441
450
  task_name=task_name,
442
451
  status=status,
443
452
  extra_filter=extra_filter,
444
- limit=1000,
453
+ limit=limit,
445
454
  offset=offset,
446
455
  ).content
447
456
  use_editor = not updates
@@ -495,6 +504,22 @@ def handle_editor_mode(old_tasks, readonly_fields, editor):
495
504
  with open(temp_file_path, "r", encoding="utf-8") as temp_file:
496
505
  modified = yaml.safe_load(temp_file)
497
506
 
507
+ # check if length match
508
+ if len(modified) != len(old_tasks_primitive):
509
+ raise _ReEdit(
510
+ "Number of tasks in the file should match the number of tasks in the original list. "
511
+ f"Expected {len(old_tasks_primitive)}, got {len(modified)}. "
512
+ "If you want to add/delete tasks, you should use `labtasker task submit` or `labtasker task delete`."
513
+ )
514
+
515
+ # check if task order match
516
+ for mod, old in zip(modified, old_tasks_primitive):
517
+ if mod["task_id"] != old["task_id"]:
518
+ raise _ReEdit(
519
+ "Task IDs in the modified file should match the task IDs in the original list in the same order. "
520
+ f"Expected {old['task_id']}, got {mod['task_id']}."
521
+ )
522
+
498
523
  # Calculate diffs and create task updates
499
524
  update_dicts = diff(
500
525
  old_tasks_primitive, modified, readonly_fields=list(readonly_fields)
@@ -538,8 +563,8 @@ def handle_editor_mode(old_tasks, readonly_fields, editor):
538
563
  # No validation errors, we can break the loop
539
564
  break
540
565
 
541
- except yaml.error.YAMLError as e:
542
- stderr_console.print(f"[bold red]YAML Error:[/bold red] {str(e)}")
566
+ except (_ReEdit, yaml.error.YAMLError) as e:
567
+ stderr_console.print(f"[bold red]Error:[/bold red] {str(e)}")
543
568
  if not typer.confirm("Continue to edit?", default=True):
544
569
  raise typer.Abort()
545
570
  # Continue the loop with the current file state
@@ -607,7 +632,9 @@ def display_updated_tasks(updated_tasks, update_dicts):
607
632
  # If replace_fields is empty or not available, try to infer from the update
608
633
  if not modified_fields:
609
634
  modified_fields = [
610
- k for k in update_dict.keys() if k not in ("_id", "replace_fields")
635
+ k
636
+ for k in update_dict.keys()
637
+ if k not in ("_id", "task_id", "replace_fields")
611
638
  ]
612
639
 
613
640
  # Add comment for each modified field
@@ -634,7 +661,10 @@ def display_updated_tasks(updated_tasks, update_dicts):
634
661
  @app.command()
635
662
  @cli_utils_decorator
636
663
  def delete(
637
- task_id: str = typer.Argument(..., help="ID of the task to delete."),
664
+ task_ids: List[str] = typer.Argument(
665
+ ... if sys.stdin.isatty() else None,
666
+ help="IDs of the task to delete.",
667
+ ),
638
668
  yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
639
669
  ):
640
670
  """
@@ -647,14 +677,17 @@ def delete(
647
677
  labtasker task delete task-123
648
678
  labtasker task delete task-123 --yes # Skip confirmation
649
679
  """
680
+ if task_ids is None: # read from stdin to support piping
681
+ task_ids = [line.strip() for line in sys.stdin.readlines() if line.strip()]
650
682
  if not yes:
651
683
  typer.confirm(
652
- f"Are you sure you want to delete task '{task_id}'?",
684
+ f"Are you sure you want to delete tasks '{task_ids}'?",
653
685
  abort=True,
654
686
  )
655
687
  try:
656
- delete_task(task_id=task_id)
657
- stdout_console.print(f"Task {task_id} deleted.")
688
+ for task_id in task_ids:
689
+ delete_task(task_id=task_id)
690
+ stdout_console.print(f"Task {task_id} deleted.")
658
691
  except LabtaskerHTTPStatusError as e:
659
692
  if e.response.status_code == HTTP_404_NOT_FOUND:
660
693
  raise typer.BadParameter("Task not found")
@@ -1,8 +1,9 @@
1
1
  """Manage workers (CRUD operations)."""
2
2
 
3
3
  import json
4
+ import sys
4
5
  from functools import partial
5
- from typing import Optional
6
+ from typing import List, Optional
6
7
 
7
8
  import click
8
9
  import typer
@@ -206,7 +207,9 @@ def report(
206
207
  @app.command()
207
208
  @cli_utils_decorator
208
209
  def delete(
209
- worker_id: str = typer.Argument(..., help="ID of the worker to delete."),
210
+ worker_ids: List[str] = typer.Argument(
211
+ ... if sys.stdin.isatty() else None, help="IDs of the worker to delete."
212
+ ),
210
213
  cascade_update: bool = typer.Option(
211
214
  True,
212
215
  help="Whether to cascade set the worker id of relevant tasks to None",
@@ -216,14 +219,17 @@ def delete(
216
219
  """
217
220
  Delete a worker by worker_id.
218
221
  """
222
+ if worker_ids is None: # read from stdin to support piping
223
+ worker_ids = [line.strip() for line in sys.stdin.readlines() if line.strip()]
219
224
  if not yes:
220
225
  typer.confirm(
221
- f"Are you sure you want to delete worker '{worker_id}'?",
226
+ f"Are you sure you want to delete worker '{worker_ids}'?",
222
227
  abort=True,
223
228
  )
224
229
  try:
225
- delete_worker(worker_id=worker_id, cascade_update=cascade_update)
226
- stdout_console.print(f"Worker {worker_id} deleted.")
230
+ for worker_id in worker_ids:
231
+ delete_worker(worker_id=worker_id, cascade_update=cascade_update)
232
+ stdout_console.print(f"Worker {worker_id} deleted.")
227
233
  except LabtaskerHTTPStatusError as e:
228
234
  if e.response.status_code == HTTP_404_NOT_FOUND:
229
235
  raise typer.BadParameter("Worker not found")
@@ -12,7 +12,7 @@ import httpx
12
12
  import pydantic
13
13
  import typer
14
14
  import yaml
15
- from noneprompt import CancelledError, Choice, ListPrompt
15
+ from noneprompt import Choice, ListPrompt
16
16
  from prompt_toolkit import Application
17
17
  from prompt_toolkit.layout import Float, FloatContainer, Layout, Window
18
18
  from prompt_toolkit.layout.controls import FormattedTextControl
@@ -543,11 +543,17 @@ class TimedListPrompt(ListPrompt):
543
543
  """A ListPrompt with countdown and timeout functionality."""
544
544
 
545
545
  def __init__(
546
- self, *args, timeout: int = 10, default: Optional[Choice] = None, **kwargs
546
+ self,
547
+ *args,
548
+ timeout: int = 10,
549
+ default: Optional[Choice] = None,
550
+ keyboard_interrupt_default: Optional[Choice] = None,
551
+ **kwargs,
547
552
  ):
548
553
  super().__init__(*args, **kwargs)
549
554
  self.timeout = timeout
550
555
  self.default = default
556
+ self.keyboard_interrupt_default = keyboard_interrupt_default
551
557
  self.countdown_text = [
552
558
  ("class:countdown", "TIMER: "),
553
559
  ("class:countdown-emphasis", f"{timeout}"),
@@ -604,32 +610,32 @@ class TimedListPrompt(ListPrompt):
604
610
  app = self._build_application(no_ansi=False, style=Style([]))
605
611
  countdown_task = asyncio.create_task(self._run_countdown(app))
606
612
 
607
- try:
608
- result = await app.run_async()
609
- countdown_task.cancel()
610
- if result is not None and not isinstance(result, type(...)):
611
- return result
612
- if self.default is not None:
613
- return self.default
614
- raise CancelledError("No answer selected!")
615
- except (asyncio.CancelledError, KeyboardInterrupt):
616
- return None
613
+ result = await app.run_async()
614
+ countdown_task.cancel()
615
+ if result is None: # timed out
616
+ return self.default
617
+ elif isinstance(result, type(...)): # keyboard interrupt
618
+ return self.keyboard_interrupt_default
619
+ else:
620
+ return result
617
621
 
618
622
 
619
623
  def make_a_choice(
620
- question: str, options: List[Choice], default: Choice, timeout: int = 10
624
+ question: str,
625
+ options: List[Choice],
626
+ default: Choice,
627
+ keyboard_interrupt_default: Choice,
628
+ timeout: int = 10,
621
629
  ) -> Optional[Choice]:
622
630
  """Helper function to create and run a timed choice prompt."""
623
631
  prompt = TimedListPrompt(
624
- question, choices=options, timeout=timeout, default=default
632
+ question,
633
+ choices=options,
634
+ timeout=timeout,
635
+ default=default,
636
+ keyboard_interrupt_default=keyboard_interrupt_default,
625
637
  )
626
- try:
627
- result = asyncio.run(prompt.prompt_async())
628
- if isinstance(result, type(...)):
629
- return default
630
- return result
631
- except KeyboardInterrupt:
632
- return default
638
+ return asyncio.run(prompt.prompt_async())
633
639
 
634
640
 
635
641
  ls_format_iter = {