flowtask 5.8.4__cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowtask/__init__.py +93 -0
- flowtask/__main__.py +38 -0
- flowtask/bots/__init__.py +6 -0
- flowtask/bots/check.py +93 -0
- flowtask/bots/codebot.py +51 -0
- flowtask/components/ASPX.py +148 -0
- flowtask/components/AddDataset.py +352 -0
- flowtask/components/Amazon.py +523 -0
- flowtask/components/AutoTask.py +314 -0
- flowtask/components/Azure.py +80 -0
- flowtask/components/AzureUsers.py +106 -0
- flowtask/components/BaseAction.py +91 -0
- flowtask/components/BaseLoop.py +198 -0
- flowtask/components/BestBuy.py +800 -0
- flowtask/components/CSVToGCS.py +120 -0
- flowtask/components/CompanyScraper/__init__.py +1 -0
- flowtask/components/CompanyScraper/parsers/__init__.py +6 -0
- flowtask/components/CompanyScraper/parsers/base.py +102 -0
- flowtask/components/CompanyScraper/parsers/explorium.py +192 -0
- flowtask/components/CompanyScraper/parsers/leadiq.py +206 -0
- flowtask/components/CompanyScraper/parsers/rocket.py +133 -0
- flowtask/components/CompanyScraper/parsers/siccode.py +109 -0
- flowtask/components/CompanyScraper/parsers/visualvisitor.py +130 -0
- flowtask/components/CompanyScraper/parsers/zoominfo.py +118 -0
- flowtask/components/CompanyScraper/scrapper.py +1054 -0
- flowtask/components/CopyTo.py +177 -0
- flowtask/components/CopyToBigQuery.py +243 -0
- flowtask/components/CopyToMongoDB.py +291 -0
- flowtask/components/CopyToPg.py +609 -0
- flowtask/components/CopyToRethink.py +207 -0
- flowtask/components/CreateGCSBucket.py +102 -0
- flowtask/components/CreateReport/CreateReport.py +228 -0
- flowtask/components/CreateReport/__init__.py +9 -0
- flowtask/components/CreateReport/charts/__init__.py +15 -0
- flowtask/components/CreateReport/charts/bar.py +51 -0
- flowtask/components/CreateReport/charts/base.py +66 -0
- flowtask/components/CreateReport/charts/pie.py +64 -0
- flowtask/components/CreateReport/utils.py +9 -0
- flowtask/components/CustomerSatisfaction.py +196 -0
- flowtask/components/DataInput.py +200 -0
- flowtask/components/DateList.py +255 -0
- flowtask/components/DbClient.py +163 -0
- flowtask/components/DialPad.py +146 -0
- flowtask/components/DocumentDBQuery.py +200 -0
- flowtask/components/DownloadFrom.py +371 -0
- flowtask/components/DownloadFromD2L.py +113 -0
- flowtask/components/DownloadFromFTP.py +181 -0
- flowtask/components/DownloadFromIMAP.py +315 -0
- flowtask/components/DownloadFromS3.py +198 -0
- flowtask/components/DownloadFromSFTP.py +265 -0
- flowtask/components/DownloadFromSharepoint.py +110 -0
- flowtask/components/DownloadFromSmartSheet.py +114 -0
- flowtask/components/DownloadS3File.py +229 -0
- flowtask/components/Dummy.py +59 -0
- flowtask/components/DuplicatePhoto.py +411 -0
- flowtask/components/EmployeeEvaluation.py +237 -0
- flowtask/components/ExecuteSQL.py +323 -0
- flowtask/components/ExtractHTML.py +178 -0
- flowtask/components/FileBase.py +178 -0
- flowtask/components/FileCopy.py +181 -0
- flowtask/components/FileDelete.py +82 -0
- flowtask/components/FileExists.py +146 -0
- flowtask/components/FileIteratorDelete.py +112 -0
- flowtask/components/FileList.py +194 -0
- flowtask/components/FileOpen.py +75 -0
- flowtask/components/FileRead.py +120 -0
- flowtask/components/FileRename.py +106 -0
- flowtask/components/FilterIf.py +284 -0
- flowtask/components/FilterRows/FilterRows.py +200 -0
- flowtask/components/FilterRows/__init__.py +10 -0
- flowtask/components/FilterRows/functions.py +4 -0
- flowtask/components/GCSToBigQuery.py +103 -0
- flowtask/components/GoogleA4.py +150 -0
- flowtask/components/GoogleGeoCoding.py +344 -0
- flowtask/components/GooglePlaces.py +315 -0
- flowtask/components/GoogleSearch.py +539 -0
- flowtask/components/HTTPClient.py +268 -0
- flowtask/components/ICIMS.py +146 -0
- flowtask/components/IF.py +179 -0
- flowtask/components/IcimsFolderCopy.py +173 -0
- flowtask/components/ImageFeatures/__init__.py +5 -0
- flowtask/components/ImageFeatures/process.py +233 -0
- flowtask/components/IteratorBase.py +251 -0
- flowtask/components/LangchainLoader/__init__.py +5 -0
- flowtask/components/LangchainLoader/loader.py +194 -0
- flowtask/components/LangchainLoader/loaders/__init__.py +22 -0
- flowtask/components/LangchainLoader/loaders/abstract.py +362 -0
- flowtask/components/LangchainLoader/loaders/basepdf.py +50 -0
- flowtask/components/LangchainLoader/loaders/docx.py +91 -0
- flowtask/components/LangchainLoader/loaders/html.py +119 -0
- flowtask/components/LangchainLoader/loaders/pdfblocks.py +146 -0
- flowtask/components/LangchainLoader/loaders/pdfmark.py +79 -0
- flowtask/components/LangchainLoader/loaders/pdftables.py +135 -0
- flowtask/components/LangchainLoader/loaders/qa.py +67 -0
- flowtask/components/LangchainLoader/loaders/txt.py +55 -0
- flowtask/components/LeadIQ.py +650 -0
- flowtask/components/Loop.py +253 -0
- flowtask/components/Lowes.py +334 -0
- flowtask/components/MS365Usage.py +156 -0
- flowtask/components/MSTeamsMessages.py +320 -0
- flowtask/components/MarketClustering.py +1051 -0
- flowtask/components/MergeFiles.py +362 -0
- flowtask/components/MilvusOutput.py +87 -0
- flowtask/components/NearByStores.py +175 -0
- flowtask/components/NetworkNinja/__init__.py +6 -0
- flowtask/components/NetworkNinja/models/__init__.py +52 -0
- flowtask/components/NetworkNinja/models/abstract.py +177 -0
- flowtask/components/NetworkNinja/models/account.py +39 -0
- flowtask/components/NetworkNinja/models/client.py +19 -0
- flowtask/components/NetworkNinja/models/district.py +14 -0
- flowtask/components/NetworkNinja/models/events.py +101 -0
- flowtask/components/NetworkNinja/models/forms.py +499 -0
- flowtask/components/NetworkNinja/models/market.py +16 -0
- flowtask/components/NetworkNinja/models/organization.py +34 -0
- flowtask/components/NetworkNinja/models/photos.py +125 -0
- flowtask/components/NetworkNinja/models/project.py +44 -0
- flowtask/components/NetworkNinja/models/region.py +28 -0
- flowtask/components/NetworkNinja/models/store.py +203 -0
- flowtask/components/NetworkNinja/models/user.py +151 -0
- flowtask/components/NetworkNinja/router.py +854 -0
- flowtask/components/Odoo.py +175 -0
- flowtask/components/OdooInjector.py +192 -0
- flowtask/components/OpenFromXML.py +126 -0
- flowtask/components/OpenWeather.py +41 -0
- flowtask/components/OpenWithBase.py +616 -0
- flowtask/components/OpenWithPandas.py +715 -0
- flowtask/components/PGPDecrypt.py +199 -0
- flowtask/components/PandasIterator.py +187 -0
- flowtask/components/PandasToFile.py +189 -0
- flowtask/components/Paradox.py +339 -0
- flowtask/components/ParamIterator.py +117 -0
- flowtask/components/ParseHTML.py +84 -0
- flowtask/components/PlacerStores.py +249 -0
- flowtask/components/Pokemon.py +507 -0
- flowtask/components/PositiveBot.py +62 -0
- flowtask/components/PowerPointSlide.py +400 -0
- flowtask/components/PrintMessage.py +127 -0
- flowtask/components/ProductCompetitors/__init__.py +5 -0
- flowtask/components/ProductCompetitors/parsers/__init__.py +7 -0
- flowtask/components/ProductCompetitors/parsers/base.py +72 -0
- flowtask/components/ProductCompetitors/parsers/bestbuy.py +86 -0
- flowtask/components/ProductCompetitors/parsers/lowes.py +103 -0
- flowtask/components/ProductCompetitors/scrapper.py +155 -0
- flowtask/components/ProductCompliant.py +169 -0
- flowtask/components/ProductInfo/__init__.py +1 -0
- flowtask/components/ProductInfo/parsers/__init__.py +5 -0
- flowtask/components/ProductInfo/parsers/base.py +83 -0
- flowtask/components/ProductInfo/parsers/brother.py +97 -0
- flowtask/components/ProductInfo/parsers/canon.py +167 -0
- flowtask/components/ProductInfo/parsers/epson.py +118 -0
- flowtask/components/ProductInfo/parsers/hp.py +131 -0
- flowtask/components/ProductInfo/parsers/samsung.py +97 -0
- flowtask/components/ProductInfo/scraper.py +319 -0
- flowtask/components/ProductPricing.py +118 -0
- flowtask/components/QS.py +261 -0
- flowtask/components/QSBase.py +201 -0
- flowtask/components/QueryIterator.py +273 -0
- flowtask/components/QueryToInsert.py +327 -0
- flowtask/components/QueryToPandas.py +432 -0
- flowtask/components/RESTClient.py +195 -0
- flowtask/components/RethinkDBQuery.py +189 -0
- flowtask/components/Rsync.py +74 -0
- flowtask/components/RunSSH.py +59 -0
- flowtask/components/RunShell.py +71 -0
- flowtask/components/SalesForce.py +20 -0
- flowtask/components/SaveImageBank/__init__.py +257 -0
- flowtask/components/SchedulingVisits.py +592 -0
- flowtask/components/ScrapPage.py +216 -0
- flowtask/components/ScrapSearch.py +79 -0
- flowtask/components/SendNotify.py +257 -0
- flowtask/components/SentimentAnalysis.py +694 -0
- flowtask/components/ServiceScrapper/__init__.py +5 -0
- flowtask/components/ServiceScrapper/parsers/__init__.py +1 -0
- flowtask/components/ServiceScrapper/parsers/base.py +94 -0
- flowtask/components/ServiceScrapper/parsers/costco.py +93 -0
- flowtask/components/ServiceScrapper/scrapper.py +199 -0
- flowtask/components/SetVariables.py +156 -0
- flowtask/components/SubTask.py +182 -0
- flowtask/components/SuiteCRM.py +48 -0
- flowtask/components/Switch.py +175 -0
- flowtask/components/TableBase.py +148 -0
- flowtask/components/TableDelete.py +312 -0
- flowtask/components/TableInput.py +143 -0
- flowtask/components/TableOutput/TableOutput.py +384 -0
- flowtask/components/TableOutput/__init__.py +3 -0
- flowtask/components/TableSchema.py +534 -0
- flowtask/components/Target.py +223 -0
- flowtask/components/ThumbnailGenerator.py +156 -0
- flowtask/components/ToPandas.py +67 -0
- flowtask/components/TransformRows/TransformRows.py +507 -0
- flowtask/components/TransformRows/__init__.py +9 -0
- flowtask/components/TransformRows/functions.py +559 -0
- flowtask/components/TransposeRows.py +176 -0
- flowtask/components/UPCDatabase.py +86 -0
- flowtask/components/UnGzip.py +171 -0
- flowtask/components/Uncompress.py +172 -0
- flowtask/components/UniqueRows.py +126 -0
- flowtask/components/Unzip.py +107 -0
- flowtask/components/UpdateOperationalVars.py +147 -0
- flowtask/components/UploadTo.py +299 -0
- flowtask/components/UploadToS3.py +136 -0
- flowtask/components/UploadToSFTP.py +160 -0
- flowtask/components/UploadToSharepoint.py +205 -0
- flowtask/components/UserFunc.py +122 -0
- flowtask/components/VivaTracker.py +140 -0
- flowtask/components/WSDLClient.py +123 -0
- flowtask/components/Wait.py +18 -0
- flowtask/components/Walmart.py +199 -0
- flowtask/components/Workplace.py +134 -0
- flowtask/components/XMLToPandas.py +267 -0
- flowtask/components/Zammad/__init__.py +41 -0
- flowtask/components/Zammad/models.py +0 -0
- flowtask/components/ZoomInfoScraper.py +409 -0
- flowtask/components/__init__.py +104 -0
- flowtask/components/abstract.py +18 -0
- flowtask/components/flow.py +530 -0
- flowtask/components/google.py +335 -0
- flowtask/components/group.py +221 -0
- flowtask/components/py.typed +0 -0
- flowtask/components/reviewscrap.py +132 -0
- flowtask/components/tAutoincrement.py +117 -0
- flowtask/components/tConcat.py +109 -0
- flowtask/components/tExplode.py +119 -0
- flowtask/components/tFilter.py +184 -0
- flowtask/components/tGroup.py +236 -0
- flowtask/components/tJoin.py +270 -0
- flowtask/components/tMap/__init__.py +9 -0
- flowtask/components/tMap/functions.py +54 -0
- flowtask/components/tMap/tMap.py +450 -0
- flowtask/components/tMelt.py +112 -0
- flowtask/components/tMerge.py +114 -0
- flowtask/components/tOrder.py +93 -0
- flowtask/components/tPandas.py +94 -0
- flowtask/components/tPivot.py +71 -0
- flowtask/components/tPluckCols.py +76 -0
- flowtask/components/tUnnest.py +82 -0
- flowtask/components/user.py +401 -0
- flowtask/conf.py +457 -0
- flowtask/download.py +102 -0
- flowtask/events/__init__.py +11 -0
- flowtask/events/events/__init__.py +20 -0
- flowtask/events/events/abstract.py +95 -0
- flowtask/events/events/alerts/__init__.py +362 -0
- flowtask/events/events/alerts/colfunctions.py +131 -0
- flowtask/events/events/alerts/functions.py +158 -0
- flowtask/events/events/dummy.py +12 -0
- flowtask/events/events/exec.py +124 -0
- flowtask/events/events/file/__init__.py +7 -0
- flowtask/events/events/file/base.py +51 -0
- flowtask/events/events/file/copy.py +23 -0
- flowtask/events/events/file/delete.py +16 -0
- flowtask/events/events/interfaces/__init__.py +9 -0
- flowtask/events/events/interfaces/client.py +67 -0
- flowtask/events/events/interfaces/credentials.py +28 -0
- flowtask/events/events/interfaces/notifications.py +58 -0
- flowtask/events/events/jira.py +122 -0
- flowtask/events/events/log.py +26 -0
- flowtask/events/events/logerr.py +52 -0
- flowtask/events/events/notify.py +59 -0
- flowtask/events/events/notify_event.py +160 -0
- flowtask/events/events/publish.py +54 -0
- flowtask/events/events/sendfile.py +104 -0
- flowtask/events/events/task.py +97 -0
- flowtask/events/events/teams.py +98 -0
- flowtask/events/events/webhook.py +58 -0
- flowtask/events/manager.py +287 -0
- flowtask/exceptions.c +39393 -0
- flowtask/exceptions.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/extensions/__init__.py +3 -0
- flowtask/extensions/abstract.py +82 -0
- flowtask/extensions/logging/__init__.py +65 -0
- flowtask/hooks/__init__.py +9 -0
- flowtask/hooks/actions/__init__.py +22 -0
- flowtask/hooks/actions/abstract.py +66 -0
- flowtask/hooks/actions/dummy.py +23 -0
- flowtask/hooks/actions/jira.py +74 -0
- flowtask/hooks/actions/rest.py +320 -0
- flowtask/hooks/actions/sampledata.py +37 -0
- flowtask/hooks/actions/sensor.py +23 -0
- flowtask/hooks/actions/task.py +9 -0
- flowtask/hooks/actions/ticket.py +37 -0
- flowtask/hooks/actions/zammad.py +55 -0
- flowtask/hooks/hook.py +62 -0
- flowtask/hooks/models.py +17 -0
- flowtask/hooks/service.py +187 -0
- flowtask/hooks/step.py +91 -0
- flowtask/hooks/types/__init__.py +23 -0
- flowtask/hooks/types/base.py +129 -0
- flowtask/hooks/types/brokers/__init__.py +11 -0
- flowtask/hooks/types/brokers/base.py +54 -0
- flowtask/hooks/types/brokers/mqtt.py +35 -0
- flowtask/hooks/types/brokers/rabbitmq.py +82 -0
- flowtask/hooks/types/brokers/redis.py +83 -0
- flowtask/hooks/types/brokers/sqs.py +44 -0
- flowtask/hooks/types/fs.py +232 -0
- flowtask/hooks/types/http.py +49 -0
- flowtask/hooks/types/imap.py +200 -0
- flowtask/hooks/types/jira.py +279 -0
- flowtask/hooks/types/mail.py +205 -0
- flowtask/hooks/types/postgres.py +98 -0
- flowtask/hooks/types/responses/__init__.py +8 -0
- flowtask/hooks/types/responses/base.py +5 -0
- flowtask/hooks/types/sharepoint.py +288 -0
- flowtask/hooks/types/ssh.py +141 -0
- flowtask/hooks/types/tagged.py +59 -0
- flowtask/hooks/types/upload.py +85 -0
- flowtask/hooks/types/watch.py +71 -0
- flowtask/hooks/types/web.py +36 -0
- flowtask/interfaces/AzureClient.py +137 -0
- flowtask/interfaces/AzureGraph.py +839 -0
- flowtask/interfaces/Boto3Client.py +326 -0
- flowtask/interfaces/DropboxClient.py +173 -0
- flowtask/interfaces/ExcelHandler.py +94 -0
- flowtask/interfaces/FTPClient.py +131 -0
- flowtask/interfaces/GoogleCalendar.py +201 -0
- flowtask/interfaces/GoogleClient.py +133 -0
- flowtask/interfaces/GoogleDrive.py +127 -0
- flowtask/interfaces/GoogleGCS.py +89 -0
- flowtask/interfaces/GoogleGeocoding.py +93 -0
- flowtask/interfaces/GoogleLang.py +114 -0
- flowtask/interfaces/GooglePub.py +61 -0
- flowtask/interfaces/GoogleSheet.py +68 -0
- flowtask/interfaces/IMAPClient.py +137 -0
- flowtask/interfaces/O365Calendar.py +113 -0
- flowtask/interfaces/O365Client.py +220 -0
- flowtask/interfaces/OneDrive.py +284 -0
- flowtask/interfaces/Outlook.py +155 -0
- flowtask/interfaces/ParrotBot.py +130 -0
- flowtask/interfaces/SSHClient.py +378 -0
- flowtask/interfaces/Sharepoint.py +496 -0
- flowtask/interfaces/__init__.py +36 -0
- flowtask/interfaces/azureauth.py +119 -0
- flowtask/interfaces/cache.py +201 -0
- flowtask/interfaces/client.py +82 -0
- flowtask/interfaces/compress.py +525 -0
- flowtask/interfaces/credentials.py +124 -0
- flowtask/interfaces/d2l.py +239 -0
- flowtask/interfaces/databases/__init__.py +5 -0
- flowtask/interfaces/databases/db.py +223 -0
- flowtask/interfaces/databases/documentdb.py +55 -0
- flowtask/interfaces/databases/rethink.py +39 -0
- flowtask/interfaces/dataframes/__init__.py +11 -0
- flowtask/interfaces/dataframes/abstract.py +21 -0
- flowtask/interfaces/dataframes/arrow.py +71 -0
- flowtask/interfaces/dataframes/dt.py +69 -0
- flowtask/interfaces/dataframes/pandas.py +167 -0
- flowtask/interfaces/dataframes/polars.py +60 -0
- flowtask/interfaces/db.py +263 -0
- flowtask/interfaces/env.py +46 -0
- flowtask/interfaces/func.py +137 -0
- flowtask/interfaces/http.py +1780 -0
- flowtask/interfaces/locale.py +40 -0
- flowtask/interfaces/log.py +75 -0
- flowtask/interfaces/mask.py +143 -0
- flowtask/interfaces/notification.py +154 -0
- flowtask/interfaces/playwright.py +339 -0
- flowtask/interfaces/powerpoint.py +368 -0
- flowtask/interfaces/py.typed +0 -0
- flowtask/interfaces/qs.py +376 -0
- flowtask/interfaces/result.py +87 -0
- flowtask/interfaces/selenium_service.py +779 -0
- flowtask/interfaces/smartsheet.py +154 -0
- flowtask/interfaces/stat.py +39 -0
- flowtask/interfaces/task.py +96 -0
- flowtask/interfaces/template.py +118 -0
- flowtask/interfaces/vectorstores/__init__.py +1 -0
- flowtask/interfaces/vectorstores/abstract.py +133 -0
- flowtask/interfaces/vectorstores/milvus.py +669 -0
- flowtask/interfaces/zammad.py +107 -0
- flowtask/models.py +193 -0
- flowtask/parsers/__init__.py +15 -0
- flowtask/parsers/_yaml.c +11978 -0
- flowtask/parsers/_yaml.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/argparser.py +235 -0
- flowtask/parsers/base.c +15155 -0
- flowtask/parsers/base.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/json.c +11968 -0
- flowtask/parsers/json.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/maps.py +49 -0
- flowtask/parsers/toml.c +11968 -0
- flowtask/parsers/toml.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/plugins/__init__.py +16 -0
- flowtask/plugins/components/__init__.py +0 -0
- flowtask/plugins/handler/__init__.py +45 -0
- flowtask/plugins/importer.py +31 -0
- flowtask/plugins/sources/__init__.py +0 -0
- flowtask/runner.py +283 -0
- flowtask/scheduler/__init__.py +9 -0
- flowtask/scheduler/functions.py +493 -0
- flowtask/scheduler/handlers/__init__.py +8 -0
- flowtask/scheduler/handlers/manager.py +504 -0
- flowtask/scheduler/handlers/models.py +58 -0
- flowtask/scheduler/handlers/service.py +72 -0
- flowtask/scheduler/notifications.py +65 -0
- flowtask/scheduler/scheduler.py +993 -0
- flowtask/services/__init__.py +0 -0
- flowtask/services/bots/__init__.py +0 -0
- flowtask/services/bots/telegram.py +264 -0
- flowtask/services/files/__init__.py +11 -0
- flowtask/services/files/manager.py +522 -0
- flowtask/services/files/model.py +37 -0
- flowtask/services/files/service.py +767 -0
- flowtask/services/jira/__init__.py +3 -0
- flowtask/services/jira/jira_actions.py +191 -0
- flowtask/services/tasks/__init__.py +13 -0
- flowtask/services/tasks/launcher.py +213 -0
- flowtask/services/tasks/manager.py +323 -0
- flowtask/services/tasks/service.py +275 -0
- flowtask/services/tasks/task_manager.py +376 -0
- flowtask/services/tasks/tasks.py +155 -0
- flowtask/storages/__init__.py +16 -0
- flowtask/storages/exceptions.py +12 -0
- flowtask/storages/files/__init__.py +8 -0
- flowtask/storages/files/abstract.py +29 -0
- flowtask/storages/files/filesystem.py +66 -0
- flowtask/storages/tasks/__init__.py +19 -0
- flowtask/storages/tasks/abstract.py +26 -0
- flowtask/storages/tasks/database.py +33 -0
- flowtask/storages/tasks/filesystem.py +108 -0
- flowtask/storages/tasks/github.py +119 -0
- flowtask/storages/tasks/memory.py +45 -0
- flowtask/storages/tasks/row.py +25 -0
- flowtask/tasks/__init__.py +0 -0
- flowtask/tasks/abstract.py +526 -0
- flowtask/tasks/command.py +118 -0
- flowtask/tasks/pile.py +486 -0
- flowtask/tasks/py.typed +0 -0
- flowtask/tasks/task.py +778 -0
- flowtask/template/__init__.py +161 -0
- flowtask/tests.py +257 -0
- flowtask/types/__init__.py +8 -0
- flowtask/types/typedefs.c +11347 -0
- flowtask/types/typedefs.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/__init__.py +24 -0
- flowtask/utils/constants.py +117 -0
- flowtask/utils/encoders.py +21 -0
- flowtask/utils/executor.py +112 -0
- flowtask/utils/functions.cpp +14280 -0
- flowtask/utils/functions.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/json.cpp +13349 -0
- flowtask/utils/json.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/mail.py +63 -0
- flowtask/utils/parseqs.c +13324 -0
- flowtask/utils/parserqs.cpython-312-x86_64-linux-gnu.so +0 -0
- flowtask/utils/stats.py +308 -0
- flowtask/utils/transformations.py +74 -0
- flowtask/utils/uv.py +12 -0
- flowtask/utils/validators.py +97 -0
- flowtask/version.py +11 -0
- flowtask-5.8.4.dist-info/LICENSE +201 -0
- flowtask-5.8.4.dist-info/METADATA +209 -0
- flowtask-5.8.4.dist-info/RECORD +470 -0
- flowtask-5.8.4.dist-info/WHEEL +6 -0
- flowtask-5.8.4.dist-info/entry_points.txt +3 -0
- flowtask-5.8.4.dist-info/top_level.txt +2 -0
- plugins/components/CreateQR.py +39 -0
- plugins/components/TestComponent.py +28 -0
- plugins/components/Use1.py +13 -0
- plugins/components/Workplace.py +117 -0
- plugins/components/__init__.py +3 -0
- plugins/sources/__init__.py +0 -0
- plugins/sources/get_populartimes.py +78 -0
- plugins/sources/google.py +150 -0
- plugins/sources/hubspot.py +679 -0
- plugins/sources/icims.py +679 -0
- plugins/sources/mobileinsight.py +501 -0
- plugins/sources/newrelic.py +262 -0
- plugins/sources/uap.py +268 -0
- plugins/sources/venu.py +244 -0
- plugins/sources/vocinity.py +314 -0
@@ -0,0 +1,504 @@
|
|
1
|
+
"""
|
2
|
+
Scheduler Manager.
|
3
|
+
|
4
|
+
API View for Managing the Scheduler.
|
5
|
+
"""
|
6
|
+
from typing import Any, TypeVar
|
7
|
+
from collections import defaultdict
|
8
|
+
from collections.abc import Callable
|
9
|
+
import asyncio
|
10
|
+
from functools import wraps
|
11
|
+
from concurrent.futures import ThreadPoolExecutor
|
12
|
+
from aiohttp import web, hdrs
|
13
|
+
from aiohttp.abc import AbstractView
|
14
|
+
from functools import partial
|
15
|
+
from datamodel import BaseModel
|
16
|
+
from navigator_session import get_session
|
17
|
+
from navigator.views import (
|
18
|
+
BaseView,
|
19
|
+
ModelView
|
20
|
+
)
|
21
|
+
from navigator.conf import AUTH_SESSION_OBJECT
|
22
|
+
from navconfig.logging import logging
|
23
|
+
from ...exceptions import FlowTaskError, NotFound
|
24
|
+
from ...conf import SCHEDULER_SERVICE_GROUPS, SCHEDULER_ADMIN_GROUPS
|
25
|
+
from .models import Job
|
26
|
+
|
27
|
+
|
28
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
29
|
+
|
30
|
+
|
31
|
+
class SchedulerManager(BaseView):
|
32
|
+
"""Scheduler Manager Facility.
|
33
|
+
|
34
|
+
get: getting Scheduler and Jobs information, for Jobs or a single job
|
35
|
+
post: editing existing jobs
|
36
|
+
put: inserting a new task into the jobstore
|
37
|
+
delete: removing (or pausing) some jobs from the scheduler
|
38
|
+
patch: reload all jobs.
|
39
|
+
"""
|
40
|
+
|
41
|
+
async def get_session(self):
|
42
|
+
self._session = None
|
43
|
+
try:
|
44
|
+
self._session = await get_session(self.request)
|
45
|
+
except (ValueError, RuntimeError) as err:
|
46
|
+
return self.critical(
|
47
|
+
response={"message": "Error Decoding Session", "error": str(err)},
|
48
|
+
exception=err,
|
49
|
+
)
|
50
|
+
if not self._session:
|
51
|
+
self.error(
|
52
|
+
response={
|
53
|
+
"error": "Unauthorized",
|
54
|
+
"message": "Hint: maybe need to login and pass Authorization token.",
|
55
|
+
},
|
56
|
+
status=403,
|
57
|
+
)
|
58
|
+
|
59
|
+
async def get_userid(self, session, idx: str = "user_id") -> int:
|
60
|
+
if not session:
|
61
|
+
self.error(response={"error": "Unauthorized"}, status=403)
|
62
|
+
try:
|
63
|
+
if AUTH_SESSION_OBJECT in session:
|
64
|
+
return session[AUTH_SESSION_OBJECT][idx]
|
65
|
+
else:
|
66
|
+
return session[idx]
|
67
|
+
except KeyError:
|
68
|
+
self.error(
|
69
|
+
response={
|
70
|
+
"error": "Unauthorized",
|
71
|
+
"message": "Hint: maybe you need to pass an Authorization token.",
|
72
|
+
},
|
73
|
+
status=403,
|
74
|
+
)
|
75
|
+
|
76
|
+
@staticmethod
|
77
|
+
def service_auth(groups: list, *args, **kwargs):
|
78
|
+
def _wrapper(fn: F) -> Any:
|
79
|
+
@wraps(fn)
|
80
|
+
async def _wrap(self, *args, **kwargs) -> web.StreamResponse:
|
81
|
+
## get User Session:
|
82
|
+
await self.get_session()
|
83
|
+
request = self.request
|
84
|
+
if request.get("authenticated", False) is False:
|
85
|
+
# check credentials:
|
86
|
+
raise web.HTTPUnauthorized(
|
87
|
+
reason="Access Denied",
|
88
|
+
headers={
|
89
|
+
hdrs.CONTENT_TYPE: "application/json",
|
90
|
+
hdrs.CONNECTION: "keep-alive",
|
91
|
+
},
|
92
|
+
)
|
93
|
+
self._userid = await self.get_userid(self._session)
|
94
|
+
self.logger.info(f"Scheduler: Authenticated User: {self._userid}")
|
95
|
+
# already a superuser?
|
96
|
+
userinfo = self._session[AUTH_SESSION_OBJECT]
|
97
|
+
if userinfo.get("superuser", False) is True:
|
98
|
+
self.logger.info(f"Audit: User {self._userid} is calling {fn!r}")
|
99
|
+
return await fn(self, *args, **kwargs)
|
100
|
+
# Checking User Permissions:
|
101
|
+
if await self._post_auth(groups, *args, **kwargs):
|
102
|
+
self.logger.info(f"Audit: User {self._userid} is calling {fn!r}")
|
103
|
+
return await fn(self, *args, **kwargs)
|
104
|
+
else:
|
105
|
+
raise web.HTTPUnauthorized(
|
106
|
+
reason="Access Denied",
|
107
|
+
headers={
|
108
|
+
hdrs.CONTENT_TYPE: "application/json",
|
109
|
+
hdrs.CONNECTION: "keep-alive",
|
110
|
+
},
|
111
|
+
)
|
112
|
+
|
113
|
+
return _wrap
|
114
|
+
|
115
|
+
return _wrapper
|
116
|
+
|
117
|
+
async def _post_auth(self, groups: list, *args, **kwargs):
|
118
|
+
"""Post-authorization Model."""
|
119
|
+
member = False
|
120
|
+
userinfo = {}
|
121
|
+
try:
|
122
|
+
userinfo = self._session[AUTH_SESSION_OBJECT]
|
123
|
+
except KeyError:
|
124
|
+
member = False
|
125
|
+
if "groups" in userinfo:
|
126
|
+
member = bool(not set(userinfo["groups"]).isdisjoint(groups))
|
127
|
+
else:
|
128
|
+
user = self._session.decode("user")
|
129
|
+
for group in user.groups:
|
130
|
+
if group.group in groups:
|
131
|
+
member = True
|
132
|
+
if member is True:
|
133
|
+
## Check Groups belong to User
|
134
|
+
return True
|
135
|
+
else:
|
136
|
+
return False
|
137
|
+
|
138
|
+
@service_auth(groups=SCHEDULER_SERVICE_GROUPS)
|
139
|
+
async def get(self):
|
140
|
+
app = self.request.app
|
141
|
+
scheduler = app["scheduler"]
|
142
|
+
args = self.match_parameters(self.request)
|
143
|
+
qs = self.query_parameters(self.request)
|
144
|
+
try:
|
145
|
+
job = args["job"]
|
146
|
+
except KeyError:
|
147
|
+
job = None
|
148
|
+
if job is None:
|
149
|
+
if qs.get("calendar"):
|
150
|
+
filter_day = qs.get("day", None)
|
151
|
+
jobs_data = []
|
152
|
+
for job in scheduler.get_all_jobs():
|
153
|
+
if job:
|
154
|
+
ts = job.next_run_time
|
155
|
+
jobs_data.append(
|
156
|
+
{
|
157
|
+
"id": job.id,
|
158
|
+
"name": job.name,
|
159
|
+
"next_run_time": job.next_run_time,
|
160
|
+
"date": ts.date(),
|
161
|
+
"hour": ts.time(),
|
162
|
+
"day": ts.strftime("%a"),
|
163
|
+
"trigger": f"{job.trigger!r}",
|
164
|
+
}
|
165
|
+
)
|
166
|
+
# Organize by day
|
167
|
+
jobs_by_day = defaultdict(list)
|
168
|
+
job_data = sorted(jobs_data, key=lambda x: x["hour"])
|
169
|
+
if filter_day:
|
170
|
+
for job in job_data:
|
171
|
+
if job["day"] == filter_day:
|
172
|
+
jobs_by_day[filter_day].append(job)
|
173
|
+
return self.json_response(response=jobs_by_day, state=200)
|
174
|
+
else:
|
175
|
+
# returning all days data:
|
176
|
+
for job in job_data:
|
177
|
+
day_key = job["day"]
|
178
|
+
jobs_by_day[day_key].append(job)
|
179
|
+
days_order = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
180
|
+
job_data = {
|
181
|
+
day: jobs_by_day[day]
|
182
|
+
for day in days_order
|
183
|
+
if day in jobs_by_day
|
184
|
+
}
|
185
|
+
return self.json_response(response=job_data, state=200)
|
186
|
+
elif qs.get("tabular"):
|
187
|
+
job_list = []
|
188
|
+
for job in scheduler.get_all_jobs():
|
189
|
+
j = scheduler.get_job(job.id)
|
190
|
+
ts = job.next_run_time
|
191
|
+
obj = {
|
192
|
+
"job_id": job.id,
|
193
|
+
"name": job.name,
|
194
|
+
"trigger": f"{job.trigger!r}",
|
195
|
+
"next_run_time": job.next_run_time,
|
196
|
+
"date": ts.date(),
|
197
|
+
"hour": ts.time(),
|
198
|
+
"day": ts.strftime("%a"),
|
199
|
+
"function": f"{job.func!r}",
|
200
|
+
"last_status": "paused"
|
201
|
+
if not job.next_run_time
|
202
|
+
else j["status"],
|
203
|
+
}
|
204
|
+
job_list.append(obj)
|
205
|
+
return self.json_response(response=job_list, state=200)
|
206
|
+
job_list = []
|
207
|
+
try:
|
208
|
+
for job in scheduler.get_all_jobs():
|
209
|
+
j = scheduler.get_job(job.id)
|
210
|
+
if j is None:
|
211
|
+
continue
|
212
|
+
obj = {}
|
213
|
+
obj[job.id] = {
|
214
|
+
"job_id": job.id,
|
215
|
+
"name": job.name,
|
216
|
+
"trigger": f"{job.trigger!r}",
|
217
|
+
"next_run_time": job.next_run_time,
|
218
|
+
"function": f"{job.func!r}",
|
219
|
+
"last_status": "paused" if not job.next_run_time else j["status"],
|
220
|
+
}
|
221
|
+
job_list.append(obj)
|
222
|
+
except Exception as err:
|
223
|
+
self.logger.exception(f"Error getting Job Scheduler info: {err!s}")
|
224
|
+
return self.error(
|
225
|
+
response={"message": f"Error getting Job Scheduler info: {err!s}"},
|
226
|
+
state=406,
|
227
|
+
)
|
228
|
+
return self.json_response(response=job_list, state=200)
|
229
|
+
else:
|
230
|
+
# getting all information about a single job.
|
231
|
+
try:
|
232
|
+
obj = scheduler.get_job(job)
|
233
|
+
if not obj:
|
234
|
+
raise NotFound(f"There is no Job {job}")
|
235
|
+
data = obj["data"]
|
236
|
+
print(obj["data"], obj["status"])
|
237
|
+
job = obj["job"]
|
238
|
+
if job.next_run_time is None:
|
239
|
+
status = "Paused"
|
240
|
+
else:
|
241
|
+
status = obj["status"]
|
242
|
+
result = {
|
243
|
+
"job_id": job.id,
|
244
|
+
"name": job.name,
|
245
|
+
"trigger": f"{job.trigger!r}",
|
246
|
+
"next_run_time": job.next_run_time,
|
247
|
+
"last_exec_time": data["last_exec_time"],
|
248
|
+
"function": f"{job.func!r}",
|
249
|
+
"last_status": status,
|
250
|
+
"last_traceback": data["job_state"],
|
251
|
+
}
|
252
|
+
print(result)
|
253
|
+
return self.json_response(response=result, state=200)
|
254
|
+
except NotFound as exc:
|
255
|
+
return self.error(response={"message": f"{exc}"}, state=400)
|
256
|
+
except Exception as err:
|
257
|
+
self.logger.exception(f"Error getting Job Scheduler info: {err!s}")
|
258
|
+
return self.error(
|
259
|
+
response={"message": f"Error getting Job Scheduler info: {err!s}"},
|
260
|
+
state=406,
|
261
|
+
)
|
262
|
+
|
263
|
+
async def put(self):
|
264
|
+
app = self.request.app
|
265
|
+
scheduler = app["scheduler"]
|
266
|
+
return self.json_response(response="Empty", state=204)
|
267
|
+
|
268
|
+
async def reload_jobs(self, scheduler):
|
269
|
+
try:
|
270
|
+
return await scheduler.create_jobs()
|
271
|
+
except Exception as err:
|
272
|
+
raise FlowTaskError(f"{err!s}") from err
|
273
|
+
|
274
|
+
@service_auth(groups=SCHEDULER_ADMIN_GROUPS)
|
275
|
+
async def patch(self):
|
276
|
+
app = self.request.app
|
277
|
+
scheduler = app["scheduler"]
|
278
|
+
args = self.match_parameters(self.request)
|
279
|
+
try:
|
280
|
+
job = args["job"]
|
281
|
+
except KeyError:
|
282
|
+
job = None
|
283
|
+
if job is None:
|
284
|
+
self.logger.info(
|
285
|
+
f"Audit: User {self._userid} are Tearing down the JobStore (removing Jobs)"
|
286
|
+
)
|
287
|
+
try:
|
288
|
+
# Teardown the JobStore (without stopping the service)
|
289
|
+
jobstore = scheduler.jobstores["default"]
|
290
|
+
# then, remove all jobs:
|
291
|
+
jobstore.remove_all_jobs()
|
292
|
+
# after that, wait and reload again:
|
293
|
+
except Exception as err:
|
294
|
+
self.logger.exception(
|
295
|
+
f"Error Teardown the Scheduler {err!r}"
|
296
|
+
)
|
297
|
+
return self.error(
|
298
|
+
response={"message": f"Error Starting Scheduler {err!r}"}, state=406
|
299
|
+
)
|
300
|
+
await asyncio.sleep(0.1)
|
301
|
+
try:
|
302
|
+
loop = scheduler.event_loop
|
303
|
+
self.logger.info("Reloading the Scheduler JobStore ...")
|
304
|
+
error = None
|
305
|
+
result = []
|
306
|
+
try:
|
307
|
+
result = await self.reload_jobs(scheduler)
|
308
|
+
# with ThreadPoolExecutor(max_workers=2) as executor:
|
309
|
+
# fn = partial(self.reload_jobs, scheduler)
|
310
|
+
# result = await loop.run_in_executor(executor, fn)
|
311
|
+
except Exception as exc:
|
312
|
+
error = exc
|
313
|
+
self.logger.error(
|
314
|
+
f"Failed to reload jobs: {exc}"
|
315
|
+
)
|
316
|
+
result = {
|
317
|
+
"status": "Done",
|
318
|
+
"description": "Scheduler was reloaded.",
|
319
|
+
"reloaded_by": self._userid,
|
320
|
+
"errors": f"{error}",
|
321
|
+
"jobs_loaded": len(result)
|
322
|
+
}
|
323
|
+
return self.json_response(response=result, state=202)
|
324
|
+
except Exception as err:
|
325
|
+
self.logger.exception(f"Error Starting Scheduler {err!r}")
|
326
|
+
return self.error(
|
327
|
+
response={"message": f"Error Starting Scheduler {err!r}"}, state=406
|
328
|
+
)
|
329
|
+
else:
|
330
|
+
try:
|
331
|
+
job_struc = scheduler.get_job(job)
|
332
|
+
job = job_struc["job"]
|
333
|
+
# getting info about will be paused or removed (TODO removed).
|
334
|
+
job.resume()
|
335
|
+
return self.json_response(
|
336
|
+
response=f"Job {job} was Resumed from Pause state.", state=202
|
337
|
+
)
|
338
|
+
except Exception as err:
|
339
|
+
self.logger.exception(f"Invalid Job Id {job!s}: {err!s}")
|
340
|
+
return self.error(
|
341
|
+
response={"error": f"Invalid Job Id {job!s}: {err!s}"}, state=406
|
342
|
+
)
|
343
|
+
|
344
|
+
@service_auth(groups=SCHEDULER_ADMIN_GROUPS)
|
345
|
+
async def delete(self):
|
346
|
+
app = self.request.app
|
347
|
+
scheduler = app["scheduler"]
|
348
|
+
args = self.match_parameters(self.request)
|
349
|
+
try:
|
350
|
+
job = args["job"]
|
351
|
+
except KeyError:
|
352
|
+
job = None
|
353
|
+
if job is None:
|
354
|
+
# TODO: shutdown the Scheduler
|
355
|
+
self.logger.info(
|
356
|
+
f"Audit: User {self._userid} is shutting down the Scheduler."
|
357
|
+
)
|
358
|
+
# first: stop the server
|
359
|
+
scheduler.scheduler.shutdown(wait=False)
|
360
|
+
# second: remove (reload) all jobs from scheduler.
|
361
|
+
for job in scheduler.get_all_jobs():
|
362
|
+
# first: remove all existing jobs from scheduler
|
363
|
+
self.logger.debug(f"Scheduler: Removing Job {job.id} from job store")
|
364
|
+
job.remove()
|
365
|
+
# after this, call again create jobs.
|
366
|
+
self.logger.info("Restarting the Scheduler...")
|
367
|
+
try:
|
368
|
+
# loop = scheduler.event_loop
|
369
|
+
error = None
|
370
|
+
result = []
|
371
|
+
try:
|
372
|
+
result = await self.reload_jobs(scheduler)
|
373
|
+
# with ThreadPoolExecutor(max_workers=2) as executor:
|
374
|
+
# fn = partial(self.reload_jobs, scheduler)
|
375
|
+
# result = await loop.run_in_executor(executor, fn)
|
376
|
+
except Exception as e:
|
377
|
+
error = e
|
378
|
+
await asyncio.sleep(0.1)
|
379
|
+
# start server again
|
380
|
+
await scheduler.start()
|
381
|
+
result = {
|
382
|
+
"status": "Done",
|
383
|
+
"description": "Scheduler was restarted.",
|
384
|
+
"restarted_by": self._userid,
|
385
|
+
"jobs_loaded": len(result),
|
386
|
+
"errors": f"{error}",
|
387
|
+
}
|
388
|
+
return self.json_response(response=result, state=202)
|
389
|
+
except Exception as err:
|
390
|
+
self.logger.exception(f"Error Starting Scheduler {err!r}")
|
391
|
+
return self.error(
|
392
|
+
response={"message": f"Error Starting Scheduler {err!r}"}, state=406
|
393
|
+
)
|
394
|
+
else:
|
395
|
+
try:
|
396
|
+
job_struc = scheduler.get_job(job)
|
397
|
+
job = job_struc["job"]
|
398
|
+
# getting info about will be paused or removed (TODO removed).
|
399
|
+
job.pause()
|
400
|
+
return self.json_response(response=f"Job {job} was Paused.", state=202)
|
401
|
+
except Exception as err:
|
402
|
+
self.logger.exception(
|
403
|
+
f"Invalid Job Id {job!s}: {err!s}"
|
404
|
+
)
|
405
|
+
return self.error(
|
406
|
+
response={"message": f"Invalid Job Id {job!s}: {err!s}"}, state=406
|
407
|
+
)
|
408
|
+
|
409
|
+
|
410
|
+
class JobManager(ModelView):
|
411
|
+
model: BaseModel = Job
|
412
|
+
path: str = "/api/v1/scheduler/jobs"
|
413
|
+
pk: str = 'job_id'
|
414
|
+
|
415
|
+
async def _get_created_by(self, value, column, data):
|
416
|
+
return await self.get_userid(session=self._session)
|
417
|
+
|
418
|
+
async def _set_created_by(self, value, column, data):
|
419
|
+
return await self.get_userid(session=self._session)
|
420
|
+
|
421
|
+
async def _patch_response(self, result, status: int = 202) -> web.Response:
|
422
|
+
"""_patch_data.
|
423
|
+
|
424
|
+
Post-processing data after saved and before summit.
|
425
|
+
"""
|
426
|
+
try:
|
427
|
+
scheduler = self.request.app["scheduler"]
|
428
|
+
except KeyError:
|
429
|
+
return self.error(
|
430
|
+
response={"message": "Scheduler is not available"},
|
431
|
+
status=500
|
432
|
+
)
|
433
|
+
job = result.job_id
|
434
|
+
job_status = result.enabled
|
435
|
+
job_struc = scheduler.get_job(job)
|
436
|
+
if job_status is False:
|
437
|
+
# Job need to be paused
|
438
|
+
if not job_struc:
|
439
|
+
# there is no jobs in the scheduler
|
440
|
+
return self.json_response(result, status=status)
|
441
|
+
job_obj = job_struc.get('job', None)
|
442
|
+
if job_obj:
|
443
|
+
# Remove Job from Jobstore
|
444
|
+
job_obj.remove()
|
445
|
+
if job_status is True:
|
446
|
+
# Job need to be resumed
|
447
|
+
if not job_struc:
|
448
|
+
# there is no jobs in the scheduler, add it:
|
449
|
+
await scheduler.add_job(result.to_dict())
|
450
|
+
return self.json_response(result, status=status)
|
451
|
+
job_obj = job_struc.get('job', None)
|
452
|
+
if not job_obj:
|
453
|
+
# Add this job to the Jobstore
|
454
|
+
await scheduler.add_job(result.to_dict())
|
455
|
+
# I change the job and job is on the scheduler running:
|
456
|
+
if job_status is True and job_struc:
|
457
|
+
job_obj = job_struc.get('job', None)
|
458
|
+
if job_obj:
|
459
|
+
# Remove the job, and re-add it.
|
460
|
+
job_obj.remove()
|
461
|
+
await scheduler.add_job(result.to_dict())
|
462
|
+
return self.json_response(result, status=status)
|
463
|
+
|
464
|
+
async def _post_response(
|
465
|
+
self,
|
466
|
+
response: Any,
|
467
|
+
fields: list = None,
|
468
|
+
headers: dict = None,
|
469
|
+
status: int = 200
|
470
|
+
) -> web.Response:
|
471
|
+
"""_post_response.
|
472
|
+
|
473
|
+
Post-processing data after saved and before summit.
|
474
|
+
"""
|
475
|
+
try:
|
476
|
+
scheduler = self.request.app["scheduler"]
|
477
|
+
except KeyError:
|
478
|
+
return self.error(
|
479
|
+
response={"message": "Scheduler is not available"},
|
480
|
+
status=500
|
481
|
+
)
|
482
|
+
job = response.job_id
|
483
|
+
job_status = response.enabled
|
484
|
+
job_struc = scheduler.get_job(job)
|
485
|
+
if job_status is False and not job_struc:
|
486
|
+
# there is no jobs in the scheduler, nothing to do
|
487
|
+
return self.json_response(response, status=status)
|
488
|
+
# Check if the job alredy exists in the scheduler
|
489
|
+
job_obj = job_struc.get('job', None)
|
490
|
+
if job_obj:
|
491
|
+
# Remove Job from Jobstore before add-it
|
492
|
+
try:
|
493
|
+
job_obj.remove()
|
494
|
+
except Exception:
|
495
|
+
pass
|
496
|
+
if job_status is True:
|
497
|
+
await scheduler.add_job(response.to_dict())
|
498
|
+
elif job_obj is None and job_status is True:
|
499
|
+
# Job doesn't exists, please add it
|
500
|
+
await scheduler.add_job(response.to_dict())
|
501
|
+
elif job_status is True and not job_struc:
|
502
|
+
# there is no jobs in the scheduler, add it:
|
503
|
+
await scheduler.add_job(response.to_dict())
|
504
|
+
return self.json_response(response, status=status, headers=headers)
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from typing import Dict, Optional, Any, Union
|
2
|
+
from uuid import UUID, uuid4
|
3
|
+
from datetime import datetime
|
4
|
+
from asyncdb.models import Model, Field
|
5
|
+
|
6
|
+
|
7
|
+
class Job(Model):
|
8
|
+
job_id: str = Field(required=True, primary_key=True)
|
9
|
+
job_uuid: UUID = Field(
|
10
|
+
required=False,
|
11
|
+
primary_key=True,
|
12
|
+
db_default="auto",
|
13
|
+
default_factory=uuid4,
|
14
|
+
repr=False
|
15
|
+
)
|
16
|
+
job: Optional[dict] = Field(required=True, default_factory=dict)
|
17
|
+
attributes: Optional[dict] = Field(default_factory=dict)
|
18
|
+
schedule_type: str = Field(required=False, default="interval")
|
19
|
+
schedule: Optional[dict] = Field(default_factory=dict)
|
20
|
+
jitter: Optional[int] = Field(required=False, default=0)
|
21
|
+
is_coroutine: bool = Field(required=False, default=True)
|
22
|
+
jobstore: str = Field(required=False, default="default")
|
23
|
+
executor: str = Field(required=False, default="default")
|
24
|
+
run_date: Optional[datetime]
|
25
|
+
start_date: Optional[datetime]
|
26
|
+
end_date: Optional[datetime]
|
27
|
+
last_exec_time: Optional[datetime]
|
28
|
+
next_run_time: Optional[datetime]
|
29
|
+
job_status: int = Field(required=False, default=0)
|
30
|
+
job_state: Optional[bytes] = Field(required=False, repr=False)
|
31
|
+
traceback: Optional[str] = Field(required=False, repr=False)
|
32
|
+
params: Optional[dict] = Field(default_factory=dict)
|
33
|
+
enabled: bool = Field(required=False, default=True)
|
34
|
+
reschedule: bool = Field(required=False, default=False)
|
35
|
+
reschedule_jitter: Optional[int] = Field(required=False, default=0)
|
36
|
+
rescheduled_max: Optional[int] = Field(required=False, default=0)
|
37
|
+
notification: bool = Field(required=False, default=False)
|
38
|
+
priority: Optional[str] = Field(required=False)
|
39
|
+
created_at: datetime = Field(
|
40
|
+
required=False,
|
41
|
+
default=datetime.now(),
|
42
|
+
repr=False
|
43
|
+
)
|
44
|
+
updated_at: datetime = Field(
|
45
|
+
required=False,
|
46
|
+
default=datetime.now(),
|
47
|
+
repr=False
|
48
|
+
)
|
49
|
+
## ALTER TABLE troc.jobs add column if not exists created_by integer;
|
50
|
+
created_by: int = Field(required=False)
|
51
|
+
|
52
|
+
class Meta:
|
53
|
+
driver = "pg"
|
54
|
+
name = "jobs"
|
55
|
+
schema = "troc"
|
56
|
+
app_label = "troc"
|
57
|
+
strict = True
|
58
|
+
frozen = False
|
@@ -0,0 +1,72 @@
|
|
1
|
+
"""
|
2
|
+
Scheduler Service.
|
3
|
+
|
4
|
+
API View for Managing Jobs in NAV Scheduler.
|
5
|
+
"""
|
6
|
+
from navigator.views import BaseView
|
7
|
+
from datetime import datetime
|
8
|
+
|
9
|
+
|
10
|
+
class SchedulerService(BaseView):
|
11
|
+
"""Scheduler Manager Facility.
|
12
|
+
|
13
|
+
Facility for a remotely accessible Scheduler Service.
|
14
|
+
can add, modify, remove, pause or re-schedule jobs, looking for job information, etc.
|
15
|
+
|
16
|
+
TODO: Can we use it also as RPC Service.
|
17
|
+
|
18
|
+
get: Get all information about a Job.
|
19
|
+
put: inserting a new Job into the jobstore.
|
20
|
+
post: modify a Job or re-schedule a Job.
|
21
|
+
delete: removing (or pausing) a Job.
|
22
|
+
patch: restart a Job o submitting a Job.
|
23
|
+
"""
|
24
|
+
|
25
|
+
def next_runtime(self):
|
26
|
+
app = self.request.app
|
27
|
+
scheduler = app["scheduler"]
|
28
|
+
args = self.match_parameters(self.request)
|
29
|
+
try:
|
30
|
+
job = args["job"]
|
31
|
+
except KeyError:
|
32
|
+
job = None
|
33
|
+
obj = scheduler.get_job(job)
|
34
|
+
job = obj["job"]
|
35
|
+
if job and job.next_run_time:
|
36
|
+
status = job.next_run_time.strftime("%Y-%m-%d %H:%M:%S")
|
37
|
+
result = {"job_id": job.id, "name": job.name, "next_run_time": status}
|
38
|
+
return self.json_response(response=result, state=200)
|
39
|
+
else:
|
40
|
+
return self.error(
|
41
|
+
request=self.request,
|
42
|
+
response=f"There is not such Job Scheduled {job!s}",
|
43
|
+
state=404,
|
44
|
+
)
|
45
|
+
|
46
|
+
def time_left(self):
|
47
|
+
app = self.request.app
|
48
|
+
scheduler = app["scheduler"]
|
49
|
+
args = self.match_parameters(self.request)
|
50
|
+
try:
|
51
|
+
job = args["job"]
|
52
|
+
except KeyError:
|
53
|
+
job = None
|
54
|
+
obj = scheduler.get_job(job)
|
55
|
+
job = obj["job"]
|
56
|
+
if job and job.next_run_time:
|
57
|
+
delta = job.next_run_time.replace(tzinfo=None) - datetime.now()
|
58
|
+
hours, remainder = divmod(delta.seconds, 3600)
|
59
|
+
minutes, seconds = divmod(remainder, 60)
|
60
|
+
days = f"{delta.days} days, " if delta.days else ""
|
61
|
+
result = {
|
62
|
+
"job_id": job.id,
|
63
|
+
"name": job.name,
|
64
|
+
"time_left": f"{days}{hours}h:{minutes}m:{seconds}s",
|
65
|
+
}
|
66
|
+
return self.json_response(response=result, state=200)
|
67
|
+
else:
|
68
|
+
return self.error(
|
69
|
+
request=self.request,
|
70
|
+
response=f"There is not such Job Scheduled {job!s}",
|
71
|
+
state=404,
|
72
|
+
)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
# config
|
4
|
+
from navconfig.logging import logging
|
5
|
+
|
6
|
+
# Notify:
|
7
|
+
from notify import Notify
|
8
|
+
from notify.models import Actor, Chat
|
9
|
+
from notify.providers.email import Email
|
10
|
+
|
11
|
+
from ..conf import (
|
12
|
+
DEFAULT_RECIPIENT,
|
13
|
+
EMAIL_HOST,
|
14
|
+
EMAIL_PASSWORD,
|
15
|
+
EMAIL_PORT,
|
16
|
+
EMAIL_USERNAME,
|
17
|
+
EVENT_CHAT_BOT,
|
18
|
+
EVENT_CHAT_ID,
|
19
|
+
)
|
20
|
+
|
21
|
+
|
22
|
+
def getNotify(notify, **kwargs):
|
23
|
+
args = {}
|
24
|
+
if notify == "telegram":
|
25
|
+
# defining the Default chat object:
|
26
|
+
recipient = Chat(**{"chat_id": EVENT_CHAT_ID, "chat_name": "Navigator"})
|
27
|
+
# send notifications to Telegram bot
|
28
|
+
args = {"bot_token": EVENT_CHAT_BOT, **kwargs}
|
29
|
+
ntf = Notify("telegram", **args)
|
30
|
+
elif notify == "email":
|
31
|
+
account = {
|
32
|
+
"host": EMAIL_HOST,
|
33
|
+
"port": EMAIL_PORT,
|
34
|
+
"username": EMAIL_USERNAME,
|
35
|
+
"password": EMAIL_PASSWORD,
|
36
|
+
}
|
37
|
+
recipient = Actor(**DEFAULT_RECIPIENT)
|
38
|
+
ntf = Email(debug=True, **account)
|
39
|
+
else:
|
40
|
+
recipient = Actor(**DEFAULT_RECIPIENT)
|
41
|
+
ntf = Notify(notify, **args)
|
42
|
+
return [ntf, recipient]
|
43
|
+
|
44
|
+
|
45
|
+
async def _send_(ntf, **kwargs):
|
46
|
+
async with ntf as conn:
|
47
|
+
await conn.send(**kwargs)
|
48
|
+
|
49
|
+
|
50
|
+
def send_notification(event_loop, message, provider):
|
51
|
+
asyncio.set_event_loop(event_loop)
|
52
|
+
ntf, recipients = getNotify(provider)
|
53
|
+
args = {
|
54
|
+
"recipient": [recipients],
|
55
|
+
"message": message,
|
56
|
+
}
|
57
|
+
if ntf.provider_type == "email":
|
58
|
+
args["subject"] = message
|
59
|
+
if ntf.provider == "telegram":
|
60
|
+
args["disable_notification"] = False
|
61
|
+
try:
|
62
|
+
result = event_loop.run_until_complete(_send_(ntf, **args))
|
63
|
+
logging.debug(f"NOTIFY result: {result}")
|
64
|
+
except Exception as err:
|
65
|
+
logging.exception(err)
|