from __future__ import annotations import os import sys import threading import uuid from pathlib import Path import pytest from imagefind import api as api_module from imagefind.database import Database @pytest.fixture(autouse=True) def _isolated_postgres_database(monkeypatch): """Give every distinct test data directory its own PostgreSQL database. Set ``IMAGEFIND_TEST_POSTGRES_ADMIN_DSN`` to a PostgreSQL/pgvector server where the configured user may create and drop databases. Tests which do not open ``Database`` remain usable without that environment variable. """ admin_dsn = os.environ.get("IMAGEFIND_TEST_POSTGRES_ADMIN_DSN", "").strip() if not admin_dsn: yield return import psycopg from psycopg import sql from psycopg.conninfo import conninfo_to_dict admin_parameters = conninfo_to_dict(admin_dsn) admin_parameters.setdefault("dbname", "postgres") configurations: dict[Path, tuple[str, Path]] = {} guard = threading.Lock() def postgres_conf(database: Database) -> Path: data_dir = database.path.parent.resolve() with guard: existing = configurations.get(data_dir) if existing: return existing[1] database_name = f"imagefind_test_{uuid.uuid4().hex[:24]}" with psycopg.connect(**admin_parameters, autocommit=True) as admin: admin.execute(sql.SQL("CREATE DATABASE {}").format(sql.Identifier(database_name))) conf_path = data_dir / ".postgres-client.conf" conf_path.parent.mkdir(parents=True, exist_ok=True) conf_path.write_text( "\n".join( ( f"host={admin_parameters.get('host', '127.0.0.1')}", f"port={admin_parameters.get('port', '5432')}", f"database={database_name}", f"username={admin_parameters.get('user', 'postgres')}", f"password={admin_parameters.get('password', '')}", f"sslmode={admin_parameters.get('sslmode', 'disable')}", "", ) ), encoding="utf-8", ) configurations[data_dir] = (database_name, conf_path) return conf_path monkeypatch.setattr(Database, "postgres_conf_path", property(postgres_conf)) try: yield finally: for database_name, _ in configurations.values(): with psycopg.connect(**admin_parameters, autocommit=True) as admin: admin.execute( sql.SQL("DROP DATABASE IF EXISTS {} WITH (FORCE)").format( sql.Identifier(database_name) ) ) @pytest.fixture(autouse=True) def _python_313_asgi_thread_compatibility(monkeypatch): """Avoid a Python 3.13 sandbox-only ASGI selector deadlock. Production fnOS uses Python 3.12 and keeps blocking API reads in worker threads. The repository test sandbox can deadlock when ASGITransport awaits ``asyncio.to_thread`` on 3.13, so tests execute only this wrapper inline. """ if sys.version_info < (3, 13): return async def inline(function, /, *args, **kwargs): return function(*args, **kwargs) monkeypatch.setattr(api_module, "_background_api", inline)