labtasker 0.2.3__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 (186) hide show
  1. labtasker-0.2.4/.gitmodules +8 -0
  2. {labtasker-0.2.3 → labtasker-0.2.4}/.pre-commit-config.yaml +2 -2
  3. {labtasker-0.2.3 → labtasker-0.2.4}/PKG-INFO +25 -11
  4. {labtasker-0.2.3 → labtasker-0.2.4}/README.md +20 -8
  5. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/__init__.py +1 -1
  6. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/api_models.py +2 -2
  7. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/cli/loop.py +23 -15
  8. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/cli/task.py +41 -8
  9. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/cli/worker.py +11 -5
  10. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/cli_utils.py +27 -21
  11. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/parser.py +16 -11
  12. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/config.py +2 -2
  13. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/exceptions.py +12 -0
  14. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/job_runner.py +49 -7
  15. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/database.py +58 -23
  16. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/endpoints.py +5 -0
  17. {labtasker-0.2.3 → labtasker-0.2.4}/pyproject.toml +6 -4
  18. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/conftest.py +10 -0
  19. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/test_loop.py +22 -0
  20. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/test_task.py +18 -0
  21. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_with_resolver.py +16 -0
  22. labtasker-0.2.3/.gitmodules +0 -4
  23. {labtasker-0.2.3 → labtasker-0.2.4}/.coveragerc +0 -0
  24. {labtasker-0.2.3 → labtasker-0.2.4}/.dockerignore +0 -0
  25. {labtasker-0.2.3 → labtasker-0.2.4}/.flake8 +0 -0
  26. {labtasker-0.2.3 → labtasker-0.2.4}/.gitattributes +0 -0
  27. {labtasker-0.2.3 → labtasker-0.2.4}/.gitignore +0 -0
  28. {labtasker-0.2.3 → labtasker-0.2.4}/.pytest.ini +0 -0
  29. {labtasker-0.2.3 → labtasker-0.2.4}/.python-version +0 -0
  30. {labtasker-0.2.3 → labtasker-0.2.4}/Dockerfile +0 -0
  31. {labtasker-0.2.3 → labtasker-0.2.4}/LICENSE +0 -0
  32. {labtasker-0.2.3 → labtasker-0.2.4}/MANIFEST.in +0 -0
  33. {labtasker-0.2.3 → labtasker-0.2.4}/PRIVACY.md +0 -0
  34. {labtasker-0.2.3 → labtasker-0.2.4}/demo/advanced/custom_resolver/submit_job.py +0 -0
  35. {labtasker-0.2.3 → labtasker-0.2.4}/demo/advanced/custom_resolver/w.py +0 -0
  36. {labtasker-0.2.3 → labtasker-0.2.4}/demo/advanced/custom_resolver/wo.py +0 -0
  37. {labtasker-0.2.3 → labtasker-0.2.4}/demo/advanced/event_system/email_on_task_failure.py +0 -0
  38. {labtasker-0.2.3 → labtasker-0.2.4}/demo/advanced/event_system/send_email.py +0 -0
  39. {labtasker-0.2.3 → labtasker-0.2.4}/demo/advanced/event_system/sim_unstable_job.py +0 -0
  40. {labtasker-0.2.3 → labtasker-0.2.4}/demo/advanced/event_system/submit.sh +0 -0
  41. {labtasker-0.2.3 → labtasker-0.2.4}/demo/basic/bash_demo/job_main.py +0 -0
  42. {labtasker-0.2.3 → labtasker-0.2.4}/demo/basic/bash_demo/run_job.sh +0 -0
  43. {labtasker-0.2.3 → labtasker-0.2.4}/demo/basic/bash_demo/submit_job.sh +0 -0
  44. {labtasker-0.2.3 → labtasker-0.2.4}/demo/basic/python_demo/run_job.py +0 -0
  45. {labtasker-0.2.3 → labtasker-0.2.4}/demo/basic/python_demo/submit_job.py +0 -0
  46. {labtasker-0.2.3 → labtasker-0.2.4}/docker/mongodb/init.d/init-keyfile.sh +0 -0
  47. {labtasker-0.2.3 → labtasker-0.2.4}/docker/mongodb/post-init.d/init-mongo.sh +0 -0
  48. {labtasker-0.2.3 → labtasker-0.2.4}/docker-compose.yml +0 -0
  49. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/__main__.py +0 -0
  50. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/__init__.py +0 -0
  51. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/cli/__init__.py +0 -0
  52. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/cli/cli.py +0 -0
  53. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/cli/config.py +0 -0
  54. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/cli/event.py +0 -0
  55. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/cli/init.py +0 -0
  56. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/cli/queue.py +0 -0
  57. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/client_api.py +0 -0
  58. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/__init__.py +0 -0
  59. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/api.py +0 -0
  60. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/LabCmd.g4 +0 -0
  61. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/LabCmdLexer.g4 +0 -0
  62. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/__init__.py +0 -0
  63. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/generated/LabCmd.py +0 -0
  64. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/generated/LabCmdLexer.py +0 -0
  65. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/generated/LabCmdListener.py +0 -0
  66. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/cmd_parser/generated/__init__.py +0 -0
  67. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/context.py +0 -0
  68. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/events.py +0 -0
  69. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/heartbeat.py +0 -0
  70. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/logging.py +0 -0
  71. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/paths.py +0 -0
  72. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/plugin_utils.py +0 -0
  73. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/query_transpiler.py +0 -0
  74. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/resolver/__init__.py +0 -0
  75. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/resolver/models.py +0 -0
  76. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/resolver/utils.py +0 -0
  77. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/utils.py +0 -0
  78. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/core/version_checker.py +0 -0
  79. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/templates/labtasker_root/.gitignore +0 -0
  80. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/templates/labtasker_root/client.toml +0 -0
  81. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/client/templates/labtasker_root/logs/.gitkeep +0 -0
  82. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/constants.py +0 -0
  83. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/filtering.py +0 -0
  84. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/security.py +0 -0
  85. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/__init__.py +0 -0
  86. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/cli.py +0 -0
  87. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/config.py +0 -0
  88. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/db_utils.py +0 -0
  89. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/dependencies.py +0 -0
  90. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/embedded_db.py +0 -0
  91. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/event_manager.py +0 -0
  92. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/fsm.py +0 -0
  93. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/server/logging.py +0 -0
  94. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker/utils.py +0 -0
  95. {labtasker-0.2.3 → labtasker-0.2.4}/labtasker.egg-info/SOURCES.txt +0 -0
  96. {labtasker-0.2.3 → labtasker-0.2.4}/script_tests/test_ban_datetime_now/allowed.py +0 -0
  97. {labtasker-0.2.3 → labtasker-0.2.4}/script_tests/test_ban_datetime_now/disallowed.py +0 -0
  98. {labtasker-0.2.3 → labtasker-0.2.4}/script_tests/test_ban_datetime_now/test_ban.py +0 -0
  99. {labtasker-0.2.3 → labtasker-0.2.4}/scripts/asciinema_adjust_timestamp.py +0 -0
  100. {labtasker-0.2.3 → labtasker-0.2.4}/scripts/check_version.py +0 -0
  101. {labtasker-0.2.3 → labtasker-0.2.4}/scripts/datetime_now_checker.py +0 -0
  102. {labtasker-0.2.3 → labtasker-0.2.4}/server.example.env +0 -0
  103. {labtasker-0.2.3 → labtasker-0.2.4}/setup.cfg +0 -0
  104. {labtasker-0.2.3 → labtasker-0.2.4}/tests/__init__.py +0 -0
  105. {labtasker-0.2.3 → labtasker-0.2.4}/tests/conftest.py +0 -0
  106. {labtasker-0.2.3 → labtasker-0.2.4}/tests/demo_pager_iterator.py +0 -0
  107. {labtasker-0.2.3 → labtasker-0.2.4}/tests/dummy_jobs/job_1.py +0 -0
  108. {labtasker-0.2.3 → labtasker-0.2.4}/tests/fixtures/__init__.py +0 -0
  109. {labtasker-0.2.3 → labtasker-0.2.4}/tests/fixtures/database/__init__.py +0 -0
  110. {labtasker-0.2.3 → labtasker-0.2.4}/tests/fixtures/database/mock.py +0 -0
  111. {labtasker-0.2.3 → labtasker-0.2.4}/tests/fixtures/database/real.py +0 -0
  112. {labtasker-0.2.3 → labtasker-0.2.4}/tests/fixtures/logging.py +0 -0
  113. {labtasker-0.2.3 → labtasker-0.2.4}/tests/fixtures/mock_datetime_now.py +0 -0
  114. {labtasker-0.2.3 → labtasker-0.2.4}/tests/fixtures/server/__init__.py +0 -0
  115. {labtasker-0.2.3 → labtasker-0.2.4}/tests/fixtures/server/async_app.py +0 -0
  116. {labtasker-0.2.3 → labtasker-0.2.4}/tests/fixtures/server/sync_app.py +0 -0
  117. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_api_models.py +0 -0
  118. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/__init__.py +0 -0
  119. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/__init__.py +0 -0
  120. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/conftest.py +0 -0
  121. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/test_basic.py +0 -0
  122. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/test_config.py +0 -0
  123. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/test_event.py +0 -0
  124. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/test_init.py +0 -0
  125. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/test_queue.py +0 -0
  126. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_cli/test_worker.py +0 -0
  127. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/__init__.py +0 -0
  128. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_cli_utils.py +0 -0
  129. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_event/__init__.py +0 -0
  130. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_event/test_concurrency_job_flow_event.py +0 -0
  131. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_event/test_event_listener_basic.py +0 -0
  132. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_event/test_various_actions.py +0 -0
  133. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_event/utils.py +0 -0
  134. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_heartbeat.py +0 -0
  135. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_job_runner.py +0 -0
  136. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_logging.py +0 -0
  137. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_loop_internal_error_handler.py +0 -0
  138. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_pager_iterator.py +0 -0
  139. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_parser.py +0 -0
  140. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/__init__.py +0 -0
  141. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/conftest.py +0 -0
  142. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/test_behavior.py +0 -0
  143. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/test_matching.py +0 -0
  144. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/test_utils.py +0 -0
  145. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_query_transpiler/utils.py +0 -0
  146. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_resolver.py +0 -0
  147. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_concurrency/__init__.py +0 -0
  148. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_concurrency/run_concurrent.py +0 -0
  149. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_concurrency/test_runner_concurrency_success_failure.py +0 -0
  150. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_concurrency/test_runner_high_concurrency.py +0 -0
  151. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_timeout/__init__.py +0 -0
  152. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_timeout/conftest.py +0 -0
  153. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_timeout/test_job_runner_timeout.py +0 -0
  154. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_runner_timeout/test_job_runner_with_resolver_timeout.py +0 -0
  155. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_server_notification_and_client_version.py +0 -0
  156. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_client/test_core/test_version_checker.py +0 -0
  157. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_filtering/__init__.py +0 -0
  158. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_filtering/exception_utils.py +0 -0
  159. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_filtering/test_exception_filtering.py +0 -0
  160. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_mock_time.py +0 -0
  161. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_security.py +0 -0
  162. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/__init__.py +0 -0
  163. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/conftest.py +0 -0
  164. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_database/__init__.py +0 -0
  165. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_database/conftest.py +0 -0
  166. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_database/test_database_basic.py +0 -0
  167. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_database/test_fetch_extra_filter.py +0 -0
  168. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_database/test_query_dict_to_mongo_filter.py +0 -0
  169. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_database/test_required_field_fetching.py +0 -0
  170. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_db_utils/__init__.py +0 -0
  171. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_db_utils/test_arg_match.py +0 -0
  172. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_db_utils/test_keys_to_query_dict.py +0 -0
  173. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_db_utils/test_keys_to_query_dict_deepest.py +0 -0
  174. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_db_utils/test_keys_to_query_dict_topmost.py +0 -0
  175. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_embedded_db.py +0 -0
  176. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_endpoint/__init__.py +0 -0
  177. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_endpoint/test_event_basic.py +0 -0
  178. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_endpoint/test_server.py +0 -0
  179. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_endpoint/test_server_async.py +0 -0
  180. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_endpoint/test_server_async_ping.py +0 -0
  181. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_fsm.py +0 -0
  182. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_server/test_get_verified_queue_dependency.py +0 -0
  183. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_utils/__init__.py +0 -0
  184. {labtasker-0.2.3 → labtasker-0.2.4}/tests/test_utils/test_utils.py +0 -0
  185. {labtasker-0.2.3 → labtasker-0.2.4}/tests/utils.py +0 -0
  186. {labtasker-0.2.3 → labtasker-0.2.4}/tox.ini +0 -0
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: labtasker
3
- Version: 0.2.3
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
@@ -42,6 +42,7 @@ Requires-Dist: stamina<26.0.0,>=25.1.0
42
42
  Requires-Dist: noneprompt<0.2.0,>=0.1.9
