small updates

This commit is contained in:
tpv committed 2026-06-19 10:18:23 -07:00
1 parent 25632e1938
commit 965af7e4ee
5 files changed
+57 -16

No files matched your search

+18 -6
View File
@@ -1,6 +1,8 @@
import json import json
from enum import Enum from enum import Enum
from functools import cache from functools import cache
import pathlib
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
from monitor_utils.constants import PROXIES_PATH, PROJECT_ROOT from monitor_utils.constants import PROXIES_PATH, PROJECT_ROOT
@@ -22,6 +24,13 @@ class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=(".env", ".env.local"), extra="ignore") model_config = SettingsConfigDict(env_file=(".env", ".env.local"), extra="ignore")
@property
def bot_token(self) -> str:
if self.development_mode == DevelopmentMode.PRODUCTION:
return self.production_bot_token
return self.development_bot_token
@cache @cache
def get_settings() -> Settings: def get_settings() -> Settings:
@@ -39,11 +48,14 @@ def get_proxies() -> list[str]:
return proxies return proxies
def get_config(config_name: str, use_production_path: bool = False) -> dict: def get_config(
config_name = config_name.replace(".json", "") monitor_name: str, use_production_path: bool = False
) -> tuple[dict, pathlib.Path]:
monitor_name = monitor_name.replace(".json", "")
if use_production_path: if use_production_path:
config_path = PROJECT_ROOT / "configs" / f"{config_name}.json" config_path = PROJECT_ROOT / "configs" / f"{monitor_name}.json"
else: else:
config_path = PROJECT_ROOT / "tests" / "configs" / f"{config_name}.json" config_path = PROJECT_ROOT / "tests" / "configs" / f"{monitor_name}.json"
with open(config_path) as config:
return json.load(config) with open(config_path) as f:
return json.load(f), config_path
+8
View File
@@ -11,6 +11,10 @@ PROXIES_PATH = pathlib.Path("proxies.txt")
RETRY_LIMIT = 5 RETRY_LIMIT = 5
RETRY_WAIT = 3 RETRY_WAIT = 3
BACKOFF_DELAY = 0.001
FAILURE_DELAY = 0.001
MAX_BACKOFF = 10
TIMEOUT_SECONDS = 30 TIMEOUT_SECONDS = 30
CLIENT_IDENTIFIERS = ["chrome", "chrome_android", "safari", "safari_ios"] CLIENT_IDENTIFIERS = ["chrome", "chrome_android", "safari", "safari_ios"]
CLIENT_TIMEOUT = aiohttp.ClientTimeout( CLIENT_TIMEOUT = aiohttp.ClientTimeout(
@@ -32,6 +36,10 @@ BUCKET_WEBHOOK_MAP = {
"price_drop": "40-49", "price_drop": "40-49",
} }
MONITOR_NAMES = ["bestbuy", "woot", "lg", "hp", "samsung"] MONITOR_NAMES = ["bestbuy", "woot", "lg", "hp", "samsung"]
STATUS_PENDING = "pending"
STATUS_SENT = "sent"
STATUS_SKIPPED = "skipped"
STATUS_FAILED = "failed"
EXPECTED_TASK_ERRORS = ( EXPECTED_TASK_ERRORS = (
RequestsError, RequestsError,
+4 -5
View File
@@ -55,20 +55,20 @@ async def add_products_to_database(
for product in products: for product in products:
document = convert_product_to_mongo(product, category_identifier) document = convert_product_to_mongo(product, category_identifier)
doc_id = document["_id"] doc_id = document["_id"]
old_price = existing_prices.get(doc_id) old_price = existing_prices.get(doc_id)
new_price = document.get("currentPrice") new_price = document.get("currentPrice")
price_dropped = old_price is None or ( price_dropped = old_price is None or (
new_price is not None and new_price < old_price new_price is not None and new_price < old_price
) )
if price_dropped: if price_dropped:
document["webhookSent"] = False document["status"] = STATUS_PENDING
document["priceBeforeDrop"] = old_price document["priceBeforeDrop"] = old_price
set_on_insert = {"discoveredDate": datetime.now(timezone.utc)} set_on_insert = {"discoveredDate": datetime.now(timezone.utc)}
if not price_dropped: if not price_dropped:
set_on_insert["webhookSent"] = False set_on_insert["status"] = STATUS_PENDING
ops.append( ops.append(
UpdateOne( UpdateOne(
@@ -81,7 +81,7 @@ async def add_products_to_database(
try: try:
await collection.bulk_write(ops, ordered=False) await collection.bulk_write(ops, ordered=False)
except PyMongoError: except PyMongoError:
LOGGER.exception(f"{category_identifier} - Failed to add products to database!") LOGGER.exception(f"{category_identifier} - failed to add products to database")
raise raise
@@ -91,7 +91,6 @@ def convert_product_to_mongo(product: Product, category_identifier: str) -> dict
timing = data["timing"] timing = data["timing"]
pricing = data["pricing"] pricing = data["pricing"]
availability = data["availability"] availability = data["availability"]
current_price = pricing["current_price"] current_price = pricing["current_price"]
original_price = pricing["original_price"] original_price = pricing["original_price"]
if isinstance(current_price, str): if isinstance(current_price, str):
+23 -1
View File
@@ -3,6 +3,28 @@ from dataclasses import field
from pydantic.dataclasses import dataclass from pydantic.dataclasses import dataclass
@dataclass
class ConsumerConfig:
# used claude to refactor the consumer building in the discord bot
"""Configuration for a single retailer's consumer.
Attributes:
monitor_name: Retailer name (matches setup_config key).
collection: MongoDB collection for this retailer's products.
channel_ids: Discord webhook/channel routing config.
min_percentage: Minimum discount % to trigger a ping.
ping_threshold: Price floor for the secondary 40% rule (e.g. Woot pings
on 40%+ discounts if the item is >= this price). None disables
the secondary rule.
"""
monitor_name: str
collection: object
channel_ids: dict
min_percentage: int
ping_threshold: int | None = None
@dataclass @dataclass
class PricingInfo: class PricingInfo:
original_price: float original_price: float
@@ -43,7 +65,7 @@ class Product:
product_id: str product_id: str
title: str title: str
url: str url: str
pricing: PricingInfo
metadata: ProductMetadata = field(default_factory=ProductMetadata) metadata: ProductMetadata = field(default_factory=ProductMetadata)
timing: ProductTiming = field(default_factory=ProductTiming) timing: ProductTiming = field(default_factory=ProductTiming)
pricing: PricingInfo = field(default_factory=PricingInfo)
availability: AvailabilityInfo = field(default_factory=AvailabilityInfo) availability: AvailabilityInfo = field(default_factory=AvailabilityInfo)
+4 -4
View File
@@ -33,16 +33,16 @@ def configure_logging():
) )
def setup_config(config_name: str) -> tuple[list[str], dict, AsyncIOMotorCollection]: def setup_config(monitor_name: str) -> tuple[list[str], dict, AsyncIOMotorCollection]:
is_production = get_settings().development_mode == "PRODUCTION" is_production = get_settings().development_mode == "PRODUCTION"
config = get_config( config, _ = get_config(
config_name=config_name, monitor_name=monitor_name,
use_production_path=is_production, use_production_path=is_production,
) )
category_names = config["categories"] category_names = config["categories"]
collection = get_collection( collection = get_collection(
config_name if is_production else f"{config_name}_testing" monitor_name if is_production else f"{monitor_name}_testing"
) )
return category_names, config, collection return category_names, config, collection