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.
Files changed (535) hide show
  1. agentui/.prettierrc +15 -0
  2. agentui/QUICKSTART.md +272 -0
  3. agentui/README.md +59 -0
  4. agentui/env.example +16 -0
  5. agentui/jsconfig.json +14 -0
  6. agentui/package-lock.json +4242 -0
  7. agentui/package.json +34 -0
  8. agentui/scripts/postinstall/apply-patches.mjs +260 -0
  9. agentui/src/app.css +61 -0
  10. agentui/src/app.d.ts +13 -0
  11. agentui/src/app.html +12 -0
  12. agentui/src/components/LoadingSpinner.svelte +64 -0
  13. agentui/src/components/ThemeSwitcher.svelte +159 -0
  14. agentui/src/components/index.js +4 -0
  15. agentui/src/lib/api/bots.ts +60 -0
  16. agentui/src/lib/api/chat.ts +22 -0
  17. agentui/src/lib/api/http.ts +25 -0
  18. agentui/src/lib/components/BotCard.svelte +33 -0
  19. agentui/src/lib/components/ChatBubble.svelte +63 -0
  20. agentui/src/lib/components/Toast.svelte +21 -0
  21. agentui/src/lib/config.ts +20 -0
  22. agentui/src/lib/stores/auth.svelte.ts +73 -0
  23. agentui/src/lib/stores/theme.svelte.js +64 -0
  24. agentui/src/lib/stores/toast.svelte.ts +31 -0
  25. agentui/src/lib/utils/conversation.ts +39 -0
  26. agentui/src/routes/+layout.svelte +20 -0
  27. agentui/src/routes/+page.svelte +232 -0
  28. agentui/src/routes/login/+page.svelte +200 -0
  29. agentui/src/routes/talk/[agentId]/+page.svelte +297 -0
  30. agentui/src/routes/talk/[agentId]/+page.ts +7 -0
  31. agentui/static/README.md +1 -0
  32. agentui/svelte.config.js +11 -0
  33. agentui/tailwind.config.ts +53 -0
  34. agentui/tsconfig.json +3 -0
  35. agentui/vite.config.ts +10 -0
  36. ai_parrot-0.17.2.dist-info/METADATA +472 -0
  37. ai_parrot-0.17.2.dist-info/RECORD +535 -0
  38. ai_parrot-0.17.2.dist-info/WHEEL +6 -0
  39. ai_parrot-0.17.2.dist-info/entry_points.txt +2 -0
  40. ai_parrot-0.17.2.dist-info/licenses/LICENSE +21 -0
  41. ai_parrot-0.17.2.dist-info/top_level.txt +6 -0
  42. crew-builder/.prettierrc +15 -0
  43. crew-builder/QUICKSTART.md +259 -0
  44. crew-builder/README.md +113 -0
  45. crew-builder/env.example +17 -0
  46. crew-builder/jsconfig.json +14 -0
  47. crew-builder/package-lock.json +4182 -0
  48. crew-builder/package.json +37 -0
  49. crew-builder/scripts/postinstall/apply-patches.mjs +260 -0
  50. crew-builder/src/app.css +62 -0
  51. crew-builder/src/app.d.ts +13 -0
  52. crew-builder/src/app.html +12 -0
  53. crew-builder/src/components/LoadingSpinner.svelte +64 -0
  54. crew-builder/src/components/ThemeSwitcher.svelte +149 -0
  55. crew-builder/src/components/index.js +9 -0
  56. crew-builder/src/lib/api/bots.ts +60 -0
  57. crew-builder/src/lib/api/chat.ts +80 -0
  58. crew-builder/src/lib/api/client.ts +56 -0
  59. crew-builder/src/lib/api/crew/crew.ts +136 -0
  60. crew-builder/src/lib/api/index.ts +5 -0
  61. crew-builder/src/lib/api/o365/auth.ts +65 -0
  62. crew-builder/src/lib/auth/auth.ts +54 -0
  63. crew-builder/src/lib/components/AgentNode.svelte +43 -0
  64. crew-builder/src/lib/components/BotCard.svelte +33 -0
  65. crew-builder/src/lib/components/ChatBubble.svelte +67 -0
  66. crew-builder/src/lib/components/ConfigPanel.svelte +278 -0
  67. crew-builder/src/lib/components/JsonTreeNode.svelte +76 -0
  68. crew-builder/src/lib/components/JsonViewer.svelte +24 -0
  69. crew-builder/src/lib/components/MarkdownEditor.svelte +48 -0
  70. crew-builder/src/lib/components/ThemeToggle.svelte +36 -0
  71. crew-builder/src/lib/components/Toast.svelte +67 -0
  72. crew-builder/src/lib/components/Toolbar.svelte +157 -0
  73. crew-builder/src/lib/components/index.ts +10 -0
  74. crew-builder/src/lib/config.ts +8 -0
  75. crew-builder/src/lib/stores/auth.svelte.ts +228 -0
  76. crew-builder/src/lib/stores/crewStore.ts +369 -0
  77. crew-builder/src/lib/stores/theme.svelte.js +145 -0
  78. crew-builder/src/lib/stores/toast.svelte.ts +69 -0
  79. crew-builder/src/lib/utils/conversation.ts +39 -0
  80. crew-builder/src/lib/utils/markdown.ts +122 -0
  81. crew-builder/src/lib/utils/talkHistory.ts +47 -0
  82. crew-builder/src/routes/+layout.svelte +20 -0
  83. crew-builder/src/routes/+page.svelte +539 -0
  84. crew-builder/src/routes/agents/+page.svelte +247 -0
  85. crew-builder/src/routes/agents/[agentId]/+page.svelte +288 -0
  86. crew-builder/src/routes/agents/[agentId]/+page.ts +7 -0
  87. crew-builder/src/routes/builder/+page.svelte +204 -0
  88. crew-builder/src/routes/crew/ask/+page.svelte +1052 -0
  89. crew-builder/src/routes/crew/ask/+page.ts +1 -0
  90. crew-builder/src/routes/integrations/o365/+page.svelte +304 -0
  91. crew-builder/src/routes/login/+page.svelte +197 -0
  92. crew-builder/src/routes/talk/[agentId]/+page.svelte +487 -0
  93. crew-builder/src/routes/talk/[agentId]/+page.ts +7 -0
  94. crew-builder/static/README.md +1 -0
  95. crew-builder/svelte.config.js +11 -0
  96. crew-builder/tailwind.config.ts +53 -0
  97. crew-builder/tsconfig.json +3 -0
  98. crew-builder/vite.config.ts +10 -0
  99. mcp_servers/calculator_server.py +309 -0
  100. parrot/__init__.py +27 -0
  101. parrot/__pycache__/__init__.cpython-310.pyc +0 -0
  102. parrot/__pycache__/version.cpython-310.pyc +0 -0
  103. parrot/_version.py +34 -0
  104. parrot/a2a/__init__.py +48 -0
  105. parrot/a2a/client.py +658 -0
  106. parrot/a2a/discovery.py +89 -0
  107. parrot/a2a/mixin.py +257 -0
  108. parrot/a2a/models.py +376 -0
  109. parrot/a2a/server.py +770 -0
  110. parrot/agents/__init__.py +29 -0
  111. parrot/bots/__init__.py +12 -0
  112. parrot/bots/a2a_agent.py +19 -0
  113. parrot/bots/abstract.py +3139 -0
  114. parrot/bots/agent.py +1129 -0
  115. parrot/bots/basic.py +9 -0
  116. parrot/bots/chatbot.py +669 -0
  117. parrot/bots/data.py +1618 -0
  118. parrot/bots/database/__init__.py +5 -0
  119. parrot/bots/database/abstract.py +3071 -0
  120. parrot/bots/database/cache.py +286 -0
  121. parrot/bots/database/models.py +468 -0
  122. parrot/bots/database/prompts.py +154 -0
  123. parrot/bots/database/retries.py +98 -0
  124. parrot/bots/database/router.py +269 -0
  125. parrot/bots/database/sql.py +41 -0
  126. parrot/bots/db/__init__.py +6 -0
  127. parrot/bots/db/abstract.py +556 -0
  128. parrot/bots/db/bigquery.py +602 -0
  129. parrot/bots/db/cache.py +85 -0
  130. parrot/bots/db/documentdb.py +668 -0
  131. parrot/bots/db/elastic.py +1014 -0
  132. parrot/bots/db/influx.py +898 -0
  133. parrot/bots/db/mock.py +96 -0
  134. parrot/bots/db/multi.py +783 -0
  135. parrot/bots/db/prompts.py +185 -0
  136. parrot/bots/db/sql.py +1255 -0
  137. parrot/bots/db/tools.py +212 -0
  138. parrot/bots/document.py +680 -0
  139. parrot/bots/hrbot.py +15 -0
  140. parrot/bots/kb.py +170 -0
  141. parrot/bots/mcp.py +36 -0
  142. parrot/bots/orchestration/README.md +463 -0
  143. parrot/bots/orchestration/__init__.py +1 -0
  144. parrot/bots/orchestration/agent.py +155 -0
  145. parrot/bots/orchestration/crew.py +3330 -0
  146. parrot/bots/orchestration/fsm.py +1179 -0
  147. parrot/bots/orchestration/hr.py +434 -0
  148. parrot/bots/orchestration/storage/__init__.py +4 -0
  149. parrot/bots/orchestration/storage/memory.py +100 -0
  150. parrot/bots/orchestration/storage/mixin.py +119 -0
  151. parrot/bots/orchestration/verify.py +202 -0
  152. parrot/bots/product.py +204 -0
  153. parrot/bots/prompts/__init__.py +96 -0
  154. parrot/bots/prompts/agents.py +155 -0
  155. parrot/bots/prompts/data.py +216 -0
  156. parrot/bots/prompts/output_generation.py +8 -0
  157. parrot/bots/scraper/__init__.py +3 -0
  158. parrot/bots/scraper/models.py +122 -0
  159. parrot/bots/scraper/scraper.py +1173 -0
  160. parrot/bots/scraper/templates.py +115 -0
  161. parrot/bots/stores/__init__.py +5 -0
  162. parrot/bots/stores/local.py +172 -0
  163. parrot/bots/webdev.py +81 -0
  164. parrot/cli.py +17 -0
  165. parrot/clients/__init__.py +16 -0
  166. parrot/clients/base.py +1491 -0
  167. parrot/clients/claude.py +1191 -0
  168. parrot/clients/factory.py +129 -0
  169. parrot/clients/google.py +4567 -0
  170. parrot/clients/gpt.py +1975 -0
  171. parrot/clients/grok.py +432 -0
  172. parrot/clients/groq.py +986 -0
  173. parrot/clients/hf.py +582 -0
  174. parrot/clients/models.py +18 -0
  175. parrot/conf.py +395 -0
  176. parrot/embeddings/__init__.py +9 -0
  177. parrot/embeddings/base.py +157 -0
  178. parrot/embeddings/google.py +98 -0
  179. parrot/embeddings/huggingface.py +74 -0
  180. parrot/embeddings/openai.py +84 -0
  181. parrot/embeddings/processor.py +88 -0
  182. parrot/exceptions.c +13868 -0
  183. parrot/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
  184. parrot/exceptions.pxd +22 -0
  185. parrot/exceptions.pxi +15 -0
  186. parrot/exceptions.pyx +44 -0
  187. parrot/generators/__init__.py +29 -0
  188. parrot/generators/base.py +200 -0
  189. parrot/generators/html.py +293 -0
  190. parrot/generators/react.py +205 -0
  191. parrot/generators/streamlit.py +203 -0
  192. parrot/generators/template.py +105 -0
  193. parrot/handlers/__init__.py +4 -0
  194. parrot/handlers/agent.py +861 -0
  195. parrot/handlers/agents/__init__.py +1 -0
  196. parrot/handlers/agents/abstract.py +900 -0
  197. parrot/handlers/bots.py +338 -0
  198. parrot/handlers/chat.py +915 -0
  199. parrot/handlers/creation.sql +192 -0
  200. parrot/handlers/crew/ARCHITECTURE.md +362 -0
  201. parrot/handlers/crew/README_BOTMANAGER_PERSISTENCE.md +303 -0
  202. parrot/handlers/crew/README_REDIS_PERSISTENCE.md +366 -0
  203. parrot/handlers/crew/__init__.py +0 -0
  204. parrot/handlers/crew/handler.py +801 -0
  205. parrot/handlers/crew/models.py +229 -0
  206. parrot/handlers/crew/redis_persistence.py +523 -0
  207. parrot/handlers/jobs/__init__.py +10 -0
  208. parrot/handlers/jobs/job.py +384 -0
  209. parrot/handlers/jobs/mixin.py +627 -0
  210. parrot/handlers/jobs/models.py +115 -0
  211. parrot/handlers/jobs/worker.py +31 -0
  212. parrot/handlers/models.py +596 -0
  213. parrot/handlers/o365_auth.py +105 -0
  214. parrot/handlers/stream.py +337 -0
  215. parrot/interfaces/__init__.py +6 -0
  216. parrot/interfaces/aws.py +143 -0
  217. parrot/interfaces/credentials.py +113 -0
  218. parrot/interfaces/database.py +27 -0
  219. parrot/interfaces/google.py +1123 -0
  220. parrot/interfaces/hierarchy.py +1227 -0
  221. parrot/interfaces/http.py +651 -0
  222. parrot/interfaces/images/__init__.py +0 -0
  223. parrot/interfaces/images/plugins/__init__.py +24 -0
  224. parrot/interfaces/images/plugins/abstract.py +58 -0
  225. parrot/interfaces/images/plugins/analisys.py +148 -0
  226. parrot/interfaces/images/plugins/classify.py +150 -0
  227. parrot/interfaces/images/plugins/classifybase.py +182 -0
  228. parrot/interfaces/images/plugins/detect.py +150 -0
  229. parrot/interfaces/images/plugins/exif.py +1103 -0
  230. parrot/interfaces/images/plugins/hash.py +52 -0
  231. parrot/interfaces/images/plugins/vision.py +104 -0
  232. parrot/interfaces/images/plugins/yolo.py +66 -0
  233. parrot/interfaces/images/plugins/zerodetect.py +197 -0
  234. parrot/interfaces/o365.py +978 -0
  235. parrot/interfaces/onedrive.py +822 -0
  236. parrot/interfaces/sharepoint.py +1435 -0
  237. parrot/interfaces/soap.py +257 -0
  238. parrot/loaders/__init__.py +8 -0
  239. parrot/loaders/abstract.py +1131 -0
  240. parrot/loaders/audio.py +199 -0
  241. parrot/loaders/basepdf.py +53 -0
  242. parrot/loaders/basevideo.py +1568 -0
  243. parrot/loaders/csv.py +409 -0
  244. parrot/loaders/docx.py +116 -0
  245. parrot/loaders/epubloader.py +316 -0
  246. parrot/loaders/excel.py +199 -0
  247. parrot/loaders/factory.py +55 -0
  248. parrot/loaders/files/__init__.py +0 -0
  249. parrot/loaders/files/abstract.py +39 -0
  250. parrot/loaders/files/html.py +26 -0
  251. parrot/loaders/files/text.py +63 -0
  252. parrot/loaders/html.py +152 -0
  253. parrot/loaders/markdown.py +442 -0
  254. parrot/loaders/pdf.py +373 -0
  255. parrot/loaders/pdfmark.py +320 -0
  256. parrot/loaders/pdftables.py +506 -0
  257. parrot/loaders/ppt.py +476 -0
  258. parrot/loaders/qa.py +63 -0
  259. parrot/loaders/splitters/__init__.py +10 -0
  260. parrot/loaders/splitters/base.py +138 -0
  261. parrot/loaders/splitters/md.py +228 -0
  262. parrot/loaders/splitters/token.py +143 -0
  263. parrot/loaders/txt.py +26 -0
  264. parrot/loaders/video.py +89 -0
  265. parrot/loaders/videolocal.py +218 -0
  266. parrot/loaders/videounderstanding.py +377 -0
  267. parrot/loaders/vimeo.py +167 -0
  268. parrot/loaders/web.py +599 -0
  269. parrot/loaders/youtube.py +504 -0
  270. parrot/manager/__init__.py +5 -0
  271. parrot/manager/manager.py +1030 -0
  272. parrot/mcp/__init__.py +28 -0
  273. parrot/mcp/adapter.py +105 -0
  274. parrot/mcp/cli.py +174 -0
  275. parrot/mcp/client.py +119 -0
  276. parrot/mcp/config.py +75 -0
  277. parrot/mcp/integration.py +842 -0
  278. parrot/mcp/oauth.py +933 -0
  279. parrot/mcp/server.py +225 -0
  280. parrot/mcp/transports/__init__.py +3 -0
  281. parrot/mcp/transports/base.py +279 -0
  282. parrot/mcp/transports/grpc_session.py +163 -0
  283. parrot/mcp/transports/http.py +312 -0
  284. parrot/mcp/transports/mcp.proto +108 -0
  285. parrot/mcp/transports/quic.py +1082 -0
  286. parrot/mcp/transports/sse.py +330 -0
  287. parrot/mcp/transports/stdio.py +309 -0
  288. parrot/mcp/transports/unix.py +395 -0
  289. parrot/mcp/transports/websocket.py +547 -0
  290. parrot/memory/__init__.py +16 -0
  291. parrot/memory/abstract.py +209 -0
  292. parrot/memory/agent.py +32 -0
  293. parrot/memory/cache.py +175 -0
  294. parrot/memory/core.py +555 -0
  295. parrot/memory/file.py +153 -0
  296. parrot/memory/mem.py +131 -0
  297. parrot/memory/redis.py +613 -0
  298. parrot/models/__init__.py +46 -0
  299. parrot/models/basic.py +118 -0
  300. parrot/models/compliance.py +208 -0
  301. parrot/models/crew.py +395 -0
  302. parrot/models/detections.py +654 -0
  303. parrot/models/generation.py +85 -0
  304. parrot/models/google.py +223 -0
  305. parrot/models/groq.py +23 -0
  306. parrot/models/openai.py +30 -0
  307. parrot/models/outputs.py +285 -0
  308. parrot/models/responses.py +938 -0
  309. parrot/notifications/__init__.py +743 -0
  310. parrot/openapi/__init__.py +3 -0
  311. parrot/openapi/components.yaml +641 -0
  312. parrot/openapi/config.py +322 -0
  313. parrot/outputs/__init__.py +32 -0
  314. parrot/outputs/formats/__init__.py +108 -0
  315. parrot/outputs/formats/altair.py +359 -0
  316. parrot/outputs/formats/application.py +122 -0
  317. parrot/outputs/formats/base.py +351 -0
  318. parrot/outputs/formats/bokeh.py +356 -0
  319. parrot/outputs/formats/card.py +424 -0
  320. parrot/outputs/formats/chart.py +436 -0
  321. parrot/outputs/formats/d3.py +255 -0
  322. parrot/outputs/formats/echarts.py +310 -0
  323. parrot/outputs/formats/generators/__init__.py +0 -0
  324. parrot/outputs/formats/generators/abstract.py +61 -0
  325. parrot/outputs/formats/generators/panel.py +145 -0
  326. parrot/outputs/formats/generators/streamlit.py +86 -0
  327. parrot/outputs/formats/generators/terminal.py +63 -0
  328. parrot/outputs/formats/holoviews.py +310 -0
  329. parrot/outputs/formats/html.py +147 -0
  330. parrot/outputs/formats/jinja2.py +46 -0
  331. parrot/outputs/formats/json.py +87 -0
  332. parrot/outputs/formats/map.py +933 -0
  333. parrot/outputs/formats/markdown.py +172 -0
  334. parrot/outputs/formats/matplotlib.py +237 -0
  335. parrot/outputs/formats/mixins/__init__.py +0 -0
  336. parrot/outputs/formats/mixins/emaps.py +855 -0
  337. parrot/outputs/formats/plotly.py +341 -0
  338. parrot/outputs/formats/seaborn.py +310 -0
  339. parrot/outputs/formats/table.py +397 -0
  340. parrot/outputs/formats/template_report.py +138 -0
  341. parrot/outputs/formats/yaml.py +125 -0
  342. parrot/outputs/formatter.py +152 -0
  343. parrot/outputs/templates/__init__.py +95 -0
  344. parrot/pipelines/__init__.py +0 -0
  345. parrot/pipelines/abstract.py +210 -0
  346. parrot/pipelines/detector.py +124 -0
  347. parrot/pipelines/models.py +90 -0
  348. parrot/pipelines/planogram.py +3002 -0
  349. parrot/pipelines/table.sql +97 -0
  350. parrot/plugins/__init__.py +106 -0
  351. parrot/plugins/importer.py +80 -0
  352. parrot/py.typed +0 -0
  353. parrot/registry/__init__.py +18 -0
  354. parrot/registry/registry.py +594 -0
  355. parrot/scheduler/__init__.py +1189 -0
  356. parrot/scheduler/models.py +60 -0
  357. parrot/security/__init__.py +16 -0
  358. parrot/security/prompt_injection.py +268 -0
  359. parrot/security/security_events.sql +25 -0
  360. parrot/services/__init__.py +1 -0
  361. parrot/services/mcp/__init__.py +8 -0
  362. parrot/services/mcp/config.py +13 -0
  363. parrot/services/mcp/server.py +295 -0
  364. parrot/services/o365_remote_auth.py +235 -0
  365. parrot/stores/__init__.py +7 -0
  366. parrot/stores/abstract.py +352 -0
  367. parrot/stores/arango.py +1090 -0
  368. parrot/stores/bigquery.py +1377 -0
  369. parrot/stores/cache.py +106 -0
  370. parrot/stores/empty.py +10 -0
  371. parrot/stores/faiss_store.py +1157 -0
  372. parrot/stores/kb/__init__.py +9 -0
  373. parrot/stores/kb/abstract.py +68 -0
  374. parrot/stores/kb/cache.py +165 -0
  375. parrot/stores/kb/doc.py +325 -0
  376. parrot/stores/kb/hierarchy.py +346 -0
  377. parrot/stores/kb/local.py +457 -0
  378. parrot/stores/kb/prompt.py +28 -0
  379. parrot/stores/kb/redis.py +659 -0
  380. parrot/stores/kb/store.py +115 -0
  381. parrot/stores/kb/user.py +374 -0
  382. parrot/stores/models.py +59 -0
  383. parrot/stores/pgvector.py +3 -0
  384. parrot/stores/postgres.py +2853 -0
  385. parrot/stores/utils/__init__.py +0 -0
  386. parrot/stores/utils/chunking.py +197 -0
  387. parrot/telemetry/__init__.py +3 -0
  388. parrot/telemetry/mixin.py +111 -0
  389. parrot/template/__init__.py +3 -0
  390. parrot/template/engine.py +259 -0
  391. parrot/tools/__init__.py +23 -0
  392. parrot/tools/abstract.py +644 -0
  393. parrot/tools/agent.py +363 -0
  394. parrot/tools/arangodbsearch.py +537 -0
  395. parrot/tools/arxiv_tool.py +188 -0
  396. parrot/tools/calculator/__init__.py +3 -0
  397. parrot/tools/calculator/operations/__init__.py +38 -0
  398. parrot/tools/calculator/operations/calculus.py +80 -0
  399. parrot/tools/calculator/operations/statistics.py +76 -0
  400. parrot/tools/calculator/tool.py +150 -0
  401. parrot/tools/cloudwatch.py +988 -0
  402. parrot/tools/codeinterpreter/__init__.py +127 -0
  403. parrot/tools/codeinterpreter/executor.py +371 -0
  404. parrot/tools/codeinterpreter/internals.py +473 -0
  405. parrot/tools/codeinterpreter/models.py +643 -0
  406. parrot/tools/codeinterpreter/prompts.py +224 -0
  407. parrot/tools/codeinterpreter/tool.py +664 -0
  408. parrot/tools/company_info/__init__.py +6 -0
  409. parrot/tools/company_info/tool.py +1138 -0
  410. parrot/tools/correlationanalysis.py +437 -0
  411. parrot/tools/database/abstract.py +286 -0
  412. parrot/tools/database/bq.py +115 -0
  413. parrot/tools/database/cache.py +284 -0
  414. parrot/tools/database/models.py +95 -0
  415. parrot/tools/database/pg.py +343 -0
  416. parrot/tools/databasequery.py +1159 -0
  417. parrot/tools/db.py +1800 -0
  418. parrot/tools/ddgo.py +370 -0
  419. parrot/tools/decorators.py +271 -0
  420. parrot/tools/dftohtml.py +282 -0
  421. parrot/tools/document.py +549 -0
  422. parrot/tools/ecs.py +819 -0
  423. parrot/tools/edareport.py +368 -0
  424. parrot/tools/elasticsearch.py +1049 -0
  425. parrot/tools/employees.py +462 -0
  426. parrot/tools/epson/__init__.py +96 -0
  427. parrot/tools/excel.py +683 -0
  428. parrot/tools/file/__init__.py +13 -0
  429. parrot/tools/file/abstract.py +76 -0
  430. parrot/tools/file/gcs.py +378 -0
  431. parrot/tools/file/local.py +284 -0
  432. parrot/tools/file/s3.py +511 -0
  433. parrot/tools/file/tmp.py +309 -0
  434. parrot/tools/file/tool.py +501 -0
  435. parrot/tools/file_reader.py +129 -0
  436. parrot/tools/flowtask/__init__.py +19 -0
  437. parrot/tools/flowtask/tool.py +761 -0
  438. parrot/tools/gittoolkit.py +508 -0
  439. parrot/tools/google/__init__.py +18 -0
  440. parrot/tools/google/base.py +169 -0
  441. parrot/tools/google/tools.py +1251 -0
  442. parrot/tools/googlelocation.py +5 -0
  443. parrot/tools/googleroutes.py +5 -0
  444. parrot/tools/googlesearch.py +5 -0
  445. parrot/tools/googlesitesearch.py +5 -0
  446. parrot/tools/googlevoice.py +2 -0
  447. parrot/tools/gvoice.py +695 -0
  448. parrot/tools/ibisworld/README.md +225 -0
  449. parrot/tools/ibisworld/__init__.py +11 -0
  450. parrot/tools/ibisworld/tool.py +366 -0
  451. parrot/tools/jiratoolkit.py +1718 -0
  452. parrot/tools/manager.py +1098 -0
  453. parrot/tools/math.py +152 -0
  454. parrot/tools/metadata.py +476 -0
  455. parrot/tools/msteams.py +1621 -0
  456. parrot/tools/msword.py +635 -0
  457. parrot/tools/multidb.py +580 -0
  458. parrot/tools/multistoresearch.py +369 -0
  459. parrot/tools/networkninja.py +167 -0
  460. parrot/tools/nextstop/__init__.py +4 -0
  461. parrot/tools/nextstop/base.py +286 -0
  462. parrot/tools/nextstop/employee.py +733 -0
  463. parrot/tools/nextstop/store.py +462 -0
  464. parrot/tools/notification.py +435 -0
  465. parrot/tools/o365/__init__.py +42 -0
  466. parrot/tools/o365/base.py +295 -0
  467. parrot/tools/o365/bundle.py +522 -0
  468. parrot/tools/o365/events.py +554 -0
  469. parrot/tools/o365/mail.py +992 -0
  470. parrot/tools/o365/onedrive.py +497 -0
  471. parrot/tools/o365/sharepoint.py +641 -0
  472. parrot/tools/openapi_toolkit.py +904 -0
  473. parrot/tools/openweather.py +527 -0
  474. parrot/tools/pdfprint.py +1001 -0
  475. parrot/tools/powerbi.py +518 -0
  476. parrot/tools/powerpoint.py +1113 -0
  477. parrot/tools/pricestool.py +146 -0
  478. parrot/tools/products/__init__.py +246 -0
  479. parrot/tools/prophet_tool.py +171 -0
  480. parrot/tools/pythonpandas.py +630 -0
  481. parrot/tools/pythonrepl.py +910 -0
  482. parrot/tools/qsource.py +436 -0
  483. parrot/tools/querytoolkit.py +395 -0
  484. parrot/tools/quickeda.py +827 -0
  485. parrot/tools/resttool.py +553 -0
  486. parrot/tools/retail/__init__.py +0 -0
  487. parrot/tools/retail/bby.py +528 -0
  488. parrot/tools/sandboxtool.py +703 -0
  489. parrot/tools/sassie/__init__.py +352 -0
  490. parrot/tools/scraping/__init__.py +7 -0
  491. parrot/tools/scraping/docs/select.md +466 -0
  492. parrot/tools/scraping/documentation.md +1278 -0
  493. parrot/tools/scraping/driver.py +436 -0
  494. parrot/tools/scraping/models.py +576 -0
  495. parrot/tools/scraping/options.py +85 -0
  496. parrot/tools/scraping/orchestrator.py +517 -0
  497. parrot/tools/scraping/readme.md +740 -0
  498. parrot/tools/scraping/tool.py +3115 -0
  499. parrot/tools/seasonaldetection.py +642 -0
  500. parrot/tools/shell_tool/__init__.py +5 -0
  501. parrot/tools/shell_tool/actions.py +408 -0
  502. parrot/tools/shell_tool/engine.py +155 -0
  503. parrot/tools/shell_tool/models.py +322 -0
  504. parrot/tools/shell_tool/tool.py +442 -0
  505. parrot/tools/site_search.py +214 -0
  506. parrot/tools/textfile.py +418 -0
  507. parrot/tools/think.py +378 -0
  508. parrot/tools/toolkit.py +298 -0
  509. parrot/tools/webapp_tool.py +187 -0
  510. parrot/tools/whatif.py +1279 -0
  511. parrot/tools/workday/MULTI_WSDL_EXAMPLE.md +249 -0
  512. parrot/tools/workday/__init__.py +6 -0
  513. parrot/tools/workday/models.py +1389 -0
  514. parrot/tools/workday/tool.py +1293 -0
  515. parrot/tools/yfinance_tool.py +306 -0
  516. parrot/tools/zipcode.py +217 -0
  517. parrot/utils/__init__.py +2 -0
  518. parrot/utils/helpers.py +73 -0
  519. parrot/utils/parsers/__init__.py +5 -0
  520. parrot/utils/parsers/toml.c +12078 -0
  521. parrot/utils/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
  522. parrot/utils/parsers/toml.pyx +21 -0
  523. parrot/utils/toml.py +11 -0
  524. parrot/utils/types.cpp +20936 -0
  525. parrot/utils/types.cpython-310-x86_64-linux-gnu.so +0 -0
  526. parrot/utils/types.pyx +213 -0
  527. parrot/utils/uv.py +11 -0
  528. parrot/version.py +10 -0
  529. parrot/yaml-rs/Cargo.lock +350 -0
  530. parrot/yaml-rs/Cargo.toml +19 -0
  531. parrot/yaml-rs/pyproject.toml +19 -0
  532. parrot/yaml-rs/python/yaml_rs/__init__.py +81 -0
  533. parrot/yaml-rs/src/lib.rs +222 -0
  534. requirements/docker-compose.yml +24 -0
  535. requirements/requirements-dev.txt +21 -0
