sunholo 0.145.0__tar.gz → 0.145.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sunholo-0.145.0/src/sunholo.egg-info → sunholo-0.145.3}/PKG-INFO +2 -2
- {sunholo-0.145.0 → sunholo-0.145.3}/pyproject.toml +3 -3
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/invoke/__init__.py +1 -1
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/invoke/async_task_runner.py +30 -5
- {sunholo-0.145.0 → sunholo-0.145.3/src/sunholo.egg-info}/PKG-INFO +2 -2
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo.egg-info/SOURCES.txt +2 -3
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo.egg-info/requires.txt +1 -0
- sunholo-0.145.3/tests/test_async_task_runner.py +426 -0
- sunholo-0.145.0/tests/test_vac_routes_fastapi.py +0 -226
- sunholo-0.145.0/tests/test_vac_routes_mcp.py +0 -398
- {sunholo-0.145.0 → sunholo-0.145.3}/LICENSE.txt +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/MANIFEST.in +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/README.md +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/setup.cfg +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/a2a/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/a2a/agent_card.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/a2a/task_manager.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/a2a/vac_a2a_agent.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/chat_history.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/dispatch_to_qa.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/fastapi/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/fastapi/base.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/fastapi/qna_routes.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/fastapi/vac_routes.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/flask/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/flask/base.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/flask/vac_routes.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/langserve.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/pubsub.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/route.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/special_commands.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/agents/swagger.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/archive/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/archive/archive.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/auth/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/auth/gcloud.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/auth/refresh.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/auth/run.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/azure/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/azure/auth.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/azure/blobs.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/azure/event_grid.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/bots/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/bots/discord.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/bots/github_webhook.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/bots/webapp.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/azure.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/doc_handling.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/encode_metadata.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/images.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/loaders.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/message_data.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/pdfs.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/process_chunker_data.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/publish.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/pubsub.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/chunker/splitter.py +0 -0
- {sunholo-0.145.0/src/sunholo/templates/system_services → sunholo-0.145.3/src/sunholo/cli}/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/chat_vac.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/cli.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/cli_init.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/configs.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/deploy.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/embedder.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/merge_texts.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/run_proxy.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/sun_rich.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/swagger.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/cli/vertex.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/components/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/components/llm.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/components/retriever.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/components/vectorstore.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/custom_logging.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/alloydb.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/alloydb_client.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/database.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/lancedb.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/sql/sb/create_function.sql +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/sql/sb/create_function_time.sql +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/sql/sb/create_table.sql +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/sql/sb/delete_source_row.sql +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/sql/sb/return_sources.sql +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/sql/sb/setup.sql +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/static_dbs.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/database/uuid.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/discovery_engine/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/discovery_engine/chunker_handler.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/discovery_engine/cli.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/discovery_engine/create_new.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/discovery_engine/discovery_engine_client.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/discovery_engine/get_ai_search_chunks.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/embedder/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/embedder/embed_chunk.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/embedder/embed_metadata.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/excel/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/excel/plugin.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/gcs/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/gcs/add_file.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/gcs/download_folder.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/gcs/download_gcs_text.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/gcs/download_url.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/gcs/extract_and_sign.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/gcs/metadata.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/genai/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/genai/file_handling.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/genai/genaiv2.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/genai/images.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/genai/init.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/genai/process_funcs_cls.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/genai/safety.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/invoke/async_class.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/invoke/direct_vac_func.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/invoke/invoke_vac_utils.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/langchain_types.py +0 -0
- {sunholo-0.145.0/src/sunholo/templates/project → sunholo-0.145.3/src/sunholo/langfuse}/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/langfuse/callback.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/langfuse/evals.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/langfuse/prompts.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/llamaindex/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/llamaindex/get_files.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/llamaindex/import_files.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/llamaindex/llamaindex_class.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/llamaindex/user_history.py +0 -0
- {sunholo-0.145.0/src/sunholo/templates/agent/tools → sunholo-0.145.3/src/sunholo/lookup}/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/lookup/model_lookup.yaml +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/cli.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/cli_fastmcp.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/extensible_mcp_server.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/mcp_manager.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/sse_utils.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/stdio_http_bridge.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/vac_mcp_server.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/vac_mcp_server_fastmcp.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/mcp/vac_tools.py +0 -0
- {sunholo-0.145.0/src/sunholo/templates/agent → sunholo-0.145.3/src/sunholo/ollama}/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/ollama/ollama_images.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/pubsub/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/pubsub/process_pubsub.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/pubsub/pubsub_manager.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/qna/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/qna/parsers.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/qna/retry.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/senses/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/senses/stream_voice.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/streaming/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/streaming/content_buffer.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/streaming/langserve.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/streaming/stream_lookup.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/streaming/streaming.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/summarise/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/summarise/summarise.py +0 -0
- {sunholo-0.145.0/src/sunholo/ollama → sunholo-0.145.3/src/sunholo/templates/agent}/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/templates/agent/agent_service.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/templates/agent/app.py +0 -0
- {sunholo-0.145.0/src/sunholo/templates/project → sunholo-0.145.3/src/sunholo/templates/agent}/my_log.py +0 -0
- {sunholo-0.145.0/src/sunholo/lookup → sunholo-0.145.3/src/sunholo/templates/agent/tools}/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/templates/agent/tools/your_agent.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/templates/agent/vac_service.py +0 -0
- {sunholo-0.145.0/src/sunholo/langfuse → sunholo-0.145.3/src/sunholo/templates/project}/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/templates/project/app.py +0 -0
- {sunholo-0.145.0/src/sunholo/templates/agent → sunholo-0.145.3/src/sunholo/templates/project}/my_log.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/templates/project/vac_service.py +0 -0
- {sunholo-0.145.0/src/sunholo/cli → sunholo-0.145.3/src/sunholo/templates/system_services}/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/templates/system_services/app.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/templates/system_services/my_log.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/terraform/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/terraform/tfvars_editor.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/tools/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/tools/web_browser.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/api_key.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/big_context.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/config.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/config_class.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/config_schema.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/gcp.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/gcp_project.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/mime.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/parsers.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/proto_convert.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/timedelta.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/user_ids.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/utils/version.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/vertex/__init__.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/vertex/extensions_call.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/vertex/extensions_class.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/vertex/genai_functions.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/vertex/init.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/vertex/memory_tools.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/vertex/safety.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo/vertex/type_dict_to_json.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo.egg-info/dependency_links.txt +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo.egg-info/entry_points.txt +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/src/sunholo.egg-info/top_level.txt +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/tests/test_async.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/tests/test_async_genai2.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/tests/test_chat_history.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/tests/test_config.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/tests/test_genai2.py +0 -0
- {sunholo-0.145.0 → sunholo-0.145.3}/tests/test_unstructured.py +0 -0
@@ -1,11 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: sunholo
|
3
|
-
Version: 0.145.
|
3
|
+
Version: 0.145.3
|
4
4
|
Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
|
5
5
|
Author-email: Holosun ApS <multivac@sunholo.com>
|
6
6
|
License: Apache License, Version 2.0
|
7
7
|
Project-URL: Homepage, https://github.com/sunholo-data/sunholo-py
|
8
|
-
Project-URL: Download, https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz
|
9
8
|
Keywords: llms,devops,google_cloud_platform
|
10
9
|
Classifier: Development Status :: 3 - Alpha
|
11
10
|
Classifier: Intended Audience :: Developers
|
@@ -107,6 +106,7 @@ Requires-Dist: azure-storage-blob; extra == "azure"
|
|
107
106
|
Provides-Extra: cli
|
108
107
|
Requires-Dist: jsonschema>=4.21.1; extra == "cli"
|
109
108
|
Requires-Dist: rich; extra == "cli"
|
109
|
+
Requires-Dist: fastmcp; extra == "cli"
|
110
110
|
Provides-Extra: database
|
111
111
|
Requires-Dist: asyncpg; extra == "database"
|
112
112
|
Requires-Dist: supabase; extra == "database"
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "sunholo"
|
7
|
-
version = "0.145.
|
7
|
+
version = "0.145.3"
|
8
8
|
description = "AI DevOps - a package to help deploy GenAI to the Cloud."
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.11"
|
@@ -33,7 +33,6 @@ dependencies = [
|
|
33
33
|
|
34
34
|
[project.urls]
|
35
35
|
Homepage = "https://github.com/sunholo-data/sunholo-py"
|
36
|
-
Download = "https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz"
|
37
36
|
|
38
37
|
[project.scripts]
|
39
38
|
sunholo = "sunholo.cli.cli:main"
|
@@ -140,7 +139,8 @@ azure = [
|
|
140
139
|
]
|
141
140
|
cli = [
|
142
141
|
"jsonschema>=4.21.1",
|
143
|
-
"rich"
|
142
|
+
"rich",
|
143
|
+
"fastmcp"
|
144
144
|
]
|
145
145
|
database = [
|
146
146
|
"asyncpg",
|
@@ -96,6 +96,13 @@ class AsyncTaskRunner:
|
|
96
96
|
>>> results = await runner.get_aggregated_results()
|
97
97
|
>>> print(results['results']) # {'fetch_data': 'data from api'}
|
98
98
|
|
99
|
+
# Custom task names for better clarity
|
100
|
+
>>> runner = AsyncTaskRunner()
|
101
|
+
>>> runner.add_task(fetch_data, "user_api", task_name="fetch_user_data")
|
102
|
+
>>> runner.add_task(fetch_data, "posts_api", task_name="fetch_posts")
|
103
|
+
>>> results = await runner.get_aggregated_results()
|
104
|
+
>>> print(results['results']['fetch_user_data']) # User data
|
105
|
+
|
99
106
|
# Silent mode - no console output but still collects results
|
100
107
|
>>> runner = AsyncTaskRunner(verbose=False)
|
101
108
|
|
@@ -108,6 +115,7 @@ class AsyncTaskRunner:
|
|
108
115
|
>>> runner = AsyncTaskRunner(use_default_callbacks=False)
|
109
116
|
"""
|
110
117
|
self.tasks = []
|
118
|
+
self.task_name_counts = {} # Track task names to ensure uniqueness
|
111
119
|
self.retry_enabled = retry_enabled
|
112
120
|
self.retry_kwargs = retry_kwargs or {}
|
113
121
|
self.timeout = timeout
|
@@ -209,18 +217,35 @@ class AsyncTaskRunner:
|
|
209
217
|
func: Callable[..., Any],
|
210
218
|
*args: Any,
|
211
219
|
task_config: Optional[TaskConfig] = None,
|
220
|
+
task_name: Optional[str] = None,
|
212
221
|
**kwargs: Any):
|
213
222
|
"""
|
214
223
|
Adds a task to the list of tasks to be executed, with optional per-task configuration.
|
224
|
+
|
225
|
+
Automatically ensures task names are unique by appending a suffix if needed.
|
215
226
|
|
216
227
|
Args:
|
217
228
|
func: The function to be executed.
|
218
229
|
*args: Positional arguments for the function.
|
219
230
|
task_config: Optional per-task configuration for timeout, retry, and callbacks.
|
231
|
+
task_name: Optional custom name for the task. If not provided, uses func.__name__.
|
220
232
|
**kwargs: Keyword arguments for the function.
|
221
233
|
"""
|
222
|
-
|
223
|
-
|
234
|
+
# Get base name from task_name or function name
|
235
|
+
base_name = task_name if task_name is not None else func.__name__
|
236
|
+
|
237
|
+
# Ensure uniqueness by adding suffix if needed
|
238
|
+
if base_name in self.task_name_counts:
|
239
|
+
# Name already exists, increment count and add suffix
|
240
|
+
self.task_name_counts[base_name] += 1
|
241
|
+
name = f"{base_name}_{self.task_name_counts[base_name]}"
|
242
|
+
else:
|
243
|
+
# First occurrence of this name
|
244
|
+
self.task_name_counts[base_name] = 0
|
245
|
+
name = base_name
|
246
|
+
|
247
|
+
log.info(f"Adding task: {name} with args: {args}, kwargs: {kwargs}, config: {task_config}")
|
248
|
+
self.tasks.append((name, func, args, kwargs, task_config))
|
224
249
|
|
225
250
|
async def run_async_with_callbacks(self) -> AsyncGenerator[CallbackContext, None]:
|
226
251
|
"""
|
@@ -313,10 +338,10 @@ class AsyncTaskRunner:
|
|
313
338
|
|
314
339
|
Example:
|
315
340
|
>>> runner = AsyncTaskRunner()
|
316
|
-
>>> runner.add_task(fetch_data, "api")
|
317
|
-
>>> runner.add_task(process_data, "raw_data")
|
341
|
+
>>> runner.add_task(fetch_data, "api", task_name="api_fetch")
|
342
|
+
>>> runner.add_task(process_data, "raw_data", task_name="data_processing")
|
318
343
|
>>> results = await runner.get_aggregated_results()
|
319
|
-
>>> print(results['results']['
|
344
|
+
>>> print(results['results']['api_fetch']) # Access specific result
|
320
345
|
>>> if results['errors']: # Check for any errors
|
321
346
|
... print(f"Errors occurred: {results['errors']}")
|
322
347
|
"""
|
@@ -1,11 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: sunholo
|
3
|
-
Version: 0.145.
|
3
|
+
Version: 0.145.3
|
4
4
|
Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
|
5
5
|
Author-email: Holosun ApS <multivac@sunholo.com>
|
6
6
|
License: Apache License, Version 2.0
|
7
7
|
Project-URL: Homepage, https://github.com/sunholo-data/sunholo-py
|
8
|
-
Project-URL: Download, https://github.com/sunholo-data/sunholo-py/archive/refs/tags/v0.118.0.tar.gz
|
9
8
|
Keywords: llms,devops,google_cloud_platform
|
10
9
|
Classifier: Development Status :: 3 - Alpha
|
11
10
|
Classifier: Intended Audience :: Developers
|
@@ -107,6 +106,7 @@ Requires-Dist: azure-storage-blob; extra == "azure"
|
|
107
106
|
Provides-Extra: cli
|
108
107
|
Requires-Dist: jsonschema>=4.21.1; extra == "cli"
|
109
108
|
Requires-Dist: rich; extra == "cli"
|
109
|
+
Requires-Dist: fastmcp; extra == "cli"
|
110
110
|
Provides-Extra: database
|
111
111
|
Requires-Dist: asyncpg; extra == "database"
|
112
112
|
Requires-Dist: supabase; extra == "database"
|
@@ -195,9 +195,8 @@ src/sunholo/vertex/safety.py
|
|
195
195
|
src/sunholo/vertex/type_dict_to_json.py
|
196
196
|
tests/test_async.py
|
197
197
|
tests/test_async_genai2.py
|
198
|
+
tests/test_async_task_runner.py
|
198
199
|
tests/test_chat_history.py
|
199
200
|
tests/test_config.py
|
200
201
|
tests/test_genai2.py
|
201
|
-
tests/test_unstructured.py
|
202
|
-
tests/test_vac_routes_fastapi.py
|
203
|
-
tests/test_vac_routes_mcp.py
|
202
|
+
tests/test_unstructured.py
|
@@ -0,0 +1,426 @@
|
|
1
|
+
"""
|
2
|
+
Tests for AsyncTaskRunner with default callbacks and enhanced features.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import asyncio
|
6
|
+
import pytest
|
7
|
+
from unittest.mock import AsyncMock, MagicMock
|
8
|
+
from sunholo.invoke.async_task_runner import AsyncTaskRunner, TaskConfig, CallbackContext
|
9
|
+
from tenacity import stop_after_attempt
|
10
|
+
|
11
|
+
|
12
|
+
# Test fixtures - async functions to use in tests
|
13
|
+
async def simple_task(value: str):
|
14
|
+
"""Simple async task that returns a value."""
|
15
|
+
await asyncio.sleep(0.1)
|
16
|
+
return f"Result: {value}"
|
17
|
+
|
18
|
+
|
19
|
+
async def failing_task(value: str):
|
20
|
+
"""Task that always fails for testing error handling."""
|
21
|
+
await asyncio.sleep(0.1)
|
22
|
+
raise ValueError(f"Intentional error: {value}")
|
23
|
+
|
24
|
+
|
25
|
+
async def slow_task(value: str, duration: float = 2.0):
|
26
|
+
"""Task with configurable duration for timeout testing."""
|
27
|
+
await asyncio.sleep(duration)
|
28
|
+
return f"Slow result: {value}"
|
29
|
+
|
30
|
+
|
31
|
+
@pytest.mark.asyncio
|
32
|
+
async def test_default_callbacks_basic():
|
33
|
+
"""Test that default callbacks populate shared_state correctly."""
|
34
|
+
runner = AsyncTaskRunner(verbose=False) # Quiet for tests
|
35
|
+
|
36
|
+
# Add some tasks
|
37
|
+
runner.add_task(simple_task, "test1")
|
38
|
+
runner.add_task(simple_task, "test2")
|
39
|
+
|
40
|
+
# Run and get results
|
41
|
+
results = await runner.get_aggregated_results()
|
42
|
+
|
43
|
+
# Check that default callbacks populated the state
|
44
|
+
assert 'results' in results
|
45
|
+
assert 'completed' in results
|
46
|
+
assert 'started' in results
|
47
|
+
assert 'errors' in results
|
48
|
+
|
49
|
+
# Check task results - with auto-naming, second task gets _1 suffix
|
50
|
+
assert 'simple_task' in results['results']
|
51
|
+
assert results['results']['simple_task'] == "Result: test1"
|
52
|
+
assert 'simple_task_1' in results['results']
|
53
|
+
assert results['results']['simple_task_1'] == "Result: test2"
|
54
|
+
|
55
|
+
# Check completed list - should have unique names
|
56
|
+
assert len(results['completed']) == 2
|
57
|
+
assert 'simple_task' in results['completed']
|
58
|
+
assert 'simple_task_1' in results['completed']
|
59
|
+
|
60
|
+
# Check started list
|
61
|
+
assert len(results['started']) == 2
|
62
|
+
|
63
|
+
# No errors expected
|
64
|
+
assert len(results['errors']) == 0
|
65
|
+
|
66
|
+
|
67
|
+
@pytest.mark.asyncio
|
68
|
+
async def test_default_callbacks_with_errors():
|
69
|
+
"""Test that default callbacks handle errors correctly."""
|
70
|
+
runner = AsyncTaskRunner(verbose=False)
|
71
|
+
|
72
|
+
# Add both successful and failing tasks
|
73
|
+
runner.add_task(simple_task, "success")
|
74
|
+
runner.add_task(failing_task, "failure")
|
75
|
+
|
76
|
+
results = await runner.get_aggregated_results()
|
77
|
+
|
78
|
+
# Check successful task
|
79
|
+
assert 'simple_task' in results['results']
|
80
|
+
assert results['results']['simple_task'] == "Result: success"
|
81
|
+
|
82
|
+
# Check error was captured
|
83
|
+
assert 'failing_task' in results['errors']
|
84
|
+
assert "Intentional error: failure" in results['errors']['failing_task']
|
85
|
+
|
86
|
+
# Check completed list (only successful task)
|
87
|
+
assert 'simple_task' in results['completed']
|
88
|
+
|
89
|
+
# Both tasks should have started
|
90
|
+
assert len(results['started']) == 2
|
91
|
+
|
92
|
+
|
93
|
+
@pytest.mark.asyncio
|
94
|
+
async def test_default_callbacks_with_retry():
|
95
|
+
"""Test that default callbacks track retry attempts."""
|
96
|
+
runner = AsyncTaskRunner(
|
97
|
+
verbose=False,
|
98
|
+
retry_enabled=False # Global default
|
99
|
+
)
|
100
|
+
|
101
|
+
# Add task with retry enabled
|
102
|
+
runner.add_task(
|
103
|
+
failing_task,
|
104
|
+
"retry_test",
|
105
|
+
task_config=TaskConfig(
|
106
|
+
retry_enabled=True,
|
107
|
+
retry_kwargs={'stop': stop_after_attempt(3)}
|
108
|
+
)
|
109
|
+
)
|
110
|
+
|
111
|
+
results = await runner.get_aggregated_results()
|
112
|
+
|
113
|
+
# Check that retries were tracked
|
114
|
+
assert 'retries' in results
|
115
|
+
assert len(results['retries']) == 2 # Attempts 2 and 3 (not 1)
|
116
|
+
assert results['retries'][0] == 'failing_task_attempt_2'
|
117
|
+
assert results['retries'][1] == 'failing_task_attempt_3'
|
118
|
+
|
119
|
+
# Task should have error after all retries
|
120
|
+
assert 'failing_task' in results['errors']
|
121
|
+
|
122
|
+
|
123
|
+
@pytest.mark.asyncio
|
124
|
+
async def test_custom_callback_override():
|
125
|
+
"""Test that custom callbacks override defaults correctly."""
|
126
|
+
custom_complete_called = []
|
127
|
+
|
128
|
+
async def custom_on_complete(ctx: CallbackContext):
|
129
|
+
"""Custom completion handler for testing."""
|
130
|
+
custom_complete_called.append(ctx.task_name)
|
131
|
+
# Still populate state like default would
|
132
|
+
ctx.shared_state['results'][ctx.task_name] = f"CUSTOM: {ctx.result}"
|
133
|
+
ctx.shared_state['completed'].append(ctx.task_name)
|
134
|
+
|
135
|
+
runner = AsyncTaskRunner(
|
136
|
+
verbose=False,
|
137
|
+
callbacks={'on_task_complete': custom_on_complete}
|
138
|
+
# Other callbacks remain as defaults
|
139
|
+
)
|
140
|
+
|
141
|
+
runner.add_task(simple_task, "test")
|
142
|
+
results = await runner.get_aggregated_results()
|
143
|
+
|
144
|
+
# Check custom callback was called
|
145
|
+
assert len(custom_complete_called) == 1
|
146
|
+
assert custom_complete_called[0] == 'simple_task'
|
147
|
+
|
148
|
+
# Check custom result format
|
149
|
+
assert results['results']['simple_task'] == "CUSTOM: Result: test"
|
150
|
+
|
151
|
+
# Default callbacks should still work for other events
|
152
|
+
assert 'started' in results
|
153
|
+
assert 'simple_task' in results['started']
|
154
|
+
|
155
|
+
|
156
|
+
@pytest.mark.asyncio
|
157
|
+
async def test_no_default_callbacks():
|
158
|
+
"""Test that disabling default callbacks works."""
|
159
|
+
runner = AsyncTaskRunner(
|
160
|
+
use_default_callbacks=False,
|
161
|
+
verbose=False
|
162
|
+
)
|
163
|
+
|
164
|
+
runner.add_task(simple_task, "test")
|
165
|
+
results = await runner.get_aggregated_results()
|
166
|
+
|
167
|
+
# State should be initialized but empty (no callbacks to populate it)
|
168
|
+
assert results == {
|
169
|
+
'results': {},
|
170
|
+
'errors': {},
|
171
|
+
'completed': [],
|
172
|
+
'started': [],
|
173
|
+
'retries': [],
|
174
|
+
'timed_out': []
|
175
|
+
}
|
176
|
+
|
177
|
+
|
178
|
+
@pytest.mark.asyncio
|
179
|
+
async def test_per_task_timeout_with_defaults():
|
180
|
+
"""Test per-task timeout configuration with default callbacks."""
|
181
|
+
runner = AsyncTaskRunner(
|
182
|
+
timeout=1, # Default 1 second timeout
|
183
|
+
verbose=False
|
184
|
+
)
|
185
|
+
|
186
|
+
# This task should timeout with default
|
187
|
+
runner.add_task(slow_task, "timeout_task", 2.0) # Takes 2 seconds
|
188
|
+
|
189
|
+
# This task should complete with custom timeout
|
190
|
+
runner.add_task(
|
191
|
+
slow_task,
|
192
|
+
"complete_task",
|
193
|
+
0.5, # Takes 0.5 seconds
|
194
|
+
task_config=TaskConfig(timeout=3) # 3 second timeout
|
195
|
+
)
|
196
|
+
|
197
|
+
results = await runner.get_aggregated_results()
|
198
|
+
|
199
|
+
# Check that one timed out (first slow_task)
|
200
|
+
assert 'timed_out' in results
|
201
|
+
assert 'slow_task' in results['timed_out']
|
202
|
+
|
203
|
+
# Check that timeout was recorded as error
|
204
|
+
assert 'slow_task' in results['errors']
|
205
|
+
# The default callback stores "Timeout after Xs" or the error might be "Unknown error" if timeout wasn't caught properly
|
206
|
+
error_msg = results['errors'].get('slow_task', '').lower()
|
207
|
+
assert 'timeout' in error_msg or 'unknown' in error_msg
|
208
|
+
|
209
|
+
# The one with extended timeout should complete (gets _1 suffix)
|
210
|
+
assert 'slow_task_1' in results['results']
|
211
|
+
assert results['results']['slow_task_1'] == "Slow result: complete_task"
|
212
|
+
|
213
|
+
|
214
|
+
@pytest.mark.asyncio
|
215
|
+
async def test_shared_state_persistence():
|
216
|
+
"""Test that shared_state is accessible and modifiable across callbacks."""
|
217
|
+
shared_state = {
|
218
|
+
'custom_counter': 0,
|
219
|
+
'task_order': []
|
220
|
+
}
|
221
|
+
|
222
|
+
async def counting_callback(ctx: CallbackContext):
|
223
|
+
"""Callback that increments a counter."""
|
224
|
+
ctx.shared_state['custom_counter'] += 1
|
225
|
+
ctx.shared_state['task_order'].append(ctx.task_name)
|
226
|
+
# Also do the default behavior
|
227
|
+
ctx.shared_state.setdefault('results', {})[ctx.task_name] = ctx.result
|
228
|
+
|
229
|
+
runner = AsyncTaskRunner(
|
230
|
+
shared_state=shared_state,
|
231
|
+
callbacks={'on_task_complete': counting_callback},
|
232
|
+
verbose=False
|
233
|
+
)
|
234
|
+
|
235
|
+
runner.add_task(simple_task, "first")
|
236
|
+
runner.add_task(simple_task, "second")
|
237
|
+
runner.add_task(simple_task, "third")
|
238
|
+
|
239
|
+
results = await runner.get_aggregated_results()
|
240
|
+
|
241
|
+
# Check custom state was maintained
|
242
|
+
assert results['custom_counter'] == 3
|
243
|
+
assert len(results['task_order']) == 3
|
244
|
+
# With auto-naming, tasks are now: simple_task, simple_task_1, simple_task_2
|
245
|
+
assert 'simple_task' in results['task_order']
|
246
|
+
assert 'simple_task_1' in results['task_order']
|
247
|
+
assert 'simple_task_2' in results['task_order']
|
248
|
+
|
249
|
+
# Default keys should also be present
|
250
|
+
assert 'results' in results
|
251
|
+
assert 'errors' in results
|
252
|
+
assert 'completed' in results
|
253
|
+
|
254
|
+
|
255
|
+
@pytest.mark.asyncio
|
256
|
+
async def test_verbose_mode():
|
257
|
+
"""Test that verbose mode affects shared_state population but not its behavior."""
|
258
|
+
# Test verbose=True (default)
|
259
|
+
runner_verbose = AsyncTaskRunner(verbose=True)
|
260
|
+
runner_verbose.add_task(simple_task, "verbose_test")
|
261
|
+
results_verbose = await runner_verbose.get_aggregated_results()
|
262
|
+
|
263
|
+
# Should have results regardless of verbose mode
|
264
|
+
assert 'simple_task' in results_verbose['results']
|
265
|
+
assert 'simple_task' in results_verbose['completed']
|
266
|
+
|
267
|
+
# Test verbose=False - should still work but quietly
|
268
|
+
runner_quiet = AsyncTaskRunner(verbose=False)
|
269
|
+
runner_quiet.add_task(simple_task, "quiet_test")
|
270
|
+
results_quiet = await runner_quiet.get_aggregated_results()
|
271
|
+
|
272
|
+
# Should have same results structure
|
273
|
+
assert 'simple_task' in results_quiet['results']
|
274
|
+
assert 'simple_task' in results_quiet['completed']
|
275
|
+
|
276
|
+
# Both should produce same result structure
|
277
|
+
assert set(results_verbose.keys()) == set(results_quiet.keys())
|
278
|
+
|
279
|
+
|
280
|
+
@pytest.mark.asyncio
|
281
|
+
async def test_multiple_tasks_same_name():
|
282
|
+
"""Test behavior when multiple tasks have the same function name."""
|
283
|
+
runner = AsyncTaskRunner(verbose=False)
|
284
|
+
|
285
|
+
# Add multiple tasks with same function
|
286
|
+
runner.add_task(simple_task, "first")
|
287
|
+
runner.add_task(simple_task, "second")
|
288
|
+
runner.add_task(simple_task, "third")
|
289
|
+
|
290
|
+
results = await runner.get_aggregated_results()
|
291
|
+
|
292
|
+
# Results dict should have all three with unique names
|
293
|
+
assert results['results']['simple_task'] == "Result: first"
|
294
|
+
assert results['results']['simple_task_1'] == "Result: second"
|
295
|
+
assert results['results']['simple_task_2'] == "Result: third"
|
296
|
+
|
297
|
+
# Completed list should have all three with unique names
|
298
|
+
assert 'simple_task' in results['completed']
|
299
|
+
assert 'simple_task_1' in results['completed']
|
300
|
+
assert 'simple_task_2' in results['completed']
|
301
|
+
|
302
|
+
# Started list should have all three with unique names
|
303
|
+
assert 'simple_task' in results['started']
|
304
|
+
assert 'simple_task_1' in results['started']
|
305
|
+
assert 'simple_task_2' in results['started']
|
306
|
+
|
307
|
+
|
308
|
+
@pytest.mark.asyncio
|
309
|
+
async def test_empty_runner():
|
310
|
+
"""Test that runner works with no tasks."""
|
311
|
+
runner = AsyncTaskRunner(verbose=False)
|
312
|
+
|
313
|
+
# Run with no tasks
|
314
|
+
results = await runner.get_aggregated_results()
|
315
|
+
|
316
|
+
# Should return empty but initialized state
|
317
|
+
assert results == {
|
318
|
+
'results': {},
|
319
|
+
'errors': {},
|
320
|
+
'completed': [],
|
321
|
+
'started': [],
|
322
|
+
'retries': [],
|
323
|
+
'timed_out': []
|
324
|
+
}
|
325
|
+
|
326
|
+
|
327
|
+
@pytest.mark.asyncio
|
328
|
+
async def test_task_config_none_values():
|
329
|
+
"""Test that TaskConfig with None values falls back to global settings."""
|
330
|
+
runner = AsyncTaskRunner(
|
331
|
+
timeout=5,
|
332
|
+
retry_enabled=True,
|
333
|
+
verbose=False
|
334
|
+
)
|
335
|
+
|
336
|
+
# Add task with partial config (None values should use globals)
|
337
|
+
runner.add_task(
|
338
|
+
simple_task,
|
339
|
+
"test",
|
340
|
+
task_config=TaskConfig(
|
341
|
+
timeout=None, # Should use global (5)
|
342
|
+
retry_enabled=None, # Should use global (True)
|
343
|
+
metadata={'custom': 'data'}
|
344
|
+
)
|
345
|
+
)
|
346
|
+
|
347
|
+
results = await runner.get_aggregated_results()
|
348
|
+
|
349
|
+
# Task should complete successfully
|
350
|
+
assert 'simple_task' in results['results']
|
351
|
+
assert results['results']['simple_task'] == "Result: test"
|
352
|
+
|
353
|
+
|
354
|
+
@pytest.mark.asyncio
|
355
|
+
async def test_custom_task_names():
|
356
|
+
"""Test custom task naming feature for better differentiation."""
|
357
|
+
runner = AsyncTaskRunner(verbose=False)
|
358
|
+
|
359
|
+
# Use custom task names to differentiate multiple calls to the same function
|
360
|
+
runner.add_task(simple_task, "API", task_name="fetch_api_data")
|
361
|
+
runner.add_task(simple_task, "Database", task_name="fetch_db_data")
|
362
|
+
runner.add_task(simple_task, "Cache", task_name="fetch_cache_data")
|
363
|
+
|
364
|
+
results = await runner.get_aggregated_results()
|
365
|
+
|
366
|
+
# Check that custom names were used
|
367
|
+
assert 'fetch_api_data' in results['results']
|
368
|
+
assert 'fetch_db_data' in results['results']
|
369
|
+
assert 'fetch_cache_data' in results['results']
|
370
|
+
|
371
|
+
# Check the results values
|
372
|
+
assert results['results']['fetch_api_data'] == "Result: API"
|
373
|
+
assert results['results']['fetch_db_data'] == "Result: Database"
|
374
|
+
assert results['results']['fetch_cache_data'] == "Result: Cache"
|
375
|
+
|
376
|
+
# Check completed list has custom names
|
377
|
+
assert set(results['completed']) == {'fetch_api_data', 'fetch_db_data', 'fetch_cache_data'}
|
378
|
+
|
379
|
+
# Check started list has custom names
|
380
|
+
assert set(results['started']) == {'fetch_api_data', 'fetch_db_data', 'fetch_cache_data'}
|
381
|
+
|
382
|
+
|
383
|
+
@pytest.mark.asyncio
|
384
|
+
async def test_custom_task_names_with_duplicates():
|
385
|
+
"""Test that duplicate custom task names are automatically made unique."""
|
386
|
+
runner = AsyncTaskRunner(verbose=False)
|
387
|
+
|
388
|
+
# Add multiple tasks with the same custom name - should auto-suffix
|
389
|
+
runner.add_task(simple_task, "first", task_name="duplicate_name")
|
390
|
+
runner.add_task(simple_task, "second", task_name="duplicate_name")
|
391
|
+
runner.add_task(simple_task, "third", task_name="duplicate_name")
|
392
|
+
|
393
|
+
results = await runner.get_aggregated_results()
|
394
|
+
|
395
|
+
# Check that names were made unique with suffixes
|
396
|
+
assert 'duplicate_name' in results['results']
|
397
|
+
assert 'duplicate_name_1' in results['results']
|
398
|
+
assert 'duplicate_name_2' in results['results']
|
399
|
+
|
400
|
+
# Check the results values
|
401
|
+
assert results['results']['duplicate_name'] == "Result: first"
|
402
|
+
assert results['results']['duplicate_name_1'] == "Result: second"
|
403
|
+
assert results['results']['duplicate_name_2'] == "Result: third"
|
404
|
+
|
405
|
+
# Check completed list has unique names
|
406
|
+
assert 'duplicate_name' in results['completed']
|
407
|
+
assert 'duplicate_name_1' in results['completed']
|
408
|
+
assert 'duplicate_name_2' in results['completed']
|
409
|
+
|
410
|
+
|
411
|
+
if __name__ == "__main__":
|
412
|
+
# Run tests with asyncio
|
413
|
+
asyncio.run(test_default_callbacks_basic())
|
414
|
+
asyncio.run(test_default_callbacks_with_errors())
|
415
|
+
asyncio.run(test_default_callbacks_with_retry())
|
416
|
+
asyncio.run(test_custom_callback_override())
|
417
|
+
asyncio.run(test_no_default_callbacks())
|
418
|
+
asyncio.run(test_per_task_timeout_with_defaults())
|
419
|
+
asyncio.run(test_shared_state_persistence())
|
420
|
+
asyncio.run(test_verbose_mode())
|
421
|
+
asyncio.run(test_multiple_tasks_same_name())
|
422
|
+
asyncio.run(test_empty_runner())
|
423
|
+
asyncio.run(test_task_config_none_values())
|
424
|
+
asyncio.run(test_custom_task_names())
|
425
|
+
asyncio.run(test_custom_task_names_with_duplicates())
|
426
|
+
print("All tests passed!")
|