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,337 @@
|
|
|
1
|
+
from typing import AsyncGenerator, Dict, Any
|
|
2
|
+
import asyncio
|
|
3
|
+
from aiohttp import web
|
|
4
|
+
from datamodel.parsers.json import json_encoder, json_decoder # pylint: disable=E0611 # noqa
|
|
5
|
+
from navigator.views import BaseHandler
|
|
6
|
+
from navigator_auth.conf import exclude_list
|
|
7
|
+
from parrot.bots import AbstractBot
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StreamHandler(BaseHandler):
|
|
11
|
+
"""Streaming Endpoints for Parrot LLM Responses.
|
|
12
|
+
|
|
13
|
+
Supports:
|
|
14
|
+
- SSE (Server-Sent Events)
|
|
15
|
+
- WebSockets
|
|
16
|
+
"""
|
|
17
|
+
def __init__(self, *args, **kwargs):
|
|
18
|
+
super().__init__(*args, **kwargs)
|
|
19
|
+
self.active_connections = set()
|
|
20
|
+
|
|
21
|
+
def _get_botmanager(self, request: web.Request):
|
|
22
|
+
"""Retrieve the bot manager from the application context."""
|
|
23
|
+
try:
|
|
24
|
+
return request.app['bot_manager']
|
|
25
|
+
except KeyError as e:
|
|
26
|
+
raise web.HTTPInternalServerError(
|
|
27
|
+
reason="Bot manager not found in application."
|
|
28
|
+
) from e
|
|
29
|
+
|
|
30
|
+
async def _get_bot(self, request: web.Request) -> AbstractBot:
|
|
31
|
+
"""Retrieve the bot instance based on bot_id from the request."""
|
|
32
|
+
bot_manager = self._get_botmanager(request)
|
|
33
|
+
bot_id = request.match_info.get('bot_id')
|
|
34
|
+
bot = await bot_manager.get_bot(bot_id)
|
|
35
|
+
if bot is None:
|
|
36
|
+
raise web.HTTPNotFound(
|
|
37
|
+
reason=f"Bot with ID '{bot_id}' not found."
|
|
38
|
+
)
|
|
39
|
+
return bot
|
|
40
|
+
|
|
41
|
+
def _extract_stream_params(self, payload: Dict[str, Any], *extra_ignored_keys: str):
|
|
42
|
+
"""Split incoming payload into prompt and kwargs for ask_stream."""
|
|
43
|
+
ignored_keys = {"prompt", *extra_ignored_keys}
|
|
44
|
+
prompt = payload.get('prompt', '')
|
|
45
|
+
kwargs = {k: v for k, v in payload.items() if k not in ignored_keys}
|
|
46
|
+
return prompt, kwargs
|
|
47
|
+
|
|
48
|
+
async def stream_sse(self, request: web.Request) -> web.StreamResponse:
|
|
49
|
+
"""
|
|
50
|
+
Server-Sent Events (SSE) streaming endpoint
|
|
51
|
+
Best for: Unidirectional streaming, HTTP/1.1 compatible
|
|
52
|
+
"""
|
|
53
|
+
data = await request.json()
|
|
54
|
+
prompt, ask_kwargs = self._extract_stream_params(data)
|
|
55
|
+
bot = await self._get_bot(request)
|
|
56
|
+
response = web.StreamResponse(
|
|
57
|
+
status=200,
|
|
58
|
+
reason='OK',
|
|
59
|
+
headers={
|
|
60
|
+
'Content-Type': 'text/event-stream',
|
|
61
|
+
'Cache-Control': 'no-cache',
|
|
62
|
+
'Connection': 'keep-alive',
|
|
63
|
+
'Access-Control-Allow-Origin': '*',
|
|
64
|
+
'X-Accel-Buffering': 'no', # Disable nginx buffering
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
await response.prepare(request)
|
|
68
|
+
try:
|
|
69
|
+
async for chunk in bot.ask_stream(prompt, **ask_kwargs):
|
|
70
|
+
# Format as SSE
|
|
71
|
+
sse_data = f"data: {json_encoder({'content': chunk})}\n\n"
|
|
72
|
+
await response.write(sse_data.encode('utf-8'))
|
|
73
|
+
await response.drain()
|
|
74
|
+
|
|
75
|
+
# Send completion event
|
|
76
|
+
await response.write(b"data: [DONE]\n\n")
|
|
77
|
+
await response.drain()
|
|
78
|
+
except asyncio.CancelledError as e:
|
|
79
|
+
raise web.HTTPInternalServerError(
|
|
80
|
+
reason="Client disconnected during streaming."
|
|
81
|
+
) from e
|
|
82
|
+
except Exception as e:
|
|
83
|
+
await response.write(
|
|
84
|
+
f"error: {str(e)}\n\n".encode('utf-8')
|
|
85
|
+
)
|
|
86
|
+
finally:
|
|
87
|
+
await response.write_eof()
|
|
88
|
+
return response
|
|
89
|
+
|
|
90
|
+
async def stream_ndjson(self, request: web.Request) -> web.StreamResponse:
|
|
91
|
+
"""
|
|
92
|
+
NDJSON (Newline Delimited JSON) streaming endpoint
|
|
93
|
+
Best for: Clients that can parse JSON lines, more flexible than SSE
|
|
94
|
+
"""
|
|
95
|
+
data = await request.json()
|
|
96
|
+
prompt, ask_kwargs = self._extract_stream_params(data)
|
|
97
|
+
bot = await self._get_bot(request)
|
|
98
|
+
response = web.StreamResponse(
|
|
99
|
+
status=200,
|
|
100
|
+
reason='OK',
|
|
101
|
+
headers={
|
|
102
|
+
'Content-Type': 'application/x-ndjson',
|
|
103
|
+
'Cache-Control': 'no-cache',
|
|
104
|
+
'Connection': 'keep-alive',
|
|
105
|
+
'Access-Control-Allow-Origin': '*',
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
await response.prepare(request)
|
|
109
|
+
try:
|
|
110
|
+
async for chunk in bot.ask_stream(prompt, **ask_kwargs):
|
|
111
|
+
# Each line is a complete JSON object
|
|
112
|
+
line = json_encoder({
|
|
113
|
+
'type': 'content',
|
|
114
|
+
'data': chunk,
|
|
115
|
+
'timestamp': asyncio.get_event_loop().time()
|
|
116
|
+
}) + '\n'
|
|
117
|
+
await response.write(line.encode('utf-8'))
|
|
118
|
+
await response.drain()
|
|
119
|
+
|
|
120
|
+
# Send completion line
|
|
121
|
+
await response.write(
|
|
122
|
+
json_encoder({'done': True}).encode('utf-8') + b'\n'
|
|
123
|
+
)
|
|
124
|
+
await response.drain()
|
|
125
|
+
except asyncio.CancelledError as e:
|
|
126
|
+
raise web.HTTPInternalServerError(
|
|
127
|
+
reason="Client disconnected during streaming."
|
|
128
|
+
) from e
|
|
129
|
+
except Exception as e:
|
|
130
|
+
error_line = json_encoder({'error': str(e)}) + '\n'
|
|
131
|
+
await response.write(error_line.encode('utf-8'))
|
|
132
|
+
finally:
|
|
133
|
+
await response.write_eof()
|
|
134
|
+
return response
|
|
135
|
+
|
|
136
|
+
async def stream_chunked(self, request: web.Request) -> web.StreamResponse:
|
|
137
|
+
"""
|
|
138
|
+
Plain chunked transfer encoding
|
|
139
|
+
Best for: Simple text streaming without special formatting
|
|
140
|
+
"""
|
|
141
|
+
data = await request.json()
|
|
142
|
+
prompt, ask_kwargs = self._extract_stream_params(data)
|
|
143
|
+
bot = await self._get_bot(request)
|
|
144
|
+
response = web.StreamResponse(
|
|
145
|
+
status=200,
|
|
146
|
+
reason='OK',
|
|
147
|
+
headers={
|
|
148
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
149
|
+
'Transfer-Encoding': 'chunked',
|
|
150
|
+
'Cache-Control': 'no-cache',
|
|
151
|
+
'Connection': 'keep-alive',
|
|
152
|
+
'Access-Control-Allow-Origin': '*',
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
await response.prepare(request)
|
|
156
|
+
try:
|
|
157
|
+
async for chunk in bot.ask_stream(prompt, **ask_kwargs):
|
|
158
|
+
await response.write(chunk.encode('utf-8'))
|
|
159
|
+
await response.drain()
|
|
160
|
+
|
|
161
|
+
# Indicate end of stream
|
|
162
|
+
await response.write_eof()
|
|
163
|
+
except asyncio.CancelledError as e:
|
|
164
|
+
raise web.HTTPInternalServerError(
|
|
165
|
+
reason="Client disconnected during streaming."
|
|
166
|
+
) from e
|
|
167
|
+
except Exception as e:
|
|
168
|
+
await response.write(f"\n[ERROR]: {str(e)}\n".encode('utf-8'))
|
|
169
|
+
finally:
|
|
170
|
+
await response.write_eof()
|
|
171
|
+
return response
|
|
172
|
+
|
|
173
|
+
async def stream_websocket(self, request: web.Request) -> web.WebSocketResponse:
|
|
174
|
+
"""
|
|
175
|
+
WebSocket endpoint for bidirectional streaming
|
|
176
|
+
Best for: Real-time bidirectional communication, chat applications
|
|
177
|
+
"""
|
|
178
|
+
ws = web.WebSocketResponse(
|
|
179
|
+
heartbeat=30.0, # Send ping every 30s
|
|
180
|
+
max_msg_size=10 * 1024 * 1024 # 10MB max message size
|
|
181
|
+
)
|
|
182
|
+
# Extract and validate JWT from Sec-WebSocket-Protocol
|
|
183
|
+
# Client sends: new WebSocket(url, ["jwt", token])
|
|
184
|
+
# Header received: Sec-WebSocket-Protocol: jwt, <token>
|
|
185
|
+
protocol_header = request.headers.get('Sec-WebSocket-Protocol')
|
|
186
|
+
selected_protocol = None
|
|
187
|
+
|
|
188
|
+
if protocol_header:
|
|
189
|
+
parts = [p.strip() for p in protocol_header.split(',')]
|
|
190
|
+
if 'jwt' in parts:
|
|
191
|
+
# Find the token (assuming it's the other part)
|
|
192
|
+
# This is a bit naive if there are other protocols, but fits the requirement
|
|
193
|
+
try:
|
|
194
|
+
token_idx = parts.index('jwt') + 1
|
|
195
|
+
# If 'jwt' is the last item, try look elsewhere or it might be (token, jwt) order?
|
|
196
|
+
# Browsers usually send in order requested.
|
|
197
|
+
# Actually, if we send ["jwt", "token"], header is "jwt, token"
|
|
198
|
+
# But we need to identify WHICH one is the token.
|
|
199
|
+
# Heuristic: The token is the long string that isn't 'jwt'.
|
|
200
|
+
# Or simpler: remove 'jwt' and take the first remaining non-empty string.
|
|
201
|
+
parts.remove('jwt')
|
|
202
|
+
if parts:
|
|
203
|
+
token = parts[0]
|
|
204
|
+
if await self._validate_token(token):
|
|
205
|
+
# Auth success
|
|
206
|
+
# We MUST return the selected protocol in the response
|
|
207
|
+
# to the client, otherwise the WS connection fails.
|
|
208
|
+
# Usually we echo 'jwt' or the protocol used.
|
|
209
|
+
selected_protocol = 'jwt'
|
|
210
|
+
else:
|
|
211
|
+
raise web.HTTPUnauthorized(reason="Invalid Token")
|
|
212
|
+
else:
|
|
213
|
+
raise web.HTTPUnauthorized(reason="Missing Token")
|
|
214
|
+
except (ValueError, IndexError):
|
|
215
|
+
raise web.HTTPUnauthorized(reason="Invalid Protocol Format")
|
|
216
|
+
|
|
217
|
+
if selected_protocol:
|
|
218
|
+
await ws.prepare(request, protocols=[selected_protocol])
|
|
219
|
+
else:
|
|
220
|
+
await ws.prepare(request)
|
|
221
|
+
|
|
222
|
+
self.active_connections.add(ws)
|
|
223
|
+
bot = await self._get_bot(request)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
await ws.send_json({
|
|
227
|
+
'type': 'connection',
|
|
228
|
+
'status': 'connected',
|
|
229
|
+
'message': 'WebSocket connection established'
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
async for msg in ws:
|
|
233
|
+
if msg.type == web.WSMsgType.TEXT:
|
|
234
|
+
# Handle incoming messages
|
|
235
|
+
try:
|
|
236
|
+
data = json_decoder(msg.data)
|
|
237
|
+
await self._handle_message(ws, data, bot)
|
|
238
|
+
except Exception:
|
|
239
|
+
await ws.send_json({
|
|
240
|
+
'type': 'error',
|
|
241
|
+
'message': 'Invalid JSON'
|
|
242
|
+
})
|
|
243
|
+
elif msg.type == web.WSMsgType.ERROR:
|
|
244
|
+
# Handle errors
|
|
245
|
+
print(f'WebSocket error: {ws.exception()}')
|
|
246
|
+
self.active_connections.remove(ws)
|
|
247
|
+
except Exception as e:
|
|
248
|
+
self.active_connections.remove(ws)
|
|
249
|
+
raise web.HTTPInternalServerError(
|
|
250
|
+
reason="Error occurred during WebSocket communication."
|
|
251
|
+
) from e
|
|
252
|
+
return ws
|
|
253
|
+
|
|
254
|
+
async def _validate_token(self, token: str) -> bool:
|
|
255
|
+
"""Validate the provided token using the app's auth system."""
|
|
256
|
+
if not token:
|
|
257
|
+
return False
|
|
258
|
+
# auth_manager = self.app.get('auth_manager')
|
|
259
|
+
# if not auth_manager:
|
|
260
|
+
# return False
|
|
261
|
+
# is_valid = await auth_manager.validate_token(token)
|
|
262
|
+
# return is_valid
|
|
263
|
+
return True # Temporarily allow all tokens for testing
|
|
264
|
+
|
|
265
|
+
async def _handle_message(self, ws: web.WebSocketResponse, data: dict, bot: AbstractBot):
|
|
266
|
+
"""Handle incoming WebSocket messages"""
|
|
267
|
+
msg_type = data.get('type')
|
|
268
|
+
if msg_type == 'auth':
|
|
269
|
+
auth_header = data.get('authorization', '')
|
|
270
|
+
token = auth_header.replace('Bearer ', '') if auth_header.startswith('Bearer ') else None
|
|
271
|
+
|
|
272
|
+
if await self._validate_token(token):
|
|
273
|
+
ws._authenticated = True
|
|
274
|
+
await ws.send_json({'type': 'auth_success', 'message': 'Authentication successful'})
|
|
275
|
+
else:
|
|
276
|
+
await ws.send_json({'type': 'auth_error', 'message': 'Invalid or expired token'})
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
if msg_type == 'stream_request':
|
|
280
|
+
prompt, ask_kwargs = self._extract_stream_params(data, 'type')
|
|
281
|
+
|
|
282
|
+
# Send acknowledgment
|
|
283
|
+
await ws.send_json({
|
|
284
|
+
'type': 'stream_start',
|
|
285
|
+
'prompt': prompt
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
# Stream the LLM response
|
|
290
|
+
async for chunk in bot.ask_stream(prompt, **ask_kwargs):
|
|
291
|
+
await ws.send_json({
|
|
292
|
+
'type': 'content',
|
|
293
|
+
'data': chunk
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
# Send completion
|
|
297
|
+
await ws.send_json({
|
|
298
|
+
'type': 'stream_complete'
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
except Exception as e:
|
|
302
|
+
await ws.send_json({
|
|
303
|
+
'type': 'error',
|
|
304
|
+
'message': str(e)
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
elif msg_type == 'ping':
|
|
308
|
+
await ws.send_json({'type': 'pong'})
|
|
309
|
+
|
|
310
|
+
else:
|
|
311
|
+
await ws.send_json({
|
|
312
|
+
'type': 'error',
|
|
313
|
+
'message': f'Unknown message type: {msg_type}'
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
async def broadcast(self, message: dict):
|
|
317
|
+
"""Broadcast message to all connected clients"""
|
|
318
|
+
for ws in self.active_connections:
|
|
319
|
+
try:
|
|
320
|
+
await ws.send_json(message)
|
|
321
|
+
except Exception as e:
|
|
322
|
+
print(f"Error broadcasting to client: {e}")
|
|
323
|
+
|
|
324
|
+
def configure_routes(self, app: web.Application):
|
|
325
|
+
"""Configure routes for streaming endpoints."""
|
|
326
|
+
# sse endpoint
|
|
327
|
+
exclude_list.append('/bots/*/stream/sse')
|
|
328
|
+
app.router.add_post('/bots/{bot_id}/stream/sse', self.stream_sse)
|
|
329
|
+
# ndjson endpoint
|
|
330
|
+
exclude_list.append('/bots/*/stream/ndjson')
|
|
331
|
+
app.router.add_post('/bots/{bot_id}/stream/ndjson', self.stream_ndjson)
|
|
332
|
+
# chunked endpoint
|
|
333
|
+
exclude_list.append('/bots/*/stream/chunked')
|
|
334
|
+
app.router.add_post('/bots/{bot_id}/stream/chunked', self.stream_chunked)
|
|
335
|
+
# websocket endpoint
|
|
336
|
+
exclude_list.append('/bots/*/stream/ws')
|
|
337
|
+
app.router.add_get('/bots/{bot_id}/stream/ws', self.stream_websocket)
|
parrot/interfaces/aws.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Interface for AI-Parrot
|
|
3
|
+
Provides async context manager for AWS service clients using aioboto3
|
|
4
|
+
"""
|
|
5
|
+
from typing import Optional, Dict, Any, AsyncIterator
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
import aioboto3
|
|
8
|
+
from botocore.exceptions import ClientError, NoCredentialsError
|
|
9
|
+
from ..conf import (
|
|
10
|
+
AWS_ACCESS_KEY,
|
|
11
|
+
AWS_SECRET_KEY,
|
|
12
|
+
AWS_REGION_NAME,
|
|
13
|
+
AWS_CREDENTIALS
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AWSInterface:
|
|
18
|
+
"""
|
|
19
|
+
Base interface for AWS services using aioboto3.
|
|
20
|
+
|
|
21
|
+
Provides async context manager for creating service clients.
|
|
22
|
+
Handles credential management and session lifecycle.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
>>> aws = AWSInterface(aws_id='default')
|
|
26
|
+
>>> async with aws.client('s3') as s3:
|
|
27
|
+
... response = await s3.list_buckets()
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
aws_id: str = 'default',
|
|
33
|
+
region_name: Optional[str] = None,
|
|
34
|
+
credentials: Optional[Dict[str, Any]] = None,
|
|
35
|
+
**kwargs
|
|
36
|
+
):
|
|
37
|
+
"""
|
|
38
|
+
Initialize AWS interface.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
aws_id: Identifier for credentials in AWS_CREDENTIALS dict
|
|
42
|
+
region_name: AWS region (overrides credentials config)
|
|
43
|
+
credentials: Direct credential dict (overrides aws_id lookup)
|
|
44
|
+
**kwargs: Additional boto3 session parameters
|
|
45
|
+
"""
|
|
46
|
+
# Get credentials from config or direct input
|
|
47
|
+
if credentials is None:
|
|
48
|
+
credentials = AWS_CREDENTIALS.get(aws_id, {})
|
|
49
|
+
if not credentials or credentials == 'default':
|
|
50
|
+
credentials = AWS_CREDENTIALS.get('default', {
|
|
51
|
+
'aws_key': AWS_ACCESS_KEY,
|
|
52
|
+
'aws_secret': AWS_SECRET_KEY,
|
|
53
|
+
'region_name': AWS_REGION_NAME
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
# Build AWS config
|
|
57
|
+
self.aws_config = {
|
|
58
|
+
'aws_access_key_id': credentials.get('aws_key') or credentials.get('aws_access_key_id'),
|
|
59
|
+
'aws_secret_access_key': credentials.get('aws_secret') or credentials.get('aws_secret_access_key'),
|
|
60
|
+
'region_name': region_name or credentials.get('region_name', AWS_REGION_NAME),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# Add optional session token if present
|
|
64
|
+
if 'aws_session_token' in credentials:
|
|
65
|
+
self.aws_config['aws_session_token'] = credentials['aws_session_token']
|
|
66
|
+
|
|
67
|
+
# Add any additional kwargs
|
|
68
|
+
self.aws_config.update(kwargs)
|
|
69
|
+
|
|
70
|
+
# Remove None values
|
|
71
|
+
self.aws_config = {k: v for k, v in self.aws_config.items() if v is not None}
|
|
72
|
+
|
|
73
|
+
# Create session
|
|
74
|
+
self.session = aioboto3.Session(**self.aws_config)
|
|
75
|
+
self._region = self.aws_config.get('region_name')
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def region(self) -> str:
|
|
79
|
+
"""Get configured AWS region"""
|
|
80
|
+
return self._region
|
|
81
|
+
|
|
82
|
+
@asynccontextmanager
|
|
83
|
+
async def client(self, service_name: str, **kwargs) -> AsyncIterator[Any]:
|
|
84
|
+
"""
|
|
85
|
+
Async context manager for AWS service client.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
service_name: AWS service name (e.g., 's3', 'cloudwatch', 'logs')
|
|
89
|
+
**kwargs: Additional client configuration
|
|
90
|
+
|
|
91
|
+
Yields:
|
|
92
|
+
AWS service client
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
>>> async with aws.client('cloudwatch') as cw:
|
|
96
|
+
... metrics = await cw.list_metrics()
|
|
97
|
+
"""
|
|
98
|
+
async with self.session.client(service_name, **kwargs) as client:
|
|
99
|
+
yield client
|
|
100
|
+
|
|
101
|
+
@asynccontextmanager
|
|
102
|
+
async def resource(self, service_name: str, **kwargs) -> AsyncIterator[Any]:
|
|
103
|
+
"""
|
|
104
|
+
Async context manager for AWS service resource.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
service_name: AWS service name (e.g., 's3', 'dynamodb')
|
|
108
|
+
**kwargs: Additional resource configuration
|
|
109
|
+
|
|
110
|
+
Yields:
|
|
111
|
+
AWS service resource
|
|
112
|
+
"""
|
|
113
|
+
async with self.session.resource(service_name, **kwargs) as resource:
|
|
114
|
+
yield resource
|
|
115
|
+
|
|
116
|
+
async def validate_credentials(self) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Validate AWS credentials by making a simple API call.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if credentials are valid, False otherwise
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
async with self.client('sts') as sts:
|
|
125
|
+
await sts.get_caller_identity()
|
|
126
|
+
return True
|
|
127
|
+
except (ClientError, NoCredentialsError):
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
async def get_caller_identity(self) -> Dict[str, Any]:
|
|
131
|
+
"""
|
|
132
|
+
Get AWS caller identity information.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dict with UserId, Account, and Arn
|
|
136
|
+
"""
|
|
137
|
+
async with self.client('sts') as sts:
|
|
138
|
+
response = await sts.get_caller_identity()
|
|
139
|
+
return {
|
|
140
|
+
'user_id': response.get('UserId'),
|
|
141
|
+
'account': response.get('Account'),
|
|
142
|
+
'arn': response.get('Arn')
|
|
143
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from abc import ABC
|
|
3
|
+
import contextlib
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
from typing_extensions import ParamSpec
|
|
6
|
+
from navconfig import config
|
|
7
|
+
from navconfig.logging import logging
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
P = ParamSpec("P")
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
valid_types = {
|
|
15
|
+
"<class 'str'>": str,
|
|
16
|
+
"<class 'int'>": int,
|
|
17
|
+
"<class 'float'>": float,
|
|
18
|
+
"<class 'list'>": list,
|
|
19
|
+
"<class 'tuple'>": tuple,
|
|
20
|
+
"<class 'dict'>": dict
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CredentialsInterface(ABC):
|
|
25
|
+
"""
|
|
26
|
+
Abstract Base Class for handling credentials and environment variables.
|
|
27
|
+
This class provides methods to process and validate credentials, as well as
|
|
28
|
+
retrieve values from environment variables or configuration files.
|
|
29
|
+
"""
|
|
30
|
+
_credentials: dict = {"username": str, "password": str}
|
|
31
|
+
|
|
32
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
33
|
+
# if credentials:
|
|
34
|
+
self.credentials: dict = kwargs.pop('credentials', None)
|
|
35
|
+
self._no_warnings = kwargs.get("no_warnings", False)
|
|
36
|
+
if expected := kwargs.pop("expected_credentials", None):
|
|
37
|
+
self._credentials = expected
|
|
38
|
+
self._environment = config
|
|
39
|
+
try:
|
|
40
|
+
super().__init__(*args, **kwargs)
|
|
41
|
+
except TypeError:
|
|
42
|
+
super().__init__()
|
|
43
|
+
# Interface not started:
|
|
44
|
+
self._started: bool = False
|
|
45
|
+
self.logger = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
def get_env_value(self, key, default: str = None, expected_type: object = None):
|
|
48
|
+
"""
|
|
49
|
+
Retrieves a value from the environment variables or the configuration.
|
|
50
|
+
|
|
51
|
+
:param key: The key for the environment variable.
|
|
52
|
+
:param default: The default value to return if the key is not found.
|
|
53
|
+
:return: The value of the environment variable or the default value.
|
|
54
|
+
"""
|
|
55
|
+
if key is None:
|
|
56
|
+
return default
|
|
57
|
+
if expected_type is not None:
|
|
58
|
+
with contextlib.suppress(TypeError):
|
|
59
|
+
if expected_type in (int, float):
|
|
60
|
+
return val if (val := self._environment.getint(key)) else key
|
|
61
|
+
elif expected_type == bool:
|
|
62
|
+
return val if (val := self._environment.getboolean(key)) else key
|
|
63
|
+
else:
|
|
64
|
+
return val if (val := self._environment.get(key)) else key
|
|
65
|
+
return default
|
|
66
|
+
if val := os.getenv(str(key), default):
|
|
67
|
+
return val
|
|
68
|
+
return val if (val := self._environment.get(key, default)) else key
|
|
69
|
+
|
|
70
|
+
def processing_credentials(self):
|
|
71
|
+
if self.credentials:
|
|
72
|
+
for key, expected_type in self._credentials.items():
|
|
73
|
+
try:
|
|
74
|
+
value = self.credentials.get(key, None)
|
|
75
|
+
default = getattr(self, key, value)
|
|
76
|
+
# print('KEY ', key, 'VAL ', value, 'DEF ', default)
|
|
77
|
+
if type(value) == expected_type or isinstance(value, valid_types[str(expected_type)]): # pylint: disable=E1136 # noqa
|
|
78
|
+
# can process the credentials, extracted from environment or variables:
|
|
79
|
+
val = self.get_env_value(
|
|
80
|
+
value, default=default, expected_type=expected_type
|
|
81
|
+
)
|
|
82
|
+
# print('VAL > ', val, 'DEFAULT > ', default, expected_type)
|
|
83
|
+
self.credentials[key] = val
|
|
84
|
+
# print('KEY: ', key, self.credentials[key])
|
|
85
|
+
elif isinstance(value, str):
|
|
86
|
+
# Use os.getenv to get the value from environment variables
|
|
87
|
+
env_value = self.get_env_value(
|
|
88
|
+
value, default=default, expected_type=expected_type
|
|
89
|
+
)
|
|
90
|
+
self.credentials[key] = env_value
|
|
91
|
+
else:
|
|
92
|
+
self.credentials[key] = default
|
|
93
|
+
except KeyError as exc:
|
|
94
|
+
print(f'Failed credential {key} with value {value}: {exc}')
|
|
95
|
+
continue
|
|
96
|
+
except (TypeError, ValueError) as ex:
|
|
97
|
+
self.logger.error(f"{__name__}: Wrong or missing Credentials")
|
|
98
|
+
raise RuntimeError(
|
|
99
|
+
f"{__name__}: Wrong or missing Credentials"
|
|
100
|
+
) from ex
|
|
101
|
+
except Exception as ex:
|
|
102
|
+
self.logger.exception(
|
|
103
|
+
f"Error Processing Credentials: {ex}"
|
|
104
|
+
)
|
|
105
|
+
raise RuntimeError(
|
|
106
|
+
f"Error Processing Credentials: {ex}"
|
|
107
|
+
) from ex
|
|
108
|
+
if self.credentials is None:
|
|
109
|
+
if self._no_warnings is False:
|
|
110
|
+
self.logger.warning(
|
|
111
|
+
"No credentials where Found."
|
|
112
|
+
)
|
|
113
|
+
self.credentials = {}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""DB (asyncdb) Extension.
|
|
2
|
+
DB connection for any Application.
|
|
3
|
+
"""
|
|
4
|
+
from asyncdb import AsyncDB
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DBInterface:
|
|
8
|
+
"""
|
|
9
|
+
Interface for using database connections in an Application using AsyncDB.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def get_database(
|
|
13
|
+
self,
|
|
14
|
+
driver: str,
|
|
15
|
+
dsn: str = None,
|
|
16
|
+
params: dict = None,
|
|
17
|
+
timeout: int = 60,
|
|
18
|
+
**kwargs
|
|
19
|
+
) -> AsyncDB:
|
|
20
|
+
"""Get the driver."""
|
|
21
|
+
return AsyncDB(
|
|
22
|
+
driver,
|
|
23
|
+
dsn=dsn,
|
|
24
|
+
params=params,
|
|
25
|
+
timeout=timeout,
|
|
26
|
+
**kwargs
|
|
27
|
+
)
|