flowtask 5.8.4__cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flowtask/__init__.py +93 -0
- flowtask/__main__.py +38 -0
- flowtask/bots/__init__.py +6 -0
- flowtask/bots/check.py +93 -0
- flowtask/bots/codebot.py +51 -0
- flowtask/components/ASPX.py +148 -0
- flowtask/components/AddDataset.py +352 -0
- flowtask/components/Amazon.py +523 -0
- flowtask/components/AutoTask.py +314 -0
- flowtask/components/Azure.py +80 -0
- flowtask/components/AzureUsers.py +106 -0
- flowtask/components/BaseAction.py +91 -0
- flowtask/components/BaseLoop.py +198 -0
- flowtask/components/BestBuy.py +800 -0
- flowtask/components/CSVToGCS.py +120 -0
- flowtask/components/CompanyScraper/__init__.py +1 -0
- flowtask/components/CompanyScraper/parsers/__init__.py +6 -0
- flowtask/components/CompanyScraper/parsers/base.py +102 -0
- flowtask/components/CompanyScraper/parsers/explorium.py +192 -0
- flowtask/components/CompanyScraper/parsers/leadiq.py +206 -0
- flowtask/components/CompanyScraper/parsers/rocket.py +133 -0
- flowtask/components/CompanyScraper/parsers/siccode.py +109 -0
- flowtask/components/CompanyScraper/parsers/visualvisitor.py +130 -0
- flowtask/components/CompanyScraper/parsers/zoominfo.py +118 -0
- flowtask/components/CompanyScraper/scrapper.py +1054 -0
- flowtask/components/CopyTo.py +177 -0
- flowtask/components/CopyToBigQuery.py +243 -0
- flowtask/components/CopyToMongoDB.py +291 -0
- flowtask/components/CopyToPg.py +609 -0
- flowtask/components/CopyToRethink.py +207 -0
- flowtask/components/CreateGCSBucket.py +102 -0
- flowtask/components/CreateReport/CreateReport.py +228 -0
- flowtask/components/CreateReport/__init__.py +9 -0
- flowtask/components/CreateReport/charts/__init__.py +15 -0
- flowtask/components/CreateReport/charts/bar.py +51 -0
- flowtask/components/CreateReport/charts/base.py +66 -0
- flowtask/components/CreateReport/charts/pie.py +64 -0
- flowtask/components/CreateReport/utils.py +9 -0
- flowtask/components/CustomerSatisfaction.py +196 -0
- flowtask/components/DataInput.py +200 -0
- flowtask/components/DateList.py +255 -0
- flowtask/components/DbClient.py +163 -0
- flowtask/components/DialPad.py +146 -0
- flowtask/components/DocumentDBQuery.py +200 -0
- flowtask/components/DownloadFrom.py +371 -0
- flowtask/components/DownloadFromD2L.py +113 -0
- flowtask/components/DownloadFromFTP.py +181 -0
- flowtask/components/DownloadFromIMAP.py +315 -0
- flowtask/components/DownloadFromS3.py +198 -0
- flowtask/components/DownloadFromSFTP.py +265 -0
- flowtask/components/DownloadFromSharepoint.py +110 -0
- flowtask/components/DownloadFromSmartSheet.py +114 -0
- flowtask/components/DownloadS3File.py +229 -0
- flowtask/components/Dummy.py +59 -0
- flowtask/components/DuplicatePhoto.py +411 -0
- flowtask/components/EmployeeEvaluation.py +237 -0
- flowtask/components/ExecuteSQL.py +323 -0
- flowtask/components/ExtractHTML.py +178 -0
- flowtask/components/FileBase.py +178 -0
- flowtask/components/FileCopy.py +181 -0
- flowtask/components/FileDelete.py +82 -0
- flowtask/components/FileExists.py +146 -0
- flowtask/components/FileIteratorDelete.py +112 -0
- flowtask/components/FileList.py +194 -0
- flowtask/components/FileOpen.py +75 -0
- flowtask/components/FileRead.py +120 -0
- flowtask/components/FileRename.py +106 -0
- flowtask/components/FilterIf.py +284 -0
- flowtask/components/FilterRows/FilterRows.py +200 -0
- flowtask/components/FilterRows/__init__.py +10 -0
- flowtask/components/FilterRows/functions.py +4 -0
- flowtask/components/GCSToBigQuery.py +103 -0
- flowtask/components/GoogleA4.py +150 -0
- flowtask/components/GoogleGeoCoding.py +344 -0
- flowtask/components/GooglePlaces.py +315 -0
- flowtask/components/GoogleSearch.py +539 -0
- flowtask/components/HTTPClient.py +268 -0
- flowtask/components/ICIMS.py +146 -0
- flowtask/components/IF.py +179 -0
- flowtask/components/IcimsFolderCopy.py +173 -0
- flowtask/components/ImageFeatures/__init__.py +5 -0
- flowtask/components/ImageFeatures/process.py +233 -0
- flowtask/components/IteratorBase.py +251 -0
- flowtask/components/LangchainLoader/__init__.py +5 -0
- flowtask/components/LangchainLoader/loader.py +194 -0
- flowtask/components/LangchainLoader/loaders/__init__.py +22 -0
- flowtask/components/LangchainLoader/loaders/abstract.py +362 -0
- flowtask/components/LangchainLoader/loaders/basepdf.py +50 -0
- flowtask/components/LangchainLoader/loaders/docx.py +91 -0
- flowtask/components/LangchainLoader/loaders/html.py +119 -0
- flowtask/components/LangchainLoader/loaders/pdfblocks.py +146 -0
- flowtask/components/LangchainLoader/loaders/pdfmark.py +79 -0
- flowtask/components/LangchainLoader/loaders/pdftables.py +135 -0
- flowtask/components/LangchainLoader/loaders/qa.py +67 -0
- flowtask/components/LangchainLoader/loaders/txt.py +55 -0
- flowtask/components/LeadIQ.py +650 -0
- flowtask/components/Loop.py +253 -0
- flowtask/components/Lowes.py +334 -0
- flowtask/components/MS365Usage.py +156 -0
- flowtask/components/MSTeamsMessages.py +320 -0
- flowtask/components/MarketClustering.py +1051 -0
- flowtask/components/MergeFiles.py +362 -0
- flowtask/components/MilvusOutput.py +87 -0
- flowtask/components/NearByStores.py +175 -0
- flowtask/components/NetworkNinja/__init__.py +6 -0
- flowtask/components/NetworkNinja/models/__init__.py +52 -0
- flowtask/components/NetworkNinja/models/abstract.py +177 -0
- flowtask/components/NetworkNinja/models/account.py +39 -0
- flowtask/components/NetworkNinja/models/client.py +19 -0
- flowtask/components/NetworkNinja/models/district.py +14 -0
- flowtask/components/NetworkNinja/models/events.py +101 -0
- flowtask/components/NetworkNinja/models/forms.py +499 -0
- flowtask/components/NetworkNinja/models/market.py +16 -0
- flowtask/components/NetworkNinja/models/organization.py +34 -0
- flowtask/components/NetworkNinja/models/photos.py +125 -0
- flowtask/components/NetworkNinja/models/project.py +44 -0
- flowtask/components/NetworkNinja/models/region.py +28 -0
- flowtask/components/NetworkNinja/models/store.py +203 -0
- flowtask/components/NetworkNinja/models/user.py +151 -0
- flowtask/components/NetworkNinja/router.py +854 -0
- flowtask/components/Odoo.py +175 -0
- flowtask/components/OdooInjector.py +192 -0
- flowtask/components/OpenFromXML.py +126 -0
- flowtask/components/OpenWeather.py +41 -0
- flowtask/components/OpenWithBase.py +616 -0
- flowtask/components/OpenWithPandas.py +715 -0
- flowtask/components/PGPDecrypt.py +199 -0
- flowtask/components/PandasIterator.py +187 -0
- flowtask/components/PandasToFile.py +189 -0
- flowtask/components/Paradox.py +339 -0
- flowtask/components/ParamIterator.py +117 -0
- flowtask/components/ParseHTML.py +84 -0
- flowtask/components/PlacerStores.py +249 -0
- flowtask/components/Pokemon.py +507 -0
- flowtask/components/PositiveBot.py +62 -0
- flowtask/components/PowerPointSlide.py +400 -0
- flowtask/components/PrintMessage.py +127 -0
- flowtask/components/ProductCompetitors/__init__.py +5 -0
- flowtask/components/ProductCompetitors/parsers/__init__.py +7 -0
- flowtask/components/ProductCompetitors/parsers/base.py +72 -0
- flowtask/components/ProductCompetitors/parsers/bestbuy.py +86 -0
- flowtask/components/ProductCompetitors/parsers/lowes.py +103 -0
- flowtask/components/ProductCompetitors/scrapper.py +155 -0
- flowtask/components/ProductCompliant.py +169 -0
- flowtask/components/ProductInfo/__init__.py +1 -0
- flowtask/components/ProductInfo/parsers/__init__.py +5 -0
- flowtask/components/ProductInfo/parsers/base.py +83 -0
- flowtask/components/ProductInfo/parsers/brother.py +97 -0
- flowtask/components/ProductInfo/parsers/canon.py +167 -0
- flowtask/components/ProductInfo/parsers/epson.py +118 -0
- flowtask/components/ProductInfo/parsers/hp.py +131 -0
- flowtask/components/ProductInfo/parsers/samsung.py +97 -0
- flowtask/components/ProductInfo/scraper.py +319 -0
- flowtask/components/ProductPricing.py +118 -0
- flowtask/components/QS.py +261 -0
- flowtask/components/QSBase.py +201 -0
- flowtask/components/QueryIterator.py +273 -0
- flowtask/components/QueryToInsert.py +327 -0
- flowtask/components/QueryToPandas.py +432 -0
- flowtask/components/RESTClient.py +195 -0
- flowtask/components/RethinkDBQuery.py +189 -0
- flowtask/components/Rsync.py +74 -0
- flowtask/components/RunSSH.py +59 -0
- flowtask/components/RunShell.py +71 -0
- flowtask/components/SalesForce.py +20 -0
- flowtask/components/SaveImageBank/__init__.py +257 -0
- flowtask/components/SchedulingVisits.py +592 -0
- flowtask/components/ScrapPage.py +216 -0
- flowtask/components/ScrapSearch.py +79 -0
- flowtask/components/SendNotify.py +257 -0
- flowtask/components/SentimentAnalysis.py +694 -0
- flowtask/components/ServiceScrapper/__init__.py +5 -0
- flowtask/components/ServiceScrapper/parsers/__init__.py +1 -0
- flowtask/components/ServiceScrapper/parsers/base.py +94 -0
- flowtask/components/ServiceScrapper/parsers/costco.py +93 -0
- flowtask/components/ServiceScrapper/scrapper.py +199 -0
- flowtask/components/SetVariables.py +156 -0
- flowtask/components/SubTask.py +182 -0
- flowtask/components/SuiteCRM.py +48 -0
- flowtask/components/Switch.py +175 -0
- flowtask/components/TableBase.py +148 -0
- flowtask/components/TableDelete.py +312 -0
- flowtask/components/TableInput.py +143 -0
- flowtask/components/TableOutput/TableOutput.py +384 -0
- flowtask/components/TableOutput/__init__.py +3 -0
- flowtask/components/TableSchema.py +534 -0
- flowtask/components/Target.py +223 -0
- flowtask/components/ThumbnailGenerator.py +156 -0
- flowtask/components/ToPandas.py +67 -0
- flowtask/components/TransformRows/TransformRows.py +507 -0
- flowtask/components/TransformRows/__init__.py +9 -0
- flowtask/components/TransformRows/functions.py +559 -0
- flowtask/components/TransposeRows.py +176 -0
- flowtask/components/UPCDatabase.py +86 -0
- flowtask/components/UnGzip.py +171 -0
- flowtask/components/Uncompress.py +172 -0
- flowtask/components/UniqueRows.py +126 -0
- flowtask/components/Unzip.py +107 -0
- flowtask/components/UpdateOperationalVars.py +147 -0
- flowtask/components/UploadTo.py +299 -0
- flowtask/components/UploadToS3.py +136 -0
- flowtask/components/UploadToSFTP.py +160 -0
- flowtask/components/UploadToSharepoint.py +205 -0
- flowtask/components/UserFunc.py +122 -0
- flowtask/components/VivaTracker.py +140 -0
- flowtask/components/WSDLClient.py +123 -0
- flowtask/components/Wait.py +18 -0
- flowtask/components/Walmart.py +199 -0
- flowtask/components/Workplace.py +134 -0
- flowtask/components/XMLToPandas.py +267 -0
- flowtask/components/Zammad/__init__.py +41 -0
- flowtask/components/Zammad/models.py +0 -0
- flowtask/components/ZoomInfoScraper.py +409 -0
- flowtask/components/__init__.py +104 -0
- flowtask/components/abstract.py +18 -0
- flowtask/components/flow.py +530 -0
- flowtask/components/google.py +335 -0
- flowtask/components/group.py +221 -0
- flowtask/components/py.typed +0 -0
- flowtask/components/reviewscrap.py +132 -0
- flowtask/components/tAutoincrement.py +117 -0
- flowtask/components/tConcat.py +109 -0
- flowtask/components/tExplode.py +119 -0
- flowtask/components/tFilter.py +184 -0
- flowtask/components/tGroup.py +236 -0
- flowtask/components/tJoin.py +270 -0
- flowtask/components/tMap/__init__.py +9 -0
- flowtask/components/tMap/functions.py +54 -0
- flowtask/components/tMap/tMap.py +450 -0
- flowtask/components/tMelt.py +112 -0
- flowtask/components/tMerge.py +114 -0
- flowtask/components/tOrder.py +93 -0
- flowtask/components/tPandas.py +94 -0
- flowtask/components/tPivot.py +71 -0
- flowtask/components/tPluckCols.py +76 -0
- flowtask/components/tUnnest.py +82 -0
- flowtask/components/user.py +401 -0
- flowtask/conf.py +457 -0
- flowtask/download.py +102 -0
- flowtask/events/__init__.py +11 -0
- flowtask/events/events/__init__.py +20 -0
- flowtask/events/events/abstract.py +95 -0
- flowtask/events/events/alerts/__init__.py +362 -0
- flowtask/events/events/alerts/colfunctions.py +131 -0
- flowtask/events/events/alerts/functions.py +158 -0
- flowtask/events/events/dummy.py +12 -0
- flowtask/events/events/exec.py +124 -0
- flowtask/events/events/file/__init__.py +7 -0
- flowtask/events/events/file/base.py +51 -0
- flowtask/events/events/file/copy.py +23 -0
- flowtask/events/events/file/delete.py +16 -0
- flowtask/events/events/interfaces/__init__.py +9 -0
- flowtask/events/events/interfaces/client.py +67 -0
- flowtask/events/events/interfaces/credentials.py +28 -0
- flowtask/events/events/interfaces/notifications.py +58 -0
- flowtask/events/events/jira.py +122 -0
- flowtask/events/events/log.py +26 -0
- flowtask/events/events/logerr.py +52 -0
- flowtask/events/events/notify.py +59 -0
- flowtask/events/events/notify_event.py +160 -0
- flowtask/events/events/publish.py +54 -0
- flowtask/events/events/sendfile.py +104 -0
- flowtask/events/events/task.py +97 -0
- flowtask/events/events/teams.py +98 -0
- flowtask/events/events/webhook.py +58 -0
- flowtask/events/manager.py +287 -0
- flowtask/exceptions.c +39393 -0
- flowtask/exceptions.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/extensions/__init__.py +3 -0
- flowtask/extensions/abstract.py +82 -0
- flowtask/extensions/logging/__init__.py +65 -0
- flowtask/hooks/__init__.py +9 -0
- flowtask/hooks/actions/__init__.py +22 -0
- flowtask/hooks/actions/abstract.py +66 -0
- flowtask/hooks/actions/dummy.py +23 -0
- flowtask/hooks/actions/jira.py +74 -0
- flowtask/hooks/actions/rest.py +320 -0
- flowtask/hooks/actions/sampledata.py +37 -0
- flowtask/hooks/actions/sensor.py +23 -0
- flowtask/hooks/actions/task.py +9 -0
- flowtask/hooks/actions/ticket.py +37 -0
- flowtask/hooks/actions/zammad.py +55 -0
- flowtask/hooks/hook.py +62 -0
- flowtask/hooks/models.py +17 -0
- flowtask/hooks/service.py +187 -0
- flowtask/hooks/step.py +91 -0
- flowtask/hooks/types/__init__.py +23 -0
- flowtask/hooks/types/base.py +129 -0
- flowtask/hooks/types/brokers/__init__.py +11 -0
- flowtask/hooks/types/brokers/base.py +54 -0
- flowtask/hooks/types/brokers/mqtt.py +35 -0
- flowtask/hooks/types/brokers/rabbitmq.py +82 -0
- flowtask/hooks/types/brokers/redis.py +83 -0
- flowtask/hooks/types/brokers/sqs.py +44 -0
- flowtask/hooks/types/fs.py +232 -0
- flowtask/hooks/types/http.py +49 -0
- flowtask/hooks/types/imap.py +200 -0
- flowtask/hooks/types/jira.py +279 -0
- flowtask/hooks/types/mail.py +205 -0
- flowtask/hooks/types/postgres.py +98 -0
- flowtask/hooks/types/responses/__init__.py +8 -0
- flowtask/hooks/types/responses/base.py +5 -0
- flowtask/hooks/types/sharepoint.py +288 -0
- flowtask/hooks/types/ssh.py +141 -0
- flowtask/hooks/types/tagged.py +59 -0
- flowtask/hooks/types/upload.py +85 -0
- flowtask/hooks/types/watch.py +71 -0
- flowtask/hooks/types/web.py +36 -0
- flowtask/interfaces/AzureClient.py +137 -0
- flowtask/interfaces/AzureGraph.py +839 -0
- flowtask/interfaces/Boto3Client.py +326 -0
- flowtask/interfaces/DropboxClient.py +173 -0
- flowtask/interfaces/ExcelHandler.py +94 -0
- flowtask/interfaces/FTPClient.py +131 -0
- flowtask/interfaces/GoogleCalendar.py +201 -0
- flowtask/interfaces/GoogleClient.py +133 -0
- flowtask/interfaces/GoogleDrive.py +127 -0
- flowtask/interfaces/GoogleGCS.py +89 -0
- flowtask/interfaces/GoogleGeocoding.py +93 -0
- flowtask/interfaces/GoogleLang.py +114 -0
- flowtask/interfaces/GooglePub.py +61 -0
- flowtask/interfaces/GoogleSheet.py +68 -0
- flowtask/interfaces/IMAPClient.py +137 -0
- flowtask/interfaces/O365Calendar.py +113 -0
- flowtask/interfaces/O365Client.py +220 -0
- flowtask/interfaces/OneDrive.py +284 -0
- flowtask/interfaces/Outlook.py +155 -0
- flowtask/interfaces/ParrotBot.py +130 -0
- flowtask/interfaces/SSHClient.py +378 -0
- flowtask/interfaces/Sharepoint.py +496 -0
- flowtask/interfaces/__init__.py +36 -0
- flowtask/interfaces/azureauth.py +119 -0
- flowtask/interfaces/cache.py +201 -0
- flowtask/interfaces/client.py +82 -0
- flowtask/interfaces/compress.py +525 -0
- flowtask/interfaces/credentials.py +124 -0
- flowtask/interfaces/d2l.py +239 -0
- flowtask/interfaces/databases/__init__.py +5 -0
- flowtask/interfaces/databases/db.py +223 -0
- flowtask/interfaces/databases/documentdb.py +55 -0
- flowtask/interfaces/databases/rethink.py +39 -0
- flowtask/interfaces/dataframes/__init__.py +11 -0
- flowtask/interfaces/dataframes/abstract.py +21 -0
- flowtask/interfaces/dataframes/arrow.py +71 -0
- flowtask/interfaces/dataframes/dt.py +69 -0
- flowtask/interfaces/dataframes/pandas.py +167 -0
- flowtask/interfaces/dataframes/polars.py +60 -0
- flowtask/interfaces/db.py +263 -0
- flowtask/interfaces/env.py +46 -0
- flowtask/interfaces/func.py +137 -0
- flowtask/interfaces/http.py +1780 -0
- flowtask/interfaces/locale.py +40 -0
- flowtask/interfaces/log.py +75 -0
- flowtask/interfaces/mask.py +143 -0
- flowtask/interfaces/notification.py +154 -0
- flowtask/interfaces/playwright.py +339 -0
- flowtask/interfaces/powerpoint.py +368 -0
- flowtask/interfaces/py.typed +0 -0
- flowtask/interfaces/qs.py +376 -0
- flowtask/interfaces/result.py +87 -0
- flowtask/interfaces/selenium_service.py +779 -0
- flowtask/interfaces/smartsheet.py +154 -0
- flowtask/interfaces/stat.py +39 -0
- flowtask/interfaces/task.py +96 -0
- flowtask/interfaces/template.py +118 -0
- flowtask/interfaces/vectorstores/__init__.py +1 -0
- flowtask/interfaces/vectorstores/abstract.py +133 -0
- flowtask/interfaces/vectorstores/milvus.py +669 -0
- flowtask/interfaces/zammad.py +107 -0
- flowtask/models.py +193 -0
- flowtask/parsers/__init__.py +15 -0
- flowtask/parsers/_yaml.c +11978 -0
- flowtask/parsers/_yaml.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/argparser.py +235 -0
- flowtask/parsers/base.c +15155 -0
- flowtask/parsers/base.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/json.c +11968 -0
- flowtask/parsers/json.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/parsers/maps.py +49 -0
- flowtask/parsers/toml.c +11968 -0
- flowtask/parsers/toml.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/plugins/__init__.py +16 -0
- flowtask/plugins/components/__init__.py +0 -0
- flowtask/plugins/handler/__init__.py +45 -0
- flowtask/plugins/importer.py +31 -0
- flowtask/plugins/sources/__init__.py +0 -0
- flowtask/runner.py +283 -0
- flowtask/scheduler/__init__.py +9 -0
- flowtask/scheduler/functions.py +493 -0
- flowtask/scheduler/handlers/__init__.py +8 -0
- flowtask/scheduler/handlers/manager.py +504 -0
- flowtask/scheduler/handlers/models.py +58 -0
- flowtask/scheduler/handlers/service.py +72 -0
- flowtask/scheduler/notifications.py +65 -0
- flowtask/scheduler/scheduler.py +993 -0
- flowtask/services/__init__.py +0 -0
- flowtask/services/bots/__init__.py +0 -0
- flowtask/services/bots/telegram.py +264 -0
- flowtask/services/files/__init__.py +11 -0
- flowtask/services/files/manager.py +522 -0
- flowtask/services/files/model.py +37 -0
- flowtask/services/files/service.py +767 -0
- flowtask/services/jira/__init__.py +3 -0
- flowtask/services/jira/jira_actions.py +191 -0
- flowtask/services/tasks/__init__.py +13 -0
- flowtask/services/tasks/launcher.py +213 -0
- flowtask/services/tasks/manager.py +323 -0
- flowtask/services/tasks/service.py +275 -0
- flowtask/services/tasks/task_manager.py +376 -0
- flowtask/services/tasks/tasks.py +155 -0
- flowtask/storages/__init__.py +16 -0
- flowtask/storages/exceptions.py +12 -0
- flowtask/storages/files/__init__.py +8 -0
- flowtask/storages/files/abstract.py +29 -0
- flowtask/storages/files/filesystem.py +66 -0
- flowtask/storages/tasks/__init__.py +19 -0
- flowtask/storages/tasks/abstract.py +26 -0
- flowtask/storages/tasks/database.py +33 -0
- flowtask/storages/tasks/filesystem.py +108 -0
- flowtask/storages/tasks/github.py +119 -0
- flowtask/storages/tasks/memory.py +45 -0
- flowtask/storages/tasks/row.py +25 -0
- flowtask/tasks/__init__.py +0 -0
- flowtask/tasks/abstract.py +526 -0
- flowtask/tasks/command.py +118 -0
- flowtask/tasks/pile.py +486 -0
- flowtask/tasks/py.typed +0 -0
- flowtask/tasks/task.py +778 -0
- flowtask/template/__init__.py +161 -0
- flowtask/tests.py +257 -0
- flowtask/types/__init__.py +8 -0
- flowtask/types/typedefs.c +11347 -0
- flowtask/types/typedefs.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/utils/__init__.py +24 -0
- flowtask/utils/constants.py +117 -0
- flowtask/utils/encoders.py +21 -0
- flowtask/utils/executor.py +112 -0
- flowtask/utils/functions.cpp +14280 -0
- flowtask/utils/functions.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/utils/json.cpp +13349 -0
- flowtask/utils/json.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/utils/mail.py +63 -0
- flowtask/utils/parseqs.c +13324 -0
- flowtask/utils/parserqs.cpython-39-x86_64-linux-gnu.so +0 -0
- flowtask/utils/stats.py +308 -0
- flowtask/utils/transformations.py +74 -0
- flowtask/utils/uv.py +12 -0
- flowtask/utils/validators.py +97 -0
- flowtask/version.py +11 -0
- flowtask-5.8.4.dist-info/LICENSE +201 -0
- flowtask-5.8.4.dist-info/METADATA +209 -0
- flowtask-5.8.4.dist-info/RECORD +470 -0
- flowtask-5.8.4.dist-info/WHEEL +6 -0
- flowtask-5.8.4.dist-info/entry_points.txt +3 -0
- flowtask-5.8.4.dist-info/top_level.txt +2 -0
- plugins/components/CreateQR.py +39 -0
- plugins/components/TestComponent.py +28 -0
- plugins/components/Use1.py +13 -0
- plugins/components/Workplace.py +117 -0
- plugins/components/__init__.py +3 -0
- plugins/sources/__init__.py +0 -0
- plugins/sources/get_populartimes.py +78 -0
- plugins/sources/google.py +150 -0
- plugins/sources/hubspot.py +679 -0
- plugins/sources/icims.py +679 -0
- plugins/sources/mobileinsight.py +501 -0
- plugins/sources/newrelic.py +262 -0
- plugins/sources/uap.py +268 -0
- plugins/sources/venu.py +244 -0
- plugins/sources/vocinity.py +314 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
from navconfig.logging import logging
|
3
|
+
from querysource.exceptions import DataNotFound as QSNotFound
|
4
|
+
from ..exceptions import ComponentError, DataNotFound
|
5
|
+
from .QSBase import QSBase
|
6
|
+
|
7
|
+
|
8
|
+
class GoogleA4(QSBase):
|
9
|
+
"""
|
10
|
+
GoogleA4
|
11
|
+
|
12
|
+
Overview
|
13
|
+
|
14
|
+
The GoogleA4 class is a component for interacting with Google Analytics 4 (GA4) to fetch and transform report data.
|
15
|
+
It extends the QSBase class and provides methods for retrieving reports and transforming the data into a specified format.
|
16
|
+
|
17
|
+
.. table:: Properties
|
18
|
+
:widths: auto
|
19
|
+
|
20
|
+
+--------------+----------+-----------+----------------------------------------------------------+
|
21
|
+
| Name | Required | Summary |
|
22
|
+
+--------------+----------+-----------+----------------------------------------------------------+
|
23
|
+
| datalist | Yes | Method for reports |
|
24
|
+
+--------------+----------+-----------+----------------------------------------------------------+
|
25
|
+
| subtask | Yes | Identifiers of property and metrics |
|
26
|
+
+--------------+----------+-----------+----------------------------------------------------------+
|
27
|
+
| type | Yes | Defines the type of data handled by the component, set to "report". |
|
28
|
+
+--------------+----------+-----------+----------------------------------------------------------+
|
29
|
+
| _driver | Yes | Specifies the driver used by the component, set to "ga". |
|
30
|
+
+--------------+----------+-----------+----------------------------------------------------------+
|
31
|
+
| _metrics | Yes | A dictionary mapping GA4 metrics to their corresponding output names.|
|
32
|
+
+--------------+----------+-----------+----------------------------------------------------------+
|
33
|
+
| _qs | Yes | Instance of the QSBase class used to interact with the data source. |
|
34
|
+
+--------------+----------+-----------+----------------------------------------------------------+
|
35
|
+
|
36
|
+
Raises:
|
37
|
+
DataNotFound: If no data is found.
|
38
|
+
ComponentError: If any other error occurs during execution.
|
39
|
+
|
40
|
+
Return
|
41
|
+
|
42
|
+
The methods in this class return the requested report data from Google Analytics 4, formatted according to the specific requirements of the component.
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
Example:
|
47
|
+
|
48
|
+
```yaml
|
49
|
+
GoogleA4:
|
50
|
+
type: report
|
51
|
+
property_id: '306735132'
|
52
|
+
pattern:
|
53
|
+
start_date:
|
54
|
+
- date_diff
|
55
|
+
- value: now
|
56
|
+
diff: 14
|
57
|
+
mode: days
|
58
|
+
mask: '%Y-%m-%d'
|
59
|
+
end_date:
|
60
|
+
- today
|
61
|
+
- mask: '%Y-%m-%d'
|
62
|
+
dimensions:
|
63
|
+
- mobileDeviceBranding
|
64
|
+
- mobileDeviceModel
|
65
|
+
metric:
|
66
|
+
- sessions
|
67
|
+
- totalUsers
|
68
|
+
- newUsers
|
69
|
+
- engagedSessions
|
70
|
+
- sessionsPerUser
|
71
|
+
company_id: 52
|
72
|
+
ga4_dimension: 10
|
73
|
+
```
|
74
|
+
|
75
|
+
"""
|
76
|
+
type = "report"
|
77
|
+
_driver = "ga"
|
78
|
+
_metrics: Dict = {
|
79
|
+
"sessions": "sessions",
|
80
|
+
"totalUsers": "total_users",
|
81
|
+
"newUsers": "new_users",
|
82
|
+
"engagedSessions": "engaged_users",
|
83
|
+
"sessionsPerUser": "per_user",
|
84
|
+
}
|
85
|
+
|
86
|
+
async def report(self):
|
87
|
+
try:
|
88
|
+
resultset = await self._qs.report()
|
89
|
+
if not resultset:
|
90
|
+
raise DataNotFound(
|
91
|
+
"No data found on GA4."
|
92
|
+
)
|
93
|
+
result = []
|
94
|
+
# TODO: making a better data-transformation
|
95
|
+
conditions = self._kwargs.get('conditions', {})
|
96
|
+
start_date = conditions.get('start_date', None)
|
97
|
+
end_date = conditions.get('end_date', None)
|
98
|
+
dimensions = conditions.get('dimensions', [])
|
99
|
+
if not start_date or not end_date:
|
100
|
+
raise ComponentError(
|
101
|
+
"Google Analytics 4 ERROR: Start and end dates are required."
|
102
|
+
)
|
103
|
+
company_id = conditions.get('company_id', None)
|
104
|
+
if not company_id:
|
105
|
+
raise ComponentError(
|
106
|
+
"Google Analytics 4 ERROR: Company ID is required."
|
107
|
+
)
|
108
|
+
ga4_dimension = conditions.get('ga4_dimension', None)
|
109
|
+
self.add_metric(
|
110
|
+
"START_DATE", start_date
|
111
|
+
)
|
112
|
+
self.add_metric("END_DATE", end_date)
|
113
|
+
self.add_metric("COMPANY", company_id)
|
114
|
+
self.add_metric("DIMENSION", ga4_dimension)
|
115
|
+
for row in resultset:
|
116
|
+
res = {}
|
117
|
+
res["start_date"] = start_date
|
118
|
+
res["end_date"] = end_date
|
119
|
+
res["company_id"] = company_id
|
120
|
+
res["dimension"] = dimensions
|
121
|
+
if "ga4_dimension" in self._variables:
|
122
|
+
res["ga4_dimension"] = self._variables["ga4_dimension"]
|
123
|
+
elif "ga4_dimension" in conditions:
|
124
|
+
res["ga4_dimension"] = ga4_dimension
|
125
|
+
d = {}
|
126
|
+
for dimension in conditions.get('dimensions', []):
|
127
|
+
d[dimension] = row[dimension]
|
128
|
+
res["dimensions"] = d
|
129
|
+
metrics = {}
|
130
|
+
for metric in conditions["metric"]:
|
131
|
+
metrics[metric] = row[metric]
|
132
|
+
try:
|
133
|
+
new_metric = self._metrics[metric]
|
134
|
+
res[new_metric] = row[metric]
|
135
|
+
except KeyError:
|
136
|
+
pass
|
137
|
+
res["metric"] = metrics
|
138
|
+
result.append(res)
|
139
|
+
return result
|
140
|
+
except DataNotFound:
|
141
|
+
raise
|
142
|
+
except QSNotFound as err:
|
143
|
+
raise DataNotFound(
|
144
|
+
f"GA4 Not Found: {err}"
|
145
|
+
) from err
|
146
|
+
except Exception as err:
|
147
|
+
logging.exception(err)
|
148
|
+
raise ComponentError(
|
149
|
+
f"Google Analytics 4 ERROR: {err!s}"
|
150
|
+
) from err
|
@@ -0,0 +1,344 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
import asyncio
|
3
|
+
from aiohttp.resolver import AsyncResolver
|
4
|
+
import aiohttp
|
5
|
+
import logging
|
6
|
+
import backoff
|
7
|
+
import pandas as pd
|
8
|
+
from datamodel.parsers.json import json_encoder
|
9
|
+
from ..conf import GOOGLE_API_KEY
|
10
|
+
from ..components import FlowComponent
|
11
|
+
from ..exceptions import ComponentError
|
12
|
+
|
13
|
+
|
14
|
+
logging.getLogger("aiohttp").setLevel(logging.WARNING)
|
15
|
+
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
16
|
+
|
17
|
+
|
18
|
+
class GoogleGeoCoding(FlowComponent):
|
19
|
+
"""
|
20
|
+
Google GeoCoding
|
21
|
+
|
22
|
+
Overview
|
23
|
+
|
24
|
+
This component retrieves geographical coordinates (latitude and longitude)
|
25
|
+
for a given set of addresses using Google Maps Geocoding API.
|
26
|
+
It utilizes asynchronous processing to handle multiple requests concurrently and
|
27
|
+
offers error handling for various scenarios.
|
28
|
+
|
29
|
+
.. table:: Properties
|
30
|
+
:widths: auto
|
31
|
+
|
32
|
+
+-----------------------+----------+------------------------------------------------------------------------------------------------------+
|
33
|
+
| Name | Required | Description |
|
34
|
+
+-----------------------+----------+------------------------------------------------------------------------------------------------------+
|
35
|
+
| data (pd.DataFrame) | Yes | Pandas DataFrame containing the addresses. Requires a column with the address information. |
|
36
|
+
+-----------------------+----------+------------------------------------------------------------------------------------------------------+
|
37
|
+
| columns (list) | Yes | List of column names in the DataFrame that contain the address components (e.g., ["street", "city"]).|
|
38
|
+
+-----------------------+----------+------------------------------------------------------------------------------------------------------+
|
39
|
+
|
40
|
+
Return
|
41
|
+
|
42
|
+
The component modifies the input DataFrame by adding new columns named
|
43
|
+
'latitude', 'longitude', 'formatted_address', 'place_id' and 'zipcode' containing the retrieved
|
44
|
+
geocoding information for each address. The original DataFrame is returned.
|
45
|
+
|
46
|
+
|
47
|
+
Example:
|
48
|
+
|
49
|
+
```yaml
|
50
|
+
GoogleGeoCoding:
|
51
|
+
skipError: skip
|
52
|
+
place_prefix: account_name
|
53
|
+
use_find_place: true
|
54
|
+
return_pluscode: true
|
55
|
+
chunk_size: 50
|
56
|
+
keywords:
|
57
|
+
- electronics_store
|
58
|
+
columns:
|
59
|
+
- street_address
|
60
|
+
- city
|
61
|
+
- state_code
|
62
|
+
- zipcode
|
63
|
+
```
|
64
|
+
|
65
|
+
""" # noqa
|
66
|
+
base_url: str = "https://maps.googleapis.com/maps/api/geocode/json"
|
67
|
+
|
68
|
+
def __init__(
|
69
|
+
self,
|
70
|
+
loop: asyncio.AbstractEventLoop = None,
|
71
|
+
job: Callable = None,
|
72
|
+
stat: Callable = None,
|
73
|
+
**kwargs,
|
74
|
+
) -> None:
|
75
|
+
self.chunk_size: int = kwargs.get('chunk_size', 100)
|
76
|
+
self.check_field = kwargs.get('comparison_field', 'formatted_address')
|
77
|
+
self.use_find_place: bool = kwargs.get('use_find_place', False)
|
78
|
+
self.return_pluscode: bool = kwargs.get('return_pluscode', False)
|
79
|
+
self.place_prefix: str = kwargs.get('place_prefix', None)
|
80
|
+
self._wait_time: float = kwargs.get('wait_time', 0.1)
|
81
|
+
super(GoogleGeoCoding, self).__init__(loop=loop, job=job, stat=stat, **kwargs)
|
82
|
+
self.semaphore = asyncio.Semaphore(10)
|
83
|
+
|
84
|
+
async def start(self, **kwargs):
|
85
|
+
self._counter: int = 0
|
86
|
+
if self.previous:
|
87
|
+
self.data = self.input
|
88
|
+
if not hasattr(self, 'columns'):
|
89
|
+
raise RuntimeError(
|
90
|
+
'GoogleGeoCoding requires a Column Attribute'
|
91
|
+
)
|
92
|
+
if not isinstance(self.columns, list):
|
93
|
+
raise RuntimeError(
|
94
|
+
'GoogleGeoCoding requires a Column Attribute as list'
|
95
|
+
)
|
96
|
+
if not isinstance(self.data, pd.DataFrame):
|
97
|
+
raise ComponentError(
|
98
|
+
"Incompatible Pandas Dataframe", status=404
|
99
|
+
)
|
100
|
+
if not GOOGLE_API_KEY:
|
101
|
+
raise ComponentError(
|
102
|
+
"Google API Key is missing", status=404
|
103
|
+
)
|
104
|
+
return True
|
105
|
+
|
106
|
+
async def find_place(
|
107
|
+
self,
|
108
|
+
address: str,
|
109
|
+
place_prefix: str = None,
|
110
|
+
fields="place_id,plus_code"
|
111
|
+
) -> tuple:
|
112
|
+
"""Searches for a place using the Google Places API.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
idx: row index
|
116
|
+
row: pandas row
|
117
|
+
return_pluscode: return the Google +Code
|
118
|
+
place_prefix: adding a prefix to address
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
The Place ID of the first matching result, or None if no results are found.
|
122
|
+
"""
|
123
|
+
base_url = "https://maps.googleapis.com/maps/api/place/findplacefromtext/json"
|
124
|
+
params = {
|
125
|
+
"input": address,
|
126
|
+
"inputtype": "textquery",
|
127
|
+
"fields": fields,
|
128
|
+
"key": GOOGLE_API_KEY
|
129
|
+
}
|
130
|
+
async with aiohttp.ClientSession() as session:
|
131
|
+
async with session.get(base_url, params=params) as response:
|
132
|
+
if response.status == 200:
|
133
|
+
result = await response.json()
|
134
|
+
if result['status'] == 'OK' and result["candidates"]:
|
135
|
+
candidate = result["candidates"][0]
|
136
|
+
place_id = candidate["place_id"]
|
137
|
+
plus_code = candidate.get('plus_code', {}).get('compound_code', None)
|
138
|
+
global_code = candidate.get('plus_code', {}).get('global_code', None)
|
139
|
+
return place_id, plus_code, global_code
|
140
|
+
return None, None, None
|
141
|
+
|
142
|
+
@backoff.on_exception(
|
143
|
+
backoff.expo,
|
144
|
+
(aiohttp.ClientError, asyncio.TimeoutError),
|
145
|
+
max_tries=2
|
146
|
+
)
|
147
|
+
async def get_coordinates(
|
148
|
+
self,
|
149
|
+
idx,
|
150
|
+
row,
|
151
|
+
return_pluscode: bool = False,
|
152
|
+
place_prefix: str = None
|
153
|
+
):
|
154
|
+
async with self.semaphore:
|
155
|
+
street_address = self.columns[0]
|
156
|
+
if pd.notnull(row[street_address]):
|
157
|
+
try:
|
158
|
+
address = ', '.join(
|
159
|
+
[
|
160
|
+
str(row[column]) for column in self.columns
|
161
|
+
if column is not None and pd.notna(row[column])
|
162
|
+
]
|
163
|
+
)
|
164
|
+
except (ValueError, TypeError, KeyError):
|
165
|
+
address = row[street_address]
|
166
|
+
if not address:
|
167
|
+
return idx, None
|
168
|
+
if place_prefix:
|
169
|
+
try:
|
170
|
+
place_prefix = row[place_prefix]
|
171
|
+
except (ValueError, KeyError):
|
172
|
+
pass
|
173
|
+
address = f"{place_prefix} {address}"
|
174
|
+
more_params = {}
|
175
|
+
if hasattr(self, 'keywords'):
|
176
|
+
keywords = []
|
177
|
+
for element in self.keywords:
|
178
|
+
keywords.append(f"keyword:{element}")
|
179
|
+
more_params = {
|
180
|
+
"components": "|".join(keywords)
|
181
|
+
}
|
182
|
+
params = {
|
183
|
+
"address": address,
|
184
|
+
**more_params,
|
185
|
+
"key": GOOGLE_API_KEY
|
186
|
+
}
|
187
|
+
self._logger.notice(
|
188
|
+
f"Looking for {address}"
|
189
|
+
)
|
190
|
+
try:
|
191
|
+
# Total timeout for the request
|
192
|
+
timeout = aiohttp.ClientTimeout(total=20)
|
193
|
+
resolver = AsyncResolver(
|
194
|
+
nameservers=["1.1.1.1", "8.8.8.8"]
|
195
|
+
)
|
196
|
+
connector = aiohttp.TCPConnector(
|
197
|
+
limit=100,
|
198
|
+
resolver=resolver
|
199
|
+
)
|
200
|
+
session_args = {
|
201
|
+
"connector": connector,
|
202
|
+
"timeout": timeout,
|
203
|
+
"json_serialize": json_encoder
|
204
|
+
}
|
205
|
+
async with aiohttp.ClientSession(**session_args) as session:
|
206
|
+
async with session.get(self.base_url, params=params) as response:
|
207
|
+
if response.status == 200:
|
208
|
+
result = await response.json()
|
209
|
+
if result['status'] == 'OK':
|
210
|
+
data = result['results'][0]
|
211
|
+
more_args = {}
|
212
|
+
plus_code = None
|
213
|
+
global_code = None
|
214
|
+
# Check if it's a subpremise or has an empty plus_code
|
215
|
+
if "subpremise" in data["types"] or not data.get("plus_code", {}).get("global_code", None): # noqa
|
216
|
+
# Refine the search:
|
217
|
+
if self.use_find_place is True:
|
218
|
+
place_id, plus_code, global_code = await self.find_place(
|
219
|
+
address, place_prefix
|
220
|
+
)
|
221
|
+
else:
|
222
|
+
place_id = data.get('place_id', None)
|
223
|
+
else:
|
224
|
+
place_id = data.get('place_id', None)
|
225
|
+
try:
|
226
|
+
plus_code = data.get("plus_code", {}).get('compound_code')
|
227
|
+
global_code = data.get("plus_code", {}).get('global_code')
|
228
|
+
except KeyError:
|
229
|
+
pass
|
230
|
+
# extract all information:
|
231
|
+
if return_pluscode is True:
|
232
|
+
more_args = {
|
233
|
+
"plus_code": plus_code,
|
234
|
+
"global_code": global_code
|
235
|
+
}
|
236
|
+
# Extract postal code
|
237
|
+
postal_code = None
|
238
|
+
for component in data['address_components']:
|
239
|
+
if 'postal_code' in component['types']:
|
240
|
+
postal_code = component['long_name']
|
241
|
+
break
|
242
|
+
latitude = data['geometry']['location']['lat']
|
243
|
+
longitude = data['geometry']['location']['lng']
|
244
|
+
formatted_address = data['formatted_address']
|
245
|
+
self._counter += 1
|
246
|
+
# Avoid overwhelming Google APIs
|
247
|
+
await asyncio.sleep(self._wait_time)
|
248
|
+
return idx, {
|
249
|
+
"latitude": latitude,
|
250
|
+
"longitude": longitude,
|
251
|
+
"formatted_address": formatted_address,
|
252
|
+
"place_id": place_id,
|
253
|
+
"zipcode": postal_code,
|
254
|
+
**more_args
|
255
|
+
}
|
256
|
+
else:
|
257
|
+
error = result.get('error_message', result)
|
258
|
+
status = result.get('status', 'Unknown')
|
259
|
+
self._logger.error(
|
260
|
+
f"{status}: {error}"
|
261
|
+
)
|
262
|
+
except asyncio.TimeoutError as exc:
|
263
|
+
self._logger.error(
|
264
|
+
f"TimeoutException: {exc}"
|
265
|
+
)
|
266
|
+
return idx, None
|
267
|
+
except TypeError as exc:
|
268
|
+
self._logger.error(
|
269
|
+
f"TypeError: {exc}"
|
270
|
+
)
|
271
|
+
return idx, None
|
272
|
+
return idx, None
|
273
|
+
|
274
|
+
def column_exists(self, column: str):
|
275
|
+
if column not in self.data.columns:
|
276
|
+
self._logger.warning(
|
277
|
+
f"Column {column} does not exist in the dataframe"
|
278
|
+
)
|
279
|
+
self.data[column] = None
|
280
|
+
return False
|
281
|
+
return True
|
282
|
+
|
283
|
+
def chunkify(self, lst, n):
|
284
|
+
"""Split list lst into chunks of size n."""
|
285
|
+
for i in range(0, len(lst), n):
|
286
|
+
yield lst[i:i + n]
|
287
|
+
|
288
|
+
async def run(self):
|
289
|
+
# initialize columns:
|
290
|
+
self.column_exists('place_id')
|
291
|
+
self.column_exists('latitude')
|
292
|
+
self.column_exists('longitude')
|
293
|
+
self.column_exists('formatted_address')
|
294
|
+
self.column_exists('zipcode')
|
295
|
+
self._counter = 0
|
296
|
+
tasks = [
|
297
|
+
self.get_coordinates(
|
298
|
+
idx,
|
299
|
+
row,
|
300
|
+
return_pluscode=self.return_pluscode,
|
301
|
+
place_prefix=self.place_prefix
|
302
|
+
) for idx, row in self.data.iterrows()
|
303
|
+
if pd.isnull(row[self.check_field])
|
304
|
+
]
|
305
|
+
results = []
|
306
|
+
for chunk in self.chunkify(tasks, self.chunk_size):
|
307
|
+
result = await asyncio.gather(*chunk, return_exceptions=True)
|
308
|
+
if result:
|
309
|
+
for res in result:
|
310
|
+
if isinstance(res, Exception):
|
311
|
+
# Handle the exception
|
312
|
+
self._logger.error(
|
313
|
+
f"Task failed with exception: {res}"
|
314
|
+
)
|
315
|
+
continue
|
316
|
+
results.append(res)
|
317
|
+
# else:
|
318
|
+
# results += result
|
319
|
+
results_list = []
|
320
|
+
for idx, result in results:
|
321
|
+
if result:
|
322
|
+
result['idx'] = idx # Add the index to the result dictionary
|
323
|
+
results_list.append(result)
|
324
|
+
if results_list:
|
325
|
+
results_df = pd.DataFrame(results_list)
|
326
|
+
results_df.set_index('idx', inplace=True)
|
327
|
+
# If necessary, reindex results_df to match self.data
|
328
|
+
results_df = results_df.reindex(self.data.index)
|
329
|
+
# Directly assign columns from results_df to self.data
|
330
|
+
for column in results_df.columns:
|
331
|
+
mask = results_df[column].notnull()
|
332
|
+
indices = results_df.index[mask]
|
333
|
+
self.data.loc[indices, column] = results_df.loc[indices, column]
|
334
|
+
self.add_metric("DOWNLOADED", self._counter)
|
335
|
+
# if self._debug is True:
|
336
|
+
print(self.data)
|
337
|
+
print("::: Printing Column Information === ")
|
338
|
+
for column, t in self.data.dtypes.items():
|
339
|
+
print(column, "->", t, "->", self.data[column].iloc[0])
|
340
|
+
self._result = self.data
|
341
|
+
return self._result
|
342
|
+
|
343
|
+
async def close(self):
|
344
|
+
pass
|