"""recompute book matching keys Revision ID: ed41acf21270 Revises: 4358e7d4743a Create Date: 2026-08-15 15:44:28.341020 """ import warnings import sqlalchemy as sa from alembic import op from advanced_alchemy.types import EncryptedString, EncryptedText, GUID, ORA_JSONB, DateTimeUTC, StoredObject, PasswordHash, FernetBackend from advanced_alchemy.types.encrypted_string import PGCryptoBackend from advanced_alchemy.types.password_hash.argon2 import Argon2Hasher from advanced_alchemy.types.password_hash.passlib import PasslibHasher from advanced_alchemy.types.password_hash.pwdlib import PwdlibHasher from sqlalchemy import Text # noqa: F401 __all__ = ["downgrade", "upgrade", "schema_upgrades", "schema_downgrades", "data_upgrades", "data_downgrades"] sa.GUID = GUID sa.DateTimeUTC = DateTimeUTC sa.ORA_JSONB = ORA_JSONB sa.EncryptedString = EncryptedString sa.EncryptedText = EncryptedText sa.StoredObject = StoredObject sa.PasswordHash = PasswordHash sa.Argon2Hasher = Argon2Hasher sa.PasslibHasher = PasslibHasher sa.PwdlibHasher = PwdlibHasher sa.FernetBackend = FernetBackend sa.PGCryptoBackend = PGCryptoBackend # revision identifiers, used by Alembic. revision = 'ed41acf21270' down_revision = '4358e7d4743a' branch_labels = None depends_on = None def upgrade() -> None: with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) with op.get_context().autocommit_block(): schema_upgrades() data_upgrades() def downgrade() -> None: with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning) with op.get_context().autocommit_block(): data_downgrades() schema_downgrades() def schema_upgrades() -> None: """schema upgrade migrations go here.""" pass def schema_downgrades() -> None: """schema downgrade migrations go here.""" pass def data_upgrades() -> None: """ Recompute every matching key against the current normalization. The keys are derived, so changing a helper in `services/matching.py` silently invalidates every row already written — a book stored under the old rules simply stops matching one stored under the new ones, with nothing to show that anything is wrong. `normalize_title` learned to strip the compact edition markers a cover actually carries ("2E", "5e"), which moved `Building Microservices, 2E` onto the same key as `Building Microservices`. Any later change to those helpers wants a revision that looks exactly like this one. It is idempotent and safe to re-run. """ from chitai.services.matching import ( normalize_author, normalize_identifier, normalize_title, ) connection = op.get_bind() books = connection.execute(sa.text("SELECT id, title FROM books")).fetchall() _apply( connection, "UPDATE books SET normalized_title = :key WHERE id = :id", [{"id": id, "key": normalize_title(title)} for id, title in books], ) authors = connection.execute(sa.text("SELECT id, name FROM authors")).fetchall() _apply( connection, "UPDATE authors SET normalized_name = :key WHERE id = :id", [{"id": id, "key": normalize_author(name)} for id, name in authors], ) identifiers = connection.execute( sa.text("SELECT id, name, value FROM identifiers") ).fetchall() _apply( connection, "UPDATE identifiers SET normalized_value = :key WHERE id = :id", [ {"id": id, "key": normalize_identifier(name, value)} for id, name, value in identifiers ], ) def _apply(connection, statement: str, parameters: list[dict]) -> None: """Run one update per row, in batches, skipping the work when there are none.""" batch_size = 1000 for start in range(0, len(parameters), batch_size): connection.execute(sa.text(statement), parameters[start : start + batch_size]) def data_downgrades() -> None: """Add any optional data downgrade migrations here!"""