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,651 @@
|
|
|
1
|
+
from typing import Optional, Union, Dict, Any, Tuple
|
|
2
|
+
import random
|
|
3
|
+
import os
|
|
4
|
+
import asyncio
|
|
5
|
+
import ssl
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
8
|
+
from io import BytesIO
|
|
9
|
+
from email.message import Message
|
|
10
|
+
from urllib import parse
|
|
11
|
+
from urllib.parse import urlencode, urlparse
|
|
12
|
+
import urllib3
|
|
13
|
+
import aiofiles
|
|
14
|
+
# parsing:
|
|
15
|
+
from bs4 import BeautifulSoup as bs
|
|
16
|
+
from lxml import html, etree
|
|
17
|
+
# backoff retries:
|
|
18
|
+
import backoff
|
|
19
|
+
# aiohttp:
|
|
20
|
+
import aiohttp
|
|
21
|
+
from aiohttp import BasicAuth
|
|
22
|
+
# httpx
|
|
23
|
+
import httpx
|
|
24
|
+
# config:
|
|
25
|
+
from datamodel.typedefs import SafeDict
|
|
26
|
+
from datamodel.parsers.json import JSONContent, json_encoder # pylint: disable=E0611 # noqa
|
|
27
|
+
from navconfig.logging import logging
|
|
28
|
+
from proxylists.proxies import FreeProxy, Oxylabs
|
|
29
|
+
from ..conf import (
|
|
30
|
+
HTTPCLIENT_MAX_SEMAPHORE,
|
|
31
|
+
HTTPCLIENT_MAX_WORKERS,
|
|
32
|
+
GOOGLE_SEARCH_API_KEY,
|
|
33
|
+
GOOGLE_SEARCH_ENGINE_ID
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Suppress warnings
|
|
38
|
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
39
|
+
urllib3.disable_warnings()
|
|
40
|
+
for logger_name in ["httpx", "httpcore", "hpack"]:
|
|
41
|
+
logging.getLogger(logger_name).setLevel(logging.WARNING)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# User agents and impersonation profiles
|
|
45
|
+
UA_LIST = [
|
|
46
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
|
47
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
|
48
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
|
|
49
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0",
|
|
50
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
MOBILE_UA_LIST = [
|
|
54
|
+
"Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1",
|
|
55
|
+
"Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Mobile Safari/537.36",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
IMPERSONATES = (
|
|
59
|
+
"chrome_120", "chrome_123", "chrome_124", "chrome_126", "chrome_127", "chrome_128",
|
|
60
|
+
"chrome_129", "chrome_130", "chrome_131",
|
|
61
|
+
"safari_ios_18.1.1", "safari_18", "safari_18.2", "safari_ipad_18",
|
|
62
|
+
"edge_127", "edge_131", "firefox_128", "firefox_133",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
VALID_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class BackoffConfig:
|
|
69
|
+
"""Configuration for backoff retry behavior."""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
max_tries: int = 3,
|
|
74
|
+
max_time: int = 120,
|
|
75
|
+
exponential: bool = True,
|
|
76
|
+
jitter: bool = True,
|
|
77
|
+
retry_statuses: tuple = (429, 500, 502, 503, 504)
|
|
78
|
+
):
|
|
79
|
+
self.max_tries = max_tries
|
|
80
|
+
self.max_time = max_time
|
|
81
|
+
self.wait_gen = backoff.expo if exponential else backoff.constant
|
|
82
|
+
self.jitter = backoff.full_jitter if jitter else None
|
|
83
|
+
self.retry_statuses = retry_statuses
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class HTTPService:
|
|
87
|
+
"""
|
|
88
|
+
Unified HTTP client abstraction supporting both aiohttp and httpx.
|
|
89
|
+
|
|
90
|
+
Features:
|
|
91
|
+
- Proxy support (free and paid)
|
|
92
|
+
- User-Agent rotation
|
|
93
|
+
- Automatic retry with backoff
|
|
94
|
+
- HTTP/2 support
|
|
95
|
+
- Session management
|
|
96
|
+
- Response processing or raw response
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, *args, **kwargs):
|
|
100
|
+
# Proxy configuration
|
|
101
|
+
self.use_proxy: bool = kwargs.pop("use_proxy", False)
|
|
102
|
+
self._free_proxy: bool = kwargs.pop('free_proxy', False)
|
|
103
|
+
self._proxies: list = []
|
|
104
|
+
|
|
105
|
+
# User-Agent configuration
|
|
106
|
+
self.rotate_ua: bool = kwargs.pop("rotate_ua", True)
|
|
107
|
+
self._ua: str = random.choice(UA_LIST) if self.rotate_ua else UA_LIST[0]
|
|
108
|
+
|
|
109
|
+
# HTTP configuration
|
|
110
|
+
self.use_async: bool = kwargs.pop("use_async", True)
|
|
111
|
+
self.timeout: int = kwargs.get('timeout', 30)
|
|
112
|
+
self.use_streams: bool = kwargs.get('use_streams', True)
|
|
113
|
+
self.as_binary: bool = kwargs.get('as_binary', False)
|
|
114
|
+
self.accept: str = kwargs.get('accept', "application/json")
|
|
115
|
+
|
|
116
|
+
# HTTP/2 support
|
|
117
|
+
self.use_http2: bool = kwargs.pop('use_http2', False)
|
|
118
|
+
|
|
119
|
+
# Backoff configuration
|
|
120
|
+
backoff_config = kwargs.pop('backoff_config', None)
|
|
121
|
+
if backoff_config and isinstance(backoff_config, dict):
|
|
122
|
+
self.backoff_config = BackoffConfig(**backoff_config)
|
|
123
|
+
elif isinstance(backoff_config, BackoffConfig):
|
|
124
|
+
self.backoff_config = backoff_config
|
|
125
|
+
else:
|
|
126
|
+
self.backoff_config = BackoffConfig()
|
|
127
|
+
|
|
128
|
+
# Headers setup
|
|
129
|
+
self.headers: dict = kwargs.get('headers', {})
|
|
130
|
+
self.headers.update({
|
|
131
|
+
"Accept": self.accept,
|
|
132
|
+
"Accept-Encoding": "gzip, deflate, br" if self.use_http2 else "gzip, deflate",
|
|
133
|
+
"DNT": "1",
|
|
134
|
+
"Connection": "keep-alive",
|
|
135
|
+
"Upgrade-Insecure-Requests": "1",
|
|
136
|
+
"User-Agent": self._ua,
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
# Cookies
|
|
140
|
+
self.cookies = kwargs.get('cookies', {})
|
|
141
|
+
|
|
142
|
+
# Authentication
|
|
143
|
+
self.credentials: dict = kwargs.get('credentials', {})
|
|
144
|
+
self.auth_type: str = None
|
|
145
|
+
self.token_type: str = "Bearer"
|
|
146
|
+
self._user, self._pwd = None, None
|
|
147
|
+
self._setup_auth()
|
|
148
|
+
|
|
149
|
+
# Google API
|
|
150
|
+
self.google_api_key: str = kwargs.pop('google_api_key', GOOGLE_SEARCH_API_KEY)
|
|
151
|
+
self.google_cse: str = kwargs.pop('google_cse', GOOGLE_SEARCH_ENGINE_ID)
|
|
152
|
+
|
|
153
|
+
# Response handling
|
|
154
|
+
self.no_errors: dict = kwargs.get('no_errors', {})
|
|
155
|
+
self._default_parser: str = kwargs.pop('bs4_parser', 'html.parser')
|
|
156
|
+
|
|
157
|
+
# Utilities
|
|
158
|
+
self._encoder = JSONContent()
|
|
159
|
+
self._executor = ThreadPoolExecutor(max_workers=int(HTTPCLIENT_MAX_WORKERS))
|
|
160
|
+
self._semaphore = asyncio.Semaphore(int(HTTPCLIENT_MAX_SEMAPHORE))
|
|
161
|
+
self._debug: bool = kwargs.pop('debug', False)
|
|
162
|
+
self.logger = logging.getLogger('Parrot.HTTPService')
|
|
163
|
+
|
|
164
|
+
# Store remaining arguments
|
|
165
|
+
self.arguments = kwargs
|
|
166
|
+
|
|
167
|
+
def _setup_auth(self):
|
|
168
|
+
"""Setup authentication based on credentials."""
|
|
169
|
+
if "apikey" in self.credentials:
|
|
170
|
+
self.auth_type = "api_key"
|
|
171
|
+
self.headers["Authorization"] = f"{self.token_type} {self.credentials['apikey']}"
|
|
172
|
+
elif "username" in self.credentials:
|
|
173
|
+
self.auth_type = "basic"
|
|
174
|
+
self._user = self.credentials["username"]
|
|
175
|
+
self._pwd = self.credentials["password"]
|
|
176
|
+
elif "token" in self.credentials:
|
|
177
|
+
self.auth_type = "token"
|
|
178
|
+
self.headers["Authorization"] = f"{self.token_type} {self.credentials['token']}"
|
|
179
|
+
elif "key" in self.credentials:
|
|
180
|
+
self.auth_type = "key"
|
|
181
|
+
|
|
182
|
+
async def get_proxies(self, session_time: float = 1) -> list:
|
|
183
|
+
"""Get proxy list (free or paid)."""
|
|
184
|
+
if self._free_proxy:
|
|
185
|
+
return await FreeProxy().get_list()
|
|
186
|
+
else:
|
|
187
|
+
return await Oxylabs(session_time=session_time, timeout=10).get_list()
|
|
188
|
+
|
|
189
|
+
async def refresh_proxies(self):
|
|
190
|
+
"""Refresh the proxy list."""
|
|
191
|
+
if self.use_proxy:
|
|
192
|
+
self._proxies = await self.get_proxies()
|
|
193
|
+
|
|
194
|
+
def build_url(
|
|
195
|
+
self,
|
|
196
|
+
url: str,
|
|
197
|
+
queryparams: Optional[str] = None,
|
|
198
|
+
args: Optional[dict] = None,
|
|
199
|
+
params: Optional[dict] = None
|
|
200
|
+
) -> str:
|
|
201
|
+
"""Build complete URL with query parameters."""
|
|
202
|
+
if args:
|
|
203
|
+
url = str(url).format_map(SafeDict(**args))
|
|
204
|
+
if queryparams:
|
|
205
|
+
url += ("&" if "?" in url else "?") + queryparams
|
|
206
|
+
if params:
|
|
207
|
+
url += ("&" if "?" in url else "?") + urlencode(params)
|
|
208
|
+
|
|
209
|
+
if self._debug:
|
|
210
|
+
self.logger.debug(f"Built URL: {url}")
|
|
211
|
+
return url
|
|
212
|
+
|
|
213
|
+
def _create_ssl_context(self, verify_ssl: bool = True) -> ssl.SSLContext:
|
|
214
|
+
"""Create SSL context with secure defaults."""
|
|
215
|
+
ssl_context = ssl.create_default_context()
|
|
216
|
+
ssl_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
|
|
217
|
+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
|
218
|
+
|
|
219
|
+
if verify_ssl:
|
|
220
|
+
ssl_context.check_hostname = True
|
|
221
|
+
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
|
222
|
+
else:
|
|
223
|
+
ssl_context.check_hostname = False
|
|
224
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
225
|
+
|
|
226
|
+
return ssl_context
|
|
227
|
+
|
|
228
|
+
def _should_retry(self, exception) -> bool:
|
|
229
|
+
"""Determine if request should be retried based on exception."""
|
|
230
|
+
if isinstance(exception, (httpx.HTTPStatusError, aiohttp.ClientResponseError)):
|
|
231
|
+
status = getattr(exception, 'status_code', getattr(exception, 'status', None))
|
|
232
|
+
return status in self.backoff_config.retry_statuses
|
|
233
|
+
return isinstance(exception, (
|
|
234
|
+
httpx.TimeoutException,
|
|
235
|
+
aiohttp.ServerTimeoutError,
|
|
236
|
+
aiohttp.ClientError
|
|
237
|
+
))
|
|
238
|
+
|
|
239
|
+
@backoff.on_exception(
|
|
240
|
+
backoff.expo,
|
|
241
|
+
(httpx.HTTPStatusError, httpx.TimeoutException, httpx.RequestError),
|
|
242
|
+
max_tries=3,
|
|
243
|
+
max_time=120,
|
|
244
|
+
jitter=backoff.full_jitter,
|
|
245
|
+
giveup=lambda e: isinstance(e, httpx.HTTPStatusError) and
|
|
246
|
+
e.response.status_code not in [429, 500, 502, 503, 504]
|
|
247
|
+
)
|
|
248
|
+
async def httpx_request(
|
|
249
|
+
self,
|
|
250
|
+
url: str,
|
|
251
|
+
method: str = 'GET',
|
|
252
|
+
headers: Optional[Dict[str, str]] = None,
|
|
253
|
+
cookies: Optional[httpx.Cookies] = None,
|
|
254
|
+
params: Optional[Dict[str, Any]] = None,
|
|
255
|
+
data: Optional[Dict[str, Any]] = None,
|
|
256
|
+
use_json: bool = False,
|
|
257
|
+
use_proxy: bool = None,
|
|
258
|
+
use_ssl: bool = True,
|
|
259
|
+
verify_ssl: bool = True,
|
|
260
|
+
follow_redirects: bool = True,
|
|
261
|
+
full_response: bool = False,
|
|
262
|
+
download: bool = False,
|
|
263
|
+
filename: Optional[str] = None,
|
|
264
|
+
timeout: Optional[Union[int, float]] = None,
|
|
265
|
+
**kwargs
|
|
266
|
+
) -> Tuple[Any, Optional[Dict[str, Any]]]:
|
|
267
|
+
"""
|
|
268
|
+
Make async HTTP request using httpx with HTTP/2 support.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Tuple[result, error]: Processed result or raw response, and any error
|
|
272
|
+
"""
|
|
273
|
+
# Determine proxy usage
|
|
274
|
+
use_proxy = use_proxy if use_proxy is not None else self.use_proxy
|
|
275
|
+
proxy_config = None
|
|
276
|
+
|
|
277
|
+
if use_proxy:
|
|
278
|
+
proxies = await self.get_proxies()
|
|
279
|
+
if proxies:
|
|
280
|
+
proxy = proxies[0]
|
|
281
|
+
proxy_config = {
|
|
282
|
+
"http://": f"http://{proxy}",
|
|
283
|
+
"https://": f"http://{proxy}"
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# SSL configuration
|
|
287
|
+
ssl_context = self._create_ssl_context(verify_ssl) if use_ssl else None
|
|
288
|
+
|
|
289
|
+
# Setup transport with retry capability
|
|
290
|
+
transport = httpx.AsyncHTTPTransport(
|
|
291
|
+
retries=kwargs.pop('num_retries', 2),
|
|
292
|
+
verify=ssl_context
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Timeout configuration
|
|
296
|
+
timeout_config = httpx.Timeout(
|
|
297
|
+
timeout=timeout or self.timeout,
|
|
298
|
+
connect=kwargs.pop('connect_timeout', 5.0),
|
|
299
|
+
read=kwargs.pop('read_timeout', 20.0),
|
|
300
|
+
write=kwargs.pop('write_timeout', 5.0),
|
|
301
|
+
pool=kwargs.pop('pool_timeout', 20.0)
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Merge headers
|
|
305
|
+
request_headers = {**self.headers, **(headers or {})}
|
|
306
|
+
|
|
307
|
+
method = method.upper()
|
|
308
|
+
if method not in VALID_METHODS:
|
|
309
|
+
raise ValueError(f"Invalid HTTP method: {method}")
|
|
310
|
+
|
|
311
|
+
async with httpx.AsyncClient(
|
|
312
|
+
transport=transport,
|
|
313
|
+
headers=request_headers,
|
|
314
|
+
cookies=cookies,
|
|
315
|
+
proxy=proxy_config or None,
|
|
316
|
+
timeout=timeout_config,
|
|
317
|
+
http2=self.use_http2, # Enable HTTP/2
|
|
318
|
+
follow_redirects=follow_redirects,
|
|
319
|
+
**kwargs
|
|
320
|
+
) as client:
|
|
321
|
+
try:
|
|
322
|
+
# Build request arguments
|
|
323
|
+
request_args = {
|
|
324
|
+
"method": method,
|
|
325
|
+
"url": url,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if data:
|
|
329
|
+
request_args["json" if use_json else "data"] = data
|
|
330
|
+
if params:
|
|
331
|
+
request_args["params"] = params
|
|
332
|
+
|
|
333
|
+
# Make request
|
|
334
|
+
response = await client.request(**request_args)
|
|
335
|
+
response.raise_for_status()
|
|
336
|
+
|
|
337
|
+
# Return full response if requested
|
|
338
|
+
if full_response:
|
|
339
|
+
return response, None
|
|
340
|
+
|
|
341
|
+
# Process response
|
|
342
|
+
result, error = await self._process_response(
|
|
343
|
+
response,
|
|
344
|
+
url,
|
|
345
|
+
download=download,
|
|
346
|
+
filename=filename
|
|
347
|
+
)
|
|
348
|
+
return result, error
|
|
349
|
+
|
|
350
|
+
except httpx.TimeoutException as e:
|
|
351
|
+
self.logger.error(f"Request timeout: {url}")
|
|
352
|
+
raise
|
|
353
|
+
except httpx.HTTPStatusError as e:
|
|
354
|
+
self.logger.error(f"HTTP {e.response.status_code}: {url}")
|
|
355
|
+
raise
|
|
356
|
+
except httpx.RequestError as e:
|
|
357
|
+
self.logger.error(f"Request error: {e}")
|
|
358
|
+
raise
|
|
359
|
+
|
|
360
|
+
@backoff.on_exception(
|
|
361
|
+
backoff.expo,
|
|
362
|
+
(aiohttp.ClientError, aiohttp.ServerTimeoutError, aiohttp.ClientResponseError),
|
|
363
|
+
max_tries=3,
|
|
364
|
+
max_time=60,
|
|
365
|
+
jitter=backoff.full_jitter,
|
|
366
|
+
giveup=lambda e: isinstance(e, aiohttp.ClientResponseError) and
|
|
367
|
+
e.status not in [429, 500, 502, 503, 504]
|
|
368
|
+
)
|
|
369
|
+
async def aiohttp_request(
|
|
370
|
+
self,
|
|
371
|
+
url: str,
|
|
372
|
+
method: str = 'GET',
|
|
373
|
+
headers: Optional[Dict[str, str]] = None,
|
|
374
|
+
data: Optional[Dict[str, Any]] = None,
|
|
375
|
+
use_json: bool = False,
|
|
376
|
+
use_proxy: bool = None,
|
|
377
|
+
use_ssl: bool = False,
|
|
378
|
+
verify_ssl: bool = True,
|
|
379
|
+
full_response: bool = False,
|
|
380
|
+
download: bool = False,
|
|
381
|
+
filename: Optional[str] = None,
|
|
382
|
+
timeout: Optional[Union[int, float]] = None,
|
|
383
|
+
**kwargs
|
|
384
|
+
) -> Tuple[Any, Optional[Dict[str, Any]]]:
|
|
385
|
+
"""
|
|
386
|
+
Make async HTTP request using aiohttp.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Tuple[result, error]: Processed result or raw response, and any error
|
|
390
|
+
"""
|
|
391
|
+
# Determine proxy usage
|
|
392
|
+
use_proxy = use_proxy if use_proxy is not None else self.use_proxy
|
|
393
|
+
proxy = None
|
|
394
|
+
|
|
395
|
+
if use_proxy:
|
|
396
|
+
proxies = await self.get_proxies()
|
|
397
|
+
if proxies:
|
|
398
|
+
proxy = random.choice(proxies)
|
|
399
|
+
|
|
400
|
+
# Authentication setup
|
|
401
|
+
auth = None
|
|
402
|
+
if self.auth_type == "basic" and self._user:
|
|
403
|
+
auth = BasicAuth(self._user, self._pwd)
|
|
404
|
+
|
|
405
|
+
# SSL configuration
|
|
406
|
+
ssl_context = self._create_ssl_context(verify_ssl) if use_ssl else None
|
|
407
|
+
|
|
408
|
+
# Merge headers
|
|
409
|
+
request_headers = {**self.headers, **(headers or {})}
|
|
410
|
+
|
|
411
|
+
# Timeout configuration
|
|
412
|
+
timeout_config = aiohttp.ClientTimeout(total=timeout or self.timeout)
|
|
413
|
+
|
|
414
|
+
method = method.upper()
|
|
415
|
+
|
|
416
|
+
async with aiohttp.ClientSession(
|
|
417
|
+
headers=request_headers,
|
|
418
|
+
timeout=timeout_config,
|
|
419
|
+
auth=auth,
|
|
420
|
+
json_serialize=json_encoder,
|
|
421
|
+
) as session:
|
|
422
|
+
try:
|
|
423
|
+
# Build request kwargs
|
|
424
|
+
request_kwargs = {
|
|
425
|
+
"proxy": proxy,
|
|
426
|
+
"ssl": ssl_context if ssl_context else use_ssl,
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
# Make request
|
|
430
|
+
if use_json and data:
|
|
431
|
+
async with session.request(method, url, json=data, **request_kwargs) as response:
|
|
432
|
+
return await self._handle_aiohttp_response(
|
|
433
|
+
response, url, full_response, download, filename
|
|
434
|
+
)
|
|
435
|
+
else:
|
|
436
|
+
async with session.request(method, url, data=data, **request_kwargs) as response:
|
|
437
|
+
return await self._handle_aiohttp_response(
|
|
438
|
+
response, url, full_response, download, filename
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
except aiohttp.ClientError as e:
|
|
442
|
+
self.logger.error(f"aiohttp error: {e}")
|
|
443
|
+
raise
|
|
444
|
+
|
|
445
|
+
async def _handle_aiohttp_response(
|
|
446
|
+
self,
|
|
447
|
+
response,
|
|
448
|
+
url: str,
|
|
449
|
+
full_response: bool,
|
|
450
|
+
download: bool,
|
|
451
|
+
filename: Optional[str]
|
|
452
|
+
) -> Tuple[Any, Optional[Dict[str, Any]]]:
|
|
453
|
+
"""Handle aiohttp response."""
|
|
454
|
+
if full_response:
|
|
455
|
+
return response, None
|
|
456
|
+
|
|
457
|
+
# Check status
|
|
458
|
+
if response.status >= 400:
|
|
459
|
+
raise aiohttp.ClientResponseError(
|
|
460
|
+
request_info=response.request_info,
|
|
461
|
+
history=response.history,
|
|
462
|
+
status=response.status,
|
|
463
|
+
message=f"HTTP {response.status}",
|
|
464
|
+
headers=response.headers
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
return await self._process_response(response, url, download, filename)
|
|
468
|
+
|
|
469
|
+
async def _process_response(
|
|
470
|
+
self,
|
|
471
|
+
response,
|
|
472
|
+
url: str,
|
|
473
|
+
download: bool = False,
|
|
474
|
+
filename: Optional[str] = None
|
|
475
|
+
) -> Tuple[Any, Optional[Any]]:
|
|
476
|
+
"""
|
|
477
|
+
Unified response processing for both httpx and aiohttp.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
Tuple[result, error]
|
|
481
|
+
"""
|
|
482
|
+
error = None
|
|
483
|
+
result = None
|
|
484
|
+
|
|
485
|
+
# Download handling
|
|
486
|
+
if download:
|
|
487
|
+
result = await self._handle_download(response, url, filename)
|
|
488
|
+
return result, error
|
|
489
|
+
|
|
490
|
+
# Content type based processing
|
|
491
|
+
if self.accept == 'application/octet-stream':
|
|
492
|
+
data = await self._get_response_content(response)
|
|
493
|
+
buffer = BytesIO(data)
|
|
494
|
+
buffer.seek(0)
|
|
495
|
+
result = buffer
|
|
496
|
+
|
|
497
|
+
elif self.accept in ('text/html', 'application/xhtml+xml', 'application/xml'):
|
|
498
|
+
content = await self._get_response_content(response)
|
|
499
|
+
try:
|
|
500
|
+
if self.accept == 'text/html':
|
|
501
|
+
result = bs(content, self._default_parser)
|
|
502
|
+
else:
|
|
503
|
+
result = etree.fromstring(content)
|
|
504
|
+
except Exception as e:
|
|
505
|
+
error = e
|
|
506
|
+
result = content
|
|
507
|
+
|
|
508
|
+
elif self.accept == "application/json":
|
|
509
|
+
try:
|
|
510
|
+
result = await self._get_response_json(response)
|
|
511
|
+
except Exception as e:
|
|
512
|
+
# Fallback to text/HTML parsing
|
|
513
|
+
try:
|
|
514
|
+
text = await self._get_response_text(response)
|
|
515
|
+
result = bs(text, self._default_parser)
|
|
516
|
+
except Exception:
|
|
517
|
+
error = e
|
|
518
|
+
|
|
519
|
+
elif self.as_binary:
|
|
520
|
+
result = await self._get_response_content(response)
|
|
521
|
+
else:
|
|
522
|
+
result = await self._get_response_text(response)
|
|
523
|
+
|
|
524
|
+
return result, error
|
|
525
|
+
|
|
526
|
+
async def _handle_download(
|
|
527
|
+
self,
|
|
528
|
+
response,
|
|
529
|
+
url: str,
|
|
530
|
+
filename: Optional[str] = None
|
|
531
|
+
) -> Path:
|
|
532
|
+
"""Handle file download."""
|
|
533
|
+
if not filename:
|
|
534
|
+
filename = os.path.basename(url)
|
|
535
|
+
|
|
536
|
+
# Try to get filename from headers
|
|
537
|
+
content_disposition = response.headers.get("content-disposition")
|
|
538
|
+
if content_disposition:
|
|
539
|
+
msg = Message()
|
|
540
|
+
msg["Content-Disposition"] = content_disposition
|
|
541
|
+
header_filename = msg.get_param("filename", header="Content-Disposition")
|
|
542
|
+
utf8_filename = msg.get_param("filename*", header="Content-Disposition")
|
|
543
|
+
|
|
544
|
+
if utf8_filename:
|
|
545
|
+
_, utf8_filename = utf8_filename.split("''", 1)
|
|
546
|
+
filename = parse.unquote(utf8_filename)
|
|
547
|
+
elif header_filename:
|
|
548
|
+
filename = header_filename
|
|
549
|
+
|
|
550
|
+
filepath = Path(filename)
|
|
551
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
552
|
+
|
|
553
|
+
if filepath.exists():
|
|
554
|
+
self.logger.warning(f"File already exists: {filepath}")
|
|
555
|
+
return filepath
|
|
556
|
+
|
|
557
|
+
# Download file
|
|
558
|
+
total_size = response.headers.get("Content-Length")
|
|
559
|
+
self.logger.info(f"Downloading: {filepath} (size: {total_size})")
|
|
560
|
+
|
|
561
|
+
# Stream download
|
|
562
|
+
if self.use_streams and hasattr(response, 'content'):
|
|
563
|
+
async with aiofiles.open(filepath, 'wb') as f:
|
|
564
|
+
if hasattr(response.content, 'iter_chunked'): # aiohttp
|
|
565
|
+
async for chunk in response.content.iter_chunked(8192):
|
|
566
|
+
await f.write(chunk)
|
|
567
|
+
else: # httpx
|
|
568
|
+
async for chunk in response.aiter_bytes(8192):
|
|
569
|
+
await f.write(chunk)
|
|
570
|
+
else:
|
|
571
|
+
content = await self._get_response_content(response)
|
|
572
|
+
async with aiofiles.open(filepath, 'wb') as f:
|
|
573
|
+
await f.write(content)
|
|
574
|
+
|
|
575
|
+
self.logger.info(f"Downloaded: {filepath}")
|
|
576
|
+
return filepath
|
|
577
|
+
|
|
578
|
+
async def _get_response_json(self, response):
|
|
579
|
+
"""Get JSON from response (handles both httpx and aiohttp)."""
|
|
580
|
+
if hasattr(response, 'json'):
|
|
581
|
+
if asyncio.iscoroutinefunction(response.json):
|
|
582
|
+
return await response.json()
|
|
583
|
+
return response.json()
|
|
584
|
+
raise ValueError("Response does not support JSON")
|
|
585
|
+
|
|
586
|
+
async def _get_response_text(self, response):
|
|
587
|
+
"""Get text from response (handles both httpx and aiohttp)."""
|
|
588
|
+
if hasattr(response, 'text'):
|
|
589
|
+
if asyncio.iscoroutinefunction(response.text):
|
|
590
|
+
return await response.text()
|
|
591
|
+
return response.text
|
|
592
|
+
raise ValueError("Response does not support text")
|
|
593
|
+
|
|
594
|
+
async def _get_response_content(self, response):
|
|
595
|
+
"""Get binary content from response (handles both httpx and aiohttp)."""
|
|
596
|
+
if hasattr(response, 'content'):
|
|
597
|
+
if asyncio.iscoroutinefunction(response.content):
|
|
598
|
+
return await response.content()
|
|
599
|
+
return response.content
|
|
600
|
+
if hasattr(response, 'read'):
|
|
601
|
+
return await response.read()
|
|
602
|
+
if hasattr(response, 'aread'):
|
|
603
|
+
return await response.aread()
|
|
604
|
+
raise ValueError("Response does not support content reading")
|
|
605
|
+
|
|
606
|
+
async def request(
|
|
607
|
+
self,
|
|
608
|
+
url: str,
|
|
609
|
+
method: str = 'GET',
|
|
610
|
+
client: str = 'httpx',
|
|
611
|
+
**kwargs
|
|
612
|
+
) -> Tuple[Any, Optional[Dict[str, Any]]]:
|
|
613
|
+
"""
|
|
614
|
+
Unified request method that delegates to httpx or aiohttp.
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
url: URL to request
|
|
618
|
+
method: HTTP method
|
|
619
|
+
client: 'httpx' or 'aiohttp' (default: 'httpx')
|
|
620
|
+
**kwargs: Additional arguments passed to specific client
|
|
621
|
+
|
|
622
|
+
Returns:
|
|
623
|
+
Tuple[result, error]
|
|
624
|
+
"""
|
|
625
|
+
if client.lower() == 'httpx':
|
|
626
|
+
return await self.httpx_request(url, method, **kwargs)
|
|
627
|
+
elif client.lower() == 'aiohttp':
|
|
628
|
+
return await self.aiohttp_request(url, method, **kwargs)
|
|
629
|
+
else:
|
|
630
|
+
raise ValueError(f"Unsupported client: {client}. Use 'httpx' or 'aiohttp'")
|
|
631
|
+
|
|
632
|
+
# Convenience methods
|
|
633
|
+
async def get(self, url: str, client: str = 'httpx', **kwargs):
|
|
634
|
+
"""GET request."""
|
|
635
|
+
return await self.request(url, 'GET', client, **kwargs)
|
|
636
|
+
|
|
637
|
+
async def post(self, url: str, client: str = 'httpx', **kwargs):
|
|
638
|
+
"""POST request."""
|
|
639
|
+
return await self.request(url, 'POST', client, **kwargs)
|
|
640
|
+
|
|
641
|
+
async def put(self, url: str, client: str = 'httpx', **kwargs):
|
|
642
|
+
"""PUT request."""
|
|
643
|
+
return await self.request(url, 'PUT', client, **kwargs)
|
|
644
|
+
|
|
645
|
+
async def delete(self, url: str, client: str = 'httpx', **kwargs):
|
|
646
|
+
"""DELETE request."""
|
|
647
|
+
return await self.request(url, 'DELETE', client, **kwargs)
|
|
648
|
+
|
|
649
|
+
async def patch(self, url: str, client: str = 'httpx', **kwargs):
|
|
650
|
+
"""PATCH request."""
|
|
651
|
+
return await self.request(url, 'PATCH', client, **kwargs)
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Image processing and generation interfaces for Parrot.
|
|
3
|
+
"""
|
|
4
|
+
from .yolo import YOLOPlugin
|
|
5
|
+
from .vision import VisionTransformerPlugin
|
|
6
|
+
from .hash import ImageHashPlugin
|
|
7
|
+
from .abstract import ImagePlugin
|
|
8
|
+
from .exif import EXIFPlugin
|
|
9
|
+
from .zerodetect import ZeroShotDetectionPlugin
|
|
10
|
+
from .classify import ClassificationPlugin
|
|
11
|
+
from .detect import DetectionPlugin
|
|
12
|
+
from .analisys import AnalysisPlugin
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
PLUGINS = {
|
|
16
|
+
"exif": EXIFPlugin,
|
|
17
|
+
"hash": ImageHashPlugin,
|
|
18
|
+
"yolo": YOLOPlugin,
|
|
19
|
+
"vectorization": VisionTransformerPlugin,
|
|
20
|
+
"zeroshot": ZeroShotDetectionPlugin,
|
|
21
|
+
"classification": ClassificationPlugin,
|
|
22
|
+
'detection': DetectionPlugin,
|
|
23
|
+
'analysis': AnalysisPlugin
|
|
24
|
+
}
|