flowtask 5.8.4__cp39-cp39-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-39-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-39-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/argparser.py +235 -0
- flowtask/parsers/base.c +15155 -0
- flowtask/parsers/base.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/json.c +11968 -0
- flowtask/parsers/json.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/maps.py +49 -0
- flowtask/parsers/toml.c +11968 -0
- flowtask/parsers/toml.cpython-39-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-39-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-39-x86_64-linux-gnu.so +0 -0
- flowtask/utils/json.cpp +13349 -0
- flowtask/utils/json.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/utils/mail.py +63 -0
- flowtask/utils/parseqs.c +13324 -0
- flowtask/utils/parserqs.cpython-39-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,767 @@
|
|
1
|
+
"""
|
2
|
+
FileService.
|
3
|
+
|
4
|
+
Work with slug-based file definitions, upload, download, enable tasks, etc.
|
5
|
+
"""
|
6
|
+
import asyncio
|
7
|
+
import logging
|
8
|
+
import os
|
9
|
+
import re
|
10
|
+
import traceback
|
11
|
+
import uuid
|
12
|
+
from io import BytesIO, StringIO
|
13
|
+
from pathlib import Path
|
14
|
+
import cchardet
|
15
|
+
import magic
|
16
|
+
import pandas
|
17
|
+
import aiofiles
|
18
|
+
from asyncdb import AsyncDB
|
19
|
+
from asyncdb.exceptions import NoDataFound
|
20
|
+
from navigator.conf import asyncpg_url
|
21
|
+
from navigator.views import BaseView
|
22
|
+
from ...exceptions import (
|
23
|
+
FileError,
|
24
|
+
FileNotFound,
|
25
|
+
NotSupported,
|
26
|
+
TaskFailed,
|
27
|
+
TaskNotFound
|
28
|
+
)
|
29
|
+
# tasks
|
30
|
+
from ...services.tasks import launch_task
|
31
|
+
from ...conf import FILES_PATH
|
32
|
+
from .model import FileModel
|
33
|
+
|
34
|
+
|
35
|
+
excel_based = [
|
36
|
+
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
37
|
+
"application/vnd.ms-excel.sheet.macroEnabled.12",
|
38
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
39
|
+
"application/vnd.ms-excel",
|
40
|
+
"text/xml"
|
41
|
+
]
|
42
|
+
|
43
|
+
|
44
|
+
class FileUploaded:
|
45
|
+
"""
|
46
|
+
FileUploaded.
|
47
|
+
processing a FileField
|
48
|
+
"""
|
49
|
+
_file = None
|
50
|
+
_filefield: str = 'file'
|
51
|
+
_content = None
|
52
|
+
_directory = None
|
53
|
+
_path = None
|
54
|
+
_df = None
|
55
|
+
_numRows = 0
|
56
|
+
_columns = []
|
57
|
+
_mimetype = 'application/octet-stream'
|
58
|
+
_encoding = 'ISO-8859-1'
|
59
|
+
|
60
|
+
def __init__(self, post, mimetype=None):
|
61
|
+
self._file = post[self._filefield]
|
62
|
+
self._content = self._file.file.read()
|
63
|
+
if mimetype:
|
64
|
+
self._mimetype = mimetype
|
65
|
+
else:
|
66
|
+
self._mimetype = self._file.content_type
|
67
|
+
|
68
|
+
def content(self):
|
69
|
+
return self._content
|
70
|
+
|
71
|
+
@property
|
72
|
+
def filename(self):
|
73
|
+
return self._file.filename
|
74
|
+
|
75
|
+
@property
|
76
|
+
def df(self):
|
77
|
+
return self._df
|
78
|
+
|
79
|
+
def num_rows(self):
|
80
|
+
return self._numRows
|
81
|
+
|
82
|
+
@property
|
83
|
+
def columns(self):
|
84
|
+
return self._columns
|
85
|
+
|
86
|
+
@property
|
87
|
+
def content_type(self):
|
88
|
+
return self._mimetype
|
89
|
+
|
90
|
+
def is_empty(self):
|
91
|
+
return not bool(self._content)
|
92
|
+
|
93
|
+
def is_mime_valid(self, type):
|
94
|
+
if type is not None:
|
95
|
+
if self._mimetype == type or self._file.content_type == type:
|
96
|
+
return True
|
97
|
+
else:
|
98
|
+
return False
|
99
|
+
else:
|
100
|
+
f = magic.Magic(mime=True)
|
101
|
+
try:
|
102
|
+
mime = f.from_buffer(self._content)
|
103
|
+
return bool(mime)
|
104
|
+
except Exception:
|
105
|
+
# return True # cant enforcing Mime check using mime Magic
|
106
|
+
return False
|
107
|
+
|
108
|
+
def valid_name(self, name, pattern, rename=False):
|
109
|
+
# TODO: using pattern to validate name structure
|
110
|
+
if rename:
|
111
|
+
return True
|
112
|
+
# print(self._file.filename, pattern, name)
|
113
|
+
if pattern is not None:
|
114
|
+
return re.match(pattern, self._file.filename)
|
115
|
+
elif self._file.filename != name:
|
116
|
+
return False
|
117
|
+
else:
|
118
|
+
return True
|
119
|
+
|
120
|
+
def valid_content(self, **kwargs):
|
121
|
+
"""
|
122
|
+
valid_content.
|
123
|
+
check if is a valid content-type
|
124
|
+
ex: if is a csv, json or excel, open with pandas, if txt with stream, if image, etc
|
125
|
+
"""
|
126
|
+
if self._mimetype in ('application/octet-stream', 'application/x-zip-compressed', 'application/zip'):
|
127
|
+
return [True, None]
|
128
|
+
|
129
|
+
self._encoding = self._get_encoding()
|
130
|
+
s = self._get_decoded_content()
|
131
|
+
try:
|
132
|
+
data = StringIO(s)
|
133
|
+
bdata = BytesIO(self._content)
|
134
|
+
except Exception as err:
|
135
|
+
logging.error(f'Error encoding data: {err}')
|
136
|
+
s = str(self._content.decode('latin1').encode('utf-8'), 'utf-8')
|
137
|
+
data = StringIO(s)
|
138
|
+
bdata = BytesIO(self._content)
|
139
|
+
# check if is a valid content-type
|
140
|
+
try:
|
141
|
+
if self._mimetype == 'text/csv' or self._mimetype == 'text/plain':
|
142
|
+
self._df = pandas.read_csv(
|
143
|
+
data,
|
144
|
+
decimal=',',
|
145
|
+
engine='c',
|
146
|
+
keep_default_na=False,
|
147
|
+
na_values=['TBD', 'NULL', 'null', ''],
|
148
|
+
encoding=self._encoding,
|
149
|
+
skipinitialspace=True,
|
150
|
+
low_memory=False,
|
151
|
+
**kwargs
|
152
|
+
)
|
153
|
+
elif self._mimetype == 'application/json':
|
154
|
+
self._df = pandas.read_json(
|
155
|
+
data,
|
156
|
+
orient='records',
|
157
|
+
encoding=self._encoding,
|
158
|
+
low_memory=False,
|
159
|
+
**kwargs
|
160
|
+
)
|
161
|
+
elif self._mimetype in excel_based:
|
162
|
+
if self._mimetype == 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
|
163
|
+
# xlsx or any openxml based document
|
164
|
+
file_engine = 'openpyxl'
|
165
|
+
elif self._mimetype == 'application/vnd.ms-excel.sheet.binary.macroEnabled.12':
|
166
|
+
file_engine = 'pyxlsb'
|
167
|
+
elif self._mimetype == "application/vnd.ms-excel":
|
168
|
+
file_engine = 'xlrd'
|
169
|
+
else:
|
170
|
+
try:
|
171
|
+
ext = Path(self._file.filename).suffix
|
172
|
+
except (AttributeError, ValueError):
|
173
|
+
ext = '.xls'
|
174
|
+
if ext == '.xls':
|
175
|
+
file_engine = 'xlrd'
|
176
|
+
else:
|
177
|
+
file_engine = 'openpyxl'
|
178
|
+
self._df = pandas.read_excel(
|
179
|
+
bdata,
|
180
|
+
engine=file_engine,
|
181
|
+
keep_default_na=True,
|
182
|
+
# low_memory=False,
|
183
|
+
**kwargs
|
184
|
+
)
|
185
|
+
else:
|
186
|
+
# TODO: currently is and invalid content
|
187
|
+
return [False, f'Error: Invalid content type {self._file.content_type}']
|
188
|
+
except pandas.errors.EmptyDataError as err:
|
189
|
+
raise Exception(
|
190
|
+
f"Error on Empty File: {self._file.filename}, error: {err}"
|
191
|
+
) from err
|
192
|
+
except pandas.errors.ParserError as err:
|
193
|
+
raise Exception(
|
194
|
+
f"Error on Parsing File: {self._file.filename}, error: {err}"
|
195
|
+
) from err
|
196
|
+
except ValueError as err:
|
197
|
+
raise Exception(
|
198
|
+
f"Error on File: {self._file.filename}, error: {err}"
|
199
|
+
) from err
|
200
|
+
except Exception as err:
|
201
|
+
raise Exception(
|
202
|
+
f"Cannot Process File: {self._file.filename}, error: {err}"
|
203
|
+
) from err
|
204
|
+
# exists dataframe:
|
205
|
+
print(self._df)
|
206
|
+
try:
|
207
|
+
if self._df.empty:
|
208
|
+
return [True, 'DataFrame is Empty']
|
209
|
+
else:
|
210
|
+
# self._df = self._df.fillna('')
|
211
|
+
self._columns = list(self._df.columns)
|
212
|
+
self._numRows = len(self._df.index)
|
213
|
+
return [True, None]
|
214
|
+
except Exception as err:
|
215
|
+
return [False, f'Error Processing Dataframe, {err.__class__} {err}']
|
216
|
+
|
217
|
+
def _get_encoding(self):
|
218
|
+
try:
|
219
|
+
result_charset = cchardet.detect(self._content)
|
220
|
+
confidence = result_charset['confidence']
|
221
|
+
encoding = result_charset['encoding']
|
222
|
+
if confidence < 0.6:
|
223
|
+
logging.warning(
|
224
|
+
f'Warning: charset confidence was only {confidence}'
|
225
|
+
)
|
226
|
+
if result_charset['encoding'] == 'ascii' or result_charset['encoding'] == 'ASCII':
|
227
|
+
encoding = "utf-8"
|
228
|
+
except Exception as e:
|
229
|
+
print('Error: ', e)
|
230
|
+
encoding = 'ISO-8859–1'
|
231
|
+
finally:
|
232
|
+
if encoding is None:
|
233
|
+
encoding = "ISO-8859–1"
|
234
|
+
|
235
|
+
logging.debug(f'Detected Encoding is: {encoding}')
|
236
|
+
return encoding
|
237
|
+
|
238
|
+
def _get_decoded_content(self) -> str:
|
239
|
+
for enc in (self._encoding, 'utf-8', 'latin1', 'ascii'):
|
240
|
+
try:
|
241
|
+
return str(self._content, enc)
|
242
|
+
except UnicodeDecodeError:
|
243
|
+
continue
|
244
|
+
else:
|
245
|
+
raise FileError(message="Can't decode file!", status=422)
|
246
|
+
|
247
|
+
@property
|
248
|
+
def path(self):
|
249
|
+
return self._path
|
250
|
+
|
251
|
+
def directory(self, dirname, filename: str = None):
|
252
|
+
if not filename:
|
253
|
+
filename = self._file.filename
|
254
|
+
self._directory = dirname
|
255
|
+
self._path = os.path.join(dirname, filename)
|
256
|
+
|
257
|
+
async def save(self, directory=None, forcing_charset: bool = False):
|
258
|
+
if directory:
|
259
|
+
self.directory(directory)
|
260
|
+
async with aiofiles.open(self._path, 'w+b') as fp:
|
261
|
+
try:
|
262
|
+
if forcing_charset is True:
|
263
|
+
await fp.write(self._content.decode(self._encoding).encode('utf-8'))
|
264
|
+
else:
|
265
|
+
await fp.write(self._content)
|
266
|
+
await fp.flush()
|
267
|
+
except (ValueError, asyncio.InvalidStateError) as err:
|
268
|
+
logging.exception(f'Saving File Error: {err!s}')
|
269
|
+
raise
|
270
|
+
except Exception as err:
|
271
|
+
logging.exception(f'Saving File Error: {err!s}')
|
272
|
+
raise
|
273
|
+
|
274
|
+
|
275
|
+
class FileService(BaseView):
|
276
|
+
|
277
|
+
log_data = {}
|
278
|
+
file_slug = None
|
279
|
+
user_id = None
|
280
|
+
scheduler = None
|
281
|
+
job = None
|
282
|
+
permission = None
|
283
|
+
|
284
|
+
async def get(self):
|
285
|
+
"""
|
286
|
+
---
|
287
|
+
description: Get all the Files objects in the current scope and program
|
288
|
+
summary: get the files information and attributes
|
289
|
+
tags:
|
290
|
+
- FileService
|
291
|
+
produces:
|
292
|
+
- application/json
|
293
|
+
parameters:
|
294
|
+
- name: user_id
|
295
|
+
description: user id to filter
|
296
|
+
in: path
|
297
|
+
required: true
|
298
|
+
type: integer
|
299
|
+
- name: file_slug
|
300
|
+
description: file slug
|
301
|
+
in: path
|
302
|
+
required: true
|
303
|
+
type: string
|
304
|
+
responses:
|
305
|
+
"200":
|
306
|
+
description: returns valid data
|
307
|
+
"204":
|
308
|
+
description: No data
|
309
|
+
"403":
|
310
|
+
description: Forbidden Call
|
311
|
+
"404":
|
312
|
+
description: Program o File not found
|
313
|
+
"406":
|
314
|
+
description: Query Error
|
315
|
+
"""
|
316
|
+
try:
|
317
|
+
data = {}
|
318
|
+
user_id = self.request.rel_url.query['user_id']
|
319
|
+
file_slug = self.request.rel_url.query['file_slug']
|
320
|
+
except Exception as e:
|
321
|
+
headers = {
|
322
|
+
'X-STATUS': 'EMPTY',
|
323
|
+
'X-MESSAGE': 'Data not Found',
|
324
|
+
'X-ERROR': str(e)
|
325
|
+
}
|
326
|
+
return self.no_content(headers=headers)
|
327
|
+
finally:
|
328
|
+
await self.close()
|
329
|
+
|
330
|
+
async def valid_permission(self, user_id, codename):
|
331
|
+
"""
|
332
|
+
valid_permission.
|
333
|
+
Check if the user have permission for one func on the system.
|
334
|
+
"""
|
335
|
+
# get from db
|
336
|
+
try:
|
337
|
+
sql = f"SELECT DISTINCT(tug.groups_id), ap.id as permission_id, ap.name as permission_name, ap.codename \
|
338
|
+
FROM troc.troc_user_group tug \
|
339
|
+
LEFT JOIN public.auth_group_permissions agp on agp.group_id = tug.groups_id \
|
340
|
+
LEFT JOIN public.auth_permission ap on ap.id = agp.permission_id \
|
341
|
+
WHERE tug.user_id = {user_id} and ap.codename = '{codename}';"
|
342
|
+
result = await self.query(sql)
|
343
|
+
if result:
|
344
|
+
self.permission = result[0]
|
345
|
+
return True
|
346
|
+
else:
|
347
|
+
return False
|
348
|
+
except Exception as e:
|
349
|
+
return False
|
350
|
+
|
351
|
+
async def put(self):
|
352
|
+
""" PUT FileService.
|
353
|
+
description: Upload a File and, optionally, running an associated Task
|
354
|
+
Parameters:
|
355
|
+
file_slug: slug of the file in TROC files table
|
356
|
+
program_slug: associated program
|
357
|
+
mimetype: optional mime-type, default csv
|
358
|
+
module_id: optional module ID
|
359
|
+
task: boolean in query-params to disable running task.
|
360
|
+
long_running: query-param to attach a Task in a Thread Pool
|
361
|
+
"""
|
362
|
+
error = ''
|
363
|
+
no_worker = False
|
364
|
+
try:
|
365
|
+
qp = self.query_parameters(self.request)
|
366
|
+
except ValueError as e:
|
367
|
+
logging.exception(f'Error getting Parameters for FileService: {e}')
|
368
|
+
return self.critical(
|
369
|
+
reason=f'Error getting Parameters for FileService: {e}'
|
370
|
+
)
|
371
|
+
try:
|
372
|
+
runtask = bool(qp['task'])
|
373
|
+
except KeyError:
|
374
|
+
runtask = False
|
375
|
+
logging.debug(f'Run Task is: {runtask}')
|
376
|
+
try:
|
377
|
+
queued = qp['long_running']
|
378
|
+
except KeyError:
|
379
|
+
queued = False
|
380
|
+
try:
|
381
|
+
no_worker = qp['no_worker']
|
382
|
+
except KeyError:
|
383
|
+
no_worker = False
|
384
|
+
try:
|
385
|
+
post = await self.request.post()
|
386
|
+
except Exception as e:
|
387
|
+
return self.critical(
|
388
|
+
f'Error getting Parameters for FileService: {e}'
|
389
|
+
)
|
390
|
+
# file slug
|
391
|
+
try:
|
392
|
+
file_slug = post['file_slug']
|
393
|
+
except (TypeError, KeyError):
|
394
|
+
file_slug = None
|
395
|
+
try:
|
396
|
+
program_slug = post['program_slug']
|
397
|
+
except (TypeError, KeyError):
|
398
|
+
if file_slug:
|
399
|
+
program_slug = file_slug.split('_')[0]
|
400
|
+
else:
|
401
|
+
program_slug = 'troc'
|
402
|
+
# TODO: get program id from program_slug
|
403
|
+
try:
|
404
|
+
subdir = qp['subdir']
|
405
|
+
except KeyError:
|
406
|
+
try:
|
407
|
+
subdir = post['subdir']
|
408
|
+
except KeyError:
|
409
|
+
subdir = ''
|
410
|
+
# mime type
|
411
|
+
try:
|
412
|
+
mimetype = post['mimetype']
|
413
|
+
except (TypeError, KeyError):
|
414
|
+
mimetype = 'text/csv'
|
415
|
+
logging.debug(
|
416
|
+
f'File Upload for program {program_slug} for slug {file_slug}'
|
417
|
+
)
|
418
|
+
try:
|
419
|
+
# getting the FileField Object
|
420
|
+
f = FileUploaded(post, mimetype=mimetype)
|
421
|
+
filename = f.filename
|
422
|
+
content_type = f.content_type
|
423
|
+
logging.debug(
|
424
|
+
f'Opening File: {filename} with content type: {content_type}'
|
425
|
+
)
|
426
|
+
except Exception as e:
|
427
|
+
return self.error(
|
428
|
+
reason=f"Error on Uploaded File: {e}"
|
429
|
+
)
|
430
|
+
if f.is_empty():
|
431
|
+
headers = {
|
432
|
+
'X-STATUS': 'EMPTY',
|
433
|
+
'X-MESSAGE': 'File is Empty'
|
434
|
+
}
|
435
|
+
return self.no_content(headers=headers)
|
436
|
+
elif not f.is_mime_valid(mimetype):
|
437
|
+
headers = {
|
438
|
+
'X-STATUS': 'Error',
|
439
|
+
'X-MESSAGE': 'File has wrong mimetype',
|
440
|
+
'X-FILENAME': f.filename
|
441
|
+
}
|
442
|
+
data = {
|
443
|
+
"message": f"Wrong mimetype on file {f.filename}: got {mimetype}, expected: {f.content_type}"
|
444
|
+
}
|
445
|
+
logging.error(
|
446
|
+
f"Wrong mimetype on {f.filename}: got {mimetype}, expected: {f.content_type}"
|
447
|
+
)
|
448
|
+
return self.error(response=data, headers=headers, status=401)
|
449
|
+
if not file_slug:
|
450
|
+
filepath = FILES_PATH.joinpath(program_slug, 'files', subdir)
|
451
|
+
if not filepath.exists():
|
452
|
+
try:
|
453
|
+
filepath.mkdir(parents=True)
|
454
|
+
except Exception as err:
|
455
|
+
logging.error(
|
456
|
+
f'Error creating Directory: {err}'
|
457
|
+
)
|
458
|
+
# TODO: get configuration for saving files from frontend
|
459
|
+
# TODO: validate user permissions
|
460
|
+
# TODO: using the current program directory to upload the file
|
461
|
+
f.directory(filepath)
|
462
|
+
try:
|
463
|
+
await f.save()
|
464
|
+
headers = {
|
465
|
+
'X-STATUS': 'OK',
|
466
|
+
'X-MESSAGE': 'File Uploaded'
|
467
|
+
}
|
468
|
+
data = {
|
469
|
+
"message": f"file was uploaded to directory: {f.path}"
|
470
|
+
}
|
471
|
+
return self.json_response(response=data, headers=headers)
|
472
|
+
except Exception as e:
|
473
|
+
return self.critical(reason=f'Uncaught Error on File Uploaded: {e}')
|
474
|
+
else:
|
475
|
+
try:
|
476
|
+
headers = {
|
477
|
+
'X-STATUS': 'Error',
|
478
|
+
'X-MESSAGE': f'File Slug doesnt exists or is disabled {file_slug}',
|
479
|
+
'X-FILENAME': f.filename
|
480
|
+
}
|
481
|
+
db = AsyncDB('pg', dsn=asyncpg_url)
|
482
|
+
async with await db.connection() as conn:
|
483
|
+
FileModel.Meta.connection = conn
|
484
|
+
# getting from model:
|
485
|
+
filedef = await FileModel.get(file_slug=file_slug)
|
486
|
+
logging.debug(f'Detected File Definition: {filedef}')
|
487
|
+
if not filedef:
|
488
|
+
return self.error(
|
489
|
+
response=f"File Slug doesn't Exists: {file_slug}",
|
490
|
+
headers=headers
|
491
|
+
)
|
492
|
+
except NoDataFound:
|
493
|
+
return self.error(
|
494
|
+
response=f"File Service: Slug not Found: {file_slug}",
|
495
|
+
status=404
|
496
|
+
)
|
497
|
+
except Exception as err:
|
498
|
+
print(err, type(err))
|
499
|
+
return self.error(
|
500
|
+
response=f"Error querying File Service: {file_slug}: {err!s}"
|
501
|
+
)
|
502
|
+
# start validation
|
503
|
+
mime = filedef.mimetype
|
504
|
+
if mime and not f.is_mime_valid(mime):
|
505
|
+
data = {
|
506
|
+
"message": f"Wrong mimetype for File, got: {f.content_type} expected: {mime}"
|
507
|
+
}
|
508
|
+
return self.error(response=data)
|
509
|
+
name = filedef.filename
|
510
|
+
attributes = filedef.attributes
|
511
|
+
params = filedef.params
|
512
|
+
logging.debug(
|
513
|
+
f'File Definition: {name}, Attributes: {attributes}, {params}'
|
514
|
+
)
|
515
|
+
# rename file
|
516
|
+
try:
|
517
|
+
rename = name['rename']
|
518
|
+
except (KeyError, TypeError):
|
519
|
+
rename = False
|
520
|
+
# create directory
|
521
|
+
try:
|
522
|
+
create = attributes['create_dir']
|
523
|
+
except (KeyError, TypeError):
|
524
|
+
create = False
|
525
|
+
|
526
|
+
if 'name' in name:
|
527
|
+
if not f.valid_name(name['name'], name['pattern'], rename=rename):
|
528
|
+
data = {
|
529
|
+
"message": f"Wrong Filename for File, expected: {name['name']}"
|
530
|
+
}
|
531
|
+
return self.error(response=data)
|
532
|
+
# file is valid, needs to save:
|
533
|
+
try:
|
534
|
+
path = name['path']
|
535
|
+
except KeyError:
|
536
|
+
path = params['data_path']
|
537
|
+
path = Path(path).resolve()
|
538
|
+
if not path.exists():
|
539
|
+
# if doesnt exists path, is a relative path
|
540
|
+
logging.debug(f'Saving file on path: {path!s}')
|
541
|
+
try:
|
542
|
+
if create is True:
|
543
|
+
path.mkdir(exist_ok=True, parents=True)
|
544
|
+
else:
|
545
|
+
# directory to upload file doesn't exists
|
546
|
+
data = {
|
547
|
+
"message": f"Directory for upload File doesn't exists {path}"
|
548
|
+
}
|
549
|
+
return self.error(response=data)
|
550
|
+
except Exception as err:
|
551
|
+
data = {
|
552
|
+
"message": f"Error creating Directory: {err!s}"
|
553
|
+
}
|
554
|
+
return self.error(
|
555
|
+
exception=err,
|
556
|
+
response=data
|
557
|
+
)
|
558
|
+
# validacion de contenido (verificar csv, validate columns, data structure, etc)
|
559
|
+
try:
|
560
|
+
validate = params['validate']
|
561
|
+
except (KeyError, TypeError):
|
562
|
+
validate = True
|
563
|
+
if validate:
|
564
|
+
try:
|
565
|
+
pargs = params['pandas']
|
566
|
+
except KeyError:
|
567
|
+
pargs = {}
|
568
|
+
result, error = f.valid_content(**pargs)
|
569
|
+
logging.debug(
|
570
|
+
f'Validate pandas File: {result}, error: {error}')
|
571
|
+
if error and not result:
|
572
|
+
data = {"status": error}
|
573
|
+
return self.error(response=data, status=403)
|
574
|
+
elif result and error:
|
575
|
+
data = {
|
576
|
+
"status": error,
|
577
|
+
"message": "Empty File"
|
578
|
+
}
|
579
|
+
return self.error(response=data, status=404)
|
580
|
+
else:
|
581
|
+
# dataframe is valid, we need to make other validations
|
582
|
+
if filedef.fields:
|
583
|
+
try:
|
584
|
+
case_sensitive = filedef.case_sensitive
|
585
|
+
except Exception:
|
586
|
+
case_sensitive = False
|
587
|
+
if case_sensitive is True:
|
588
|
+
validate_cols = filedef.fields
|
589
|
+
columns = f.columns
|
590
|
+
else:
|
591
|
+
validate_cols = [f.lower() for f in filedef.fields]
|
592
|
+
columns = [c.lower() for c in f.columns]
|
593
|
+
if validate_cols:
|
594
|
+
if validate_cols != columns:
|
595
|
+
data = {
|
596
|
+
"status": "Error",
|
597
|
+
"message": "Invalid Column Names",
|
598
|
+
"expected": validate_cols,
|
599
|
+
"columns": f.columns
|
600
|
+
}
|
601
|
+
return self.error(response=data, status=401)
|
602
|
+
# TODO: make other validations, as data validations, and data quality
|
603
|
+
# define the directory to save file
|
604
|
+
f.directory(path)
|
605
|
+
try:
|
606
|
+
forcing_charset = attributes['forcing_charset']
|
607
|
+
except KeyError:
|
608
|
+
forcing_charset = False
|
609
|
+
if os.path.exists(f.path) and attributes['overwrite'] is False:
|
610
|
+
# file exists
|
611
|
+
data = {
|
612
|
+
"message": f"File already exists {f.path}"
|
613
|
+
}
|
614
|
+
return self.error(response=data)
|
615
|
+
else:
|
616
|
+
try:
|
617
|
+
await f.save(
|
618
|
+
directory=path,
|
619
|
+
forcing_charset=forcing_charset
|
620
|
+
)
|
621
|
+
except OSError as err:
|
622
|
+
logging.exception(
|
623
|
+
f'Connection Aborted on Upload: {err!s}'
|
624
|
+
)
|
625
|
+
except Exception as err:
|
626
|
+
print(err)
|
627
|
+
data = {
|
628
|
+
"message": f"Error Saving File on Path {f.path}"
|
629
|
+
}
|
630
|
+
return self.error(response=data)
|
631
|
+
# file exists and was uploaded
|
632
|
+
headers = {
|
633
|
+
'X-STATUS': 'OK',
|
634
|
+
'X-MESSAGE': 'File was Uploaded'
|
635
|
+
}
|
636
|
+
response = {
|
637
|
+
"message": f"file {f.filename} was uploaded to {f.path}",
|
638
|
+
"state": 202
|
639
|
+
}
|
640
|
+
logging.debug(response['message'])
|
641
|
+
result = {}
|
642
|
+
run_task = filedef.task_enabled if filedef.task_enabled else runtask
|
643
|
+
if run_task is True:
|
644
|
+
# if runtask is True or filedef.task_enabled is True:
|
645
|
+
try:
|
646
|
+
args = params['args']
|
647
|
+
except KeyError:
|
648
|
+
args = {}
|
649
|
+
# need to instanciate a Task Object to launch Task
|
650
|
+
try:
|
651
|
+
queued = params['long_running']
|
652
|
+
except KeyError:
|
653
|
+
pass
|
654
|
+
try:
|
655
|
+
task_id = params['task_id']
|
656
|
+
except (TypeError, ValueError, KeyError):
|
657
|
+
task_id = filedef.file_slug
|
658
|
+
program_slug = filedef.program_slug
|
659
|
+
try:
|
660
|
+
# loop = asyncio.new_event_loop()
|
661
|
+
task_uuid = uuid.uuid4()
|
662
|
+
if not filedef.params:
|
663
|
+
params = {}
|
664
|
+
else:
|
665
|
+
params = filedef.params
|
666
|
+
args = {**args, **params}
|
667
|
+
status, action, result = await launch_task(
|
668
|
+
program_slug=program_slug,
|
669
|
+
# task_id=task_id,
|
670
|
+
loop=self._loop,
|
671
|
+
task_uuid=task_uuid,
|
672
|
+
queued=queued,
|
673
|
+
no_worker=no_worker,
|
674
|
+
**args
|
675
|
+
)
|
676
|
+
result = {
|
677
|
+
"task": f"{program_slug}.{task_id}",
|
678
|
+
"task_execution": task_uuid,
|
679
|
+
"result": f"{status!s}"
|
680
|
+
}
|
681
|
+
if action == 'Executed':
|
682
|
+
state = 200
|
683
|
+
else:
|
684
|
+
state = 202
|
685
|
+
response = {
|
686
|
+
"state": state,
|
687
|
+
"message": f"Task associated with file {f.filename} was {action}",
|
688
|
+
**result
|
689
|
+
}
|
690
|
+
except OSError as err:
|
691
|
+
logging.exception(f'Connection Aborted: {err!s}')
|
692
|
+
return self.error(
|
693
|
+
reason=f'Connection Aborted: {err!s}'
|
694
|
+
)
|
695
|
+
except FileNotFound as err:
|
696
|
+
headers = {
|
697
|
+
'X-STATUS': 'Task Failed',
|
698
|
+
'X-MESSAGE': 'File Not Found'
|
699
|
+
}
|
700
|
+
response = {
|
701
|
+
"message": f"File Not Found {f.filename}",
|
702
|
+
"task": f"{program_slug}.{task_id}",
|
703
|
+
"state": 404,
|
704
|
+
"exception": str(err),
|
705
|
+
**result
|
706
|
+
}
|
707
|
+
return self.error(
|
708
|
+
response=response
|
709
|
+
)
|
710
|
+
except (NotSupported, TaskNotFound, TaskFailed, FileError) as err:
|
711
|
+
headers = {
|
712
|
+
'X-STATUS': 'Task Failed',
|
713
|
+
'X-MESSAGE': 'Task Execution Failed'
|
714
|
+
}
|
715
|
+
response = {
|
716
|
+
"message": f"Task error on associated file {f.filename}",
|
717
|
+
"task": f"{program_slug}.{task_id}",
|
718
|
+
"state": 406,
|
719
|
+
"exception": str(err),
|
720
|
+
**result
|
721
|
+
}
|
722
|
+
return self.error(
|
723
|
+
response=response
|
724
|
+
)
|
725
|
+
except Exception as err:
|
726
|
+
return self.critical(
|
727
|
+
exception=err, stacktrace=traceback.format_exc()
|
728
|
+
)
|
729
|
+
try:
|
730
|
+
show_preview = attributes['show_preview']
|
731
|
+
except KeyError:
|
732
|
+
show_preview = True
|
733
|
+
try:
|
734
|
+
num_preview = attributes['num_preview']
|
735
|
+
except KeyError:
|
736
|
+
num_preview = 10
|
737
|
+
if show_preview is True:
|
738
|
+
dt = f.df.head(num_preview).fillna('')
|
739
|
+
preview = dt.to_dict(orient='records')
|
740
|
+
else:
|
741
|
+
preview = None
|
742
|
+
try:
|
743
|
+
data = {
|
744
|
+
"task": f"{program_slug}.{task_id}",
|
745
|
+
'NumRows': f.num_rows(),
|
746
|
+
'columns': f.columns,
|
747
|
+
'data': preview,
|
748
|
+
'status': f"Upload completed: {f.filename}",
|
749
|
+
**response
|
750
|
+
}
|
751
|
+
if response['state'] > 300:
|
752
|
+
return self.error(
|
753
|
+
response=data,
|
754
|
+
headers=headers,
|
755
|
+
exception=response['exception'],
|
756
|
+
status=response['state']
|
757
|
+
)
|
758
|
+
else:
|
759
|
+
return self.json_response(
|
760
|
+
response=data,
|
761
|
+
headers=headers,
|
762
|
+
status=response['state']
|
763
|
+
)
|
764
|
+
except OSError as err:
|
765
|
+
logging.exception(f'Connection Aborted: {err!s}')
|
766
|
+
finally:
|
767
|
+
del filedef
|