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.
- flowtask/__init__.py +93 -0
- flowtask/__main__.py +38 -0
- flowtask/bots/__init__.py +6 -0
- flowtask/bots/check.py +93 -0
- flowtask/bots/codebot.py +51 -0
- flowtask/components/ASPX.py +148 -0
- flowtask/components/AddDataset.py +352 -0
- flowtask/components/Amazon.py +523 -0
- flowtask/components/AutoTask.py +314 -0
- flowtask/components/Azure.py +80 -0
- flowtask/components/AzureUsers.py +106 -0
- flowtask/components/BaseAction.py +91 -0
- flowtask/components/BaseLoop.py +198 -0
- flowtask/components/BestBuy.py +800 -0
- flowtask/components/CSVToGCS.py +120 -0
- flowtask/components/CompanyScraper/__init__.py +1 -0
- flowtask/components/CompanyScraper/parsers/__init__.py +6 -0
- flowtask/components/CompanyScraper/parsers/base.py +102 -0
- flowtask/components/CompanyScraper/parsers/explorium.py +192 -0
- flowtask/components/CompanyScraper/parsers/leadiq.py +206 -0
- flowtask/components/CompanyScraper/parsers/rocket.py +133 -0
- flowtask/components/CompanyScraper/parsers/siccode.py +109 -0
- flowtask/components/CompanyScraper/parsers/visualvisitor.py +130 -0
- flowtask/components/CompanyScraper/parsers/zoominfo.py +118 -0
- flowtask/components/CompanyScraper/scrapper.py +1054 -0
- flowtask/components/CopyTo.py +177 -0
- flowtask/components/CopyToBigQuery.py +243 -0
- flowtask/components/CopyToMongoDB.py +291 -0
- flowtask/components/CopyToPg.py +609 -0
- flowtask/components/CopyToRethink.py +207 -0
- flowtask/components/CreateGCSBucket.py +102 -0
- flowtask/components/CreateReport/CreateReport.py +228 -0
- flowtask/components/CreateReport/__init__.py +9 -0
- flowtask/components/CreateReport/charts/__init__.py +15 -0
- flowtask/components/CreateReport/charts/bar.py +51 -0
- flowtask/components/CreateReport/charts/base.py +66 -0
- flowtask/components/CreateReport/charts/pie.py +64 -0
- flowtask/components/CreateReport/utils.py +9 -0
- flowtask/components/CustomerSatisfaction.py +196 -0
- flowtask/components/DataInput.py +200 -0
- flowtask/components/DateList.py +255 -0
- flowtask/components/DbClient.py +163 -0
- flowtask/components/DialPad.py +146 -0
- flowtask/components/DocumentDBQuery.py +200 -0
- flowtask/components/DownloadFrom.py +371 -0
- flowtask/components/DownloadFromD2L.py +113 -0
- flowtask/components/DownloadFromFTP.py +181 -0
- flowtask/components/DownloadFromIMAP.py +315 -0
- flowtask/components/DownloadFromS3.py +198 -0
- flowtask/components/DownloadFromSFTP.py +265 -0
- flowtask/components/DownloadFromSharepoint.py +110 -0
- flowtask/components/DownloadFromSmartSheet.py +114 -0
- flowtask/components/DownloadS3File.py +229 -0
- flowtask/components/Dummy.py +59 -0
- flowtask/components/DuplicatePhoto.py +411 -0
- flowtask/components/EmployeeEvaluation.py +237 -0
- flowtask/components/ExecuteSQL.py +323 -0
- flowtask/components/ExtractHTML.py +178 -0
- flowtask/components/FileBase.py +178 -0
- flowtask/components/FileCopy.py +181 -0
- flowtask/components/FileDelete.py +82 -0
- flowtask/components/FileExists.py +146 -0
- flowtask/components/FileIteratorDelete.py +112 -0
- flowtask/components/FileList.py +194 -0
- flowtask/components/FileOpen.py +75 -0
- flowtask/components/FileRead.py +120 -0
- flowtask/components/FileRename.py +106 -0
- flowtask/components/FilterIf.py +284 -0
- flowtask/components/FilterRows/FilterRows.py +200 -0
- flowtask/components/FilterRows/__init__.py +10 -0
- flowtask/components/FilterRows/functions.py +4 -0
- flowtask/components/GCSToBigQuery.py +103 -0
- flowtask/components/GoogleA4.py +150 -0
- flowtask/components/GoogleGeoCoding.py +344 -0
- flowtask/components/GooglePlaces.py +315 -0
- flowtask/components/GoogleSearch.py +539 -0
- flowtask/components/HTTPClient.py +268 -0
- flowtask/components/ICIMS.py +146 -0
- flowtask/components/IF.py +179 -0
- flowtask/components/IcimsFolderCopy.py +173 -0
- flowtask/components/ImageFeatures/__init__.py +5 -0
- flowtask/components/ImageFeatures/process.py +233 -0
- flowtask/components/IteratorBase.py +251 -0
- flowtask/components/LangchainLoader/__init__.py +5 -0
- flowtask/components/LangchainLoader/loader.py +194 -0
- flowtask/components/LangchainLoader/loaders/__init__.py +22 -0
- flowtask/components/LangchainLoader/loaders/abstract.py +362 -0
- flowtask/components/LangchainLoader/loaders/basepdf.py +50 -0
- flowtask/components/LangchainLoader/loaders/docx.py +91 -0
- flowtask/components/LangchainLoader/loaders/html.py +119 -0
- flowtask/components/LangchainLoader/loaders/pdfblocks.py +146 -0
- flowtask/components/LangchainLoader/loaders/pdfmark.py +79 -0
- flowtask/components/LangchainLoader/loaders/pdftables.py +135 -0
- flowtask/components/LangchainLoader/loaders/qa.py +67 -0
- flowtask/components/LangchainLoader/loaders/txt.py +55 -0
- flowtask/components/LeadIQ.py +650 -0
- flowtask/components/Loop.py +253 -0
- flowtask/components/Lowes.py +334 -0
- flowtask/components/MS365Usage.py +156 -0
- flowtask/components/MSTeamsMessages.py +320 -0
- flowtask/components/MarketClustering.py +1051 -0
- flowtask/components/MergeFiles.py +362 -0
- flowtask/components/MilvusOutput.py +87 -0
- flowtask/components/NearByStores.py +175 -0
- flowtask/components/NetworkNinja/__init__.py +6 -0
- flowtask/components/NetworkNinja/models/__init__.py +52 -0
- flowtask/components/NetworkNinja/models/abstract.py +177 -0
- flowtask/components/NetworkNinja/models/account.py +39 -0
- flowtask/components/NetworkNinja/models/client.py +19 -0
- flowtask/components/NetworkNinja/models/district.py +14 -0
- flowtask/components/NetworkNinja/models/events.py +101 -0
- flowtask/components/NetworkNinja/models/forms.py +499 -0
- flowtask/components/NetworkNinja/models/market.py +16 -0
- flowtask/components/NetworkNinja/models/organization.py +34 -0
- flowtask/components/NetworkNinja/models/photos.py +125 -0
- flowtask/components/NetworkNinja/models/project.py +44 -0
- flowtask/components/NetworkNinja/models/region.py +28 -0
- flowtask/components/NetworkNinja/models/store.py +203 -0
- flowtask/components/NetworkNinja/models/user.py +151 -0
- flowtask/components/NetworkNinja/router.py +854 -0
- flowtask/components/Odoo.py +175 -0
- flowtask/components/OdooInjector.py +192 -0
- flowtask/components/OpenFromXML.py +126 -0
- flowtask/components/OpenWeather.py +41 -0
- flowtask/components/OpenWithBase.py +616 -0
- flowtask/components/OpenWithPandas.py +715 -0
- flowtask/components/PGPDecrypt.py +199 -0
- flowtask/components/PandasIterator.py +187 -0
- flowtask/components/PandasToFile.py +189 -0
- flowtask/components/Paradox.py +339 -0
- flowtask/components/ParamIterator.py +117 -0
- flowtask/components/ParseHTML.py +84 -0
- flowtask/components/PlacerStores.py +249 -0
- flowtask/components/Pokemon.py +507 -0
- flowtask/components/PositiveBot.py +62 -0
- flowtask/components/PowerPointSlide.py +400 -0
- flowtask/components/PrintMessage.py +127 -0
- flowtask/components/ProductCompetitors/__init__.py +5 -0
- flowtask/components/ProductCompetitors/parsers/__init__.py +7 -0
- flowtask/components/ProductCompetitors/parsers/base.py +72 -0
- flowtask/components/ProductCompetitors/parsers/bestbuy.py +86 -0
- flowtask/components/ProductCompetitors/parsers/lowes.py +103 -0
- flowtask/components/ProductCompetitors/scrapper.py +155 -0
- flowtask/components/ProductCompliant.py +169 -0
- flowtask/components/ProductInfo/__init__.py +1 -0
- flowtask/components/ProductInfo/parsers/__init__.py +5 -0
- flowtask/components/ProductInfo/parsers/base.py +83 -0
- flowtask/components/ProductInfo/parsers/brother.py +97 -0
- flowtask/components/ProductInfo/parsers/canon.py +167 -0
- flowtask/components/ProductInfo/parsers/epson.py +118 -0
- flowtask/components/ProductInfo/parsers/hp.py +131 -0
- flowtask/components/ProductInfo/parsers/samsung.py +97 -0
- flowtask/components/ProductInfo/scraper.py +319 -0
- flowtask/components/ProductPricing.py +118 -0
- flowtask/components/QS.py +261 -0
- flowtask/components/QSBase.py +201 -0
- flowtask/components/QueryIterator.py +273 -0
- flowtask/components/QueryToInsert.py +327 -0
- flowtask/components/QueryToPandas.py +432 -0
- flowtask/components/RESTClient.py +195 -0
- flowtask/components/RethinkDBQuery.py +189 -0
- flowtask/components/Rsync.py +74 -0
- flowtask/components/RunSSH.py +59 -0
- flowtask/components/RunShell.py +71 -0
- flowtask/components/SalesForce.py +20 -0
- flowtask/components/SaveImageBank/__init__.py +257 -0
- flowtask/components/SchedulingVisits.py +592 -0
- flowtask/components/ScrapPage.py +216 -0
- flowtask/components/ScrapSearch.py +79 -0
- flowtask/components/SendNotify.py +257 -0
- flowtask/components/SentimentAnalysis.py +694 -0
- flowtask/components/ServiceScrapper/__init__.py +5 -0
- flowtask/components/ServiceScrapper/parsers/__init__.py +1 -0
- flowtask/components/ServiceScrapper/parsers/base.py +94 -0
- flowtask/components/ServiceScrapper/parsers/costco.py +93 -0
- flowtask/components/ServiceScrapper/scrapper.py +199 -0
- flowtask/components/SetVariables.py +156 -0
- flowtask/components/SubTask.py +182 -0
- flowtask/components/SuiteCRM.py +48 -0
- flowtask/components/Switch.py +175 -0
- flowtask/components/TableBase.py +148 -0
- flowtask/components/TableDelete.py +312 -0
- flowtask/components/TableInput.py +143 -0
- flowtask/components/TableOutput/TableOutput.py +384 -0
- flowtask/components/TableOutput/__init__.py +3 -0
- flowtask/components/TableSchema.py +534 -0
- flowtask/components/Target.py +223 -0
- flowtask/components/ThumbnailGenerator.py +156 -0
- flowtask/components/ToPandas.py +67 -0
- flowtask/components/TransformRows/TransformRows.py +507 -0
- flowtask/components/TransformRows/__init__.py +9 -0
- flowtask/components/TransformRows/functions.py +559 -0
- flowtask/components/TransposeRows.py +176 -0
- flowtask/components/UPCDatabase.py +86 -0
- flowtask/components/UnGzip.py +171 -0
- flowtask/components/Uncompress.py +172 -0
- flowtask/components/UniqueRows.py +126 -0
- flowtask/components/Unzip.py +107 -0
- flowtask/components/UpdateOperationalVars.py +147 -0
- flowtask/components/UploadTo.py +299 -0
- flowtask/components/UploadToS3.py +136 -0
- flowtask/components/UploadToSFTP.py +160 -0
- flowtask/components/UploadToSharepoint.py +205 -0
- flowtask/components/UserFunc.py +122 -0
- flowtask/components/VivaTracker.py +140 -0
- flowtask/components/WSDLClient.py +123 -0
- flowtask/components/Wait.py +18 -0
- flowtask/components/Walmart.py +199 -0
- flowtask/components/Workplace.py +134 -0
- flowtask/components/XMLToPandas.py +267 -0
- flowtask/components/Zammad/__init__.py +41 -0
- flowtask/components/Zammad/models.py +0 -0
- flowtask/components/ZoomInfoScraper.py +409 -0
- flowtask/components/__init__.py +104 -0
- flowtask/components/abstract.py +18 -0
- flowtask/components/flow.py +530 -0
- flowtask/components/google.py +335 -0
- flowtask/components/group.py +221 -0
- flowtask/components/py.typed +0 -0
- flowtask/components/reviewscrap.py +132 -0
- flowtask/components/tAutoincrement.py +117 -0
- flowtask/components/tConcat.py +109 -0
- flowtask/components/tExplode.py +119 -0
- flowtask/components/tFilter.py +184 -0
- flowtask/components/tGroup.py +236 -0
- flowtask/components/tJoin.py +270 -0
- flowtask/components/tMap/__init__.py +9 -0
- flowtask/components/tMap/functions.py +54 -0
- flowtask/components/tMap/tMap.py +450 -0
- flowtask/components/tMelt.py +112 -0
- flowtask/components/tMerge.py +114 -0
- flowtask/components/tOrder.py +93 -0
- flowtask/components/tPandas.py +94 -0
- flowtask/components/tPivot.py +71 -0
- flowtask/components/tPluckCols.py +76 -0
- flowtask/components/tUnnest.py +82 -0
- flowtask/components/user.py +401 -0
- flowtask/conf.py +457 -0
- flowtask/download.py +102 -0
- flowtask/events/__init__.py +11 -0
- flowtask/events/events/__init__.py +20 -0
- flowtask/events/events/abstract.py +95 -0
- flowtask/events/events/alerts/__init__.py +362 -0
- flowtask/events/events/alerts/colfunctions.py +131 -0
- flowtask/events/events/alerts/functions.py +158 -0
- flowtask/events/events/dummy.py +12 -0
- flowtask/events/events/exec.py +124 -0
- flowtask/events/events/file/__init__.py +7 -0
- flowtask/events/events/file/base.py +51 -0
- flowtask/events/events/file/copy.py +23 -0
- flowtask/events/events/file/delete.py +16 -0
- flowtask/events/events/interfaces/__init__.py +9 -0
- flowtask/events/events/interfaces/client.py +67 -0
- flowtask/events/events/interfaces/credentials.py +28 -0
- flowtask/events/events/interfaces/notifications.py +58 -0
- flowtask/events/events/jira.py +122 -0
- flowtask/events/events/log.py +26 -0
- flowtask/events/events/logerr.py +52 -0
- flowtask/events/events/notify.py +59 -0
- flowtask/events/events/notify_event.py +160 -0
- flowtask/events/events/publish.py +54 -0
- flowtask/events/events/sendfile.py +104 -0
- flowtask/events/events/task.py +97 -0
- flowtask/events/events/teams.py +98 -0
- flowtask/events/events/webhook.py +58 -0
- flowtask/events/manager.py +287 -0
- flowtask/exceptions.c +39393 -0
- flowtask/exceptions.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/extensions/__init__.py +3 -0
- flowtask/extensions/abstract.py +82 -0
- flowtask/extensions/logging/__init__.py +65 -0
- flowtask/hooks/__init__.py +9 -0
- flowtask/hooks/actions/__init__.py +22 -0
- flowtask/hooks/actions/abstract.py +66 -0
- flowtask/hooks/actions/dummy.py +23 -0
- flowtask/hooks/actions/jira.py +74 -0
- flowtask/hooks/actions/rest.py +320 -0
- flowtask/hooks/actions/sampledata.py +37 -0
- flowtask/hooks/actions/sensor.py +23 -0
- flowtask/hooks/actions/task.py +9 -0
- flowtask/hooks/actions/ticket.py +37 -0
- flowtask/hooks/actions/zammad.py +55 -0
- flowtask/hooks/hook.py +62 -0
- flowtask/hooks/models.py +17 -0
- flowtask/hooks/service.py +187 -0
- flowtask/hooks/step.py +91 -0
- flowtask/hooks/types/__init__.py +23 -0
- flowtask/hooks/types/base.py +129 -0
- flowtask/hooks/types/brokers/__init__.py +11 -0
- flowtask/hooks/types/brokers/base.py +54 -0
- flowtask/hooks/types/brokers/mqtt.py +35 -0
- flowtask/hooks/types/brokers/rabbitmq.py +82 -0
- flowtask/hooks/types/brokers/redis.py +83 -0
- flowtask/hooks/types/brokers/sqs.py +44 -0
- flowtask/hooks/types/fs.py +232 -0
- flowtask/hooks/types/http.py +49 -0
- flowtask/hooks/types/imap.py +200 -0
- flowtask/hooks/types/jira.py +279 -0
- flowtask/hooks/types/mail.py +205 -0
- flowtask/hooks/types/postgres.py +98 -0
- flowtask/hooks/types/responses/__init__.py +8 -0
- flowtask/hooks/types/responses/base.py +5 -0
- flowtask/hooks/types/sharepoint.py +288 -0
- flowtask/hooks/types/ssh.py +141 -0
- flowtask/hooks/types/tagged.py +59 -0
- flowtask/hooks/types/upload.py +85 -0
- flowtask/hooks/types/watch.py +71 -0
- flowtask/hooks/types/web.py +36 -0
- flowtask/interfaces/AzureClient.py +137 -0
- flowtask/interfaces/AzureGraph.py +839 -0
- flowtask/interfaces/Boto3Client.py +326 -0
- flowtask/interfaces/DropboxClient.py +173 -0
- flowtask/interfaces/ExcelHandler.py +94 -0
- flowtask/interfaces/FTPClient.py +131 -0
- flowtask/interfaces/GoogleCalendar.py +201 -0
- flowtask/interfaces/GoogleClient.py +133 -0
- flowtask/interfaces/GoogleDrive.py +127 -0
- flowtask/interfaces/GoogleGCS.py +89 -0
- flowtask/interfaces/GoogleGeocoding.py +93 -0
- flowtask/interfaces/GoogleLang.py +114 -0
- flowtask/interfaces/GooglePub.py +61 -0
- flowtask/interfaces/GoogleSheet.py +68 -0
- flowtask/interfaces/IMAPClient.py +137 -0
- flowtask/interfaces/O365Calendar.py +113 -0
- flowtask/interfaces/O365Client.py +220 -0
- flowtask/interfaces/OneDrive.py +284 -0
- flowtask/interfaces/Outlook.py +155 -0
- flowtask/interfaces/ParrotBot.py +130 -0
- flowtask/interfaces/SSHClient.py +378 -0
- flowtask/interfaces/Sharepoint.py +496 -0
- flowtask/interfaces/__init__.py +36 -0
- flowtask/interfaces/azureauth.py +119 -0
- flowtask/interfaces/cache.py +201 -0
- flowtask/interfaces/client.py +82 -0
- flowtask/interfaces/compress.py +525 -0
- flowtask/interfaces/credentials.py +124 -0
- flowtask/interfaces/d2l.py +239 -0
- flowtask/interfaces/databases/__init__.py +5 -0
- flowtask/interfaces/databases/db.py +223 -0
- flowtask/interfaces/databases/documentdb.py +55 -0
- flowtask/interfaces/databases/rethink.py +39 -0
- flowtask/interfaces/dataframes/__init__.py +11 -0
- flowtask/interfaces/dataframes/abstract.py +21 -0
- flowtask/interfaces/dataframes/arrow.py +71 -0
- flowtask/interfaces/dataframes/dt.py +69 -0
- flowtask/interfaces/dataframes/pandas.py +167 -0
- flowtask/interfaces/dataframes/polars.py +60 -0
- flowtask/interfaces/db.py +263 -0
- flowtask/interfaces/env.py +46 -0
- flowtask/interfaces/func.py +137 -0
- flowtask/interfaces/http.py +1780 -0
- flowtask/interfaces/locale.py +40 -0
- flowtask/interfaces/log.py +75 -0
- flowtask/interfaces/mask.py +143 -0
- flowtask/interfaces/notification.py +154 -0
- flowtask/interfaces/playwright.py +339 -0
- flowtask/interfaces/powerpoint.py +368 -0
- flowtask/interfaces/py.typed +0 -0
- flowtask/interfaces/qs.py +376 -0
- flowtask/interfaces/result.py +87 -0
- flowtask/interfaces/selenium_service.py +779 -0
- flowtask/interfaces/smartsheet.py +154 -0
- flowtask/interfaces/stat.py +39 -0
- flowtask/interfaces/task.py +96 -0
- flowtask/interfaces/template.py +118 -0
- flowtask/interfaces/vectorstores/__init__.py +1 -0
- flowtask/interfaces/vectorstores/abstract.py +133 -0
- flowtask/interfaces/vectorstores/milvus.py +669 -0
- flowtask/interfaces/zammad.py +107 -0
- flowtask/models.py +193 -0
- flowtask/parsers/__init__.py +15 -0
- flowtask/parsers/_yaml.c +11978 -0
- flowtask/parsers/_yaml.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/argparser.py +235 -0
- flowtask/parsers/base.c +15155 -0
- flowtask/parsers/base.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/json.c +11968 -0
- flowtask/parsers/json.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/maps.py +49 -0
- flowtask/parsers/toml.c +11968 -0
- flowtask/parsers/toml.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/plugins/__init__.py +16 -0
- flowtask/plugins/components/__init__.py +0 -0
- flowtask/plugins/handler/__init__.py +45 -0
- flowtask/plugins/importer.py +31 -0
- flowtask/plugins/sources/__init__.py +0 -0
- flowtask/runner.py +283 -0
- flowtask/scheduler/__init__.py +9 -0
- flowtask/scheduler/functions.py +493 -0
- flowtask/scheduler/handlers/__init__.py +8 -0
- flowtask/scheduler/handlers/manager.py +504 -0
- flowtask/scheduler/handlers/models.py +58 -0
- flowtask/scheduler/handlers/service.py +72 -0
- flowtask/scheduler/notifications.py +65 -0
- flowtask/scheduler/scheduler.py +993 -0
- flowtask/services/__init__.py +0 -0
- flowtask/services/bots/__init__.py +0 -0
- flowtask/services/bots/telegram.py +264 -0
- flowtask/services/files/__init__.py +11 -0
- flowtask/services/files/manager.py +522 -0
- flowtask/services/files/model.py +37 -0
- flowtask/services/files/service.py +767 -0
- flowtask/services/jira/__init__.py +3 -0
- flowtask/services/jira/jira_actions.py +191 -0
- flowtask/services/tasks/__init__.py +13 -0
- flowtask/services/tasks/launcher.py +213 -0
- flowtask/services/tasks/manager.py +323 -0
- flowtask/services/tasks/service.py +275 -0
- flowtask/services/tasks/task_manager.py +376 -0
- flowtask/services/tasks/tasks.py +155 -0
- flowtask/storages/__init__.py +16 -0
- flowtask/storages/exceptions.py +12 -0
- flowtask/storages/files/__init__.py +8 -0
- flowtask/storages/files/abstract.py +29 -0
- flowtask/storages/files/filesystem.py +66 -0
- flowtask/storages/tasks/__init__.py +19 -0
- flowtask/storages/tasks/abstract.py +26 -0
- flowtask/storages/tasks/database.py +33 -0
- flowtask/storages/tasks/filesystem.py +108 -0
- flowtask/storages/tasks/github.py +119 -0
- flowtask/storages/tasks/memory.py +45 -0
- flowtask/storages/tasks/row.py +25 -0
- flowtask/tasks/__init__.py +0 -0
- flowtask/tasks/abstract.py +526 -0
- flowtask/tasks/command.py +118 -0
- flowtask/tasks/pile.py +486 -0
- flowtask/tasks/py.typed +0 -0
- flowtask/tasks/task.py +778 -0
- flowtask/template/__init__.py +161 -0
- flowtask/tests.py +257 -0
- flowtask/types/__init__.py +8 -0
- flowtask/types/typedefs.c +11347 -0
- flowtask/types/typedefs.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/__init__.py +24 -0
- flowtask/utils/constants.py +117 -0
- flowtask/utils/encoders.py +21 -0
- flowtask/utils/executor.py +112 -0
- flowtask/utils/functions.cpp +14280 -0
- flowtask/utils/functions.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/json.cpp +13349 -0
- flowtask/utils/json.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/mail.py +63 -0
- flowtask/utils/parseqs.c +13324 -0
- flowtask/utils/parserqs.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/stats.py +308 -0
- flowtask/utils/transformations.py +74 -0
- flowtask/utils/uv.py +12 -0
- flowtask/utils/validators.py +97 -0
- flowtask/version.py +11 -0
- flowtask-5.8.4.dist-info/LICENSE +201 -0
- flowtask-5.8.4.dist-info/METADATA +209 -0
- flowtask-5.8.4.dist-info/RECORD +470 -0
- flowtask-5.8.4.dist-info/WHEEL +6 -0
- flowtask-5.8.4.dist-info/entry_points.txt +3 -0
- flowtask-5.8.4.dist-info/top_level.txt +2 -0
- plugins/components/CreateQR.py +39 -0
- plugins/components/TestComponent.py +28 -0
- plugins/components/Use1.py +13 -0
- plugins/components/Workplace.py +117 -0
- plugins/components/__init__.py +3 -0
- plugins/sources/__init__.py +0 -0
- plugins/sources/get_populartimes.py +78 -0
- plugins/sources/google.py +150 -0
- plugins/sources/hubspot.py +679 -0
- plugins/sources/icims.py +679 -0
- plugins/sources/mobileinsight.py +501 -0
- plugins/sources/newrelic.py +262 -0
- plugins/sources/uap.py +268 -0
- plugins/sources/venu.py +244 -0
- 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,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
|