43
43
  Requires-Dist: mongomock<4.4.0,>=4.3.0
44
44
  Requires-Dist: jsonpickle<5.0.0,>=4.0.2
45
+ Requires-Dist: mslex<2.0.0,>=1.3.0
45
46
  Provides-Extra: dev
46
47
  Requires-Dist: pytest<9.0.0,>=8.0.0; extra == "dev"
47
48
  Requires-Dist: pytest-cov<7.0.0,>=5.0.0; extra == "dev"
@@ -52,9 +53,9 @@ 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"
59
60
  Requires-Dist: pytest-sugar<2.0.0,>=1.0.0; extra == "dev"
60
61
  Provides-Extra: doc
@@ -63,6 +64,7 @@ Requires-Dist: mkdocs-glightbox<0.5.0,>=0.4.0; extra == "doc"
63
64
  Requires-Dist: mike<2.2.0,>=2.1.3; extra == "doc"
64
65
  Provides-Extra: plugins
65
66
  Requires-Dist: labtasker-plugin-task-count; extra == "plugins"
67
+ Requires-Dist: labtasker-plugin-script-generate; extra == "plugins"
66
68
  Dynamic: license-file
67
69
 
68
70
  <p align="center"><em>Make your ML experiment wrapper scripts smarter with...</em></p>
