"""Refresh listings_search_mv materialized view (daily 3 AM). Currently tradein-mvp has no Celery infrastructure (verified — no celery_app.py exists in tradein-mvp/backend/app/). This module provides both: - A Celery task wrapper (active only if celery is installed and celery_app present). - A plain async refresh_search_matview() callable for cron / one-shot invocation. NEEDS COORDINATION: main session must decide whether to bootstrap Celery in tradein-mvp or run via external scheduler (systemd timer / OS cron). Until then, no Beat schedule. """ from __future__ import annotations import logging import time import psycopg from app.core.config import settings logger = logging.getLogger(__name__) def refresh_search_matview() -> None: """REFRESH MATERIALIZED VIEW CONCURRENTLY listings_search_mv (psycopg v3, sync). Logs duration. Idempotent. Safe to run during read traffic (CONCURRENTLY). """ start = time.monotonic() # DATABASE_URL is SQLAlchemy dialect form (postgresql+psycopg://...) in tradein-mvp # (see tradein-mvp/docker-compose.prod.yml). libpq / psycopg.connect() accepts only # postgresql:// or postgres:// — strip the +psycopg dialect prefix. dsn = settings.database_url.replace("postgresql+psycopg://", "postgresql://", 1) with psycopg.connect(dsn, autocommit=True) as conn: with conn.cursor() as cur: cur.execute("REFRESH MATERIALIZED VIEW CONCURRENTLY listings_search_mv") elapsed = time.monotonic() - start logger.info("listings_search_mv refresh completed in %.2fs", elapsed) # Celery-task wrapper — only registers if celery_app exists. try: from app.celery_app import celery_app # type: ignore[import-not-found] except ImportError: logger.info( "Celery not configured in tradein-mvp — refresh_search_matview() callable directly. " "Bootstrap app/celery_app.py to enable scheduled execution.", ) else: @celery_app.task(name="tradein.refresh_search_matview") # type: ignore[misc] def refresh_search_matview_task() -> None: """Celery wrapper for refresh_search_matview().""" refresh_search_matview() # ============================================================================= # Beat schedule snippet — paste into app/celery_app.py if/when Celery is bootstrapped: # # from celery.schedules import crontab # # celery_app.conf.beat_schedule = { # 'tradein-refresh-matview': { # 'task': 'tradein.refresh_search_matview', # 'schedule': crontab(hour=3, minute=0), # }, # # ... other tradein beat entries # } # # Per master plan sec 10.1 ('refresh-search-matview-daily': crontab(minute=0, hour=3)). # =============================================================================