flowtask 5.8.4__cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowtask/__init__.py +93 -0
- flowtask/__main__.py +38 -0
- flowtask/bots/__init__.py +6 -0
- flowtask/bots/check.py +93 -0
- flowtask/bots/codebot.py +51 -0
- flowtask/components/ASPX.py +148 -0
- flowtask/components/AddDataset.py +352 -0
- flowtask/components/Amazon.py +523 -0
- flowtask/components/AutoTask.py +314 -0
- flowtask/components/Azure.py +80 -0
- flowtask/components/AzureUsers.py +106 -0
- flowtask/components/BaseAction.py +91 -0
- flowtask/components/BaseLoop.py +198 -0
- flowtask/components/BestBuy.py +800 -0
- flowtask/components/CSVToGCS.py +120 -0
- flowtask/components/CompanyScraper/__init__.py +1 -0
- flowtask/components/CompanyScraper/parsers/__init__.py +6 -0
- flowtask/components/CompanyScraper/parsers/base.py +102 -0
- flowtask/components/CompanyScraper/parsers/explorium.py +192 -0
- flowtask/components/CompanyScraper/parsers/leadiq.py +206 -0
- flowtask/components/CompanyScraper/parsers/rocket.py +133 -0
- flowtask/components/CompanyScraper/parsers/siccode.py +109 -0
- flowtask/components/CompanyScraper/parsers/visualvisitor.py +130 -0
- flowtask/components/CompanyScraper/parsers/zoominfo.py +118 -0
- flowtask/components/CompanyScraper/scrapper.py +1054 -0
- flowtask/components/CopyTo.py +177 -0
- flowtask/components/CopyToBigQuery.py +243 -0
- flowtask/components/CopyToMongoDB.py +291 -0
- flowtask/components/CopyToPg.py +609 -0
- flowtask/components/CopyToRethink.py +207 -0
- flowtask/components/CreateGCSBucket.py +102 -0
- flowtask/components/CreateReport/CreateReport.py +228 -0
- flowtask/components/CreateReport/__init__.py +9 -0
- flowtask/components/CreateReport/charts/__init__.py +15 -0
- flowtask/components/CreateReport/charts/bar.py +51 -0
- flowtask/components/CreateReport/charts/base.py +66 -0
- flowtask/components/CreateReport/charts/pie.py +64 -0
- flowtask/components/CreateReport/utils.py +9 -0
- flowtask/components/CustomerSatisfaction.py +196 -0
- flowtask/components/DataInput.py +200 -0
- flowtask/components/DateList.py +255 -0
- flowtask/components/DbClient.py +163 -0
- flowtask/components/DialPad.py +146 -0
- flowtask/components/DocumentDBQuery.py +200 -0
- flowtask/components/DownloadFrom.py +371 -0
- flowtask/components/DownloadFromD2L.py +113 -0
- flowtask/components/DownloadFromFTP.py +181 -0
- flowtask/components/DownloadFromIMAP.py +315 -0
- flowtask/components/DownloadFromS3.py +198 -0
- flowtask/components/DownloadFromSFTP.py +265 -0
- flowtask/components/DownloadFromSharepoint.py +110 -0
- flowtask/components/DownloadFromSmartSheet.py +114 -0
- flowtask/components/DownloadS3File.py +229 -0
- flowtask/components/Dummy.py +59 -0
- flowtask/components/DuplicatePhoto.py +411 -0
- flowtask/components/EmployeeEvaluation.py +237 -0
- flowtask/components/ExecuteSQL.py +323 -0
- flowtask/components/ExtractHTML.py +178 -0
- flowtask/components/FileBase.py +178 -0
- flowtask/components/FileCopy.py +181 -0
- flowtask/components/FileDelete.py +82 -0
- flowtask/components/FileExists.py +146 -0
- flowtask/components/FileIteratorDelete.py +112 -0
- flowtask/components/FileList.py +194 -0
- flowtask/components/FileOpen.py +75 -0
- flowtask/components/FileRead.py +120 -0
- flowtask/components/FileRename.py +106 -0
- flowtask/components/FilterIf.py +284 -0
- flowtask/components/FilterRows/FilterRows.py +200 -0
- flowtask/components/FilterRows/__init__.py +10 -0
- flowtask/components/FilterRows/functions.py +4 -0
- flowtask/components/GCSToBigQuery.py +103 -0
- flowtask/components/GoogleA4.py +150 -0
- flowtask/components/GoogleGeoCoding.py +344 -0
- flowtask/components/GooglePlaces.py +315 -0
- flowtask/components/GoogleSearch.py +539 -0
- flowtask/components/HTTPClient.py +268 -0
- flowtask/components/ICIMS.py +146 -0
- flowtask/components/IF.py +179 -0
- flowtask/components/IcimsFolderCopy.py +173 -0
- flowtask/components/ImageFeatures/__init__.py +5 -0
- flowtask/components/ImageFeatures/process.py +233 -0
- flowtask/components/IteratorBase.py +251 -0
- flowtask/components/LangchainLoader/__init__.py +5 -0
- flowtask/components/LangchainLoader/loader.py +194 -0
- flowtask/components/LangchainLoader/loaders/__init__.py +22 -0
- flowtask/components/LangchainLoader/loaders/abstract.py +362 -0
- flowtask/components/LangchainLoader/loaders/basepdf.py +50 -0
- flowtask/components/LangchainLoader/loaders/docx.py +91 -0
- flowtask/components/LangchainLoader/loaders/html.py +119 -0
- flowtask/components/LangchainLoader/loaders/pdfblocks.py +146 -0
- flowtask/components/LangchainLoader/loaders/pdfmark.py +79 -0
- flowtask/components/LangchainLoader/loaders/pdftables.py +135 -0
- flowtask/components/LangchainLoader/loaders/qa.py +67 -0
- flowtask/components/LangchainLoader/loaders/txt.py +55 -0
- flowtask/components/LeadIQ.py +650 -0
- flowtask/components/Loop.py +253 -0
- flowtask/components/Lowes.py +334 -0
- flowtask/components/MS365Usage.py +156 -0
- flowtask/components/MSTeamsMessages.py +320 -0
- flowtask/components/MarketClustering.py +1051 -0
- flowtask/components/MergeFiles.py +362 -0
- flowtask/components/MilvusOutput.py +87 -0
- flowtask/components/NearByStores.py +175 -0
- flowtask/components/NetworkNinja/__init__.py +6 -0
- flowtask/components/NetworkNinja/models/__init__.py +52 -0
- flowtask/components/NetworkNinja/models/abstract.py +177 -0
- flowtask/components/NetworkNinja/models/account.py +39 -0
- flowtask/components/NetworkNinja/models/client.py +19 -0
- flowtask/components/NetworkNinja/models/district.py +14 -0
- flowtask/components/NetworkNinja/models/events.py +101 -0
- flowtask/components/NetworkNinja/models/forms.py +499 -0
- flowtask/components/NetworkNinja/models/market.py +16 -0
- flowtask/components/NetworkNinja/models/organization.py +34 -0
- flowtask/components/NetworkNinja/models/photos.py +125 -0
- flowtask/components/NetworkNinja/models/project.py +44 -0
- flowtask/components/NetworkNinja/models/region.py +28 -0
- flowtask/components/NetworkNinja/models/store.py +203 -0
- flowtask/components/NetworkNinja/models/user.py +151 -0
- flowtask/components/NetworkNinja/router.py +854 -0
- flowtask/components/Odoo.py +175 -0
- flowtask/components/OdooInjector.py +192 -0
- flowtask/components/OpenFromXML.py +126 -0
- flowtask/components/OpenWeather.py +41 -0
- flowtask/components/OpenWithBase.py +616 -0
- flowtask/components/OpenWithPandas.py +715 -0
- flowtask/components/PGPDecrypt.py +199 -0
- flowtask/components/PandasIterator.py +187 -0
- flowtask/components/PandasToFile.py +189 -0
- flowtask/components/Paradox.py +339 -0
- flowtask/components/ParamIterator.py +117 -0
- flowtask/components/ParseHTML.py +84 -0
- flowtask/components/PlacerStores.py +249 -0
- flowtask/components/Pokemon.py +507 -0
- flowtask/components/PositiveBot.py +62 -0
- flowtask/components/PowerPointSlide.py +400 -0
- flowtask/components/PrintMessage.py +127 -0
- flowtask/components/ProductCompetitors/__init__.py +5 -0
- flowtask/components/ProductCompetitors/parsers/__init__.py +7 -0
- flowtask/components/ProductCompetitors/parsers/base.py +72 -0
- flowtask/components/ProductCompetitors/parsers/bestbuy.py +86 -0
- flowtask/components/ProductCompetitors/parsers/lowes.py +103 -0
- flowtask/components/ProductCompetitors/scrapper.py +155 -0
- flowtask/components/ProductCompliant.py +169 -0
- flowtask/components/ProductInfo/__init__.py +1 -0
- flowtask/components/ProductInfo/parsers/__init__.py +5 -0
- flowtask/components/ProductInfo/parsers/base.py +83 -0
- flowtask/components/ProductInfo/parsers/brother.py +97 -0
- flowtask/components/ProductInfo/parsers/canon.py +167 -0
- flowtask/components/ProductInfo/parsers/epson.py +118 -0
- flowtask/components/ProductInfo/parsers/hp.py +131 -0
- flowtask/components/ProductInfo/parsers/samsung.py +97 -0
- flowtask/components/ProductInfo/scraper.py +319 -0
- flowtask/components/ProductPricing.py +118 -0
- flowtask/components/QS.py +261 -0
- flowtask/components/QSBase.py +201 -0
- flowtask/components/QueryIterator.py +273 -0
- flowtask/components/QueryToInsert.py +327 -0
- flowtask/components/QueryToPandas.py +432 -0
- flowtask/components/RESTClient.py +195 -0
- flowtask/components/RethinkDBQuery.py +189 -0
- flowtask/components/Rsync.py +74 -0
- flowtask/components/RunSSH.py +59 -0
- flowtask/components/RunShell.py +71 -0
- flowtask/components/SalesForce.py +20 -0
- flowtask/components/SaveImageBank/__init__.py +257 -0
- flowtask/components/SchedulingVisits.py +592 -0
- flowtask/components/ScrapPage.py +216 -0
- flowtask/components/ScrapSearch.py +79 -0
- flowtask/components/SendNotify.py +257 -0
- flowtask/components/SentimentAnalysis.py +694 -0
- flowtask/components/ServiceScrapper/__init__.py +5 -0
- flowtask/components/ServiceScrapper/parsers/__init__.py +1 -0
- flowtask/components/ServiceScrapper/parsers/base.py +94 -0
- flowtask/components/ServiceScrapper/parsers/costco.py +93 -0
- flowtask/components/ServiceScrapper/scrapper.py +199 -0
- flowtask/components/SetVariables.py +156 -0
- flowtask/components/SubTask.py +182 -0
- flowtask/components/SuiteCRM.py +48 -0
- flowtask/components/Switch.py +175 -0
- flowtask/components/TableBase.py +148 -0
- flowtask/components/TableDelete.py +312 -0
- flowtask/components/TableInput.py +143 -0
- flowtask/components/TableOutput/TableOutput.py +384 -0
- flowtask/components/TableOutput/__init__.py +3 -0
- flowtask/components/TableSchema.py +534 -0
- flowtask/components/Target.py +223 -0
- flowtask/components/ThumbnailGenerator.py +156 -0
- flowtask/components/ToPandas.py +67 -0
- flowtask/components/TransformRows/TransformRows.py +507 -0
- flowtask/components/TransformRows/__init__.py +9 -0
- flowtask/components/TransformRows/functions.py +559 -0
- flowtask/components/TransposeRows.py +176 -0
- flowtask/components/UPCDatabase.py +86 -0
- flowtask/components/UnGzip.py +171 -0
- flowtask/components/Uncompress.py +172 -0
- flowtask/components/UniqueRows.py +126 -0
- flowtask/components/Unzip.py +107 -0
- flowtask/components/UpdateOperationalVars.py +147 -0
- flowtask/components/UploadTo.py +299 -0
- flowtask/components/UploadToS3.py +136 -0
- flowtask/components/UploadToSFTP.py +160 -0
- flowtask/components/UploadToSharepoint.py +205 -0
- flowtask/components/UserFunc.py +122 -0
- flowtask/components/VivaTracker.py +140 -0
- flowtask/components/WSDLClient.py +123 -0
- flowtask/components/Wait.py +18 -0
- flowtask/components/Walmart.py +199 -0
- flowtask/components/Workplace.py +134 -0
- flowtask/components/XMLToPandas.py +267 -0
- flowtask/components/Zammad/__init__.py +41 -0
- flowtask/components/Zammad/models.py +0 -0
- flowtask/components/ZoomInfoScraper.py +409 -0
- flowtask/components/__init__.py +104 -0
- flowtask/components/abstract.py +18 -0
- flowtask/components/flow.py +530 -0
- flowtask/components/google.py +335 -0
- flowtask/components/group.py +221 -0
- flowtask/components/py.typed +0 -0
- flowtask/components/reviewscrap.py +132 -0
- flowtask/components/tAutoincrement.py +117 -0
- flowtask/components/tConcat.py +109 -0
- flowtask/components/tExplode.py +119 -0
- flowtask/components/tFilter.py +184 -0
- flowtask/components/tGroup.py +236 -0
- flowtask/components/tJoin.py +270 -0
- flowtask/components/tMap/__init__.py +9 -0
- flowtask/components/tMap/functions.py +54 -0
- flowtask/components/tMap/tMap.py +450 -0
- flowtask/components/tMelt.py +112 -0
- flowtask/components/tMerge.py +114 -0
- flowtask/components/tOrder.py +93 -0
- flowtask/components/tPandas.py +94 -0
- flowtask/components/tPivot.py +71 -0
- flowtask/components/tPluckCols.py +76 -0
- flowtask/components/tUnnest.py +82 -0
- flowtask/components/user.py +401 -0
- flowtask/conf.py +457 -0
- flowtask/download.py +102 -0
- flowtask/events/__init__.py +11 -0
- flowtask/events/events/__init__.py +20 -0
- flowtask/events/events/abstract.py +95 -0
- flowtask/events/events/alerts/__init__.py +362 -0
- flowtask/events/events/alerts/colfunctions.py +131 -0
- flowtask/events/events/alerts/functions.py +158 -0
- flowtask/events/events/dummy.py +12 -0
- flowtask/events/events/exec.py +124 -0
- flowtask/events/events/file/__init__.py +7 -0
- flowtask/events/events/file/base.py +51 -0
- flowtask/events/events/file/copy.py +23 -0
- flowtask/events/events/file/delete.py +16 -0
- flowtask/events/events/interfaces/__init__.py +9 -0
- flowtask/events/events/interfaces/client.py +67 -0
- flowtask/events/events/interfaces/credentials.py +28 -0
- flowtask/events/events/interfaces/notifications.py +58 -0
- flowtask/events/events/jira.py +122 -0
- flowtask/events/events/log.py +26 -0
- flowtask/events/events/logerr.py +52 -0
- flowtask/events/events/notify.py +59 -0
- flowtask/events/events/notify_event.py +160 -0
- flowtask/events/events/publish.py +54 -0
- flowtask/events/events/sendfile.py +104 -0
- flowtask/events/events/task.py +97 -0
- flowtask/events/events/teams.py +98 -0
- flowtask/events/events/webhook.py +58 -0
- flowtask/events/manager.py +287 -0
- flowtask/exceptions.c +39393 -0
- flowtask/exceptions.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/extensions/__init__.py +3 -0
- flowtask/extensions/abstract.py +82 -0
- flowtask/extensions/logging/__init__.py +65 -0
- flowtask/hooks/__init__.py +9 -0
- flowtask/hooks/actions/__init__.py +22 -0
- flowtask/hooks/actions/abstract.py +66 -0
- flowtask/hooks/actions/dummy.py +23 -0
- flowtask/hooks/actions/jira.py +74 -0
- flowtask/hooks/actions/rest.py +320 -0
- flowtask/hooks/actions/sampledata.py +37 -0
- flowtask/hooks/actions/sensor.py +23 -0
- flowtask/hooks/actions/task.py +9 -0
- flowtask/hooks/actions/ticket.py +37 -0
- flowtask/hooks/actions/zammad.py +55 -0
- flowtask/hooks/hook.py +62 -0
- flowtask/hooks/models.py +17 -0
- flowtask/hooks/service.py +187 -0
- flowtask/hooks/step.py +91 -0
- flowtask/hooks/types/__init__.py +23 -0
- flowtask/hooks/types/base.py +129 -0
- flowtask/hooks/types/brokers/__init__.py +11 -0
- flowtask/hooks/types/brokers/base.py +54 -0
- flowtask/hooks/types/brokers/mqtt.py +35 -0
- flowtask/hooks/types/brokers/rabbitmq.py +82 -0
- flowtask/hooks/types/brokers/redis.py +83 -0
- flowtask/hooks/types/brokers/sqs.py +44 -0
- flowtask/hooks/types/fs.py +232 -0
- flowtask/hooks/types/http.py +49 -0
- flowtask/hooks/types/imap.py +200 -0
- flowtask/hooks/types/jira.py +279 -0
- flowtask/hooks/types/mail.py +205 -0
- flowtask/hooks/types/postgres.py +98 -0
- flowtask/hooks/types/responses/__init__.py +8 -0
- flowtask/hooks/types/responses/base.py +5 -0
- flowtask/hooks/types/sharepoint.py +288 -0
- flowtask/hooks/types/ssh.py +141 -0
- flowtask/hooks/types/tagged.py +59 -0
- flowtask/hooks/types/upload.py +85 -0
- flowtask/hooks/types/watch.py +71 -0
- flowtask/hooks/types/web.py +36 -0
- flowtask/interfaces/AzureClient.py +137 -0
- flowtask/interfaces/AzureGraph.py +839 -0
- flowtask/interfaces/Boto3Client.py +326 -0
- flowtask/interfaces/DropboxClient.py +173 -0
- flowtask/interfaces/ExcelHandler.py +94 -0
- flowtask/interfaces/FTPClient.py +131 -0
- flowtask/interfaces/GoogleCalendar.py +201 -0
- flowtask/interfaces/GoogleClient.py +133 -0
- flowtask/interfaces/GoogleDrive.py +127 -0
- flowtask/interfaces/GoogleGCS.py +89 -0
- flowtask/interfaces/GoogleGeocoding.py +93 -0
- flowtask/interfaces/GoogleLang.py +114 -0
- flowtask/interfaces/GooglePub.py +61 -0
- flowtask/interfaces/GoogleSheet.py +68 -0
- flowtask/interfaces/IMAPClient.py +137 -0
- flowtask/interfaces/O365Calendar.py +113 -0
- flowtask/interfaces/O365Client.py +220 -0
- flowtask/interfaces/OneDrive.py +284 -0
- flowtask/interfaces/Outlook.py +155 -0
- flowtask/interfaces/ParrotBot.py +130 -0
- flowtask/interfaces/SSHClient.py +378 -0
- flowtask/interfaces/Sharepoint.py +496 -0
- flowtask/interfaces/__init__.py +36 -0
- flowtask/interfaces/azureauth.py +119 -0
- flowtask/interfaces/cache.py +201 -0
- flowtask/interfaces/client.py +82 -0
- flowtask/interfaces/compress.py +525 -0
- flowtask/interfaces/credentials.py +124 -0
- flowtask/interfaces/d2l.py +239 -0
- flowtask/interfaces/databases/__init__.py +5 -0
- flowtask/interfaces/databases/db.py +223 -0
- flowtask/interfaces/databases/documentdb.py +55 -0
- flowtask/interfaces/databases/rethink.py +39 -0
- flowtask/interfaces/dataframes/__init__.py +11 -0
- flowtask/interfaces/dataframes/abstract.py +21 -0
- flowtask/interfaces/dataframes/arrow.py +71 -0
- flowtask/interfaces/dataframes/dt.py +69 -0
- flowtask/interfaces/dataframes/pandas.py +167 -0
- flowtask/interfaces/dataframes/polars.py +60 -0
- flowtask/interfaces/db.py +263 -0
- flowtask/interfaces/env.py +46 -0
- flowtask/interfaces/func.py +137 -0
- flowtask/interfaces/http.py +1780 -0
- flowtask/interfaces/locale.py +40 -0
- flowtask/interfaces/log.py +75 -0
- flowtask/interfaces/mask.py +143 -0
- flowtask/interfaces/notification.py +154 -0
- flowtask/interfaces/playwright.py +339 -0
- flowtask/interfaces/powerpoint.py +368 -0
- flowtask/interfaces/py.typed +0 -0
- flowtask/interfaces/qs.py +376 -0
- flowtask/interfaces/result.py +87 -0
- flowtask/interfaces/selenium_service.py +779 -0
- flowtask/interfaces/smartsheet.py +154 -0
- flowtask/interfaces/stat.py +39 -0
- flowtask/interfaces/task.py +96 -0
- flowtask/interfaces/template.py +118 -0
- flowtask/interfaces/vectorstores/__init__.py +1 -0
- flowtask/interfaces/vectorstores/abstract.py +133 -0
- flowtask/interfaces/vectorstores/milvus.py +669 -0
- flowtask/interfaces/zammad.py +107 -0
- flowtask/models.py +193 -0
- flowtask/parsers/__init__.py +15 -0
- flowtask/parsers/_yaml.c +11978 -0
- flowtask/parsers/_yaml.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/argparser.py +235 -0
- flowtask/parsers/base.c +15155 -0
- flowtask/parsers/base.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/json.c +11968 -0
- flowtask/parsers/json.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/maps.py +49 -0
- flowtask/parsers/toml.c +11968 -0
- flowtask/parsers/toml.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/plugins/__init__.py +16 -0
- flowtask/plugins/components/__init__.py +0 -0
- flowtask/plugins/handler/__init__.py +45 -0
- flowtask/plugins/importer.py +31 -0
- flowtask/plugins/sources/__init__.py +0 -0
- flowtask/runner.py +283 -0
- flowtask/scheduler/__init__.py +9 -0
- flowtask/scheduler/functions.py +493 -0
- flowtask/scheduler/handlers/__init__.py +8 -0
- flowtask/scheduler/handlers/manager.py +504 -0
- flowtask/scheduler/handlers/models.py +58 -0
- flowtask/scheduler/handlers/service.py +72 -0
- flowtask/scheduler/notifications.py +65 -0
- flowtask/scheduler/scheduler.py +993 -0
- flowtask/services/__init__.py +0 -0
- flowtask/services/bots/__init__.py +0 -0
- flowtask/services/bots/telegram.py +264 -0
- flowtask/services/files/__init__.py +11 -0
- flowtask/services/files/manager.py +522 -0
- flowtask/services/files/model.py +37 -0
- flowtask/services/files/service.py +767 -0
- flowtask/services/jira/__init__.py +3 -0
- flowtask/services/jira/jira_actions.py +191 -0
- flowtask/services/tasks/__init__.py +13 -0
- flowtask/services/tasks/launcher.py +213 -0
- flowtask/services/tasks/manager.py +323 -0
- flowtask/services/tasks/service.py +275 -0
- flowtask/services/tasks/task_manager.py +376 -0
- flowtask/services/tasks/tasks.py +155 -0
- flowtask/storages/__init__.py +16 -0
- flowtask/storages/exceptions.py +12 -0
- flowtask/storages/files/__init__.py +8 -0
- flowtask/storages/files/abstract.py +29 -0
- flowtask/storages/files/filesystem.py +66 -0
- flowtask/storages/tasks/__init__.py +19 -0
- flowtask/storages/tasks/abstract.py +26 -0
- flowtask/storages/tasks/database.py +33 -0
- flowtask/storages/tasks/filesystem.py +108 -0
- flowtask/storages/tasks/github.py +119 -0
- flowtask/storages/tasks/memory.py +45 -0
- flowtask/storages/tasks/row.py +25 -0
- flowtask/tasks/__init__.py +0 -0
- flowtask/tasks/abstract.py +526 -0
- flowtask/tasks/command.py +118 -0
- flowtask/tasks/pile.py +486 -0
- flowtask/tasks/py.typed +0 -0
- flowtask/tasks/task.py +778 -0
- flowtask/template/__init__.py +161 -0
- flowtask/tests.py +257 -0
- flowtask/types/__init__.py +8 -0
- flowtask/types/typedefs.c +11347 -0
- flowtask/types/typedefs.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/__init__.py +24 -0
- flowtask/utils/constants.py +117 -0
- flowtask/utils/encoders.py +21 -0
- flowtask/utils/executor.py +112 -0
- flowtask/utils/functions.cpp +14280 -0
- flowtask/utils/functions.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/json.cpp +13349 -0
- flowtask/utils/json.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/mail.py +63 -0
- flowtask/utils/parseqs.c +13324 -0
- flowtask/utils/parserqs.cpython-310-x86_64-linux-gnu.so +0 -0
- flowtask/utils/stats.py +308 -0
- flowtask/utils/transformations.py +74 -0
- flowtask/utils/uv.py +12 -0
- flowtask/utils/validators.py +97 -0
- flowtask/version.py +11 -0
- flowtask-5.8.4.dist-info/LICENSE +201 -0
- flowtask-5.8.4.dist-info/METADATA +209 -0
- flowtask-5.8.4.dist-info/RECORD +470 -0
- flowtask-5.8.4.dist-info/WHEEL +6 -0
- flowtask-5.8.4.dist-info/entry_points.txt +3 -0
- flowtask-5.8.4.dist-info/top_level.txt +2 -0
- plugins/components/CreateQR.py +39 -0
- plugins/components/TestComponent.py +28 -0
- plugins/components/Use1.py +13 -0
- plugins/components/Workplace.py +117 -0
- plugins/components/__init__.py +3 -0
- plugins/sources/__init__.py +0 -0
- plugins/sources/get_populartimes.py +78 -0
- plugins/sources/google.py +150 -0
- plugins/sources/hubspot.py +679 -0
- plugins/sources/icims.py +679 -0
- plugins/sources/mobileinsight.py +501 -0
- plugins/sources/newrelic.py +262 -0
- plugins/sources/uap.py +268 -0
- plugins/sources/venu.py +244 -0
- plugins/sources/vocinity.py +314 -0
@@ -0,0 +1,326 @@
|
|
1
|
+
from typing import Dict, List, Optional
|
2
|
+
import re
|
3
|
+
import boto3
|
4
|
+
from botocore.exceptions import ClientError
|
5
|
+
import aiofiles
|
6
|
+
from navconfig.logging import logging
|
7
|
+
from io import BytesIO
|
8
|
+
from ..conf import aws_region
|
9
|
+
try:
|
10
|
+
from settings.settings import AWS_CREDENTIALS
|
11
|
+
except ImportError as e:
|
12
|
+
logging.exception(
|
13
|
+
f"AWS_CREDENTIALS not found. Please check your settings file: {e}"
|
14
|
+
)
|
15
|
+
from ..conf import AWS_CREDENTIALS
|
16
|
+
from .client import ClientInterface
|
17
|
+
from ..exceptions import FileNotFound, FileError, ComponentError
|
18
|
+
|
19
|
+
|
20
|
+
logging.getLogger("boto3").setLevel(logging.CRITICAL)
|
21
|
+
logging.getLogger("botocore").setLevel(logging.CRITICAL)
|
22
|
+
logging.getLogger("s3transfer").setLevel(logging.CRITICAL)
|
23
|
+
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
|
24
|
+
|
25
|
+
|
26
|
+
class Boto3Client(ClientInterface):
|
27
|
+
"""
|
28
|
+
Boto3 AWS Client.
|
29
|
+
|
30
|
+
Overview
|
31
|
+
|
32
|
+
Abstract class for interaction with Boto3 (AWS).
|
33
|
+
|
34
|
+
.. table:: Properties
|
35
|
+
:widths: auto
|
36
|
+
|
37
|
+
+------------------------+----------+-----------+-------------------------------------------------------+
|
38
|
+
| Name | Required | Summary |
|
39
|
+
+------------------------+----------+-----------+-------------------------------------------------------+
|
40
|
+
|_credentials | Yes | The function is loaded and then we define the necessary code to |
|
41
|
+
| | | call the script |
|
42
|
+
+------------------------+----------+-----------+-------------------------------------------------------+
|
43
|
+
| _init_ | Yes | Component for Data Integrator |
|
44
|
+
+------------------------+----------+-----------+-------------------------------------------------------+
|
45
|
+
| _host | Yes | The IPv4 or domain name of the server |
|
46
|
+
+------------------------+----------+-----------+-------------------------------------------------------+
|
47
|
+
| get_client | Yes | Gets the client access credentials, by which the user logs in to |
|
48
|
+
| | | perform an action |
|
49
|
+
+------------------------+----------+-----------+-------------------------------------------------------+
|
50
|
+
| print | Yes | Print message to display |
|
51
|
+
+------------------------+----------+-----------+-------------------------------------------------------+
|
52
|
+
| get_env_value | Yes | Get env value policies for setting virtual environment |
|
53
|
+
+------------------------+----------+-----------+-------------------------------------------------------+
|
54
|
+
| processing_credentials | Yes | client credentials configured for used of the app |
|
55
|
+
+------------------------+----------+-----------+-------------------------------------------------------+
|
56
|
+
|
57
|
+
Return the list of arbitrary days
|
58
|
+
|
59
|
+
|
60
|
+
""" # noqa
|
61
|
+
|
62
|
+
_credentials: Dict = {
|
63
|
+
"aws_key": str,
|
64
|
+
"aws_secret": str,
|
65
|
+
"client_id": str,
|
66
|
+
"client_secret": str,
|
67
|
+
"service": str,
|
68
|
+
"region_name": str,
|
69
|
+
"bucket": str,
|
70
|
+
}
|
71
|
+
|
72
|
+
def __init__(self, *args, **kwargs) -> None:
|
73
|
+
self.region_name: str = kwargs.pop('region_name', None)
|
74
|
+
self.service: str = kwargs.pop('service', 's3')
|
75
|
+
self.bucket: Optional[str] = kwargs.pop('bucket', None)
|
76
|
+
self.ContentType: str = kwargs.pop('ContentType', 'application/octet-stream')
|
77
|
+
if 'config_section' in kwargs:
|
78
|
+
self._config = kwargs.pop('config_section', 'default')
|
79
|
+
else:
|
80
|
+
self._config: str = kwargs.pop('config', 'default')
|
81
|
+
super().__init__(*args, **kwargs)
|
82
|
+
|
83
|
+
def define_host(self):
|
84
|
+
return True
|
85
|
+
|
86
|
+
async def open(
|
87
|
+
self,
|
88
|
+
host: str = None,
|
89
|
+
port: int = None,
|
90
|
+
credentials: dict = None,
|
91
|
+
**kwargs
|
92
|
+
):
|
93
|
+
use_credentials = credentials.pop('use_credentials', True) if credentials else True
|
94
|
+
service = self.credentials.get('service', self.service)
|
95
|
+
|
96
|
+
if use_credentials is False:
|
97
|
+
self._logger.notice("Boto3: Using default credentials from environment")
|
98
|
+
self._connection = boto3.client(
|
99
|
+
service,
|
100
|
+
region_name=self.credentials.get("region_name", self.region_name)
|
101
|
+
)
|
102
|
+
else:
|
103
|
+
self._logger.notice("Boto3: Enter signed with explicit credentials")
|
104
|
+
cred = {
|
105
|
+
"aws_access_key_id": self.credentials["aws_key"],
|
106
|
+
"aws_secret_access_key": self.credentials["aws_secret"],
|
107
|
+
"region_name": self.credentials["region_name"],
|
108
|
+
}
|
109
|
+
self._connection = boto3.client(
|
110
|
+
service,
|
111
|
+
**cred
|
112
|
+
)
|
113
|
+
return self
|
114
|
+
|
115
|
+
def processing_credentials(self):
|
116
|
+
# getting credentials from self.credentials:
|
117
|
+
if self.credentials:
|
118
|
+
super().processing_credentials()
|
119
|
+
else:
|
120
|
+
# getting credentials from config
|
121
|
+
self.credentials = AWS_CREDENTIALS.get(self._config)
|
122
|
+
if not self.credentials:
|
123
|
+
raise ValueError(
|
124
|
+
f'Credentials not found for section {self._config}.'
|
125
|
+
)
|
126
|
+
## getting Tenant and Site from credentials:
|
127
|
+
self.region_name = self.credentials.get('region_name', aws_region)
|
128
|
+
self.bucket = self.credentials.get('bucket_name', self.bucket)
|
129
|
+
self.service = self.credentials.get('service', 's3')
|
130
|
+
|
131
|
+
async def get_s3_object(self, bucket: str, filename: str):
|
132
|
+
"""
|
133
|
+
Retrieve an object from an S3 bucket.
|
134
|
+
|
135
|
+
Parameters
|
136
|
+
----------
|
137
|
+
bucket: str
|
138
|
+
The name of the S3 bucket.
|
139
|
+
filename: str
|
140
|
+
The name of the file (key) in the S3 bucket.
|
141
|
+
|
142
|
+
Returns
|
143
|
+
-------
|
144
|
+
dict
|
145
|
+
A dictionary containing the object data and metadata.
|
146
|
+
|
147
|
+
Raises
|
148
|
+
------
|
149
|
+
FileNotFound
|
150
|
+
If the object is not found in the bucket.
|
151
|
+
ComponentError
|
152
|
+
If there is an issue with retrieving the object.
|
153
|
+
"""
|
154
|
+
# Ensure connection is established
|
155
|
+
if not self._connection:
|
156
|
+
raise ComponentError(
|
157
|
+
"S3 client is not connected. Call `open` first."
|
158
|
+
)
|
159
|
+
|
160
|
+
# Get the object from S3
|
161
|
+
obj = self._connection.get_object(Bucket=bucket, Key=filename)
|
162
|
+
# Validate the response
|
163
|
+
status_code = int(obj["ResponseMetadata"]["HTTPStatusCode"])
|
164
|
+
if status_code != 200:
|
165
|
+
raise FileNotFound(
|
166
|
+
f"File '{filename}' not found in bucket '{bucket}'."
|
167
|
+
)
|
168
|
+
return obj
|
169
|
+
|
170
|
+
async def download_file(self, filename, obj):
|
171
|
+
result = None
|
172
|
+
ob_info = obj["ResponseMetadata"]["HTTPHeaders"]
|
173
|
+
rsp = obj["ResponseMetadata"]
|
174
|
+
status_code = int(rsp["HTTPStatusCode"])
|
175
|
+
if status_code == 200:
|
176
|
+
print('Content ', ob_info["content-type"])
|
177
|
+
filepath = self.directory.joinpath(filename)
|
178
|
+
if ob_info["content-type"] == self.ContentType:
|
179
|
+
contenttype = ob_info["content-type"]
|
180
|
+
|
181
|
+
# Usar BytesIO solo cuando es un archivo individual
|
182
|
+
if hasattr(self, '_srcfiles') and not self._srcfiles:
|
183
|
+
# Para múltiples archivos, usar streaming
|
184
|
+
async with aiofiles.open(filepath, mode="wb") as fp:
|
185
|
+
with obj["Body"] as stream:
|
186
|
+
chunk_size = 8192 # 8KB chunks
|
187
|
+
while True:
|
188
|
+
chunk = stream.read(chunk_size)
|
189
|
+
if not chunk:
|
190
|
+
break
|
191
|
+
await fp.write(chunk)
|
192
|
+
result = {
|
193
|
+
"type": contenttype,
|
194
|
+
"file": filepath
|
195
|
+
}
|
196
|
+
else:
|
197
|
+
# Para un archivo individual, mantener BytesIO
|
198
|
+
data = None
|
199
|
+
with obj["Body"] as stream:
|
200
|
+
data = stream.read()
|
201
|
+
output = BytesIO()
|
202
|
+
output.write(data)
|
203
|
+
output.seek(0)
|
204
|
+
result = {"type": contenttype, "data": output, "file": filepath}
|
205
|
+
# then save it into directory
|
206
|
+
await self.save_attachment(filepath, data)
|
207
|
+
else:
|
208
|
+
return FileError(
|
209
|
+
f'S3: Wrong File type: {ob_info["content-type"]!s}'
|
210
|
+
)
|
211
|
+
else:
|
212
|
+
return FileNotFound(
|
213
|
+
f"S3: File {filename} was not found: {rsp!s}"
|
214
|
+
)
|
215
|
+
return result
|
216
|
+
|
217
|
+
async def save_attachment(self, filepath, content):
|
218
|
+
try:
|
219
|
+
self._logger.info(f"S3: Saving attachment file: {filepath}")
|
220
|
+
if filepath.exists() is True:
|
221
|
+
if (
|
222
|
+
"replace" in self.destination and self.destination["replace"] is True
|
223
|
+
):
|
224
|
+
# overwrite only if replace is True
|
225
|
+
async with aiofiles.open(filepath, mode="wb") as fp:
|
226
|
+
await fp.write(content)
|
227
|
+
else:
|
228
|
+
self._logger.warning(
|
229
|
+
f"S3: File {filepath!s} was not saved, already exists."
|
230
|
+
)
|
231
|
+
else:
|
232
|
+
# saving file:
|
233
|
+
async with aiofiles.open(filepath, mode="wb") as fp:
|
234
|
+
await fp.write(content)
|
235
|
+
except Exception as err:
|
236
|
+
raise FileError(f"File {filepath} was not saved: {err}") from err
|
237
|
+
|
238
|
+
async def close(self, **kwargs):
|
239
|
+
self._connection = None
|
240
|
+
|
241
|
+
async def _list_objects_with_pagination(self, kwargs: dict) -> List:
|
242
|
+
"""Helper method to handle S3 pagination"""
|
243
|
+
all_objects = []
|
244
|
+
while True:
|
245
|
+
response = self._connection.list_objects_v2(**kwargs)
|
246
|
+
if response["KeyCount"] == 0:
|
247
|
+
if not all_objects: # Solo lanzar error si no hay objetos
|
248
|
+
raise FileNotFound(
|
249
|
+
f"S3 Bucket Error: Content not found on {self.bucket}"
|
250
|
+
)
|
251
|
+
break
|
252
|
+
|
253
|
+
all_objects.extend(response.get("Contents", []))
|
254
|
+
|
255
|
+
if not response.get("IsTruncated"): # No hay más resultados
|
256
|
+
break
|
257
|
+
|
258
|
+
kwargs["ContinuationToken"] = response["NextContinuationToken"]
|
259
|
+
|
260
|
+
return all_objects
|
261
|
+
|
262
|
+
async def s3_list(self, suffix: str = "") -> List:
|
263
|
+
kwargs = {
|
264
|
+
"Bucket": self.bucket,
|
265
|
+
"Delimiter": "/",
|
266
|
+
"Prefix": self.source_dir,
|
267
|
+
"MaxKeys": 1000
|
268
|
+
}
|
269
|
+
prefix = self.source_dir
|
270
|
+
files = []
|
271
|
+
_patterns = []
|
272
|
+
|
273
|
+
if not self._srcfiles:
|
274
|
+
_patterns.append(re.compile(f"^{self.source_dir}.{suffix}+$"))
|
275
|
+
# List objects in the S3 bucket with the specified prefix
|
276
|
+
objects = await self._list_objects_with_pagination(kwargs)
|
277
|
+
|
278
|
+
for obj in objects:
|
279
|
+
key = obj["Key"]
|
280
|
+
if obj["Size"] == 0:
|
281
|
+
# is a directory
|
282
|
+
continue
|
283
|
+
if suffix is not None:
|
284
|
+
if key.startswith(prefix) and re.match(
|
285
|
+
prefix + suffix, key
|
286
|
+
):
|
287
|
+
files.append(obj)
|
288
|
+
else:
|
289
|
+
try:
|
290
|
+
for pat in _patterns:
|
291
|
+
mt = pat.match(key)
|
292
|
+
if mt:
|
293
|
+
files.append(obj)
|
294
|
+
except Exception as e:
|
295
|
+
self._logger.exception(e, stack_info=True)
|
296
|
+
|
297
|
+
if self._srcfiles:
|
298
|
+
for file in self._srcfiles:
|
299
|
+
_patterns.append(re.compile(f"^{self.source_dir}.{file}+$"))
|
300
|
+
objects = await self._list_objects_with_pagination(kwargs)
|
301
|
+
|
302
|
+
for obj in objects:
|
303
|
+
key = obj["Key"]
|
304
|
+
if obj["Size"] == 0:
|
305
|
+
continue
|
306
|
+
try:
|
307
|
+
if hasattr(self, "source") and "filename" in self.source:
|
308
|
+
if self.source["filename"] == key:
|
309
|
+
files.append(obj)
|
310
|
+
except (KeyError, AttributeError):
|
311
|
+
pass
|
312
|
+
if suffix is not None:
|
313
|
+
if key.startswith(prefix) and re.match(
|
314
|
+
prefix + suffix, key
|
315
|
+
):
|
316
|
+
files.append(obj)
|
317
|
+
else:
|
318
|
+
try:
|
319
|
+
for pat in _patterns:
|
320
|
+
mt = pat.match(key)
|
321
|
+
if mt:
|
322
|
+
files.append(obj)
|
323
|
+
except Exception as e:
|
324
|
+
self._logger.exception(e, stack_info=True)
|
325
|
+
|
326
|
+
return files
|
@@ -0,0 +1,173 @@
|
|
1
|
+
from abc import ABC
|
2
|
+
from typing import Union
|
3
|
+
from pathlib import Path
|
4
|
+
import asyncio
|
5
|
+
import aiofiles
|
6
|
+
import dropbox
|
7
|
+
from dropbox.exceptions import ApiError
|
8
|
+
from dropbox.files import WriteMode
|
9
|
+
from ..exceptions import ComponentError
|
10
|
+
|
11
|
+
|
12
|
+
class DropboxClient(ABC):
|
13
|
+
"""
|
14
|
+
Dropbox Client for downloading and uploading files and folders to/from Dropbox.
|
15
|
+
"""
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
*args,
|
19
|
+
access_key: Union[str, dict] = None,
|
20
|
+
**kwargs
|
21
|
+
):
|
22
|
+
self.access_key: str = access_key
|
23
|
+
self.dbx = dropbox.Dropbox(self.access_key)
|
24
|
+
super().__init__(*args, **kwargs)
|
25
|
+
|
26
|
+
async def download_file(
|
27
|
+
self,
|
28
|
+
source_filename: str,
|
29
|
+
destination_dir: Union[str, Path] = "."
|
30
|
+
) -> Path:
|
31
|
+
"""
|
32
|
+
Download a file from Dropbox by its name.
|
33
|
+
"""
|
34
|
+
try:
|
35
|
+
search_result = await asyncio.to_thread(
|
36
|
+
self.dbx.files_search_v2,
|
37
|
+
query=source_filename
|
38
|
+
)
|
39
|
+
if not search_result.matches:
|
40
|
+
raise ComponentError(
|
41
|
+
f"File '{source_filename}' not found in Dropbox."
|
42
|
+
)
|
43
|
+
|
44
|
+
file_path = Path(destination_dir) / source_filename
|
45
|
+
file_metadata = search_result.matches[0].metadata
|
46
|
+
file_id = file_metadata.metadata.id
|
47
|
+
|
48
|
+
async with aiofiles.open(file_path, 'wb') as f:
|
49
|
+
_, res = await asyncio.to_thread(self.dbx.files_download, file_id)
|
50
|
+
await f.write(res.content)
|
51
|
+
|
52
|
+
return file_path
|
53
|
+
|
54
|
+
except ApiError as error:
|
55
|
+
raise ComponentError(
|
56
|
+
f"Error downloading file from Dropbox: {error}"
|
57
|
+
)
|
58
|
+
|
59
|
+
async def download_folder(
|
60
|
+
self,
|
61
|
+
folder_name: str,
|
62
|
+
destination_dir: Union[str, Path] = "."
|
63
|
+
) -> None:
|
64
|
+
"""
|
65
|
+
Download all files within a specified Dropbox folder by name.
|
66
|
+
"""
|
67
|
+
try:
|
68
|
+
search_result = await asyncio.to_thread(
|
69
|
+
self.dbx.files_search_v2,
|
70
|
+
query=folder_name
|
71
|
+
)
|
72
|
+
if not search_result.matches:
|
73
|
+
raise ComponentError(
|
74
|
+
f"Folder '{folder_name}' not found in Dropbox."
|
75
|
+
)
|
76
|
+
|
77
|
+
folder_metadata = search_result.matches[0].metadata
|
78
|
+
folder_id = folder_metadata.metadata.id
|
79
|
+
destination_dir = Path(destination_dir) / folder_name
|
80
|
+
destination_dir.mkdir(parents=True, exist_ok=True)
|
81
|
+
|
82
|
+
entries = await asyncio.to_thread(
|
83
|
+
self.dbx.files_list_folder,
|
84
|
+
folder_id
|
85
|
+
)
|
86
|
+
|
87
|
+
for entry in entries.entries:
|
88
|
+
if isinstance(entry, dropbox.files.FileMetadata):
|
89
|
+
file_name = entry.name
|
90
|
+
file_path = destination_dir / file_name
|
91
|
+
|
92
|
+
async with aiofiles.open(file_path, 'wb') as f:
|
93
|
+
_, res = await asyncio.to_thread(self.dbx.files_download, entry.id)
|
94
|
+
await f.write(res.content)
|
95
|
+
|
96
|
+
print(
|
97
|
+
f"Downloaded '{file_name}' to '{file_path}'."
|
98
|
+
)
|
99
|
+
|
100
|
+
except ApiError as error:
|
101
|
+
raise ComponentError(
|
102
|
+
f"Error downloading folder '{folder_name}' from Dropbox: {error}"
|
103
|
+
)
|
104
|
+
|
105
|
+
async def upload_file(
|
106
|
+
self,
|
107
|
+
source_filepath: Union[str, Path],
|
108
|
+
destination_path: Union[str, Path] = "/"
|
109
|
+
) -> None:
|
110
|
+
"""
|
111
|
+
Upload a file to Dropbox.
|
112
|
+
"""
|
113
|
+
source_filepath = Path(source_filepath)
|
114
|
+
if not source_filepath.exists() or not source_filepath.is_file():
|
115
|
+
raise ComponentError(
|
116
|
+
f"Source file '{source_filepath}' does not exist."
|
117
|
+
)
|
118
|
+
|
119
|
+
try:
|
120
|
+
async with aiofiles.open(source_filepath, 'rb') as f:
|
121
|
+
file_data = await f.read()
|
122
|
+
destination_path = str(Path(destination_path) / source_filepath.name)
|
123
|
+
|
124
|
+
await asyncio.to_thread(
|
125
|
+
self.dbx.files_upload,
|
126
|
+
file_data,
|
127
|
+
destination_path,
|
128
|
+
mode=WriteMode.overwrite
|
129
|
+
)
|
130
|
+
print(
|
131
|
+
f"Uploaded file '{source_filepath.name}' to '{destination_path}'."
|
132
|
+
)
|
133
|
+
|
134
|
+
except ApiError as error:
|
135
|
+
raise ComponentError(
|
136
|
+
f"Error uploading file '{source_filepath}': {error}"
|
137
|
+
)
|
138
|
+
|
139
|
+
async def upload_folder(
|
140
|
+
self,
|
141
|
+
source_dir: Union[str, Path],
|
142
|
+
destination_path: Union[str, Path] = "/"
|
143
|
+
) -> None:
|
144
|
+
"""
|
145
|
+
Upload all files within a specified local folder to Dropbox.
|
146
|
+
"""
|
147
|
+
source_dir = Path(source_dir)
|
148
|
+
if not source_dir.exists() or not source_dir.is_dir():
|
149
|
+
raise ComponentError(
|
150
|
+
f"Source directory '{source_dir}' does not exist."
|
151
|
+
)
|
152
|
+
|
153
|
+
for file_path in source_dir.rglob('*'):
|
154
|
+
if file_path.is_file():
|
155
|
+
# Preserve directory structure within Dropbox
|
156
|
+
relative_path = file_path.relative_to(source_dir)
|
157
|
+
dropbox_path = str(Path(destination_path) / relative_path)
|
158
|
+
|
159
|
+
try:
|
160
|
+
async with aiofiles.open(file_path, 'rb') as f:
|
161
|
+
file_data = await f.read()
|
162
|
+
await asyncio.to_thread(
|
163
|
+
self.dbx.files_upload,
|
164
|
+
file_data,
|
165
|
+
dropbox_path,
|
166
|
+
mode=WriteMode.overwrite
|
167
|
+
)
|
168
|
+
print(f"Uploaded '{file_path}' to '{dropbox_path}'.")
|
169
|
+
|
170
|
+
except ApiError as error:
|
171
|
+
raise ComponentError(
|
172
|
+
f"Error uploading file '{file_path}': {error}"
|
173
|
+
)
|
@@ -0,0 +1,94 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Callable
|
3
|
+
import pandas as pd
|
4
|
+
from openpyxl import load_workbook, Workbook
|
5
|
+
from openpyxl.utils.dataframe import dataframe_to_rows
|
6
|
+
from openpyxl.workbook import Workbook as OpenpyxlWorkbook
|
7
|
+
|
8
|
+
|
9
|
+
class ExcelHandler:
|
10
|
+
def pandas_to_excel(self, df: pd.DataFrame, file_path: Path, **kwargs):
|
11
|
+
"""
|
12
|
+
Save a pandas DataFrame to an Excel file.
|
13
|
+
|
14
|
+
:param df: DataFrame to save.
|
15
|
+
:param file_path: Path to save the Excel file.
|
16
|
+
:param kwargs: Additional keyword arguments for pandas' to_excel method.
|
17
|
+
"""
|
18
|
+
df.to_excel(file_path, index=False, **kwargs)
|
19
|
+
|
20
|
+
def edit_excel(self, file_path: Path, edit_func: Callable[[OpenpyxlWorkbook], None]):
|
21
|
+
"""
|
22
|
+
Edit an existing Excel file using a provided function.
|
23
|
+
|
24
|
+
:param file_path: Path to the Excel file.
|
25
|
+
:param edit_func: Function that takes a Workbook object and edits it.
|
26
|
+
"""
|
27
|
+
if not file_path.exists():
|
28
|
+
raise FileNotFoundError(f"The file {file_path} does not exist.")
|
29
|
+
wb = load_workbook(file_path)
|
30
|
+
edit_func(wb)
|
31
|
+
wb.save(file_path)
|
32
|
+
|
33
|
+
def append_data_to_excel(
|
34
|
+
self,
|
35
|
+
df: pd.DataFrame,
|
36
|
+
file_path: Path,
|
37
|
+
sheet_name: str,
|
38
|
+
start_row: int = None,
|
39
|
+
start_col: int = 1,
|
40
|
+
include_header: bool = False
|
41
|
+
):
|
42
|
+
"""
|
43
|
+
Append data from a DataFrame to an existing Excel file.
|
44
|
+
|
45
|
+
:param df: DataFrame containing data to append.
|
46
|
+
:param file_path: Path to the Excel file.
|
47
|
+
:param sheet_name: Name of the sheet to append data to.
|
48
|
+
:param start_row: Starting row to append data (default is after last row).
|
49
|
+
:param start_col: Starting column to append data.
|
50
|
+
:param include_header: Whether to include the DataFrame's header.
|
51
|
+
"""
|
52
|
+
if not file_path.exists():
|
53
|
+
raise FileNotFoundError(f"The file {file_path} does not exist.")
|
54
|
+
wb = load_workbook(file_path)
|
55
|
+
if sheet_name not in wb.sheetnames:
|
56
|
+
ws = wb.create_sheet(title=sheet_name)
|
57
|
+
start_row = 1
|
58
|
+
else:
|
59
|
+
ws = wb[sheet_name]
|
60
|
+
if start_row is None:
|
61
|
+
start_row = ws.max_row + 1
|
62
|
+
|
63
|
+
for r_idx, row in enumerate(
|
64
|
+
dataframe_to_rows(df, index=False, header=include_header), start=start_row
|
65
|
+
):
|
66
|
+
for c_idx, value in enumerate(row, start=start_col):
|
67
|
+
ws.cell(row=r_idx, column=c_idx, value=value)
|
68
|
+
|
69
|
+
wb.save(file_path)
|
70
|
+
|
71
|
+
def create_new_excel_file(self, file_path: Path):
|
72
|
+
"""
|
73
|
+
Create a new Excel file.
|
74
|
+
|
75
|
+
:param file_path: Path where the new Excel file will be saved.
|
76
|
+
"""
|
77
|
+
if file_path.exists():
|
78
|
+
raise FileExistsError(f"The file {file_path} already exists.")
|
79
|
+
wb = Workbook()
|
80
|
+
wb.save(file_path)
|
81
|
+
|
82
|
+
def create_excel_from_template(self, template_path: Path, new_file_path: Path):
|
83
|
+
"""
|
84
|
+
Create a new Excel file from a template.
|
85
|
+
|
86
|
+
:param template_path: Path to the Excel template.
|
87
|
+
:param new_file_path: Path where the new Excel file will be saved.
|
88
|
+
"""
|
89
|
+
if not template_path.exists():
|
90
|
+
raise FileNotFoundError(f"The template {template_path} does not exist.")
|
91
|
+
if new_file_path.exists():
|
92
|
+
raise FileExistsError(f"The file {new_file_path} already exists.")
|
93
|
+
wb = load_workbook(template_path)
|
94
|
+
wb.save(new_file_path)
|