flowtask 5.8.4__cp310-cp310-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-310-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-310-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/argparser.py +235 -0
- flowtask/parsers/base.c +15155 -0
- flowtask/parsers/base.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/json.c +11968 -0
- flowtask/parsers/json.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/maps.py +49 -0
- flowtask/parsers/toml.c +11968 -0
- flowtask/parsers/toml.cpython-310-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-310-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-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/json.cpp +13349 -0
- flowtask/utils/json.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/mail.py +63 -0
- flowtask/utils/parseqs.c +13324 -0
- flowtask/utils/parserqs.cpython-310-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,131 @@
|
|
1
|
+
"""
|
2
|
+
FTP Client.
|
3
|
+
|
4
|
+
Overview
|
5
|
+
|
6
|
+
This class provides functionalities for asynchronous operations with an FTP server, including establishing connections,
|
7
|
+
checking directory existence, changing working directories, and downloading files.
|
8
|
+
|
9
|
+
"""
|
10
|
+
from typing import List, Dict
|
11
|
+
from collections.abc import Callable
|
12
|
+
import aioftp
|
13
|
+
from ..exceptions import ComponentError
|
14
|
+
from .client import ClientInterface
|
15
|
+
|
16
|
+
|
17
|
+
"""
|
18
|
+
FOR FTP over SSL
|
19
|
+
|
20
|
+
from ftplib import FTP_TLS
|
21
|
+
|
22
|
+
ftps = FTP_TLS(timeout=10)
|
23
|
+
ftps.set_debuglevel(2)
|
24
|
+
ftps.context.set_ciphers('DEFAULT@SECLEVEL=1')
|
25
|
+
|
26
|
+
ftps.connect(host, port)
|
27
|
+
|
28
|
+
ftps.login('user', 'password')
|
29
|
+
# enable TLS
|
30
|
+
ftps.auth()
|
31
|
+
ftps.prot_p()
|
32
|
+
ftps.retrlines('LIST')
|
33
|
+
"""
|
34
|
+
|
35
|
+
|
36
|
+
class FTPClient(ClientInterface):
|
37
|
+
block_size = 8192
|
38
|
+
algorithms: List = [
|
39
|
+
"ssh-rsa",
|
40
|
+
"ssh-dss",
|
41
|
+
"sk-ssh-ed25519-cert-v01@openssh.com",
|
42
|
+
"sk-ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
43
|
+
"ssh-ed25519-cert-v01@openssh.com",
|
44
|
+
"ssh-ed448-cert-v01@openssh.com",
|
45
|
+
"ecdsa-sha2-nistp521-cert-v01@openssh.com",
|
46
|
+
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
|
47
|
+
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
|
48
|
+
"ecdsa-sha2-1.3.132.0.10-cert-v01@openssh.com",
|
49
|
+
"ssh-rsa-cert-v01@openssh.com",
|
50
|
+
"ssh-dss-cert-v01@openssh.com",
|
51
|
+
"sk-ssh-ed25519@openssh.com",
|
52
|
+
"sk-ecdsa-sha2-nistp256@openssh.com",
|
53
|
+
"ssh-ed25519",
|
54
|
+
"ssh-ed448",
|
55
|
+
"ecdsa-sha2-nistp521",
|
56
|
+
"ecdsa-sha2-nistp384",
|
57
|
+
"ecdsa-sha2-nistp256",
|
58
|
+
"ecdsa-sha2-1.3.132.0.10",
|
59
|
+
"rsa-sha2-256",
|
60
|
+
"rsa-sha2-512",
|
61
|
+
]
|
62
|
+
|
63
|
+
async def close(self):
|
64
|
+
"""Close Method."""
|
65
|
+
try:
|
66
|
+
await self._connection.quit()
|
67
|
+
except Exception as err:
|
68
|
+
print(f"Error on FTP disconnection, reason: {err!s}")
|
69
|
+
|
70
|
+
async def init_connection(
|
71
|
+
self, host, port, credentials: Dict, ssl: Callable = None
|
72
|
+
):
|
73
|
+
"""
|
74
|
+
init an FTP connection
|
75
|
+
"""
|
76
|
+
args = {
|
77
|
+
"socket_timeout": 120,
|
78
|
+
"path_timeout": 30,
|
79
|
+
"encoding": "utf-8",
|
80
|
+
"ssl": ssl,
|
81
|
+
}
|
82
|
+
connection = None
|
83
|
+
try:
|
84
|
+
connection = aioftp.Client(**args)
|
85
|
+
await connection.connect(host, port)
|
86
|
+
except ValueError as err:
|
87
|
+
raise ComponentError(f"{err!s}") from err
|
88
|
+
except OSError as err:
|
89
|
+
raise ComponentError(f"FTP connection failed: {err!s}") from err
|
90
|
+
except Exception as err:
|
91
|
+
raise ComponentError(f"FTP Exception: {err!s}") from err
|
92
|
+
try:
|
93
|
+
await connection.login(*credentials.values())
|
94
|
+
return connection
|
95
|
+
except aioftp.StatusCodeError as err:
|
96
|
+
raise ComponentError(f"FTP connection Error: {err!s}") from err
|
97
|
+
except Exception as err:
|
98
|
+
raise ComponentError(f"FTP Exception: {err!s}") from err
|
99
|
+
|
100
|
+
def err_handler(self, err):
|
101
|
+
print(f"FTP Error: reason: {err.reason}, error: {err}")
|
102
|
+
return False
|
103
|
+
|
104
|
+
async def directory_exists(self, directory: str):
|
105
|
+
return await self._connection.exists(directory)
|
106
|
+
|
107
|
+
async def change_directory(self, directory: str):
|
108
|
+
await self._connection.change_directory(directory)
|
109
|
+
|
110
|
+
async def open(self):
|
111
|
+
pass
|
112
|
+
|
113
|
+
async def download_file(self, file: str, destination: str, rewrite: bool = False):
|
114
|
+
"""download_file
|
115
|
+
|
116
|
+
Download a File from FTP based on Path.
|
117
|
+
Args:
|
118
|
+
file (str): file to be downloaded
|
119
|
+
destination (str): path to destination
|
120
|
+
rewrite (bool): file if exists, will be overwrite
|
121
|
+
TODO: Support for write_into and Renaming Files.
|
122
|
+
"""
|
123
|
+
try:
|
124
|
+
await self._connection.download(
|
125
|
+
file,
|
126
|
+
destination=destination,
|
127
|
+
write_into=rewrite,
|
128
|
+
block_size=self.block_size,
|
129
|
+
)
|
130
|
+
except Exception as err:
|
131
|
+
raise ComponentError(f"FTP Download Exception: {err!s}") from err
|
@@ -0,0 +1,201 @@
|
|
1
|
+
from abc import ABC
|
2
|
+
from typing import List, Dict
|
3
|
+
import asyncio
|
4
|
+
from datetime import datetime, timedelta
|
5
|
+
import requests
|
6
|
+
from googleapiclient.discovery import build
|
7
|
+
from .GoogleClient import GoogleClient
|
8
|
+
from ..exceptions import ComponentError
|
9
|
+
|
10
|
+
|
11
|
+
class GoogleCalendarClient(GoogleClient, ABC):
|
12
|
+
"""
|
13
|
+
Google Calendar Client for managing calendar events.
|
14
|
+
"""
|
15
|
+
|
16
|
+
async def get_client(self):
|
17
|
+
"""Get the Google Calendar client, with caching."""
|
18
|
+
if not hasattr(self, '_client'):
|
19
|
+
self.service = await asyncio.to_thread(build, 'calendar', 'v3', credentials=self.credentials)
|
20
|
+
return self.service
|
21
|
+
|
22
|
+
async def create_event(self, calendar_id: str, event: Dict) -> Dict:
|
23
|
+
"""
|
24
|
+
Create an event in the specified Google Calendar.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
calendar_id (str): The ID of the calendar to add the event to.
|
28
|
+
event (dict): The event details.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
dict: Details of the created event.
|
32
|
+
"""
|
33
|
+
client = await self.get_client()
|
34
|
+
created_event = await asyncio.to_thread(client.events().insert(calendarId=calendar_id, body=event).execute)
|
35
|
+
print(f"Event created: {created_event.get('htmlLink')}")
|
36
|
+
return created_event
|
37
|
+
|
38
|
+
async def list_events(self, calendar_id: str, time_min: datetime, time_max: datetime, max_results: int = 10) -> List[Dict]:
|
39
|
+
"""
|
40
|
+
List events in a specified time range.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
calendar_id (str): The ID of the calendar to list events from.
|
44
|
+
time_min (datetime): Start time to retrieve events.
|
45
|
+
time_max (datetime): End time to retrieve events.
|
46
|
+
max_results (int): Maximum number of events to retrieve (default is 10).
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
list: List of events within the time range.
|
50
|
+
"""
|
51
|
+
client = await self.get_client()
|
52
|
+
events_result = await asyncio.to_thread(
|
53
|
+
client.events().list,
|
54
|
+
calendarId=calendar_id,
|
55
|
+
timeMin=time_min.isoformat() + 'Z',
|
56
|
+
timeMax=time_max.isoformat() + 'Z',
|
57
|
+
maxResults=max_results,
|
58
|
+
singleEvents=True,
|
59
|
+
orderBy="startTime"
|
60
|
+
)
|
61
|
+
events = events_result.execute().get('items', [])
|
62
|
+
return events
|
63
|
+
|
64
|
+
async def update_event(self, calendar_id: str, event_id: str, updated_event: Dict) -> Dict:
|
65
|
+
"""
|
66
|
+
Update an existing event in the calendar.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
calendar_id (str): The ID of the calendar containing the event.
|
70
|
+
event_id (str): The ID of the event to update.
|
71
|
+
updated_event (dict): The updated event details.
|
72
|
+
|
73
|
+
Returns:
|
74
|
+
dict: Details of the updated event.
|
75
|
+
"""
|
76
|
+
client = await self.get_client()
|
77
|
+
event = await asyncio.to_thread(client.events().update, calendarId=calendar_id, eventId=event_id, body=updated_event)
|
78
|
+
return event.execute()
|
79
|
+
|
80
|
+
async def delete_event(self, calendar_id: str, event_id: str):
|
81
|
+
"""
|
82
|
+
Delete an event from the specified calendar.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
calendar_id (str): The ID of the calendar.
|
86
|
+
event_id (str): The ID of the event to delete.
|
87
|
+
"""
|
88
|
+
client = await self.get_client()
|
89
|
+
await asyncio.to_thread(client.events().delete(calendarId=calendar_id, eventId=event_id).execute)
|
90
|
+
print(f"Event {event_id} deleted.")
|
91
|
+
|
92
|
+
async def get_event(self, calendar_id: str, event_id: str) -> Dict:
|
93
|
+
"""
|
94
|
+
Retrieve details of a specific event.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
calendar_id (str): The ID of the calendar.
|
98
|
+
event_id (str): The ID of the event.
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
dict: Details of the event.
|
102
|
+
"""
|
103
|
+
client = await self.get_client()
|
104
|
+
event = await asyncio.to_thread(client.events().get(calendarId=calendar_id, eventId=event_id).execute)
|
105
|
+
return event
|
106
|
+
|
107
|
+
async def setup_watch(self, calendar_id: str, webhook_url: str, channel_id: str) -> Dict:
|
108
|
+
"""
|
109
|
+
Sets up a watch on the specified calendar to receive notifications when events are created, updated, or deleted.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
calendar_id (str): The ID of the calendar to monitor.
|
113
|
+
webhook_url (str): The URL of the webhook endpoint to receive notifications.
|
114
|
+
channel_id (str): Unique ID for this notification channel.
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
dict: The response from the Google Calendar API containing the channel information.
|
118
|
+
"""
|
119
|
+
# Define request body for the watch request
|
120
|
+
request_body = {
|
121
|
+
"id": channel_id, # Unique identifier for the channel
|
122
|
+
"type": "webhook",
|
123
|
+
"address": webhook_url, # Webhook URL to receive notifications
|
124
|
+
"params": {
|
125
|
+
"ttl": "86400" # Time-to-live in seconds, max 604800 (7 days)
|
126
|
+
}
|
127
|
+
}
|
128
|
+
if not self.service:
|
129
|
+
await self.get_client()
|
130
|
+
|
131
|
+
try:
|
132
|
+
# Set up the watch on the specified calendar
|
133
|
+
response = self.service.events().watch(calendarId=calendar_id, body=request_body).execute()
|
134
|
+
print("Watch setup successful:", response)
|
135
|
+
return response
|
136
|
+
except Exception as e:
|
137
|
+
print(f"Error setting up watch: {e}")
|
138
|
+
raise
|
139
|
+
|
140
|
+
async def check_event_start_time(self, event_start_time: datetime):
|
141
|
+
"""
|
142
|
+
Check if the current time has reached or passed the event's start time and trigger an action if so.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
event_start_time (datetime): The start time of the event to check.
|
146
|
+
|
147
|
+
"""
|
148
|
+
while True:
|
149
|
+
now = datetime.utcnow()
|
150
|
+
if now >= event_start_time:
|
151
|
+
# Trigger the action when event time is reached
|
152
|
+
print("Event time has been reached! Triggering action...")
|
153
|
+
# You can add your custom action here
|
154
|
+
break
|
155
|
+
await asyncio.sleep(60) # Check every minute
|
156
|
+
|
157
|
+
async def create_subscription(self, webhook_url: str, client_state: str = "secret_string", expiration_hours: int = 1) -> dict:
|
158
|
+
"""
|
159
|
+
Create a subscription to receive notifications when events are created, updated, or deleted in the calendar.
|
160
|
+
|
161
|
+
Args:
|
162
|
+
webhook_url (str): The webhook URL that will receive the notifications.
|
163
|
+
client_state (str): A client secret string for verifying notifications.
|
164
|
+
expiration_hours (int): Duration for which the subscription should be valid (maximum is 4230 minutes or 7 days).
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
dict: The response from Microsoft Graph API with subscription details.
|
168
|
+
"""
|
169
|
+
# Set up expiration for subscription (max 7 days)
|
170
|
+
expiration_date = datetime.utcnow() + timedelta(hours=expiration_hours)
|
171
|
+
expiration_datetime = expiration_date.isoformat() + "Z"
|
172
|
+
|
173
|
+
# Define the subscription request body
|
174
|
+
request_body = {
|
175
|
+
"changeType": "created,updated,deleted",
|
176
|
+
"notificationUrl": webhook_url,
|
177
|
+
"resource": "me/events", # Subscribe to the user's calendar events
|
178
|
+
"expirationDateTime": expiration_datetime,
|
179
|
+
"clientState": client_state
|
180
|
+
}
|
181
|
+
|
182
|
+
# Acquire access token for authentication
|
183
|
+
access_token = self._access_token
|
184
|
+
headers = {
|
185
|
+
"Authorization": f"Bearer {access_token}",
|
186
|
+
"Content-Type": "application/json"
|
187
|
+
}
|
188
|
+
|
189
|
+
# Send the subscription request to Microsoft Graph API
|
190
|
+
url = "https://graph.microsoft.com/v1.0/subscriptions"
|
191
|
+
response = requests.post(url, headers=headers, json=request_body)
|
192
|
+
|
193
|
+
# Check for successful response
|
194
|
+
if response.status_code == 201:
|
195
|
+
subscription_info = response.json()
|
196
|
+
print("Subscription created successfully:", subscription_info)
|
197
|
+
return subscription_info
|
198
|
+
else:
|
199
|
+
error_message = response.json().get("error", {}).get("message", "Unknown error")
|
200
|
+
print(f"Failed to create subscription: {error_message}")
|
201
|
+
raise ComponentError(f"Failed to create subscription: {error_message}")
|
@@ -0,0 +1,133 @@
|
|
1
|
+
from pathlib import Path, PurePath
|
2
|
+
from typing import Union, List, Dict, Callable
|
3
|
+
from abc import ABC
|
4
|
+
from oauth2client.service_account import ServiceAccountCredentials
|
5
|
+
from googleapiclient.discovery import build
|
6
|
+
from navconfig import BASE_DIR
|
7
|
+
from ..exceptions import ComponentError, ConfigError
|
8
|
+
from ..conf import GOOGLE_CREDENTIALS_FILE
|
9
|
+
|
10
|
+
|
11
|
+
# Define the scope
|
12
|
+
default_scopes = [
|
13
|
+
"https://spreadsheets.google.com/feeds",
|
14
|
+
"https://www.googleapis.com/auth/drive"
|
15
|
+
]
|
16
|
+
|
17
|
+
|
18
|
+
class GoogleClient(ABC):
|
19
|
+
"""
|
20
|
+
Google Client Client.
|
21
|
+
|
22
|
+
Managing Authentication and resources from Google Apps.
|
23
|
+
|
24
|
+
"""
|
25
|
+
def __init__(self, *args, credentials: Union[str, dict] = None, **kwargs):
|
26
|
+
self.credentials_file: PurePath = None
|
27
|
+
self.credentials_str: str = None
|
28
|
+
self.credentials_dict: dict = None
|
29
|
+
self.scopes: list = kwargs.pop('scopes', default_scopes)
|
30
|
+
if credentials is None:
|
31
|
+
if not GOOGLE_CREDENTIALS_FILE.exists():
|
32
|
+
raise ComponentError(
|
33
|
+
"Google: No credentials provided."
|
34
|
+
)
|
35
|
+
self.credentials_file = GOOGLE_CREDENTIALS_FILE
|
36
|
+
if isinstance(credentials, str):
|
37
|
+
# end with JSON, then are a credentials file:
|
38
|
+
if credentials.endswith(".json"):
|
39
|
+
self.credentials_file = Path(credentials).resolve()
|
40
|
+
if not self.credentials_file.exists():
|
41
|
+
# Check if File is on BASE PATH env.
|
42
|
+
self.credentials_file = BASE_DIR.joinpath(credentials).resolve()
|
43
|
+
if not self.credentials_file.exists():
|
44
|
+
raise ConfigError(
|
45
|
+
f"Google: Credentials file not found: {self.credentials_file}"
|
46
|
+
)
|
47
|
+
else:
|
48
|
+
# Assume is a JSON string
|
49
|
+
self.credentials_str = credentials
|
50
|
+
elif isinstance(credentials, PurePath):
|
51
|
+
self.credentials_file = Path(credentials).resolve()
|
52
|
+
if not self.credentials_file.exists():
|
53
|
+
raise ConfigError(
|
54
|
+
f"Google: No credentials file on {self.credentials_file}"
|
55
|
+
)
|
56
|
+
elif isinstance(credentials, dict):
|
57
|
+
self.credentials_dict = credentials
|
58
|
+
|
59
|
+
super().__init__(*args, **kwargs)
|
60
|
+
|
61
|
+
def connection(self):
|
62
|
+
if self.credentials_file:
|
63
|
+
self.credentials = ServiceAccountCredentials.from_json_keyfile_name(
|
64
|
+
self.credentials_file,
|
65
|
+
scopes=self.scopes,
|
66
|
+
)
|
67
|
+
elif self.credentials_dict:
|
68
|
+
self.credentials = ServiceAccountCredentials.from_json_keyfile_dict(
|
69
|
+
self.credentials_dict,
|
70
|
+
scopes=self.scopes
|
71
|
+
)
|
72
|
+
elif self.credentials_str:
|
73
|
+
self.credentials = ServiceAccountCredentials.from_json(
|
74
|
+
self.credentials_str,
|
75
|
+
scopes=self.scopes
|
76
|
+
)
|
77
|
+
else:
|
78
|
+
self.credentials = ServiceAccountCredentials.from_json_keyfile_name(
|
79
|
+
GOOGLE_CREDENTIALS_FILE,
|
80
|
+
scopes=self.scopes
|
81
|
+
)
|
82
|
+
return self
|
83
|
+
|
84
|
+
def get_service(self, service: str, version: str = 'v3'):
|
85
|
+
"""
|
86
|
+
Get a cached Google API service instance or create one if not cached.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
service (str): Name of the Google service (e.g., 'drive', 'sheets').
|
90
|
+
version (str): Version of the API (default: 'v3').
|
91
|
+
|
92
|
+
Returns:
|
93
|
+
googleapiclient.discovery.Resource: The requested Google API service client.
|
94
|
+
"""
|
95
|
+
if not self.credentials:
|
96
|
+
self.connection()
|
97
|
+
if (srv := getattr(self, f"_{service}", None)):
|
98
|
+
return srv
|
99
|
+
srv = build(service, version, credentials=self.credentials)
|
100
|
+
setattr(self, f"_{service}", srv)
|
101
|
+
return srv
|
102
|
+
|
103
|
+
def get_drive_client(self):
|
104
|
+
"""Shortcut for accessing the Google Drive client."""
|
105
|
+
return self.get_service("drive", "v3")
|
106
|
+
|
107
|
+
def get_sheets_client(self):
|
108
|
+
"""Shortcut for accessing the Google Sheets client."""
|
109
|
+
return self.get_service("sheets", "v4")
|
110
|
+
|
111
|
+
def close(self):
|
112
|
+
"""Clears cached credentials and services."""
|
113
|
+
self.credentials = None
|
114
|
+
self._services_cache.clear()
|
115
|
+
|
116
|
+
def get_search(self, query: str, version: str = 'v1', cse_id: str = None, **kwargs):
|
117
|
+
"""
|
118
|
+
Get a cached Google API service instance or create one if not cached.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
|
122
|
+
query (str): The search query.
|
123
|
+
version (str): Version of the API (default: 'v1').
|
124
|
+
cse_id (str): The Custom Search Engine ID.
|
125
|
+
**kwargs: Additional arguments for the API request.
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
googleapiclient.discovery.Resource: The requested Google API service client.
|
129
|
+
"""
|
130
|
+
if not self.credentials:
|
131
|
+
self.connection()
|
132
|
+
srv = build("customsearch", version, credentials=self.credentials)
|
133
|
+
return srv.cse().list(q=query, cx=cse_id, **kwargs)
|
@@ -0,0 +1,127 @@
|
|
1
|
+
from abc import ABC
|
2
|
+
from typing import Union
|
3
|
+
from pathlib import Path, PurePath
|
4
|
+
import asyncio
|
5
|
+
import aiofiles
|
6
|
+
import pandas as pd
|
7
|
+
from googleapiclient.errors import HttpError
|
8
|
+
from googleapiclient.http import MediaIoBaseDownload
|
9
|
+
from .GoogleClient import GoogleClient
|
10
|
+
from ..exceptions import ComponentError
|
11
|
+
|
12
|
+
|
13
|
+
class GoogleDriveClient(GoogleClient, ABC):
|
14
|
+
"""
|
15
|
+
Google Drive Client for downloading files from Google Drive.
|
16
|
+
"""
|
17
|
+
|
18
|
+
async def download_file(
|
19
|
+
self,
|
20
|
+
source_filename: str,
|
21
|
+
destination_dir: Union[str, Path] = "."
|
22
|
+
) -> PurePath:
|
23
|
+
"""
|
24
|
+
Download a file from Google Drive by its name.
|
25
|
+
|
26
|
+
Args:
|
27
|
+
source_filename (str): The name of the file to download.
|
28
|
+
destination_dir (str or Path): Directory where the file will be saved (default is current directory).
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
str: Path to the downloaded file.
|
32
|
+
"""
|
33
|
+
try:
|
34
|
+
drive_service = await asyncio.to_thread(self.get_drive_client)
|
35
|
+
|
36
|
+
# Search for the file by name
|
37
|
+
results = await asyncio.to_thread(
|
38
|
+
drive_service.files().list,
|
39
|
+
q=f"name='{source_filename}'",
|
40
|
+
fields="files(id, name)",
|
41
|
+
pageSize=1
|
42
|
+
)
|
43
|
+
files = results.execute().get('files', [])
|
44
|
+
if not files:
|
45
|
+
raise ComponentError(f"File '{source_filename}' not found on Google Drive.")
|
46
|
+
|
47
|
+
file_id = files[0]['id']
|
48
|
+
request = drive_service.files().get_media(fileId=file_id)
|
49
|
+
file_path = Path(destination_dir) / source_filename
|
50
|
+
|
51
|
+
# Asynchronously open the file with aiofiles
|
52
|
+
async with aiofiles.open(file_path, 'wb') as f:
|
53
|
+
downloader = MediaIoBaseDownload(f, request)
|
54
|
+
done = False
|
55
|
+
while not done:
|
56
|
+
# Run the next_chunk call in a separate thread
|
57
|
+
status, done = await asyncio.to_thread(downloader.next_chunk)
|
58
|
+
print(f"Download {int(status.progress() * 100)}%.")
|
59
|
+
|
60
|
+
return file_path
|
61
|
+
|
62
|
+
except HttpError as error:
|
63
|
+
raise ComponentError(f"Error downloading file from Google Drive: {error}")
|
64
|
+
|
65
|
+
async def download_folder(
|
66
|
+
self,
|
67
|
+
folder_name: str,
|
68
|
+
destination_dir: Union[str, Path] = "."
|
69
|
+
) -> None:
|
70
|
+
"""
|
71
|
+
Download all files within a specified Google Drive folder by name.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
folder_name (str): The name of the folder to download.
|
75
|
+
destination_dir (str or Path): Directory where the files will be saved.
|
76
|
+
|
77
|
+
Returns:
|
78
|
+
None
|
79
|
+
"""
|
80
|
+
try:
|
81
|
+
drive_service = await asyncio.to_thread(self.get_drive_client)
|
82
|
+
|
83
|
+
# Search for the folder by name to get the folder ID
|
84
|
+
folder_results = await asyncio.to_thread(
|
85
|
+
drive_service.files().list,
|
86
|
+
q=f"name='{folder_name}' and mimeType='application/vnd.google-apps.folder'",
|
87
|
+
fields="files(id, name)",
|
88
|
+
pageSize=1
|
89
|
+
)
|
90
|
+
folders = folder_results.execute().get('files', [])
|
91
|
+
if not folders:
|
92
|
+
raise ComponentError(f"Folder '{folder_name}' not found on Google Drive.")
|
93
|
+
|
94
|
+
folder_id = folders[0]['id']
|
95
|
+
destination_dir = Path(destination_dir) / folder_name
|
96
|
+
destination_dir.mkdir(parents=True, exist_ok=True)
|
97
|
+
|
98
|
+
# List all files in the folder
|
99
|
+
file_results = await asyncio.to_thread(
|
100
|
+
drive_service.files().list,
|
101
|
+
q=f"'{folder_id}' in parents and mimeType != 'application/vnd.google-apps.folder'",
|
102
|
+
fields="files(id, name)"
|
103
|
+
)
|
104
|
+
files = file_results.execute().get('files', [])
|
105
|
+
|
106
|
+
if not files:
|
107
|
+
print(f"No files found in folder '{folder_name}'.")
|
108
|
+
return
|
109
|
+
|
110
|
+
# Download each file in the folder
|
111
|
+
for file_info in files:
|
112
|
+
file_id = file_info['id']
|
113
|
+
file_name = file_info['name']
|
114
|
+
file_path = destination_dir / file_name
|
115
|
+
|
116
|
+
request = drive_service.files().get_media(fileId=file_id)
|
117
|
+
async with aiofiles.open(file_path, 'wb') as f:
|
118
|
+
downloader = MediaIoBaseDownload(f, request)
|
119
|
+
done = False
|
120
|
+
while not done:
|
121
|
+
status, done = await asyncio.to_thread(downloader.next_chunk)
|
122
|
+
print(f"Downloading '{file_name}' - {int(status.progress() * 100)}% complete.")
|
123
|
+
|
124
|
+
print(f"Downloaded '{file_name}' to '{file_path}'.")
|
125
|
+
|
126
|
+
except HttpError as error:
|
127
|
+
raise ComponentError(f"Error downloading folder '{folder_name}' from Google Drive: {error}")
|
@@ -0,0 +1,89 @@
|
|
1
|
+
from abc import ABC
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Union
|
4
|
+
import asyncio
|
5
|
+
from google.cloud import storage
|
6
|
+
from google.auth.exceptions import GoogleAuthError
|
7
|
+
from .GoogleClient import GoogleClient
|
8
|
+
from ..exceptions import ComponentError
|
9
|
+
|
10
|
+
|
11
|
+
class GoogleCloudStorageClient(GoogleClient, ABC):
|
12
|
+
"""
|
13
|
+
Google Cloud Storage Client for interacting with Google Cloud Storage (GCS).
|
14
|
+
Provides methods for file and folder operations.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def __init__(self, *args, bucket_name: str, **kwargs):
|
18
|
+
super().__init__(*args, **kwargs)
|
19
|
+
self.bucket_name = bucket_name
|
20
|
+
self._bucket = None
|
21
|
+
|
22
|
+
async def get_bucket(self):
|
23
|
+
"""Get the GCS bucket, with caching."""
|
24
|
+
if not self._bucket:
|
25
|
+
try:
|
26
|
+
client = await asyncio.to_thread(storage.Client, credentials=self.credentials)
|
27
|
+
self._bucket = client.bucket(self.bucket_name)
|
28
|
+
except GoogleAuthError as e:
|
29
|
+
raise ComponentError(f"Google GCS authentication error: {e}")
|
30
|
+
return self._bucket
|
31
|
+
|
32
|
+
async def create_folder(self, folder_name: str):
|
33
|
+
"""Create a folder in GCS by creating an empty blob with a trailing '/'."""
|
34
|
+
bucket = await self.get_bucket()
|
35
|
+
blob = bucket.blob(f"{folder_name}/")
|
36
|
+
await asyncio.to_thread(blob.upload_from_string, "")
|
37
|
+
print(f"Folder '{folder_name}' created in GCS.")
|
38
|
+
|
39
|
+
async def upload_file(self, source_path: Union[str, Path], destination_path: str):
|
40
|
+
"""Upload a file to GCS."""
|
41
|
+
bucket = await self.get_bucket()
|
42
|
+
blob = bucket.blob(destination_path)
|
43
|
+
await asyncio.to_thread(blob.upload_from_filename, str(source_path))
|
44
|
+
print(f"File '{source_path}' uploaded to '{destination_path}' in GCS.")
|
45
|
+
|
46
|
+
async def download_file(self, source_path: str, destination_dir: Union[str, Path]):
|
47
|
+
"""Download a file from GCS."""
|
48
|
+
bucket = await self.get_bucket()
|
49
|
+
blob = bucket.blob(source_path)
|
50
|
+
destination_file = Path(destination_dir) / Path(source_path).name
|
51
|
+
await asyncio.to_thread(blob.download_to_filename, str(destination_file))
|
52
|
+
print(f"File '{source_path}' downloaded to '{destination_file}'.")
|
53
|
+
|
54
|
+
async def delete_file(self, file_path: str):
|
55
|
+
"""Delete a file from GCS."""
|
56
|
+
bucket = await self.get_bucket()
|
57
|
+
blob = bucket.blob(file_path)
|
58
|
+
await asyncio.to_thread(blob.delete)
|
59
|
+
print(f"File '{file_path}' deleted from GCS.")
|
60
|
+
|
61
|
+
async def upload_folder(self, source_folder: Union[str, Path], destination_folder: str):
|
62
|
+
"""Upload all files from a local folder to a GCS folder."""
|
63
|
+
source_folder = Path(source_folder)
|
64
|
+
tasks = []
|
65
|
+
for file_path in source_folder.glob("**/*"):
|
66
|
+
if file_path.is_file():
|
67
|
+
relative_path = file_path.relative_to(source_folder)
|
68
|
+
destination_path = f"{destination_folder}/{relative_path}"
|
69
|
+
tasks.append(self.upload_file(file_path, destination_path))
|
70
|
+
await asyncio.gather(*tasks)
|
71
|
+
print(f"Folder '{source_folder}' uploaded to '{destination_folder}' in GCS.")
|
72
|
+
|
73
|
+
async def download_folder(self, source_folder: str, destination_folder: Union[str, Path]):
|
74
|
+
"""Download all files from a GCS folder to a local folder."""
|
75
|
+
bucket = await self.get_bucket()
|
76
|
+
blobs = bucket.list_blobs(prefix=f"{source_folder}/")
|
77
|
+
destination_folder = Path(destination_folder) / source_folder
|
78
|
+
destination_folder.mkdir(parents=True, exist_ok=True)
|
79
|
+
|
80
|
+
tasks = []
|
81
|
+
async for blob in blobs:
|
82
|
+
if not blob.name.endswith("/"): # Skip "directory" entries
|
83
|
+
relative_path = Path(blob.name).relative_to(source_folder)
|
84
|
+
local_file_path = destination_folder / relative_path
|
85
|
+
local_file_path.parent.mkdir(parents=True, exist_ok=True)
|
86
|
+
tasks.append(asyncio.to_thread(blob.download_to_filename, str(local_file_path)))
|
87
|
+
await asyncio.gather(*tasks)
|
88
|
+
print(f"Folder '{source_folder}' downloaded to '{destination_folder}'.")
|
89
|
+
|