@@ -0,0 +1,822 @@
1
+ import os
2
+ from typing import List, Optional, Union, Dict, Any
3
+ import contextlib
4
+ from pathlib import Path
5
+ from urllib.parse import quote, unquote
6
+ import asyncio
7
+ import aiofiles
8
+ from tqdm import tqdm
9
+ from io import BytesIO
10
+ import httpx
11
+ import aiohttp
12
+ import pandas as pd
13
+ # Microsoft Graph SDK
14
+ from msgraph.generated.models.drive_item import DriveItem
15
+ from msgraph.generated.models.folder import Folder
16
+ from msgraph.generated.models.file import File
17
+ from msgraph.generated.models.upload_session import UploadSession
18
+ from msgraph.generated.drives.item.items.item.create_upload_session.create_upload_session_post_request_body import (
19
+ CreateUploadSessionPostRequestBody
20
+ )
21
+ from msgraph.generated.models.drive_item_uploadable_properties import DriveItemUploadableProperties
22
+ from .o365 import O365Client
23
+
24
+
25
+ class OneDriveClient(O365Client):
26
+ """
27
+ OneDrive Client - Migrated to Microsoft Graph SDK
28
+
29
+ Uses Microsoft Graph SDK for all OneDrive operations.
30
+
31
+ Interface for Managing connections to OneDrive resources.
32
+
33
+ Methods:
34
+ file_list: Lists files in a specified OneDrive folder.
35
+ file_search: Searches for files matching a query.
36
+ file_download: Downloads a single file by its item ID.
37
+ download_files: Downloads multiple files provided as a list of dictionaries containing file info.
38
+ folder_download: Downloads a folder and its contents recursively.
39
+ file_delete: Deletes a file or folder by its item ID.
40
+ upload_files: Uploads multiple files to a specified OneDrive folder.
41
+ upload_file: Uploads a single file to OneDrive.
42
+ upload_folder: Uploads a local folder and its contents to OneDrive recursively.
43
+ download_excel_file: Downloads Excel files and optionally converts to pandas DataFrame.
44
+ upload_dataframe_as_excel: Uploads pandas DataFrame as Excel file to OneDrive.
45
+
46
+ """
47
+
48
+ def __init__(self, *args, **kwargs):
49
+ super().__init__(*args, **kwargs)
50
+ # OneDrive-specific properties
51
+ self.directory: Optional[str] = None
52
+ self.filename: Optional[str] = None
53
+ self._srcfiles: List = []
54
+ self._destination: List = []
55
+
56
+ # Upload settings
57
+ self.small_file_threshold = 4 * 1024 * 1024 # 4 MB
58
+ self.chunk_size = 10 * 1024 * 1024 # 10 MB
59
+
60
+ # Cached OneDrive objects
61
+ self._drive_id: Optional[str] = None
62
+ self._drive_info: Optional[DriveItem] = None
63
+
64
+ def connection(self):
65
+ """
66
+ Establish OneDrive connection using the migrated O365Client.
67
+
68
+ This replaces the old office365-rest-python-client authentication
69
+ with Microsoft Graph SDK authentication.
70
+ """
71
+ # Use the parent O365Client connection method
72
+ super().connection()
73
+
74
+ self.logger.info("OneDrive connection established successfully")
75
+ return self
76
+
77
+ async def verify_onedrive_access(self):
78
+ """Verify OneDrive access and cache drive info."""
79
+ try:
80
+ # Resolve and cache drive info
81
+ self._drive_info = await self._resolve_drive()
82
+ self.logger.info(f"OneDrive accessible: {self._drive_info.name or 'Personal OneDrive'}")
83
+
84
+ except Exception as e:
85
+ self.logger.error(f"OneDrive access verification failed: {e}")
86
+ raise RuntimeError(f"OneDrive access verification failed: {e}") from e
87
+
88
+ async def _resolve_drive(self) -> DriveItem:
89
+ """Resolve OneDrive using Graph API."""
90
+ if self._drive_info:
91
+ return self._drive_info
92
+
93
+ try:
94
+ # Get user's personal OneDrive
95
+ drive = await self.graph_client.me.drive.get()
96
+
97
+ if drive and drive.id:
98
+ self._drive_id = drive.id
99
+ self._drive_info = drive
100
+ self.logger.info(f"OneDrive resolved: {drive.name or drive.id}")
101
+ return drive
102
+ else:
103
+ raise RuntimeError("Could not resolve OneDrive")
104
+
105
+ except Exception as e:
106
+ raise RuntimeError(f"Failed to resolve OneDrive: {e}") from e
107
+
108
+ async def _ensure_folder(self, folder_path: str, create: bool = True) -> DriveItem:
109
+ """Ensure folder exists in OneDrive using Graph API."""
110
+ drive_info = await self._resolve_drive()
111
+ drive_id = drive_info.id
112
+
113
+ folder_path = (folder_path or "").strip("/")
114
+ if not folder_path:
115
+ # Return root folder
116
+ root = await self.graph_client.drives.by_drive_id(drive_id).root.get()
117
+ return root
118
+
119
+ # Try to resolve existing folder
120
+ try:
121
+ folder_item = await self.graph_client.drives.by_drive_id(drive_id)\
122
+ .items.by_drive_item_id(f"root:/{folder_path}:").get()
123
+ if folder_item:
124
+ return folder_item
125
+ except Exception:
126
+ if not create:
127
+ raise
128
+
129
+ # Create folder recursively
130
+ root = await self.graph_client.drives.by_drive_id(drive_id).root.get()
131
+ parent_id = root.id
132
+
133
+ for segment in [s for s in folder_path.split("/") if s]:
134
+ # Check if segment already exists
135
+ children = await self.graph_client.drives.by_drive_id(drive_id)\
136
+ .items.by_drive_item_id(parent_id).children.get()
137
+
138
+ existing_folder = None
139
+ if children and children.value:
140
+ for child in children.value:
141
+ if child.name == segment and child.folder:
142
+ existing_folder = child
143
+ break
144
+
145
+ if existing_folder:
146
+ parent_id = existing_folder.id
147
+ continue
148
+
149
+ # Create new folder
150
+ new_folder = DriveItem()
151
+ new_folder.name = segment
152
+ new_folder.folder = Folder()
153
+ new_folder.additional_data = {
154
+ "@microsoft.graph.conflictBehavior": "replace"
155
+ }
156
+
157
+ created = await self.graph_client.drives.by_drive_id(drive_id)\
158
+ .items.by_drive_item_id(parent_id).children.post(new_folder)
159
+ parent_id = created.id
160
+ self.logger.info(f"Created folder: {segment}")
161
+
162
+ # Return the final folder
163
+ final_folder = await self.graph_client.drives.by_drive_id(drive_id)\
164
+ .items.by_drive_item_id(parent_id).get()
165
+ return final_folder
166
+
167
+ async def file_list(self, folder_path: str = None) -> List[dict]:
168
+ """
169
+ List files in a given OneDrive folder using Microsoft Graph API.
170
+ """
171
+ try:
172
+ drive_info = await self._resolve_drive()
173
+ drive_id = drive_info.id
174
+
175
+ if folder_path:
176
+ # Get specific folder
177
+ folder_path = folder_path.strip("/")
178
+ try:
179
+ folder_item = await self.graph_client.drives.by_drive_id(drive_id)\
180
+ .items.by_drive_item_id(f"root:/{folder_path}:").get()
181
+ except Exception as e:
182
+ raise RuntimeError(f"Folder '{folder_path}' not found: {e}") from e
183
+ else:
184
+ # Get root folder
185
+ folder_item = await self.graph_client.drives.by_drive_id(drive_id).root.get()
186
+
187
+ # Get children
188
+ children = await self.graph_client.drives.by_drive_id(drive_id)\
189
+ .items.by_drive_item_id(folder_item.id).children.get()
190
+
191
+ file_list = []
192
+ if children and children.value:
193
+ for item in children.value:
194
+ file_info = {
195
+ "name": item.name,
196
+ "id": item.id,
197
+ "webUrl": item.web_url,
198
+ "path": self._get_item_path_from_item(item),
199
+ "isFolder": item.folder is not None,
200
+ "size": item.size or 0,
201
+ "modified": item.last_modified_date_time.isoformat() if item.last_modified_date_time else None
202
+ }
203
+ file_list.append(file_info)
204
+
205
+ return file_list
206
+
207
+ except Exception as err:
208
+ self.logger.error(f"Error listing files: {err}")
209
+ raise RuntimeError(f"Error listing files: {err}") from err
210
+
211
+ async def file_search(self, search_query: str) -> List[dict]:
212
+ """
213
+ Search for files in OneDrive matching the search query using Microsoft Graph API.
214
+ """
215
+ try:
216
+ drive_info = await self._resolve_drive()
217
+ drive_id = drive_info.id
218
+
219
+ # Use Graph API search
220
+ search_results = await self.graph_client.drives.by_drive_id(drive_id)\
221
+ .search_with_q(search_query).get()
222
+
223
+ results = []
224
+ if search_results and search_results.value:
225
+ for item in search_results.value:
226
+ if item.file: # Only include files, not folders
227
+ file_info = {
228
+ "name": item.name,
229
+ "id": item.id,
230
+ "webUrl": item.web_url,
231
+ "path": self._get_item_path_from_item(item),
232
+ "isFolder": False,
233
+ "size": item.size or 0,
234
+ "modified": item.last_modified_date_time.isoformat() if item.last_modified_date_time else None # noqa
235
+ }
236
+ results.append(file_info)
237
+
238
+ return results
239
+
240
+ except Exception as err:
241
+ self.logger.error(f"Error searching files: {err}")
242
+ raise RuntimeError(f"Error searching files: {err}") from err
243
+
244
+ async def file_download(self, item_id: str, destination: Path) -> str:
245
+ """
246
+ Download a file from OneDrive by item ID using Microsoft Graph API.
247
+ """
248
+ try:
249
+ drive_info = await self._resolve_drive()
250
+ drive_id = drive_info.id
251
+
252
+ # Get item info
253
+ item = await self.graph_client.drives.by_drive_id(drive_id)\
254
+ .items.by_drive_item_id(item_id).get()
255
+
256
+ if not item.file:
257
+ raise RuntimeError(f"Item {item_id} is not a file")
258
+
259
+ self.logger.info(f"Downloading {item.name} to {destination}")
260
+
261
+ # Get download URL if available
262
+ download_url = ""
263
+ try:
264
+ add = getattr(item, "additional_data", {}) or {}
265
+ download_url = add.get("@microsoft.graph.downloadUrl", "") or ""
266
+ except Exception:
267
+ download_url = ""
268
+
269
+ # Ensure destination directory exists
270
+ destination.parent.mkdir(parents=True, exist_ok=True)
271
+
272
+ if download_url:
273
+ # Stream via downloadUrl
274
+ async with httpx.AsyncClient(follow_redirects=True, timeout=None) as client:
275
+ async with client.stream("GET", download_url) as resp:
276
+ resp.raise_for_status()
277
+ async with aiofiles.open(destination, "wb") as f:
278
+ async for chunk in resp.aiter_bytes(1 << 20): # 1 MiB
279
+ await f.write(chunk)
280
+ else:
281
+ # Fallback: GET /content via Graph
282
+ content = await self.graph_client.drives.by_drive_id(drive_id)\
283
+ .items.by_drive_item_id(item_id).content.get()
284
+ async with aiofiles.open(destination, "wb") as f:
285
+ await f.write(content)
286
+
287
+ self.logger.info(f"Downloaded {item.name} successfully")
288
+ return str(destination)
289
+
290
+ except Exception as err:
291
+ self.logger.error(f"Error downloading file {item_id}: {err}")
292
+ raise RuntimeError(f"Error downloading file {item_id}: {err}") from err
293
+
294
+ async def download_files(self, items: List[dict], destination_folder: Path) -> List[str]:
295
+ """
296
+ Download multiple files from OneDrive using Microsoft Graph API.
297
+ """
298
+ downloaded_files = []
299
+ destination_folder = Path(destination_folder)
300
+ destination_folder.mkdir(parents=True, exist_ok=True)
301
+
302
+ for item in items:
303
+ try:
304
+ item_id = item.get("id")
305
+ file_name = item.get("name")
306
+ if not item_id or not file_name:
307
+ self.logger.warning(f"Skipping invalid item: {item}")
308
+ continue
309
+
310
+ destination = destination_folder / file_name
311
+ downloaded_path = await self.file_download(item_id, destination)
312
+ downloaded_files.append(downloaded_path)
313
+
314
+ except Exception as e:
315
+ self.logger.error(f"Failed to download {item.get('name', 'unknown')}: {e}")
316
+ continue
317
+
318
+ return downloaded_files
319
+
320
+ async def folder_download(self, folder_id: str, destination_folder: Path) -> bool:
321
+ """
322
+ Download a folder and its contents from OneDrive using Microsoft Graph API.
323
+ """
324
+ try:
325
+ drive_info = await self._resolve_drive()
326
+ drive_id = drive_info.id
327
+
328
+ # Get folder info
329
+ folder_item = await self.graph_client.drives.by_drive_id(drive_id)\
330
+ .items.by_drive_item_id(folder_id).get()
331
+
332
+ if not folder_item.folder:
333
+ raise RuntimeError(f"Item {folder_id} is not a folder")
334
+
335
+ await self._download_folder_recursive(drive_id, folder_item, destination_folder)
336
+ return True
337
+
338
+ except Exception as err:
339
+ self.logger.error(f"Error downloading folder {folder_id}: {err}")
340
+ raise RuntimeError(f"Error downloading folder {folder_id}: {err}") from err
341
+
342
+ async def _download_folder_recursive(self, drive_id: str, folder_item: DriveItem, local_path: Path):
343
+ """
344
+ Recursively download a folder's contents using Microsoft Graph API.
345
+ """
346
+ if not local_path.exists():
347
+ local_path.mkdir(parents=True, exist_ok=True)
348
+
349
+ # Get children
350
+ children = await self.graph_client.drives.by_drive_id(drive_id)\
351
+ .items.by_drive_item_id(folder_item.id).children.get()
352
+
353
+ if children and children.value:
354
+ for item in children.value:
355
+ item_path = local_path / item.name
356
+
357
+ if item.folder:
358
+ # Recursively download subfolder
359
+ await self._download_folder_recursive(drive_id, item, item_path)
360
+ else:
361
+ # Download file
362
+ await self.file_download(item.id, item_path)
363
+
364
+ async def file_delete(self, item_id: str) -> bool:
365
+ """
366
+ Delete a file or folder in OneDrive by item ID using Microsoft Graph API.
367
+ """
368
+ try:
369
+ drive_info = await self._resolve_drive()
370
+ drive_id = drive_info.id
371
+
372
+ # Get item info for logging
373
+ try:
374
+ item = await self.graph_client.drives.by_drive_id(drive_id)\
375
+ .items.by_drive_item_id(item_id).get()
376
+ item_name = item.name
377
+ item_type = "folder" if item.folder else "file"
378
+ except Exception:
379
+ item_name = f"item {item_id}"
380
+ item_type = "item"
381
+
382
+ # Delete the item
383
+ await self.graph_client.drives.by_drive_id(drive_id)\
384
+ .items.by_drive_item_id(item_id).delete()
385
+
386
+ self.logger.info(f"Deleted {item_type}: {item_name}")
387
+ return True
388
+
389
+ except Exception as err:
390
+ self.logger.error(f"Error deleting item {item_id}: {err}")
391
+ raise RuntimeError(f"Error deleting item {item_id}: {err}") from err
392
+
393
+ async def upload_file(self, file_path: Path, destination_folder: str = None) -> dict:
394
+ """
395
+ Upload a single file to OneDrive using Microsoft Graph API.
396
+ """
397
+ try:
398
+ file_path = Path(file_path)
399
+ if not file_path.exists():
400
+ raise RuntimeError(f"File not found: {file_path}")
401
+
402
+ drive_info = await self._resolve_drive()
403
+ drive_id = drive_info.id
404
+
405
+ # Ensure destination folder exists
406
+ if destination_folder:
407
+ folder_info = await self._ensure_folder(destination_folder, create=True)
408
+ parent_id = folder_info.id
409
+ else:
410
+ root = await self.graph_client.drives.by_drive_id(drive_id).root.get()
411
+ parent_id = root.id
412
+
413
+ file_size = file_path.stat().st_size
414
+ target_name = file_path.name
415
+
416
+ self.logger.info(f"Uploading {target_name} ({file_size:,} bytes)")
417
+
418
+ if file_size <= self.small_file_threshold:
419
+ # Small file upload
420
+ result = await self._upload_small_file(drive_id, parent_id, file_path, target_name)
421
+ else:
422
+ # Large file upload
423
+ upload_session = await self._create_upload_session(drive_id, parent_id, target_name)
424
+ result = await self._upload_large_file(upload_session, file_path)
425
+
426
+ self.logger.info(f"Uploaded successfully: {result.name}")
427
+
428
+ return {
429
+ "name": result.name,
430
+ "id": result.id,
431
+ "webUrl": result.web_url,
432
+ "size": getattr(result, 'size', file_size)
433
+ }
434
+
435
+ except Exception as err:
436
+ self.logger.error(f"Error uploading file {file_path}: {err}")
437
+ raise RuntimeError(f"Error uploading file {file_path}: {err}") from err
438
+
439
+ async def upload_files(self, files: List[Path], destination_folder: str = None) -> List[dict]:
440
+ """
441
+ Upload multiple files to OneDrive using Microsoft Graph API.
442
+ """
443
+ uploaded_files = []
444
+
445
+ for file_path in files:
446
+ try:
447
+ uploaded_item = await self.upload_file(file_path, destination_folder)
448
+ uploaded_files.append(uploaded_item)
449
+ except Exception as e:
450
+ self.logger.error(f"Failed to upload {file_path}: {e}")
451
+ continue
452
+
453
+ return uploaded_files
454
+
455
+ async def upload_folder(self, local_folder: Path, destination_folder: str = None) -> List[dict]:
456
+ """
457
+ Upload a local folder and its contents to OneDrive using Microsoft Graph API.
458
+ """
459
+ try:
460
+ local_path = Path(local_folder)
461
+ if not local_path.exists() or not local_path.is_dir():
462
+ raise FileNotFoundError(
463
+ f"Local folder does not exist or is not a directory: {local_folder}"
464
+ )
465
+
466
+ uploaded_items = []
467
+
468
+ # Get all files in the folder recursively
469
+ for root, dirs, files in os.walk(local_path):
470
+ relative_path = Path(root).relative_to(local_path)
471
+
472
+ # Calculate OneDrive destination path
473
+ if destination_folder:
474
+ onedrive_path = f"{destination_folder}/{relative_path}".replace("\\", "/").strip("/")
475
+ else:
476
+ onedrive_path = str(relative_path).replace("\\", "/")
477
+
478
+ # Ensure the directory exists in OneDrive
479
+ if onedrive_path and onedrive_path != ".":
480
+ await self._ensure_folder(onedrive_path, create=True)
481
+
482
+ # Upload files in this directory
483
+ for file_name in files:
484
+ file_path = Path(root) / file_name
485
+ try:
486
+ uploaded_item = await self.upload_file(
487
+ file_path,
488
+ onedrive_path if onedrive_path != "." else None
489
+ )
490
+ uploaded_items.append(uploaded_item)
491
+ except Exception as e:
492
+ self.logger.error(f"Failed to upload {file_path}: {e}")
493
+ continue
494
+
495
+ return uploaded_items
496
+
497
+ except Exception as err:
498
+ self.logger.error(f"Error uploading folder {local_folder}: {err}")
499
+ raise RuntimeError(f"Error uploading folder {local_folder}: {err}") from err
500
+
501
+ async def download_excel_file(self, item_id: str, destination: Path = None, as_pandas: bool = False):
502
+ """
503
+ Download an Excel file from OneDrive by item ID using Microsoft Graph API.
504
+ If `as_pandas` is True, return as a pandas DataFrame.
505
+ If `as_pandas` is False, save to the destination path.
506
+ """
507
+ try:
508
+ drive_info = await self._resolve_drive()
509
+ drive_id = drive_info.id
510
+
511
+ # Get item info
512
+ item = await self.graph_client.drives.by_drive_id(drive_id)\
513
+ .items.by_drive_item_id(item_id).get()
514
+
515
+ if not item.file:
516
+ raise RuntimeError(f"Item {item_id} is not a file")
517
+
518
+ # Get file content
519
+ content = await self.graph_client.drives.by_drive_id(drive_id)\
520
+ .items.by_drive_item_id(item_id).content.get()
521
+
522
+ if as_pandas:
523
+ bytes_buffer = BytesIO(content)
524
+ return pd.read_excel(bytes_buffer)
525
+ else:
526
+ if not destination:
527
+ raise ValueError("Destination path must be provided when `as_pandas` is False.")
528
+
529
+ destination.parent.mkdir(parents=True, exist_ok=True)
530
+ async with aiofiles.open(destination, "wb") as f:
531
+ await f.write(content)
532
+ return str(destination)
533
+
534
+ except Exception as err:
535
+ self.logger.error(f"Error downloading Excel file {item_id}: {err}")
536
+ raise RuntimeError(f"Error downloading Excel file {item_id}: {err}") from err
537
+
538
+ async def upload_dataframe_as_excel(self, df: pd.DataFrame, file_name: str, destination_folder: str = None) -> dict:
539
+ """
540
+ Upload a pandas DataFrame as an Excel file to OneDrive using Microsoft Graph API.
541
+ """
542
+ try:
543
+ # Convert DataFrame to Excel bytes
544
+ output = BytesIO()
545
+ df.to_excel(output, index=False)
546
+ excel_content = output.getvalue()
547
+
548
+ drive_info = await self._resolve_drive()
549
+ drive_id = drive_info.id
550
+
551
+ # Ensure destination folder exists
552
+ if destination_folder:
553
+ folder_info = await self._ensure_folder(destination_folder, create=True)
554
+ parent_id = folder_info.id
555
+ else:
556
+ root = await self.graph_client.drives.by_drive_id(drive_id).root.get()
557
+ parent_id = root.id
558
+
559
+ self.logger.info(f"Uploading DataFrame as Excel: {file_name}")
560
+
561
+ # Upload the Excel content
562
+ if len(excel_content) <= self.small_file_threshold:
563
+ # Small file upload
564
+ encoded_name = quote(file_name)
565
+ request_path = f"{parent_id}:/{encoded_name}:"
566
+
567
+ uploaded_item = await self.graph_client.drives.by_drive_id(drive_id)\
568
+ .items.by_drive_item_id(request_path).content.put(excel_content)
569
+ else:
570
+ # Large file upload (create upload session)
571
+ upload_session = await self._create_upload_session(drive_id, parent_id, file_name)
572
+ uploaded_item = await self._upload_large_file_content(upload_session, excel_content, file_name)
573
+
574
+ self.logger.info(f"Uploaded DataFrame as Excel successfully: {uploaded_item.name}")
575
+
576
+ return {
577
+ "name": uploaded_item.name,
578
+ "id": uploaded_item.id,
579
+ "webUrl": uploaded_item.web_url
580
+ }
581
+
582
+ except Exception as err:
583
+ self.logger.error(f"Error uploading DataFrame as Excel file {file_name}: {err}")
584
+ raise RuntimeError(f"Error uploading DataFrame as Excel file {file_name}: {err}") from err
585
+
586
+ # Helper methods (similar to SharepointClient)
587
+
588
+ async def _upload_small_file(self, drive_id: str, parent_id: str, local_path: Path, target_name: str) -> DriveItem:
589
+ """Upload small file using Graph API."""
590
+ try:
591
+ async with aiofiles.open(local_path, "rb") as f:
592
+ content = await f.read()
593
+
594
+ # URL encode the target name to handle special characters
595
+ encoded_name = quote(target_name)
596
+ request_path = f"{parent_id}:/{encoded_name}:"
597
+
598
+ return await self.graph_client.drives.by_drive_id(drive_id)\
599
+ .items.by_drive_item_id(request_path).content.put(content)
600
+
601
+ except Exception as e:
602
+ raise RuntimeError(f"Small file upload failed for {target_name}: {e}") from e
603
+
604
+ async def _create_upload_session(self, drive_id: str, parent_id: str, target_name: str) -> UploadSession:
605
+ """Create upload session for large files using Graph API."""
606
+ try:
607
+ body = CreateUploadSessionPostRequestBody()
608
+ body.item = DriveItemUploadableProperties()
609
+ body.item.additional_data = {"@microsoft.graph.conflictBehavior": "replace"}
610
+
611
+ # URL encode the target name to handle special characters
612
+ encoded_name = quote(target_name)
613
+
614
+ return await self.graph_client.drives.by_drive_id(drive_id)\
615
+ .items.by_drive_item_id(f"{parent_id}:/{encoded_name}:/")\
616
+ .create_upload_session.post(body)
617
+
618
+ except Exception as e:
619
+ raise RuntimeError(f"Upload session creation failed for {target_name}: {e}") from e
620
+
621
+ async def _upload_large_file(self, upload_session: UploadSession, local_path: Union[str, Path]) -> DriveItem:
622
+ """Upload large file using resumable upload session."""
623
+ file_size = os.path.getsize(local_path)
624
+ uploaded = 0
625
+
626
+ async with aiohttp.ClientSession() as session:
627
+ async with aiofiles.open(local_path, "rb") as f:
628
+ with tqdm(total=file_size, unit='B', unit_scale=True, desc=f'Uploading {Path(local_path).name}') as pbar: # noqa
629
+ while uploaded < file_size:
630
+ chunk = await f.read(self.chunk_size)
631
+ if not chunk:
632
+ break
633
+
634
+ start = uploaded
635
+ end = uploaded + len(chunk) - 1
636
+
637
+ headers = {
638
+ "Content-Length": str(len(chunk)),
639
+ "Content-Range": f"bytes {start}-{end}/{file_size}"
640
+ }
641
+
642
+ async with session.put(
643
+ upload_session.upload_url,
644
+ headers=headers,
645
+ data=chunk
646
+ ) as response:
647
+ if response.status in (200, 201):
648
+ # Upload complete
649
+ pbar.update(file_size - uploaded)
650
+ result_data = await response.json()
651
+
652
+ # Convert to DriveItem (simplified)
653
+ drive_item = DriveItem()
654
+ drive_item.name = result_data.get('name')
655
+ drive_item.size = result_data.get('size')
656
+ drive_item.web_url = result_data.get('webUrl')
657
+ drive_item.additional_data = result_data
658
+
659
+ return drive_item
660
+
661
+ elif response.status == 202:
662
+ # Continue uploading
663
+ uploaded = end + 1
664
+ pbar.update(len(chunk))
665
+
666
+ # Check for retry-after header
667
+ if (retry_after := response.headers.get('Retry-After')):
668
+ await asyncio.sleep(int(retry_after))
669
+ continue
670
+
671
+ else:
672
+ error_text = await response.text()
673
+ raise RuntimeError(f"Chunk upload failed: {response.status} {error_text}")
674
+
675
+ raise RuntimeError("Upload session completed without final item response")
676
+
677
+ async def _upload_large_file_content(
678
+ self,
679
+ upload_session: UploadSession,
680
+ content: bytes,
681
+ file_name: str
682
+ ) -> DriveItem:
683
+ """Upload large content using resumable upload session."""
684
+ file_size = len(content)
685
+ uploaded = 0
686
+
687
+ async with aiohttp.ClientSession() as session:
688
+ with tqdm(total=file_size, unit='B', unit_scale=True, desc=f'Uploading {file_name}') as pbar:
689
+ while uploaded < file_size:
690
+ chunk = content[uploaded:uploaded + self.chunk_size]
691
+ if not chunk:
692
+ break
693
+
694
+ start = uploaded
695
+ end = uploaded + len(chunk) - 1
696
+
697
+ headers = {
698
+ "Content-Length": str(len(chunk)),
699
+ "Content-Range": f"bytes {start}-{end}/{file_size}"
700
+ }
701
+
702
+ async with session.put(
703
+ upload_session.upload_url,
704
+ headers=headers,
705
+ data=chunk
706
+ ) as response:
707
+ if response.status in (200, 201):
708
+ # Upload complete
709
+ pbar.update(file_size - uploaded)
710
+ result_data = await response.json()
711
+
712
+ # Convert to DriveItem (simplified)
713
+ drive_item = DriveItem()
714
+ drive_item.name = result_data.get('name')
715
+ drive_item.size = result_data.get('size')
716
+ drive_item.web_url = result_data.get('webUrl')
717
+ drive_item.additional_data = result_data
718
+
719
+ return drive_item
720
+
721
+ elif response.status == 202:
722
+ # Continue uploading
723
+ uploaded = end + 1
724
+ pbar.update(len(chunk))
725
+
726
+ # Check for retry-after header
727
+ if (retry_after := response.headers.get('Retry-After')):
728
+ await asyncio.sleep(int(retry_after))
729
+ continue
730
+
731
+ else:
732
+ error_text = await response.text()
733
+ raise RuntimeError(f"Chunk upload failed: {response.status} {error_text}")
734
+
735
+ raise RuntimeError("Upload session completed without final item response")
736
+
737
+ def _get_item_path_from_item(self, item) -> str:
738
+ """
739
+ Extract the full path from a DriveItem object.
740
+ """
741
+ try:
742
+ # Try to get path from parent_reference
743
+ if hasattr(item, 'parent_reference') and item.parent_reference and item.parent_reference.path:
744
+ parent_path = item.parent_reference.path or ""
745
+
746
+ # Clean up the parent path
747
+ if parent_path.startswith("/drive/root:"):
748
+ parent_path = parent_path[12:]
749
+ elif parent_path.startswith("/drives/") and "/root:" in parent_path:
750
+ parent_path = parent_path.split("/root:")[-1]
751
+
752
+ # Build the full path
753
+ if parent_path:
754
+ full_path = f"{parent_path}/{item.name}".replace("//", "/").lstrip("/")
755
+ else:
756
+ full_path = item.name or ""
757
+
758
+ return full_path
759
+
760
+ # Fallback: try to get path from web_url if available
761
+ if hasattr(item, 'web_url') and item.web_url:
762
+ try:
763
+ # Extract path from OneDrive web URL
764
+ web_url = item.web_url
765
+ if "/personal/" in web_url and "/_layouts/15/onedrive.aspx" in web_url:
766
+ # Personal OneDrive URL format
767
+ return item.name or ""
768
+ else:
769
+ return unquote(item.name or "")
770
+ except Exception:
771
+ pass
772
+
773
+ # Final fallback: just return the filename
774
+ return item.name or ""
775
+
776
+ except Exception as e:
777
+ self.logger.debug(f"Error extracting path from item: {e}")
778
+ return item.name or ""
779
+
780
+ async def test_permissions(self) -> Dict[str, Any]:
781
+ """
782
+ Test OneDrive permissions using Microsoft Graph API.
783
+ """
784
+ results = {
785
+ "drive_access": False,
786
+ "folder_access": False,
787
+ "upload_access": False,
788
+ "errors": []
789
+ }
790
+
791
+ try:
792
+ # Test 1: Drive access
793
+ drive_info = await self._resolve_drive()
794
+ results["drive_access"] = True
795
+ self.logger.info(f"Drive access: {drive_info.name or 'Personal OneDrive'}")
796
+
797
+ # Test 2: Folder access (list root)
798
+ file_list = await self.file_list()
799
+ results["folder_access"] = True
800
+ self.logger.info(f"Folder access: Listed {len(file_list)} items")
801
+
802
+ # Test 3: Folder creation (upload capability test)
803
+ test_folder = await self._ensure_folder("test-folder-permissions", create=True)
804
+ results["upload_access"] = True
805
+ self.logger.info("Upload permissions confirmed")
806
+
807
+ # Clean up test folder
808
+ with contextlib.suppress(Exception):
809
+ await self.file_delete(test_folder.id)
810
+ self.logger.info("Test folder cleaned up")
811
+
812
+ except Exception as e:
813
+ results["errors"].append(str(e))
814
+ self.logger.error(f"Permission test failed: {e}")
815
+
816
+ return results
817
+
818
+ async def close(self):
819
+ """Clean up resources."""
820
+ await super().close()
821
+ self._drive_info = None
822
+ self._drive_id = None