Acquisition, normalization, versioning, and persistence for Riftbound data.
Project description
riftbound-database
riftbound-database est la librairie d'acquisition, de normalisation,
d'historisation et de persistance des données Riftbound.
Le projet suit cette direction de dépendances :
riftbound-core
↑ utilisé par
riftbound-database
↓ utilise
baobab-database
Le modèle métier canonique appartient à riftbound-core. Ce dépôt ne doit pas
redéfinir les cartes, sets, domaines, types, tags, coûts ou textes de carte.
L'accès aux moteurs, sessions et transactions SQLAlchemy sera encapsulé derrière
un adaptateur local de baobab-database.
Prérequis
- Python 3.12 ou 3.13 ;
- PostgreSQL pour l'intégration, SQLite étant réservé aux tests rapides.
Installation de développement
python -m venv .venv
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
La dépendance riftbound-core est résolue depuis PyPI (>=0.1.1,<0.2).
Configuration
| Variable | Obligatoire | Valeur par défaut | Rôle |
|---|---|---|---|
RIFTBOUND_DATABASE_URL |
Non au bootstrap | aucune | URL PostgreSQL ; obligatoire en production et pour l'API |
RIFTBOUND_STORAGE_PATH |
Non | storage |
Racine du stockage local |
RIFTBOUND_LOG_LEVEL |
Non | INFO |
Niveau de logs Python |
RIFTBOUND_ENVIRONMENT |
Non | development |
Nom de l'environnement d'exécution |
RIFTBOUND_SYNC_JOB_STALE_AFTER_SECONDS |
Non | 300 |
Seuil (secondes) sans heartbeat avant qu'un job RUNNING soit considéré stale |
RIFTBOUND_TEST_POSTGRESQL_URL |
Tests E2E | aucune | URL PostgreSQL pour pytest -m postgresql |
Les secrets éventuellement présents dans RIFTBOUND_DATABASE_URL ne doivent
jamais être écrits dans les logs. Les fichiers .env sont ignorés par Git.
Provider de base de données
Les moteurs, sessions et transactions passent exclusivement par
riftbound_database.database.BaobabProvider. PostgreSQL utilise
baobab-database 2.0.0 avec le driver psycopg; SQLite est limité aux tests
sur fichier temporaire.
L'adaptation complète et les options SSL supportées sont documentées dans
docs/database-provider.md.
Modèles et migrations
Les modèles SQLAlchemy vivent sous riftbound_database.database.models. Les
mappers domaine ↔ persistance sont sous database/mappers. Alembic est configuré
via alembic.ini et exécutable avec :
python -m alembic upgrade head
Pour les tests locaux sans PostgreSQL, définir RIFTBOUND_MIGRATION_SQLITE_PATH
ou laisser Alembic utiliser la base SQLite par défaut sous .my_cache/.
Repositories
Les écritures applicatives passent par RiftboundUnitOfWork, qui expose les
repositories sans créer de session locale. Les retours sont limités aux objets
riftbound-core ou aux read models sous database/read_models/.
from riftbound_database.database.baobab_provider import BaobabProvider
from riftbound_database.database.unit_of_work import RiftboundUnitOfWork
provider = BaobabProvider.for_sqlite("test.db")
unit_of_work = RiftboundUnitOfWork(provider)
with unit_of_work.transaction() as repositories:
card = repositories.cards.find_by_stable_id("card-001")
job = repositories.sync_jobs.create(
SyncJobCreateInput(type=SyncJobType.CARDS, mode=SyncJobMode.INCREMENTAL),
)
Collections utilisateur et decks (v1.0.0)
La persistance applicative pour riftbound-api est disponible via les mêmes
repositories transactionnels :
with unit_of_work.transaction() as repositories:
collection = repositories.collections.get_or_create_default_collection(user_id)
deck = repositories.decks.create_deck(
user_id,
DeckCreatePayload(name="Competitive", visibility="private"),
)
Guide complet : docs/collections-decks.md (tables,
migrations, intégration API, remplacement des adaptateurs mémoire).
Ingestion
Les imports passent par BaseImporter, qui orchestre fetch, stockage raw,
checksum, batch et persistance via les repositories. SetImporter importe
des fichiers JSON locaux de façon idempotente avec normalisation, checksum
canonique et stockage sous normalized_data/sets/. CardNormalizer produit
des DTO canoniques compatibles avec riftbound-core, calcule un checksum
stable et signale les champs ambigus via le statut needs_review. Les
importers cartes (LocalFileCardImporter, OfficialApiCardImporter,
CardGalleryImporter) normalisent, versionnent et persistent les cartes via
les repositories. Les documents normatifs passent par RulesImporter,
ErrataImporter et BanlistImporter avec normalisation, checksums stables,
historisation (rules_versions, snapshots banlist) et statuts
matched / ambiguous / needs_review. Un FakeImporter est disponible
pour les tests.
Assets
Les images de cartes sont téléchargées par CardAssetDownloader, validées
(MIME, taille minimale, signature binaire) puis stockées sous
{RIFTBOUND_STORAGE_PATH}/card_assets/{set_code}/{card_id}/ avec un
metadata.json par carte. La persistance relationnelle passe par
AssetRepository ; un checksum inchangé évite les écritures inutiles.
from riftbound_database.assets import AssetDownloadRequest, AssetVariant, CardAssetDownloader
from riftbound_database.config.settings import Settings
from riftbound_database.database.baobab_provider import BaobabProvider
from riftbound_database.database.unit_of_work import RiftboundUnitOfWork
settings = Settings()
downloader = CardAssetDownloader()
provider = BaobabProvider.for_sqlite("test.db")
unit_of_work = RiftboundUnitOfWork(provider)
request = AssetDownloadRequest(
set_code="OGN",
card_id="card-unit-001",
variant=AssetVariant.ORIGINAL,
source_url="https://example.test/card.png",
)
with unit_of_work.transaction() as repositories:
result = downloader.download(request, settings, repositories.assets)
Synchronisation
Les jobs sous riftbound_database.sync orchestrent les importers via un
SyncManifest JSON (ADR-0002) : chemins locaux + liste assets[] avec URLs
source explicites. FullSyncJob exécute dans l'ordre
sets → cartes → assets → règles → errata → banlists, avec dry_run et
SyncReport final.
from riftbound_database.sync import FullSyncJob, SyncContext, SyncManifestLoader
manifest = SyncManifestLoader.load(Path("sync.json"))
context = SyncContext(settings=settings, unit_of_work=unit_of_work, manifest=manifest)
report = FullSyncJob().run(context)
Synchronisation pilotée (sync_job, v1.1.0)
Lorsqu'un sync_job_id est fourni, les imports appellent périodiquement
heartbeat() et vérifient is_cancel_requested() via
ImportExecutionSupervisor. L'annulation est coopérative : elle ne tue
pas le processus worker ; le job en cours détecte la demande, interrompt
proprement l'import et passe le sync_job en CANCELLED. Les jobs RUNNING
sans heartbeat récent sont détectables via SyncJobStaleDetector (seuil
configurable par RIFTBOUND_SYNC_JOB_STALE_AFTER_SECONDS).
from riftbound_database.sync import SyncJobStaleDetector
detector = SyncJobStaleDetector(unit_of_work, settings)
stale_jobs = detector.list_stale_jobs()
Concurrence et idempotence
Deux demandes identiques (même type + source) sans force=True retournent le
job actif existant au lieu d'en créer un second silencieusement. Avec
force=True, un nouveau job est créé même si un job actif existe déjà.
from riftbound_database.ingestion.services.sync_job_service import SyncJobService
with unit_of_work.transaction() as repositories:
service = SyncJobService(repositories.sync_jobs)
job = service.create_card_sync_job("admin", {"set_code": "OGN"}, force=False)
same = service.create_card_sync_job("admin", {"set_code": "OGN"}, force=False)
assert job.id == same.id
Guide complet : docs/sync-jobs.md (modèle, CLI, frontière
API/Web, checklist intégration riftbound-api).
CLI
La CLI Typer est exposée via le script riftbound-database (ou
python -m riftbound_database.cli.main). Options globales :
--database-url, --sqlite-path, --storage-path, --log-level.
riftbound-database --sqlite-path ./local.db db upgrade
riftbound-database --sqlite-path ./local.db import sets --path ./sets.json
riftbound-database --sqlite-path ./local.db import cards --source local-file --path ./cards.json
riftbound-database --sqlite-path ./local.db import rules --path ./rules.json
riftbound-database --sqlite-path ./local.db import errata --path ./errata.json
riftbound-database --sqlite-path ./local.db import banlist --path ./banlist.json
riftbound-database --sqlite-path ./local.db assets download --manifest ./sync.json
riftbound-database --sqlite-path ./local.db sync full --manifest ./sync.json
riftbound-database --sqlite-path ./local.db sync jobs list
riftbound-database --sqlite-path ./local.db sync jobs status <job-uuid>
riftbound-database --sqlite-path ./local.db sync jobs logs <job-uuid>
riftbound-database --sqlite-path ./local.db sync jobs cancel <job-uuid>
riftbound-database checksums verify
riftbound-database export normalized --format json
Les imports supportent --dry-run et --limit. Les commandes retournent un
code de sortie non nul en cas d'échec ; les secrets de connexion ne sont jamais
loggés.
Qualité
python -m pytest
python -m coverage run -m pytest
python -m coverage report
python -m ruff check .
python -m mypy src tests
python -m bandit -r src
Tests rapides vs intégration
# PostgreSQL local via Docker (optionnel)
docker compose up -d postgres
$env:RIFTBOUND_TEST_POSTGRESQL_URL="postgresql+psycopg://riftbound:riftbound@localhost:5432/riftbound_test"
# unitaires (CI par défaut)
python -m pytest -m "not integration and not postgresql"
# migrations SQLite
python -m pytest tests/integration/riftbound_database/database/migrations/
# PostgreSQL (service local requis)
set RIFTBOUND_TEST_POSTGRESQL_URL=postgresql+psycopg://user:pass@localhost:5432/riftbound_test
python -m pytest -m postgresql
# E2E collections/decks PostgreSQL uniquement
python -m pytest tests/integration/riftbound_database/database/test_collections_decks_e2e_postgresql.py -q
# E2E sync jobs PostgreSQL uniquement
python -m pytest tests/integration/riftbound_database/sync/test_sync_jobs_e2e_postgresql.py -q
# tests contractuels (BL-027)
python -m pytest -m contract -q
Voir docs/v1.0.0/contract-tests.md,
docs/v1.0.0/e2e-postgresql.md,
docs/v1.0.0/collections-decks.md et
docs/sync-jobs.md.
Couverture minimale configurée : 85 % (pyproject.toml).
CI GitHub Actions
La suite de tests est factorisée dans .github/workflows/reusable-tests.yml
et invoquée par :
- CI (
.github/workflows/ci.yml) : chaque push/PR versmainourelease/**; - Release readiness (
.github/workflows/release-readiness.yml) : déclenchement manuel ou sur branchesrelease/**— exécute la même suite puis génère les snapshotsintegration-snapshot.mdetanalysis-snapshot.md(artefacts) ; - Release (
.github/workflows/release.yml) : sur tagv*.*.*— tests PostgreSQL, gates bloquants (alignement tag /__version__, section CHANGELOG datée, guide de migration si nouvelles révisions Alembic, version absente de PyPI), puis build et publication PyPI.
Jobs de la suite partagée :
- qualité : ruff, mypy, bandit ;
- unitaires : pytest + couverture (Python 3.12 et 3.13) ;
- intégration : migrations SQLite + scénarios PostgreSQL via service container.
Les dépendances (riftbound-core, baobab-database) sont installées depuis PyPI ;
aucun secret GitHub n'est requis pour la CI si tous les packages restent publics.
Documentation projet
Le cahier des charges et le découpage fonctionnel sont disponibles dans
docs/.
Guides opérationnels :
docs/collections-decks.md— intégration collections/decks (v1.0.0) ;docs/sync-jobs.md— synchronisation pilotée et frontière API (v1.1.0) ;docs/MIGRATION-1.1.0.md— migration Alembic 1.0.0 → 1.1.0 ;docs/contract-tests.md— tests contractuels ;docs/e2e-postgresql.md— E2E PostgreSQL ;docs/api-port-compatibility.md— contrat ports API ;
Le catalogue initial a été livré via les backlogs BL-001 à BL-014. L'épopée
collections/decks (BL-015 → BL-029) clôt la version 1.0.0 ; l'épopée
sync jobs (BL-030 → BL-042) clôt la version 1.1.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file riftbound_database-1.1.0.tar.gz.
File metadata
- Download URL: riftbound_database-1.1.0.tar.gz
- Upload date:
- Size: 324.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4900133efeb7184aad03b8181b858ef7a4fe41ddd6eb84c79710aef6841468bd
|
|
| MD5 |
01227f07713272e6c5fd017fdc1a76c8
|
|
| BLAKE2b-256 |
4efa999b87a501f3265b13568932e441bf805d5fe0519051ec862b0d8d2e8f7c
|
Provenance
The following attestation bundles were made for riftbound_database-1.1.0.tar.gz:
Publisher:
release.yml on baobabgit/riftbound-database
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
riftbound_database-1.1.0.tar.gz -
Subject digest:
4900133efeb7184aad03b8181b858ef7a4fe41ddd6eb84c79710aef6841468bd - Sigstore transparency entry: 1842265085
- Sigstore integration time:
-
Permalink:
baobabgit/riftbound-database@2ca4b79bc3e7924a7cd66680a4cf2b0d756626a0 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/baobabgit
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2ca4b79bc3e7924a7cd66680a4cf2b0d756626a0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file riftbound_database-1.1.0-py3-none-any.whl.
File metadata
- Download URL: riftbound_database-1.1.0-py3-none-any.whl
- Upload date:
- Size: 209.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6384875de9d4eadc296a8d857d68e2bd060aabbc39198cd3d6464a384d946355
|
|
| MD5 |
26eefdf8ba8f8e0027b26164d7df8ec1
|
|
| BLAKE2b-256 |
f7ef9cf29eaace0c106922a3c15fa11901a650e96eba8c5ce6666f2977db787c
|
Provenance
The following attestation bundles were made for riftbound_database-1.1.0-py3-none-any.whl:
Publisher:
release.yml on baobabgit/riftbound-database
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
riftbound_database-1.1.0-py3-none-any.whl -
Subject digest:
6384875de9d4eadc296a8d857d68e2bd060aabbc39198cd3d6464a384d946355 - Sigstore transparency entry: 1842265749
- Sigstore integration time:
-
Permalink:
baobabgit/riftbound-database@2ca4b79bc3e7924a7cd66680a4cf2b0d756626a0 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/baobabgit
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2ca4b79bc3e7924a7cd66680a4cf2b0d756626a0 -
Trigger Event:
push
-
Statement type: