flowtask 5.8.4__cp312-cp312-manylinux2014_x86_64.manylinux_2_17_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 (470) hide show
  1. flowtask/__init__.py +93 -0
  2. flowtask/__main__.py +38 -0
  3. flowtask/bots/__init__.py +6 -0
  4. flowtask/bots/check.py +93 -0
  5. flowtask/bots/codebot.py +51 -0
  6. flowtask/components/ASPX.py +148 -0
  7. flowtask/components/AddDataset.py +352 -0
  8. flowtask/components/Amazon.py +523 -0
  9. flowtask/components/AutoTask.py +314 -0
  10. flowtask/components/Azure.py +80 -0
  11. flowtask/components/AzureUsers.py +106 -0
  12. flowtask/components/BaseAction.py +91 -0
  13. flowtask/components/BaseLoop.py +198 -0
  14. flowtask/components/BestBuy.py +800 -0
  15. flowtask/components/CSVToGCS.py +120 -0
  16. flowtask/components/CompanyScraper/__init__.py +1 -0
  17. flowtask/components/CompanyScraper/parsers/__init__.py +6 -0
  18. flowtask/components/CompanyScraper/parsers/base.py +102 -0
  19. flowtask/components/CompanyScraper/parsers/explorium.py +192 -0
  20. flowtask/components/CompanyScraper/parsers/leadiq.py +206 -0
  21. flowtask/components/CompanyScraper/parsers/rocket.py +133 -0
  22. flowtask/components/CompanyScraper/parsers/siccode.py +109 -0
  23. flowtask/components/CompanyScraper/parsers/visualvisitor.py +130 -0
  24. flowtask/components/CompanyScraper/parsers/zoominfo.py +118 -0
  25. flowtask/components/CompanyScraper/scrapper.py +1054 -0
  26. flowtask/components/CopyTo.py +177 -0
  27. flowtask/components/CopyToBigQuery.py +243 -0
  28. flowtask/components/CopyToMongoDB.py +291 -0
  29. flowtask/components/CopyToPg.py +609 -0
  30. flowtask/components/CopyToRethink.py +207 -0
  31. flowtask/components/CreateGCSBucket.py +102 -0
  32. flowtask/components/CreateReport/CreateReport.py +228 -0
  33. flowtask/components/CreateReport/__init__.py +9 -0
  34. flowtask/components/CreateReport/charts/__init__.py +15 -0
  35. flowtask/components/CreateReport/charts/bar.py +51 -0
  36. flowtask/components/CreateReport/charts/base.py +66 -0
  37. flowtask/components/CreateReport/charts/pie.py +64 -0
  38. flowtask/components/CreateReport/utils.py +9 -0
  39. flowtask/components/CustomerSatisfaction.py +196 -0
  40. flowtask/components/DataInput.py +200 -0
  41. flowtask/components/DateList.py +255 -0
  42. flowtask/components/DbClient.py +163 -0
  43. flowtask/components/DialPad.py +146 -0
  44. flowtask/components/DocumentDBQuery.py +200 -0
  45. flowtask/components/DownloadFrom.py +371 -0
  46. flowtask/components/DownloadFromD2L.py +113 -0
  47. flowtask/components/DownloadFromFTP.py +181 -0
  48. flowtask/components/DownloadFromIMAP.py +315 -0
  49. flowtask/components/DownloadFromS3.py +198 -0
  50. flowtask/components/DownloadFromSFTP.py +265 -0
  51. flowtask/components/DownloadFromSharepoint.py +110 -0
  52. flowtask/components/DownloadFromSmartSheet.py +114 -0
  53. flowtask/components/DownloadS3File.py +229 -0
  54. flowtask/components/Dummy.py +59 -0
  55. flowtask/components/DuplicatePhoto.py +411 -0
  56. flowtask/components/EmployeeEvaluation.py +237 -0
  57. flowtask/components/ExecuteSQL.py +323 -0
  58. flowtask/components/ExtractHTML.py +178 -0
  59. flowtask/components/FileBase.py +178 -0
  60. flowtask/components/FileCopy.py +181 -0
  61. flowtask/components/FileDelete.py +82 -0
  62. flowtask/components/FileExists.py +146 -0
  63. flowtask/components/FileIteratorDelete.py +112 -0
  64. flowtask/components/FileList.py +194 -0
  65. flowtask/components/FileOpen.py +75 -0
  66. flowtask/components/FileRead.py +120 -0
  67. flowtask/components/FileRename.py +106 -0
  68. flowtask/components/FilterIf.py +284 -0
  69. flowtask/components/FilterRows/FilterRows.py +200 -0
  70. flowtask/components/FilterRows/__init__.py +10 -0
  71. flowtask/components/FilterRows/functions.py +4 -0
  72. flowtask/components/GCSToBigQuery.py +103 -0
  73. flowtask/components/GoogleA4.py +150 -0
  74. flowtask/components/GoogleGeoCoding.py +344 -0
  75. flowtask/components/GooglePlaces.py +315 -0
  76. flowtask/components/GoogleSearch.py +539 -0
  77. flowtask/components/HTTPClient.py +268 -0
  78. flowtask/components/ICIMS.py +146 -0
  79. flowtask/components/IF.py +179 -0
  80. flowtask/components/IcimsFolderCopy.py +173 -0
  81. flowtask/components/ImageFeatures/__init__.py +5 -0
  82. flowtask/components/ImageFeatures/process.py +233 -0
  83. flowtask/components/IteratorBase.py +251 -0
  84. flowtask/components/LangchainLoader/__init__.py +5 -0
  85. flowtask/components/LangchainLoader/loader.py +194 -0
  86. flowtask/components/LangchainLoader/loaders/__init__.py +22 -0
  87. flowtask/components/LangchainLoader/loaders/abstract.py +362 -0
  88. flowtask/components/LangchainLoader/loaders/basepdf.py +50 -0
  89. flowtask/components/LangchainLoader/loaders/docx.py +91 -0
  90. flowtask/components/LangchainLoader/loaders/html.py +119 -0
  91. flowtask/components/LangchainLoader/loaders/pdfblocks.py +146 -0
  92. flowtask/components/LangchainLoader/loaders/pdfmark.py +79 -0
  93. flowtask/components/LangchainLoader/loaders/pdftables.py +135 -0
  94. flowtask/components/LangchainLoader/loaders/qa.py +67 -0
  95. flowtask/components/LangchainLoader/loaders/txt.py +55 -0
  96. flowtask/components/LeadIQ.py +650 -0
  97. flowtask/components/Loop.py +253 -0
  98. flowtask/components/Lowes.py +334 -0
  99. flowtask/components/MS365Usage.py +156 -0
  100. flowtask/components/MSTeamsMessages.py +320 -0
  101. flowtask/components/MarketClustering.py +1051 -0
  102. flowtask/components/MergeFiles.py +362 -0
  103. flowtask/components/MilvusOutput.py +87 -0
  104. flowtask/components/NearByStores.py +175 -0
  105. flowtask/components/NetworkNinja/__init__.py +6 -0
  106. flowtask/components/NetworkNinja/models/__init__.py +52 -0
  107. flowtask/components/NetworkNinja/models/abstract.py +177 -0
  108. flowtask/components/NetworkNinja/models/account.py +39 -0
  109. flowtask/components/NetworkNinja/models/client.py +19 -0
  110. flowtask/components/NetworkNinja/models/district.py +14 -0
  111. flowtask/components/NetworkNinja/models/events.py +101 -0
  112. flowtask/components/NetworkNinja/models/forms.py +499 -0
  113. flowtask/components/NetworkNinja/models/market.py +16 -0
  114. flowtask/components/NetworkNinja/models/organization.py +34 -0
  115. flowtask/components/NetworkNinja/models/photos.py +125 -0
  116. flowtask/components/NetworkNinja/models/project.py +44 -0
  117. flowtask/components/NetworkNinja/models/region.py +28 -0
  118. flowtask/components/NetworkNinja/models/store.py +203 -0
  119. flowtask/components/NetworkNinja/models/user.py +151 -0
  120. flowtask/components/NetworkNinja/router.py +854 -0
  121. flowtask/components/Odoo.py +175 -0
  122. flowtask/components/OdooInjector.py +192 -0
  123. flowtask/components/OpenFromXML.py +126 -0
  124. flowtask/components/OpenWeather.py +41 -0
  125. flowtask/components/OpenWithBase.py +616 -0
  126. flowtask/components/OpenWithPandas.py +715 -0
  127. flowtask/components/PGPDecrypt.py +199 -0
  128. flowtask/components/PandasIterator.py +187 -0
  129. flowtask/components/PandasToFile.py +189 -0
  130. flowtask/components/Paradox.py +339 -0
  131. flowtask/components/ParamIterator.py +117 -0
  132. flowtask/components/ParseHTML.py +84 -0
  133. flowtask/components/PlacerStores.py +249 -0
  134. flowtask/components/Pokemon.py +507 -0
  135. flowtask/components/PositiveBot.py +62 -0
  136. flowtask/components/PowerPointSlide.py +400 -0
  137. flowtask/components/PrintMessage.py +127 -0
  138. flowtask/components/ProductCompetitors/__init__.py +5 -0
  139. flowtask/components/ProductCompetitors/parsers/__init__.py +7 -0
  140. flowtask/components/ProductCompetitors/parsers/base.py +72 -0
  141. flowtask/components/ProductCompetitors/parsers/bestbuy.py +86 -0
  142. flowtask/components/ProductCompetitors/parsers/lowes.py +103 -0
  143. flowtask/components/ProductCompetitors/scrapper.py +155 -0
  144. flowtask/components/ProductCompliant.py +169 -0
  145. flowtask/components/ProductInfo/__init__.py +1 -0
  146. flowtask/components/ProductInfo/parsers/__init__.py +5 -0
  147. flowtask/components/ProductInfo/parsers/base.py +83 -0
  148. flowtask/components/ProductInfo/parsers/brother.py +97 -0
  149. flowtask/components/ProductInfo/parsers/canon.py +167 -0
  150. flowtask/components/ProductInfo/parsers/epson.py +118 -0
  151. flowtask/components/ProductInfo/parsers/hp.py +131 -0
  152. flowtask/components/ProductInfo/parsers/samsung.py +97 -0
  153. flowtask/components/ProductInfo/scraper.py +319 -0
  154. flowtask/components/ProductPricing.py +118 -0
  155. flowtask/components/QS.py +261 -0
  156. flowtask/components/QSBase.py +201 -0
  157. flowtask/components/QueryIterator.py +273 -0
  158. flowtask/components/QueryToInsert.py +327 -0
  159. flowtask/components/QueryToPandas.py +432 -0
  160. flowtask/components/RESTClient.py +195 -0
  161. flowtask/components/RethinkDBQuery.py +189 -0
  162. flowtask/components/Rsync.py +74 -0
  163. flowtask/components/RunSSH.py +59 -0
  164. flowtask/components/RunShell.py +71 -0
  165. flowtask/components/SalesForce.py +20 -0
  166. flowtask/components/SaveImageBank/__init__.py +257 -0
  167. flowtask/components/SchedulingVisits.py +592 -0
  168. flowtask/components/ScrapPage.py +216 -0
  169. flowtask/components/ScrapSearch.py +79 -0
  170. flowtask/components/SendNotify.py +257 -0
  171. flowtask/components/SentimentAnalysis.py +694 -0
  172. flowtask/components/ServiceScrapper/__init__.py +5 -0
  173. flowtask/components/ServiceScrapper/parsers/__init__.py +1 -0
  174. flowtask/components/ServiceScrapper/parsers/base.py +94 -0
  175. flowtask/components/ServiceScrapper/parsers/costco.py +93 -0
  176. flowtask/components/ServiceScrapper/scrapper.py +199 -0
  177. flowtask/components/SetVariables.py +156 -0
  178. flowtask/components/SubTask.py +182 -0
  179. flowtask/components/SuiteCRM.py +48 -0
  180. flowtask/components/Switch.py +175 -0
  181. flowtask/components/TableBase.py +148 -0
  182. flowtask/components/TableDelete.py +312 -0
  183. flowtask/components/TableInput.py +143 -0
  184. flowtask/components/TableOutput/TableOutput.py +384 -0
  185. flowtask/components/TableOutput/__init__.py +3 -0
  186. flowtask/components/TableSchema.py +534 -0
  187. flowtask/components/Target.py +223 -0
  188. flowtask/components/ThumbnailGenerator.py +156 -0
  189. flowtask/components/ToPandas.py +67 -0
  190. flowtask/components/TransformRows/TransformRows.py +507 -0
  191. flowtask/components/TransformRows/__init__.py +9 -0
  192. flowtask/components/TransformRows/functions.py +559 -0
  193. flowtask/components/TransposeRows.py +176 -0
  194. flowtask/components/UPCDatabase.py +86 -0
  195. flowtask/components/UnGzip.py +171 -0
  196. flowtask/components/Uncompress.py +172 -0
  197. flowtask/components/UniqueRows.py +126 -0
  198. flowtask/components/Unzip.py +107 -0
  199. flowtask/components/UpdateOperationalVars.py +147 -0
  200. flowtask/components/UploadTo.py +299 -0
  201. flowtask/components/UploadToS3.py +136 -0
  202. flowtask/components/UploadToSFTP.py +160 -0
  203. flowtask/components/UploadToSharepoint.py +205 -0
  204. flowtask/components/UserFunc.py +122 -0
  205. flowtask/components/VivaTracker.py +140 -0
  206. flowtask/components/WSDLClient.py +123 -0
  207. flowtask/components/Wait.py +18 -0
  208. flowtask/components/Walmart.py +199 -0
  209. flowtask/components/Workplace.py +134 -0
  210. flowtask/components/XMLToPandas.py +267 -0
  211. flowtask/components/Zammad/__init__.py +41 -0
  212. flowtask/components/Zammad/models.py +0 -0
  213. flowtask/components/ZoomInfoScraper.py +409 -0
  214. flowtask/components/__init__.py +104 -0
  215. flowtask/components/abstract.py +18 -0
  216. flowtask/components/flow.py +530 -0
  217. flowtask/components/google.py +335 -0
  218. flowtask/components/group.py +221 -0
  219. flowtask/components/py.typed +0 -0
  220. flowtask/components/reviewscrap.py +132 -0
  221. flowtask/components/tAutoincrement.py +117 -0
  222. flowtask/components/tConcat.py +109 -0
  223. flowtask/components/tExplode.py +119 -0
  224. flowtask/components/tFilter.py +184 -0
  225. flowtask/components/tGroup.py +236 -0
  226. flowtask/components/tJoin.py +270 -0
  227. flowtask/components/tMap/__init__.py +9 -0
  228. flowtask/components/tMap/functions.py +54 -0
  229. flowtask/components/tMap/tMap.py +450 -0
  230. flowtask/components/tMelt.py +112 -0
  231. flowtask/components/tMerge.py +114 -0
  232. flowtask/components/tOrder.py +93 -0
  233. flowtask/components/tPandas.py +94 -0
  234. flowtask/components/tPivot.py +71 -0
  235. flowtask/components/tPluckCols.py +76 -0
  236. flowtask/components/tUnnest.py +82 -0
  237. flowtask/components/user.py +401 -0
  238. flowtask/conf.py +457 -0
  239. flowtask/download.py +102 -0
  240. flowtask/events/__init__.py +11 -0
  241. flowtask/events/events/__init__.py +20 -0
  242. flowtask/events/events/abstract.py +95 -0
  243. flowtask/events/events/alerts/__init__.py +362 -0
  244. flowtask/events/events/alerts/colfunctions.py +131 -0
  245. flowtask/events/events/alerts/functions.py +158 -0
  246. flowtask/events/events/dummy.py +12 -0
  247. flowtask/events/events/exec.py +124 -0
  248. flowtask/events/events/file/__init__.py +7 -0
  249. flowtask/events/events/file/base.py +51 -0
  250. flowtask/events/events/file/copy.py +23 -0
  251. flowtask/events/events/file/delete.py +16 -0
  252. flowtask/events/events/interfaces/__init__.py +9 -0
  253. flowtask/events/events/interfaces/client.py +67 -0
  254. flowtask/events/events/interfaces/credentials.py +28 -0
  255. flowtask/events/events/interfaces/notifications.py +58 -0
  256. flowtask/events/events/jira.py +122 -0
  257. flowtask/events/events/log.py +26 -0
  258. flowtask/events/events/logerr.py +52 -0
  259. flowtask/events/events/notify.py +59 -0
  260. flowtask/events/events/notify_event.py +160 -0
  261. flowtask/events/events/publish.py +54 -0
  262. flowtask/events/events/sendfile.py +104 -0
  263. flowtask/events/events/task.py +97 -0
  264. flowtask/events/events/teams.py +98 -0
  265. flowtask/events/events/webhook.py +58 -0
  266. flowtask/events/manager.py +287 -0
  267. flowtask/exceptions.c +39393 -0
  268. flowtask/exceptions.cpython-312-x86_64-linux-gnu.so +0 -0
  269. flowtask/extensions/__init__.py +3 -0
  270. flowtask/extensions/abstract.py +82 -0
  271. flowtask/extensions/logging/__init__.py +65 -0
  272. flowtask/hooks/__init__.py +9 -0
  273. flowtask/hooks/actions/__init__.py +22 -0
  274. flowtask/hooks/actions/abstract.py +66 -0
  275. flowtask/hooks/actions/dummy.py +23 -0
  276. flowtask/hooks/actions/jira.py +74 -0
  277. flowtask/hooks/actions/rest.py +320 -0
  278. flowtask/hooks/actions/sampledata.py +37 -0
  279. flowtask/hooks/actions/sensor.py +23 -0
  280. flowtask/hooks/actions/task.py +9 -0
  281. flowtask/hooks/actions/ticket.py +37 -0
  282. flowtask/hooks/actions/zammad.py +55 -0
  283. flowtask/hooks/hook.py +62 -0
  284. flowtask/hooks/models.py +17 -0
  285. flowtask/hooks/service.py +187 -0
  286. flowtask/hooks/step.py +91 -0
  287. flowtask/hooks/types/__init__.py +23 -0
  288. flowtask/hooks/types/base.py +129 -0
  289. flowtask/hooks/types/brokers/__init__.py +11 -0
  290. flowtask/hooks/types/brokers/base.py +54 -0
  291. flowtask/hooks/types/brokers/mqtt.py +35 -0
  292. flowtask/hooks/types/brokers/rabbitmq.py +82 -0
  293. flowtask/hooks/types/brokers/redis.py +83 -0
  294. flowtask/hooks/types/brokers/sqs.py +44 -0
  295. flowtask/hooks/types/fs.py +232 -0
  296. flowtask/hooks/types/http.py +49 -0
  297. flowtask/hooks/types/imap.py +200 -0
  298. flowtask/hooks/types/jira.py +279 -0
  299. flowtask/hooks/types/mail.py +205 -0
  300. flowtask/hooks/types/postgres.py +98 -0
  301. flowtask/hooks/types/responses/__init__.py +8 -0
  302. flowtask/hooks/types/responses/base.py +5 -0
  303. flowtask/hooks/types/sharepoint.py +288 -0
  304. flowtask/hooks/types/ssh.py +141 -0
  305. flowtask/hooks/types/tagged.py +59 -0
  306. flowtask/hooks/types/upload.py +85 -0
  307. flowtask/hooks/types/watch.py +71 -0
  308. flowtask/hooks/types/web.py +36 -0
  309. flowtask/interfaces/AzureClient.py +137 -0
  310. flowtask/interfaces/AzureGraph.py +839 -0
  311. flowtask/interfaces/Boto3Client.py +326 -0
  312. flowtask/interfaces/DropboxClient.py +173 -0
  313. flowtask/interfaces/ExcelHandler.py +94 -0
  314. flowtask/interfaces/FTPClient.py +131 -0
  315. flowtask/interfaces/GoogleCalendar.py +201 -0
  316. flowtask/interfaces/GoogleClient.py +133 -0
  317. flowtask/interfaces/GoogleDrive.py +127 -0
  318. flowtask/interfaces/GoogleGCS.py +89 -0
  319. flowtask/interfaces/GoogleGeocoding.py +93 -0
  320. flowtask/interfaces/GoogleLang.py +114 -0
  321. flowtask/interfaces/GooglePub.py +61 -0
  322. flowtask/interfaces/GoogleSheet.py +68 -0
  323. flowtask/interfaces/IMAPClient.py +137 -0
  324. flowtask/interfaces/O365Calendar.py +113 -0
  325. flowtask/interfaces/O365Client.py +220 -0
  326. flowtask/interfaces/OneDrive.py +284 -0
  327. flowtask/interfaces/Outlook.py +155 -0
  328. flowtask/interfaces/ParrotBot.py +130 -0
  329. flowtask/interfaces/SSHClient.py +378 -0
  330. flowtask/interfaces/Sharepoint.py +496 -0
  331. flowtask/interfaces/__init__.py +36 -0
  332. flowtask/interfaces/azureauth.py +119 -0
  333. flowtask/interfaces/cache.py +201 -0
  334. flowtask/interfaces/client.py +82 -0
  335. flowtask/interfaces/compress.py +525 -0
  336. flowtask/interfaces/credentials.py +124 -0
  337. flowtask/interfaces/d2l.py +239 -0
  338. flowtask/interfaces/databases/__init__.py +5 -0
  339. flowtask/interfaces/databases/db.py +223 -0
  340. flowtask/interfaces/databases/documentdb.py +55 -0
  341. flowtask/interfaces/databases/rethink.py +39 -0
  342. flowtask/interfaces/dataframes/__init__.py +11 -0
  343. flowtask/interfaces/dataframes/abstract.py +21 -0
  344. flowtask/interfaces/dataframes/arrow.py +71 -0
  345. flowtask/interfaces/dataframes/dt.py +69 -0
  346. flowtask/interfaces/dataframes/pandas.py +167 -0
  347. flowtask/interfaces/dataframes/polars.py +60 -0
  348. flowtask/interfaces/db.py +263 -0
  349. flowtask/interfaces/env.py +46 -0
  350. flowtask/interfaces/func.py +137 -0
  351. flowtask/interfaces/http.py +1780 -0
  352. flowtask/interfaces/locale.py +40 -0
  353. flowtask/interfaces/log.py +75 -0
  354. flowtask/interfaces/mask.py +143 -0
  355. flowtask/interfaces/notification.py +154 -0
  356. flowtask/interfaces/playwright.py +339 -0
  357. flowtask/interfaces/powerpoint.py +368 -0
  358. flowtask/interfaces/py.typed +0 -0
  359. flowtask/interfaces/qs.py +376 -0
  360. flowtask/interfaces/result.py +87 -0
  361. flowtask/interfaces/selenium_service.py +779 -0
  362. flowtask/interfaces/smartsheet.py +154 -0
  363. flowtask/interfaces/stat.py +39 -0
  364. flowtask/interfaces/task.py +96 -0
  365. flowtask/interfaces/template.py +118 -0
  366. flowtask/interfaces/vectorstores/__init__.py +1 -0
  367. flowtask/interfaces/vectorstores/abstract.py +133 -0
  368. flowtask/interfaces/vectorstores/milvus.py +669 -0
  369. flowtask/interfaces/zammad.py +107 -0
  370. flowtask/models.py +193 -0
  371. flowtask/parsers/__init__.py +15 -0
  372. flowtask/parsers/_yaml.c +11978 -0
  373. flowtask/parsers/_yaml.cpython-312-x86_64-linux-gnu.so +0 -0
  374. flowtask/parsers/argparser.py +235 -0
  375. flowtask/parsers/base.c +15155 -0
  376. flowtask/parsers/base.cpython-312-x86_64-linux-gnu.so +0 -0
  377. flowtask/parsers/json.c +11968 -0
  378. flowtask/parsers/json.cpython-312-x86_64-linux-gnu.so +0 -0
  379. flowtask/parsers/maps.py +49 -0
  380. flowtask/parsers/toml.c +11968 -0
  381. flowtask/parsers/toml.cpython-312-x86_64-linux-gnu.so +0 -0
  382. flowtask/plugins/__init__.py +16 -0
  383. flowtask/plugins/components/__init__.py +0 -0
  384. flowtask/plugins/handler/__init__.py +45 -0
  385. flowtask/plugins/importer.py +31 -0
  386. flowtask/plugins/sources/__init__.py +0 -0
  387. flowtask/runner.py +283 -0
  388. flowtask/scheduler/__init__.py +9 -0
  389. flowtask/scheduler/functions.py +493 -0
  390. flowtask/scheduler/handlers/__init__.py +8 -0
  391. flowtask/scheduler/handlers/manager.py +504 -0
  392. flowtask/scheduler/handlers/models.py +58 -0
  393. flowtask/scheduler/handlers/service.py +72 -0
  394. flowtask/scheduler/notifications.py +65 -0
  395. flowtask/scheduler/scheduler.py +993 -0
  396. flowtask/services/__init__.py +0 -0
  397. flowtask/services/bots/__init__.py +0 -0
  398. flowtask/services/bots/telegram.py +264 -0
  399. flowtask/services/files/__init__.py +11 -0
  400. flowtask/services/files/manager.py +522 -0
  401. flowtask/services/files/model.py +37 -0
  402. flowtask/services/files/service.py +767 -0
  403. flowtask/services/jira/__init__.py +3 -0
  404. flowtask/services/jira/jira_actions.py +191 -0
  405. flowtask/services/tasks/__init__.py +13 -0
  406. flowtask/services/tasks/launcher.py +213 -0
  407. flowtask/services/tasks/manager.py +323 -0
  408. flowtask/services/tasks/service.py +275 -0
  409. flowtask/services/tasks/task_manager.py +376 -0
  410. flowtask/services/tasks/tasks.py +155 -0
  411. flowtask/storages/__init__.py +16 -0
  412. flowtask/storages/exceptions.py +12 -0
  413. flowtask/storages/files/__init__.py +8 -0
  414. flowtask/storages/files/abstract.py +29 -0
  415. flowtask/storages/files/filesystem.py +66 -0
  416. flowtask/storages/tasks/__init__.py +19 -0
  417. flowtask/storages/tasks/abstract.py +26 -0
  418. flowtask/storages/tasks/database.py +33 -0
  419. flowtask/storages/tasks/filesystem.py +108 -0
  420. flowtask/storages/tasks/github.py +119 -0
  421. flowtask/storages/tasks/memory.py +45 -0
  422. flowtask/storages/tasks/row.py +25 -0
  423. flowtask/tasks/__init__.py +0 -0
  424. flowtask/tasks/abstract.py +526 -0
  425. flowtask/tasks/command.py +118 -0
  426. flowtask/tasks/pile.py +486 -0
  427. flowtask/tasks/py.typed +0 -0
  428. flowtask/tasks/task.py +778 -0
  429. flowtask/template/__init__.py +161 -0
  430. flowtask/tests.py +257 -0
  431. flowtask/types/__init__.py +8 -0
  432. flowtask/types/typedefs.c +11347 -0
  433. flowtask/types/typedefs.cpython-312-x86_64-linux-gnu.so +0 -0
  434. flowtask/utils/__init__.py +24 -0
  435. flowtask/utils/constants.py +117 -0
  436. flowtask/utils/encoders.py +21 -0
  437. flowtask/utils/executor.py +112 -0
  438. flowtask/utils/functions.cpp +14280 -0
  439. flowtask/utils/functions.cpython-312-x86_64-linux-gnu.so +0 -0
  440. flowtask/utils/json.cpp +13349 -0
  441. flowtask/utils/json.cpython-312-x86_64-linux-gnu.so +0 -0
  442. flowtask/utils/mail.py +63 -0
  443. flowtask/utils/parseqs.c +13324 -0
  444. flowtask/utils/parserqs.cpython-312-x86_64-linux-gnu.so +0 -0
  445. flowtask/utils/stats.py +308 -0
  446. flowtask/utils/transformations.py +74 -0
  447. flowtask/utils/uv.py +12 -0
  448. flowtask/utils/validators.py +97 -0
  449. flowtask/version.py +11 -0
  450. flowtask-5.8.4.dist-info/LICENSE +201 -0
  451. flowtask-5.8.4.dist-info/METADATA +209 -0
  452. flowtask-5.8.4.dist-info/RECORD +470 -0
  453. flowtask-5.8.4.dist-info/WHEEL +6 -0
  454. flowtask-5.8.4.dist-info/entry_points.txt +3 -0
  455. flowtask-5.8.4.dist-info/top_level.txt +2 -0
  456. plugins/components/CreateQR.py +39 -0
  457. plugins/components/TestComponent.py +28 -0
  458. plugins/components/Use1.py +13 -0
  459. plugins/components/Workplace.py +117 -0
  460. plugins/components/__init__.py +3 -0
  461. plugins/sources/__init__.py +0 -0
  462. plugins/sources/get_populartimes.py +78 -0
  463. plugins/sources/google.py +150 -0
  464. plugins/sources/hubspot.py +679 -0
  465. plugins/sources/icims.py +679 -0
  466. plugins/sources/mobileinsight.py +501 -0
  467. plugins/sources/newrelic.py +262 -0
  468. plugins/sources/uap.py +268 -0
  469. plugins/sources/venu.py +244 -0
  470. plugins/sources/vocinity.py +314 -0
