refactor(adapters): accept SharedTableProtocol; replace thread-local Store pattern with clone()
This commit is contained in:
parent
9d8b627fe1
commit
80ac13e69f
3 changed files with 28 additions and 7 deletions
|
|
@ -22,7 +22,7 @@ _SHOPPING_API_INTER_REQUEST_DELAY = 0.5 # seconds between successive calls
|
||||||
_SELLER_ENRICH_TTL_HOURS = 24 # skip re-enrichment within this window
|
_SELLER_ENRICH_TTL_HOURS = 24 # skip re-enrichment within this window
|
||||||
|
|
||||||
from app.db.models import Listing, MarketComp, Seller
|
from app.db.models import Listing, MarketComp, Seller
|
||||||
from app.db.store import Store
|
from app.db.protocol import SharedTableProtocol
|
||||||
from app.platforms import PlatformAdapter, SearchFilters
|
from app.platforms import PlatformAdapter, SearchFilters
|
||||||
from app.platforms.ebay.auth import EbayTokenManager
|
from app.platforms.ebay.auth import EbayTokenManager
|
||||||
from app.platforms.ebay.normaliser import normalise_listing, normalise_seller
|
from app.platforms.ebay.normaliser import normalise_listing, normalise_seller
|
||||||
|
|
@ -67,7 +67,7 @@ BROWSE_BASE = {
|
||||||
|
|
||||||
|
|
||||||
class EbayAdapter(PlatformAdapter):
|
class EbayAdapter(PlatformAdapter):
|
||||||
def __init__(self, token_manager: EbayTokenManager, shared_store: Store, env: str = "production"):
|
def __init__(self, token_manager: EbayTokenManager, shared_store: SharedTableProtocol, env: str = "production"):
|
||||||
self._tokens = token_manager
|
self._tokens = token_manager
|
||||||
self._store = shared_store
|
self._store = shared_store
|
||||||
self._env = env
|
self._env = env
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ log = logging.getLogger(__name__)
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from app.db.models import Listing, MarketComp, Seller
|
from app.db.models import Listing, MarketComp, Seller
|
||||||
from app.db.store import Store
|
from app.db.protocol import SharedTableProtocol
|
||||||
from app.platforms import PlatformAdapter, SearchFilters
|
from app.platforms import PlatformAdapter, SearchFilters
|
||||||
|
|
||||||
EBAY_SEARCH_URL = "https://www.ebay.com/sch/i.html"
|
EBAY_SEARCH_URL = "https://www.ebay.com/sch/i.html"
|
||||||
|
|
@ -286,7 +286,7 @@ class ScrapedEbayAdapter(PlatformAdapter):
|
||||||
category_history) cause TrustScorer to set score_is_partial=True.
|
category_history) cause TrustScorer to set score_is_partial=True.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, shared_store: Store, delay: float = 1.0):
|
def __init__(self, shared_store: SharedTableProtocol, delay: float = 1.0):
|
||||||
self._store = shared_store
|
self._store = shared_store
|
||||||
self._delay = delay
|
self._delay = delay
|
||||||
|
|
||||||
|
|
@ -374,8 +374,6 @@ class ScrapedEbayAdapter(PlatformAdapter):
|
||||||
Does not raise — failures per-seller are silently skipped so the main
|
Does not raise — failures per-seller are silently skipped so the main
|
||||||
search response is never blocked.
|
search response is never blocked.
|
||||||
"""
|
"""
|
||||||
db_path = self._store._db_path # capture for thread-local Store creation
|
|
||||||
|
|
||||||
def _enrich_one(item: tuple[str, str]) -> None:
|
def _enrich_one(item: tuple[str, str]) -> None:
|
||||||
seller_id, listing_id = item
|
seller_id, listing_id = item
|
||||||
try:
|
try:
|
||||||
|
|
@ -388,7 +386,7 @@ class ScrapedEbayAdapter(PlatformAdapter):
|
||||||
)
|
)
|
||||||
if age_days is None and fb_count is None:
|
if age_days is None and fb_count is None:
|
||||||
return # nothing new to write
|
return # nothing new to write
|
||||||
thread_store = Store(db_path)
|
thread_store = self._store.clone()
|
||||||
seller = thread_store.get_seller("ebay", seller_id)
|
seller = thread_store.get_seller("ebay", seller_id)
|
||||||
if not seller:
|
if not seller:
|
||||||
log.warning("BTF enrich: seller %s not found in DB", seller_id)
|
log.warning("BTF enrich: seller %s not found in DB", seller_id)
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,26 @@ def test_store_clone_returns_new_instance(tmp_path):
|
||||||
assert isinstance(clone, Store)
|
assert isinstance(clone, Store)
|
||||||
assert clone is not s
|
assert clone is not s
|
||||||
assert clone._db_path == db
|
assert clone._db_path == db
|
||||||
|
|
||||||
|
|
||||||
|
def test_ebay_adapter_accepts_protocol():
|
||||||
|
from app.platforms.ebay.adapter import EbayAdapter
|
||||||
|
import tempfile
|
||||||
|
import pathlib
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
s = Store(pathlib.Path(tmp) / "t.db")
|
||||||
|
adapter = EbayAdapter(token_manager=MagicMock(), shared_store=s)
|
||||||
|
assert adapter._store is s
|
||||||
|
|
||||||
|
|
||||||
|
def test_scraped_adapter_no_db_path_ref():
|
||||||
|
from app.platforms.ebay.scraper import ScrapedEbayAdapter
|
||||||
|
import tempfile
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
s = Store(pathlib.Path(tmp) / "t.db")
|
||||||
|
adapter = ScrapedEbayAdapter(shared_store=s)
|
||||||
|
assert not hasattr(adapter, '_db_path_ref')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue