This commit is contained in:
zman 2024-03-05 22:22:44 -05:00
parent 0a9ae6460b
commit a60ce0db17
8 changed files with 58 additions and 40 deletions

View File

@ -1,9 +1,14 @@
import requests """
Interacts with the API to handle requests for post and product data.
Utilizes the `requests` library to send requests
"""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
from exceptions import APIRequestError, InvalidDataTypeError, InvalidMethodError import requests
from app_log import LoggingManager from .exceptions import APIRequestError, InvalidDataTypeError, InvalidMethodError
from models import Post from .app_log import LoggingManager
from .models import Post
class ApiRequestHandler: class ApiRequestHandler:
@ -50,16 +55,17 @@ class ApiRequestHandler:
f"Invalid data type: {type(params)} expected dict" f"Invalid data type: {type(params)} expected dict"
) )
try: try:
response = requests.request(method, api_url, data=data, params=params) response = requests.request(
method, api_url, data=data, params=params, timeout=10
)
except requests.RequestException as e: except requests.RequestException as e:
self.log_manager.error(f"API request failed: {e}") self.log_manager.error(f"API request failed: {e}")
raise APIRequestError(0, str(e)) raise APIRequestError(0, str(e)) from e
success_codes = [200, 201, 204] try:
if response.status_code not in success_codes: response.raise_for_status()
self.log_manager.error( except requests.HTTPError as e:
f"API request failed: {response.status_code} - {response.text}" self.log_manager.error(f"API request failed: {e}")
) raise APIRequestError(response.status_code, response.text) from e
raise APIRequestError(response.status_code, response.text)
return response.json() return response.json()
@ -251,7 +257,7 @@ class CostcoProductManager:
api_request_handler (ApiRequestHandler): Handles the API requests for interacting with Costco product data. api_request_handler (ApiRequestHandler): Handles the API requests for interacting with Costco product data.
log_manager (LoggingManager): Manages logging for operations performed by CostcoProductManager. log_manager (LoggingManager): Manages logging for operations performed by CostcoProductManager.
""" """
def __init__(self, api_request_handler: ApiRequestHandler): def __init__(self, api_request_handler: ApiRequestHandler):
""" """
Initializes the CostcoProductManager with an API request handler for making API calls and a logging manager Initializes the CostcoProductManager with an API request handler for making API calls and a logging manager
@ -275,7 +281,7 @@ class CostcoProductManager:
"GET", f"{self.api_request_handler.api_url}costco_products/" "GET", f"{self.api_request_handler.api_url}costco_products/"
) )
return all_products return all_products
def insert_costco_product(self, product) -> dict: def insert_costco_product(self, product) -> dict:
""" """
Inserts a new Costco product into the database through an API call. Inserts a new Costco product into the database through an API call.
@ -320,7 +326,9 @@ class CostcoProductManager:
"active": product.active, "active": product.active,
} }
response = self.api_request_handler.send_api_request( response = self.api_request_handler.send_api_request(
"PUT", f"{self.api_request_handler.api_url}costco_products/{product.sku}/", data=data "PUT",
f"{self.api_request_handler.api_url}costco_products/{product.sku}/",
data=data,
) )
return response return response
@ -338,4 +346,4 @@ class CostcoProductManager:
response = self.api_request_handler.send_api_request( response = self.api_request_handler.send_api_request(
"GET", f"{self.api_request_handler.api_url}costco_products/?sku={sku}" "GET", f"{self.api_request_handler.api_url}costco_products/?sku={sku}"
) )
return response return response

View File

@ -1,6 +1,6 @@
from app_log import LoggingManager from .app_log import LoggingManager
from threads import Scheduler from .threads import Scheduler
from costco import CostcoMonitor from .costco import CostcoMonitor
class Application: class Application:

View File

@ -1,9 +1,15 @@
import logging """
"""
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import sys import sys
import logging
class SingletonMeta(type): class SingletonMeta(type):
"""
A metaclass that creates a Singleton base class when called
"""
_instances = {} _instances = {}
def __call__(cls, *args, **kwargs): def __call__(cls, *args, **kwargs):
@ -13,6 +19,9 @@ class SingletonMeta(type):
class LoggingManager(metaclass=SingletonMeta): class LoggingManager(metaclass=SingletonMeta):
"""
A class that creates a logger object and sets up the logger with file and stream handlers
"""
def __init__(self, log_file): def __init__(self, log_file):
if not hasattr(self, "logger"): if not hasattr(self, "logger"):
self.log_file = log_file self.log_file = log_file

