labtasker 0.2.8__tar.gz → 0.2.9__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.2.8 → labtasker-0.2.9}/PKG-INFO +6 -3
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/__init__.py +3 -3
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/api_models.py +132 -10
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/cli/cli.py +2 -2
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/cli/init.py +4 -4
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/cli/loop.py +12 -81
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/cli/task.py +81 -16
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/cli/worker.py +10 -3
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/client_api.py +4 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/api.py +10 -8
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/cli_utils.py +44 -4
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/job_runner.py +2 -2
- labtasker-0.2.9/labtasker/client/core/pager.py +119 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/query_transpiler.py +55 -5
- labtasker-0.2.9/labtasker/client/core/utils.py +237 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/version_checker.py +43 -26
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/templates/labtasker_root/client.toml +1 -1
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/filtering.py +1 -1
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/database.py +2 -2
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/endpoints.py +1 -1
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/utils.py +51 -19
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker.egg-info/SOURCES.txt +1 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/pyproject.toml +8 -4
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_api_models.py +1 -1
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/test_task.py +49 -18
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_event/test_various_actions.py +2 -2
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_query_transpiler/test_matching.py +14 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_version_checker.py +6 -5
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_endpoint/test_server.py +9 -9
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_utils/test_utils.py +46 -46
- labtasker-0.2.8/labtasker/client/core/utils.py +0 -114
- {labtasker-0.2.8 → labtasker-0.2.9}/LICENSE +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/MANIFEST.in +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/README.md +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/__main__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/cli/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/cli/config.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/cli/event.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/cli/queue.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/cmd_parser/LabCmd.g4 +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/cmd_parser/LabCmdLexer.g4 +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/cmd_parser/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/cmd_parser/generated/LabCmd.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/cmd_parser/generated/LabCmdLexer.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/cmd_parser/generated/LabCmdListener.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/cmd_parser/generated/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/cmd_parser/parser.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/config.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/context.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/events.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/exceptions.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/heartbeat.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/logging.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/paths.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/plugin_utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/resolver/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/resolver/models.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/core/resolver/utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/templates/labtasker_root/.gitignore +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/client/templates/labtasker_root/logs/.gitkeep +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/constants.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/security.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/cli.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/config.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/db_utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/dependencies.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/embedded_db.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/event_manager.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/fsm.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/server/logging.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/README.txt +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/BufferedTokenStream.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/CommonTokenFactory.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/CommonTokenStream.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/FileStream.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/InputStream.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/IntervalSet.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/LL1Analyzer.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/Lexer.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/ListTokenSource.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/Parser.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/ParserInterpreter.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/ParserRuleContext.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/PredictionContext.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/Recognizer.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/RuleContext.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/StdinStream.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/Token.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/TokenStreamRewriter.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/Utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/_pygrun.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/ATN.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/ATNConfig.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/ATNConfigSet.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/ATNDeserializationOptions.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/ATNDeserializer.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/ATNSimulator.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/ATNState.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/ATNType.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/LexerATNSimulator.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/LexerAction.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/LexerActionExecutor.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/ParserATNSimulator.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/PredictionMode.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/SemanticContext.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/Transition.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/atn/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/dfa/DFA.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/dfa/DFASerializer.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/dfa/DFAState.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/dfa/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/error/DiagnosticErrorListener.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/error/ErrorListener.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/error/ErrorStrategy.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/error/Errors.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/error/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/tree/Chunk.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/tree/ParseTreeMatch.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/tree/ParseTreePattern.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/tree/ParseTreePatternMatcher.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/tree/RuleTagToken.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/tree/TokenTagToken.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/tree/Tree.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/tree/Trees.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/tree/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/xpath/XPath.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/xpath/XPathLexer.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/antlr4/xpath/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/labtasker/vendor/vendor.txt +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/setup.cfg +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/conftest.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/demo_pager_iterator.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/dummy_jobs/job_1.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/fixtures/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/fixtures/database/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/fixtures/database/mock.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/fixtures/database/real.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/fixtures/logging.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/fixtures/mock_datetime_now.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/fixtures/server/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/fixtures/server/async_app.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/fixtures/server/sync_app.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/conftest.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/conftest.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/test_basic.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/test_config.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/test_event.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/test_init.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/test_loop.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/test_queue.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_cli/test_worker.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_cli_utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_event/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_event/test_concurrency_job_flow_event.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_event/test_event_listener_basic.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_event/test_event_listener_entity_data.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_event/utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_heartbeat.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_job_runner.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_logging.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_loop_internal_error_handler.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_pager_iterator.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_parser.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_query_transpiler/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_query_transpiler/conftest.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_query_transpiler/test_behavior.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_query_transpiler/test_utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_query_transpiler/utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_resolver.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_runner_concurrency/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_runner_concurrency/run_concurrent.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_runner_concurrency/test_runner_concurrency_success_failure.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_runner_concurrency/test_runner_high_concurrency.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_runner_timeout/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_runner_timeout/conftest.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_runner_timeout/test_job_runner_timeout.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_runner_timeout/test_job_runner_with_resolver_timeout.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_runner_with_resolver.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_client/test_core/test_server_notification_and_client_version.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_filtering/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_filtering/exception_utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_filtering/test_exception_filtering.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_mock_time.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_security.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/conftest.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_database/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_database/conftest.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_database/test_database_basic.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_database/test_fetch_extra_filter.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_database/test_query_dict_to_mongo_filter.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_database/test_required_field_fetching.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_db_utils/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_db_utils/test_arg_match.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_db_utils/test_keys_to_query_dict.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_db_utils/test_keys_to_query_dict_deepest.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_db_utils/test_keys_to_query_dict_topmost.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_embedded_db.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_endpoint/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_endpoint/test_event_basic.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_endpoint/test_server_async.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_endpoint/test_server_async_ping.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_fsm.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_server/test_get_verified_queue_dependency.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/test_utils/__init__.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tests/utils.py +0 -0
- {labtasker-0.2.8 → labtasker-0.2.9}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labtasker
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
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,6 +40,8 @@ Requires-Dist: mongomock<4.4.0,>=4.3.0
|
|
|
40
40
|
Requires-Dist: jsonpickle<5.0.0,>=4.0.2
|
|
41
41
|
Requires-Dist: mslex<2.0.0,>=1.3.0
|
|
42
42
|
Requires-Dist: pexpect<5.0.0,>=4.9.0
|
|
43
|
+
Requires-Dist: pip>=25
|
|
44
|
+
Requires-Dist: dateparser<2.0.0,>=1.2.2
|
|
43
45
|
Provides-Extra: dev
|
|
44
46
|
Requires-Dist: pytest<9.0.0,>=8.0.0; extra == "dev"
|
|
45
47
|
Requires-Dist: pytest-cov<7.0.0,>=5.0.0; extra == "dev"
|
|
@@ -50,9 +52,9 @@ Requires-Dist: flake8<8.0.0,>=7.0.0; extra == "dev"
|
|
|
50
52
|
Requires-Dist: pre-commit<5.0.0,>=3.0.0; extra == "dev"
|
|
51
53
|
Requires-Dist: freezegun<2.0.0,>=1.5.0; extra == "dev"
|
|
52
54
|
Requires-Dist: pytest-docker<4.0.0,>=3.0.0; extra == "dev"
|
|
53
|
-
Requires-Dist: pytest-asyncio<1.
|
|
55
|
+
Requires-Dist: pytest-asyncio<1.2.0,>=0.24.0; extra == "dev"
|
|
54
56
|
Requires-Dist: asgi-lifespan<3.0.0,>=2.1.0; extra == "dev"
|
|
55
|
-
Requires-Dist: tox<4.
|
|
57
|
+
Requires-Dist: tox<4.29.0,>=4.24.0; extra == "dev"
|
|
56
58
|
Requires-Dist: pytest-dependency<0.7.0,>=0.6.0; extra == "dev"
|
|
57
59
|
Requires-Dist: pytest-sugar<2.0.0,>=1.0.0; extra == "dev"
|
|
58
60
|
Provides-Extra: doc
|
|
@@ -62,6 +64,7 @@ Requires-Dist: mike<2.2.0,>=2.1.3; extra == "doc"
|
|
|
62
64
|
Provides-Extra: plugins
|
|
63
65
|
Requires-Dist: labtasker-plugin-task-count; extra == "plugins"
|
|
64
66
|
Requires-Dist: labtasker-plugin-script-generate; extra == "plugins"
|
|
67
|
+
Requires-Dist: labtasker-plugin-task-recency; 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>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
__version__ = "0.2.
|
|
1
|
+
__version__ = "0.2.9"
|
|
2
2
|
|
|
3
3
|
from labtasker.client.client_api import *
|
|
4
4
|
from labtasker.client.core.config import get_client_config
|
|
5
5
|
from labtasker.client.core.exceptions import *
|
|
6
6
|
from labtasker.client.core.paths import get_labtasker_client_config_path
|
|
7
|
-
from labtasker.client.core.version_checker import
|
|
7
|
+
from labtasker.client.core.version_checker import check_package_version
|
|
8
8
|
from labtasker.filtering import install_traceback_filter, set_traceback_filter_hook
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
check_package_version()
|
|
11
11
|
|
|
12
12
|
# by default, traceback filter is enabled.
|
|
13
13
|
# you may disable it via client config
|
|
@@ -1,20 +1,56 @@
|
|
|
1
|
+
import warnings
|
|
1
2
|
from datetime import datetime
|
|
2
|
-
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
|
3
|
+
from typing import Any, Dict, List, Literal, Optional, Tuple, TypeVar, Union
|
|
3
4
|
|
|
4
5
|
from packaging.version import Version
|
|
5
|
-
from pydantic import
|
|
6
|
+
from pydantic import (
|
|
7
|
+
BaseModel,
|
|
8
|
+
ConfigDict,
|
|
9
|
+
Field,
|
|
10
|
+
SecretStr,
|
|
11
|
+
ValidationInfo,
|
|
12
|
+
field_validator,
|
|
13
|
+
model_validator,
|
|
14
|
+
)
|
|
6
15
|
|
|
7
16
|
from labtasker import __version__
|
|
8
17
|
from labtasker.constants import Priority
|
|
9
|
-
from labtasker.utils import validate_dict_keys
|
|
18
|
+
from labtasker.utils import _disable_check_var, validate_dict_keys
|
|
10
19
|
|
|
11
20
|
|
|
12
21
|
class BaseApiModel(BaseModel):
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
""
|
|
22
|
+
"""Base API model for all API models."""
|
|
23
|
+
|
|
24
|
+
model_config = ConfigDict(populate_by_name=True, extra="ignore")
|
|
25
|
+
unknown_fields: Dict[str, Any] = Field(
|
|
26
|
+
default_factory=dict, exclude=True
|
|
27
|
+
) # marked as excluded from serialization
|
|
28
|
+
|
|
29
|
+
@model_validator(mode="before")
|
|
30
|
+
@classmethod
|
|
31
|
+
def collect_unknown_fields(
|
|
32
|
+
cls, data: Dict[str, Any], info: ValidationInfo
|
|
33
|
+
) -> Dict[str, Any]:
|
|
34
|
+
if _disable_check_var.get(): # if check is disabled, return data
|
|
35
|
+
return data
|
|
36
|
+
|
|
37
|
+
if isinstance(data, dict):
|
|
38
|
+
# Get the set of model field names
|
|
39
|
+
model_field_names = set(cls.model_fields.keys()) # type: ignore
|
|
16
40
|
|
|
17
|
-
|
|
41
|
+
unknown_fields = {
|
|
42
|
+
k: v for k, v in data.items() if k not in model_field_names
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if unknown_fields:
|
|
46
|
+
warnings.warn(
|
|
47
|
+
f"Unknown fields provided to {cls.__name__}: {', '.join(unknown_fields.keys())}; "
|
|
48
|
+
f"It could be that your client and server version mismatch. Try upgrading both to latest versions.",
|
|
49
|
+
UserWarning,
|
|
50
|
+
)
|
|
51
|
+
data["_unknown_fields"] = unknown_fields
|
|
52
|
+
|
|
53
|
+
return data
|
|
18
54
|
|
|
19
55
|
|
|
20
56
|
class BaseRequestModel(BaseApiModel):
|
|
@@ -119,7 +155,93 @@ class TaskSubmitRequest(
|
|
|
119
155
|
priority: int = Priority.MEDIUM
|
|
120
156
|
|
|
121
157
|
|
|
122
|
-
|
|
158
|
+
T = TypeVar("T", bound="DatetimeSerializationMixin")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class DatetimeSerializationMixin:
|
|
162
|
+
"""
|
|
163
|
+
A mixin that enables proper serialization and deserialization of datetime objects
|
|
164
|
+
in the extra_filter field.
|
|
165
|
+
|
|
166
|
+
This mixin should be used with BaseModel.
|
|
167
|
+
Example: class MyModel(DatetimeSerializationMixin, BaseModel):
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
# Define a model validator to process datetime markers in input data
|
|
171
|
+
@model_validator(mode="before")
|
|
172
|
+
@classmethod
|
|
173
|
+
def process_datetime_markers(cls, data: Any) -> Any:
|
|
174
|
+
"""Process incoming data to convert datetime markers in extra_filter field"""
|
|
175
|
+
if (
|
|
176
|
+
isinstance(data, dict)
|
|
177
|
+
and "extra_filter" in data
|
|
178
|
+
and data["extra_filter"] is not None
|
|
179
|
+
):
|
|
180
|
+
data["extra_filter"] = cls._process_datetime_markers(data["extra_filter"])
|
|
181
|
+
return data
|
|
182
|
+
|
|
183
|
+
# Add a field validator for extra_filter field
|
|
184
|
+
@field_validator("extra_filter", mode="before")
|
|
185
|
+
@classmethod
|
|
186
|
+
def validate_extra_filter(
|
|
187
|
+
cls, value: Optional[Dict[str, Any]]
|
|
188
|
+
) -> Optional[Dict[str, Any]]:
|
|
189
|
+
"""Validate and process extra_filter field"""
|
|
190
|
+
if value is None:
|
|
191
|
+
return None
|
|
192
|
+
return cls._process_datetime_markers(value)
|
|
193
|
+
|
|
194
|
+
@classmethod
|
|
195
|
+
def _process_datetime_markers(cls, obj: Any) -> Any:
|
|
196
|
+
"""Process data recursively to convert datetime markers to datetime objects"""
|
|
197
|
+
if isinstance(obj, dict):
|
|
198
|
+
# Check if it's a datetime marker
|
|
199
|
+
if "_dt" in obj and len(obj) == 1 and isinstance(obj["_dt"], str):
|
|
200
|
+
try:
|
|
201
|
+
return datetime.fromisoformat(obj["_dt"])
|
|
202
|
+
except ValueError:
|
|
203
|
+
pass
|
|
204
|
+
# Process regular dictionary
|
|
205
|
+
return {k: cls._process_datetime_markers(v) for k, v in obj.items()}
|
|
206
|
+
elif isinstance(obj, list):
|
|
207
|
+
return [cls._process_datetime_markers(item) for item in obj]
|
|
208
|
+
return obj
|
|
209
|
+
|
|
210
|
+
def dump_to_json_dict(self) -> Dict[str, Any]:
|
|
211
|
+
"""
|
|
212
|
+
Dump the model to a JSON-serializable dictionary with datetime objects
|
|
213
|
+
in extra_filter converted to {"_dt": iso_format} representation.
|
|
214
|
+
|
|
215
|
+
This method doesn't modify the model_config, but provides a way to
|
|
216
|
+
properly serialize datetime objects in the extra_filter field.
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Dict[str, Any]: A JSON-serializable dictionary
|
|
220
|
+
"""
|
|
221
|
+
# First get the standard model dump
|
|
222
|
+
data = self.model_dump() # type: ignore[attr-defined]
|
|
223
|
+
|
|
224
|
+
# Process extra_filter field if it exists
|
|
225
|
+
if "extra_filter" in data and data["extra_filter"] is not None:
|
|
226
|
+
data["extra_filter"] = self._process_datetime_objects(data["extra_filter"])
|
|
227
|
+
|
|
228
|
+
return data
|
|
229
|
+
|
|
230
|
+
def _process_datetime_objects(self, obj: Any) -> Any:
|
|
231
|
+
"""Process data recursively to convert datetime objects to markers"""
|
|
232
|
+
if isinstance(obj, dict):
|
|
233
|
+
return {k: self._process_datetime_objects(v) for k, v in obj.items()}
|
|
234
|
+
elif isinstance(obj, list):
|
|
235
|
+
return [self._process_datetime_objects(item) for item in obj]
|
|
236
|
+
elif isinstance(obj, datetime):
|
|
237
|
+
return {"_dt": obj.isoformat()}
|
|
238
|
+
elif isinstance(obj, tuple):
|
|
239
|
+
# Convert tuples to lists for JSON serialization
|
|
240
|
+
return [self._process_datetime_objects(item) for item in obj]
|
|
241
|
+
return obj
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class TaskFetchRequest(DatetimeSerializationMixin, BaseRequestModel): # type: ignore[misc]
|
|
123
245
|
worker_id: Optional[str] = None
|
|
124
246
|
eta_max: Optional[str] = None
|
|
125
247
|
heartbeat_timeout: Optional[float] = None
|
|
@@ -205,7 +327,7 @@ class TaskFetchResponse(BaseResponseModel):
|
|
|
205
327
|
task: Optional[Task] = None
|
|
206
328
|
|
|
207
329
|
|
|
208
|
-
class TaskLsRequest(BaseRequestModel):
|
|
330
|
+
class TaskLsRequest(DatetimeSerializationMixin, BaseRequestModel): # type: ignore[misc]
|
|
209
331
|
offset: int = Field(0, ge=0)
|
|
210
332
|
limit: int = Field(100, gt=0, le=1000)
|
|
211
333
|
task_id: Optional[str] = None
|
|
@@ -267,7 +389,7 @@ class WorkerStatusUpdateRequest(BaseRequestModel):
|
|
|
267
389
|
status: str = Field(..., pattern=r"^(active|suspended|crashed)$")
|
|
268
390
|
|
|
269
391
|
|
|
270
|
-
class WorkerLsRequest(BaseRequestModel):
|
|
392
|
+
class WorkerLsRequest(DatetimeSerializationMixin, BaseRequestModel): # type: ignore[misc]
|
|
271
393
|
offset: int = Field(0, ge=0)
|
|
272
394
|
limit: int = Field(100, gt=0, le=1000)
|
|
273
395
|
worker_id: Optional[str] = None
|
|
@@ -12,7 +12,7 @@ from labtasker.client.core.api import health_check
|
|
|
12
12
|
from labtasker.client.core.config import requires_client_config
|
|
13
13
|
from labtasker.client.core.exceptions import LabtaskerNetworkError
|
|
14
14
|
from labtasker.client.core.logging import stderr_console, stdout_console
|
|
15
|
-
from labtasker.client.core.version_checker import
|
|
15
|
+
from labtasker.client.core.version_checker import check_package_version
|
|
16
16
|
|
|
17
17
|
app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
|
|
|
20
20
|
def version_callback(value: bool):
|
|
21
21
|
if value:
|
|
22
22
|
stdout_console.print(f"Labtasker Version: {__version__}")
|
|
23
|
-
|
|
23
|
+
check_package_version(force_check=True, blocking=True)
|
|
24
24
|
raise typer.Exit()
|
|
25
25
|
|
|
26
26
|
|
|
@@ -211,12 +211,12 @@ def setup_queue(base_url, base_url_verified) -> Tuple[str, str]:
|
|
|
211
211
|
def confirm_set_traceback_filter() -> bool:
|
|
212
212
|
choices = [
|
|
213
213
|
Choice(
|
|
214
|
-
"
|
|
215
|
-
data=
|
|
214
|
+
"No: I want more compatibility than security.",
|
|
215
|
+
data=False,
|
|
216
216
|
),
|
|
217
217
|
Choice(
|
|
218
|
-
"
|
|
219
|
-
data=
|
|
218
|
+
"Yes: I want better exception formatting and sensitive text filtering.",
|
|
219
|
+
data=True,
|
|
220
220
|
),
|
|
221
221
|
]
|
|
222
222
|
choice = ListPrompt(
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
"""Implements `labtasker loop xxx`"""
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import os
|
|
5
|
-
import subprocess
|
|
6
4
|
import sys
|
|
7
5
|
from collections import defaultdict
|
|
8
6
|
from pathlib import Path
|
|
9
7
|
from typing import List, Optional
|
|
10
8
|
|
|
11
|
-
import pexpect
|
|
12
9
|
import typer
|
|
13
10
|
from typing_extensions import Annotated
|
|
14
11
|
|
|
@@ -16,6 +13,7 @@ from labtasker.client.cli.cli import app
|
|
|
16
13
|
from labtasker.client.core.cli_utils import (
|
|
17
14
|
cli_utils_decorator,
|
|
18
15
|
eta_max_validation,
|
|
16
|
+
is_piped_io,
|
|
19
17
|
parse_filter,
|
|
20
18
|
)
|
|
21
19
|
from labtasker.client.core.cmd_parser import cmd_interpolate
|
|
@@ -26,9 +24,14 @@ from labtasker.client.core.job_runner import loop_run
|
|
|
26
24
|
from labtasker.client.core.logging import (
|
|
27
25
|
logger,
|
|
28
26
|
set_verbose,
|
|
29
|
-
stderr_console,
|
|
30
27
|
verbose_print,
|
|
31
28
|
)
|
|
29
|
+
from labtasker.client.core.utils import (
|
|
30
|
+
check_pty_available,
|
|
31
|
+
json_serializer,
|
|
32
|
+
run_with_pty,
|
|
33
|
+
run_with_subprocess,
|
|
34
|
+
)
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
class InfiniteDefaultDict(defaultdict):
|
|
@@ -44,76 +47,6 @@ class InfiniteDefaultDict(defaultdict):
|
|
|
44
47
|
return super().get(key, default)
|
|
45
48
|
|
|
46
49
|
|
|
47
|
-
def _check_pty_available(opt: bool) -> bool:
|
|
48
|
-
if opt and os.name == "nt":
|
|
49
|
-
stderr_console.print(
|
|
50
|
-
"[bold orange1]Warning:[/bold orange1] PTY is not available on Windows. "
|
|
51
|
-
"Disabling PTY support."
|
|
52
|
-
)
|
|
53
|
-
return False
|
|
54
|
-
return opt
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _stream_child_output(child) -> None:
|
|
58
|
-
"""Stream the output of a pexpect child in real-time, supporting progress bars."""
|
|
59
|
-
try:
|
|
60
|
-
while True:
|
|
61
|
-
try:
|
|
62
|
-
output = child.read_nonblocking(size=1024, timeout=0.1)
|
|
63
|
-
if output:
|
|
64
|
-
# keep \r
|
|
65
|
-
sys.stdout.write(output)
|
|
66
|
-
sys.stdout.flush()
|
|
67
|
-
except pexpect.TIMEOUT:
|
|
68
|
-
continue
|
|
69
|
-
except pexpect.EOF:
|
|
70
|
-
break
|
|
71
|
-
finally:
|
|
72
|
-
child.close()
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def _run_with_pty(cmd, shell_exec=None, use_shell=False):
|
|
76
|
-
"""Run a command with PTY support for interactive programs."""
|
|
77
|
-
if use_shell:
|
|
78
|
-
shell_exec = shell_exec or "/bin/sh"
|
|
79
|
-
child = pexpect.spawn(shell_exec, ["-c", cmd], encoding="utf-8")
|
|
80
|
-
else:
|
|
81
|
-
child = pexpect.spawn(cmd[0], cmd[1:], encoding="utf-8")
|
|
82
|
-
|
|
83
|
-
_stream_child_output(child)
|
|
84
|
-
|
|
85
|
-
return child.exitstatus
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def _run_with_subprocess(cmd, shell_exec=None, use_shell=False):
|
|
89
|
-
"""Run a command using standard subprocess approach."""
|
|
90
|
-
with subprocess.Popen(
|
|
91
|
-
args=cmd,
|
|
92
|
-
stdin=subprocess.PIPE,
|
|
93
|
-
stdout=subprocess.PIPE,
|
|
94
|
-
stderr=subprocess.PIPE,
|
|
95
|
-
text=True,
|
|
96
|
-
executable=shell_exec,
|
|
97
|
-
shell=use_shell,
|
|
98
|
-
) as process:
|
|
99
|
-
while True:
|
|
100
|
-
output = process.stdout.readline()
|
|
101
|
-
error = process.stderr.readline()
|
|
102
|
-
|
|
103
|
-
if output:
|
|
104
|
-
sys.stdout.write(output.strip())
|
|
105
|
-
sys.stdout.flush()
|
|
106
|
-
if error:
|
|
107
|
-
sys.stderr.write(error.strip())
|
|
108
|
-
sys.stderr.flush()
|
|
109
|
-
|
|
110
|
-
# Break when process completes and streams are empty
|
|
111
|
-
if process.poll() is not None and not output and not error:
|
|
112
|
-
break
|
|
113
|
-
|
|
114
|
-
return process.returncode
|
|
115
|
-
|
|
116
|
-
|
|
117
50
|
@app.command()
|
|
118
51
|
@cli_utils_decorator
|
|
119
52
|
def loop(
|
|
@@ -163,7 +96,7 @@ def loop(
|
|
|
163
96
|
),
|
|
164
97
|
use_pty: bool = typer.Option(
|
|
165
98
|
os.name == "posix", # enabled by default on POSIX systems
|
|
166
|
-
callback=
|
|
99
|
+
callback=check_pty_available,
|
|
167
100
|
help="Use pseudo terminal on POSIX systems for better interactive program support.",
|
|
168
101
|
),
|
|
169
102
|
verbose: bool = typer.Option( # noqa
|
|
@@ -201,7 +134,7 @@ def loop(
|
|
|
201
134
|
input_cmd = f.read().strip()
|
|
202
135
|
|
|
203
136
|
# 3. Try reading from stdin if shell mode is enabled
|
|
204
|
-
if not input_cmd and
|
|
137
|
+
if not input_cmd and is_piped_io():
|
|
205
138
|
input_cmd = sys.stdin.read().strip()
|
|
206
139
|
|
|
207
140
|
# Final validation: ensure a command is present
|
|
@@ -211,7 +144,7 @@ def loop(
|
|
|
211
144
|
)
|
|
212
145
|
|
|
213
146
|
parsed_filter = parse_filter(extra_filter)
|
|
214
|
-
verbose_print(f"Parsed filter: {
|
|
147
|
+
verbose_print(f"Parsed filter: {json_serializer(parsed_filter, indent=4)}")
|
|
215
148
|
|
|
216
149
|
if heartbeat_timeout is None:
|
|
217
150
|
heartbeat_timeout = get_client_config().task.heartbeat_interval * 3
|
|
@@ -244,12 +177,10 @@ def loop(
|
|
|
244
177
|
try:
|
|
245
178
|
if use_pty:
|
|
246
179
|
# Use pexpect with PTY on POSIX systems
|
|
247
|
-
exit_code =
|
|
180
|
+
exit_code = run_with_pty(interpolated_cmd, executable, use_shell)
|
|
248
181
|
else:
|
|
249
182
|
# Standard subprocess approach for non-PTY execution
|
|
250
|
-
exit_code =
|
|
251
|
-
interpolated_cmd, executable, use_shell
|
|
252
|
-
)
|
|
183
|
+
exit_code = run_with_subprocess(interpolated_cmd, executable, use_shell)
|
|
253
184
|
|
|
254
185
|
if exit_code != 0:
|
|
255
186
|
raise _LabtaskerJobFailed(
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Manage tasks (CRUD operations)."""
|
|
2
2
|
|
|
3
3
|
import io
|
|
4
|
-
import json
|
|
5
4
|
import os
|
|
6
5
|
import sys
|
|
7
6
|
import tempfile
|
|
@@ -22,7 +21,6 @@ from typing_extensions import Annotated
|
|
|
22
21
|
from labtasker.api_models import Task, TaskUpdateRequest
|
|
23
22
|
from labtasker.client.core.api import (
|
|
24
23
|
delete_task,
|
|
25
|
-
get_queue,
|
|
26
24
|
ls_tasks,
|
|
27
25
|
submit_task,
|
|
28
26
|
update_tasks,
|
|
@@ -31,6 +29,8 @@ from labtasker.client.core.cli_utils import (
|
|
|
31
29
|
LsFmtChoices,
|
|
32
30
|
cli_utils_decorator,
|
|
33
31
|
confirm,
|
|
32
|
+
get_editor,
|
|
33
|
+
is_piped_io,
|
|
34
34
|
ls_format_iter,
|
|
35
35
|
pager_iterator,
|
|
36
36
|
parse_dict,
|
|
@@ -47,6 +47,8 @@ from labtasker.client.core.logging import (
|
|
|
47
47
|
stdout_console,
|
|
48
48
|
verbose_print,
|
|
49
49
|
)
|
|
50
|
+
from labtasker.client.core.pager import echo_via_pager_no_check_isatty
|
|
51
|
+
from labtasker.client.core.utils import json_serializer
|
|
50
52
|
from labtasker.constants import Priority
|
|
51
53
|
|
|
52
54
|
app = typer.Typer()
|
|
@@ -237,6 +239,10 @@ def ls(
|
|
|
237
239
|
help="Only show task IDs that match the query, rather than full entry. "
|
|
238
240
|
"Useful when using in bash scripts.",
|
|
239
241
|
),
|
|
242
|
+
ansi: bool = typer.Option(
|
|
243
|
+
True,
|
|
244
|
+
help="Enable ANSI colors.",
|
|
245
|
+
),
|
|
240
246
|
pager: bool = typer.Option(
|
|
241
247
|
True,
|
|
242
248
|
help="Enable pagination.",
|
|
@@ -268,6 +274,11 @@ def ls(
|
|
|
268
274
|
callback=set_verbose,
|
|
269
275
|
is_eager=True,
|
|
270
276
|
),
|
|
277
|
+
piped_in: bool = typer.Option(
|
|
278
|
+
False,
|
|
279
|
+
"--piped-in",
|
|
280
|
+
help="Read from stdin. Useful in cascaded commands to display final result. e.g. `labtasker xxx | labtasker ls --piped-in`",
|
|
281
|
+
),
|
|
271
282
|
):
|
|
272
283
|
"""
|
|
273
284
|
List and filter tasks in the queue.
|
|
@@ -298,10 +309,34 @@ def ls(
|
|
|
298
309
|
else:
|
|
299
310
|
parsed_sort = parse_sort(sort)
|
|
300
311
|
|
|
301
|
-
get_queue() # validate auth and queue existence, prevent err swallowed by pager
|
|
302
|
-
|
|
303
312
|
extra_filter = parse_filter(extra_filter)
|
|
304
|
-
verbose_print(f"Parsed filter: {
|
|
313
|
+
verbose_print(f"Parsed filter: {json_serializer(extra_filter, indent=4)}")
|
|
314
|
+
|
|
315
|
+
# Check for piped input (task ids could be passed through cascaded commands e.g. labtasker xxx | labtasker yyy)
|
|
316
|
+
if piped_in:
|
|
317
|
+
if any([task_id, task_name, status, extra_filter]):
|
|
318
|
+
raise typer.BadParameter(
|
|
319
|
+
"Cannot use --task-id, --task-name, --status, or --extra-filter with piped input --piped-in enabled."
|
|
320
|
+
)
|
|
321
|
+
# Read task IDs from stdin (pipe)
|
|
322
|
+
piped_task_ids = [line.strip() for line in sys.stdin if line.strip()]
|
|
323
|
+
verbose_print(f"Received {len(piped_task_ids)} task IDs from pipe")
|
|
324
|
+
|
|
325
|
+
# If we have piped task IDs but no task_id is specified yet
|
|
326
|
+
if piped_task_ids:
|
|
327
|
+
# If there's only one task ID from pipe and no task_id specified, use it directly
|
|
328
|
+
if len(piped_task_ids) == 1 and not task_id:
|
|
329
|
+
task_id = piped_task_ids[0]
|
|
330
|
+
verbose_print(f"Using single piped task ID: {task_id}")
|
|
331
|
+
# If there are multiple task IDs, use them to build an extra filter
|
|
332
|
+
elif len(piped_task_ids) > 1:
|
|
333
|
+
# Build a filter to include all piped task IDs
|
|
334
|
+
extra_filter = {"_id": {"$in": piped_task_ids}}
|
|
335
|
+
|
|
336
|
+
verbose_print(
|
|
337
|
+
f"Created filter from {len(piped_task_ids)} piped task IDs"
|
|
338
|
+
)
|
|
339
|
+
|
|
305
340
|
page_iter = pager_iterator(
|
|
306
341
|
fetch_function=partial(
|
|
307
342
|
ls_tasks,
|
|
@@ -321,16 +356,18 @@ def ls(
|
|
|
321
356
|
raise typer.Exit() # exit directly without other printing
|
|
322
357
|
|
|
323
358
|
if pager:
|
|
324
|
-
|
|
359
|
+
echo_via_pager_no_check_isatty(
|
|
325
360
|
ls_format_iter[fmt](
|
|
326
361
|
page_iter,
|
|
327
362
|
use_rich=False,
|
|
328
|
-
|
|
363
|
+
ansi=ansi,
|
|
364
|
+
),
|
|
329
365
|
)
|
|
330
366
|
else:
|
|
331
367
|
for item in ls_format_iter[fmt](
|
|
332
368
|
page_iter,
|
|
333
369
|
use_rich=True,
|
|
370
|
+
ansi=ansi,
|
|
334
371
|
):
|
|
335
372
|
stdout_console.print(item)
|
|
336
373
|
|
|
@@ -397,9 +434,9 @@ def update(
|
|
|
397
434
|
"-q",
|
|
398
435
|
help="Disable interactive mode and confirmations. Set this to true if you are using this in a bash script.",
|
|
399
436
|
),
|
|
400
|
-
editor:
|
|
401
|
-
|
|
402
|
-
help="Editor to use for modifying task data
|
|
437
|
+
editor: str = typer.Option(
|
|
438
|
+
get_editor(),
|
|
439
|
+
help="Editor to use for modifying task data in case you didn't specify --update.",
|
|
403
440
|
),
|
|
404
441
|
verbose: bool = typer.Option(
|
|
405
442
|
False,
|
|
@@ -417,10 +454,14 @@ def update(
|
|
|
417
454
|
priority, and other settings. You can update multiple tasks at once by using
|
|
418
455
|
filters, and either specify updates directly or use an interactive editor.
|
|
419
456
|
|
|
457
|
+
You can also pipe task IDs from other commands, for example:
|
|
458
|
+
`labtasker task ls -q | labtasker task update -u 'status=cancelled'`
|
|
459
|
+
|
|
420
460
|
Examples:
|
|
421
461
|
labtasker task update --id "task-123" -- priority=10
|
|
422
462
|
labtasker task update --status pending -- metadata.tag=important
|
|
423
463
|
labtasker task update --name "training" --editor vim # Open in editor
|
|
464
|
+
labtasker task ls -q | labtasker task update -u status=cancelled # `task ls` can be replaced with one of the self-implemented plugin commands.
|
|
424
465
|
"""
|
|
425
466
|
if updates and option_updates:
|
|
426
467
|
raise typer.BadParameter(
|
|
@@ -434,7 +475,28 @@ def update(
|
|
|
434
475
|
|
|
435
476
|
updates = updates or option_updates
|
|
436
477
|
extra_filter = parse_filter(extra_filter)
|
|
437
|
-
verbose_print(f"Parsed filter: {
|
|
478
|
+
verbose_print(f"Parsed filter: {json_serializer(extra_filter, indent=4)}")
|
|
479
|
+
|
|
480
|
+
# Check for piped input (task ids could be passed through cascaded commands e.g. labtasker xxx | labtasker yyy)
|
|
481
|
+
if is_piped_io() and not any([task_id, task_name, status, extra_filter]):
|
|
482
|
+
# Read task IDs from stdin (pipe)
|
|
483
|
+
piped_task_ids = [line.strip() for line in sys.stdin if line.strip()]
|
|
484
|
+
verbose_print(f"Received {len(piped_task_ids)} task IDs from pipe")
|
|
485
|
+
|
|
486
|
+
# If we have piped task IDs but no task_id is specified yet
|
|
487
|
+
if piped_task_ids:
|
|
488
|
+
# If there's only one task ID from pipe and no task_id specified, use it directly
|
|
489
|
+
if len(piped_task_ids) == 1 and not task_id:
|
|
490
|
+
task_id = piped_task_ids[0]
|
|
491
|
+
verbose_print(f"Using single piped task ID: {task_id}")
|
|
492
|
+
# If there are multiple task IDs, use them to build an extra filter
|
|
493
|
+
elif len(piped_task_ids) > 1:
|
|
494
|
+
# Build a filter to include all piped task IDs
|
|
495
|
+
extra_filter = {"_id": {"$in": piped_task_ids}}
|
|
496
|
+
|
|
497
|
+
verbose_print(
|
|
498
|
+
f"Created filter from {len(piped_task_ids)} piped task IDs"
|
|
499
|
+
)
|
|
438
500
|
|
|
439
501
|
# readonly fields
|
|
440
502
|
readonly_fields: Set[str] = (
|
|
@@ -541,7 +603,7 @@ def handle_editor_mode(old_tasks, readonly_fields, editor):
|
|
|
541
603
|
try:
|
|
542
604
|
task_updates.append(
|
|
543
605
|
TaskUpdateRequest(
|
|
544
|
-
|
|
606
|
+
task_id=old_tasks[i].task_id,
|
|
545
607
|
replace_fields=replace_fields,
|
|
546
608
|
**ud,
|
|
547
609
|
)
|
|
@@ -569,12 +631,12 @@ def handle_editor_mode(old_tasks, readonly_fields, editor):
|
|
|
569
631
|
raise typer.Abort()
|
|
570
632
|
# Continue the loop with the current file state
|
|
571
633
|
|
|
634
|
+
return task_updates
|
|
635
|
+
|
|
572
636
|
finally:
|
|
573
637
|
if temp_file_path and temp_file_path.exists():
|
|
574
638
|
temp_file_path.unlink()
|
|
575
639
|
|
|
576
|
-
return task_updates
|
|
577
|
-
|
|
578
640
|
|
|
579
641
|
def handle_non_editor_mode(old_tasks, updates, readonly_fields):
|
|
580
642
|
"""Handles update tasks without an editor and returns task updates."""
|
|
@@ -595,7 +657,7 @@ def handle_non_editor_mode(old_tasks, updates, readonly_fields):
|
|
|
595
657
|
for i, task in enumerate(old_tasks):
|
|
596
658
|
task_updates.append(
|
|
597
659
|
TaskUpdateRequest(
|
|
598
|
-
|
|
660
|
+
task_id=task.task_id, replace_fields=replace_fields, **update_dict
|
|
599
661
|
)
|
|
600
662
|
)
|
|
601
663
|
|
|
@@ -655,6 +717,9 @@ def display_updated_tasks(updated_tasks, update_dicts):
|
|
|
655
717
|
console = rich.console.Console()
|
|
656
718
|
with console.capture() as capture:
|
|
657
719
|
console.print(Syntax(s.getvalue(), "yaml"))
|
|
720
|
+
|
|
721
|
+
# use normal pager instead of echo_via_pager_no_check_isatty
|
|
722
|
+
# because it would break the test cases for update task
|
|
658
723
|
click.echo_via_pager(capture.get())
|
|
659
724
|
|
|
660
725
|
|
|
@@ -662,7 +727,7 @@ def display_updated_tasks(updated_tasks, update_dicts):
|
|
|
662
727
|
@cli_utils_decorator
|
|
663
728
|
def delete(
|
|
664
729
|
task_ids: List[str] = typer.Argument(
|
|
665
|
-
... if
|
|
730
|
+
... if not is_piped_io() else None,
|
|
666
731
|
help="IDs of the task to delete.",
|
|
667
732
|
),
|
|
668
733
|
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt."),
|