ag2 0.9.1__py3-none-any.whl → 0.9.1.post0__py3-none-any.whl
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.
Potentially problematic release.
This version of ag2 might be problematic. Click here for more details.
- {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info}/METADATA +264 -73
- ag2-0.9.1.post0.dist-info/RECORD +392 -0
- {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info}/WHEEL +1 -2
- autogen/__init__.py +89 -0
- autogen/_website/__init__.py +3 -0
- autogen/_website/generate_api_references.py +427 -0
- autogen/_website/generate_mkdocs.py +1174 -0
- autogen/_website/notebook_processor.py +476 -0
- autogen/_website/process_notebooks.py +656 -0
- autogen/_website/utils.py +412 -0
- autogen/agentchat/__init__.py +44 -0
- autogen/agentchat/agent.py +182 -0
- autogen/agentchat/assistant_agent.py +85 -0
- autogen/agentchat/chat.py +309 -0
- autogen/agentchat/contrib/__init__.py +5 -0
- autogen/agentchat/contrib/agent_eval/README.md +7 -0
- autogen/agentchat/contrib/agent_eval/agent_eval.py +108 -0
- autogen/agentchat/contrib/agent_eval/criterion.py +43 -0
- autogen/agentchat/contrib/agent_eval/critic_agent.py +44 -0
- autogen/agentchat/contrib/agent_eval/quantifier_agent.py +39 -0
- autogen/agentchat/contrib/agent_eval/subcritic_agent.py +45 -0
- autogen/agentchat/contrib/agent_eval/task.py +42 -0
- autogen/agentchat/contrib/agent_optimizer.py +429 -0
- autogen/agentchat/contrib/capabilities/__init__.py +5 -0
- autogen/agentchat/contrib/capabilities/agent_capability.py +20 -0
- autogen/agentchat/contrib/capabilities/generate_images.py +301 -0
- autogen/agentchat/contrib/capabilities/teachability.py +393 -0
- autogen/agentchat/contrib/capabilities/text_compressors.py +66 -0
- autogen/agentchat/contrib/capabilities/tools_capability.py +22 -0
- autogen/agentchat/contrib/capabilities/transform_messages.py +93 -0
- autogen/agentchat/contrib/capabilities/transforms.py +566 -0
- autogen/agentchat/contrib/capabilities/transforms_util.py +122 -0
- autogen/agentchat/contrib/capabilities/vision_capability.py +214 -0
- autogen/agentchat/contrib/captainagent/__init__.py +9 -0
- autogen/agentchat/contrib/captainagent/agent_builder.py +790 -0
- autogen/agentchat/contrib/captainagent/captainagent.py +512 -0
- autogen/agentchat/contrib/captainagent/tool_retriever.py +335 -0
- autogen/agentchat/contrib/captainagent/tools/README.md +44 -0
- autogen/agentchat/contrib/captainagent/tools/__init__.py +5 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_correlation.py +40 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_skewness_and_kurtosis.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_iqr.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_zscore.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/explore_csv.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/data_analysis/shapiro_wilk_test.py +30 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_download.py +27 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_search.py +53 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_image.py +53 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_text.py +38 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_wikipedia_text.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_youtube_caption.py +34 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/image_qa.py +60 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/optical_character_recognition.py +61 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/perform_web_search.py +47 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/scrape_wikipedia_tables.py +33 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/transcribe_audio_file.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/information_retrieval/youtube_download.py +35 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_circle_area_from_diameter.py +21 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_day_of_the_week.py +18 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_fraction_sum.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_matrix_power.py +31 -0
- autogen/agentchat/contrib/captainagent/tools/math/calculate_reflected_point.py +16 -0
- autogen/agentchat/contrib/captainagent/tools/math/complex_numbers_product.py +25 -0
- autogen/agentchat/contrib/captainagent/tools/math/compute_currency_conversion.py +23 -0
- autogen/agentchat/contrib/captainagent/tools/math/count_distinct_permutations.py +27 -0
- autogen/agentchat/contrib/captainagent/tools/math/evaluate_expression.py +28 -0
- autogen/agentchat/contrib/captainagent/tools/math/find_continuity_point.py +34 -0
- autogen/agentchat/contrib/captainagent/tools/math/fraction_to_mixed_numbers.py +39 -0
- autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py +23 -0
- autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py +36 -0
- autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py +15 -0
- autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py +15 -0
- autogen/agentchat/contrib/captainagent/tools/requirements.txt +10 -0
- autogen/agentchat/contrib/captainagent/tools/tool_description.tsv +34 -0
- autogen/agentchat/contrib/gpt_assistant_agent.py +526 -0
- autogen/agentchat/contrib/graph_rag/__init__.py +9 -0
- autogen/agentchat/contrib/graph_rag/document.py +29 -0
- autogen/agentchat/contrib/graph_rag/falkor_graph_query_engine.py +170 -0
- autogen/agentchat/contrib/graph_rag/falkor_graph_rag_capability.py +103 -0
- autogen/agentchat/contrib/graph_rag/graph_query_engine.py +53 -0
- autogen/agentchat/contrib/graph_rag/graph_rag_capability.py +63 -0
- autogen/agentchat/contrib/graph_rag/neo4j_graph_query_engine.py +268 -0
- autogen/agentchat/contrib/graph_rag/neo4j_graph_rag_capability.py +83 -0
- autogen/agentchat/contrib/graph_rag/neo4j_native_graph_query_engine.py +210 -0
- autogen/agentchat/contrib/graph_rag/neo4j_native_graph_rag_capability.py +93 -0
- autogen/agentchat/contrib/img_utils.py +397 -0
- autogen/agentchat/contrib/llamaindex_conversable_agent.py +117 -0
- autogen/agentchat/contrib/llava_agent.py +187 -0
- autogen/agentchat/contrib/math_user_proxy_agent.py +464 -0
- autogen/agentchat/contrib/multimodal_conversable_agent.py +125 -0
- autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py +324 -0
- autogen/agentchat/contrib/rag/__init__.py +10 -0
- autogen/agentchat/contrib/rag/chromadb_query_engine.py +272 -0
- autogen/agentchat/contrib/rag/llamaindex_query_engine.py +198 -0
- autogen/agentchat/contrib/rag/mongodb_query_engine.py +329 -0
- autogen/agentchat/contrib/rag/query_engine.py +74 -0
- autogen/agentchat/contrib/retrieve_assistant_agent.py +56 -0
- autogen/agentchat/contrib/retrieve_user_proxy_agent.py +703 -0
- autogen/agentchat/contrib/society_of_mind_agent.py +199 -0
- autogen/agentchat/contrib/swarm_agent.py +1425 -0
- autogen/agentchat/contrib/text_analyzer_agent.py +79 -0
- autogen/agentchat/contrib/vectordb/__init__.py +5 -0
- autogen/agentchat/contrib/vectordb/base.py +232 -0
- autogen/agentchat/contrib/vectordb/chromadb.py +315 -0
- autogen/agentchat/contrib/vectordb/couchbase.py +407 -0
- autogen/agentchat/contrib/vectordb/mongodb.py +550 -0
- autogen/agentchat/contrib/vectordb/pgvectordb.py +928 -0
- autogen/agentchat/contrib/vectordb/qdrant.py +320 -0
- autogen/agentchat/contrib/vectordb/utils.py +126 -0
- autogen/agentchat/contrib/web_surfer.py +303 -0
- autogen/agentchat/conversable_agent.py +4020 -0
- autogen/agentchat/group/__init__.py +64 -0
- autogen/agentchat/group/available_condition.py +91 -0
- autogen/agentchat/group/context_condition.py +77 -0
- autogen/agentchat/group/context_expression.py +238 -0
- autogen/agentchat/group/context_str.py +41 -0
- autogen/agentchat/group/context_variables.py +192 -0
- autogen/agentchat/group/group_tool_executor.py +202 -0
- autogen/agentchat/group/group_utils.py +591 -0
- autogen/agentchat/group/handoffs.py +244 -0
- autogen/agentchat/group/llm_condition.py +93 -0
- autogen/agentchat/group/multi_agent_chat.py +237 -0
- autogen/agentchat/group/on_condition.py +58 -0
- autogen/agentchat/group/on_context_condition.py +54 -0
- autogen/agentchat/group/patterns/__init__.py +18 -0
- autogen/agentchat/group/patterns/auto.py +159 -0
- autogen/agentchat/group/patterns/manual.py +176 -0
- autogen/agentchat/group/patterns/pattern.py +288 -0
- autogen/agentchat/group/patterns/random.py +106 -0
- autogen/agentchat/group/patterns/round_robin.py +117 -0
- autogen/agentchat/group/reply_result.py +26 -0
- autogen/agentchat/group/speaker_selection_result.py +41 -0
- autogen/agentchat/group/targets/__init__.py +4 -0
- autogen/agentchat/group/targets/group_chat_target.py +132 -0
- autogen/agentchat/group/targets/group_manager_target.py +151 -0
- autogen/agentchat/group/targets/transition_target.py +413 -0
- autogen/agentchat/group/targets/transition_utils.py +6 -0
- autogen/agentchat/groupchat.py +1694 -0
- autogen/agentchat/realtime/__init__.py +3 -0
- autogen/agentchat/realtime/experimental/__init__.py +20 -0
- autogen/agentchat/realtime/experimental/audio_adapters/__init__.py +8 -0
- autogen/agentchat/realtime/experimental/audio_adapters/twilio_audio_adapter.py +148 -0
- autogen/agentchat/realtime/experimental/audio_adapters/websocket_audio_adapter.py +139 -0
- autogen/agentchat/realtime/experimental/audio_observer.py +42 -0
- autogen/agentchat/realtime/experimental/clients/__init__.py +15 -0
- autogen/agentchat/realtime/experimental/clients/gemini/__init__.py +7 -0
- autogen/agentchat/realtime/experimental/clients/gemini/client.py +274 -0
- autogen/agentchat/realtime/experimental/clients/oai/__init__.py +8 -0
- autogen/agentchat/realtime/experimental/clients/oai/base_client.py +220 -0
- autogen/agentchat/realtime/experimental/clients/oai/rtc_client.py +243 -0
- autogen/agentchat/realtime/experimental/clients/oai/utils.py +48 -0
- autogen/agentchat/realtime/experimental/clients/realtime_client.py +190 -0
- autogen/agentchat/realtime/experimental/function_observer.py +85 -0
- autogen/agentchat/realtime/experimental/realtime_agent.py +158 -0
- autogen/agentchat/realtime/experimental/realtime_events.py +42 -0
- autogen/agentchat/realtime/experimental/realtime_observer.py +100 -0
- autogen/agentchat/realtime/experimental/realtime_swarm.py +475 -0
- autogen/agentchat/realtime/experimental/websockets.py +21 -0
- autogen/agentchat/realtime_agent/__init__.py +21 -0
- autogen/agentchat/user_proxy_agent.py +111 -0
- autogen/agentchat/utils.py +206 -0
- autogen/agents/__init__.py +3 -0
- autogen/agents/contrib/__init__.py +10 -0
- autogen/agents/contrib/time/__init__.py +8 -0
- autogen/agents/contrib/time/time_reply_agent.py +73 -0
- autogen/agents/contrib/time/time_tool_agent.py +51 -0
- autogen/agents/experimental/__init__.py +27 -0
- autogen/agents/experimental/deep_research/__init__.py +7 -0
- autogen/agents/experimental/deep_research/deep_research.py +52 -0
- autogen/agents/experimental/discord/__init__.py +7 -0
- autogen/agents/experimental/discord/discord.py +66 -0
- autogen/agents/experimental/document_agent/__init__.py +19 -0
- autogen/agents/experimental/document_agent/chroma_query_engine.py +316 -0
- autogen/agents/experimental/document_agent/docling_doc_ingest_agent.py +118 -0
- autogen/agents/experimental/document_agent/document_agent.py +461 -0
- autogen/agents/experimental/document_agent/document_conditions.py +50 -0
- autogen/agents/experimental/document_agent/document_utils.py +380 -0
- autogen/agents/experimental/document_agent/inmemory_query_engine.py +220 -0
- autogen/agents/experimental/document_agent/parser_utils.py +130 -0
- autogen/agents/experimental/document_agent/url_utils.py +426 -0
- autogen/agents/experimental/reasoning/__init__.py +7 -0
- autogen/agents/experimental/reasoning/reasoning_agent.py +1178 -0
- autogen/agents/experimental/slack/__init__.py +7 -0
- autogen/agents/experimental/slack/slack.py +73 -0
- autogen/agents/experimental/telegram/__init__.py +7 -0
- autogen/agents/experimental/telegram/telegram.py +77 -0
- autogen/agents/experimental/websurfer/__init__.py +7 -0
- autogen/agents/experimental/websurfer/websurfer.py +62 -0
- autogen/agents/experimental/wikipedia/__init__.py +7 -0
- autogen/agents/experimental/wikipedia/wikipedia.py +90 -0
- autogen/browser_utils.py +309 -0
- autogen/cache/__init__.py +10 -0
- autogen/cache/abstract_cache_base.py +75 -0
- autogen/cache/cache.py +203 -0
- autogen/cache/cache_factory.py +88 -0
- autogen/cache/cosmos_db_cache.py +144 -0
- autogen/cache/disk_cache.py +102 -0
- autogen/cache/in_memory_cache.py +58 -0
- autogen/cache/redis_cache.py +123 -0
- autogen/code_utils.py +596 -0
- autogen/coding/__init__.py +22 -0
- autogen/coding/base.py +119 -0
- autogen/coding/docker_commandline_code_executor.py +268 -0
- autogen/coding/factory.py +47 -0
- autogen/coding/func_with_reqs.py +202 -0
- autogen/coding/jupyter/__init__.py +23 -0
- autogen/coding/jupyter/base.py +36 -0
- autogen/coding/jupyter/docker_jupyter_server.py +167 -0
- autogen/coding/jupyter/embedded_ipython_code_executor.py +182 -0
- autogen/coding/jupyter/import_utils.py +82 -0
- autogen/coding/jupyter/jupyter_client.py +231 -0
- autogen/coding/jupyter/jupyter_code_executor.py +160 -0
- autogen/coding/jupyter/local_jupyter_server.py +172 -0
- autogen/coding/local_commandline_code_executor.py +405 -0
- autogen/coding/markdown_code_extractor.py +45 -0
- autogen/coding/utils.py +56 -0
- autogen/doc_utils.py +34 -0
- autogen/events/__init__.py +7 -0
- autogen/events/agent_events.py +1010 -0
- autogen/events/base_event.py +99 -0
- autogen/events/client_events.py +167 -0
- autogen/events/helpers.py +36 -0
- autogen/events/print_event.py +46 -0
- autogen/exception_utils.py +73 -0
- autogen/extensions/__init__.py +5 -0
- autogen/fast_depends/__init__.py +16 -0
- autogen/fast_depends/_compat.py +80 -0
- autogen/fast_depends/core/__init__.py +14 -0
- autogen/fast_depends/core/build.py +225 -0
- autogen/fast_depends/core/model.py +576 -0
- autogen/fast_depends/dependencies/__init__.py +15 -0
- autogen/fast_depends/dependencies/model.py +29 -0
- autogen/fast_depends/dependencies/provider.py +39 -0
- autogen/fast_depends/library/__init__.py +10 -0
- autogen/fast_depends/library/model.py +46 -0
- autogen/fast_depends/py.typed +6 -0
- autogen/fast_depends/schema.py +66 -0
- autogen/fast_depends/use.py +280 -0
- autogen/fast_depends/utils.py +187 -0
- autogen/formatting_utils.py +83 -0
- autogen/function_utils.py +13 -0
- autogen/graph_utils.py +178 -0
- autogen/import_utils.py +526 -0
- autogen/interop/__init__.py +22 -0
- autogen/interop/crewai/__init__.py +7 -0
- autogen/interop/crewai/crewai.py +88 -0
- autogen/interop/interoperability.py +71 -0
- autogen/interop/interoperable.py +46 -0
- autogen/interop/langchain/__init__.py +8 -0
- autogen/interop/langchain/langchain_chat_model_factory.py +155 -0
- autogen/interop/langchain/langchain_tool.py +82 -0
- autogen/interop/litellm/__init__.py +7 -0
- autogen/interop/litellm/litellm_config_factory.py +113 -0
- autogen/interop/pydantic_ai/__init__.py +7 -0
- autogen/interop/pydantic_ai/pydantic_ai.py +168 -0
- autogen/interop/registry.py +69 -0
- autogen/io/__init__.py +15 -0
- autogen/io/base.py +151 -0
- autogen/io/console.py +56 -0
- autogen/io/processors/__init__.py +12 -0
- autogen/io/processors/base.py +21 -0
- autogen/io/processors/console_event_processor.py +56 -0
- autogen/io/run_response.py +293 -0
- autogen/io/thread_io_stream.py +63 -0
- autogen/io/websockets.py +213 -0
- autogen/json_utils.py +43 -0
- autogen/llm_config.py +379 -0
- autogen/logger/__init__.py +11 -0
- autogen/logger/base_logger.py +128 -0
- autogen/logger/file_logger.py +261 -0
- autogen/logger/logger_factory.py +42 -0
- autogen/logger/logger_utils.py +57 -0
- autogen/logger/sqlite_logger.py +523 -0
- autogen/math_utils.py +339 -0
- autogen/mcp/__init__.py +7 -0
- autogen/mcp/mcp_client.py +208 -0
- autogen/messages/__init__.py +7 -0
- autogen/messages/agent_messages.py +948 -0
- autogen/messages/base_message.py +107 -0
- autogen/messages/client_messages.py +171 -0
- autogen/messages/print_message.py +49 -0
- autogen/oai/__init__.py +53 -0
- autogen/oai/anthropic.py +714 -0
- autogen/oai/bedrock.py +628 -0
- autogen/oai/cerebras.py +299 -0
- autogen/oai/client.py +1435 -0
- autogen/oai/client_utils.py +169 -0
- autogen/oai/cohere.py +479 -0
- autogen/oai/gemini.py +990 -0
- autogen/oai/gemini_types.py +129 -0
- autogen/oai/groq.py +305 -0
- autogen/oai/mistral.py +303 -0
- autogen/oai/oai_models/__init__.py +11 -0
- autogen/oai/oai_models/_models.py +16 -0
- autogen/oai/oai_models/chat_completion.py +87 -0
- autogen/oai/oai_models/chat_completion_audio.py +32 -0
- autogen/oai/oai_models/chat_completion_message.py +86 -0
- autogen/oai/oai_models/chat_completion_message_tool_call.py +37 -0
- autogen/oai/oai_models/chat_completion_token_logprob.py +63 -0
- autogen/oai/oai_models/completion_usage.py +60 -0
- autogen/oai/ollama.py +643 -0
- autogen/oai/openai_utils.py +881 -0
- autogen/oai/together.py +370 -0
- autogen/retrieve_utils.py +491 -0
- autogen/runtime_logging.py +160 -0
- autogen/token_count_utils.py +267 -0
- autogen/tools/__init__.py +20 -0
- autogen/tools/contrib/__init__.py +9 -0
- autogen/tools/contrib/time/__init__.py +7 -0
- autogen/tools/contrib/time/time.py +41 -0
- autogen/tools/dependency_injection.py +254 -0
- autogen/tools/experimental/__init__.py +43 -0
- autogen/tools/experimental/browser_use/__init__.py +7 -0
- autogen/tools/experimental/browser_use/browser_use.py +161 -0
- autogen/tools/experimental/crawl4ai/__init__.py +7 -0
- autogen/tools/experimental/crawl4ai/crawl4ai.py +153 -0
- autogen/tools/experimental/deep_research/__init__.py +7 -0
- autogen/tools/experimental/deep_research/deep_research.py +328 -0
- autogen/tools/experimental/duckduckgo/__init__.py +7 -0
- autogen/tools/experimental/duckduckgo/duckduckgo_search.py +109 -0
- autogen/tools/experimental/google/__init__.py +14 -0
- autogen/tools/experimental/google/authentication/__init__.py +11 -0
- autogen/tools/experimental/google/authentication/credentials_hosted_provider.py +43 -0
- autogen/tools/experimental/google/authentication/credentials_local_provider.py +91 -0
- autogen/tools/experimental/google/authentication/credentials_provider.py +35 -0
- autogen/tools/experimental/google/drive/__init__.py +9 -0
- autogen/tools/experimental/google/drive/drive_functions.py +124 -0
- autogen/tools/experimental/google/drive/toolkit.py +88 -0
- autogen/tools/experimental/google/model.py +17 -0
- autogen/tools/experimental/google/toolkit_protocol.py +19 -0
- autogen/tools/experimental/google_search/__init__.py +8 -0
- autogen/tools/experimental/google_search/google_search.py +93 -0
- autogen/tools/experimental/google_search/youtube_search.py +181 -0
- autogen/tools/experimental/messageplatform/__init__.py +17 -0
- autogen/tools/experimental/messageplatform/discord/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/discord/discord.py +288 -0
- autogen/tools/experimental/messageplatform/slack/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/slack/slack.py +391 -0
- autogen/tools/experimental/messageplatform/telegram/__init__.py +7 -0
- autogen/tools/experimental/messageplatform/telegram/telegram.py +275 -0
- autogen/tools/experimental/perplexity/__init__.py +7 -0
- autogen/tools/experimental/perplexity/perplexity_search.py +260 -0
- autogen/tools/experimental/tavily/__init__.py +7 -0
- autogen/tools/experimental/tavily/tavily_search.py +183 -0
- autogen/tools/experimental/web_search_preview/__init__.py +7 -0
- autogen/tools/experimental/web_search_preview/web_search_preview.py +114 -0
- autogen/tools/experimental/wikipedia/__init__.py +7 -0
- autogen/tools/experimental/wikipedia/wikipedia.py +287 -0
- autogen/tools/function_utils.py +411 -0
- autogen/tools/tool.py +187 -0
- autogen/tools/toolkit.py +86 -0
- autogen/types.py +29 -0
- autogen/version.py +7 -0
- ag2-0.9.1.dist-info/RECORD +0 -6
- ag2-0.9.1.dist-info/top_level.txt +0 -1
- {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info/licenses}/LICENSE +0 -0
- {ag2-0.9.1.dist-info → ag2-0.9.1.post0.dist-info/licenses}/NOTICE.md +0 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import concurrent.futures
|
|
8
|
+
import functools
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
import signal
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
import threading
|
|
17
|
+
import time
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from functools import lru_cache
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Callable, Optional, TypeVar, Union
|
|
22
|
+
|
|
23
|
+
from ..import_utils import optional_import_block, require_optional_import
|
|
24
|
+
|
|
25
|
+
with optional_import_block():
|
|
26
|
+
import nbformat
|
|
27
|
+
from nbclient.client import NotebookClient
|
|
28
|
+
from nbclient.exceptions import (
|
|
29
|
+
CellExecutionError,
|
|
30
|
+
CellTimeoutError,
|
|
31
|
+
)
|
|
32
|
+
from nbformat import NotebookNode
|
|
33
|
+
from termcolor import colored
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Notebook execution based on nbmake: https://github.com/treebeardtech/nbmakes
|
|
37
|
+
@dataclass
|
|
38
|
+
class NotebookError:
|
|
39
|
+
error_name: str
|
|
40
|
+
error_value: Optional[str]
|
|
41
|
+
traceback: str
|
|
42
|
+
cell_source: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class NotebookSkip:
|
|
47
|
+
reason: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
NB_VERSION = 4
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Result:
|
|
54
|
+
def __init__(self, returncode: int, stdout: str, stderr: str):
|
|
55
|
+
self.returncode = returncode
|
|
56
|
+
self.stdout = stdout
|
|
57
|
+
self.stderr = stderr
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def path(path_str: str) -> Path:
|
|
61
|
+
"""Return a Path object."""
|
|
62
|
+
return Path(path_str)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@lru_cache
|
|
66
|
+
def check_quarto_bin(quarto_bin: str = "quarto") -> bool:
|
|
67
|
+
"""Check if quarto is installed."""
|
|
68
|
+
try:
|
|
69
|
+
version_str = subprocess.check_output([quarto_bin, "--version"], text=True).strip()
|
|
70
|
+
version = tuple(map(int, version_str.split(".")))
|
|
71
|
+
return version >= (1, 5, 23)
|
|
72
|
+
|
|
73
|
+
except FileNotFoundError:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
C = TypeVar("C", bound=Callable[..., Any])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def require_quarto_bin(f: C) -> C:
|
|
81
|
+
"""Decorator to skip a function if quarto is not installed."""
|
|
82
|
+
|
|
83
|
+
if check_quarto_bin():
|
|
84
|
+
return f
|
|
85
|
+
else:
|
|
86
|
+
|
|
87
|
+
@functools.wraps(f)
|
|
88
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
89
|
+
return ImportError("Quarto is not installed")
|
|
90
|
+
|
|
91
|
+
return wrapper # type: ignore[return-value]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def load_metadata(notebook: Path) -> dict[str, dict[str, Union[str, list[str], None]]]:
|
|
95
|
+
content = json.load(notebook.open(encoding="utf-8"))
|
|
96
|
+
metadata: dict[str, dict[str, Union[str, list[str], None]]] = content.get("metadata", {})
|
|
97
|
+
return metadata
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def skip_reason_or_none_if_ok(notebook: Path) -> Union[str, None, dict[str, Any]]:
|
|
101
|
+
"""Return a reason to skip the notebook, or None if it should not be skipped."""
|
|
102
|
+
if notebook.suffix != ".ipynb":
|
|
103
|
+
return "not a notebook"
|
|
104
|
+
|
|
105
|
+
if not notebook.exists():
|
|
106
|
+
return "file does not exist"
|
|
107
|
+
|
|
108
|
+
# Extra checks for notebooks in the notebook directory
|
|
109
|
+
if "notebook" not in notebook.parts:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
with open(notebook, encoding="utf-8") as f:
|
|
113
|
+
content = f.read()
|
|
114
|
+
|
|
115
|
+
# Load the json and get the first cell
|
|
116
|
+
json_content = json.loads(content)
|
|
117
|
+
first_cell = json_content["cells"][0]
|
|
118
|
+
|
|
119
|
+
# <!-- and --> must exists on lines on their own
|
|
120
|
+
if first_cell["cell_type"] == "markdown" and first_cell["source"][0].strip() == "<!--":
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"Error in {notebook.resolve()!s} - Front matter should be defined in the notebook metadata now."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
metadata = load_metadata(notebook)
|
|
126
|
+
|
|
127
|
+
if "skip_render" in metadata:
|
|
128
|
+
return metadata["skip_render"]
|
|
129
|
+
|
|
130
|
+
if "front_matter" not in metadata:
|
|
131
|
+
return "front matter missing from notebook metadata ⚠️"
|
|
132
|
+
|
|
133
|
+
front_matter = metadata["front_matter"]
|
|
134
|
+
|
|
135
|
+
if "tags" not in front_matter:
|
|
136
|
+
return "tags is not in front matter"
|
|
137
|
+
|
|
138
|
+
if "description" not in front_matter:
|
|
139
|
+
return "description is not in front matter"
|
|
140
|
+
|
|
141
|
+
# Make sure tags is a list of strings
|
|
142
|
+
if front_matter["tags"] is not None and not all([isinstance(tag, str) for tag in front_matter["tags"]]):
|
|
143
|
+
return "tags must be a list of strings"
|
|
144
|
+
|
|
145
|
+
# Make sure description is a string
|
|
146
|
+
if not isinstance(front_matter["description"], str):
|
|
147
|
+
return "description must be a string"
|
|
148
|
+
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def extract_title(notebook: Path) -> Optional[str]:
|
|
153
|
+
"""Extract the title of the notebook."""
|
|
154
|
+
with open(notebook, encoding="utf-8") as f:
|
|
155
|
+
content = f.read()
|
|
156
|
+
|
|
157
|
+
# Load the json and get the first cell
|
|
158
|
+
json_content = json.loads(content)
|
|
159
|
+
first_cell = json_content["cells"][0]
|
|
160
|
+
|
|
161
|
+
# find the # title
|
|
162
|
+
for line in first_cell["source"]:
|
|
163
|
+
if line.startswith("# "):
|
|
164
|
+
title: str = line[2:].strip()
|
|
165
|
+
# Strip off the { if it exists
|
|
166
|
+
if "{" in title:
|
|
167
|
+
title = title[: title.find("{")].strip()
|
|
168
|
+
return title
|
|
169
|
+
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def start_thread_to_terminate_when_parent_process_dies(ppid: int) -> None:
|
|
174
|
+
pid = os.getpid()
|
|
175
|
+
|
|
176
|
+
def f() -> None:
|
|
177
|
+
while True:
|
|
178
|
+
try:
|
|
179
|
+
os.kill(ppid, 0)
|
|
180
|
+
except OSError:
|
|
181
|
+
os.kill(pid, signal.SIGTERM)
|
|
182
|
+
time.sleep(1)
|
|
183
|
+
|
|
184
|
+
thread = threading.Thread(target=f, daemon=True)
|
|
185
|
+
thread.start()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@require_optional_import("termcolor", "docs")
|
|
189
|
+
def fmt_skip(notebook: Path, reason: str) -> str:
|
|
190
|
+
return f"{colored('[Skip]', 'yellow')} {colored(notebook.name, 'blue')}: {reason}"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@require_optional_import("termcolor", "docs")
|
|
194
|
+
def fmt_ok(notebook: Path) -> str:
|
|
195
|
+
return f"{colored('[OK]', 'green')} {colored(notebook.name, 'blue')} ✅"
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@require_optional_import("termcolor", "docs")
|
|
199
|
+
def fmt_error(notebook: Path, error: Union[NotebookError, str]) -> str:
|
|
200
|
+
if isinstance(error, str):
|
|
201
|
+
return f"{colored('[Error]', 'red')} {colored(notebook.name, 'blue')}: {error}"
|
|
202
|
+
elif isinstance(error, NotebookError):
|
|
203
|
+
return f"{colored('[Error]', 'red')} {colored(notebook.name, 'blue')}: {error.error_name} - {error.error_value}"
|
|
204
|
+
else:
|
|
205
|
+
raise ValueError("error must be a string or a NotebookError")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@require_quarto_bin
|
|
209
|
+
@require_optional_import("nbclient", "docs")
|
|
210
|
+
def test_notebook(notebook_path: Path, timeout: int = 300) -> tuple[Path, Optional[Union[NotebookError, NotebookSkip]]]:
|
|
211
|
+
nb = nbformat.read(str(notebook_path), NB_VERSION) # type: ignore[arg-type,no-untyped-call]
|
|
212
|
+
|
|
213
|
+
if "skip_test" in nb.metadata:
|
|
214
|
+
return notebook_path, NotebookSkip(reason=nb.metadata.skip_test)
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
c = NotebookClient(
|
|
218
|
+
nb,
|
|
219
|
+
timeout=timeout,
|
|
220
|
+
allow_errors=False,
|
|
221
|
+
record_timing=True,
|
|
222
|
+
)
|
|
223
|
+
os.environ["PYDEVD_DISABLE_FILE_VALIDATION"] = "1"
|
|
224
|
+
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
225
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
|
226
|
+
c.execute(cwd=tempdir)
|
|
227
|
+
except CellExecutionError:
|
|
228
|
+
error = get_error_info(nb)
|
|
229
|
+
assert error is not None
|
|
230
|
+
return notebook_path, error
|
|
231
|
+
except CellTimeoutError:
|
|
232
|
+
error = get_timeout_info(nb)
|
|
233
|
+
assert error is not None
|
|
234
|
+
return notebook_path, error
|
|
235
|
+
|
|
236
|
+
return notebook_path, None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Find the first code cell which did not complete.
|
|
240
|
+
@require_optional_import("nbclient", "docs")
|
|
241
|
+
def get_timeout_info(
|
|
242
|
+
nb: NotebookNode,
|
|
243
|
+
) -> Optional[NotebookError]:
|
|
244
|
+
for i, cell in enumerate(nb.cells):
|
|
245
|
+
if cell.cell_type != "code":
|
|
246
|
+
continue
|
|
247
|
+
if "shell.execute_reply" not in cell.metadata.execution:
|
|
248
|
+
return NotebookError(
|
|
249
|
+
error_name="timeout",
|
|
250
|
+
error_value="",
|
|
251
|
+
traceback="",
|
|
252
|
+
cell_source="".join(cell["source"]),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@require_optional_import("nbclient", "docs")
|
|
259
|
+
def get_error_info(nb: NotebookNode) -> Optional[NotebookError]:
|
|
260
|
+
for cell in nb["cells"]: # get LAST error
|
|
261
|
+
if cell["cell_type"] != "code":
|
|
262
|
+
continue
|
|
263
|
+
errors = [output for output in cell["outputs"] if output["output_type"] == "error" or "ename" in output]
|
|
264
|
+
|
|
265
|
+
if errors:
|
|
266
|
+
traceback = "\n".join(errors[0].get("traceback", ""))
|
|
267
|
+
return NotebookError(
|
|
268
|
+
error_name=errors[0].get("ename", ""),
|
|
269
|
+
error_value=errors[0].get("evalue", ""),
|
|
270
|
+
traceback=traceback,
|
|
271
|
+
cell_source="".join(cell["source"]),
|
|
272
|
+
)
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def collect_notebooks(notebook_directory: Path, website_build_directory: Path) -> list[Path]:
|
|
277
|
+
notebooks = list(notebook_directory.glob("*.ipynb"))
|
|
278
|
+
notebooks.extend(list(website_build_directory.glob("docs/**/*.ipynb")))
|
|
279
|
+
return notebooks
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@require_quarto_bin
|
|
283
|
+
@require_optional_import(["nbclient", "termcolor"], "docs")
|
|
284
|
+
def process_notebook(
|
|
285
|
+
src_notebook: Path,
|
|
286
|
+
website_build_directory: Path,
|
|
287
|
+
notebook_dir: Path,
|
|
288
|
+
quarto_bin: str,
|
|
289
|
+
dry_run: bool,
|
|
290
|
+
target_dir_func: Callable[[Path], Path],
|
|
291
|
+
post_processor: Optional[Callable[[Path, Path, dict[str, Any], Path], None]] = None,
|
|
292
|
+
) -> str:
|
|
293
|
+
"""Process a single notebook.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
src_notebook: Source notebook path
|
|
297
|
+
website_build_directory: Output directory
|
|
298
|
+
notebook_dir: Base notebooks directory
|
|
299
|
+
quarto_bin: Path to quarto binary
|
|
300
|
+
dry_run: If True, don't actually process
|
|
301
|
+
target_dir_func: Function to determine target directory for notebooks
|
|
302
|
+
post_processor: Optional callback for post-processing
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
in_notebook_dir = "notebook" in src_notebook.parts
|
|
306
|
+
|
|
307
|
+
metadata = load_metadata(src_notebook)
|
|
308
|
+
|
|
309
|
+
title = extract_title(src_notebook)
|
|
310
|
+
if title is None:
|
|
311
|
+
return fmt_error(src_notebook, "Title not found in notebook")
|
|
312
|
+
|
|
313
|
+
front_matter = {}
|
|
314
|
+
if "front_matter" in metadata:
|
|
315
|
+
front_matter = metadata["front_matter"]
|
|
316
|
+
|
|
317
|
+
front_matter["title"] = title
|
|
318
|
+
|
|
319
|
+
if in_notebook_dir:
|
|
320
|
+
relative_notebook = src_notebook.resolve().relative_to(notebook_dir.resolve())
|
|
321
|
+
dest_dir = target_dir_func(website_build_directory)
|
|
322
|
+
target_file = dest_dir / relative_notebook.with_suffix(".mdx")
|
|
323
|
+
intermediate_notebook = dest_dir / relative_notebook
|
|
324
|
+
|
|
325
|
+
# If the intermediate_notebook already exists, check if it is newer than the source file
|
|
326
|
+
if target_file.exists() and target_file.stat().st_mtime > src_notebook.stat().st_mtime:
|
|
327
|
+
return fmt_skip(src_notebook, f"target file ({target_file.name}) is newer ☑️")
|
|
328
|
+
|
|
329
|
+
if dry_run:
|
|
330
|
+
return colored(f"Would process {src_notebook.name}", "green")
|
|
331
|
+
|
|
332
|
+
# Copy notebook to target dir
|
|
333
|
+
# The reason we copy the notebook is that quarto does not support rendering from a different directory
|
|
334
|
+
shutil.copy(src_notebook, intermediate_notebook)
|
|
335
|
+
|
|
336
|
+
# Check if another file has to be copied too
|
|
337
|
+
# Solely added for the purpose of agent_library_example.json
|
|
338
|
+
if "extra_files_to_copy" in metadata:
|
|
339
|
+
for file in metadata["extra_files_to_copy"]:
|
|
340
|
+
shutil.copy(src_notebook.parent / file, dest_dir / file)
|
|
341
|
+
|
|
342
|
+
# Capture output
|
|
343
|
+
result = subprocess.run([quarto_bin, "render", intermediate_notebook], capture_output=True, text=True)
|
|
344
|
+
if result.returncode != 0:
|
|
345
|
+
return fmt_error(
|
|
346
|
+
src_notebook, f"Failed to render {src_notebook}\n\nstderr:\n{result.stderr}\nstdout:\n{result.stdout}"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Unlink intermediate files
|
|
350
|
+
intermediate_notebook.unlink()
|
|
351
|
+
else:
|
|
352
|
+
target_file = src_notebook.with_suffix(".mdx")
|
|
353
|
+
|
|
354
|
+
# If the intermediate_notebook already exists, check if it is newer than the source file
|
|
355
|
+
if target_file.exists() and target_file.stat().st_mtime > src_notebook.stat().st_mtime:
|
|
356
|
+
return fmt_skip(src_notebook, f"target file ({target_file.name}) is newer ☑️")
|
|
357
|
+
|
|
358
|
+
if dry_run:
|
|
359
|
+
return colored(f"Would process {src_notebook.name}", "green")
|
|
360
|
+
|
|
361
|
+
result = subprocess.run([quarto_bin, "render", src_notebook], capture_output=True, text=True)
|
|
362
|
+
if result.returncode != 0:
|
|
363
|
+
return fmt_error(
|
|
364
|
+
src_notebook, f"Failed to render {src_notebook}\n\nstderr:\n{result.stderr}\nstdout:\n{result.stdout}"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Use post-processor if provided
|
|
368
|
+
if post_processor and not dry_run:
|
|
369
|
+
post_processor(target_file, src_notebook, front_matter, website_build_directory)
|
|
370
|
+
|
|
371
|
+
return fmt_ok(src_notebook)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def create_base_argument_parser() -> argparse.ArgumentParser:
|
|
375
|
+
"""Create the base argument parser with common options."""
|
|
376
|
+
parser = argparse.ArgumentParser()
|
|
377
|
+
subparsers = parser.add_subparsers(dest="subcommand")
|
|
378
|
+
|
|
379
|
+
parser.add_argument(
|
|
380
|
+
"--notebook-directory",
|
|
381
|
+
type=path,
|
|
382
|
+
help="Directory containing notebooks to process",
|
|
383
|
+
)
|
|
384
|
+
parser.add_argument("--website-build-directory", type=path, help="Root directory of website build")
|
|
385
|
+
parser.add_argument("--force", help="Force re-rendering of all notebooks", action="store_true", default=False)
|
|
386
|
+
|
|
387
|
+
render_parser = subparsers.add_parser("render")
|
|
388
|
+
render_parser.add_argument("--quarto-bin", help="Path to quarto binary", default="quarto")
|
|
389
|
+
render_parser.add_argument("--dry-run", help="Don't render", action="store_true")
|
|
390
|
+
render_parser.add_argument("notebooks", type=path, nargs="*", default=None)
|
|
391
|
+
|
|
392
|
+
test_parser = subparsers.add_parser("test")
|
|
393
|
+
test_parser.add_argument("--timeout", help="Timeout for each notebook", type=int, default=60)
|
|
394
|
+
test_parser.add_argument("--exit-on-first-fail", "-e", help="Exit after first test fail", action="store_true")
|
|
395
|
+
test_parser.add_argument("notebooks", type=path, nargs="*", default=None)
|
|
396
|
+
test_parser.add_argument("--workers", help="Number of workers to use", type=int, default=-1)
|
|
397
|
+
|
|
398
|
+
return parser
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def process_notebooks_core(
|
|
402
|
+
args: argparse.Namespace,
|
|
403
|
+
post_process_func: Optional[Callable[[Path, Path, dict[str, Any], Path], None]],
|
|
404
|
+
target_dir_func: Callable[[Path], Path],
|
|
405
|
+
) -> list[Path]:
|
|
406
|
+
"""Core logic for processing notebooks shared across build systems.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
args: Command line arguments
|
|
410
|
+
post_process_func: Function for post-processing rendered notebooks
|
|
411
|
+
target_dir_func: Function to determine target directory for notebooks
|
|
412
|
+
"""
|
|
413
|
+
collected_notebooks = (
|
|
414
|
+
args.notebooks if args.notebooks else collect_notebooks(args.notebook_directory, args.website_build_directory)
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
filtered_notebooks = []
|
|
418
|
+
for notebook in collected_notebooks:
|
|
419
|
+
reason = skip_reason_or_none_if_ok(notebook)
|
|
420
|
+
if reason and isinstance(reason, str):
|
|
421
|
+
print(fmt_skip(notebook, reason))
|
|
422
|
+
else:
|
|
423
|
+
filtered_notebooks.append(notebook)
|
|
424
|
+
|
|
425
|
+
if args.subcommand == "test":
|
|
426
|
+
if args.workers == -1:
|
|
427
|
+
args.workers = None
|
|
428
|
+
failure = False
|
|
429
|
+
with concurrent.futures.ProcessPoolExecutor(
|
|
430
|
+
max_workers=args.workers,
|
|
431
|
+
initializer=start_thread_to_terminate_when_parent_process_dies,
|
|
432
|
+
initargs=(os.getpid(),),
|
|
433
|
+
) as executor:
|
|
434
|
+
futures = [executor.submit(test_notebook, f, args.timeout) for f in filtered_notebooks]
|
|
435
|
+
for future in concurrent.futures.as_completed(futures):
|
|
436
|
+
notebook, optional_error_or_skip = future.result()
|
|
437
|
+
if isinstance(optional_error_or_skip, NotebookError):
|
|
438
|
+
if optional_error_or_skip.error_name == "timeout":
|
|
439
|
+
print(fmt_error(notebook, optional_error_or_skip.error_name))
|
|
440
|
+
else:
|
|
441
|
+
print("-" * 80)
|
|
442
|
+
print(fmt_error(notebook, optional_error_or_skip))
|
|
443
|
+
print(optional_error_or_skip.traceback)
|
|
444
|
+
print("-" * 80)
|
|
445
|
+
if args.exit_on_first_fail:
|
|
446
|
+
sys.exit(1)
|
|
447
|
+
failure = True
|
|
448
|
+
elif isinstance(optional_error_or_skip, NotebookSkip):
|
|
449
|
+
print(fmt_skip(notebook, optional_error_or_skip.reason))
|
|
450
|
+
else:
|
|
451
|
+
print(fmt_ok(notebook))
|
|
452
|
+
|
|
453
|
+
if failure:
|
|
454
|
+
sys.exit(1)
|
|
455
|
+
|
|
456
|
+
elif args.subcommand == "render":
|
|
457
|
+
check_quarto_bin(args.quarto_bin)
|
|
458
|
+
|
|
459
|
+
target_dir = target_dir_func(args.website_build_directory)
|
|
460
|
+
if not target_dir.exists():
|
|
461
|
+
target_dir.mkdir(parents=True)
|
|
462
|
+
|
|
463
|
+
for notebook in filtered_notebooks:
|
|
464
|
+
print(
|
|
465
|
+
process_notebook(
|
|
466
|
+
notebook,
|
|
467
|
+
args.website_build_directory,
|
|
468
|
+
args.notebook_directory,
|
|
469
|
+
args.quarto_bin,
|
|
470
|
+
args.dry_run,
|
|
471
|
+
target_dir_func,
|
|
472
|
+
post_process_func,
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
return filtered_notebooks
|