View File

@ -1,12 +1,13 @@
import os
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException from selenium.common.exceptions import TimeoutException
from app_log import LoggingManager from .app_log import LoggingManager
from models import Product from .models import Product
import os
class CostcoMonitor: class CostcoMonitor:

View File

@ -1,9 +1,9 @@
from webhook import WebhookNotifier from .webhook import WebhookNotifier
from app import Application from .app import Application
from api import ApiRequestHandler, PostManager, PostAnalyticsManager, CostcoProductManager from .api import ApiRequestHandler, PostManager, PostAnalyticsManager, CostcoProductManager
from reddit import RedditMonitor, SubmissionManager from .reddit import RedditMonitor, SubmissionManager
from config import Config from .config import Config
from app_log import LoggingManager from .app_log import LoggingManager
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,7 +1,8 @@
import praw import praw
from app_log import LoggingManager from .app_log import LoggingManager
from models import Post from .models import Post
from api import PostManager, PostAnalyticsManager from .api import PostManager, PostAnalyticsManager
from .webhook import WebhookNotifier
class RedditMonitor: class RedditMonitor:
@ -87,7 +88,7 @@ class SubmissionManager:
reddit_monitor: RedditMonitor, reddit_monitor: RedditMonitor,
post_manager: PostManager, post_manager: PostManager,
post_analytics_manager: PostAnalyticsManager, post_analytics_manager: PostAnalyticsManager,
WebhookNotifier, webhook_notifier: WebhookNotifier,
): ):
""" """
Initializes the SubmissionManager with necessary components for processing submissions. Initializes the SubmissionManager with necessary components for processing submissions.
@ -101,7 +102,7 @@ class SubmissionManager:
self.reddit_monitor = reddit_monitor self.reddit_monitor = reddit_monitor
self.post_manager = post_manager self.post_manager = post_manager
self.post_analytics_manager = post_analytics_manager self.post_analytics_manager = post_analytics_manager
self.webhook_notifier = WebhookNotifier self.webhook_notifier = webhook_notifier
self.log_manager = LoggingManager("scraper.log") self.log_manager = LoggingManager("scraper.log")
def convert_submission_to_post(self, submission): def convert_submission_to_post(self, submission):

View File

@ -1,5 +1,4 @@
import threading import threading
import time
class Scheduler: class Scheduler:
def __init__(self): def __init__(self):

View File

@ -1,6 +1,6 @@
import requests import requests
from app_log import LoggingManager from .app_log import LoggingManager
from models import Product, Post from .models import Product, Post
class WebhookNotifier: class WebhookNotifier:
@ -9,7 +9,7 @@ class WebhookNotifier:
self.disable_webhook = disable_webhook self.disable_webhook = disable_webhook
self.log_manager = LoggingManager("scraper.log") self.log_manager = LoggingManager("scraper.log")
def send_notification(self, post): def send_notification(self, post: Post):
title = post.title title = post.title
url = post.url url = post.url
permalink = post.permalink permalink = post.permalink
@ -23,7 +23,7 @@ class WebhookNotifier:
if not self.disable_webhook: if not self.disable_webhook:
self.log_manager.log(f"Sending notification to {self.webhook_url}") self.log_manager.log(f"Sending notification to {self.webhook_url}")
try: try:
requests.post(self.webhook_url, data={"content": content}) requests.post(self.webhook_url, data={"content": content}, timeout=5)
except Exception as e: except Exception as e:
self.log_manager.error(f"Failed to send notification: {e}") self.log_manager.error(f"Failed to send notification: {e}")
@ -42,6 +42,6 @@ class WebhookNotifier:
if not self.disable_webhook: if not self.disable_webhook:
self.log_manager.log(f"Sending notification to {self.webhook_url}") self.log_manager.log(f"Sending notification to {self.webhook_url}")
try: try:
requests.post(self.webhook_url, data={"content": content}) requests.post(self.webhook_url, data={"content": content}, timeout=5)
except Exception as e: except Exception as e:
self.log_manager.error(f"Failed to send notification: {e}") self.log_manager.error(f"Failed to send notification: {e}")