@@ -0,0 +1,496 @@
1
+ import os
2
+ from typing import List, Optional, Union
3
+ from collections.abc import Callable
4
+ from urllib.parse import quote
5
+ from pathlib import Path, PurePath
6
+ from datetime import datetime, timedelta
7
+ from urllib.parse import urlparse
8
+ import requests
9
+ from tqdm import tqdm # Progress bar library
10
+ import pandas as pd
11
+ from io import BytesIO
12
+ from office365.sharepoint.client_context import ClientContext
13
+ from office365.runtime.http.request_options import RequestOptions
14
+ from office365.sharepoint.files.file import File
15
+ from ..exceptions import FileError, FileNotFound
16
+ from .O365Client import O365Client
17
+ from ..conf import (
18
+ SHAREPOINT_APP_ID,
19
+ SHAREPOINT_APP_SECRET,
20
+ SHAREPOINT_TENANT_ID,
21
+ SHAREPOINT_TENANT_NAME
22
+ )
23
+
24
+ class SharepointClient(O365Client):
25
+ """
26
+ Sharepoint Client.
27
+
28
+ Managing connections to MS Sharepoint Resources.
29
+ """
30
+ def __init__(self, *args, **kwargs):
31
+ super().__init__(*args, **kwargs)
32
+ # Default credentials
33
+ self._default_tenant_id = SHAREPOINT_TENANT_ID
34
+ self._default_client_id = SHAREPOINT_APP_ID
35
+ self._default_client_secret = SHAREPOINT_APP_SECRET
36
+ self._default_tenant_name = SHAREPOINT_TENANT_NAME
37
+
38
+ def get_context(self, url: str, *args) -> Callable:
39
+ return ClientContext(url, *args)
40
+
41
+ async def __aenter__(self):
42
+ return self
43
+
44
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
45
+ await self.close()
46
+ return self
47
+
48
+ def _start_(self, **kwargs):
49
+ # processing URL:
50
+ site = f"sites/{self.site}/" if self.site is not None else ""
51
+ self.site_url = f"https://{self.tenant}.sharepoint.com"
52
+ self.url = f"{self.site_url}/{site}".rstrip('/') # Ensure no trailing slash
53
+ if hasattr(self, '_srcfiles'):
54
+ for file in self._srcfiles:
55
+ fd = file.get('directory')
56
+ if 'sites' in fd:
57
+ file['directory'] = f"{fd}"
58
+ else:
59
+ file["directory"] = f"/{site}{fd}"
60
+ return True
61
+
62
+ def download_excel_from_sharepoint(
63
+ self,
64
+ file_url: str,
65
+ destination: Path = None,
66
+ as_pandas: bool = False
67
+ ):
68
+ try:
69
+ response = self.context.web.get_file_by_server_relative_url(file_url).download().execute_query()
70
+ content = response.content
71
+ if as_pandas:
72
+ bytes_buffer = BytesIO(content)
73
+ df = pd.read_excel(bytes_buffer)
74
+ return df
75
+ else:
76
+ with open(destination, "wb") as local_file:
77
+ local_file.write(content)
78
+ return str(destination)
79
+ except Exception as err:
80
+ self._logger.error(
81
+ f"Error downloading Excel file {file_url}: {err}"
82
+ )
83
+ raise FileError(
84
+ f"Error downloading Excel file {file_url}: {err}"
85
+ ) from err
86
+
87
+ async def file_search(self) -> List:
88
+ destinations = []
89
+ try:
90
+ # Get the default document library and its drive
91
+ drive = self.context.web.get_default_document_library().drive
92
+
93
+ for file in self._srcfiles:
94
+ directory = file["directory"]
95
+ fname = file["filename"]
96
+
97
+ # Search for the file within the drive
98
+ items = drive.root.search(f"name:{fname}")
99
+ self.context.load(items)
100
+ self.context.execute_query()
101
+
102
+ # Filter results to ensure they are within the specified directory
103
+ paths_matched = [
104
+ item.serverRelativeUrl for item in items
105
+ if fname in item.name and directory in item.serverRelativeUrl
106
+ ]
107
+
108
+ if len(paths_matched) == 0:
109
+ self._logger.error(
110
+ f"Error downloading File: Pattern not match {fname}"
111
+ )
112
+ raise FileError(
113
+ f"Error downloading File: Pattern not match {fname}"
114
+ )
115
+ else:
116
+ for path in paths_matched:
117
+ file = path[path.rfind("/") + 1: len(path)]
118
+ destination = "{}/{}".format(self.directory, file)
119
+ try:
120
+ with open(destination, "wb") as local_file:
121
+ self.context.web.get_file_by_server_relative_url(
122
+ path
123
+ ).download(local_file).execute_query()
124
+ destinations.append(destination)
125
+ except Exception as err:
126
+ raise RuntimeError(
127
+ f"Sharepoint: Error downloading file {path}: {err}"
128
+ ) from err
129
+ return destinations
130
+
131
+ except Exception as e:
132
+ print(e)
133
+
134
+ async def file_download(self) -> List:
135
+ destinations = []
136
+ for file in self._srcfiles:
137
+ directory = file.get('directory', self.directory)
138
+ fname = file.get('filename', self.filename)
139
+ if self.filename is None:
140
+ self.filename = fname
141
+ destination = self.directory.joinpath(fname)
142
+ else:
143
+ destination = self.filename if self.filename else fname
144
+ if not directory.endswith('/'):
145
+ directory += '/'
146
+ source = f"{directory}{fname}"
147
+ try:
148
+ self._logger.notice(
149
+ f"Sharepoint Download: {source}"
150
+ )
151
+ self.context.web.get_file_by_server_relative_url(
152
+ source
153
+ ).get().execute_query()
154
+ with open(destination, "wb") as local_file:
155
+ self.context.web.get_file_by_server_relative_url(source).download(
156
+ local_file
157
+ ).execute_query()
158
+ destinations.append(destination)
159
+ except Exception as err:
160
+ print('Sharepoint ERROR > ', err)
161
+ if 'Not Found for url' in str(err):
162
+ raise FileNotFound(
163
+ f"File {fname} not found: {err}"
164
+ )
165
+ else:
166
+ self._logger.error(
167
+ f"Error downloading file {fname}: {err}"
168
+ )
169
+ raise FileError(
170
+ f"Error downloading file {fname}: {err}"
171
+ ) from err
172
+ return destinations
173
+
174
+ async def download_files(self, files: List[dict], destination_dir: str) -> List:
175
+ """
176
+ Download a list of files from SharePoint to a specified destination directory.
177
+
178
+ Args:
179
+ files (List[dict]): A list of dictionaries with 'directory' and 'filename' keys.
180
+ destination_dir (str): The local directory where files will be downloaded.
181
+
182
+ Returns:
183
+ List: A list of paths to the downloaded files.
184
+ """
185
+ destination_dir = Path(destination_dir).resolve()
186
+ if not destination_dir.exists():
187
+ destination_dir.mkdir(parents=True, exist_ok=True)
188
+
189
+ destinations = []
190
+
191
+ for file in files:
192
+ directory = file.get('directory')
193
+ fname = file.get('filename')
194
+
195
+ if not directory or not fname:
196
+ raise ValueError(
197
+ "Each file entry must have both 'directory' and 'filename'."
198
+ )
199
+
200
+ # Build the SharePoint source path (directory + filename)
201
+ # Ensure forward slashes for SharePoint URLs
202
+ source = f"{directory}/{fname}".replace("\\", "/")
203
+
204
+ # Determine the destination file path
205
+ destination = destination_dir.joinpath(fname)
206
+
207
+ try:
208
+ # Fetch the file from SharePoint
209
+ self.context.web.get_file_by_server_relative_url(source).get().execute_query()
210
+
211
+ # Download the file to the local destination
212
+ with open(destination, "wb") as local_file:
213
+ self.context.web.get_file_by_server_relative_url(source).download(local_file).execute_query()
214
+
215
+ # Append the local destination path to the results
216
+ destinations.append(destination)
217
+ except Exception as err:
218
+ if 'Not Found for url' in str(err):
219
+ raise FileNotFound(
220
+ f"File {fname} not found: {err}"
221
+ )
222
+ else:
223
+ self._logger.error(
224
+ f"Error downloading file {fname}: {err}"
225
+ )
226
+ raise FileError(
227
+ f"Error downloading file {fname}: {err}"
228
+ ) from err
229
+
230
+ return destinations
231
+
232
+ def print_upload_progress(self, offset):
233
+ file_size = os.path.getsize(str(self._file_handler))
234
+ print(
235
+ "Uploaded '{0}' bytes from '{1}'...[{2}%]".format(offset, file_size, round(offset / file_size * 100, 2))
236
+ )
237
+
238
+ def _update_progress_bar(self, progress_bar, offset):
239
+ """Update the progress bar based on the current offset."""
240
+ progress_bar.n = offset # Set the current position
241
+ progress_bar.refresh() # Refresh the tqdm bar to show the update
242
+
243
+ async def _chunked_upload(
244
+ self,
245
+ target_folder: File,
246
+ file_path: Union[PurePath, Path],
247
+ file_name: str,
248
+ chunk_size: int = 10 * 1024 * 1024
249
+ ) -> File:
250
+ """
251
+ Perform a chunked upload for large files using the method from the GitHub example.
252
+
253
+ Args:
254
+ target_folder: SharePoint folder where the file is to be uploaded.
255
+ file_path: Path of the local file.
256
+ file_name: Name of the file in SharePoint.
257
+ chunk_size: Size of each chunk in bytes (default is 10 MB).
258
+
259
+ Returns:
260
+ File: The uploaded file object in SharePoint.
261
+ """
262
+ self._file_handler = file_path
263
+ file_size = os.path.getsize(file_path)
264
+
265
+ # Initialize tqdm progress bar
266
+ with tqdm(
267
+ total=file_size, unit='B', unit_scale=True, desc=f'Uploading {file_name}'
268
+ ) as pbar:
269
+ with open(file_path, 'rb') as file:
270
+ uploaded_file: File = target_folder.files.create_upload_session(
271
+ file,
272
+ chunk_size,
273
+ lambda offset: self._update_progress_bar(pbar, offset),
274
+ file_name=file_name
275
+ ).execute_query()
276
+
277
+ self._logger.debug(
278
+ 'File {0} has been uploaded successfully'.format(uploaded_file.serverRelativeUrl)
279
+ )
280
+
281
+ server_relative_url = uploaded_file.serverRelativeUrl
282
+ encoded_path = quote(server_relative_url)
283
+
284
+ absolute_url = f"{self.site_url}{encoded_path}"
285
+
286
+ self._logger.debug(
287
+ f"File {absolute_url} has been uploaded successfully"
288
+ )
289
+
290
+ # Return the uploaded file object
291
+ return {
292
+ # "filename": uploaded_file,
293
+ "relative_url": server_relative_url,
294
+ "absolute_url": absolute_url
295
+ }
296
+
297
+ async def upload_files(self, filenames: Optional[List[Union[Path, PurePath]]] = None) -> List[dict]:
298
+ """Upload files to a SharePoint folder using a resumable upload for large files."""
299
+ files = []
300
+ if not filenames:
301
+ filenames = self._srcfiles
302
+
303
+ for idx, file in enumerate(filenames):
304
+ destination_folder = self.directory # Destination SharePoint folder URL
305
+ try:
306
+ destination_file = self._destination[idx]
307
+ except KeyError:
308
+ destination_file = None
309
+ if isinstance(file, str):
310
+ file_path = Path(file) # Convert to Path object for compatibility
311
+ else:
312
+ file_path = file
313
+ file_size = file_path.stat().st_size
314
+ file_name = destination_file or file_path.name
315
+
316
+ try:
317
+ # Get the target folder
318
+ target_folder = self.context.web.get_folder_by_server_relative_url(destination_folder)
319
+ self.context.load(target_folder)
320
+ self.context.execute_query()
321
+
322
+ if file_size <= 4 * 1024 * 1024: # 4 MB threshold
323
+ # Small file, upload directly
324
+ async with open(file_path, "rb") as content_file:
325
+ file_content = await content_file.read()
326
+ target_file = target_folder.upload_file(file_name, file_content).execute_query()
327
+ else:
328
+ # Large file, use custom chunked upload
329
+ target_file = await self._chunked_upload(target_folder, file_path, file_name)
330
+
331
+ # Append file URL after successful upload
332
+ files.append(
333
+ {"filename": target_file}
334
+ )
335
+
336
+ except Exception as err:
337
+ self._logger.error(f"Error uploading file {file_name}: {err}")
338
+ raise FileError(f"Error uploading file {file_name}: {err}") from err
339
+
340
+ return files
341
+
342
+ async def upload_folder(self, local_folder: PurePath):
343
+ destinations = []
344
+ # destination:
345
+ destination = self._destination
346
+ try:
347
+ for p in local_folder.glob('**/*'):
348
+ # Check if it's a file or directory
349
+ if p.is_dir():
350
+ # Create corresponding folder in SharePoint if it doesn't exist
351
+ folder_path = self._get_sharepoint_folder_path(
352
+ p, local_folder, destination
353
+ )
354
+ await self._create_sharepoint_folder(folder_path)
355
+ elif p.is_file():
356
+ # Upload file to SharePoint
357
+ sharepoint_folder = self._get_sharepoint_folder_path(
358
+ p.parent, local_folder, destination
359
+ )
360
+ file_url = await self.upload_file(p, sharepoint_folder)
361
+ destinations.append(file_url)
362
+ return destinations # Return list of uploaded file URLs
363
+
364
+ except Exception as err:
365
+ self._logger.error(f"Error uploading folder {local_folder}: {err}")
366
+ raise FileError(f"Error uploading folder {local_folder}: {err}") from err
367
+
368
+ # Helper method to create folder paths in SharePoint
369
+ def _get_sharepoint_folder_path(
370
+ self,
371
+ path: PurePath,
372
+ local_folder: PurePath,
373
+ destination: str
374
+ ) -> str:
375
+ """Get the corresponding SharePoint folder path for a local file or folder."""
376
+ # Strip the local folder prefix and combine with SharePoint destination folder
377
+ relative_path = path.relative_to(local_folder)
378
+ sharepoint_folder_path = f"{destination}/{relative_path}".replace("\\", "/") # Convert to forward slashes
379
+ return sharepoint_folder_path
380
+
381
+ # Helper method to create a folder in SharePoint
382
+ async def _create_sharepoint_folder(
383
+ self,
384
+ folder_url: str
385
+ ):
386
+ """Create a folder in SharePoint if it doesn't exist."""
387
+ try:
388
+ folder = self.context.web.get_folder_by_server_relative_url(folder_url)
389
+ folder.execute_query() # Check if folder exists
390
+ except Exception:
391
+ # Folder does not exist, so create it
392
+ parent_folder_url = "/".join(folder_url.split("/")[:-1]) # Parent folder path
393
+ parent_folder = self.context.web.get_folder_by_server_relative_url(parent_folder_url)
394
+ parent_folder.folders.add(folder_url.split("/")[-1]).execute_query()
395
+ self._logger.info(f"Created folder: {folder_url}")
396
+
397
+ async def create_subscription(
398
+ self,
399
+ library_id: str,
400
+ webhook_url: str,
401
+ client_state: str = "secret_string",
402
+ expiration_days: int = 1
403
+ ) -> dict:
404
+ """
405
+ Create a webhook subscription to receive notifications when files are added, updated,
406
+ or deleted in a SharePoint document library.
407
+
408
+ Args:
409
+ library_id (str): The ID of the SharePoint document library to subscribe to.
410
+ webhook_url (str): The webhook URL to receive notifications.
411
+ client_state (str): A secret string to verify notifications.
412
+ expiration_days (int): Duration in days for the subscription to be valid (maximum is 180 days).
413
+
414
+ Returns:
415
+ dict: The response from Microsoft Graph API containing the subscription details.
416
+ """
417
+ # Set up expiration for the subscription (max 180 days)
418
+ expiration_date = datetime.utcnow() + timedelta(days=expiration_days)
419
+ expiration_datetime = expiration_date.isoformat() + "Z"
420
+
421
+ # Define the subscription request body
422
+ request_body = {
423
+ "changeType": "created,updated,deleted",
424
+ "notificationUrl": webhook_url,
425
+ "resource": f"sites/{self.tenant}/lists/{library_id}", # Resource ID for the library
426
+ "expirationDateTime": expiration_datetime,
427
+ "clientState": client_state
428
+ }
429
+
430
+ # Acquire an access token for Microsoft Graph
431
+ access_token = self._access_token
432
+ headers = {
433
+ "Authorization": f"Bearer {access_token}",
434
+ "Content-Type": "application/json"
435
+ }
436
+
437
+ # Send the subscription request to Microsoft Graph API
438
+ url = "https://graph.microsoft.com/v1.0/subscriptions"
439
+ response = requests.post(url, headers=headers, json=request_body)
440
+
441
+ # Handle the response
442
+ if response.status_code == 201:
443
+ subscription_info = response.json()
444
+ print("Subscription created successfully:", subscription_info)
445
+ return subscription_info
446
+ else:
447
+ error_message = response.json().get("error", {}).get("message", "Unknown error")
448
+ print(f"Failed to create subscription: {error_message}")
449
+ raise RuntimeError(f"Failed to create subscription: {error_message}")
450
+
451
+ def get_library_id(self, absolute_url: str) -> str:
452
+ """
453
+ Extracts the Library ID of a SharePoint document library from an absolute URL.
454
+
455
+ Args:
456
+ absolute_url (str): The absolute URL of the SharePoint resource.
457
+
458
+ Returns:
459
+ str: The ID of the document library.
460
+
461
+ Raises:
462
+ RuntimeError: If the library ID could not be retrieved.
463
+ """
464
+ try:
465
+ # Parse the absolute URL to get site and document library path
466
+ parsed_url = urlparse(absolute_url)
467
+ path_parts = parsed_url.path.strip("/").split("/")
468
+
469
+ # Format the site name and library path
470
+ site_name = path_parts[1] # e.g., 'sites/mysite'
471
+ library_name = "/".join(path_parts[2:]) # e.g., 'Documents'
472
+
473
+ # Construct the Microsoft Graph API endpoint
474
+ graph_api_url = f"https://graph.microsoft.com/v1.0/sites/{self._default_tenant_name}:/{site_name}:/lists/{library_name}" # noqa
475
+
476
+ # Acquire access token
477
+ access_token = self._access_token
478
+ headers = {
479
+ "Authorization": f"Bearer {access_token}",
480
+ "Content-Type": "application/json"
481
+ }
482
+
483
+ # Make a GET request to retrieve the library details
484
+ response = requests.get(graph_api_url, headers=headers)
485
+ response.raise_for_status()
486
+ library_info = response.json()
487
+
488
+ # Extract and return the library ID
489
+ library_id = library_info.get("id")
490
+ if not library_id:
491
+ raise RuntimeError("Library ID could not be found in the response.")
492
+ print(f"Library ID for {absolute_url} is {library_id}")
493
+ return library_id
494
+
495
+ except Exception as err:
496
+ raise RuntimeError(f"Failed to retrieve library ID: {err}") from err
@@ -0,0 +1,36 @@
1
+ """
2
+ Interfaces.
3
+
4
+ Services and methods covered by Flowtask.
5
+ Support interfaces for many options on Task Components.
6
+ """
7
+ from .func import FuncSupport
8
+ from .mask import MaskSupport
9
+ from .databases import DBSupport
10
+ from .log import LogSupport, SkipErrors
11
+ from .result import ResultSupport
12
+ from .cache import CacheSupport
13
+ from .stat import StatSupport
14
+ from .locale import LocaleSupport
15
+ from .template import TemplateSupport
16
+ from .http import HTTPService
17
+ from .selenium_service import SeleniumService
18
+ from .client import ClientInterface
19
+ from .db import DBInterface
20
+
21
+
22
+ __all__ = (
23
+ "FuncSupport",
24
+ "MaskSupport",
25
+ "DBSupport",
26
+ "LogSupport",
27
+ "ResultSupport",
28
+ "StatSupport",
29
+ "LocaleSupport",
30
+ "TemplateSupport",
31
+ "SkipErrors",
32
+ # interfaces:
33
+ "DBInterface",
34
+ "ClientInterface",
35
+ "HTTPService",
36
+ )
@@ -0,0 +1,119 @@
1
+ import msal
2
+ from navconfig.logging import logging
3
+ from ..conf import (
4
+ AZURE_TENANT_ID,
5
+ AZURE_CLIENT_ID,
6
+ AZURE_SECRET_ID,
7
+ )
8
+ """
9
+ AzureAuth Class
10
+
11
+ Overview
12
+
13
+ This class facilitates Azure Active Directory (AAD) authentication using the Microsoft Authentication Library (MSAL) for Python. It retrieves access tokens for a user or a client application based on provided credentials or cached tokens.
14
+
15
+ .. table:: Properties
16
+ :widths: auto
17
+
18
+ +------------------------+----------+-----------+---------------------------------------------------------------------------------+
19
+ | Name | Required | Summary |
20
+ +------------------------+----------+-----------+---------------------------------------------------------------------------------+
21
+ | tenant_id (optional) | No | Azure tenant ID (defaults to value from settings.settings.AZURE_TENANT_ID). |
22
+ +------------------------+----------+-----------+---------------------------------------------------------------------------------+
23
+ | client_id (optional) | No | Azure client application ID (defaults to value from settings.settings.AZURE_CLIENT_ID). |
24
+ +------------------------+----------+-----------+---------------------------------------------------------------------------------+
25
+ | client_secret | No | Azure client application secret (defaults to value from settings.settings.AZURE_SECRET_ID). |
26
+ +------------------------+----------+-----------+---------------------------------------------------------------------------------+
27
+ | scopes (optional) | No | List of OAuth 2.0 scopes to request during token acquisition |
28
+ | | | (defaults to a list including "offline_access", "https://outlook.office365.com/.default", |
29
+ | | | "email", "openid", and "profile"). |
30
+ +------------------------+----------+-----------+---------------------------------------------------------------------------------+
31
+
32
+ Methods:
33
+ * get_msal_client(client: bool = True): Returns an MSAL ClientApplication or ConfidentialClientApplication instance based on the client argument.
34
+ * get_token(username: str = None, password: str = None) -> str: Acquires an access token using username/password or from cache if available. Raises an exception on failure.
35
+ * binary_token(username: str = None, password: str = None) -> str: Retrieves an access token and constructs a binary auth string with user and token information.
36
+
37
+ This class is likely used within other components or scripts to authenticate with Azure services.
38
+
39
+ """ # noqa
40
+
41
+ DEFAULT_SCOPES = [
42
+ "offline_access https://outlook.office365.com/.default email openid profile"
43
+ ]
44
+
45
+ msal_logger = logging.getLogger("msal")
46
+ msal_logger.setLevel(logging.WARNING)
47
+
48
+
49
+ def generate_auth_string(user, token):
50
+ return f"user={user}\x01Auth=Bearer {token}\x01\x01"
51
+
52
+
53
+ class AzureAuth:
54
+ def get_msal_client(self, client: bool = True):
55
+ if client is True:
56
+ return msal.ClientApplication(
57
+ self.client_id,
58
+ authority=self.authority,
59
+ client_credential=self.client_secret,
60
+ validate_authority=True,
61
+ )
62
+ else:
63
+ return msal.ConfidentialClientApplication(
64
+ self.client_id,
65
+ authority=self.authority,
66
+ client_credential=self.client_secret,
67
+ validate_authority=True,
68
+ )
69
+
70
+ def __init__(
71
+ self,
72
+ tenant_id: str = None,
73
+ client_id: str = None,
74
+ client_secret: str = None,
75
+ scopes: list = None,
76
+ ) -> None:
77
+ self.tenant_id = tenant_id if tenant_id else AZURE_TENANT_ID
78
+ self.authority = f"https://login.microsoftonline.com/{self.tenant_id}"
79
+ # credentials:
80
+ self.client_id = client_id if client_id else AZURE_CLIENT_ID
81
+ self.client_secret = client_secret if client_secret else AZURE_SECRET_ID
82
+ # Token URL
83
+ self.token_uri = (
84
+ f"https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token"
85
+ )
86
+ # scopes:
87
+ self.scopes = scopes if scopes is not None else DEFAULT_SCOPES
88
+
89
+ def get_token(self, username: str = None, password: str = None) -> str:
90
+ result = None
91
+ if username is not None:
92
+ app = self.get_msal_client(client=True)
93
+ account = app.get_accounts(username=username)
94
+ if account:
95
+ logging.info("Account(s) exists in cache, probably with token too")
96
+ result = app.acquire_token_silent(self.scopes, account=account[0])
97
+ else:
98
+ result = app.acquire_token_by_username_password(
99
+ username, password, self.scopes
100
+ )
101
+ else:
102
+ app = self.get_msal_client(client=False)
103
+ result = app.acquire_token_silent(self.scopes, account=None)
104
+ if not result:
105
+ logging.info("No suitable token in cache. Get new one.")
106
+ result = app.acquire_token_for_client(scopes=self.scopes)
107
+ if "access_token" in result:
108
+ return result
109
+ else:
110
+ error = {
111
+ "error": result.get("error"),
112
+ "message": result.get("error_description"),
113
+ "correlation_id": result.get("correlation_id"),
114
+ }
115
+ raise Exception(f"Unable to Access: {error!s}")
116
+
117
+ def binary_token(self, username: str = None, password: str = None) -> str:
118
+ result = self.get_token(username=username, password=password)
119
+ return generate_auth_string(username, result["access_token"])