| """Delete the seeded demo data (generated-dot 'dogs' from scripts.make_sample_images) and everything |
| attached to it, leaving the real loaded datasets untouched. |
| |
| Demo records are the ones created directly by scripts/seed.py (NOT via the batch loader), so they |
| have ``dataset_id IS NULL``. We remove: |
| * every known/unknown dog with ``dataset_id IS NULL`` (Rex, Maple, the demo found dogs) + |
| their pictures, embeddings, breed predictions, cases, matches, notifications, and media files; |
| * the demo OWNER users of those dogs — but ONLY non-admin users who own no *dataset* dogs, so the |
| admin account and the (dataset_id-NULL but real) detached dataset owners are preserved. |
| |
| Idempotent. Dry-run by default. |
| |
| Usage (from backend/): python -m scripts.purge_seed_data --delete |
| """ |
| from __future__ import annotations |
|
|
| import argparse |
|
|
| from sqlalchemy import and_, delete, func, or_, select |
|
|
| from app.db import SessionLocal |
| from app.models import ( |
| BreedPrediction, Case, Embedding, KnownDog, Match, Notification, Picture, UnknownDog, User, |
| ) |
| from app.models.base import SubjectType, UserRole |
| from app.storage import get_storage |
|
|
|
|
| def run(do_delete: bool) -> None: |
| db = SessionLocal() |
| try: |
| known_ids = [i for (i,) in db.execute(select(KnownDog.id).where(KnownDog.dataset_id.is_(None)))] |
| unknown_ids = [i for (i,) in db.execute(select(UnknownDog.id).where(UnknownDog.dataset_id.is_(None)))] |
|
|
| |
| owner_ids = { |
| o for (o,) in db.execute( |
| select(KnownDog.owner_id).where(KnownDog.dataset_id.is_(None)).distinct() |
| ) |
| } |
| demo_user_ids = [] |
| for uid in owner_ids: |
| u = db.get(User, uid) |
| if u is None or u.role == UserRole.admin: |
| continue |
| keeps = db.execute( |
| select(func.count()).select_from(KnownDog).where( |
| KnownDog.owner_id == uid, KnownDog.dataset_id.isnot(None) |
| ) |
| ).scalar_one() |
| if keeps == 0: |
| demo_user_ids.append(uid) |
|
|
| |
| case_conds = [] |
| if known_ids: |
| case_conds.append(Case.known_dog_id.in_(known_ids)) |
| if unknown_ids: |
| case_conds.append(Case.unknown_dog_id.in_(unknown_ids)) |
| if demo_user_ids: |
| case_conds.append(Case.person_id.in_(demo_user_ids)) |
| case_ids = ( |
| [i for (i,) in db.execute(select(Case.id).where(or_(*case_conds)))] if case_conds else [] |
| ) |
|
|
| pic_conds = [] |
| if known_ids: |
| pic_conds.append(and_(Picture.subject_type == SubjectType.known, Picture.subject_id.in_(known_ids))) |
| if unknown_ids: |
| pic_conds.append(and_(Picture.subject_type == SubjectType.unknown, Picture.subject_id.in_(unknown_ids))) |
| pic_rows = ( |
| db.execute(select(Picture.id, Picture.file_path, Picture.thumb_path).where(or_(*pic_conds))).all() |
| if pic_conds else [] |
| ) |
| pic_ids = [r[0] for r in pic_rows] |
| media_keys = [k for r in pic_rows for k in (r[1], r[2]) if k] |
|
|
| match_conds = [] |
| if case_ids: |
| match_conds.append(Match.case_id.in_(case_ids)) |
| match_conds.append(Match.candidate_case_id.in_(case_ids)) |
| if known_ids: |
| match_conds.append(and_(Match.candidate_type == SubjectType.known, Match.candidate_id.in_(known_ids))) |
| if unknown_ids: |
| match_conds.append(and_(Match.candidate_type == SubjectType.unknown, Match.candidate_id.in_(unknown_ids))) |
| match_ids = ( |
| [i for (i,) in db.execute(select(Match.id).where(or_(*match_conds)))] if match_conds else [] |
| ) |
|
|
| print("Seed data to remove:") |
| print(f" known dogs: {len(known_ids)} {known_ids}") |
| print(f" unknown dogs: {len(unknown_ids)} {unknown_ids}") |
| print(f" demo owners: {len(demo_user_ids)} " |
| f"{[db.get(User, u).email for u in demo_user_ids]}") |
| print(f" cases: {len(case_ids)} matches: {len(match_ids)} pictures: {len(pic_ids)} " |
| f"media files: {len(set(media_keys))}") |
|
|
| if not do_delete: |
| print("\nDRY RUN — nothing deleted. Re-run with --delete.") |
| return |
|
|
| def _del(stmt): |
| return db.execute(stmt).rowcount or 0 |
|
|
| |
| if demo_user_ids or case_ids or match_ids: |
| nconds = [] |
| if demo_user_ids: |
| nconds.append(Notification.user_id.in_(demo_user_ids)) |
| if case_ids: |
| nconds.append(Notification.case_id.in_(case_ids)) |
| if match_ids: |
| nconds.append(Notification.match_id.in_(match_ids)) |
| if nconds: |
| _del(delete(Notification).where(or_(*nconds))) |
| if pic_ids: |
| _del(delete(BreedPrediction).where(BreedPrediction.picture_id.in_(pic_ids))) |
| _del(delete(Embedding).where(Embedding.picture_id.in_(pic_ids))) |
| if match_ids: |
| _del(delete(Match).where(Match.id.in_(match_ids))) |
| if demo_user_ids: |
| db.execute(Match.__table__.update().where(Match.reviewed_by.in_(demo_user_ids)).values(reviewed_by=None)) |
| if pic_ids: |
| _del(delete(Picture).where(Picture.id.in_(pic_ids))) |
| if case_ids: |
| _del(delete(Case).where(Case.id.in_(case_ids))) |
| if unknown_ids: |
| _del(delete(UnknownDog).where(UnknownDog.id.in_(unknown_ids))) |
| if known_ids: |
| _del(delete(KnownDog).where(KnownDog.id.in_(known_ids))) |
| if demo_user_ids: |
| _del(delete(User).where(User.id.in_(demo_user_ids))) |
| db.commit() |
|
|
| |
| storage = get_storage() |
| removed = 0 |
| for key in dict.fromkeys(media_keys): |
| try: |
| storage.delete(key) |
| removed += 1 |
| except Exception: |
| pass |
| print(f"\nDeleted seed data. Media files removed: {removed}. Admin + dataset dogs untouched.") |
| finally: |
| db.close() |
|
|
|
|
| def main() -> None: |
| ap = argparse.ArgumentParser(description="Purge seeded demo (generated-dot) data.") |
| ap.add_argument("--delete", action="store_true", help="Actually delete (default: dry run)") |
| args = ap.parse_args() |
| run(args.delete) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|