Module — Développement local
Setup pour faire tourner better-search sur un poste dev. Le tooling Python utilise uv (installeur déterministe rapide).
Prérequis
- Python 3.13 (managé par
uv, pas besoin d'install système). - Docker + Docker Compose (pour Postgres + pgvector).
uv(curl -LsSf https://astral.sh/uv/install.sh | shou viabrew/apt).- Accès aux credentials Infomaniak (pour ingestion réelle). En leur absence, le service plante au boot (
assert os.getenv(var, '') != ''dansapp.py).
Boot rapide
# 1. Récupérer les secrets (Infomaniak PID + token)
echo "<pid>" > infomaniak_pid.txt # ou via .env
echo "<token>" > infomaniak_token.txt
# 2. Créer un .env avec les vars que docker-compose.yml lit
cat > .env <<EOF
INFOMANIAK_PID=<pid>
INFOMANIAK_TOKEN=<token>
EOF
# 3. Build et démarrage
docker compose up --build
# 4. Test
curl http://localhost:8088/
# {"app-name": "better-search", "datetime": "...", ...}
curl 'http://localhost:8088/travels?size=3'L'image build, Postgres démarre, le service applique les migrations yoyo, puis lance un reindex initial (puisque la DB est vide). Quelques dizaines de secondes pour avoir un index complet.
Stack Compose (docker-compose.yml)
- Service
postgres:pgvector/pgvector:pg17. Userbetter_search/ passwordbetter_search_pwd/ DBbetter_search. Pas de volume persistant — toute la DB part audocker compose down. - Service
better-search: build local, mount.:/app(live source code). - Ports :
8088 → 80côté API,5432 → 5432côté Postgres. - Vars d'env :
SLOW_SEARCH_URL=https://horizon.buchard.ch/api/travels(par défaut, peut pointer ailleurs en.env).WITH_DOCS=true— Swagger UI accessible surhttp://localhost:8088/docs.DEFAULT_RRF_VECTOR_WEIGHT=0.4,DEFAULT_SEMANTIC_FLOOR=0.5,DEFAULT_TSVECTOR_FLOOR=0.0— réglages dev plus permissifs.REINDEX_FREQUENCY=900— 15 min.
Workflow d'itération
Modifier du Python
Le volume - .:/app partage le code, mais FastAPI ne reload pas à chaud (lancé via fastapi run, pas fastapi dev). Deux options :
- Redémarrer le conteneur :
docker compose restart better-search. - Lancer FastAPI en local plutôt que conteneur, contre la DB Compose :shAvec
uv sync # installe les deps export DATABASE_URL=postgresql://better_search:better_search_pwd@localhost:5432/better_search export SLOW_SEARCH_URL=https://horizon.buchard.ch/api/travels export INFOMANIAK_PID=<...> export INFOMANIAK_TOKEN=<...> uv run fastapi dev app.py --port 8088dev, FastAPI watch les fichiers et reload.
Modifier une migration
# Appliquer la nouvelle migration sur la DB dev
uv run yoyo apply --database "postgresql://better_search:better_search_pwd@localhost:5432/better_search" ./migrations --batch
# Vérifier le schéma
docker compose exec postgres psql -U better_search -d better_search -c '\d travels'Si la migration touche les colonnes générées, faire un reindex pour rebuild les vecteurs / search_vector / etc. :
curl http://localhost:8088/reindexRepartir d'une DB propre
docker compose down -v # supprime les containers + le volume
docker compose up --build # repart de zéro (migrations + reindex)(Pas de volume persistant en dev, donc down suffit en fait — -v est par habitude.)
Tests
DB de test sur un autre port (5433) pour cohabiter avec le dev.
# 1. Lancer la DB test
docker compose -f docker-compose-test.yml up -d
# 2. Appliquer les migrations
uv run yoyo apply --database "postgresql://better_search:better_search_pwd@localhost:5433/better_search_test" ./migrations --batch
# 3. Lancer les tests
uv sync --group test
uv run pytest tests/ -vOu en une commande :
docker compose -f docker-compose-test.yml up -d && \
uv sync --group test && \
uv run yoyo apply --database "postgresql://better_search:better_search_pwd@localhost:5433/better_search_test" ./migrations --batch && \
uv run pytest tests/ -v --cov=src --cov-report=term-missingDétail des tests : modules/testing.md.
Accès psql
# Dev
docker compose exec postgres psql -U better_search -d better_search
# Test
docker compose -f docker-compose-test.yml exec postgres-test psql -U better_search -d better_search_testQuelques commandes utiles :
\dt -- liste des tables
\d travels -- schéma de travels (colonnes générées visibles)
\dv -- liste des vues
SELECT COUNT(*) FROM travels;
SELECT id, name, destination FROM travels_view LIMIT 5;
SELECT id, ts_rank_cd(search_vector, websearch_to_tsquery('french', 'ski')) AS s
FROM travels_view ORDER BY s DESC LIMIT 5;Reset des fixtures de test
Les fixtures test_embeddings.json et test_query_embeddings.json contiennent des embeddings réels capturés à un moment donné. Pour les regénérer (si on change de modèle d'embedding) :
# Script à écrire — pas fourni dans le repo. Schéma :
# pour chaque travel dans test_db.json:
# emb = infomaniak.get_embedding_text(travel) → vecteur
# write to test_embeddings.json
# pour chaque query string testée:
# emb = infomaniak.get_embedding(query) → vecteur
# write to test_query_embeddings.jsonÀ ce jour, les fixtures n'ont pas été regénérées depuis le baseline. Si on change de modèle ou de service d'embedding, ça devient un blocker.
Pièges fréquents
assertau boot :app.pyplante siSLOW_SEARCH_URL,EMBEDDING_URL,INFOMANIAK_PID,INFOMANIAK_TOKENne sont pas définis. Pas un message clair — c'est juste unAssertionError. Le.envdoit fournir ces 4 vars.db = BuchardDatabase()au top-level : importersrc.dbdéclenche un reindex si la DB est stale. Les tests utilisentauto_reindex=False.- Port 5432 occupé : si vous avez un Postgres système qui tourne déjà, conflits. Soit
sudo service postgresql stop, soit changer le port mapping dansdocker-compose.yml. - Image stale :
docker compose up --buildrebuild seulement siDockerfileoupyproject.tomlchange. Pour forcer un rebuild complet :docker compose build --no-cache better-search. uv.lockdésynchro : après unuv add, commit le lock. Sinon CI plante (--frozen).- Live edit en conteneur : grâce au mount
-.:/app, les fichiers édités sont visibles dans le conteneur. Maisfastapi runne reload pas —restartrequis. Mieux :fastapi deven local (cf. plus haut).

