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,627 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JobManagerMixin: A mixin class to add asynchronous job execution capabilities to views.
|
|
3
|
+
|
|
4
|
+
This mixin provides:
|
|
5
|
+
- A decorator to enqueue functions to be executed by a job manager
|
|
6
|
+
- GET method override to check and retrieve job results by job_id
|
|
7
|
+
"""
|
|
8
|
+
from typing import Any, Callable, Optional, Dict, Awaitable
|
|
9
|
+
import functools
|
|
10
|
+
import inspect
|
|
11
|
+
import asyncio
|
|
12
|
+
from aiohttp import web
|
|
13
|
+
from .job import JobManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class JobManagerMixin:
|
|
17
|
+
"""
|
|
18
|
+
Mixin class to add job manager functionality to any BaseView.
|
|
19
|
+
|
|
20
|
+
This mixin allows view methods to be executed asynchronously via a job manager,
|
|
21
|
+
and provides automatic handling of job status/result retrieval via GET requests.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
job_manager: An instance of JobManager that handles async job execution
|
|
25
|
+
job_id_param: The query parameter name for job IDs (default: 'job_id')
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
job_manager: Optional[Any] = None
|
|
29
|
+
job_id_param: str = "job_id"
|
|
30
|
+
|
|
31
|
+
def __init__(self, *args, **kwargs):
|
|
32
|
+
"""Initialize the mixin with job manager instance."""
|
|
33
|
+
super().__init__(*args, **kwargs)
|
|
34
|
+
if not hasattr(self, 'job_manager') or self.job_manager is None:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
"JobManagerMixin requires a 'job_manager' attribute. "
|
|
37
|
+
"Please set it in your view class."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def as_job(
|
|
42
|
+
queue: str = "default",
|
|
43
|
+
timeout: Optional[int] = None,
|
|
44
|
+
result_ttl: Optional[int] = 500,
|
|
45
|
+
return_job_id: bool = True
|
|
46
|
+
) -> Callable:
|
|
47
|
+
"""
|
|
48
|
+
Decorator to enqueue a method to be executed by the job manager.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
queue: The queue name for job execution (default: 'default')
|
|
52
|
+
timeout: Maximum execution time in seconds
|
|
53
|
+
result_ttl: Time to live for job results in seconds
|
|
54
|
+
return_job_id: If True, returns job_id; if False, returns job result
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Decorated function that enqueues the job and returns job_id
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
@JobManagerMixin.as_job(queue="ml_tasks", timeout=3600)
|
|
61
|
+
def process_large_dataset(self, request):
|
|
62
|
+
# This will be executed asynchronously
|
|
63
|
+
result = heavy_computation()
|
|
64
|
+
return result
|
|
65
|
+
"""
|
|
66
|
+
def decorator(func: Callable) -> Callable:
|
|
67
|
+
@functools.wraps(func)
|
|
68
|
+
def wrapper(self_arg, *args, **kwargs):
|
|
69
|
+
# self_arg is the instance (self)
|
|
70
|
+
instance = self_arg
|
|
71
|
+
|
|
72
|
+
# Extract request object if present in args
|
|
73
|
+
request = None
|
|
74
|
+
if args and hasattr(args[0], 'GET'):
|
|
75
|
+
request = args[0]
|
|
76
|
+
|
|
77
|
+
# Enqueue the job with all arguments including self
|
|
78
|
+
job = instance.job_manager.enqueue(
|
|
79
|
+
func,
|
|
80
|
+
args=(self_arg,) + args, # Include self in args
|
|
81
|
+
kwargs=kwargs,
|
|
82
|
+
queue=queue,
|
|
83
|
+
timeout=timeout,
|
|
84
|
+
result_ttl=result_ttl
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Store job metadata
|
|
88
|
+
job_metadata = {
|
|
89
|
+
'job_id': job.id,
|
|
90
|
+
'queue': queue,
|
|
91
|
+
'function': func.__name__,
|
|
92
|
+
'status': 'enqueued'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if return_job_id:
|
|
96
|
+
return instance._create_job_response(job_metadata, request)
|
|
97
|
+
else:
|
|
98
|
+
# Wait for job to complete and return result
|
|
99
|
+
return job.result
|
|
100
|
+
|
|
101
|
+
# Mark function as async job
|
|
102
|
+
wrapper._is_async_job = True
|
|
103
|
+
wrapper._job_config = {
|
|
104
|
+
'queue': queue,
|
|
105
|
+
'timeout': timeout,
|
|
106
|
+
'result_ttl': result_ttl
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return wrapper
|
|
110
|
+
return decorator
|
|
111
|
+
|
|
112
|
+
def get(self, request, *args, **kwargs):
|
|
113
|
+
"""
|
|
114
|
+
Override GET method to handle job_id parameter.
|
|
115
|
+
|
|
116
|
+
If request contains job_id parameter, retrieves and returns job status/result.
|
|
117
|
+
Otherwise, delegates to the original GET method of the view.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
request: The HTTP request object
|
|
121
|
+
*args: Additional positional arguments
|
|
122
|
+
**kwargs: Additional keyword arguments
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Job status/result if job_id is present, otherwise original GET response
|
|
126
|
+
"""
|
|
127
|
+
if (job_id := self._get_job_id_from_request(request)):
|
|
128
|
+
return self._handle_job_status_request(job_id, request)
|
|
129
|
+
|
|
130
|
+
# Call the original GET method if it exists
|
|
131
|
+
if hasattr(super(), 'get'):
|
|
132
|
+
return super().get(request, *args, **kwargs)
|
|
133
|
+
else:
|
|
134
|
+
return self._default_get_response(request)
|
|
135
|
+
|
|
136
|
+
def _get_job_id_from_request(self, request) -> Optional[str]:
|
|
137
|
+
"""
|
|
138
|
+
Extract job_id from request parameters.
|
|
139
|
+
|
|
140
|
+
Checks both query parameters (GET) and request data (POST).
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
request: The HTTP request object
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
job_id string if found, None otherwise
|
|
147
|
+
"""
|
|
148
|
+
# Check GET parameters
|
|
149
|
+
if hasattr(request, 'GET') and self.job_id_param in request.GET:
|
|
150
|
+
return request.GET.get(self.job_id_param)
|
|
151
|
+
|
|
152
|
+
# Check POST/request data
|
|
153
|
+
if hasattr(request, 'data') and self.job_id_param in request.data:
|
|
154
|
+
return request.data.get(self.job_id_param)
|
|
155
|
+
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
def _handle_job_status_request(self, job_id: str, request) -> Dict[str, Any]:
|
|
159
|
+
"""
|
|
160
|
+
Handle request for job status and results.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
job_id: The unique job identifier
|
|
164
|
+
request: The HTTP request object
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dictionary containing job status, result, or error information
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
job = self.job_manager.fetch_job(job_id)
|
|
171
|
+
|
|
172
|
+
if job is None:
|
|
173
|
+
return self._create_error_response(
|
|
174
|
+
f"Job with id '{job_id}' not found",
|
|
175
|
+
status_code=404
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
job_status = {
|
|
179
|
+
'job_id': job.id,
|
|
180
|
+
'status': job.get_status(),
|
|
181
|
+
'created_at': str(job.created_at) if hasattr(job, 'created_at') else None,
|
|
182
|
+
'started_at': str(job.started_at) if hasattr(job, 'started_at') else None,
|
|
183
|
+
'ended_at': str(job.ended_at) if hasattr(job, 'ended_at') else None,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# Add result if job is finished
|
|
187
|
+
if job.is_finished:
|
|
188
|
+
job_status['result'] = job.result
|
|
189
|
+
job_status['success'] = True
|
|
190
|
+
|
|
191
|
+
# Add error if job failed
|
|
192
|
+
elif job.is_failed:
|
|
193
|
+
job_status['error'] = str(job.exc_info) if hasattr(job, 'exc_info') else "Unknown error"
|
|
194
|
+
job_status['success'] = False
|
|
195
|
+
|
|
196
|
+
# Job is still in progress
|
|
197
|
+
else:
|
|
198
|
+
job_status['message'] = "Job is still in progress"
|
|
199
|
+
job_status['progress'] = job.meta.get('progress') if hasattr(job, 'meta') else None
|
|
200
|
+
|
|
201
|
+
return self._create_success_response(job_status)
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
return self._create_error_response(
|
|
205
|
+
f"Error fetching job: {str(e)}",
|
|
206
|
+
status_code=500
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def _create_job_response(
|
|
210
|
+
self,
|
|
211
|
+
job_metadata: Dict[str, Any],
|
|
212
|
+
request
|
|
213
|
+
) -> Dict[str, Any]:
|
|
214
|
+
"""
|
|
215
|
+
Create standardized response for job creation.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
job_metadata: Dictionary containing job information
|
|
219
|
+
request: The HTTP request object
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Formatted response with job details and status URL
|
|
223
|
+
"""
|
|
224
|
+
response = {
|
|
225
|
+
'success': True,
|
|
226
|
+
'job_id': job_metadata['job_id'],
|
|
227
|
+
'status': job_metadata['status'],
|
|
228
|
+
'message': f"Job enqueued successfully on queue '{job_metadata['queue']}'",
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
# Add status URL if request is available
|
|
232
|
+
if request and hasattr(request, 'build_absolute_uri'):
|
|
233
|
+
status_url = self._build_status_url(request, job_metadata['job_id'])
|
|
234
|
+
response['status_url'] = status_url
|
|
235
|
+
|
|
236
|
+
return response
|
|
237
|
+
|
|
238
|
+
def _build_status_url(self, request, job_id: str) -> str:
|
|
239
|
+
"""
|
|
240
|
+
Build URL for checking job status.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
request: The HTTP request object
|
|
244
|
+
job_id: The unique job identifier
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Full URL for job status endpoint
|
|
248
|
+
"""
|
|
249
|
+
base_url = request.build_absolute_uri(request.path)
|
|
250
|
+
separator = '&' if '?' in base_url else '?'
|
|
251
|
+
return f"{base_url}{separator}{self.job_id_param}={job_id}"
|
|
252
|
+
|
|
253
|
+
def _create_success_response(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
254
|
+
"""Create standardized success response."""
|
|
255
|
+
return {
|
|
256
|
+
'success': True,
|
|
257
|
+
**data
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
def _create_error_response(
|
|
261
|
+
self,
|
|
262
|
+
message: str,
|
|
263
|
+
status_code: int = 400
|
|
264
|
+
) -> Dict[str, Any]:
|
|
265
|
+
"""Create standardized error response."""
|
|
266
|
+
return {
|
|
267
|
+
'success': False,
|
|
268
|
+
'error': message,
|
|
269
|
+
'status_code': status_code
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
def _default_get_response(self, request) -> Dict[str, Any]:
|
|
273
|
+
"""Default GET response when no job_id is provided and no parent GET exists."""
|
|
274
|
+
return {
|
|
275
|
+
'message': 'No job_id provided. Use POST to create jobs or GET with job_id to check status.',
|
|
276
|
+
'usage': {
|
|
277
|
+
'create_job': 'POST to endpoint with required parameters',
|
|
278
|
+
'check_status': f'GET to endpoint with ?{self.job_id_param}=<job_id>'
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@classmethod
|
|
283
|
+
def get_async_methods(cls) -> Dict[str, Dict[str, Any]]:
|
|
284
|
+
"""
|
|
285
|
+
Get all methods decorated with @as_job in the class.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Dictionary mapping method names to their job configurations
|
|
289
|
+
"""
|
|
290
|
+
return {
|
|
291
|
+
name: method._job_config
|
|
292
|
+
for name, method in inspect.getmembers(
|
|
293
|
+
cls, predicate=inspect.isfunction
|
|
294
|
+
)
|
|
295
|
+
if hasattr(method, '_is_async_job') and method._is_async_job
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class AsyncJobManagerMixin:
|
|
300
|
+
"""
|
|
301
|
+
Async-native mixin for aiohttp views with job manager functionality.
|
|
302
|
+
|
|
303
|
+
Unlike the sync JobManagerMixin (designed for RQ), this version:
|
|
304
|
+
- Works natively with aiohttp's async request handlers
|
|
305
|
+
- Supports both your asyncio JobManager and RQ Queue
|
|
306
|
+
- Provides async get() method
|
|
307
|
+
- Handles aiohttp request objects
|
|
308
|
+
|
|
309
|
+
Usage with aiohttp:
|
|
310
|
+
class MyView(AsyncJobManagerMixin, web.View):
|
|
311
|
+
def __init__(self, request):
|
|
312
|
+
super().__init__(request)
|
|
313
|
+
# Use either RQ Queue or adapted JobManager
|
|
314
|
+
self.job_manager = request.app['job_queue']
|
|
315
|
+
|
|
316
|
+
@AsyncJobManagerMixin.as_job(queue="tasks", timeout=3600)
|
|
317
|
+
async def post(self):
|
|
318
|
+
# Your async handler
|
|
319
|
+
return {"result": "success"}
|
|
320
|
+
|
|
321
|
+
async def get(self):
|
|
322
|
+
# Automatically handles job_id parameter
|
|
323
|
+
return await super().get()
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
job_manager: Optional[Any] = None
|
|
327
|
+
job_id_param: str = "job_id"
|
|
328
|
+
|
|
329
|
+
def __init__(self, *args, **kwargs):
|
|
330
|
+
"""Initialize the mixin."""
|
|
331
|
+
super().__init__(*args, **kwargs)
|
|
332
|
+
|
|
333
|
+
def _get_jobmanager(self, request: web.Request) -> Any:
|
|
334
|
+
"""
|
|
335
|
+
Retrieve the job manager from the aiohttp request.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
request: aiohttp Request object
|
|
339
|
+
Returns:
|
|
340
|
+
Job manager instance (RQ Queue or your JobManager)
|
|
341
|
+
"""
|
|
342
|
+
return request.app['job_manager'] if 'job_manager' in request.app else JobManager()
|
|
343
|
+
|
|
344
|
+
@staticmethod
|
|
345
|
+
def as_job(
|
|
346
|
+
queue: str = "default",
|
|
347
|
+
timeout: Optional[int] = None,
|
|
348
|
+
result_ttl: Optional[int] = 500,
|
|
349
|
+
return_job_id: bool = True,
|
|
350
|
+
async_execution: bool = True
|
|
351
|
+
) -> Callable:
|
|
352
|
+
"""
|
|
353
|
+
Decorator to enqueue an async method for job execution.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
queue: Queue name for job execution
|
|
357
|
+
timeout: Maximum execution time in seconds
|
|
358
|
+
result_ttl: Time to live for job results
|
|
359
|
+
return_job_id: If True, returns job_id immediately
|
|
360
|
+
async_execution: If True, expects async function
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Decorated async function
|
|
364
|
+
|
|
365
|
+
Example:
|
|
366
|
+
@AsyncJobManagerMixin.as_job(queue="ml_tasks", timeout=3600)
|
|
367
|
+
async def process_data(self):
|
|
368
|
+
# This executes asynchronously
|
|
369
|
+
result = await heavy_computation()
|
|
370
|
+
return result
|
|
371
|
+
"""
|
|
372
|
+
def decorator(func: Callable) -> Callable:
|
|
373
|
+
@functools.wraps(func)
|
|
374
|
+
async def async_wrapper(self_arg, *args, **kwargs):
|
|
375
|
+
instance = self_arg
|
|
376
|
+
|
|
377
|
+
if not hasattr(instance, 'job_manager') or instance.job_manager is None:
|
|
378
|
+
raise ValueError(
|
|
379
|
+
"job_manager not set. Set it in your view's __init__ method."
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
# Extract request if available
|
|
383
|
+
request = None
|
|
384
|
+
if hasattr(instance, 'request'):
|
|
385
|
+
request = instance.request
|
|
386
|
+
elif args and isinstance(args[0], web.Request):
|
|
387
|
+
request = args[0]
|
|
388
|
+
|
|
389
|
+
# Enqueue the job
|
|
390
|
+
job = instance.job_manager.enqueue(
|
|
391
|
+
func,
|
|
392
|
+
args=(self_arg,) + args,
|
|
393
|
+
kwargs=kwargs,
|
|
394
|
+
queue=queue,
|
|
395
|
+
timeout=timeout,
|
|
396
|
+
result_ttl=result_ttl
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# Create job metadata
|
|
400
|
+
job_metadata = {
|
|
401
|
+
'job_id': job.id,
|
|
402
|
+
'queue': queue,
|
|
403
|
+
'function': func.__name__,
|
|
404
|
+
'status': 'enqueued'
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if return_job_id:
|
|
408
|
+
response_data = instance._create_job_response(job_metadata, request)
|
|
409
|
+
return web.json_response(response_data)
|
|
410
|
+
else:
|
|
411
|
+
# Wait for job to complete
|
|
412
|
+
# This is async-friendly
|
|
413
|
+
while not (job.is_finished or job.is_failed):
|
|
414
|
+
await asyncio.sleep(0.1)
|
|
415
|
+
job.refresh() if hasattr(job, 'refresh') else None
|
|
416
|
+
|
|
417
|
+
if job.is_failed:
|
|
418
|
+
return web.json_response(
|
|
419
|
+
{'error': str(job.exc_info)},
|
|
420
|
+
status=500
|
|
421
|
+
)
|
|
422
|
+
return web.json_response({'result': job.result})
|
|
423
|
+
|
|
424
|
+
# Mark as async job
|
|
425
|
+
async_wrapper._is_async_job = True
|
|
426
|
+
async_wrapper._job_config = {
|
|
427
|
+
'queue': queue,
|
|
428
|
+
'timeout': timeout,
|
|
429
|
+
'result_ttl': result_ttl,
|
|
430
|
+
'async': async_execution
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return async_wrapper
|
|
434
|
+
return decorator
|
|
435
|
+
|
|
436
|
+
async def get(self) -> web.Response:
|
|
437
|
+
"""
|
|
438
|
+
Async GET method to handle job status requests.
|
|
439
|
+
|
|
440
|
+
If request contains job_id parameter, returns job status/result.
|
|
441
|
+
Otherwise, delegates to parent GET method or returns default response.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
web.Response with job status or default response
|
|
445
|
+
"""
|
|
446
|
+
# Get request object
|
|
447
|
+
request = self.request if hasattr(self, 'request') else None
|
|
448
|
+
|
|
449
|
+
if not request:
|
|
450
|
+
return web.json_response({
|
|
451
|
+
'error': 'Request object not available'
|
|
452
|
+
}, status=500)
|
|
453
|
+
|
|
454
|
+
# Check for job_id parameter
|
|
455
|
+
if (job_id := self._get_job_id_from_request(request)):
|
|
456
|
+
response_data = await self._handle_job_status_request(job_id, request)
|
|
457
|
+
status_code = response_data.pop('status_code', 200)
|
|
458
|
+
return web.json_response(response_data, status=status_code)
|
|
459
|
+
|
|
460
|
+
# Call parent get if it exists
|
|
461
|
+
if hasattr(super(), 'get'):
|
|
462
|
+
return await super().get()
|
|
463
|
+
|
|
464
|
+
# Return default response
|
|
465
|
+
return web.json_response(self._default_get_response(request))
|
|
466
|
+
|
|
467
|
+
def _get_job_id_from_request(self, request: web.Request) -> Optional[str]:
|
|
468
|
+
"""
|
|
469
|
+
Extract job_id from aiohttp request.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
request: aiohttp Request object
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
job_id if found, None otherwise
|
|
476
|
+
"""
|
|
477
|
+
# Check query parameters
|
|
478
|
+
if self.job_id_param in request.query:
|
|
479
|
+
return request.query.get(self.job_id_param)
|
|
480
|
+
|
|
481
|
+
# Check match_info (URL path parameters)
|
|
482
|
+
if self.job_id_param in request.match_info:
|
|
483
|
+
return request.match_info.get(self.job_id_param)
|
|
484
|
+
|
|
485
|
+
return None
|
|
486
|
+
|
|
487
|
+
async def _handle_job_status_request(
|
|
488
|
+
self,
|
|
489
|
+
job_id: str,
|
|
490
|
+
request: web.Request
|
|
491
|
+
) -> Dict[str, Any]:
|
|
492
|
+
"""
|
|
493
|
+
Handle async job status request.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
job_id: Job identifier
|
|
497
|
+
request: aiohttp Request object
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
Dictionary with job status/result
|
|
501
|
+
"""
|
|
502
|
+
try:
|
|
503
|
+
# Fetch job (works with both RQ and your JobManager)
|
|
504
|
+
job = self.job_manager.fetch_job(job_id)
|
|
505
|
+
|
|
506
|
+
if job is None:
|
|
507
|
+
return {
|
|
508
|
+
'success': False,
|
|
509
|
+
'error': f"Job with id '{job_id}' not found",
|
|
510
|
+
'status_code': 404
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
# Build status response
|
|
514
|
+
job_status = {
|
|
515
|
+
'job_id': job.id,
|
|
516
|
+
'status': job.get_status(),
|
|
517
|
+
'created_at': str(job.created_at) if hasattr(job, 'created_at') else None,
|
|
518
|
+
'started_at': str(job.started_at) if hasattr(job, 'started_at') else None,
|
|
519
|
+
'ended_at': str(job.ended_at) if hasattr(job, 'ended_at') else None,
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
# Add elapsed time if available
|
|
523
|
+
if hasattr(job, 'elapsed_time') and job.elapsed_time:
|
|
524
|
+
job_status['elapsed_time'] = job.elapsed_time
|
|
525
|
+
|
|
526
|
+
# Handle completed jobs
|
|
527
|
+
if job.is_finished:
|
|
528
|
+
job_status['result'] = job.result
|
|
529
|
+
job_status['success'] = True
|
|
530
|
+
|
|
531
|
+
# Handle failed jobs
|
|
532
|
+
elif job.is_failed:
|
|
533
|
+
job_status['error'] = str(job.exc_info) if hasattr(job, 'exc_info') else "Unknown error"
|
|
534
|
+
job_status['success'] = False
|
|
535
|
+
|
|
536
|
+
# Job still in progress
|
|
537
|
+
else:
|
|
538
|
+
job_status['message'] = "Job is still in progress"
|
|
539
|
+
if hasattr(job, 'meta'):
|
|
540
|
+
job_status['progress'] = job.meta.get('progress')
|
|
541
|
+
|
|
542
|
+
return self._create_success_response(job_status)
|
|
543
|
+
|
|
544
|
+
except Exception as e:
|
|
545
|
+
return {
|
|
546
|
+
'success': False,
|
|
547
|
+
'error': f"Error fetching job: {str(e)}",
|
|
548
|
+
'status_code': 500
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
def _create_job_response(
|
|
552
|
+
self,
|
|
553
|
+
job_metadata: Dict[str, Any],
|
|
554
|
+
request: Optional[web.Request]
|
|
555
|
+
) -> Dict[str, Any]:
|
|
556
|
+
"""
|
|
557
|
+
Create standardized job creation response for aiohttp.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
job_metadata: Job information
|
|
561
|
+
request: aiohttp Request object
|
|
562
|
+
|
|
563
|
+
Returns:
|
|
564
|
+
Response dictionary
|
|
565
|
+
"""
|
|
566
|
+
response = {
|
|
567
|
+
'success': True,
|
|
568
|
+
'job_id': job_metadata['job_id'],
|
|
569
|
+
'status': job_metadata['status'],
|
|
570
|
+
'message': f"Job enqueued successfully on queue '{job_metadata['queue']}'",
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
# Add status URL
|
|
574
|
+
if request:
|
|
575
|
+
status_url = self._build_status_url(request, job_metadata['job_id'])
|
|
576
|
+
response['status_url'] = status_url
|
|
577
|
+
|
|
578
|
+
return response
|
|
579
|
+
|
|
580
|
+
def _build_status_url(self, request: web.Request, job_id: str) -> str:
|
|
581
|
+
"""
|
|
582
|
+
Build status URL for aiohttp request.
|
|
583
|
+
|
|
584
|
+
Args:
|
|
585
|
+
request: aiohttp Request object
|
|
586
|
+
job_id: Job identifier
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
Full URL for checking job status
|
|
590
|
+
"""
|
|
591
|
+
# Build URL with job_id parameter
|
|
592
|
+
scheme = request.scheme
|
|
593
|
+
host = request.host
|
|
594
|
+
path = request.path
|
|
595
|
+
|
|
596
|
+
return f"{scheme}://{host}{path}?{self.job_id_param}={job_id}"
|
|
597
|
+
|
|
598
|
+
def _create_success_response(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
599
|
+
"""Create standardized success response."""
|
|
600
|
+
return {
|
|
601
|
+
'success': True,
|
|
602
|
+
**data
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
def _default_get_response(self, request: web.Request) -> Dict[str, Any]:
|
|
606
|
+
"""Default GET response for aiohttp."""
|
|
607
|
+
return {
|
|
608
|
+
'message': 'No job_id provided. Use POST to create jobs or GET with job_id to check status.',
|
|
609
|
+
'usage': {
|
|
610
|
+
'create_job': f'POST to {request.path}',
|
|
611
|
+
'check_status': f'GET to {request.path}?{self.job_id_param}=<job_id>'
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
@classmethod
|
|
616
|
+
def get_async_methods(cls) -> Dict[str, Dict[str, Any]]:
|
|
617
|
+
"""
|
|
618
|
+
Get all methods decorated with @as_job.
|
|
619
|
+
|
|
620
|
+
Returns:
|
|
621
|
+
Dictionary mapping method names to configurations
|
|
622
|
+
"""
|
|
623
|
+
return {
|
|
624
|
+
name: method._job_config
|
|
625
|
+
for name, method in inspect.getmembers(cls)
|
|
626
|
+
if (hasattr(method, '_is_async_job') and method._is_async_job)
|
|
627
|
+
}
|