""" Configuration management for mycopunk CLI. """ import os from pathlib import Path from typing import Optional, Any import yaml from dotenv import load_dotenv def get_project_root() -> Path: """Find the project root (directory containing designs/).""" current = Path.cwd() while current != current.parent: if (current / "designs").is_dir(): return current current = current.parent return Path.cwd() PROJECT_ROOT = get_project_root() CONFIG_DIR = PROJECT_ROOT / "config" class Config: """Configuration manager for mycopunk.""" _instance: Optional["Config"] = None def __new__(cls) -> "Config": if cls._instance is None: cls._instance = super().__new__(cls) cls._instance._initialized = False return cls._instance def __init__(self) -> None: if self._initialized: return self._initialized = True self._products: dict = {} self._pricing: dict = {} # Load environment variables env_path = CONFIG_DIR / ".env" if env_path.exists(): load_dotenv(env_path) # Load product configuration products_path = CONFIG_DIR / "products.yaml" if products_path.exists(): with open(products_path) as f: self._products = yaml.safe_load(f) or {} # Load pricing configuration pricing_path = CONFIG_DIR / "pricing.yaml" if pricing_path.exists(): with open(pricing_path) as f: self._pricing = yaml.safe_load(f) or {} @property def env(self) -> str: """Get current environment (development/production).""" return os.getenv("MYCOPUNK_ENV", "development") @property def is_production(self) -> bool: """Check if running in production mode.""" return self.env == "production" @property def debug(self) -> bool: """Check if debug mode is enabled.""" return os.getenv("DEBUG", "false").lower() == "true" @property def default_provider(self) -> str: """Get default POD provider.""" return os.getenv("DEFAULT_PROVIDER", "prodigi") # API Keys @property def printful_token(self) -> Optional[str]: """Get Printful API token.""" return os.getenv("PRINTFUL_API_TOKEN") @property def prodigi_key(self) -> Optional[str]: """Get Prodigi API key (sandbox or live based on env).""" if self.is_production: return os.getenv("PRODIGI_API_KEY_LIVE") return os.getenv("PRODIGI_API_KEY_SANDBOX") @property def stripe_key(self) -> Optional[str]: """Get Stripe secret key.""" return os.getenv("STRIPE_SECRET_KEY") # Product Configuration def get_product_config(self, product_type: str, size: str = "small") -> dict: """Get product configuration by type and size.""" type_config = self._products.get(product_type, {}) return type_config.get(size, {}) def get_provider_sku( self, product_type: str, size: str, provider: str ) -> Optional[str]: """Get provider-specific SKU for a product.""" config = self.get_product_config(product_type, size) providers = config.get("providers", {}) provider_config = providers.get(provider, {}) return provider_config.get("sku") def get_base_cost( self, product_type: str, size: str, provider: str, variant: Optional[str] = None ) -> float: """Get base cost for a product from provider.""" config = self.get_product_config(product_type, size) providers = config.get("providers", {}) provider_config = providers.get(provider, {}) base_cost = provider_config.get("base_cost", 0) # Handle size-specific pricing (apparel) if isinstance(base_cost, dict) and variant: return base_cost.get(variant, 0) return float(base_cost) if not isinstance(base_cost, dict) else 0 # Pricing def calculate_retail_price( self, base_cost: float, product_type: str ) -> float: """Calculate retail price from base cost with markup.""" pricing = self._pricing or self._products.get("pricing", {}) rules = pricing.get("rules", {}) type_rules = rules.get(product_type, {}) markup = type_rules.get("markup", pricing.get("default_markup", 2.0)) minimum = type_rules.get("minimum_price", 0) calculated = base_cost * markup price = max(calculated, minimum) # Apply rounding rounding = pricing.get("rounding", "nearest_99") if rounding == "nearest_99": price = round(price) - 0.01 elif rounding == "nearest_50": price = round(price * 2) / 2 return max(price, minimum) # Singleton accessor def get_config() -> Config: """Get the configuration singleton.""" return Config()