ai-parrot 0.17.2__cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
- agentui/.prettierrc +15 -0
- agentui/QUICKSTART.md +272 -0
- agentui/README.md +59 -0
- agentui/env.example +16 -0
- agentui/jsconfig.json +14 -0
- agentui/package-lock.json +4242 -0
- agentui/package.json +34 -0
- agentui/scripts/postinstall/apply-patches.mjs +260 -0
- agentui/src/app.css +61 -0
- agentui/src/app.d.ts +13 -0
- agentui/src/app.html +12 -0
- agentui/src/components/LoadingSpinner.svelte +64 -0
- agentui/src/components/ThemeSwitcher.svelte +159 -0
- agentui/src/components/index.js +4 -0
- agentui/src/lib/api/bots.ts +60 -0
- agentui/src/lib/api/chat.ts +22 -0
- agentui/src/lib/api/http.ts +25 -0
- agentui/src/lib/components/BotCard.svelte +33 -0
- agentui/src/lib/components/ChatBubble.svelte +63 -0
- agentui/src/lib/components/Toast.svelte +21 -0
- agentui/src/lib/config.ts +20 -0
- agentui/src/lib/stores/auth.svelte.ts +73 -0
- agentui/src/lib/stores/theme.svelte.js +64 -0
- agentui/src/lib/stores/toast.svelte.ts +31 -0
- agentui/src/lib/utils/conversation.ts +39 -0
- agentui/src/routes/+layout.svelte +20 -0
- agentui/src/routes/+page.svelte +232 -0
- agentui/src/routes/login/+page.svelte +200 -0
- agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
- agentui/src/routes/talk/[agentId]/+page.ts +7 -0
- agentui/static/README.md +1 -0
- agentui/svelte.config.js +11 -0
- agentui/tailwind.config.ts +53 -0
- agentui/tsconfig.json +3 -0
- agentui/vite.config.ts +10 -0
- ai_parrot-0.17.2.dist-info/METADATA +472 -0
- ai_parrot-0.17.2.dist-info/RECORD +535 -0
- ai_parrot-0.17.2.dist-info/WHEEL +6 -0
- ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
- ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
- ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
- crew-builder/.prettierrc +15 -0
- crew-builder/QUICKSTART.md +259 -0
- crew-builder/README.md +113 -0
- crew-builder/env.example +17 -0
- crew-builder/jsconfig.json +14 -0
- crew-builder/package-lock.json +4182 -0
- crew-builder/package.json +37 -0
- crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
- crew-builder/src/app.css +62 -0
- crew-builder/src/app.d.ts +13 -0
- crew-builder/src/app.html +12 -0
- crew-builder/src/components/LoadingSpinner.svelte +64 -0
- crew-builder/src/components/ThemeSwitcher.svelte +149 -0
- crew-builder/src/components/index.js +9 -0
- crew-builder/src/lib/api/bots.ts +60 -0
- crew-builder/src/lib/api/chat.ts +80 -0
- crew-builder/src/lib/api/client.ts +56 -0
- crew-builder/src/lib/api/crew/crew.ts +136 -0
- crew-builder/src/lib/api/index.ts +5 -0
- crew-builder/src/lib/api/o365/auth.ts +65 -0
- crew-builder/src/lib/auth/auth.ts +54 -0
- crew-builder/src/lib/components/AgentNode.svelte +43 -0
- crew-builder/src/lib/components/BotCard.svelte +33 -0
- crew-builder/src/lib/components/ChatBubble.svelte +67 -0
- crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
- crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
- crew-builder/src/lib/components/JsonViewer.svelte +24 -0
- crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
- crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
- crew-builder/src/lib/components/Toast.svelte +67 -0
- crew-builder/src/lib/components/Toolbar.svelte +157 -0
- crew-builder/src/lib/components/index.ts +10 -0
- crew-builder/src/lib/config.ts +8 -0
- crew-builder/src/lib/stores/auth.svelte.ts +228 -0
- crew-builder/src/lib/stores/crewStore.ts +369 -0
- crew-builder/src/lib/stores/theme.svelte.js +145 -0
- crew-builder/src/lib/stores/toast.svelte.ts +69 -0
- crew-builder/src/lib/utils/conversation.ts +39 -0
- crew-builder/src/lib/utils/markdown.ts +122 -0
- crew-builder/src/lib/utils/talkHistory.ts +47 -0
- crew-builder/src/routes/+layout.svelte +20 -0
- crew-builder/src/routes/+page.svelte +539 -0
- crew-builder/src/routes/agents/+page.svelte +247 -0
- crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
- crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
- crew-builder/src/routes/builder/+page.svelte +204 -0
- crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
- crew-builder/src/routes/crew/ask/+page.ts +1 -0
- crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
- crew-builder/src/routes/login/+page.svelte +197 -0
- crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
- crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
- crew-builder/static/README.md +1 -0
- crew-builder/svelte.config.js +11 -0
- crew-builder/tailwind.config.ts +53 -0
- crew-builder/tsconfig.json +3 -0
- crew-builder/vite.config.ts +10 -0
- mcp_servers/calculator_server.py +309 -0
- parrot/__init__.py +27 -0
- parrot/__pycache__/__init__.cpython-310.pyc +0 -0
- parrot/__pycache__/version.cpython-310.pyc +0 -0
- parrot/_version.py +34 -0
- parrot/a2a/__init__.py +48 -0
- parrot/a2a/client.py +658 -0
- parrot/a2a/discovery.py +89 -0
- parrot/a2a/mixin.py +257 -0
- parrot/a2a/models.py +376 -0
- parrot/a2a/server.py +770 -0
- parrot/agents/__init__.py +29 -0
- parrot/bots/__init__.py +12 -0
- parrot/bots/a2a_agent.py +19 -0
- parrot/bots/abstract.py +3139 -0
- parrot/bots/agent.py +1129 -0
- parrot/bots/basic.py +9 -0
- parrot/bots/chatbot.py +669 -0
- parrot/bots/data.py +1618 -0
- parrot/bots/database/__init__.py +5 -0
- parrot/bots/database/abstract.py +3071 -0
- parrot/bots/database/cache.py +286 -0
- parrot/bots/database/models.py +468 -0
- parrot/bots/database/prompts.py +154 -0
- parrot/bots/database/retries.py +98 -0
- parrot/bots/database/router.py +269 -0
- parrot/bots/database/sql.py +41 -0
- parrot/bots/db/__init__.py +6 -0
- parrot/bots/db/abstract.py +556 -0
- parrot/bots/db/bigquery.py +602 -0
- parrot/bots/db/cache.py +85 -0
- parrot/bots/db/documentdb.py +668 -0
- parrot/bots/db/elastic.py +1014 -0
- parrot/bots/db/influx.py +898 -0
- parrot/bots/db/mock.py +96 -0
- parrot/bots/db/multi.py +783 -0
- parrot/bots/db/prompts.py +185 -0
- parrot/bots/db/sql.py +1255 -0
- parrot/bots/db/tools.py +212 -0
- parrot/bots/document.py +680 -0
- parrot/bots/hrbot.py +15 -0
- parrot/bots/kb.py +170 -0
- parrot/bots/mcp.py +36 -0
- parrot/bots/orchestration/README.md +463 -0
- parrot/bots/orchestration/__init__.py +1 -0
- parrot/bots/orchestration/agent.py +155 -0
- parrot/bots/orchestration/crew.py +3330 -0
- parrot/bots/orchestration/fsm.py +1179 -0
- parrot/bots/orchestration/hr.py +434 -0
- parrot/bots/orchestration/storage/__init__.py +4 -0
- parrot/bots/orchestration/storage/memory.py +100 -0
- parrot/bots/orchestration/storage/mixin.py +119 -0
- parrot/bots/orchestration/verify.py +202 -0
- parrot/bots/product.py +204 -0
- parrot/bots/prompts/__init__.py +96 -0
- parrot/bots/prompts/agents.py +155 -0
- parrot/bots/prompts/data.py +216 -0
- parrot/bots/prompts/output_generation.py +8 -0
- parrot/bots/scraper/__init__.py +3 -0
- parrot/bots/scraper/models.py +122 -0
- parrot/bots/scraper/scraper.py +1173 -0
- parrot/bots/scraper/templates.py +115 -0
- parrot/bots/stores/__init__.py +5 -0
- parrot/bots/stores/local.py +172 -0
- parrot/bots/webdev.py +81 -0
- parrot/cli.py +17 -0
- parrot/clients/__init__.py +16 -0
- parrot/clients/base.py +1491 -0
- parrot/clients/claude.py +1191 -0
- parrot/clients/factory.py +129 -0
- parrot/clients/google.py +4567 -0
- parrot/clients/gpt.py +1975 -0
- parrot/clients/grok.py +432 -0
- parrot/clients/groq.py +986 -0
- parrot/clients/hf.py +582 -0
- parrot/clients/models.py +18 -0
- parrot/conf.py +395 -0
- parrot/embeddings/__init__.py +9 -0
- parrot/embeddings/base.py +157 -0
- parrot/embeddings/google.py +98 -0
- parrot/embeddings/huggingface.py +74 -0
- parrot/embeddings/openai.py +84 -0
- parrot/embeddings/processor.py +88 -0
- parrot/exceptions.c +13868 -0
- parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/exceptions.pxd +22 -0
- parrot/exceptions.pxi +15 -0
- parrot/exceptions.pyx +44 -0
- parrot/generators/__init__.py +29 -0
- parrot/generators/base.py +200 -0
- parrot/generators/html.py +293 -0
- parrot/generators/react.py +205 -0
- parrot/generators/streamlit.py +203 -0
- parrot/generators/template.py +105 -0
- parrot/handlers/__init__.py +4 -0
- parrot/handlers/agent.py +861 -0
- parrot/handlers/agents/__init__.py +1 -0
- parrot/handlers/agents/abstract.py +900 -0
- parrot/handlers/bots.py +338 -0
- parrot/handlers/chat.py +915 -0
- parrot/handlers/creation.sql +192 -0
- parrot/handlers/crew/ARCHITECTURE.md +362 -0
- parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
- parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
- parrot/handlers/crew/__init__.py +0 -0
- parrot/handlers/crew/handler.py +801 -0
- parrot/handlers/crew/models.py +229 -0
- parrot/handlers/crew/redis_persistence.py +523 -0
- parrot/handlers/jobs/__init__.py +10 -0
- parrot/handlers/jobs/job.py +384 -0
- parrot/handlers/jobs/mixin.py +627 -0
- parrot/handlers/jobs/models.py +115 -0
- parrot/handlers/jobs/worker.py +31 -0
- parrot/handlers/models.py +596 -0
- parrot/handlers/o365_auth.py +105 -0
- parrot/handlers/stream.py +337 -0
- parrot/interfaces/__init__.py +6 -0
- parrot/interfaces/aws.py +143 -0
- parrot/interfaces/credentials.py +113 -0
- parrot/interfaces/database.py +27 -0
- parrot/interfaces/google.py +1123 -0
- parrot/interfaces/hierarchy.py +1227 -0
- parrot/interfaces/http.py +651 -0
- parrot/interfaces/images/__init__.py +0 -0
- parrot/interfaces/images/plugins/__init__.py +24 -0
- parrot/interfaces/images/plugins/abstract.py +58 -0
- parrot/interfaces/images/plugins/analisys.py +148 -0
- parrot/interfaces/images/plugins/classify.py +150 -0
- parrot/interfaces/images/plugins/classifybase.py +182 -0
- parrot/interfaces/images/plugins/detect.py +150 -0
- parrot/interfaces/images/plugins/exif.py +1103 -0
- parrot/interfaces/images/plugins/hash.py +52 -0
- parrot/interfaces/images/plugins/vision.py +104 -0
- parrot/interfaces/images/plugins/yolo.py +66 -0
- parrot/interfaces/images/plugins/zerodetect.py +197 -0
- parrot/interfaces/o365.py +978 -0
- parrot/interfaces/onedrive.py +822 -0
- parrot/interfaces/sharepoint.py +1435 -0
- parrot/interfaces/soap.py +257 -0
- parrot/loaders/__init__.py +8 -0
- parrot/loaders/abstract.py +1131 -0
- parrot/loaders/audio.py +199 -0
- parrot/loaders/basepdf.py +53 -0
- parrot/loaders/basevideo.py +1568 -0
- parrot/loaders/csv.py +409 -0
- parrot/loaders/docx.py +116 -0
- parrot/loaders/epubloader.py +316 -0
- parrot/loaders/excel.py +199 -0
- parrot/loaders/factory.py +55 -0
- parrot/loaders/files/__init__.py +0 -0
- parrot/loaders/files/abstract.py +39 -0
- parrot/loaders/files/html.py +26 -0
- parrot/loaders/files/text.py +63 -0
- parrot/loaders/html.py +152 -0
- parrot/loaders/markdown.py +442 -0
- parrot/loaders/pdf.py +373 -0
- parrot/loaders/pdfmark.py +320 -0
- parrot/loaders/pdftables.py +506 -0
- parrot/loaders/ppt.py +476 -0
- parrot/loaders/qa.py +63 -0
- parrot/loaders/splitters/__init__.py +10 -0
- parrot/loaders/splitters/base.py +138 -0
- parrot/loaders/splitters/md.py +228 -0
- parrot/loaders/splitters/token.py +143 -0
- parrot/loaders/txt.py +26 -0
- parrot/loaders/video.py +89 -0
- parrot/loaders/videolocal.py +218 -0
- parrot/loaders/videounderstanding.py +377 -0
- parrot/loaders/vimeo.py +167 -0
- parrot/loaders/web.py +599 -0
- parrot/loaders/youtube.py +504 -0
- parrot/manager/__init__.py +5 -0
- parrot/manager/manager.py +1030 -0
- parrot/mcp/__init__.py +28 -0
- parrot/mcp/adapter.py +105 -0
- parrot/mcp/cli.py +174 -0
- parrot/mcp/client.py +119 -0
- parrot/mcp/config.py +75 -0
- parrot/mcp/integration.py +842 -0
- parrot/mcp/oauth.py +933 -0
- parrot/mcp/server.py +225 -0
- parrot/mcp/transports/__init__.py +3 -0
- parrot/mcp/transports/base.py +279 -0
- parrot/mcp/transports/grpc_session.py +163 -0
- parrot/mcp/transports/http.py +312 -0
- parrot/mcp/transports/mcp.proto +108 -0
- parrot/mcp/transports/quic.py +1082 -0
- parrot/mcp/transports/sse.py +330 -0
- parrot/mcp/transports/stdio.py +309 -0
- parrot/mcp/transports/unix.py +395 -0
- parrot/mcp/transports/websocket.py +547 -0
- parrot/memory/__init__.py +16 -0
- parrot/memory/abstract.py +209 -0
- parrot/memory/agent.py +32 -0
- parrot/memory/cache.py +175 -0
- parrot/memory/core.py +555 -0
- parrot/memory/file.py +153 -0
- parrot/memory/mem.py +131 -0
- parrot/memory/redis.py +613 -0
- parrot/models/__init__.py +46 -0
- parrot/models/basic.py +118 -0
- parrot/models/compliance.py +208 -0
- parrot/models/crew.py +395 -0
- parrot/models/detections.py +654 -0
- parrot/models/generation.py +85 -0
- parrot/models/google.py +223 -0
- parrot/models/groq.py +23 -0
- parrot/models/openai.py +30 -0
- parrot/models/outputs.py +285 -0
- parrot/models/responses.py +938 -0
- parrot/notifications/__init__.py +743 -0
- parrot/openapi/__init__.py +3 -0
- parrot/openapi/components.yaml +641 -0
- parrot/openapi/config.py +322 -0
- parrot/outputs/__init__.py +32 -0
- parrot/outputs/formats/__init__.py +108 -0
- parrot/outputs/formats/altair.py +359 -0
- parrot/outputs/formats/application.py +122 -0
- parrot/outputs/formats/base.py +351 -0
- parrot/outputs/formats/bokeh.py +356 -0
- parrot/outputs/formats/card.py +424 -0
- parrot/outputs/formats/chart.py +436 -0
- parrot/outputs/formats/d3.py +255 -0
- parrot/outputs/formats/echarts.py +310 -0
- parrot/outputs/formats/generators/__init__.py +0 -0
- parrot/outputs/formats/generators/abstract.py +61 -0
- parrot/outputs/formats/generators/panel.py +145 -0
- parrot/outputs/formats/generators/streamlit.py +86 -0
- parrot/outputs/formats/generators/terminal.py +63 -0
- parrot/outputs/formats/holoviews.py +310 -0
- parrot/outputs/formats/html.py +147 -0
- parrot/outputs/formats/jinja2.py +46 -0
- parrot/outputs/formats/json.py +87 -0
- parrot/outputs/formats/map.py +933 -0
- parrot/outputs/formats/markdown.py +172 -0
- parrot/outputs/formats/matplotlib.py +237 -0
- parrot/outputs/formats/mixins/__init__.py +0 -0
- parrot/outputs/formats/mixins/emaps.py +855 -0
- parrot/outputs/formats/plotly.py +341 -0
- parrot/outputs/formats/seaborn.py +310 -0
- parrot/outputs/formats/table.py +397 -0
- parrot/outputs/formats/template_report.py +138 -0
- parrot/outputs/formats/yaml.py +125 -0
- parrot/outputs/formatter.py +152 -0
- parrot/outputs/templates/__init__.py +95 -0
- parrot/pipelines/__init__.py +0 -0
- parrot/pipelines/abstract.py +210 -0
- parrot/pipelines/detector.py +124 -0
- parrot/pipelines/models.py +90 -0
- parrot/pipelines/planogram.py +3002 -0
- parrot/pipelines/table.sql +97 -0
- parrot/plugins/__init__.py +106 -0
- parrot/plugins/importer.py +80 -0
- parrot/py.typed +0 -0
- parrot/registry/__init__.py +18 -0
- parrot/registry/registry.py +594 -0
- parrot/scheduler/__init__.py +1189 -0
- parrot/scheduler/models.py +60 -0
- parrot/security/__init__.py +16 -0
- parrot/security/prompt_injection.py +268 -0
- parrot/security/security_events.sql +25 -0
- parrot/services/__init__.py +1 -0
- parrot/services/mcp/__init__.py +8 -0
- parrot/services/mcp/config.py +13 -0
- parrot/services/mcp/server.py +295 -0
- parrot/services/o365_remote_auth.py +235 -0
- parrot/stores/__init__.py +7 -0
- parrot/stores/abstract.py +352 -0
- parrot/stores/arango.py +1090 -0
- parrot/stores/bigquery.py +1377 -0
- parrot/stores/cache.py +106 -0
- parrot/stores/empty.py +10 -0
- parrot/stores/faiss_store.py +1157 -0
- parrot/stores/kb/__init__.py +9 -0
- parrot/stores/kb/abstract.py +68 -0
- parrot/stores/kb/cache.py +165 -0
- parrot/stores/kb/doc.py +325 -0
- parrot/stores/kb/hierarchy.py +346 -0
- parrot/stores/kb/local.py +457 -0
- parrot/stores/kb/prompt.py +28 -0
- parrot/stores/kb/redis.py +659 -0
- parrot/stores/kb/store.py +115 -0
- parrot/stores/kb/user.py +374 -0
- parrot/stores/models.py +59 -0
- parrot/stores/pgvector.py +3 -0
- parrot/stores/postgres.py +2853 -0
- parrot/stores/utils/__init__.py +0 -0
- parrot/stores/utils/chunking.py +197 -0
- parrot/telemetry/__init__.py +3 -0
- parrot/telemetry/mixin.py +111 -0
- parrot/template/__init__.py +3 -0
- parrot/template/engine.py +259 -0
- parrot/tools/__init__.py +23 -0
- parrot/tools/abstract.py +644 -0
- parrot/tools/agent.py +363 -0
- parrot/tools/arangodbsearch.py +537 -0
- parrot/tools/arxiv_tool.py +188 -0
- parrot/tools/calculator/__init__.py +3 -0
- parrot/tools/calculator/operations/__init__.py +38 -0
- parrot/tools/calculator/operations/calculus.py +80 -0
- parrot/tools/calculator/operations/statistics.py +76 -0
- parrot/tools/calculator/tool.py +150 -0
- parrot/tools/cloudwatch.py +988 -0
- parrot/tools/codeinterpreter/__init__.py +127 -0
- parrot/tools/codeinterpreter/executor.py +371 -0
- parrot/tools/codeinterpreter/internals.py +473 -0
- parrot/tools/codeinterpreter/models.py +643 -0
- parrot/tools/codeinterpreter/prompts.py +224 -0
- parrot/tools/codeinterpreter/tool.py +664 -0
- parrot/tools/company_info/__init__.py +6 -0
- parrot/tools/company_info/tool.py +1138 -0
- parrot/tools/correlationanalysis.py +437 -0
- parrot/tools/database/abstract.py +286 -0
- parrot/tools/database/bq.py +115 -0
- parrot/tools/database/cache.py +284 -0
- parrot/tools/database/models.py +95 -0
- parrot/tools/database/pg.py +343 -0
- parrot/tools/databasequery.py +1159 -0
- parrot/tools/db.py +1800 -0
- parrot/tools/ddgo.py +370 -0
- parrot/tools/decorators.py +271 -0
- parrot/tools/dftohtml.py +282 -0
- parrot/tools/document.py +549 -0
- parrot/tools/ecs.py +819 -0
- parrot/tools/edareport.py +368 -0
- parrot/tools/elasticsearch.py +1049 -0
- parrot/tools/employees.py +462 -0
- parrot/tools/epson/__init__.py +96 -0
- parrot/tools/excel.py +683 -0
- parrot/tools/file/__init__.py +13 -0
- parrot/tools/file/abstract.py +76 -0
- parrot/tools/file/gcs.py +378 -0
- parrot/tools/file/local.py +284 -0
- parrot/tools/file/s3.py +511 -0
- parrot/tools/file/tmp.py +309 -0
- parrot/tools/file/tool.py +501 -0
- parrot/tools/file_reader.py +129 -0
- parrot/tools/flowtask/__init__.py +19 -0
- parrot/tools/flowtask/tool.py +761 -0
- parrot/tools/gittoolkit.py +508 -0
- parrot/tools/google/__init__.py +18 -0
- parrot/tools/google/base.py +169 -0
- parrot/tools/google/tools.py +1251 -0
- parrot/tools/googlelocation.py +5 -0
- parrot/tools/googleroutes.py +5 -0
- parrot/tools/googlesearch.py +5 -0
- parrot/tools/googlesitesearch.py +5 -0
- parrot/tools/googlevoice.py +2 -0
- parrot/tools/gvoice.py +695 -0
- parrot/tools/ibisworld/README.md +225 -0
- parrot/tools/ibisworld/__init__.py +11 -0
- parrot/tools/ibisworld/tool.py +366 -0
- parrot/tools/jiratoolkit.py +1718 -0
- parrot/tools/manager.py +1098 -0
- parrot/tools/math.py +152 -0
- parrot/tools/metadata.py +476 -0
- parrot/tools/msteams.py +1621 -0
- parrot/tools/msword.py +635 -0
- parrot/tools/multidb.py +580 -0
- parrot/tools/multistoresearch.py +369 -0
- parrot/tools/networkninja.py +167 -0
- parrot/tools/nextstop/__init__.py +4 -0
- parrot/tools/nextstop/base.py +286 -0
- parrot/tools/nextstop/employee.py +733 -0
- parrot/tools/nextstop/store.py +462 -0
- parrot/tools/notification.py +435 -0
- parrot/tools/o365/__init__.py +42 -0
- parrot/tools/o365/base.py +295 -0
- parrot/tools/o365/bundle.py +522 -0
- parrot/tools/o365/events.py +554 -0
- parrot/tools/o365/mail.py +992 -0
- parrot/tools/o365/onedrive.py +497 -0
- parrot/tools/o365/sharepoint.py +641 -0
- parrot/tools/openapi_toolkit.py +904 -0
- parrot/tools/openweather.py +527 -0
- parrot/tools/pdfprint.py +1001 -0
- parrot/tools/powerbi.py +518 -0
- parrot/tools/powerpoint.py +1113 -0
- parrot/tools/pricestool.py +146 -0
- parrot/tools/products/__init__.py +246 -0
- parrot/tools/prophet_tool.py +171 -0
- parrot/tools/pythonpandas.py +630 -0
- parrot/tools/pythonrepl.py +910 -0
- parrot/tools/qsource.py +436 -0
- parrot/tools/querytoolkit.py +395 -0
- parrot/tools/quickeda.py +827 -0
- parrot/tools/resttool.py +553 -0
- parrot/tools/retail/__init__.py +0 -0
- parrot/tools/retail/bby.py +528 -0
- parrot/tools/sandboxtool.py +703 -0
- parrot/tools/sassie/__init__.py +352 -0
- parrot/tools/scraping/__init__.py +7 -0
- parrot/tools/scraping/docs/select.md +466 -0
- parrot/tools/scraping/documentation.md +1278 -0
- parrot/tools/scraping/driver.py +436 -0
- parrot/tools/scraping/models.py +576 -0
- parrot/tools/scraping/options.py +85 -0
- parrot/tools/scraping/orchestrator.py +517 -0
- parrot/tools/scraping/readme.md +740 -0
- parrot/tools/scraping/tool.py +3115 -0
- parrot/tools/seasonaldetection.py +642 -0
- parrot/tools/shell_tool/__init__.py +5 -0
- parrot/tools/shell_tool/actions.py +408 -0
- parrot/tools/shell_tool/engine.py +155 -0
- parrot/tools/shell_tool/models.py +322 -0
- parrot/tools/shell_tool/tool.py +442 -0
- parrot/tools/site_search.py +214 -0
- parrot/tools/textfile.py +418 -0
- parrot/tools/think.py +378 -0
- parrot/tools/toolkit.py +298 -0
- parrot/tools/webapp_tool.py +187 -0
- parrot/tools/whatif.py +1279 -0
- parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
- parrot/tools/workday/__init__.py +6 -0
- parrot/tools/workday/models.py +1389 -0
- parrot/tools/workday/tool.py +1293 -0
- parrot/tools/yfinance_tool.py +306 -0
- parrot/tools/zipcode.py +217 -0
- parrot/utils/__init__.py +2 -0
- parrot/utils/helpers.py +73 -0
- parrot/utils/parsers/__init__.py +5 -0
- parrot/utils/parsers/toml.c +12078 -0
- parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/parsers/toml.pyx +21 -0
- parrot/utils/toml.py +11 -0
- parrot/utils/types.cpp +20936 -0
- parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
- parrot/utils/types.pyx +213 -0
- parrot/utils/uv.py +11 -0
- parrot/version.py +10 -0
- parrot/yaml-rs/Cargo.lock +350 -0
- parrot/yaml-rs/Cargo.toml +19 -0
- parrot/yaml-rs/pyproject.toml +19 -0
- parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
- parrot/yaml-rs/src/lib.rs +222 -0
- requirements/docker-compose.yml +24 -0
- requirements/requirements-dev.txt +21 -0
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PythonREPLTool migrated to use AbstractTool framework with matplotlib fixes.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Optional, Dict, Any, Union
|
|
5
|
+
import ast
|
|
6
|
+
import sys
|
|
7
|
+
import asyncio
|
|
8
|
+
import threading
|
|
9
|
+
import contextlib
|
|
10
|
+
import base64
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
logging.getLogger(name='matplotlib').setLevel(logging.INFO)
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from contextlib import redirect_stdout
|
|
17
|
+
from io import StringIO, BytesIO
|
|
18
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
19
|
+
import pandas as pd
|
|
20
|
+
import numpy as np
|
|
21
|
+
import matplotlib
|
|
22
|
+
# Force matplotlib to use non-interactive backend
|
|
23
|
+
matplotlib.use('Agg')
|
|
24
|
+
import matplotlib.pyplot as plt
|
|
25
|
+
# Import these for proper cleanup handling
|
|
26
|
+
from matplotlib import _pylab_helpers
|
|
27
|
+
|
|
28
|
+
from pydantic import BaseModel, Field
|
|
29
|
+
from datamodel.parsers.json import json_decoder, json_encoder # noqa pylint: disable=E0611
|
|
30
|
+
from navconfig import BASE_DIR
|
|
31
|
+
from .abstract import AbstractTool
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def brace_escape(text: str) -> str:
|
|
35
|
+
"""Escape curly braces in text for format strings."""
|
|
36
|
+
return text.replace('{', '{{').replace('}', '}}')
|
|
37
|
+
|
|
38
|
+
def sanitize_input(query: str) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Sanitize input to the python REPL.
|
|
41
|
+
Remove whitespace, backtick & python (if llm mistakes python console as terminal)
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
query: The query to sanitize
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The sanitized query
|
|
48
|
+
"""
|
|
49
|
+
query = query.strip()
|
|
50
|
+
|
|
51
|
+
# Handle code blocks
|
|
52
|
+
if query.startswith("```python"):
|
|
53
|
+
query = query[9:]
|
|
54
|
+
elif query.startswith("```"):
|
|
55
|
+
query = query[3:]
|
|
56
|
+
|
|
57
|
+
if query.endswith("```"):
|
|
58
|
+
query = query[:-3]
|
|
59
|
+
|
|
60
|
+
# Clean up any remaining leading/trailing whitespace
|
|
61
|
+
query = query.strip()
|
|
62
|
+
|
|
63
|
+
# Handle common formatting issues
|
|
64
|
+
lines = query.split('\n')
|
|
65
|
+
# Remove empty lines at start and end
|
|
66
|
+
while lines and not lines[0].strip():
|
|
67
|
+
lines.pop(0)
|
|
68
|
+
while lines and not lines[-1].strip():
|
|
69
|
+
lines.pop()
|
|
70
|
+
|
|
71
|
+
return '\n'.join(lines)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PythonREPLArgs(BaseModel):
|
|
75
|
+
"""Arguments schema for PythonREPLTool."""
|
|
76
|
+
code: str = Field(
|
|
77
|
+
description="Python code to execute in the REPL environment"
|
|
78
|
+
)
|
|
79
|
+
debug: bool = False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class PythonREPLTool(AbstractTool):
|
|
83
|
+
"""
|
|
84
|
+
Python REPL Tool with pre-loaded data science libraries and enhanced capabilities.
|
|
85
|
+
|
|
86
|
+
Features:
|
|
87
|
+
- Pre-loaded libraries: pandas (pd), numpy (np), matplotlib.pyplot (plt), seaborn (sns), numexpr (ne)
|
|
88
|
+
- Helper functions from parrot.bots.tools under `parrot_tools`
|
|
89
|
+
- An `execution_results` dict for capturing intermediate results
|
|
90
|
+
- A `report_directory` Path for saving outputs
|
|
91
|
+
- Extended JSON encoder/decoder based on orjson (`extended_json`)
|
|
92
|
+
- Async execution support
|
|
93
|
+
- Error handling and sanitization
|
|
94
|
+
- Non-interactive matplotlib backend to avoid GUI issues
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
name = "python_repl"
|
|
98
|
+
description = "Execute Python code with pre-loaded data science libraries (pandas, numpy, matplotlib, seaborn)"
|
|
99
|
+
args_schema = PythonREPLArgs
|
|
100
|
+
|
|
101
|
+
# Class variable to track if environment has been bootstrapped
|
|
102
|
+
_bootstrapped = False
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
locals_dict: Optional[Dict] = None,
|
|
107
|
+
globals_dict: Optional[Dict] = None,
|
|
108
|
+
report_dir: Optional[Path] = None,
|
|
109
|
+
plt_style: str = 'seaborn-v0_8-whitegrid',
|
|
110
|
+
palette: str = 'Set2',
|
|
111
|
+
setup_code: Optional[str] = None,
|
|
112
|
+
sanitize_input_enabled: bool = True,
|
|
113
|
+
auto_save_plots: bool = True,
|
|
114
|
+
return_plot_as_base64: bool = False,
|
|
115
|
+
debug: bool = False,
|
|
116
|
+
**kwargs
|
|
117
|
+
):
|
|
118
|
+
"""
|
|
119
|
+
Initialize the Python REPL tool.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
locals_dict: Local variables for the REPL
|
|
123
|
+
globals_dict: Global variables for the REPL
|
|
124
|
+
report_dir: Directory for saving reports
|
|
125
|
+
plt_style: Matplotlib style
|
|
126
|
+
palette: Seaborn color palette
|
|
127
|
+
setup_code: Custom setup code to run
|
|
128
|
+
sanitize_input_enabled: Whether to sanitize input
|
|
129
|
+
auto_save_plots: Whether to automatically save plots to files
|
|
130
|
+
return_plot_as_base64: Whether to return plots as base64 strings
|
|
131
|
+
**kwargs: Additional arguments for AbstractTool
|
|
132
|
+
"""
|
|
133
|
+
# Check Python version
|
|
134
|
+
if sys.version_info < (3, 9):
|
|
135
|
+
raise ValueError(
|
|
136
|
+
"This tool requires Python 3.9 or higher "
|
|
137
|
+
f"(you have Python version: {sys.version})"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Set default output directory for reports
|
|
141
|
+
if not report_dir:
|
|
142
|
+
report_dir = BASE_DIR.joinpath('static', 'reports')
|
|
143
|
+
|
|
144
|
+
# Initialize parent class
|
|
145
|
+
super().__init__(output_dir=report_dir, **kwargs)
|
|
146
|
+
|
|
147
|
+
# Configuration
|
|
148
|
+
self.sanitize_input_enabled = sanitize_input_enabled
|
|
149
|
+
self.plt_style = plt_style
|
|
150
|
+
self.palette = palette
|
|
151
|
+
self.setup_code = setup_code or self._get_default_setup_code()
|
|
152
|
+
self.auto_save_plots = auto_save_plots
|
|
153
|
+
self.return_plot_as_base64 = return_plot_as_base64
|
|
154
|
+
# Add a process pool executor
|
|
155
|
+
self.executor = ProcessPoolExecutor(max_workers=4)
|
|
156
|
+
|
|
157
|
+
# Initialize execution environment
|
|
158
|
+
self.locals = locals_dict or {}
|
|
159
|
+
self.globals = globals_dict or {}
|
|
160
|
+
|
|
161
|
+
# Setup matplotlib to use non-interactive backend
|
|
162
|
+
self._setup_charts()
|
|
163
|
+
|
|
164
|
+
# Setup the environment
|
|
165
|
+
self._setup_environment()
|
|
166
|
+
|
|
167
|
+
# Debug:
|
|
168
|
+
self.debug = debug
|
|
169
|
+
|
|
170
|
+
# Bootstrap the environment if not already done
|
|
171
|
+
self._bootstrap()
|
|
172
|
+
|
|
173
|
+
def _setup_charts(self):
|
|
174
|
+
"""Configure matplotlib, Altair, and Bokeh for non-interactive use."""
|
|
175
|
+
# Bokeh configuration:
|
|
176
|
+
|
|
177
|
+
# Store the original backend
|
|
178
|
+
original_backend = matplotlib.get_backend()
|
|
179
|
+
with contextlib.suppress(Exception):
|
|
180
|
+
# Force non-interactive backend
|
|
181
|
+
matplotlib.use('Agg', force=True)
|
|
182
|
+
|
|
183
|
+
# Configure matplotlib to not try to show plots
|
|
184
|
+
plt.ioff() # Turn off interactive mode
|
|
185
|
+
|
|
186
|
+
# Clear any existing figures safely
|
|
187
|
+
self._safe_close_all_plots()
|
|
188
|
+
|
|
189
|
+
# Clear any existing figures
|
|
190
|
+
plt.close('all')
|
|
191
|
+
|
|
192
|
+
self.logger.info(f"Matplotlib backend set to: {matplotlib.get_backend()}")
|
|
193
|
+
|
|
194
|
+
def _safe_close_all_plots(self):
|
|
195
|
+
"""Safely close all matplotlib plots without GUI errors."""
|
|
196
|
+
try:
|
|
197
|
+
# Get all figure managers
|
|
198
|
+
fignums = list(plt.get_fignums())
|
|
199
|
+
for fignum in fignums:
|
|
200
|
+
try:
|
|
201
|
+
plt.close(fignum)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
self.logger.debug(f"Error closing figure {fignum}: {e}")
|
|
204
|
+
|
|
205
|
+
# Force garbage collection of any remaining figures
|
|
206
|
+
plt.close('all')
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
self.logger.debug(f"Error in safe_close_all_plots: {e}")
|
|
210
|
+
|
|
211
|
+
def _safe_matplotlib_cleanup(self):
|
|
212
|
+
"""Safe cleanup of matplotlib figures that won't crash."""
|
|
213
|
+
try:
|
|
214
|
+
# Only cleanup if we're in the main thread
|
|
215
|
+
if threading.current_thread() is threading.main_thread():
|
|
216
|
+
self._safe_close_all_plots()
|
|
217
|
+
|
|
218
|
+
# Clear the figure manager registry safely
|
|
219
|
+
if hasattr(_pylab_helpers, 'Gcf'):
|
|
220
|
+
try:
|
|
221
|
+
_pylab_helpers.Gcf.figs.clear()
|
|
222
|
+
except Exception as e:
|
|
223
|
+
self.logger.debug(f"Error clearing Gcf registry: {e}")
|
|
224
|
+
|
|
225
|
+
except Exception as e:
|
|
226
|
+
# Never let cleanup crash the program
|
|
227
|
+
self.logger.debug(f"Error in safe matplotlib cleanup: {e}")
|
|
228
|
+
|
|
229
|
+
def _execute_function(self, func, *args, **kwargs):
|
|
230
|
+
"""Execute a function with proper error isolation to prevent crashes."""
|
|
231
|
+
try:
|
|
232
|
+
return func(*args, **kwargs)
|
|
233
|
+
except (SystemExit, KeyboardInterrupt):
|
|
234
|
+
# Don't catch KeyboardInterrupt as users should be able to interrupt
|
|
235
|
+
raise
|
|
236
|
+
except Exception as e:
|
|
237
|
+
# Log the error but don't let it crash the system
|
|
238
|
+
self.logger.error(f"Error in {func.__name__}: {e}", exc_info=True)
|
|
239
|
+
return f"Error: {type(e).__name__}: {str(e)}"
|
|
240
|
+
|
|
241
|
+
def _default_output_dir(self) -> Path:
|
|
242
|
+
"""Get the default output directory for Python REPL outputs."""
|
|
243
|
+
return self.static_dir / "reports" / "python_repl"
|
|
244
|
+
|
|
245
|
+
def _setup_environment(self) -> None:
|
|
246
|
+
"""Set up the Python environment with libraries and tools."""
|
|
247
|
+
# Ensure output directory exists
|
|
248
|
+
if not self.output_dir.exists():
|
|
249
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
250
|
+
|
|
251
|
+
# Lazy load heavy dependencies
|
|
252
|
+
try:
|
|
253
|
+
import altair
|
|
254
|
+
import plotly.express as px
|
|
255
|
+
import plotly.graph_objects as go
|
|
256
|
+
import plotly.io as pio
|
|
257
|
+
import numexpr as ne
|
|
258
|
+
import seaborn as sns
|
|
259
|
+
import bokeh
|
|
260
|
+
import holoviews as hv
|
|
261
|
+
import folium
|
|
262
|
+
from holoviews import opts
|
|
263
|
+
|
|
264
|
+
# Configure holoviews
|
|
265
|
+
hv.extension('bokeh')
|
|
266
|
+
except ImportError as e:
|
|
267
|
+
self.logger.warning(f"Could not import some data science libraries: {e}")
|
|
268
|
+
# Define dummies or handle gracefully if desired
|
|
269
|
+
# For now, we'll just let them be missing from locals if import failed
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# Helper functions for plot handling
|
|
274
|
+
def save_current_plot(
|
|
275
|
+
filename: Optional[str] = None,
|
|
276
|
+
format: str = 'png',
|
|
277
|
+
dpi: int = 300,
|
|
278
|
+
bbox_inches: str = 'tight'
|
|
279
|
+
) -> Dict[str, Any]:
|
|
280
|
+
"""Save the current matplotlib plot to a file."""
|
|
281
|
+
if not filename:
|
|
282
|
+
filename = self.generate_filename("plot", f".{format}")
|
|
283
|
+
|
|
284
|
+
file_path = self.output_dir / filename
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
plt.savefig(file_path, format=format, dpi=dpi, bbox_inches=bbox_inches)
|
|
288
|
+
file_url = self.to_static_url(file_path)
|
|
289
|
+
|
|
290
|
+
result = {
|
|
291
|
+
"filename": filename,
|
|
292
|
+
"file_path": str(file_path),
|
|
293
|
+
"file_url": file_url,
|
|
294
|
+
"format": format,
|
|
295
|
+
"dpi": dpi
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
# Optionally add base64 representation
|
|
299
|
+
if self.return_plot_as_base64:
|
|
300
|
+
with open(file_path, 'rb') as f:
|
|
301
|
+
encoded_string = base64.b64encode(f.read()).decode('utf-8')
|
|
302
|
+
result["base64"] = f"data:image/{format};base64,{encoded_string}"
|
|
303
|
+
|
|
304
|
+
return result
|
|
305
|
+
|
|
306
|
+
except Exception as e:
|
|
307
|
+
self.logger.error(f"Error saving plot: {e}")
|
|
308
|
+
return {"error": str(e)}
|
|
309
|
+
|
|
310
|
+
def get_plot_as_base64(format: str = 'png', dpi: int = 300) -> str:
|
|
311
|
+
"""Get the current matplotlib plot as a base64 string."""
|
|
312
|
+
try:
|
|
313
|
+
buffer = BytesIO()
|
|
314
|
+
plt.savefig(buffer, format=format, dpi=dpi, bbox_inches='tight')
|
|
315
|
+
buffer.seek(0)
|
|
316
|
+
encoded_string = base64.b64encode(buffer.read()).decode('utf-8')
|
|
317
|
+
buffer.close()
|
|
318
|
+
return f"data:image/{format};base64,{encoded_string}"
|
|
319
|
+
except Exception as e:
|
|
320
|
+
self.logger.error(f"Error getting plot as base64: {e}")
|
|
321
|
+
return f"Error: {str(e)}"
|
|
322
|
+
|
|
323
|
+
def clear_plots():
|
|
324
|
+
"""Clear all matplotlib plots."""
|
|
325
|
+
try:
|
|
326
|
+
self._safe_close_all_plots()
|
|
327
|
+
return "All plots cleared"
|
|
328
|
+
except Exception as e:
|
|
329
|
+
self.logger.error(f"Error clearing plots: {e}")
|
|
330
|
+
return f"Error clearing plots: {str(e)}"
|
|
331
|
+
|
|
332
|
+
# Update locals with essential libraries and tools
|
|
333
|
+
self.locals.update({
|
|
334
|
+
# Core data science libraries
|
|
335
|
+
'pd': pd,
|
|
336
|
+
'np': np,
|
|
337
|
+
'plt': plt,
|
|
338
|
+
'matplotlib': matplotlib,
|
|
339
|
+
'altair': altair,
|
|
340
|
+
'px': px,
|
|
341
|
+
'go': go,
|
|
342
|
+
'pio': pio,
|
|
343
|
+
'numexpr': ne,
|
|
344
|
+
'sns': sns,
|
|
345
|
+
'bokeh': bokeh,
|
|
346
|
+
'hv': hv,
|
|
347
|
+
'opts': opts,
|
|
348
|
+
'folium': folium,
|
|
349
|
+
|
|
350
|
+
# JSON utilities
|
|
351
|
+
'json_encoder': json_encoder,
|
|
352
|
+
'json_decoder': json_decoder,
|
|
353
|
+
'extended_json': {
|
|
354
|
+
'dumps': json_encoder,
|
|
355
|
+
'loads': json_decoder,
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
# Directory and results management
|
|
359
|
+
'report_directory': self.output_dir,
|
|
360
|
+
'execution_results': {},
|
|
361
|
+
|
|
362
|
+
# Plot utilities
|
|
363
|
+
'save_current_plot': save_current_plot,
|
|
364
|
+
'get_plot_as_base64': get_plot_as_base64,
|
|
365
|
+
'clear_plots': clear_plots,
|
|
366
|
+
'execute_safely': lambda code: self.execute_code_safely(code),
|
|
367
|
+
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
# Mirror locals into globals so user code can see everything
|
|
371
|
+
self.globals.update(self.locals)
|
|
372
|
+
|
|
373
|
+
self.logger.info(
|
|
374
|
+
f"Python REPL environment setup complete. Output dir: {self.output_dir}"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def _get_default_setup_code(self) -> str:
|
|
378
|
+
"""Get the default setup code."""
|
|
379
|
+
return f"""
|
|
380
|
+
# Python REPL Environment Setup
|
|
381
|
+
import warnings
|
|
382
|
+
warnings.filterwarnings('ignore')
|
|
383
|
+
|
|
384
|
+
# Ensure matplotlib uses non-interactive backend
|
|
385
|
+
import matplotlib
|
|
386
|
+
matplotlib.use('Agg', force=True)
|
|
387
|
+
plt.ioff() # Turn off interactive mode
|
|
388
|
+
|
|
389
|
+
# Ensure essential libraries are imported
|
|
390
|
+
try:
|
|
391
|
+
# Uncomment when parrot.bots.tools is available
|
|
392
|
+
# from parrot.bots.tools import (
|
|
393
|
+
# quick_eda,
|
|
394
|
+
# generate_eda_report,
|
|
395
|
+
# list_available_dataframes,
|
|
396
|
+
# create_plot,
|
|
397
|
+
# generate_pdf_from_html
|
|
398
|
+
# )
|
|
399
|
+
pass
|
|
400
|
+
except ImportError as e:
|
|
401
|
+
print(f"Note: Some parrot tools not available: {{e}}")
|
|
402
|
+
|
|
403
|
+
print(f"🐍 Python REPL Environment Ready!")
|
|
404
|
+
print(f"📊 Pandas version: {{pd.__version__}}")
|
|
405
|
+
print(f"🔢 NumPy version: {{np.__version__}}")
|
|
406
|
+
print(f"📈 Matplotlib version: {{matplotlib.__version__}} (backend: {{matplotlib.get_backend()}})")
|
|
407
|
+
print(f"🎨 Seaborn version: {{sns.__version__}}")
|
|
408
|
+
print(f"📁 Report directory: {{report_directory}}")
|
|
409
|
+
print("🖼️ Plot utilities: save_current_plot(), get_plot_as_base64(), clear_plots()")
|
|
410
|
+
print("Use 'execution_results' dict to store intermediate results.")
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
def _bootstrap(self) -> None:
|
|
414
|
+
"""Bootstrap the REPL environment."""
|
|
415
|
+
if PythonREPLTool._bootstrapped:
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
self.logger.info("Running REPL bootstrap code...")
|
|
419
|
+
try:
|
|
420
|
+
result = self._execute_code(self.setup_code)
|
|
421
|
+
if result.strip():
|
|
422
|
+
self.logger.info(f"Bootstrap output: {result}")
|
|
423
|
+
except Exception as e:
|
|
424
|
+
self.logger.error("Error during REPL bootstrap", exc_info=e)
|
|
425
|
+
|
|
426
|
+
try:
|
|
427
|
+
plt.style.use(self.plt_style)
|
|
428
|
+
if 'sns' in self.locals:
|
|
429
|
+
self.locals['sns'].set_palette(self.palette)
|
|
430
|
+
|
|
431
|
+
except Exception as e:
|
|
432
|
+
self.logger.error("Error setting plot style", exc_info=e)
|
|
433
|
+
|
|
434
|
+
PythonREPLTool._bootstrapped = True
|
|
435
|
+
|
|
436
|
+
def _auto_save_plots_if_enabled(self) -> Optional[Dict[str, Any]]:
|
|
437
|
+
"""Automatically save plots if auto_save_plots is enabled and there are open figures."""
|
|
438
|
+
if not self.auto_save_plots:
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
# Check if there are any open figures
|
|
442
|
+
if len(plt.get_fignums()) == 0:
|
|
443
|
+
return None
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
# Save the current plot
|
|
447
|
+
if (save_func := self.locals.get('save_current_plot')):
|
|
448
|
+
result = save_func()
|
|
449
|
+
# Clear the plot after saving to prevent memory issues
|
|
450
|
+
plt.close('all')
|
|
451
|
+
return result
|
|
452
|
+
except Exception as e:
|
|
453
|
+
self.logger.error(f"Error auto-saving plot: {e}")
|
|
454
|
+
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
def _serialize_execution_results(self, results: Dict[str, Any]) -> Dict[str, Any]:
|
|
458
|
+
"""
|
|
459
|
+
Serialize execution results to make them JSON-compatible.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
results: Dictionary of execution results
|
|
463
|
+
|
|
464
|
+
Returns:
|
|
465
|
+
JSON-serializable dictionary
|
|
466
|
+
"""
|
|
467
|
+
serializable = {}
|
|
468
|
+
|
|
469
|
+
for key, value in results.items():
|
|
470
|
+
# Ensure key is a string
|
|
471
|
+
str_key = str(key)
|
|
472
|
+
|
|
473
|
+
try:
|
|
474
|
+
# Try to serialize different types of objects
|
|
475
|
+
if isinstance(value, pd.DataFrame):
|
|
476
|
+
serializable[str_key] = {
|
|
477
|
+
"_type": "pandas.DataFrame",
|
|
478
|
+
"data": value.to_dict(orient='records'),
|
|
479
|
+
"columns": list(value.columns),
|
|
480
|
+
"index": list(value.index),
|
|
481
|
+
"shape": value.shape,
|
|
482
|
+
"dtypes": {col: str(dtype) for col, dtype in value.dtypes.items()}
|
|
483
|
+
}
|
|
484
|
+
elif isinstance(value, pd.Series):
|
|
485
|
+
serializable[str_key] = {
|
|
486
|
+
"_type": "pandas.Series",
|
|
487
|
+
"data": value.to_dict(),
|
|
488
|
+
"name": value.name,
|
|
489
|
+
"dtype": str(value.dtype),
|
|
490
|
+
"shape": value.shape
|
|
491
|
+
}
|
|
492
|
+
elif isinstance(value, np.ndarray):
|
|
493
|
+
serializable[str_key] = {
|
|
494
|
+
"_type": "numpy.ndarray",
|
|
495
|
+
"data": value.tolist(),
|
|
496
|
+
"shape": value.shape,
|
|
497
|
+
"dtype": str(value.dtype)
|
|
498
|
+
}
|
|
499
|
+
elif hasattr(value, '__dict__') and not callable(value):
|
|
500
|
+
# For custom objects, try to serialize their __dict__
|
|
501
|
+
serializable[str_key] = {
|
|
502
|
+
"_type": f"{value.__class__.__module__}.{value.__class__.__name__}",
|
|
503
|
+
"data": str(value), # fallback to string representation
|
|
504
|
+
"attributes": {k: str(v) for k, v in value.__dict__.items() if not k.startswith('_')}
|
|
505
|
+
}
|
|
506
|
+
elif callable(value):
|
|
507
|
+
# For functions or callable objects
|
|
508
|
+
serializable[str_key] = {
|
|
509
|
+
"_type": "callable",
|
|
510
|
+
"name": getattr(value, '__name__', str(value)),
|
|
511
|
+
"data": str(value)
|
|
512
|
+
}
|
|
513
|
+
else:
|
|
514
|
+
# Try direct serialization for basic types
|
|
515
|
+
# Test if it's JSON serializable
|
|
516
|
+
json_encoder(value)
|
|
517
|
+
serializable[str_key] = value
|
|
518
|
+
|
|
519
|
+
except Exception as e:
|
|
520
|
+
# If all else fails, store as string representation
|
|
521
|
+
self.logger.warning(f"Could not serialize execution result '{str_key}': {e}")
|
|
522
|
+
serializable[str_key] = {
|
|
523
|
+
"_type": "string_representation",
|
|
524
|
+
"data": str(value),
|
|
525
|
+
"original_type": str(type(value)),
|
|
526
|
+
"serialization_error": str(e)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return serializable
|
|
530
|
+
|
|
531
|
+
def _execute_code(self, query: str, debug: bool = False) -> str:
|
|
532
|
+
"""Execute Python code and return the result."""
|
|
533
|
+
try:
|
|
534
|
+
if self.sanitize_input_enabled:
|
|
535
|
+
query = sanitize_input(query)
|
|
536
|
+
|
|
537
|
+
# capture previous state of locals
|
|
538
|
+
pre_exec_keys = set(self.locals.keys())
|
|
539
|
+
|
|
540
|
+
if debug:
|
|
541
|
+
print(f"DEBUG: Executing code:\n{repr(query)}\n" + "="*50)
|
|
542
|
+
|
|
543
|
+
if not query.strip():
|
|
544
|
+
return ""
|
|
545
|
+
|
|
546
|
+
# Parse the query
|
|
547
|
+
try:
|
|
548
|
+
tree = ast.parse(query)
|
|
549
|
+
except SyntaxError as e:
|
|
550
|
+
if debug:
|
|
551
|
+
print(f"DEBUG: SyntaxError details: {e}")
|
|
552
|
+
print(f"DEBUG: Query lines: {query.split(chr(10))}")
|
|
553
|
+
return f"SyntaxError: {str(e)}"
|
|
554
|
+
|
|
555
|
+
# If empty, return
|
|
556
|
+
if not tree.body:
|
|
557
|
+
return ""
|
|
558
|
+
|
|
559
|
+
# ✅ CREATE BUFFER - before any execution
|
|
560
|
+
io_buffer = StringIO()
|
|
561
|
+
|
|
562
|
+
with redirect_stdout(io_buffer):
|
|
563
|
+
# Execute all but the last statement
|
|
564
|
+
if len(tree.body) > 1:
|
|
565
|
+
try:
|
|
566
|
+
module = ast.Module(tree.body[:-1], type_ignores=[])
|
|
567
|
+
exec(ast.unparse(module), self.globals, self.locals)
|
|
568
|
+
except Exception as e:
|
|
569
|
+
return f"ExecutionError: {type(e).__name__}: {str(e)}"
|
|
570
|
+
|
|
571
|
+
# Handle the last statement
|
|
572
|
+
last_statement = tree.body[-1]
|
|
573
|
+
module_end = ast.Module([last_statement], type_ignores=[])
|
|
574
|
+
module_end_str = ast.unparse(module_end)
|
|
575
|
+
|
|
576
|
+
# Check if it's an expression that can be evaluated
|
|
577
|
+
if is_expression := isinstance(last_statement, ast.Expr):
|
|
578
|
+
with contextlib.suppress(Exception):
|
|
579
|
+
# Try to evaluate as expression first
|
|
580
|
+
ret = eval(module_end_str, self.globals, self.locals)
|
|
581
|
+
output = io_buffer.getvalue()
|
|
582
|
+
|
|
583
|
+
# Auto-save plots if enabled
|
|
584
|
+
plot_info = self._auto_save_plots_if_enabled()
|
|
585
|
+
if plot_info and not plot_info.get('error'):
|
|
586
|
+
plot_msg = f"\n[Plot saved: {plot_info.get('filename', 'unknown')}]"
|
|
587
|
+
output += plot_msg
|
|
588
|
+
|
|
589
|
+
if ret is None:
|
|
590
|
+
return output
|
|
591
|
+
else:
|
|
592
|
+
return output + str(ret) if output else str(ret)
|
|
593
|
+
|
|
594
|
+
try:
|
|
595
|
+
# Try to evaluate as expression first
|
|
596
|
+
ret = eval(module_end_str, self.globals, self.locals)
|
|
597
|
+
|
|
598
|
+
# Auto-save plots if enabled
|
|
599
|
+
plot_info = self._auto_save_plots_if_enabled()
|
|
600
|
+
if plot_info and not plot_info.get('error'):
|
|
601
|
+
plot_msg = f"\n[Plot saved: {plot_info.get('filename', 'unknown')}]"
|
|
602
|
+
io_buffer.write(plot_msg)
|
|
603
|
+
|
|
604
|
+
if ret is None:
|
|
605
|
+
return io_buffer.getvalue()
|
|
606
|
+
else:
|
|
607
|
+
output = io_buffer.getvalue()
|
|
608
|
+
return output + str(ret) if output else str(ret)
|
|
609
|
+
except Exception:
|
|
610
|
+
# Fall back to execution
|
|
611
|
+
try:
|
|
612
|
+
exec(module_end_str, self.globals, self.locals)
|
|
613
|
+
|
|
614
|
+
# Auto-save plots if enabled
|
|
615
|
+
plot_info = self._auto_save_plots_if_enabled()
|
|
616
|
+
if plot_info and not plot_info.get('error'):
|
|
617
|
+
plot_msg = f"\n[Plot saved: {plot_info.get('filename', 'unknown')}]"
|
|
618
|
+
io_buffer.write(plot_msg)
|
|
619
|
+
|
|
620
|
+
return io_buffer.getvalue()
|
|
621
|
+
except Exception as e:
|
|
622
|
+
return f"ExecutionError: {type(e).__name__}: {str(e)}"
|
|
623
|
+
|
|
624
|
+
# Return everything that was captured
|
|
625
|
+
output = io_buffer.getvalue() or ""
|
|
626
|
+
post_exec_keys = set(self.locals.keys())
|
|
627
|
+
|
|
628
|
+
if new_vars := post_exec_keys - pre_exec_keys:
|
|
629
|
+
context_report = []
|
|
630
|
+
# generate new report context:
|
|
631
|
+
for var_name in new_vars:
|
|
632
|
+
val = self.locals[var_name]
|
|
633
|
+
if "pandas" in str(type(val)) and hasattr(val, "shape"):
|
|
634
|
+
context_report.append(
|
|
635
|
+
f"🆕 DataFrame Created: '{var_name}' | Shape: {val.shape} | Columns: {list(val.columns)}"
|
|
636
|
+
)
|
|
637
|
+
elif not var_name.startswith("_"):
|
|
638
|
+
context_report.append(
|
|
639
|
+
f"🆕 Variable Created: '{var_name}' | Type: {type(val).__name__}"
|
|
640
|
+
)
|
|
641
|
+
else:
|
|
642
|
+
context_report.append(
|
|
643
|
+
f"🆕 Variable Created: '{var_name}' | Type: {type(val).__name__} (private)"
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
return output + "\n".join(context_report)
|
|
647
|
+
return output
|
|
648
|
+
|
|
649
|
+
except Exception as e:
|
|
650
|
+
return f"{type(e).__name__}: {str(e)}"
|
|
651
|
+
|
|
652
|
+
async def _execute(self, code: str, debug: bool = False, **kwargs) -> Dict[str, Any]:
|
|
653
|
+
"""
|
|
654
|
+
Execute Python code asynchronously (AbstractTool interface).
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
code: Python code to execute
|
|
658
|
+
debug: Enable debug mode
|
|
659
|
+
**kwargs: Additional arguments
|
|
660
|
+
|
|
661
|
+
Returns:
|
|
662
|
+
Dictionary with execution results
|
|
663
|
+
"""
|
|
664
|
+
try:
|
|
665
|
+
self.logger.info(f"Executing Python code: {code[:100]}...")
|
|
666
|
+
|
|
667
|
+
# Execute the code in a thread to avoid blocking
|
|
668
|
+
loop = asyncio.get_event_loop()
|
|
669
|
+
return await loop.run_in_executor(
|
|
670
|
+
None,
|
|
671
|
+
self._execute_code,
|
|
672
|
+
code,
|
|
673
|
+
debug
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
except Exception as e:
|
|
677
|
+
self.logger.error(f"Error executing Python code: {e}")
|
|
678
|
+
return {
|
|
679
|
+
"output": f"ToolError: {type(e).__name__}: {str(e)}",
|
|
680
|
+
"code_executed": code,
|
|
681
|
+
"debug_mode": debug,
|
|
682
|
+
"execution_successful": False,
|
|
683
|
+
"error_details": str(e)
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
def execute_sync(self, code: str, debug: bool = False) -> str:
|
|
687
|
+
"""
|
|
688
|
+
Execute Python code synchronously.
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
code: Python code to execute
|
|
692
|
+
debug: Enable debug mode
|
|
693
|
+
|
|
694
|
+
Returns:
|
|
695
|
+
Execution result as string
|
|
696
|
+
"""
|
|
697
|
+
return self._execute_code(code, debug)
|
|
698
|
+
|
|
699
|
+
def get_environment_info(self) -> Dict[str, Any]:
|
|
700
|
+
"""Get information about the current REPL environment."""
|
|
701
|
+
return {
|
|
702
|
+
"python_version": sys.version,
|
|
703
|
+
"pandas_version": pd.__version__,
|
|
704
|
+
"numpy_version": np.__version__,
|
|
705
|
+
"matplotlib_version": matplotlib.__version__,
|
|
706
|
+
"matplotlib_backend": matplotlib.get_backend(),
|
|
707
|
+
"seaborn_version": sns.__version__,
|
|
708
|
+
"output_directory": str(self.output_dir),
|
|
709
|
+
"locals_count": len(self.locals),
|
|
710
|
+
"globals_count": len(self.globals),
|
|
711
|
+
"execution_results_keys": list(self.locals.get('execution_results', {}).keys()),
|
|
712
|
+
"open_figures": len(plt.get_fignums()),
|
|
713
|
+
"bootstrapped": self._bootstrapped,
|
|
714
|
+
"plot_style": self.plt_style,
|
|
715
|
+
"color_palette": self.palette,
|
|
716
|
+
"auto_save_plots": self.auto_save_plots,
|
|
717
|
+
"return_plot_as_base64": self.return_plot_as_base64
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
def reset_environment(self) -> None:
|
|
721
|
+
"""Reset the REPL environment to its initial state."""
|
|
722
|
+
self.logger.info("Resetting Python REPL environment...")
|
|
723
|
+
|
|
724
|
+
# Clear all plots first
|
|
725
|
+
plt.close('all')
|
|
726
|
+
|
|
727
|
+
# Clear execution results
|
|
728
|
+
if 'execution_results' in self.locals:
|
|
729
|
+
self.locals['execution_results'].clear()
|
|
730
|
+
|
|
731
|
+
# Re-setup matplotlib
|
|
732
|
+
self._setup_charts()
|
|
733
|
+
|
|
734
|
+
# Re-setup the environment
|
|
735
|
+
self._setup_environment()
|
|
736
|
+
|
|
737
|
+
# Re-bootstrap
|
|
738
|
+
PythonREPLTool._bootstrapped = False
|
|
739
|
+
self._bootstrap()
|
|
740
|
+
|
|
741
|
+
self.logger.info("Python REPL environment reset complete")
|
|
742
|
+
|
|
743
|
+
def save_execution_results(self, filename: Optional[str] = None) -> Dict[str, Any]:
|
|
744
|
+
"""
|
|
745
|
+
Save current execution results to a JSON file.
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
filename: Optional filename for the output file
|
|
749
|
+
|
|
750
|
+
Returns:
|
|
751
|
+
Dictionary with file information
|
|
752
|
+
"""
|
|
753
|
+
if not filename:
|
|
754
|
+
filename = self.generate_filename("execution_results", ".json")
|
|
755
|
+
|
|
756
|
+
file_path = self.output_dir / filename
|
|
757
|
+
file_path = self.validate_output_path(file_path)
|
|
758
|
+
|
|
759
|
+
# Get execution results
|
|
760
|
+
execution_results = self.locals.get('execution_results', {})
|
|
761
|
+
|
|
762
|
+
# Serialize execution results safely
|
|
763
|
+
serializable_results = self._serialize_execution_results(execution_results)
|
|
764
|
+
|
|
765
|
+
# Save to file
|
|
766
|
+
try:
|
|
767
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
768
|
+
f.write(json_encoder(serializable_results))
|
|
769
|
+
|
|
770
|
+
file_url = self.to_static_url(file_path)
|
|
771
|
+
|
|
772
|
+
return {
|
|
773
|
+
"filename": filename,
|
|
774
|
+
"file_path": str(file_path),
|
|
775
|
+
"file_url": file_url,
|
|
776
|
+
"results_count": len(execution_results),
|
|
777
|
+
"serializable_count": len(serializable_results),
|
|
778
|
+
"saved_at": self.generate_filename("", "", include_timestamp=True)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
except Exception as e:
|
|
782
|
+
raise ValueError(
|
|
783
|
+
f"Error saving execution results: {e}"
|
|
784
|
+
) from e
|
|
785
|
+
|
|
786
|
+
def load_execution_results(self, file_path: Union[str, Path]) -> Dict[str, Any]:
|
|
787
|
+
"""
|
|
788
|
+
Load execution results from a JSON file.
|
|
789
|
+
|
|
790
|
+
Args:
|
|
791
|
+
file_path: Path to the JSON file
|
|
792
|
+
|
|
793
|
+
Returns:
|
|
794
|
+
Dictionary with loading information
|
|
795
|
+
"""
|
|
796
|
+
file_path = Path(file_path)
|
|
797
|
+
|
|
798
|
+
if not file_path.exists():
|
|
799
|
+
raise ValueError(f"File not found: {file_path}")
|
|
800
|
+
|
|
801
|
+
try:
|
|
802
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
803
|
+
serialized_results = json_decoder(f.read())
|
|
804
|
+
|
|
805
|
+
# Deserialize the results
|
|
806
|
+
results = self._deserialize_execution_results(serialized_results)
|
|
807
|
+
|
|
808
|
+
# Update execution results
|
|
809
|
+
self.locals['execution_results'].update(results)
|
|
810
|
+
|
|
811
|
+
return {
|
|
812
|
+
"file_path": str(file_path),
|
|
813
|
+
"results_loaded": len(results),
|
|
814
|
+
"total_results": len(self.locals['execution_results']),
|
|
815
|
+
"loaded_at": self.generate_filename("", "", include_timestamp=True)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
except Exception as e:
|
|
819
|
+
raise ValueError(
|
|
820
|
+
f"Error loading execution results: {e}"
|
|
821
|
+
) from e
|
|
822
|
+
|
|
823
|
+
def _deserialize_execution_results(self, serialized_results: Dict[str, Any]) -> Dict[str, Any]:
|
|
824
|
+
"""
|
|
825
|
+
Deserialize execution results from JSON-compatible format.
|
|
826
|
+
|
|
827
|
+
Args:
|
|
828
|
+
serialized_results: Dictionary of serialized results
|
|
829
|
+
|
|
830
|
+
Returns:
|
|
831
|
+
Dictionary with deserialized objects where possible
|
|
832
|
+
"""
|
|
833
|
+
results = {}
|
|
834
|
+
|
|
835
|
+
for key, value in serialized_results.items():
|
|
836
|
+
try:
|
|
837
|
+
if isinstance(value, dict) and "_type" in value:
|
|
838
|
+
obj_type = value["_type"]
|
|
839
|
+
|
|
840
|
+
if obj_type == "pandas.DataFrame":
|
|
841
|
+
# Reconstruct DataFrame
|
|
842
|
+
df = pd.DataFrame(value["data"])
|
|
843
|
+
if "columns" in value:
|
|
844
|
+
df.columns = value["columns"]
|
|
845
|
+
results[key] = df
|
|
846
|
+
|
|
847
|
+
elif obj_type == "pandas.Series":
|
|
848
|
+
# Reconstruct Series
|
|
849
|
+
series = pd.Series(value["data"])
|
|
850
|
+
if "name" in value:
|
|
851
|
+
series.name = value["name"]
|
|
852
|
+
results[key] = series
|
|
853
|
+
|
|
854
|
+
elif obj_type == "numpy.ndarray":
|
|
855
|
+
# Reconstruct numpy array
|
|
856
|
+
arr = np.array(value["data"])
|
|
857
|
+
if "shape" in value:
|
|
858
|
+
arr = arr.reshape(value["shape"])
|
|
859
|
+
results[key] = arr
|
|
860
|
+
|
|
861
|
+
elif obj_type in ["string_representation", "callable"]:
|
|
862
|
+
# Keep as metadata dict for non-reconstructible objects
|
|
863
|
+
results[key] = value
|
|
864
|
+
|
|
865
|
+
else:
|
|
866
|
+
# For other custom types, keep the serialized representation
|
|
867
|
+
results[key] = value
|
|
868
|
+
else:
|
|
869
|
+
# Direct value
|
|
870
|
+
results[key] = value
|
|
871
|
+
|
|
872
|
+
except Exception as e:
|
|
873
|
+
self.logger.warning(f"Could not deserialize execution result '{key}': {e}")
|
|
874
|
+
# Keep the serialized version
|
|
875
|
+
results[key] = value
|
|
876
|
+
|
|
877
|
+
return results
|
|
878
|
+
|
|
879
|
+
def __call__(self, code: str, debug: bool = False) -> str:
|
|
880
|
+
"""Make the tool callable for backward compatibility."""
|
|
881
|
+
return self.execute_sync(code, debug)
|
|
882
|
+
|
|
883
|
+
@contextlib.contextmanager
|
|
884
|
+
def safe_execution_context(self):
|
|
885
|
+
"""Context manager for safe code execution that prevents crashes."""
|
|
886
|
+
old_excepthook = sys.excepthook
|
|
887
|
+
|
|
888
|
+
def safe_excepthook(exc_type, exc_value, exc_traceback):
|
|
889
|
+
"""Custom exception hook that prevents crashes from matplotlib issues."""
|
|
890
|
+
if (exc_type == RuntimeError and
|
|
891
|
+
'main thread is not in main loop' in str(exc_value)):
|
|
892
|
+
self.logger.warning("Caught matplotlib threading issue, continuing...")
|
|
893
|
+
return
|
|
894
|
+
elif (exc_type == RuntimeError and 'Calling Tcl from different apartment' in str(exc_value)):
|
|
895
|
+
self.logger.warning("Caught matplotlib Tcl issue, continuing...")
|
|
896
|
+
return
|
|
897
|
+
else:
|
|
898
|
+
# Call the original exception hook for other exceptions
|
|
899
|
+
old_excepthook(exc_type, exc_value, exc_traceback)
|
|
900
|
+
|
|
901
|
+
try:
|
|
902
|
+
sys.excepthook = safe_excepthook
|
|
903
|
+
yield
|
|
904
|
+
finally:
|
|
905
|
+
sys.excepthook = old_excepthook
|
|
906
|
+
|
|
907
|
+
def execute_code_safely(self, code: str, debug: bool = False) -> str:
|
|
908
|
+
"""Execute code with maximum safety against crashes."""
|
|
909
|
+
with self.safe_execution_context():
|
|
910
|
+
return self._execute_code(code, debug)
|