"""Warranty provider abstraction. A provider looks up coverage for a unit by service tag / serial. Phase 1 ships manual entry plus provider stubs that read per-vendor API config from settings. When a real API (Dell TechDirect, Lenovo, HP) is wired later, only the matching provider's lookup() body changes - callers and the API surface stay the same. """ from shopdb.api import db from shopdb.core.models import Setting class ProviderNotConfigured(Exception): """Raised when a provider is asked to look up but has no API config.""" pass def _setting(key, default=None): row = Setting.query.filter_by(key=key).first() return row.value if row and row.value not in (None, '') else default class WarrantyProvider: """Base provider. name matches Warranty.provider values.""" name = 'base' def lookup(self, servicetag, vendor=None): """Return a dict of {servicelevel, startdate, enddate} or None. Raises ProviderNotConfigured when the provider needs API creds it does not have. """ raise NotImplementedError class ManualProvider(WarrantyProvider): """No external lookup - values are entered by hand.""" name = 'manual' def lookup(self, servicetag, vendor=None): return None class ApiProvider(WarrantyProvider): """Common shape for vendor API providers. Reads enabled/url/token from settings under warranty__*. Real HTTP call is deferred to a later phase; today it fails loud if asked to look up so manual entry is unaffected. """ def _config(self): enabled = str(_setting(f'warranty_{self.name}_enabled', 'false')).lower() == 'true' url = _setting(f'warranty_{self.name}_apiurl') token = _setting(f'warranty_{self.name}_apitoken') return enabled, url, token def lookup(self, servicetag, vendor=None): enabled, url, token = self._config() if not (enabled and url and token): raise ProviderNotConfigured( f'{self.name} warranty lookup is not configured. ' f'Set warranty_{self.name}_enabled/apiurl/apitoken in Settings.' ) # Phase 2+: perform the vendor API call here and map the response to # {servicelevel, startdate, enddate}. Until then, signal not-yet-built. raise ProviderNotConfigured( f'{self.name} API lookup not implemented yet (config present).' ) class DellProvider(ApiProvider): name = 'dell' class LenovoProvider(ApiProvider): name = 'lenovo' class HpProvider(ApiProvider): name = 'hp' _PROVIDERS = {p.name: p for p in ( ManualProvider(), DellProvider(), LenovoProvider(), HpProvider() )} def get_provider(name): """Return a provider instance, defaulting to manual for unknown names.""" return _PROVIDERS.get((name or 'manual').lower(), _PROVIDERS['manual']) def provider_names(): return list(_PROVIDERS.keys())