@@ -100,7 +102,8 @@ such as sending emails on task failure.
100
102
 
101
103
  ![demo](docs/docs/assets/gifs/demo.gif)
102
104
 
103
- 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/).
104
107
 
105
108
  ## ⚡️ Features
106
109
 
@@ -112,12 +115,18 @@ For more detailed steps, please refer to the content in the [Tutorial / Demo](ht
112
115
  ## 🔮 Supercharge Your ML Experiments with Labtasker
113
116
 
114
117
  - ⚡️ **Effortless Parallelization:** Distribute tasks across multiple GPU workers with just a few lines of code.
115
- - 🛡️ **Intelligent Failure Management:** Automatically capture exceptions, retry failed tasks, and maintain detailed error logs.
116
- - 🔄 **Seamless Recovery:** Resume failed experiments with a single command - no more scavenging through logs or directories.
117
- - 🎯 **Real-time Prioritization:** Changed your mind about experiment settings? Instantly cancel, add, or reschedule tasks without disrupting existing ones.
118
- - 🤖 **Workflow Automation:** Set up smart event triggers for email notifications or task workflow based on FSM transition events.
119
- - 📊 **Streamlined Logging:** All stdout/stderr automatically organized in `.labtasker/logs` - zero configuration required.
120
- - 🧩 **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.
121
130
 
122
131
  ## 🛠️ Installation
123
132
 
@@ -129,7 +138,8 @@ For more detailed steps, please refer to the content in the [Tutorial / Demo](ht
129
138
  ### 1. Install via PyPI
130
139
 
131
140
  ```bash
132
- pip install labtasker
141
+ # Install with optional bundled plugins
142
+ pip install 'labtasker[plugins]'
133
143
  ```
134
144
 
135
145
  ### 2. Install the Latest Version from GitHub
@@ -154,6 +164,10 @@ labtasker init
154
164
 
155
165
  Then, use `labtasker submit` to submit tasks and use `labtasker loop` to run tasks across any number of workers.
156
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
+
157
171
  ## 📚 Documentation
158
172
 
159
173
  For detailed information on demo, tutorial, deployment, usage, please refer to
@@ -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,12 +46,18 @@ 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
 
@@ -62,7 +69,8 @@ For more detailed steps, please refer to the content in the [Tutorial / Demo](ht
62
69
  ### 1. Install via PyPI
63
70
 
64
71
  ```bash
65
- pip install labtasker
72
+ # Install with optional bundled plugins
73
+ pip install 'labtasker[plugins]'
66
74
  ```
67
75
 
68
76
  ### 2. Install the Latest Version from GitHub
@@ -87,6 +95,10 @@ labtasker init
87
95
 
88
96
  Then, use `labtasker submit` to submit tasks and use `labtasker loop` to run tasks across any number of workers.
89
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
+
90
102
  ## 📚 Documentation
91
103
 
92
104
  For detailed information on demo, tutorial, deployment, usage, please refer to
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.3"
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)$")
@@ -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 = {
@@ -1,6 +1,5 @@
1
- import shlex
2
- import warnings
3
- from typing import Any, Dict, List, Set, Tuple
1
+ import os
2
+ from typing import Any, Dict, List, Set, Tuple, Union
4
3
 
5
4
  from antlr4 import CommonTokenStream, InputStream, ParserRuleContext, ParseTreeWalker
6
5
  from antlr4.error.ErrorListener import ErrorListener
@@ -19,6 +18,16 @@ from labtasker.client.core.exceptions import (
19
18
  )
20
19
  from labtasker.client.core.logging import stderr_console
21
20
 
21
+ # Posix quote and windows quote
22
+ if os.name == "nt":
23
+ import mslex
24
+
25
+ quote = mslex.quote
26
+ else:
27
+ import shlex
28
+
29
+ quote = shlex.quote
30
+
22
31
  _debug_print = False
23
32
 
24
33
 
@@ -183,7 +192,7 @@ class CmdListener(LabCmdListener):
183
192
  if isinstance(self.variable, dict):
184
193
  # convert dict into bash string
185
194
  if self.quote_dict:
186
- self.result_str += shlex.quote(reverse_quotes(str(self.variable)))
195
+ self.result_str += quote(reverse_quotes(str(self.variable)))
187
196
  else:
188
197
  self.result_str += reverse_quotes(str(self.variable))
189
198
  else:
@@ -258,8 +267,8 @@ class CustomErrorListener(ErrorListener):
258
267
 
259
268
 
260
269
  def cmd_interpolate(
261
- cmd: List[str], variable_table: Dict[str, Any]
262
- ) -> Tuple[List[str], Set[str]]:
270
+ cmd: Union[List[str], str], variable_table: Dict[str, Any]
271
+ ) -> Tuple[Union[List[str], str], Set[str]]:
263
272
  """
264
273
  Interpolate the command string %(...) with the given variable table.
265
274
 
@@ -275,11 +284,7 @@ def cmd_interpolate(
275
284
 
276
285
  """
277
286
  if isinstance(cmd, str):
278
- warnings.warn(
279
- "Using a string for 'cmd' is deprecated. Please pass a list of arguments instead.",
280
- DeprecationWarning,
281
- )
282
- return interpolate_str(cmd, variable_table) # type: ignore
287
+ return interpolate_str(cmd, variable_table)
283
288
  else:
284
289
  # cmd is a list of str
285
290
  interpolated_cmd = []
@@ -136,9 +136,9 @@ def load_client_config(
136
136
  _config = ClientConfig.model_validate(tomlkit.load(f))
137
137
 
138
138
  # register sensitive text
139
- register_sensitive_text(_config.queue.password.get_secret_value())
139
+ register_sensitive_text(_config.queue.password.get_secret_value()) # type: ignore[union-attr]
140
140
  register_sensitive_text(
141
- get_auth_headers(_config.queue.queue_name, _config.queue.password)[
141
+ get_auth_headers(_config.queue.queue_name, _config.queue.password)[ # type: ignore[union-attr]
142
142
  "Authorization"
143
143
  ]
144
144
  )
@@ -45,6 +45,18 @@ class LabtaskerConnectTimeout(httpx.ConnectTimeout, LabtaskerNetworkError):
45
45
  pass
46
46
 
47
47
 
48
+ class _LabtaskerJobFailed(LabtaskerRuntimeError):
49
+ """An internal exception used by `loop`. Triggered when joh subprocess returned non-zero exit code."""
50
+
51
+ pass
52
+
53
+
54
+ class _LabtaskerLoopExit(LabtaskerRuntimeError):
55
+ """An internal exception used by job_runner. Triggered when the loop should exit."""
56
+
57
+ pass
58
+
59
+
48
60
  class WorkerSuspended(LabtaskerRuntimeError):
49
61
  pass
50
62