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,253 @@
|
|
1
|
+
from collections.abc import Callable, Awaitable
|
2
|
+
import asyncio
|
3
|
+
from typing import Any
|
4
|
+
from pandas import DataFrame
|
5
|
+
from asyncdb.exceptions import NoDataFound, ProviderError, DriverError
|
6
|
+
from .flow import FlowComponent
|
7
|
+
from ..exceptions import (
|
8
|
+
ComponentError,
|
9
|
+
DataNotFound,
|
10
|
+
NotSupported,
|
11
|
+
FileNotFound
|
12
|
+
)
|
13
|
+
from ..utils.stats import StepMonitor
|
14
|
+
from ..interfaces.log import SkipErrors
|
15
|
+
|
16
|
+
|
17
|
+
class Loop(FlowComponent):
|
18
|
+
"""Loop.
|
19
|
+
|
20
|
+
Overview:
|
21
|
+
The Loop class is a FlowComponent that is used to iterate over the next Component and execute them in a
|
22
|
+
sequential order.
|
23
|
+
It extends the FlowComponent class and provides methods for starting tasks, retrieving steps,
|
24
|
+
and executing jobs asynchronously.
|
25
|
+
|
26
|
+
"""
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
loop: asyncio.AbstractEventLoop = None,
|
30
|
+
job: Callable = None,
|
31
|
+
stat: Callable = None,
|
32
|
+
**kwargs,
|
33
|
+
):
|
34
|
+
self._conditions: dict = {}
|
35
|
+
self._generator = kwargs.get("generator", None) # Predefined generator
|
36
|
+
self._iterate: bool = kwargs.get('iterate', False)
|
37
|
+
self._iterable = kwargs.get("iterable", None) # Custom iterable
|
38
|
+
self._done: bool = False # Flag to indicate if iteration is complete
|
39
|
+
# Component to be executed when finished.
|
40
|
+
self._ondone: str = kwargs.get("onDone", None)
|
41
|
+
super(Loop, self).__init__(loop=loop, job=job, stat=stat, **kwargs)
|
42
|
+
|
43
|
+
async def start(self, **kwargs):
|
44
|
+
"""
|
45
|
+
start.
|
46
|
+
|
47
|
+
Initialize (if needed) a task
|
48
|
+
"""
|
49
|
+
if self.previous:
|
50
|
+
self.data = self.input
|
51
|
+
|
52
|
+
# check if previous data is an iterable:
|
53
|
+
if isinstance(self.data, DataFrame):
|
54
|
+
self._iterable = self.data.iterrows()
|
55
|
+
elif self._iterate is True or hasattr(self.data, '__iter__'):
|
56
|
+
self._iterable = self.data
|
57
|
+
elif self._generator:
|
58
|
+
self._iterable = self._resolve_generator(self._generator)
|
59
|
+
elif self._iterable and not hasattr(self._iterable, "__iter__"):
|
60
|
+
raise ComponentError(
|
61
|
+
"'iterable' must be an iterable object."
|
62
|
+
)
|
63
|
+
|
64
|
+
self._iterator = iter(self._iterable)
|
65
|
+
return True
|
66
|
+
|
67
|
+
def _resolve_generator(self, generator_name):
|
68
|
+
"""
|
69
|
+
Resolve predefined generators like 'days_of_week' or 'days_of_month'.
|
70
|
+
"""
|
71
|
+
# Define predefined generators here.
|
72
|
+
# Each generator should return a list of values.
|
73
|
+
# For example, 'days_of_week' could return a list of weekdays,
|
74
|
+
# and 'days_of_month' could return a list of days of the month.
|
75
|
+
predefined_generators = {
|
76
|
+
"days_of_week": lambda: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
|
77
|
+
"days_of_month": lambda: [f"Day {i}" for i in range(1, 32)],
|
78
|
+
}
|
79
|
+
if generator_name not in predefined_generators:
|
80
|
+
raise ComponentError(
|
81
|
+
f"Unknown generator: {generator_name}"
|
82
|
+
)
|
83
|
+
return predefined_generators[generator_name]()
|
84
|
+
|
85
|
+
async def close(self):
|
86
|
+
pass
|
87
|
+
|
88
|
+
def get_component(self, step, idx):
|
89
|
+
params = None
|
90
|
+
try:
|
91
|
+
if not self._TaskPile:
|
92
|
+
raise ComponentError(
|
93
|
+
"No Components in TaskPile"
|
94
|
+
)
|
95
|
+
params = step.params()
|
96
|
+
try:
|
97
|
+
if params["conditions"]:
|
98
|
+
self._conditions[step.name] = params["conditions"]
|
99
|
+
except KeyError:
|
100
|
+
pass
|
101
|
+
params["ENV"] = self._environment
|
102
|
+
# params
|
103
|
+
params["params"] = self._params
|
104
|
+
# parameters
|
105
|
+
params["parameters"] = self._parameters
|
106
|
+
# useful to change variables in set var components
|
107
|
+
params["_vars"] = self._vars
|
108
|
+
# variables dictionary
|
109
|
+
params["variables"] = self._variables
|
110
|
+
params["_args"] = self._args
|
111
|
+
# argument list for components (or tasks) that need argument lists
|
112
|
+
params["arguments"] = self._arguments
|
113
|
+
# for components with conditions, we can add more conditions
|
114
|
+
conditions = params.get("conditions", {})
|
115
|
+
step_conds = self._conditions.get(step.name, {})
|
116
|
+
if self.conditions is not None:
|
117
|
+
step_conds = {**self.conditions, **step_conds}
|
118
|
+
params["conditions"] = {**conditions, **step_conds}
|
119
|
+
# attributes only usable component-only
|
120
|
+
params["attributes"] = self._attributes
|
121
|
+
# the current Pile of components
|
122
|
+
params["TaskPile"] = self._TaskPile
|
123
|
+
# params['TaskName'] = step_name
|
124
|
+
params["debug"] = self._debug
|
125
|
+
params["argparser"] = self._argparser
|
126
|
+
# the current in-memory connector
|
127
|
+
params["memory"] = self._memory
|
128
|
+
target = step.component
|
129
|
+
# remove this element from tasks, doesn't need to run again
|
130
|
+
self._TaskPile.delStep(idx)
|
131
|
+
# return target and params
|
132
|
+
return [target, params]
|
133
|
+
finally:
|
134
|
+
pass
|
135
|
+
|
136
|
+
def create_component(self, target, value: Any = None, **params):
|
137
|
+
"""get_component.
|
138
|
+
|
139
|
+
Create a new component instance.
|
140
|
+
"""
|
141
|
+
try:
|
142
|
+
return target(
|
143
|
+
job=self,
|
144
|
+
loop=self._loop,
|
145
|
+
stat=self.stat,
|
146
|
+
input_result=value,
|
147
|
+
**params
|
148
|
+
)
|
149
|
+
except Exception as err:
|
150
|
+
raise ComponentError(
|
151
|
+
f"Component Error on {target}: {err}"
|
152
|
+
) from err
|
153
|
+
|
154
|
+
async def exec_component(self, job, step_name):
|
155
|
+
start = getattr(job, "start", None)
|
156
|
+
if callable(start):
|
157
|
+
try:
|
158
|
+
if asyncio.iscoroutinefunction(start):
|
159
|
+
st = await job.start()
|
160
|
+
else:
|
161
|
+
st = job.start()
|
162
|
+
self._logger.debug(f"STARTED: {st}")
|
163
|
+
except (NoDataFound, DataNotFound) as err:
|
164
|
+
raise DataNotFound(f"{err!s}") from err
|
165
|
+
except (ProviderError, ComponentError, NotSupported) as err:
|
166
|
+
raise ComponentError(
|
167
|
+
f"Error running Start Function on {step_name}, error: {err}"
|
168
|
+
) from err
|
169
|
+
else:
|
170
|
+
raise ComponentError(
|
171
|
+
f"Error running Function on {step_name}"
|
172
|
+
)
|
173
|
+
try:
|
174
|
+
run = getattr(job, "run", None)
|
175
|
+
if asyncio.iscoroutinefunction(run):
|
176
|
+
result = await job.run()
|
177
|
+
else:
|
178
|
+
result = job.run()
|
179
|
+
self._result = result
|
180
|
+
return self._result
|
181
|
+
except (NoDataFound, DataNotFound, FileNotFound) as err:
|
182
|
+
try:
|
183
|
+
if job.skipError == SkipErrors.SKIP:
|
184
|
+
self._logger.warning(
|
185
|
+
f"Component {job!s} was Skipped, error: {err}"
|
186
|
+
)
|
187
|
+
self._result = self.data
|
188
|
+
return self._result
|
189
|
+
elif job.skipError == SkipErrors.ENFORCE:
|
190
|
+
# Enforcing to Raise Error:
|
191
|
+
raise DataNotFound(f"{err!s}") from err
|
192
|
+
else:
|
193
|
+
# Log Only
|
194
|
+
self._logger.error(
|
195
|
+
f"Component {job!s} was Skipped, error: {err}"
|
196
|
+
)
|
197
|
+
except AttributeError:
|
198
|
+
raise DataNotFound(f"{err!s}") from err
|
199
|
+
except (ProviderError, ComponentError, NotSupported) as err:
|
200
|
+
raise NotSupported(
|
201
|
+
f"Error running Component {step_name}, error: {err}"
|
202
|
+
) from err
|
203
|
+
except Exception as err:
|
204
|
+
self._logger.exception(err, exc_info=True)
|
205
|
+
raise ComponentError(
|
206
|
+
f"Iterator Error on {step_name}, error: {err}"
|
207
|
+
) from err
|
208
|
+
finally:
|
209
|
+
try:
|
210
|
+
close = getattr(job, "close", None)
|
211
|
+
if asyncio.iscoroutinefunction(close):
|
212
|
+
await job.close()
|
213
|
+
else:
|
214
|
+
job.close()
|
215
|
+
except Exception:
|
216
|
+
pass
|
217
|
+
|
218
|
+
async def run(self):
|
219
|
+
"""Async Run Method."""
|
220
|
+
# iterate over next Component
|
221
|
+
step, idx = self._TaskPile.nextStep(self.StepName)
|
222
|
+
target, params = self.get_component(step, idx)
|
223
|
+
step_name = step.name
|
224
|
+
i = 0
|
225
|
+
results = []
|
226
|
+
while True:
|
227
|
+
try:
|
228
|
+
# Get the next item from the iterator
|
229
|
+
item = next(self._iterator)
|
230
|
+
self.setTaskVar('value', item)
|
231
|
+
cp = self.create_component(target, item, **params)
|
232
|
+
try:
|
233
|
+
result = await self.exec_component(cp, step_name)
|
234
|
+
results.append(result)
|
235
|
+
i += 1
|
236
|
+
except (NoDataFound, DataNotFound) as err:
|
237
|
+
# its a data component a no data was found
|
238
|
+
self._logger.notice(
|
239
|
+
f"Data not Found over {step_name} at {i} iteration, got: {err}"
|
240
|
+
)
|
241
|
+
i += 1
|
242
|
+
continue
|
243
|
+
except StopIteration:
|
244
|
+
self._done = True
|
245
|
+
break # Exit loop when iteration is complete
|
246
|
+
# when iteration is finished, return the results collected:
|
247
|
+
if self.stat:
|
248
|
+
parent_stat = self.stat.parent()
|
249
|
+
stat = StepMonitor(name=step_name, parent=parent_stat)
|
250
|
+
parent_stat.add_step(stat)
|
251
|
+
stat.add_metric('ITERATIONS', i)
|
252
|
+
self._result = results
|
253
|
+
return self._result
|
@@ -0,0 +1,334 @@
|
|
1
|
+
"""
|
2
|
+
Scrapping a Web Page Using Selenium + ChromeDriver + BeautifulSoup.
|
3
|
+
|
4
|
+
|
5
|
+
Example:
|
6
|
+
|
7
|
+
```yaml
|
8
|
+
Lowes:
|
9
|
+
type: reviews
|
10
|
+
use_proxies: true
|
11
|
+
paid_proxy: true
|
12
|
+
api_token: xxx
|
13
|
+
```
|
14
|
+
|
15
|
+
"""
|
16
|
+
import asyncio
|
17
|
+
from collections.abc import Callable
|
18
|
+
import random
|
19
|
+
import httpx
|
20
|
+
import pandas as pd
|
21
|
+
import backoff
|
22
|
+
# Internals
|
23
|
+
from ..exceptions import (
|
24
|
+
ComponentError,
|
25
|
+
ConfigError,
|
26
|
+
NotSupported
|
27
|
+
)
|
28
|
+
from ..interfaces.http import ua
|
29
|
+
from .reviewscrap import ReviewScrapper, bad_gateway_exception
|
30
|
+
|
31
|
+
|
32
|
+
class Lowes(ReviewScrapper):
|
33
|
+
"""Lowes.
|
34
|
+
|
35
|
+
Combining API Key and Web Scrapping, this component will be able to extract
|
36
|
+
Lowes Information (reviews, etc).
|
37
|
+
"""
|
38
|
+
def __init__(
|
39
|
+
self,
|
40
|
+
loop: asyncio.AbstractEventLoop = None,
|
41
|
+
job: Callable = None,
|
42
|
+
stat: Callable = None,
|
43
|
+
**kwargs,
|
44
|
+
):
|
45
|
+
super(Lowes, self).__init__(
|
46
|
+
loop=loop,
|
47
|
+
job=job,
|
48
|
+
stat=stat,
|
49
|
+
**kwargs
|
50
|
+
)
|
51
|
+
# Always use proxies:
|
52
|
+
self.use_proxy: bool = True
|
53
|
+
self._free_proxy: bool = False
|
54
|
+
self.cookies = {
|
55
|
+
"dbidv2": "956aa8ea-87f3-4068-96a8-3e2bdf4e84ec",
|
56
|
+
"al_sess": "FuA4EWsuT07UWryyq/3foLUcOGRVVGi7yYKO2imCjWnuWxkaJXwqJRDEw8CjJaWJ",
|
57
|
+
# Add other necessary cookies here
|
58
|
+
# Ensure tokens are valid and not expired
|
59
|
+
}
|
60
|
+
self.headers: dict = {
|
61
|
+
"Accept": "application/json",
|
62
|
+
"Accept-Encoding": "gzip, deflate, br, zstd",
|
63
|
+
"Accept-Language": "es-US,es;q=0.9,en-US;q=0.8,en;q=0.7,es-419;q=0.6",
|
64
|
+
"Cache-Control": "no-cache",
|
65
|
+
"Connection": "keep-alive",
|
66
|
+
"Content-Type": "application/json",
|
67
|
+
"Host": "www.lowes.com",
|
68
|
+
"Pragma": "no-cache",
|
69
|
+
"Origin": "https://www.lowes.com",
|
70
|
+
"Referer": "https://www.lowes.com/pd/",
|
71
|
+
"Sec-CH-UA": '"Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"',
|
72
|
+
"Sec-CH-UA-Mobile": "?0",
|
73
|
+
"Sec-CH-UA-Platform": '"Linux"',
|
74
|
+
'sec-fetch-site': 'none',
|
75
|
+
'sec-fetch-mode': 'navigate',
|
76
|
+
'sec-fetch-dest': 'document',
|
77
|
+
"Sec-Fetch-Site": "same-origin",
|
78
|
+
"User-Agent": random.choice(ua)
|
79
|
+
}
|
80
|
+
self.semaphore = asyncio.Semaphore(10)
|
81
|
+
|
82
|
+
async def close(self, **kwargs) -> bool:
|
83
|
+
self.close_driver()
|
84
|
+
return True
|
85
|
+
|
86
|
+
async def start(self, **kwargs) -> bool:
|
87
|
+
await super(Lowes, self).start(**kwargs)
|
88
|
+
if self.previous:
|
89
|
+
self.data = self.input
|
90
|
+
if not isinstance(self.data, pd.DataFrame):
|
91
|
+
raise ComponentError(
|
92
|
+
"Incompatible Pandas Dataframe"
|
93
|
+
)
|
94
|
+
self.api_token = self.get_env_value(self.api_token) if hasattr(self, 'api_token') else self.get_env_value('TARGET_API_KEY') # noqa
|
95
|
+
if not hasattr(self, self._fn):
|
96
|
+
raise ConfigError(
|
97
|
+
f"BestBuy: Unable to found Function {self._fn} in Target Component."
|
98
|
+
)
|
99
|
+
|
100
|
+
@backoff.on_exception(
|
101
|
+
backoff.expo,
|
102
|
+
(httpx.ConnectTimeout, httpx.HTTPStatusError),
|
103
|
+
max_tries=2,
|
104
|
+
giveup=lambda e: not bad_gateway_exception(e) and not isinstance(e, httpx.ConnectTimeout)
|
105
|
+
)
|
106
|
+
async def _product_reviews(self, idx, row, cookies):
|
107
|
+
async with self.semaphore:
|
108
|
+
# Prepare payload for the API request
|
109
|
+
sku = row['sku']
|
110
|
+
page_size = 10 # fixed size
|
111
|
+
current_offset = 0
|
112
|
+
max_pages = 20 # Maximum number of pages to fetch
|
113
|
+
all_reviews = []
|
114
|
+
total_reviews = 0
|
115
|
+
try:
|
116
|
+
while current_offset < max_pages * page_size:
|
117
|
+
if current_offset == 0:
|
118
|
+
payload = {
|
119
|
+
"sortBy": "newestFirst"
|
120
|
+
}
|
121
|
+
else:
|
122
|
+
payload = {
|
123
|
+
"sortBy": "newestFirst",
|
124
|
+
"offset": current_offset
|
125
|
+
}
|
126
|
+
url = f"https://www.lowes.com/rnr/r/get-by-product/{sku}"
|
127
|
+
result = await self.api_get(
|
128
|
+
url=url,
|
129
|
+
cookies=cookies,
|
130
|
+
params=payload,
|
131
|
+
headers=self.headers
|
132
|
+
)
|
133
|
+
if not result:
|
134
|
+
self._logger.warning(
|
135
|
+
f"No Product Reviews found for {sku}."
|
136
|
+
)
|
137
|
+
break
|
138
|
+
# Extract the reviews data from the API response
|
139
|
+
reviews_section = result.get('results', [])
|
140
|
+
total_reviews = result.get('totalResults', 0)
|
141
|
+
if not reviews_section:
|
142
|
+
self._logger.info(f"No more reviews found for SKU {sku} at offset {current_offset}.")
|
143
|
+
break
|
144
|
+
if len(reviews_section) == 0:
|
145
|
+
break
|
146
|
+
all_reviews.extend(reviews_section)
|
147
|
+
# Update total_reviews
|
148
|
+
|
149
|
+
# Check if we've fetched all reviews
|
150
|
+
if len(all_reviews) >= total_reviews:
|
151
|
+
self._logger.info(f"Fetched all reviews for SKU {sku}.")
|
152
|
+
break
|
153
|
+
current_offset += page_size # Move to the next page
|
154
|
+
except (httpx.TimeoutException, httpx.HTTPError) as ex:
|
155
|
+
self._logger.warning(f"Request failed: {ex}")
|
156
|
+
return []
|
157
|
+
except Exception as ex:
|
158
|
+
self._logger.error(f"An error occurred: {ex}")
|
159
|
+
return []
|
160
|
+
|
161
|
+
# Extract the reviews data from the API response
|
162
|
+
reviews = []
|
163
|
+
for item in all_reviews:
|
164
|
+
# Exclude certain keys
|
165
|
+
# Extract relevant fields
|
166
|
+
# Combine with original row data
|
167
|
+
review_data = row.to_dict()
|
168
|
+
review = {
|
169
|
+
**review_data,
|
170
|
+
"id": item.get("id"),
|
171
|
+
"legacyId": item.get("legacyId"),
|
172
|
+
"title": item.get("title"),
|
173
|
+
"review": item.get("reviewText"),
|
174
|
+
"rating": item.get("rating"),
|
175
|
+
"isRecommended": item.get("isRecommended"),
|
176
|
+
"userNickname": item.get("userNickname"),
|
177
|
+
"submissionTime": item.get("submissionTime"),
|
178
|
+
"verifiedPurchaser": item.get("verifiedPurchaser"),
|
179
|
+
"helpfulVoteCount": item.get("helpfulVoteCount"),
|
180
|
+
"notHelpfulVoteCount": item.get("notHelpfulVoteCount"),
|
181
|
+
"clientResponses": item.get("clientResponses"),
|
182
|
+
"relevancyScore": item.get("relevancyScore"),
|
183
|
+
"productId": item.get("productId"),
|
184
|
+
}
|
185
|
+
review['total_reviews'] = total_reviews
|
186
|
+
# Optionally, handle client responses
|
187
|
+
if review["clientResponses"]:
|
188
|
+
# For simplicity, concatenate all responses into a single string
|
189
|
+
responses = []
|
190
|
+
for response in review["clientResponses"]:
|
191
|
+
response_text = response.get("response", "")
|
192
|
+
responses.append(response_text.strip())
|
193
|
+
review["clientResponses"] = " | ".join(responses)
|
194
|
+
reviews.append(review)
|
195
|
+
self._logger.info(
|
196
|
+
f"Fetched {len(reviews)} reviews for SKU {sku}."
|
197
|
+
)
|
198
|
+
return reviews
|
199
|
+
|
200
|
+
async def reviews(self):
|
201
|
+
"""reviews.
|
202
|
+
|
203
|
+
Target Product Reviews.
|
204
|
+
"""
|
205
|
+
httpx_cookies = httpx.Cookies()
|
206
|
+
for key, value in self.cookies.items():
|
207
|
+
httpx_cookies.set(
|
208
|
+
key, value,
|
209
|
+
domain='.lowes.com',
|
210
|
+
path='/'
|
211
|
+
)
|
212
|
+
|
213
|
+
# Iterate over each row in the DataFrame
|
214
|
+
print('starting ...')
|
215
|
+
|
216
|
+
tasks = [
|
217
|
+
self._product_reviews(
|
218
|
+
idx,
|
219
|
+
row,
|
220
|
+
httpx_cookies
|
221
|
+
) for idx, row in self.data.iterrows()
|
222
|
+
]
|
223
|
+
# Gather results concurrently
|
224
|
+
all_reviews_nested = await self._processing_tasks(tasks)
|
225
|
+
|
226
|
+
# Flatten the list of lists
|
227
|
+
all_reviews = [review for reviews in all_reviews_nested for review in reviews]
|
228
|
+
|
229
|
+
# Convert to DataFrame
|
230
|
+
reviews_df = pd.DataFrame(all_reviews)
|
231
|
+
|
232
|
+
# at the end, adding a column for origin of reviews:
|
233
|
+
reviews_df['origin'] = 'lowes'
|
234
|
+
|
235
|
+
# show the num of rows in final dataframe:
|
236
|
+
self._logger.notice(
|
237
|
+
f"Ending Product Reviews: {len(reviews_df)}"
|
238
|
+
)
|
239
|
+
|
240
|
+
# Override previous dataframe:
|
241
|
+
self.data = reviews_df
|
242
|
+
|
243
|
+
# return existing data
|
244
|
+
return self.data
|
245
|
+
|
246
|
+
@backoff.on_exception(
|
247
|
+
backoff.expo,
|
248
|
+
(httpx.ConnectTimeout, httpx.HTTPStatusError),
|
249
|
+
max_tries=2,
|
250
|
+
giveup=lambda e: not bad_gateway_exception(e) and not isinstance(e, httpx.ConnectTimeout)
|
251
|
+
)
|
252
|
+
async def _product_details(self, idx, row, cookies):
|
253
|
+
async with self.semaphore:
|
254
|
+
# Prepare payload for the API request
|
255
|
+
sku = row['sku']
|
256
|
+
storeid = row['store_id']
|
257
|
+
zipcode = row['zipcode'],
|
258
|
+
state_code = row['state_code']
|
259
|
+
payload = {
|
260
|
+
"nearByStore": storeid,
|
261
|
+
"zipState": state_code,
|
262
|
+
"quantity": 1
|
263
|
+
}
|
264
|
+
try:
|
265
|
+
# url = "https://www.lowes.com/lowes-proxy/wpd/1000379005/productdetail/1845/Guest/60639?nearByStore=1845&zipState=IL&quantity=1"
|
266
|
+
# url = f"https://www.lowes.com/wpd/{sku}/productdetail/{storeid}/Guest/{zipcode}"
|
267
|
+
url = f"https://www.lowes.com/lowes-proxy/wpd/{sku}/productdetail/{storeid}/Guest/{zipcode}"
|
268
|
+
result = await self.api_get(
|
269
|
+
url=url,
|
270
|
+
# cookies=cookies,
|
271
|
+
# params=payload,
|
272
|
+
headers=self.headers
|
273
|
+
)
|
274
|
+
if not result:
|
275
|
+
self._logger.warning(
|
276
|
+
f"No Product Details found for {sku}."
|
277
|
+
)
|
278
|
+
return []
|
279
|
+
# Extract the product details data from the API response
|
280
|
+
print('RESULT > ', result)
|
281
|
+
except (httpx.TimeoutException, httpx.HTTPError) as ex:
|
282
|
+
self._logger.warning(f"Request failed: {ex}")
|
283
|
+
return []
|
284
|
+
except Exception as ex:
|
285
|
+
print(ex)
|
286
|
+
self._logger.error(f"An error occurred: {ex}")
|
287
|
+
return []
|
288
|
+
|
289
|
+
async def product_details(self):
|
290
|
+
"""product_details.
|
291
|
+
|
292
|
+
Get Product Details from Lowes URL.
|
293
|
+
"""
|
294
|
+
self.cookies = {}
|
295
|
+
httpx_cookies = httpx.Cookies()
|
296
|
+
for key, value in self.cookies.items():
|
297
|
+
httpx_cookies.set(
|
298
|
+
key, value,
|
299
|
+
domain='.lowes.com',
|
300
|
+
path='/'
|
301
|
+
)
|
302
|
+
|
303
|
+
# Iterate over each row in the DataFrame
|
304
|
+
print('starting ...')
|
305
|
+
|
306
|
+
tasks = [
|
307
|
+
self._product_details(
|
308
|
+
idx,
|
309
|
+
row,
|
310
|
+
httpx_cookies
|
311
|
+
) for idx, row in self.data.iterrows()
|
312
|
+
]
|
313
|
+
# Gather results concurrently
|
314
|
+
all_products_nested = await self._processing_tasks(tasks)
|
315
|
+
|
316
|
+
# Flatten the list of lists
|
317
|
+
all_products = [product for products in all_products_nested for product in products]
|
318
|
+
|
319
|
+
# Convert to DataFrame
|
320
|
+
_df = pd.DataFrame(all_products)
|
321
|
+
|
322
|
+
# at the end, adding a column for origin of reviews:
|
323
|
+
_df['origin'] = 'lowes'
|
324
|
+
|
325
|
+
# show the num of rows in final dataframe:
|
326
|
+
self._logger.notice(
|
327
|
+
f"Ending Product Details: {len(_df)}"
|
328
|
+
)
|
329
|
+
|
330
|
+
# Override previous dataframe:
|
331
|
+
self.data = _df
|
332
|
+
|
333
|
+
# return existing data
|
334
|
+
return self.data
|