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,98 @@
1
+ from typing import Optional
2
+ from dataclasses import dataclass
3
+ import asyncio
4
+ import asyncpg
5
+ import backoff # For exponential backoff
6
+ from navconfig.logging import logging
7
+ from navigator.conf import default_dsn # Your default DSN configuration
8
+ from .base import BaseTrigger # Base class for triggers
9
+
10
+
11
+ @dataclass
12
+ class Notification:
13
+ channel: str
14
+ payload: str
15
+ pid: str
16
+
17
+
18
+ class PostgresTrigger(BaseTrigger):
19
+ """PostgresTrigger.
20
+
21
+ Trigger that listens for PostgreSQL events and sends notifications.
22
+ Using LISTEN/NOTIFY Infraestructure.
23
+ """
24
+ def __init__(
25
+ self,
26
+ *args,
27
+ dsn: Optional[str] = None,
28
+ channel: str = 'notifications',
29
+ **kwargs
30
+ ):
31
+ super().__init__(*args, **kwargs)
32
+ self.dsn = dsn or default_dsn
33
+ self.channel = channel
34
+ self.connection: Optional[asyncpg.Connection] = None
35
+ self._listening_task: Optional[asyncio.Task] = None
36
+ self._reconnecting = False
37
+
38
+ async def on_startup(self, app):
39
+ self._logger.info(
40
+ f"Starting PostgresTrigger listening on channel '{self.channel}'"
41
+ )
42
+ await self.connect()
43
+
44
+ async def on_shutdown(self, app):
45
+ self._logger.info("Shutting down PostgresTrigger")
46
+ if self._listening_task:
47
+ self._listening_task.cancel()
48
+ if self.connection:
49
+ await self.connection.close()
50
+
51
+ @backoff.on_exception(
52
+ backoff.expo,
53
+ (asyncpg.exceptions.PostgresError, ConnectionError),
54
+ max_tries=5,
55
+ on_backoff=lambda details: logging.warning(f"Retrying connection in {details['wait']} seconds...")
56
+ )
57
+ async def connect(self):
58
+ self.connection = await asyncpg.connect(dsn=self.dsn)
59
+ await self.connection.add_listener(
60
+ self.channel,
61
+ self.notification_handler
62
+ )
63
+ self._logger.info(
64
+ f"Connected to PostgreSQL and listening on channel '{self.channel}'"
65
+ )
66
+ self._listening_task = asyncio.create_task(self.keep_listening())
67
+
68
+ async def keep_listening(self):
69
+ try:
70
+ while True:
71
+ await asyncio.sleep(3600) # Keep the coroutine alive
72
+ except asyncio.CancelledError:
73
+ self._logger.info(
74
+ "Listening task cancelled"
75
+ )
76
+ except Exception as e:
77
+ self._logger.error(
78
+ f"Error in keep_listening: {e}"
79
+ )
80
+ await self.reconnect()
81
+
82
+ async def reconnect(self):
83
+ if not self._reconnecting:
84
+ self._reconnecting = True
85
+ if self.connection:
86
+ await self.connection.close()
87
+ self._logger.info(
88
+ "Attempting to reconnect to PostgreSQL..."
89
+ )
90
+ await self.connect()
91
+ self._reconnecting = False
92
+
93
+ async def notification_handler(self, connection, pid, channel, payload):
94
+ self._logger.info(
95
+ f"Received notification on channel '{channel}': {payload}"
96
+ )
97
+ notification = Notification(channel=channel, payload=payload, pid=pid)
98
+ await self.run_actions(notification=notification)
@@ -0,0 +1,8 @@
1
+ """
2
+ Responses.
3
+
4
+ Pre-defined responses for Hooks.
5
+ """
6
+ from .base import TriggerResponse
7
+
8
+ __all__ = ("TriggerResponse",)
@@ -0,0 +1,5 @@
1
+ class TriggerResponse:
2
+ """Basic Response.
3
+
4
+ Basic Reponse dispatched by a Hook.
5
+ """
@@ -0,0 +1,288 @@
1
+ from typing import Any, Optional
2
+ from collections.abc import Callable
3
+ import asyncio
4
+ import datetime
5
+ from urllib.parse import quote
6
+ import aiohttp
7
+ from aiohttp import web
8
+ from azure.identity.aio import ClientSecretCredential
9
+ from msgraph import GraphServiceClient, GraphRequestAdapter
10
+ from msgraph.generated.models.subscription import Subscription
11
+ from kiota_authentication_azure.azure_identity_authentication_provider import AzureIdentityAuthenticationProvider
12
+ from navconfig.logging import logging
13
+ from .http import HTTPHook
14
+ from ...conf import (
15
+ SHAREPOINT_APP_ID,
16
+ SHAREPOINT_APP_SECRET,
17
+ SHAREPOINT_TENANT_ID,
18
+ SHAREPOINT_DEFAULT_HOST,
19
+ SHAREPOINT_TENANT_NAME
20
+ )
21
+
22
+ logging.getLogger(name='azure.identity.aio').setLevel(logging.WARNING)
23
+ logging.getLogger(name='azure.core').setLevel(logging.WARNING)
24
+
25
+ DEFAULT_SCOPES = ["https://graph.microsoft.com/.default"]
26
+
27
+
28
+ class SharePointTrigger(HTTPHook):
29
+ def __init__(
30
+ self,
31
+ *args,
32
+ webhook_url: str,
33
+ tenant_id: Optional[str] = None,
34
+ client_id: Optional[str] = None,
35
+ client_secret: Optional[str] = None,
36
+ resource: Optional[str] = None,
37
+ **kwargs
38
+ ):
39
+ self.methods: list = ['POST']
40
+ super().__init__(*args, **kwargs)
41
+ self.tenant_id = tenant_id or SHAREPOINT_TENANT_ID
42
+ self._tenant_name = kwargs.get('tenant', SHAREPOINT_TENANT_NAME)
43
+ self.client_id = client_id or SHAREPOINT_APP_ID
44
+ self.client_secret = client_secret or SHAREPOINT_APP_SECRET
45
+ self.site_name: str = kwargs.get('site_name')
46
+ self._host: str = kwargs.get('host', SHAREPOINT_DEFAULT_HOST)
47
+ self.folder_path: str = kwargs.get('folder_path')
48
+ self.site_id: Optional[str] = None
49
+ self.resource = resource
50
+ self.webhook_url = webhook_url
51
+ self.client_state: str = kwargs.get('client_state', 'flowtask_state')
52
+ self.changetype: str = kwargs.get('changetype', 'updated')
53
+ self.access_token = None
54
+ self.subscription_id = None
55
+ self._msapp: Callable = None
56
+ self._graph_client: Callable = None
57
+ self.renewal_task = None
58
+ self.renewal_interval = 3600 * 24 # Renew every 24 hours
59
+ self.scopes = kwargs.get('scopes', DEFAULT_SCOPES)
60
+
61
+ def get_client(self):
62
+ return ClientSecretCredential(
63
+ tenant_id=self.tenant_id,
64
+ client_id=self.client_id,
65
+ client_secret=self.client_secret
66
+ )
67
+
68
+ def get_graph_client(
69
+ self,
70
+ client: Any,
71
+ scopes: Optional[list] = None
72
+ ):
73
+ if not scopes:
74
+ scopes = self.scopes
75
+ return GraphServiceClient(credentials=client, scopes=scopes)
76
+
77
+ async def authenticate(self):
78
+ """
79
+ Authenticates the client with Azure AD and initializes the Graph client.
80
+
81
+ This method creates a ClientSecretCredential using the tenant ID, client ID,
82
+ and client secret. It then sets up an AzureIdentityAuthenticationProvider,
83
+ initializes a GraphRequestAdapter, and finally creates a GraphServiceClient.
84
+
85
+ The method doesn't take any parameters as it uses the instance variables
86
+ for authentication details.
87
+
88
+ Returns:
89
+ None
90
+ """
91
+ self._client = ClientSecretCredential(
92
+ tenant_id=self.tenant_id,
93
+ client_id=self.client_id,
94
+ client_secret=self.client_secret
95
+ )
96
+ auth_provider = AzureIdentityAuthenticationProvider(self._client)
97
+ self._request_adapter = GraphRequestAdapter(auth_provider)
98
+ self._graph_client = GraphServiceClient(
99
+ credentials=self._client,
100
+ scopes=self.scopes,
101
+ request_adapter=self._request_adapter
102
+ )
103
+
104
+ async def create_subscription(self):
105
+ if not self._graph_client:
106
+ await self.authenticate()
107
+ expiration_datetime = (datetime.datetime.utcnow() + datetime.timedelta(minutes=4230)).isoformat() + 'Z'
108
+ subscription_data = Subscription(
109
+ change_type=self.changetype,
110
+ notification_url=self.webhook_url,
111
+ resource=self.resource,
112
+ expiration_date_time=expiration_datetime,
113
+ client_state=self.client_state,
114
+ )
115
+ # subscription_data.include_resource_data = True
116
+ self._logger.info(
117
+ f"Using webhook URL: {self.webhook_url}"
118
+ )
119
+ self._logger.info(f"Subscription data: {subscription_data}")
120
+ response = await self._graph_client.subscriptions.post(subscription_data)
121
+ if response:
122
+ self.subscription_id = response.id
123
+ else:
124
+ self._logger.error("Failed to create subscription")
125
+ raise Exception(
126
+ "Failed to create Sharepoint subscription"
127
+ )
128
+
129
+ async def delete_subscription(self):
130
+ if not self.subscription_id:
131
+ return
132
+ if not self._graph_client:
133
+ await self.authenticate()
134
+ result = await self._graph_client.subscriptions.by_subscription_id(
135
+ self.subscription_id
136
+ ).delete()
137
+ if result is None:
138
+ self._logger.info("Subscription deleted")
139
+ else:
140
+ self._logger.error("Failed to delete subscription")
141
+
142
+ async def renew_subscription(self):
143
+ while True:
144
+ await asyncio.sleep(self.renewal_interval)
145
+ if not self.subscription_id:
146
+ continue
147
+ if not self._graph_client:
148
+ await self.authenticate()
149
+ expiration_datetime = (datetime.datetime.utcnow() + datetime.timedelta(minutes=4230)).isoformat() + 'Z'
150
+ subscription = Subscription()
151
+ subscription.expiration_date_time = expiration_datetime
152
+ response = await self._graph_client.subscriptions.by_subscription_id(
153
+ self.subscription_id
154
+ ).patch(subscription)
155
+ if response:
156
+ self._logger.info("Subscription renewed")
157
+ else:
158
+ self._logger.error("Failed to renew subscription")
159
+
160
+ async def on_startup(self, app):
161
+ self._logger.info("Starting SharePointTrigger")
162
+ asyncio.create_task(self._subscription_creation())
163
+
164
+ async def _subscription_creation(self):
165
+ # delaying startup for 5 sconds
166
+ await asyncio.sleep(5)
167
+ if not self.resource:
168
+ await self.get_site_id()
169
+ self.build_resource()
170
+ await self.create_subscription()
171
+ self.renewal_task = asyncio.create_task(
172
+ self.renew_subscription()
173
+ )
174
+
175
+ async def on_shutdown(self, app):
176
+ self._logger.info("Shutting down SharePointTrigger")
177
+ if self.renewal_task:
178
+ self.renewal_task.cancel()
179
+ await self.delete_subscription()
180
+
181
+ async def post(self, request: web.Request):
182
+ self._logger.info("Received POST request")
183
+ # Handle validation token
184
+ validation_token = request.query.get('validationToken')
185
+ if validation_token:
186
+ self._logger.info(f"Received validation token: {validation_token}")
187
+ return web.Response(text=validation_token, status=200)
188
+
189
+ # Handle notifications
190
+ try:
191
+ data = await request.json()
192
+ self._logger.info(f"Received notification data: {data}")
193
+ except Exception as e:
194
+ self._logger.error(f"Failed to parse request JSON: {e}")
195
+ return web.Response(status=400)
196
+
197
+ # Verify clientState
198
+ if self.client_state:
199
+ client_state = data.get('value', [{}])[0].get('clientState')
200
+ if client_state != self.client_state:
201
+ self._logger.warning("Invalid clientState in notification")
202
+ return web.Response(status=202)
203
+
204
+ # Process notifications
205
+ await self.process_notifications(data)
206
+ return web.Response(status=202)
207
+
208
+ async def process_notifications(self, data):
209
+ notifications = data.get('value', [])
210
+ for notification in notifications:
211
+ resource = notification.get('resource')
212
+ self._logger.info(
213
+ f"Received notification for resource: {resource}"
214
+ )
215
+ # Fetch details about the changed file
216
+ file_details = await self.get_resource_details(resource)
217
+ # Run actions with the file details
218
+ await self.run_actions(file_details=file_details)
219
+
220
+ async def get_resource_details(self, resource):
221
+ client = self.get_client()
222
+ # Get the access token
223
+ token = await client.get_token("https://graph.microsoft.com/.default")
224
+ access_token = token.token
225
+
226
+ # Build the full URL
227
+ resource_url = f"https://graph.microsoft.com/v1.0/{resource.lstrip('/')}"
228
+
229
+ # Log the resource URL for debugging
230
+ self._logger.info(f"Fetching resource details from URL: {resource_url}")
231
+
232
+ headers = {
233
+ 'Authorization': f'Bearer {access_token}',
234
+ 'Accept': 'application/json'
235
+ }
236
+
237
+ async with aiohttp.ClientSession() as session:
238
+ async with session.get(resource_url, headers=headers) as response:
239
+ if response.status == 200:
240
+ data = await response.json()
241
+ return data
242
+ else:
243
+ error_text = await response.text()
244
+ self._logger.error(f"Failed to get resource details: {error_text}")
245
+ return {}
246
+
247
+ async def get_site_id(self):
248
+ if not self._graph_client:
249
+ await self.authenticate()
250
+ # Ensure site_name does not start with a slash
251
+ site_name = self.site_name.lstrip('/')
252
+
253
+ # Construct the site URL
254
+ site_request_url = f"https://graph.microsoft.com/v1.0/sites/{self._tenant_name}.sharepoint.com:/sites/{site_name}" # noqa
255
+
256
+ # Log the constructed URL for debugging
257
+ self._logger.info(
258
+ f"Site request URL: {site_request_url}"
259
+ )
260
+
261
+ # Use `with_url` with the relative path, not the full URL
262
+ site_request_builder = self._graph_client.sites.with_url(site_request_url)
263
+
264
+ # Fetch the site information
265
+ site = await site_request_builder.get()
266
+ if site:
267
+ self.site_id = site.additional_data.get('id').split(',')[1]
268
+ self._logger.info(
269
+ f"Site ID obtained: {self.site_id}"
270
+ )
271
+ else:
272
+ self._logger.error(
273
+ f"Failed to get site ID for site: {self.site_name}"
274
+ )
275
+ raise Exception(
276
+ f"Failed to get site ID for site: {self.site_name}"
277
+ )
278
+
279
+ def build_resource(self):
280
+ if not self.site_id:
281
+ raise Exception("Site ID not available. Cannot build resource URL.")
282
+ folder_path = self.folder_path.strip('/')
283
+ encoded_folder_path = quote(folder_path)
284
+ # /{encoded_folder_path}:/children
285
+ self.resource = f"/sites/{self.site_id}/drive/root"
286
+ self._logger.info(
287
+ f"Resource URL built: {self.resource}"
288
+ )
@@ -0,0 +1,141 @@
1
+ import time
2
+ import fnmatch
3
+ import threading
4
+ import paramiko
5
+ from navconfig.logging import logging
6
+ from .watch import BaseWatchdog, BaseWatcher
7
+ from ...interfaces import CacheSupport
8
+
9
+ logging.getLogger("paramiko").setLevel(logging.WARNING)
10
+
11
+ class SFTPWatcher(BaseWatcher):
12
+ def __init__(
13
+ self,
14
+ host: str,
15
+ port: int,
16
+ username: str,
17
+ password: str,
18
+ path: str,
19
+ interval: int = 300, # Intervalo de verificación en segundos
20
+ max_retries: int = 5, # Número máximo de reintentos
21
+ retry_delay: int = 60, # Tiempo de espera entre reintentos en segundos
22
+ **kwargs,
23
+ ):
24
+ super(SFTPWatcher, self).__init__(**kwargs)
25
+ self.host = host
26
+ self.port = port
27
+ self.user = username
28
+ self.password = password
29
+ self.interval = interval
30
+ self.path = path
31
+ self._expiration = kwargs.pop("every", None)
32
+ self.max_retries = max_retries
33
+ self.retry_delay = retry_delay
34
+ self.stop_event = kwargs.get("stop_event", threading.Event()) # Evento para detener el Watchdog
35
+
36
+ def close_watcher(self):
37
+ pass
38
+
39
+ def run(self, *args, **kwargs):
40
+ retries = 0
41
+ while not self.stop_event.is_set():
42
+ try:
43
+ self._logger.info(f"Intentando conectar a {self.host}:{self.port}")
44
+ # Conectar al servidor SSH
45
+ ssh_client = paramiko.SSHClient()
46
+ ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
47
+ ssh_client.connect(
48
+ hostname=self.host,
49
+ port=self.port,
50
+ username=self.user,
51
+ password=self.password,
52
+ timeout=10 # Timeout de 10 segundos
53
+ )
54
+ # Conectar al servidor SFTP
55
+ sftp_client = ssh_client.open_sftp()
56
+ self._logger.info(f"Conexión SFTP establecida en {self.host}:{self.port}")
57
+
58
+ # Verificar si el archivo o directorio existe
59
+ try:
60
+ directory, pattern = self.path.rsplit("/", 1)
61
+ files = sftp_client.listdir(directory)
62
+ matching_files = fnmatch.filter(files, pattern)
63
+
64
+ found_files = []
65
+ with CacheSupport(every=self._expiration) as cache:
66
+ for file in matching_files:
67
+ filepath = f"{directory}/{file}"
68
+ if cache.exists(filepath):
69
+ continue
70
+ stat = sftp_client.stat(filepath)
71
+ file_info = {
72
+ "filename": file,
73
+ "directory": directory,
74
+ "path": filepath,
75
+ "host": self.host,
76
+ "size": stat.st_size,
77
+ "perms": oct(stat.st_mode),
78
+ "modified": time.ctime(stat.st_mtime),
79
+ }
80
+ self._logger.notice(f"Encontrado {self.path} en {self.host}: {file_info}")
81
+ found_files.append(file_info)
82
+ cache.setexp(filepath, value=filepath)
83
+ if found_files:
84
+ args = {"files": found_files, **kwargs}
85
+ self.parent.call_actions(**args)
86
+ except FileNotFoundError:
87
+ self._logger.warning(f"Ruta no encontrada: {self.path}")
88
+ finally:
89
+ sftp_client.close()
90
+ ssh_client.close()
91
+ self._logger.info(f"Desconectado de {self.host}:{self.port}")
92
+ retries = 0 # Reiniciar el contador de reintentos tras una conexión exitosa
93
+ except (paramiko.SSHException, paramiko.AuthenticationException, paramiko.BadHostKeyException) as e:
94
+ self._logger.error(f"Error de conexión SSH/SFTP: {e}")
95
+ retries += 1
96
+ if retries >= self.max_retries:
97
+ self._logger.error(f"Se alcanzó el máximo de reintentos ({self.max_retries}). Deteniendo Watchdog.")
98
+ break
99
+ self._logger.info(f"Reintentando en {self.retry_delay} segundos... (Intento {retries}/{self.max_retries})")
100
+ self._sleep_with_stop(self.retry_delay)
101
+ continue
102
+ except Exception as e:
103
+ self._logger.error(f"Se produjo un error al verificar el servidor: {e}")
104
+ retries += 1
105
+ if retries >= self.max_retries:
106
+ self._logger.error(f"Se alcanzó el máximo de reintentos ({self.max_retries}). Deteniendo Watchdog.")
107
+ break
108
+ self._logger.info(f"Reintentando en {self.retry_delay} segundos... (Intento {retries}/{self.max_retries})")
109
+ self._sleep_with_stop(self.retry_delay)
110
+ continue
111
+
112
+ # Esperar el intervalo antes de la siguiente verificación
113
+ self._sleep_with_stop(self.interval)
114
+
115
+ def _sleep_with_stop(self, duration: int):
116
+ """
117
+ Duerme de manera interrumpible, verificando el evento de parada cada segundo.
118
+ """
119
+ for _ in range(duration):
120
+ if self.stop_event and self.stop_event.is_set():
121
+ break
122
+ time.sleep(1)
123
+
124
+
125
+ class SFTPWatchdog(BaseWatchdog):
126
+ def create_watcher(self, *args, **kwargs) -> BaseWatcher:
127
+ credentials = kwargs.pop("credentials", {})
128
+ interval = kwargs.pop("interval", 300)
129
+ self.mask_start(**kwargs)
130
+ self.credentials = self.set_credentials(credentials)
131
+ self.path = self.mask_replacement(kwargs.pop("path", None))
132
+ # Crear un evento de parada para controlar la detención del Watchdog
133
+ stop_event = threading.Event()
134
+ self.stop_event = stop_event
135
+ return SFTPWatcher(
136
+ **self.credentials,
137
+ path=self.path,
138
+ interval=interval,
139
+ stop_event=self.stop_event,
140
+ **kwargs
141
+ )
@@ -0,0 +1,59 @@
1
+ from .watch import BaseWatcher
2
+ from .imap import IMAPWatchdog, ImapWatcher
3
+
4
+
5
+ class TaggedImapWatcher(ImapWatcher):
6
+ def __init__(self, tagged_address: str, *args, **kwargs):
7
+ self.tagged_address = tagged_address
8
+ super(TaggedImapWatcher, self).__init__(*args, **kwargs)
9
+
10
+ def process_email(self, email_id, **kwargs):
11
+ # Explicitly mark the email as SEEN
12
+ self.imap_server.store(email_id, "+FLAGS", "\SEEN")
13
+ typ, _ = self.imap_server.fetch(email_id, "(FLAGS)")
14
+ if typ != "OK":
15
+ self._logger.warning(f"Cannot mark email {email_id} as read (SEEN)")
16
+ super(TaggedImapWatcher, self).process_email(email_id, **kwargs)
17
+
18
+ def build_search_criteria(self, search_criteria):
19
+ criteria = []
20
+ for key, value in search_criteria.items():
21
+ if value is None:
22
+ criteria.append(key)
23
+ else:
24
+ criteria.append(f'({key} "{value}")')
25
+ criteria.append(f'(TO "{self.tagged_address}") UNSEEN')
26
+ return " ".join(criteria)
27
+
28
+
29
+ class TaggedIMAPWatchdog(IMAPWatchdog):
30
+ def get_tagged_address(self, username: str, tag: str) -> str:
31
+ user, domain = username.split("@")
32
+ return f"{user}+{tag}@{domain}"
33
+
34
+ def create_watcher(self, *args, **kwargs) -> BaseWatcher:
35
+ tag = kwargs.pop("tag", None)
36
+ if not tag:
37
+ raise ValueError("TAG is required for TaggedIMAPWatchdog")
38
+ credentials = kwargs.pop("credentials", {})
39
+ self.mailbox = kwargs.pop("mailbox", "INBOX")
40
+ interval = kwargs.pop("interval", 10)
41
+ authmech = credentials.pop("authmech", None)
42
+ self.mask_start(**kwargs)
43
+ search = self.processing_search(kwargs.pop("search", {}))
44
+ # TODO: processing with masks the Search criteria:
45
+ self.credentials = self.set_credentials(credentials)
46
+ alias_address = kwargs.pop("alias_address", None)
47
+ if alias_address:
48
+ tagged_address = alias_address
49
+ else:
50
+ tagged_address = self.get_tagged_address(credentials["user"], tag)
51
+ return TaggedImapWatcher(
52
+ **self.credentials,
53
+ mailbox=self.mailbox,
54
+ interval=interval,
55
+ authmech=authmech,
56
+ search=search,
57
+ tagged_address=tagged_address,
58
+ **kwargs,
59
+ )
@@ -0,0 +1,85 @@
1
+ import os
2
+ from typing import Optional, List
3
+ from aiohttp import web
4
+ from .http import HTTPHook
5
+
6
+
7
+ class UploadHook(HTTPHook):
8
+ """UploadHook.
9
+
10
+ A Trigger that handles file uploads via HTTP POST/PUT requests.
11
+ """
12
+
13
+ methods: list = ["POST", "PUT"]
14
+
15
+ def __init__(
16
+ self,
17
+ *args,
18
+ allowed_mime_types: Optional[List[str]] = None,
19
+ allowed_file_names: Optional[List[str]] = None,
20
+ **kwargs
21
+ ):
22
+ super().__init__(*args, **kwargs)
23
+ self.allowed_mime_types = allowed_mime_types
24
+ self.allowed_file_names = allowed_file_names
25
+
26
+ async def post(self, request: web.Request):
27
+ # Use handle_upload to get uploaded files and form data
28
+ try:
29
+ uploaded_files_info, form_data = await self.handle_upload(request)
30
+ except Exception as e:
31
+ # Return error response
32
+ return self.response(
33
+ response={'error': str(e)},
34
+ status=400,
35
+ content_type='application/json'
36
+ )
37
+
38
+ # Validate files if necessary
39
+ for file_info in uploaded_files_info:
40
+ file_path = file_info['file_path']
41
+ file_name = file_info['file_name']
42
+ mime_type = file_info['mime_type']
43
+
44
+ if self.allowed_mime_types and mime_type not in self.allowed_mime_types:
45
+ return self.response(
46
+ response={'error': f'Invalid mime type: {mime_type}'},
47
+ status=400,
48
+ content_type='application/json'
49
+ )
50
+
51
+ if self.allowed_file_names and file_name not in self.allowed_file_names:
52
+ return self.response(
53
+ response={'error': f'Invalid file name: {file_name}'},
54
+ status=400,
55
+ content_type='application/json'
56
+ )
57
+
58
+ # Prepare data to pass to run_actions
59
+ data = {
60
+ 'uploaded_files': [str(file_info['file_path']) for file_info in uploaded_files_info],
61
+ 'form_data': form_data
62
+ }
63
+ # Run actions
64
+ result = await self.run_actions(**data)
65
+ return self.response(
66
+ response=result,
67
+ status=self.default_status
68
+ )
69
+
70
+ async def put(self, request: web.Request):
71
+ return await self.post(request)
72
+
73
+ async def run_actions(self, **data):
74
+ result = await super().run_actions(**data)
75
+ try:
76
+ # Clean up uploaded files after actions have run
77
+ uploaded_files = data.get('uploaded_files', [])
78
+ for file_path in uploaded_files:
79
+ try:
80
+ os.remove(file_path)
81
+ self._logger.info(f"Deleted temporary file: {file_path}")
82
+ except Exception as e:
83
+ self._logger.warning(f"Failed to delete temporary file {file_path}: {e}")
84
+ finally:
85
+ return result