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,220 @@
|
|
1
|
+
from abc import abstractmethod
|
2
|
+
from typing import Any
|
3
|
+
from collections.abc import Callable
|
4
|
+
import asyncio
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
6
|
+
import msal
|
7
|
+
from office365.graph_client import GraphClient
|
8
|
+
from office365.sharepoint.client_context import ClientContext
|
9
|
+
from office365.runtime.auth.user_credential import UserCredential
|
10
|
+
from office365.runtime.auth.client_credential import ClientCredential
|
11
|
+
from navconfig.logging import logging
|
12
|
+
from ..conf import (
|
13
|
+
SHAREPOINT_TENANT_NAME,
|
14
|
+
O365_CLIENT_ID,
|
15
|
+
O365_CLIENT_SECRET,
|
16
|
+
O365_TENANT_ID,
|
17
|
+
)
|
18
|
+
from .credentials import CredentialsInterface
|
19
|
+
|
20
|
+
|
21
|
+
logging.getLogger('msal').setLevel(logging.INFO)
|
22
|
+
|
23
|
+
|
24
|
+
class O365Client(CredentialsInterface):
|
25
|
+
"""
|
26
|
+
O365Client
|
27
|
+
|
28
|
+
Overview
|
29
|
+
|
30
|
+
The O365Client class is an abstract base class for managing connections to Office 365 services.
|
31
|
+
It handles authentication, credential processing, and provides a method for obtaining the
|
32
|
+
Office 365 context. It uses the Office 365 Python SDK for authentication and context management.
|
33
|
+
|
34
|
+
.. table:: Properties
|
35
|
+
:widths: auto
|
36
|
+
|
37
|
+
+------------------+----------+--------------------------------------------------------------------------------------------------+
|
38
|
+
| Name | Required | Description |
|
39
|
+
+------------------+----------+--------------------------------------------------------------------------------------------------+
|
40
|
+
| url | No | The base URL for the Office 365 service. |
|
41
|
+
+------------------+----------+--------------------------------------------------------------------------------------------------+
|
42
|
+
| tenant | Yes | The tenant ID for the Office 365 service. |
|
43
|
+
+------------------+----------+--------------------------------------------------------------------------------------------------+
|
44
|
+
| site | No | The site URL for the Office 365 service. |
|
45
|
+
+------------------+----------+--------------------------------------------------------------------------------------------------+
|
46
|
+
| auth_context | Yes | The authentication context for Office 365. |
|
47
|
+
+------------------+----------+--------------------------------------------------------------------------------------------------+
|
48
|
+
| context | Yes | The context object for Office 365 operations. |
|
49
|
+
+------------------+----------+--------------------------------------------------------------------------------------------------+
|
50
|
+
| credentials | Yes | A dictionary containing the credentials for authentication. |
|
51
|
+
+------------------+----------+--------------------------------------------------------------------------------------------------+
|
52
|
+
|
53
|
+
Return
|
54
|
+
|
55
|
+
The methods in this class manage the authentication and connection setup for Office 365 services,
|
56
|
+
providing an abstract base for subclasses to implement specific service interactions.
|
57
|
+
|
58
|
+
""" # noqa
|
59
|
+
_credentials: dict = {
|
60
|
+
"username": str,
|
61
|
+
"password": str,
|
62
|
+
"client_id": str,
|
63
|
+
"client_secret": str,
|
64
|
+
"tenant": str,
|
65
|
+
"site": str,
|
66
|
+
}
|
67
|
+
|
68
|
+
def __init__(self, *args, **kwargs) -> None:
|
69
|
+
self.url: str = None
|
70
|
+
self.tenant_id: str = None
|
71
|
+
self.auth_context: Any = None
|
72
|
+
self.context: Any = None
|
73
|
+
self._access_token: str = None
|
74
|
+
self._graph_client: Callable = None
|
75
|
+
self._logger = logging.getLogger(__name__)
|
76
|
+
self._executor = ThreadPoolExecutor()
|
77
|
+
# Default credentials
|
78
|
+
self._default_tenant_id = O365_TENANT_ID
|
79
|
+
self._default_client_id = O365_CLIENT_ID
|
80
|
+
self._default_client_secret = O365_CLIENT_SECRET
|
81
|
+
self._default_tenant_name = SHAREPOINT_TENANT_NAME
|
82
|
+
super(O365Client, self).__init__(*args, **kwargs)
|
83
|
+
|
84
|
+
@abstractmethod
|
85
|
+
def get_context(self, url: str, *args):
|
86
|
+
pass
|
87
|
+
|
88
|
+
@abstractmethod
|
89
|
+
async def _start_(self, **kwargs):
|
90
|
+
pass
|
91
|
+
|
92
|
+
async def run_in_executor(self, fn, *args, **kwargs):
|
93
|
+
"""
|
94
|
+
Calling any blocking process in an executor.
|
95
|
+
"""
|
96
|
+
return await asyncio.get_event_loop().run_in_executor(
|
97
|
+
self._executor, fn, *args, **kwargs
|
98
|
+
)
|
99
|
+
|
100
|
+
def processing_credentials(self):
|
101
|
+
super().processing_credentials()
|
102
|
+
## getting Tenant and Site from credentials:
|
103
|
+
try:
|
104
|
+
self.tenant = self.credentials.get('tenant', None)
|
105
|
+
if not self.tenant:
|
106
|
+
self.tenant = self._default_tenant_name
|
107
|
+
self.site = self.credentials.get('site', None)
|
108
|
+
except KeyError as e:
|
109
|
+
raise RuntimeError(
|
110
|
+
f"Office365: Missing Tenant or Site Configuration: {e}."
|
111
|
+
) from e
|
112
|
+
|
113
|
+
def connection(self):
|
114
|
+
# calling before run:
|
115
|
+
self._start_()
|
116
|
+
# Processing The Credentials:
|
117
|
+
if hasattr(self, "credentials"):
|
118
|
+
username = self.credentials.get("username")
|
119
|
+
password = self.credentials.get("password")
|
120
|
+
client_id = self.credentials.get("client_id")
|
121
|
+
if not client_id:
|
122
|
+
client_id = self._default_client_id
|
123
|
+
client_secret = self.credentials.get("client_secret")
|
124
|
+
if not client_secret:
|
125
|
+
client_secret = self._default_client_secret
|
126
|
+
else:
|
127
|
+
# Maybe getting from default credentials?
|
128
|
+
client_id = getattr(self, 'client_id', self._default_client_id)
|
129
|
+
client_secret = getattr(self, 'client_secret', self._default_client_secret)
|
130
|
+
if not client_id or client_secret:
|
131
|
+
logging.error(
|
132
|
+
"Office365: Wrong Credentials or missing Credentials")
|
133
|
+
raise RuntimeError(
|
134
|
+
"Office365: Wrong Credentials or missing Credentials"
|
135
|
+
)
|
136
|
+
try:
|
137
|
+
if username is not None:
|
138
|
+
self.context = ClientContext(self.url).with_credentials(
|
139
|
+
UserCredential(username, password)
|
140
|
+
)
|
141
|
+
try:
|
142
|
+
token = self.user_auth(username, password)
|
143
|
+
self._access_token = token.get('access_token')
|
144
|
+
except Exception as err:
|
145
|
+
self._logger.warning(
|
146
|
+
f"Office365: Authentication Error: {err}"
|
147
|
+
)
|
148
|
+
else:
|
149
|
+
self.auth_context = ClientCredential(client_id, client_secret)
|
150
|
+
self.context = self.get_context(self.url).with_credentials(
|
151
|
+
self.auth_context
|
152
|
+
)
|
153
|
+
token = self.acquire_token()
|
154
|
+
self._access_token = token.get('access_token')
|
155
|
+
# Create Graph client
|
156
|
+
self._graph_client = GraphClient(
|
157
|
+
acquire_token_callback=lambda: self.access_token
|
158
|
+
)
|
159
|
+
logging.debug("Office365: Authentication success")
|
160
|
+
except Exception as err:
|
161
|
+
logging.error(f"Office365: Authentication Error: {err}")
|
162
|
+
raise RuntimeError(f"Office365: Authentication Error: {err}") from err
|
163
|
+
return self
|
164
|
+
|
165
|
+
def user_auth(self, username: str, password: str, scopes: list = None) -> dict:
|
166
|
+
tenant_id = self.credentials.get('tenant_id', self._default_tenant_id)
|
167
|
+
authority_url = f'https://login.microsoftonline.com/{tenant_id}'
|
168
|
+
client_id = self.credentials.get("client_id")
|
169
|
+
if not client_id:
|
170
|
+
client_id = self._default_client_id
|
171
|
+
|
172
|
+
if not scopes:
|
173
|
+
scopes = ["https://graph.microsoft.com/.default"]
|
174
|
+
app = msal.PublicClientApplication(
|
175
|
+
authority=authority_url,
|
176
|
+
client_id=client_id,
|
177
|
+
client_credential=None
|
178
|
+
)
|
179
|
+
result = app.acquire_token_by_username_password(
|
180
|
+
username,
|
181
|
+
password,
|
182
|
+
scopes=scopes
|
183
|
+
)
|
184
|
+
if "access_token" not in result:
|
185
|
+
error_message = result.get('error_description', 'Unknown error')
|
186
|
+
error_code = result.get('error', 'Unknown error code')
|
187
|
+
raise RuntimeError(
|
188
|
+
f"Failed to obtain access token: {error_code} - {error_message}"
|
189
|
+
)
|
190
|
+
return result
|
191
|
+
|
192
|
+
def acquire_token(self, scopes: list = None) -> dict:
|
193
|
+
"""
|
194
|
+
Acquire a Token via MSAL.
|
195
|
+
"""
|
196
|
+
client_id = self.credentials.get("client_id")
|
197
|
+
if not client_id:
|
198
|
+
client_id = self._default_client_id
|
199
|
+
client_secret = self.credentials.get("client_secret")
|
200
|
+
if not client_secret:
|
201
|
+
client_secret = self._default_client_secret
|
202
|
+
tenant_id = self.credentials.get('tenant_id', self._default_tenant_id)
|
203
|
+
if not scopes:
|
204
|
+
scopes = ["https://graph.microsoft.com/.default"]
|
205
|
+
authority_url = f'https://login.microsoftonline.com/{tenant_id}'
|
206
|
+
app = msal.ConfidentialClientApplication(
|
207
|
+
authority=authority_url,
|
208
|
+
client_id=client_id,
|
209
|
+
client_credential=client_secret
|
210
|
+
)
|
211
|
+
result = app.acquire_token_for_client(
|
212
|
+
scopes=scopes
|
213
|
+
)
|
214
|
+
if "access_token" not in result:
|
215
|
+
error_message = result.get('error_description', 'Unknown error')
|
216
|
+
error_code = result.get('error', 'Unknown error code')
|
217
|
+
raise RuntimeError(
|
218
|
+
f"Failed to obtain access token: {error_code} - {error_message}"
|
219
|
+
)
|
220
|
+
return result
|
@@ -0,0 +1,284 @@
|
|
1
|
+
import os
|
2
|
+
from typing import List
|
3
|
+
from collections.abc import Callable
|
4
|
+
from pathlib import Path
|
5
|
+
import pandas as pd
|
6
|
+
from io import BytesIO
|
7
|
+
from office365.graph_client import GraphClient
|
8
|
+
from office365.onedrive.driveitems.driveItem import DriveItem
|
9
|
+
from office365.onedrive.drives.drive import Drive
|
10
|
+
from ..exceptions import FileError, FileNotFound
|
11
|
+
from .O365Client import O365Client
|
12
|
+
|
13
|
+
|
14
|
+
class OneDriveClient(O365Client):
|
15
|
+
"""
|
16
|
+
OneDrive Client.
|
17
|
+
|
18
|
+
Interface for Managing connections to OneDrive resources.
|
19
|
+
|
20
|
+
Methods:
|
21
|
+
file_list: Lists files in a specified OneDrive folder.
|
22
|
+
file_search: Searches for files matching a query.
|
23
|
+
file_download: Downloads a single file by its item ID.
|
24
|
+
download_files: Downloads multiple files provided as a list of dictionaries containing file info.
|
25
|
+
folder_download: Downloads a folder and its contents recursively.
|
26
|
+
file_delete: Deletes a file or folder by its item ID.
|
27
|
+
upload_files: Uploads multiple files to a specified OneDrive folder.
|
28
|
+
upload_file: Uploads a single file to OneDrive.
|
29
|
+
upload_folder: Uploads a local folder and its contents to OneDrive recursively.
|
30
|
+
|
31
|
+
"""
|
32
|
+
|
33
|
+
def get_context(self, url: str, *args) -> Callable:
|
34
|
+
# For OneDrive, we primarily use GraphClient
|
35
|
+
if not self._graph_client:
|
36
|
+
self._graph_client = GraphClient(acquire_token=lambda: self.access_token)
|
37
|
+
return self._graph_client
|
38
|
+
|
39
|
+
def _start_(self, **kwargs):
|
40
|
+
return True
|
41
|
+
|
42
|
+
async def download_excel_file(
|
43
|
+
self,
|
44
|
+
item_id: str,
|
45
|
+
destination: Path = None,
|
46
|
+
as_pandas: bool = False
|
47
|
+
):
|
48
|
+
"""
|
49
|
+
Download an Excel file from OneDrive by item ID.
|
50
|
+
If `as_pandas` is True, return as a pandas DataFrame.
|
51
|
+
If `as_pandas` is False, save to the destination path.
|
52
|
+
"""
|
53
|
+
try:
|
54
|
+
drive = self._graph_client.me.drive
|
55
|
+
file_item = drive.items[item_id]
|
56
|
+
|
57
|
+
if as_pandas:
|
58
|
+
bytes_buffer = BytesIO()
|
59
|
+
file_item.download(bytes_buffer).execute_query()
|
60
|
+
bytes_buffer.seek(0)
|
61
|
+
df = pd.read_excel(bytes_buffer)
|
62
|
+
return df
|
63
|
+
else:
|
64
|
+
if not destination:
|
65
|
+
raise ValueError("Destination path must be provided when `as_pandas` is False.")
|
66
|
+
with open(destination, "wb") as local_file:
|
67
|
+
file_item.download(local_file).execute_query()
|
68
|
+
return str(destination)
|
69
|
+
except Exception as err:
|
70
|
+
self._logger.error(f"Error downloading Excel file {item_id}: {err}")
|
71
|
+
raise FileError(f"Error downloading Excel file {item_id}: {err}") from err
|
72
|
+
|
73
|
+
async def upload_dataframe_as_excel(
|
74
|
+
self,
|
75
|
+
df: pd.DataFrame,
|
76
|
+
file_name: str,
|
77
|
+
destination_folder: str = None
|
78
|
+
):
|
79
|
+
"""
|
80
|
+
Upload a pandas DataFrame as an Excel file to OneDrive.
|
81
|
+
"""
|
82
|
+
try:
|
83
|
+
output = BytesIO()
|
84
|
+
df.to_excel(output, index=False)
|
85
|
+
output.seek(0)
|
86
|
+
drive = self._graph_client.me.drive
|
87
|
+
if destination_folder:
|
88
|
+
item_path = f"{destination_folder}/{file_name}"
|
89
|
+
else:
|
90
|
+
item_path = file_name
|
91
|
+
target_item = drive.root.item_with_path(item_path)
|
92
|
+
target_item.upload(output).execute_query()
|
93
|
+
return {
|
94
|
+
"name": file_name,
|
95
|
+
"webUrl": target_item.web_url
|
96
|
+
}
|
97
|
+
except Exception as err:
|
98
|
+
self._logger.error(f"Error uploading DataFrame as Excel file {file_name}: {err}")
|
99
|
+
raise FileError(f"Error uploading DataFrame as Excel file {file_name}: {err}") from err
|
100
|
+
|
101
|
+
async def file_list(self, folder_path: str = None) -> List[dict]:
|
102
|
+
"""
|
103
|
+
List files in a given OneDrive folder.
|
104
|
+
"""
|
105
|
+
try:
|
106
|
+
drive = self._graph_client.me.drive
|
107
|
+
if folder_path:
|
108
|
+
folder_item = drive.root.get_by_path(folder_path)
|
109
|
+
else:
|
110
|
+
folder_item = drive.root
|
111
|
+
|
112
|
+
items = folder_item.children.get().execute_query()
|
113
|
+
file_list = []
|
114
|
+
for item in items:
|
115
|
+
file_info = {
|
116
|
+
"name": item.name,
|
117
|
+
"id": item.id,
|
118
|
+
"webUrl": item.web_url,
|
119
|
+
"isFolder": item.folder is not None
|
120
|
+
}
|
121
|
+
file_list.append(file_info)
|
122
|
+
return file_list
|
123
|
+
except Exception as err:
|
124
|
+
self._logger.error(f"Error listing files: {err}")
|
125
|
+
raise FileError(f"Error listing files: {err}") from err
|
126
|
+
|
127
|
+
async def file_search(self, search_query: str) -> List[dict]:
|
128
|
+
"""
|
129
|
+
Search for files in OneDrive matching the search query.
|
130
|
+
"""
|
131
|
+
try:
|
132
|
+
drive = self._graph_client.me.drive
|
133
|
+
items = drive.root.search(search_query).get().execute_query()
|
134
|
+
search_results = []
|
135
|
+
for item in items:
|
136
|
+
file_info = {
|
137
|
+
"name": item.name,
|
138
|
+
"id": item.id,
|
139
|
+
"webUrl": item.web_url,
|
140
|
+
"path": item.parent_reference.path,
|
141
|
+
"isFolder": item.folder is not None
|
142
|
+
}
|
143
|
+
search_results.append(file_info)
|
144
|
+
return search_results
|
145
|
+
except Exception as err:
|
146
|
+
self._logger.error(f"Error searching files: {err}")
|
147
|
+
raise FileError(f"Error searching files: {err}") from err
|
148
|
+
|
149
|
+
async def file_download(self, item_id: str, destination: Path):
|
150
|
+
"""
|
151
|
+
Download a file from OneDrive by item ID.
|
152
|
+
"""
|
153
|
+
try:
|
154
|
+
drive = self._graph_client.me.drive
|
155
|
+
file_item = drive.items[item_id]
|
156
|
+
with open(destination, "wb") as local_file:
|
157
|
+
file_item.download(local_file).execute_query()
|
158
|
+
return str(destination)
|
159
|
+
except Exception as err:
|
160
|
+
self._logger.error(f"Error downloading file {item_id}: {err}")
|
161
|
+
raise FileError(f"Error downloading file {item_id}: {err}") from err
|
162
|
+
|
163
|
+
async def download_files(self, items: List[dict], destination_folder: Path):
|
164
|
+
"""
|
165
|
+
Download multiple files from OneDrive.
|
166
|
+
"""
|
167
|
+
downloaded_files = []
|
168
|
+
for item in items:
|
169
|
+
item_id = item.get("id")
|
170
|
+
file_name = item.get("name")
|
171
|
+
destination = destination_folder / file_name
|
172
|
+
await self.file_download(item_id, destination)
|
173
|
+
downloaded_files.append(str(destination))
|
174
|
+
return downloaded_files
|
175
|
+
|
176
|
+
async def folder_download(self, folder_id: str, destination_folder: Path):
|
177
|
+
"""
|
178
|
+
Download a folder and its contents from OneDrive.
|
179
|
+
"""
|
180
|
+
try:
|
181
|
+
drive = self._graph_client.me.drive
|
182
|
+
folder_item = drive.items[folder_id]
|
183
|
+
await self._download_folder_recursive(folder_item, destination_folder)
|
184
|
+
return True
|
185
|
+
except Exception as err:
|
186
|
+
self._logger.error(f"Error downloading folder {folder_id}: {err}")
|
187
|
+
raise FileError(f"Error downloading folder {folder_id}: {err}") from err
|
188
|
+
|
189
|
+
async def _download_folder_recursive(self, folder_item: DriveItem, local_path: Path):
|
190
|
+
"""
|
191
|
+
Recursively download a folder's contents.
|
192
|
+
"""
|
193
|
+
if not local_path.exists():
|
194
|
+
local_path.mkdir(parents=True)
|
195
|
+
items = folder_item.children.get().execute_query()
|
196
|
+
for item in items:
|
197
|
+
item_path = local_path / item.name
|
198
|
+
if item.folder:
|
199
|
+
await self._download_folder_recursive(item, item_path)
|
200
|
+
else:
|
201
|
+
await self.file_download(item.id, item_path)
|
202
|
+
|
203
|
+
async def file_delete(self, item_id: str):
|
204
|
+
"""
|
205
|
+
Delete a file or folder in OneDrive by item ID.
|
206
|
+
"""
|
207
|
+
try:
|
208
|
+
drive = self._graph_client.me.drive
|
209
|
+
item = drive.items[item_id]
|
210
|
+
item.delete_object().execute_query()
|
211
|
+
return True
|
212
|
+
except Exception as err:
|
213
|
+
self._logger.error(f"Error deleting item {item_id}: {err}")
|
214
|
+
raise FileError(f"Error deleting item {item_id}: {err}") from err
|
215
|
+
|
216
|
+
async def upload_files(self, files: List[Path], destination_folder: str = None):
|
217
|
+
"""
|
218
|
+
Upload multiple files to OneDrive.
|
219
|
+
"""
|
220
|
+
uploaded_files = []
|
221
|
+
for file_path in files:
|
222
|
+
file_name = file_path.name
|
223
|
+
uploaded_item = await self.upload_file(file_path, destination_folder)
|
224
|
+
uploaded_files.append(uploaded_item)
|
225
|
+
return uploaded_files
|
226
|
+
|
227
|
+
async def upload_file(self, file_path: Path, destination_folder: str = None):
|
228
|
+
"""
|
229
|
+
Upload a single file to OneDrive.
|
230
|
+
"""
|
231
|
+
try:
|
232
|
+
drive = self._graph_client.me.drive
|
233
|
+
if destination_folder:
|
234
|
+
target_folder = drive.root.get_by_path(destination_folder)
|
235
|
+
else:
|
236
|
+
target_folder = drive.root
|
237
|
+
with open(file_path, "rb") as content_file:
|
238
|
+
file_content = content_file.read()
|
239
|
+
uploaded_item = target_folder.children[file_path.name].upload(file_content).execute_query()
|
240
|
+
return {
|
241
|
+
"name": uploaded_item.name,
|
242
|
+
"id": uploaded_item.id,
|
243
|
+
"webUrl": uploaded_item.web_url
|
244
|
+
}
|
245
|
+
except Exception as err:
|
246
|
+
self._logger.error(f"Error uploading file {file_path}: {err}")
|
247
|
+
raise FileError(f"Error uploading file {file_path}: {err}") from err
|
248
|
+
|
249
|
+
async def upload_folder(self, local_folder: Path, destination_folder: str = None):
|
250
|
+
"""
|
251
|
+
Upload a local folder and its contents to OneDrive.
|
252
|
+
"""
|
253
|
+
uploaded_items = []
|
254
|
+
for root, dirs, files in os.walk(local_folder):
|
255
|
+
relative_path = Path(root).relative_to(local_folder)
|
256
|
+
one_drive_path = f"{destination_folder}/{relative_path}".strip("/") if destination_folder else str(relative_path) # noqa
|
257
|
+
# Create folder in OneDrive if it doesn't exist
|
258
|
+
await self._create_onedrive_folder(one_drive_path)
|
259
|
+
for file_name in files:
|
260
|
+
file_path = Path(root) / file_name
|
261
|
+
destination_path = f"{one_drive_path}/{file_name}".strip("/")
|
262
|
+
uploaded_item = await self.upload_file(file_path, destination_path)
|
263
|
+
uploaded_items.append(uploaded_item)
|
264
|
+
return uploaded_items
|
265
|
+
|
266
|
+
async def _create_onedrive_folder(self, folder_path: str):
|
267
|
+
"""
|
268
|
+
Create a folder in OneDrive if it doesn't exist.
|
269
|
+
"""
|
270
|
+
try:
|
271
|
+
drive = self._graph_client.me.drive
|
272
|
+
# Try to get the folder; if it doesn't exist, create it
|
273
|
+
folder_item = drive.root.get_by_path(folder_path)
|
274
|
+
folder_item.get().execute_query()
|
275
|
+
except Exception:
|
276
|
+
# Folder does not exist; create it
|
277
|
+
parent_path = "/".join(folder_path.split("/")[:-1])
|
278
|
+
folder_name = folder_path.split("/")[-1]
|
279
|
+
if parent_path:
|
280
|
+
parent_folder = drive.root.get_by_path(parent_path)
|
281
|
+
else:
|
282
|
+
parent_folder = drive.root
|
283
|
+
new_folder = parent_folder.children.add_folder(folder_name).execute_query()
|
284
|
+
self._logger.info(f"Created folder: {new_folder.web_url}")
|
@@ -0,0 +1,155 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import List, Optional
|
3
|
+
from office365.graph_client import GraphClient
|
4
|
+
from office365.outlook.mail.attachments.attachment import Attachment
|
5
|
+
from office365.outlook.mail.folders.folder import MailFolder
|
6
|
+
from office365.outlook.user import OutlookUser
|
7
|
+
from pathlib import Path
|
8
|
+
from ..exceptions import FileError
|
9
|
+
from .O365Client import O365Client
|
10
|
+
|
11
|
+
|
12
|
+
class OutlookClient(O365Client):
|
13
|
+
"""
|
14
|
+
Outlook Client.
|
15
|
+
|
16
|
+
Managing connections to Outlook Mail API.
|
17
|
+
"""
|
18
|
+
|
19
|
+
def get_context(self, url: str = None, *args) -> GraphClient:
|
20
|
+
# For Outlook, we primarily use GraphClient
|
21
|
+
if not self._graph_client:
|
22
|
+
self._graph_client = GraphClient(acquire_token=lambda: self.access_token)
|
23
|
+
return self._graph_client
|
24
|
+
|
25
|
+
def _start_(self, **kwargs):
|
26
|
+
return True
|
27
|
+
|
28
|
+
async def list_messages(
|
29
|
+
self,
|
30
|
+
folder: str = "Inbox",
|
31
|
+
top: int = 10,
|
32
|
+
filter_query: str = None,
|
33
|
+
select_fields: List[str] = None,
|
34
|
+
) -> List[dict]:
|
35
|
+
"""
|
36
|
+
List messages in a specified folder.
|
37
|
+
"""
|
38
|
+
try:
|
39
|
+
messages = []
|
40
|
+
mail_folder = self._graph_client.me.mail_folders[folder]
|
41
|
+
query = mail_folder.messages.top(top)
|
42
|
+
if filter_query:
|
43
|
+
query = query.filter(filter_query)
|
44
|
+
if select_fields:
|
45
|
+
query = query.select(select_fields)
|
46
|
+
msg_pages = query.get_paged()
|
47
|
+
while True:
|
48
|
+
for message in msg_pages:
|
49
|
+
messages.append({
|
50
|
+
"id": message.id,
|
51
|
+
"subject": message.subject,
|
52
|
+
"sender": message.sender.email_address.address,
|
53
|
+
"receivedDateTime": message.received_date_time,
|
54
|
+
})
|
55
|
+
if not msg_pages.has_next:
|
56
|
+
break
|
57
|
+
msg_pages = msg_pages.get_next()
|
58
|
+
return messages
|
59
|
+
except Exception as err:
|
60
|
+
self._logger.error(f"Error listing messages: {err}")
|
61
|
+
raise FileError(f"Error listing messages: {err}") from err
|
62
|
+
|
63
|
+
async def download_message(self, message_id: str, destination: Path):
|
64
|
+
"""
|
65
|
+
Download a message by its ID.
|
66
|
+
"""
|
67
|
+
try:
|
68
|
+
message = self._graph_client.me.messages[message_id].get().execute_query()
|
69
|
+
eml_content = message.mime_content
|
70
|
+
with open(destination, "wb") as file:
|
71
|
+
file.write(eml_content)
|
72
|
+
return str(destination)
|
73
|
+
except Exception as err:
|
74
|
+
self._logger.error(f"Error downloading message {message_id}: {err}")
|
75
|
+
raise FileError(f"Error downloading message {message_id}: {err}") from err
|
76
|
+
|
77
|
+
async def move_message(self, message_id: str, destination_folder_id: str):
|
78
|
+
"""
|
79
|
+
Move a message to a different folder.
|
80
|
+
"""
|
81
|
+
try:
|
82
|
+
message = self._graph_client.me.messages[message_id]
|
83
|
+
moved_message = message.move(destination_folder_id).execute_query()
|
84
|
+
return {
|
85
|
+
"id": moved_message.id,
|
86
|
+
"subject": moved_message.subject,
|
87
|
+
"folderId": destination_folder_id,
|
88
|
+
}
|
89
|
+
except Exception as err:
|
90
|
+
self._logger.error(f"Error moving message {message_id}: {err}")
|
91
|
+
raise FileError(f"Error moving message {message_id}: {err}") from err
|
92
|
+
|
93
|
+
async def search_messages(self, search_query: str, top: int = 10) -> List[dict]:
|
94
|
+
"""
|
95
|
+
Search for messages matching the search query.
|
96
|
+
"""
|
97
|
+
try:
|
98
|
+
messages = []
|
99
|
+
query = self._graph_client.me.messages.search(search_query).top(top)
|
100
|
+
msg_pages = query.get_paged()
|
101
|
+
while True:
|
102
|
+
for message in msg_pages:
|
103
|
+
messages.append({
|
104
|
+
"id": message.id,
|
105
|
+
"subject": message.subject,
|
106
|
+
"sender": message.sender.email_address.address,
|
107
|
+
"receivedDateTime": message.received_date_time,
|
108
|
+
})
|
109
|
+
if not msg_pages.has_next:
|
110
|
+
break
|
111
|
+
msg_pages = msg_pages.get_next()
|
112
|
+
return messages
|
113
|
+
except Exception as err:
|
114
|
+
self._logger.error(f"Error searching messages: {err}")
|
115
|
+
raise FileError(f"Error searching messages: {err}") from err
|
116
|
+
|
117
|
+
async def send_message(
|
118
|
+
self,
|
119
|
+
subject: str,
|
120
|
+
body: str,
|
121
|
+
to_recipients: List[str],
|
122
|
+
cc_recipients: Optional[List[str]] = None,
|
123
|
+
bcc_recipients: Optional[List[str]] = None,
|
124
|
+
attachments: Optional[List[Path]] = None,
|
125
|
+
from_address: Optional[str] = None,
|
126
|
+
):
|
127
|
+
"""
|
128
|
+
Send a message with optional attachments and optional 'on behalf of' another user.
|
129
|
+
"""
|
130
|
+
try:
|
131
|
+
message = self._graph_client.me.messages.new()
|
132
|
+
message.subject = subject
|
133
|
+
message.body = {
|
134
|
+
"contentType": "HTML",
|
135
|
+
"content": body
|
136
|
+
}
|
137
|
+
message.to_recipients = [{"emailAddress": {"address": addr}} for addr in to_recipients]
|
138
|
+
if cc_recipients:
|
139
|
+
message.cc_recipients = [{"emailAddress": {"address": addr}} for addr in cc_recipients]
|
140
|
+
if bcc_recipients:
|
141
|
+
message.bcc_recipients = [{"emailAddress": {"address": addr}} for addr in bcc_recipients]
|
142
|
+
if attachments:
|
143
|
+
for attachment_path in attachments:
|
144
|
+
attachment = message.attachments.add_file_attachment(attachment_path.name)
|
145
|
+
with open(attachment_path, "rb") as file:
|
146
|
+
attachment.content_bytes = file.read()
|
147
|
+
if from_address:
|
148
|
+
# Set the 'from' address
|
149
|
+
message.from_ = {"emailAddress": {"address": from_address}}
|
150
|
+
# Send the message
|
151
|
+
message.send().execute_query()
|
152
|
+
return True
|
153
|
+
except Exception as err:
|
154
|
+
self._logger.error(f"Error sending message: {err}")
|
155
|
+
raise FileError(f"Error sending message: {err}") from err
|