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,200 @@
|
|
1
|
+
from typing import Union
|
2
|
+
from collections.abc import Callable
|
3
|
+
from pathlib import PurePath
|
4
|
+
import asyncio
|
5
|
+
import aiofiles
|
6
|
+
from navconfig.logging import logging
|
7
|
+
from ..interfaces import TemplateSupport
|
8
|
+
from ..interfaces.databases.documentdb import DocumentDBSupport
|
9
|
+
from .flow import FlowComponent
|
10
|
+
from ..utils.functions import is_empty
|
11
|
+
from ..exceptions import FileError, ComponentError, DataNotFound
|
12
|
+
from ..utils.json import json_decoder, json_encoder
|
13
|
+
|
14
|
+
|
15
|
+
# Disable Mongo Logging
|
16
|
+
logging.getLogger("pymongo").setLevel(logging.INFO)
|
17
|
+
|
18
|
+
|
19
|
+
class DocumentDBQuery(DocumentDBSupport, TemplateSupport, FlowComponent):
|
20
|
+
"""DocumentDBQuery.
|
21
|
+
|
22
|
+
DocumentDBQuery is a component that interacts with a DocumentDB database using pymongo.
|
23
|
+
|
24
|
+
Returns json-object representation of the query result.
|
25
|
+
|
26
|
+
|
27
|
+
Example:
|
28
|
+
|
29
|
+
```yaml
|
30
|
+
DocumentDBQuery:
|
31
|
+
schema: networkninja
|
32
|
+
tablename: batches
|
33
|
+
query:
|
34
|
+
data.metadata.type: form_data
|
35
|
+
data.metadata.timestamp:
|
36
|
+
$gte: 1743670800.0
|
37
|
+
$lte: 1743681600.999999
|
38
|
+
```
|
39
|
+
|
40
|
+
"""
|
41
|
+
driver: str = 'mongo'
|
42
|
+
|
43
|
+
def __init__(
|
44
|
+
self,
|
45
|
+
loop: asyncio.AbstractEventLoop = None,
|
46
|
+
job: Callable = None,
|
47
|
+
stat: Callable = None,
|
48
|
+
**kwargs,
|
49
|
+
):
|
50
|
+
self._query: dict = {}
|
51
|
+
# Collection:
|
52
|
+
self._collection: str = kwargs.pop("collection_name", None)
|
53
|
+
if not self._collection:
|
54
|
+
self._collection = kwargs.pop("tablename", None)
|
55
|
+
if not self._collection:
|
56
|
+
raise ValueError(
|
57
|
+
f"{__name__}: Missing Collection Name or Table Name"
|
58
|
+
)
|
59
|
+
self._database: str = kwargs.pop("database", None)
|
60
|
+
if not self._database:
|
61
|
+
self._database = kwargs.pop("schema", kwargs.get('program', None))
|
62
|
+
if not self._database:
|
63
|
+
raise ValueError(
|
64
|
+
f"{__name__}: Missing Database Name or Schema Name"
|
65
|
+
)
|
66
|
+
self.query: Union[str, dict] = kwargs.get('query', None)
|
67
|
+
super().__init__(
|
68
|
+
loop=loop,
|
69
|
+
job=job,
|
70
|
+
stat=stat,
|
71
|
+
**kwargs
|
72
|
+
)
|
73
|
+
if self.as_string is True:
|
74
|
+
self.infer_types = True
|
75
|
+
|
76
|
+
async def open_queryfile(self, file: PurePath, **kwargs) -> str:
|
77
|
+
if file.exists() and file.is_file():
|
78
|
+
content = None
|
79
|
+
# open SQL File:
|
80
|
+
async with aiofiles.open(file, "r+") as afp:
|
81
|
+
content = await afp.read()
|
82
|
+
# check if we need to replace masks
|
83
|
+
if hasattr(self, "masks"):
|
84
|
+
content = self.mask_replacement(content)
|
85
|
+
if self.use_template is True:
|
86
|
+
content = self._templateparser.from_string(content, kwargs)
|
87
|
+
return content
|
88
|
+
else:
|
89
|
+
raise FileError(
|
90
|
+
f"{__name__}: Missing Query File: {file}"
|
91
|
+
)
|
92
|
+
|
93
|
+
async def start(self, **kwargs) -> bool:
|
94
|
+
"""start.
|
95
|
+
|
96
|
+
Start the DocumentDBQuery component.
|
97
|
+
"""
|
98
|
+
await super(DocumentDBQuery, self).start(**kwargs)
|
99
|
+
if hasattr(self, "conditions"):
|
100
|
+
self.set_conditions("conditions")
|
101
|
+
if hasattr(self, "query_file") or hasattr(self, "query_file"):
|
102
|
+
query = self.query_file
|
103
|
+
qry = await self.open_queryfile(query)
|
104
|
+
elif isinstance(self.query, PurePath):
|
105
|
+
qry = await self.open_queryfile(self.query)
|
106
|
+
else:
|
107
|
+
if isinstance(self.query, str):
|
108
|
+
if hasattr(self, "masks"):
|
109
|
+
qry = self.mask_replacement(self.query)
|
110
|
+
if "{" in self.query and hasattr(self, "conditions"):
|
111
|
+
qry = self.query.format(**self.conditions)
|
112
|
+
else:
|
113
|
+
qry = self.query
|
114
|
+
elif isinstance(self.query, dict):
|
115
|
+
# Doing condition replacement for every element in the query
|
116
|
+
qry = {}
|
117
|
+
for key, value in self.query.items():
|
118
|
+
if isinstance(value, str):
|
119
|
+
if hasattr(self, "masks"):
|
120
|
+
value = self.mask_replacement(value)
|
121
|
+
if "{" in value and hasattr(self, "conditions"):
|
122
|
+
value = value.format(**self.conditions)
|
123
|
+
qry[key] = value
|
124
|
+
else:
|
125
|
+
raise ValueError(
|
126
|
+
f"{__name__}: Missing or Invalid Query or Query File"
|
127
|
+
)
|
128
|
+
try:
|
129
|
+
if isinstance(qry, str):
|
130
|
+
self._query = json_decoder(qry)
|
131
|
+
else:
|
132
|
+
self._query = qry
|
133
|
+
except Exception as err:
|
134
|
+
raise ComponentError(
|
135
|
+
f"{__name__}: Error decoding Query: {err}"
|
136
|
+
)
|
137
|
+
return True
|
138
|
+
|
139
|
+
async def close(self):
|
140
|
+
pass
|
141
|
+
|
142
|
+
async def run(self):
|
143
|
+
try:
|
144
|
+
db = self.default_connection(
|
145
|
+
driver=self.driver, credentials=self.credentials
|
146
|
+
)
|
147
|
+
except Exception as e:
|
148
|
+
self._logger.error(
|
149
|
+
f"Error getting Default DocumentDB Connection: {e!s}"
|
150
|
+
)
|
151
|
+
raise
|
152
|
+
try:
|
153
|
+
async with await db.connection() as conn:
|
154
|
+
if conn.is_connected() is not True:
|
155
|
+
raise ComponentError(
|
156
|
+
f"DB Error: driver {self.driver} is not connected."
|
157
|
+
)
|
158
|
+
await conn.use(self._database)
|
159
|
+
# print(
|
160
|
+
# ':: Count > ',
|
161
|
+
# await conn.count_documents(collection_name=self._collection)
|
162
|
+
# )
|
163
|
+
result, error = await conn.query(
|
164
|
+
collection_name=self._collection,
|
165
|
+
query=self._query,
|
166
|
+
)
|
167
|
+
if error:
|
168
|
+
raise ComponentError(
|
169
|
+
f"Error executing Query: {error}"
|
170
|
+
)
|
171
|
+
if is_empty(result):
|
172
|
+
raise DataNotFound(
|
173
|
+
f"Data not found for Query: {self._query}"
|
174
|
+
)
|
175
|
+
|
176
|
+
if self.as_dataframe is True:
|
177
|
+
self._result = await self.get_dataframe(result)
|
178
|
+
else:
|
179
|
+
self._result = result
|
180
|
+
# self.add_metric(
|
181
|
+
# "Query", json_encoder(self._query)
|
182
|
+
# )
|
183
|
+
self.add_metric(
|
184
|
+
"Database", str(self._database)
|
185
|
+
)
|
186
|
+
self.add_metric(
|
187
|
+
"Collection", str(self._collection)
|
188
|
+
)
|
189
|
+
self.add_metric(
|
190
|
+
"NUM_RESULTS", len(self._result)
|
191
|
+
)
|
192
|
+
return self._result
|
193
|
+
except DataNotFound:
|
194
|
+
raise
|
195
|
+
except ComponentError:
|
196
|
+
raise
|
197
|
+
except Exception as e:
|
198
|
+
raise ComponentError(
|
199
|
+
f"Error connecting to DocumentDB: {e}"
|
200
|
+
)
|
@@ -0,0 +1,371 @@
|
|
1
|
+
import asyncio
|
2
|
+
import random
|
3
|
+
import ssl
|
4
|
+
from abc import abstractmethod
|
5
|
+
from functools import partial
|
6
|
+
from typing import Dict, List
|
7
|
+
from collections.abc import Callable
|
8
|
+
from pathlib import Path, PurePath, PosixPath
|
9
|
+
from tqdm import tqdm
|
10
|
+
import aiohttp
|
11
|
+
from ..exceptions import ComponentError, FileNotFound
|
12
|
+
from ..utils.encoders import DefaultEncoder
|
13
|
+
from ..utils import SafeDict
|
14
|
+
from .flow import FlowComponent
|
15
|
+
from ..interfaces.dataframes import PandasDataframe
|
16
|
+
from ..interfaces.http import ua
|
17
|
+
|
18
|
+
|
19
|
+
class DownloadFromBase(PandasDataframe, FlowComponent):
|
20
|
+
"""
|
21
|
+
DownloadFromBase
|
22
|
+
|
23
|
+
Abstract base class for downloading files from various sources.
|
24
|
+
|
25
|
+
Inherits from `FlowComponent` and `PandasDataframe` to provide common functionalities
|
26
|
+
for component management and data handling.
|
27
|
+
|
28
|
+
This class utilizes `aiohttp` for asynchronous HTTP requests and offers support
|
29
|
+
for authentication, SSL connections, and basic file management.
|
30
|
+
|
31
|
+
.. note::
|
32
|
+
This class is intended to be subclassed for specific download implementations.
|
33
|
+
|
34
|
+
.. table:: Properties
|
35
|
+
:widths: auto
|
36
|
+
|
37
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
38
|
+
| Name | Required | Summary |
|
39
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
40
|
+
| credentials | Yes | Dictionary containing expected username and password for authentication |
|
41
|
+
| | | (default: {"username": str, "password": str}). |
|
42
|
+
+----------------------+----------+-------------------------------------------------------------------------------------+
|
43
|
+
| no_host | No | Boolean flag indicating whether to skip defining host and port (default: False). |
|
44
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
45
|
+
| overwrite | No | Boolean flag indicating whether to overwrite existing files (default: True). |
|
46
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
47
|
+
| overwrite | No | Boolean flag indicating whether to overwrite existing files (default: True). |
|
48
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
49
|
+
| create_destination | No | Boolean flag indicating whether to create the destination directory |
|
50
|
+
| | | if it doesn't exist (default: True). |
|
51
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
52
|
+
| rename | No | String defining a new filename for the downloaded file. |
|
53
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
54
|
+
| file | Yes | Access the file download through a url, with the required user credentials and |
|
55
|
+
| | | password |
|
56
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
57
|
+
| download | Yes | File destination and directory |
|
58
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
59
|
+
| source | Yes | Origin of the file to download and location where the file is located. |
|
60
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
61
|
+
| destination | Yes | Destination where the file will be save. |
|
62
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
63
|
+
| ssl | No | Boolean flag indicating whether to use SSL connection (default: False). |
|
64
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
65
|
+
| ssl_cafile | No | Path to the CA certificate file for SSL verification. |
|
66
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
67
|
+
| ssl_certs | No | List of certificate chains for SSL verification. |
|
68
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
69
|
+
| host | No | Hostname for the download source (default: "localhost"). |
|
70
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
71
|
+
| port | No | Port number for the download source (default: 22). |
|
72
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
73
|
+
| timeout | No | Timeout value in seconds for HTTP requests (default: 30). |
|
74
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
75
|
+
| url | No | URL of the download source (populated within the class). |
|
76
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
77
|
+
| headers | Yes | Dictionary containing HTTP headers for the request. |
|
78
|
+
+----------------------+----------+-----------+-------------------------------------------------------------------------+
|
79
|
+
""" # noqa
|
80
|
+
_credentials: dict = {"username": str, "password": str}
|
81
|
+
|
82
|
+
def __init__(
|
83
|
+
self,
|
84
|
+
loop: asyncio.AbstractEventLoop = None,
|
85
|
+
job: Callable = None,
|
86
|
+
stat: Callable = None,
|
87
|
+
**kwargs,
|
88
|
+
):
|
89
|
+
self.no_host: bool = kwargs.pop("no_host", False)
|
90
|
+
self.accept: str = "text/plain"
|
91
|
+
self.overwrite: bool = True
|
92
|
+
self.create_destination: bool = kwargs.pop("create_destination", False)
|
93
|
+
self.rename: str = None
|
94
|
+
# source:
|
95
|
+
self.source_file: str = None
|
96
|
+
self.source_dir: str = None
|
97
|
+
# destination:
|
98
|
+
self.filename: str = None
|
99
|
+
self._srcfiles: List = []
|
100
|
+
self._filenames: Dict = {}
|
101
|
+
self._connection: Callable = None
|
102
|
+
self.ssl: bool = False
|
103
|
+
self.ssl_cafile: str = None
|
104
|
+
self.ssl_certs: list = []
|
105
|
+
self.timeout: int = kwargs.pop("timeout", 30)
|
106
|
+
super().__init__(
|
107
|
+
loop=loop, job=job, stat=stat, **kwargs
|
108
|
+
)
|
109
|
+
if not hasattr(self, "url"):
|
110
|
+
self.url: str = None
|
111
|
+
if not hasattr(self, "headers"):
|
112
|
+
self.headers = {}
|
113
|
+
self._encoder = DefaultEncoder()
|
114
|
+
self._valid_response_status: tuple = (200, 201, 202)
|
115
|
+
# SSL Context:
|
116
|
+
if self.ssl:
|
117
|
+
# TODO: add CAFile and cert-chain
|
118
|
+
self.ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS, cafile=self.ssl_cafile)
|
119
|
+
self.ssl_ctx.minimum_version = ssl.TLSVersion.TLSv1_2
|
120
|
+
self.ssl_ctx.options &= ~ssl.OP_NO_SSLv3
|
121
|
+
self.ssl_ctx.verify_mode = ssl.CERT_NONE
|
122
|
+
if self.ssl_certs:
|
123
|
+
self.ssl_ctx.load_cert_chain(*self.ssl_certs)
|
124
|
+
else:
|
125
|
+
self.ssl_ctx = None
|
126
|
+
|
127
|
+
def build_headers(self):
|
128
|
+
self.headers = {
|
129
|
+
"Accept": self.accept,
|
130
|
+
"Accept-Encoding": "gzip, deflate",
|
131
|
+
"DNT": "1",
|
132
|
+
"Connection": "keep-alive",
|
133
|
+
"Upgrade-Insecure-Requests": "1",
|
134
|
+
"User-Agent": random.choice(ua),
|
135
|
+
**self.headers,
|
136
|
+
}
|
137
|
+
|
138
|
+
async def start(self, **kwargs):
|
139
|
+
"""Start.
|
140
|
+
|
141
|
+
Processing variables and credentials.
|
142
|
+
"""
|
143
|
+
try:
|
144
|
+
if getattr(self, 'define_host', None) is not None:
|
145
|
+
self.define_host()
|
146
|
+
self.processing_credentials()
|
147
|
+
except Exception as err:
|
148
|
+
self._logger.error(err)
|
149
|
+
raise
|
150
|
+
if hasattr(self, "directory"):
|
151
|
+
self.directory = Path(self.directory)
|
152
|
+
try:
|
153
|
+
if hasattr(self, "masks"):
|
154
|
+
p = self.mask_replacement(self.directory)
|
155
|
+
else:
|
156
|
+
p = self.directory
|
157
|
+
if not p.exists():
|
158
|
+
if self.create_destination is True:
|
159
|
+
try:
|
160
|
+
PosixPath(p).mkdir(parents=True, exist_ok=True)
|
161
|
+
except (Exception, OSError) as err:
|
162
|
+
raise ComponentError(
|
163
|
+
f"Error creating directory {self.directory}: {err}"
|
164
|
+
) from err
|
165
|
+
else:
|
166
|
+
self._logger.error(
|
167
|
+
f"DownloadFrom: Path doesn't exists: {p}"
|
168
|
+
)
|
169
|
+
raise FileNotFound(
|
170
|
+
f"DownloadFrom: Path doesn't exists: {p}"
|
171
|
+
)
|
172
|
+
except Exception as err:
|
173
|
+
self._logger.error(err)
|
174
|
+
raise ComponentError(f"{err!s}") from err
|
175
|
+
self._logger.debug(f"Destination Directory: {self.directory}")
|
176
|
+
if hasattr(self, "file"):
|
177
|
+
if isinstance(self.file, list):
|
178
|
+
# is a list of files:
|
179
|
+
for file in self.file:
|
180
|
+
filename = file
|
181
|
+
if hasattr(self, "masks"):
|
182
|
+
filename = self.mask_replacement(file)
|
183
|
+
self._logger.debug(f"Filename > {filename}")
|
184
|
+
self._srcfiles.append(filename)
|
185
|
+
else:
|
186
|
+
try:
|
187
|
+
filename = self.process_pattern("file")
|
188
|
+
directory = self.file.get('directory', None)
|
189
|
+
if hasattr(self, "masks"):
|
190
|
+
if isinstance(filename, dict):
|
191
|
+
for key, value in filename.items():
|
192
|
+
filename[key] = self.mask_replacement(value)
|
193
|
+
elif isinstance(filename, str):
|
194
|
+
filename = self.mask_replacement(filename)
|
195
|
+
# path for file
|
196
|
+
self._logger.debug(
|
197
|
+
f"Filename > {filename}"
|
198
|
+
)
|
199
|
+
self.filename = filename
|
200
|
+
# for some exception, directory is on file:
|
201
|
+
if directory:
|
202
|
+
self.source_dir = directory
|
203
|
+
if hasattr(self, "masks"):
|
204
|
+
self.source_dir = self.mask_replacement(directory)
|
205
|
+
self._srcfiles.append(filename)
|
206
|
+
except Exception as err:
|
207
|
+
raise ComponentError(f"{err!s}") from err
|
208
|
+
elif hasattr(self, "source"): # using the destination filosophy
|
209
|
+
source_dir = self.source.get('directory', '/')
|
210
|
+
if hasattr(self, "masks"):
|
211
|
+
self.source_dir = self.mask_replacement(source_dir)
|
212
|
+
else:
|
213
|
+
self.source_dir = source_dir
|
214
|
+
# filename:
|
215
|
+
if "file" in self.source:
|
216
|
+
self.source_file = self.process_pattern(
|
217
|
+
"file",
|
218
|
+
parent=self.source
|
219
|
+
)
|
220
|
+
self._srcfiles.append(self.source_file)
|
221
|
+
else:
|
222
|
+
filenames = self.source.get('filename')
|
223
|
+
try:
|
224
|
+
if isinstance(filenames, list):
|
225
|
+
for file in filenames:
|
226
|
+
filename = self.mask_replacement(file)
|
227
|
+
self._srcfiles.append(
|
228
|
+
{
|
229
|
+
"filename": file,
|
230
|
+
"directory": self.source_dir
|
231
|
+
}
|
232
|
+
)
|
233
|
+
elif isinstance(filenames, dict):
|
234
|
+
self._srcfiles.append(filenames)
|
235
|
+
else:
|
236
|
+
self.source_file = {
|
237
|
+
"filename": self.mask_replacement(
|
238
|
+
filenames
|
239
|
+
),
|
240
|
+
"directory": self.source_dir
|
241
|
+
}
|
242
|
+
self._srcfiles.append(self.source_file)
|
243
|
+
except KeyError:
|
244
|
+
self.source_file = None
|
245
|
+
if hasattr(self, "destination"):
|
246
|
+
if "filename" in self.destination:
|
247
|
+
# Rename the file to be downloaded
|
248
|
+
self.filename = self.destination.get('filename', None)
|
249
|
+
elif isinstance(self.filename, dict) and 'filename' in self.filename:
|
250
|
+
self.filename = self.filename["filename"]
|
251
|
+
else:
|
252
|
+
# Preserving Filename from Source
|
253
|
+
self.filename = None
|
254
|
+
if self.filename:
|
255
|
+
self._logger.debug(
|
256
|
+
f"Raw Destination Filename: {self.filename}\n"
|
257
|
+
)
|
258
|
+
if hasattr(self, "masks") or "{" in self.filename:
|
259
|
+
self.filename = self.mask_replacement(self.filename)
|
260
|
+
try:
|
261
|
+
_dir = self.destination.get('directory')
|
262
|
+
_direc = _dir.format_map(SafeDict(**self._variables))
|
263
|
+
_dir = self.mask_replacement(_direc)
|
264
|
+
self.directory = Path(_dir)
|
265
|
+
except KeyError:
|
266
|
+
# Maybe Filename contains directory?
|
267
|
+
if self.destination["filename"]:
|
268
|
+
self.directory = Path(self.destination["filename"]).parent
|
269
|
+
self.filename = Path(self.destination["filename"]).name
|
270
|
+
try:
|
271
|
+
if self.create_destination is True:
|
272
|
+
self.directory.mkdir(parents=True, exist_ok=True)
|
273
|
+
except OSError as err:
|
274
|
+
raise ComponentError(
|
275
|
+
f"DownloadFrom: Error creating directory {self.directory}: {err}"
|
276
|
+
) from err
|
277
|
+
except Exception as err:
|
278
|
+
self._logger.error(f"Error creating directory {self.directory}: {err}")
|
279
|
+
raise ComponentError(
|
280
|
+
f"Error creating directory {self.directory}: {err}"
|
281
|
+
) from err
|
282
|
+
if "filename" in self.destination:
|
283
|
+
if not isinstance(self.filename, PurePath):
|
284
|
+
self.filename = self.directory.joinpath(self.filename)
|
285
|
+
self._logger.debug(
|
286
|
+
f":: Destination File: {self.filename}"
|
287
|
+
)
|
288
|
+
await super(DownloadFromBase, self).start(**kwargs)
|
289
|
+
if self.url:
|
290
|
+
self.build_headers()
|
291
|
+
return True
|
292
|
+
|
293
|
+
async def http_response(self, response):
|
294
|
+
return response
|
295
|
+
|
296
|
+
async def http_session(
|
297
|
+
self,
|
298
|
+
url: str = None,
|
299
|
+
method: str = "get",
|
300
|
+
data: dict = None,
|
301
|
+
data_format: str = "json",
|
302
|
+
):
|
303
|
+
"""
|
304
|
+
session.
|
305
|
+
connect to an http source using aiohttp
|
306
|
+
"""
|
307
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
308
|
+
if url is not None:
|
309
|
+
self.url = url
|
310
|
+
# TODO: Auth, Data, etc
|
311
|
+
auth = {}
|
312
|
+
params = {}
|
313
|
+
_data = {"data": None}
|
314
|
+
if self.credentials:
|
315
|
+
if "username" in self.credentials: # basic Authentication
|
316
|
+
auth = aiohttp.BasicAuth(
|
317
|
+
self.credentials["username"], self.credentials["password"]
|
318
|
+
)
|
319
|
+
params = {"auth": auth}
|
320
|
+
elif "token" in self.credentials:
|
321
|
+
self.headers["Authorization"] = "{scheme} {token}".format(
|
322
|
+
scheme=self.credentials["scheme"], token=self.credentials["token"]
|
323
|
+
)
|
324
|
+
if data_format == "json":
|
325
|
+
params["json_serialize"] = self._encoder.dumps
|
326
|
+
_data["json"] = data
|
327
|
+
else:
|
328
|
+
_data["data"] = data
|
329
|
+
async with aiohttp.ClientSession(**params) as session:
|
330
|
+
meth = getattr(session, method)
|
331
|
+
if self.ssl:
|
332
|
+
ssl = {"ssl": self.ssl_ctx, "verify_ssl": True}
|
333
|
+
else:
|
334
|
+
ssl = {}
|
335
|
+
fn = partial(
|
336
|
+
meth,
|
337
|
+
self.url,
|
338
|
+
headers=self.headers,
|
339
|
+
timeout=timeout,
|
340
|
+
allow_redirects=True,
|
341
|
+
**ssl,
|
342
|
+
**_data,
|
343
|
+
)
|
344
|
+
try:
|
345
|
+
async with fn() as response:
|
346
|
+
if response.status in self._valid_response_status:
|
347
|
+
return await self.http_response(response)
|
348
|
+
else:
|
349
|
+
print("ERROR RESPONSE >> ", response)
|
350
|
+
raise ComponentError(
|
351
|
+
f"DownloadFrom: Error getting data from URL {response}"
|
352
|
+
)
|
353
|
+
except Exception as err:
|
354
|
+
raise ComponentError(
|
355
|
+
f"DownloadFrom: Error Making an SSL Connection to ({self.url}): {err}"
|
356
|
+
) from err
|
357
|
+
except aiohttp.exceptions.HTTPError as err:
|
358
|
+
raise ComponentError(
|
359
|
+
f"DownloadFrom: SSL Certificate Error: {err}"
|
360
|
+
) from err
|
361
|
+
|
362
|
+
@abstractmethod
|
363
|
+
async def close(self):
|
364
|
+
pass
|
365
|
+
|
366
|
+
@abstractmethod
|
367
|
+
async def run(self):
|
368
|
+
pass
|
369
|
+
|
370
|
+
def start_pbar(self, total: int = 1):
|
371
|
+
return tqdm(total=total)
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import asyncio
|
2
|
+
import pandas as pd
|
3
|
+
from collections.abc import Callable
|
4
|
+
from ..utils import is_empty
|
5
|
+
from ..exceptions import ComponentError, DataNotFound
|
6
|
+
from .DownloadFrom import DownloadFromBase
|
7
|
+
from ..interfaces.d2l import D2LClient
|
8
|
+
|
9
|
+
class DownloadFromD2L(D2LClient, DownloadFromBase):
|
10
|
+
"""
|
11
|
+
DownloadFromD2L
|
12
|
+
|
13
|
+
Overview
|
14
|
+
|
15
|
+
Download Data from D2L.
|
16
|
+
|
17
|
+
Properties (inherited from D2LClient and DownloadFromBase)
|
18
|
+
|
19
|
+
.. table:: Properties
|
20
|
+
:widths: auto
|
21
|
+
|
22
|
+
+--------------------+----------+-----------+----------------------------------------------------------------------+
|
23
|
+
| Name | Required | Summary |
|
24
|
+
+--------------------+----------+-----------+----------------------------------------------------------------------+
|
25
|
+
| credentials | Yes | Credentials to establish connection with Polestar site (user and password) |
|
26
|
+
| | | get credentials from environment if null. |
|
27
|
+
+--------------------+----------+-----------+----------------------------------------------------------------------+
|
28
|
+
| filename | Yes | The filename to use for the downloaded file. |
|
29
|
+
+--------------------+----------+-----------+----------------------------------------------------------------------+
|
30
|
+
| Action | No | Select 'download' or 'awards'. (Default: download) |
|
31
|
+
+--------------------+----------+-----------+----------------------------------------------------------------------+
|
32
|
+
| schema | No | The ID of the Schema to download. Required if action is 'download' |
|
33
|
+
+--------------------+----------+-----------+----------------------------------------------------------------------+
|
34
|
+
| org_units | No | A list of ID of Organization Units. Required if action is 'awards' |
|
35
|
+
+--------------------+----------+-----------+----------------------------------------------------------------------+
|
36
|
+
| column | No | A column name to extract the list of Organization Units. Required if action is |
|
37
|
+
| | | 'awards' and depends of a Pandas DataFrame. |
|
38
|
+
+--------------------+----------+-----------+----------------------------------------------------------------------+
|
39
|
+
| create_destination | No | Boolean flag indicating whether to create the destination directory if it |
|
40
|
+
| | | doesn't exist (default: True). |
|
41
|
+
+--------------------+----------+-----------+----------------------------------------------------------------------+
|
42
|
+
|
43
|
+
Save the downloaded files on the new destination.
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
Example:
|
48
|
+
|
49
|
+
```yaml
|
50
|
+
DownloadFromD2L:
|
51
|
+
domain: POLESTAR_DOMAIN
|
52
|
+
schema: c0b0740f-896e-4afa-bfd9-81d8e43006d9
|
53
|
+
credentials:
|
54
|
+
username: POLESTAR_USERNAME
|
55
|
+
password: POLESTAR_PASSWORD
|
56
|
+
destination:
|
57
|
+
directory: /home/ubuntu/symbits/polestar/files/organizational_unit_ancestors/
|
58
|
+
filename: organizational_unit_ancestors_{yesterday}.zip
|
59
|
+
overwrite: true
|
60
|
+
masks:
|
61
|
+
yesterday:
|
62
|
+
- yesterday
|
63
|
+
- mask: '%Y-%m-%d'
|
64
|
+
```
|
65
|
+
|
66
|
+
""" # noqa
|
67
|
+
def __init__(
|
68
|
+
self,
|
69
|
+
loop: asyncio.AbstractEventLoop = None,
|
70
|
+
job: Callable = None,
|
71
|
+
stat: Callable = None,
|
72
|
+
**kwargs,
|
73
|
+
):
|
74
|
+
super().__init__(loop=loop, job=job, stat=stat, **kwargs)
|
75
|
+
|
76
|
+
async def start(self, **kwargs):
|
77
|
+
self.action = kwargs.get('action', 'download')
|
78
|
+
if self.action == 'awards':
|
79
|
+
if hasattr(self, 'org_units'):
|
80
|
+
self.data = self.org_units
|
81
|
+
elif self.previous:
|
82
|
+
if not isinstance(self.input, pd.DataFrame):
|
83
|
+
raise ComponentError(
|
84
|
+
"DownloadFromD2L: Incompatible Pandas Dataframe", status=404
|
85
|
+
)
|
86
|
+
else:
|
87
|
+
if not hasattr(self, 'column'):
|
88
|
+
raise ComponentError(
|
89
|
+
'DownloadFromD2L: requires a "column" Attribute'
|
90
|
+
)
|
91
|
+
self.data = self.input[self.column].to_numpy()
|
92
|
+
await self.get_bearer_token()
|
93
|
+
await super(DownloadFromD2L, self).start(**kwargs)
|
94
|
+
return True
|
95
|
+
|
96
|
+
async def close(self):
|
97
|
+
pass
|
98
|
+
|
99
|
+
async def run(self):
|
100
|
+
if self.action == 'download':
|
101
|
+
await self.download_file()
|
102
|
+
self._result = self.filename
|
103
|
+
self.add_metric("D2L_FILE", self.filename)
|
104
|
+
return self._result
|
105
|
+
elif self.action == 'awards':
|
106
|
+
self._result = await self.create_dataframe(await self.awards(org_units=self.data))
|
107
|
+
if is_empty(self._result):
|
108
|
+
raise DataNotFound(f"{self.__name__}: Data Not Found")
|
109
|
+
return self._result
|
110
|
+
else:
|
111
|
+
raise ComponentError(
|
112
|
+
'DownloadFromD2L: Action not defined'
|
113
|
+
)
|