Unused imports, duplicated import lines, and bare excepts that swallowed KeyboardInterrupt along with everything else.
123 lines
4.0 KiB
Python
123 lines
4.0 KiB
Python
"""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!"""
|