fix(db): run migrations on Chinese, Japanese and Korean Windows
alembic reads alembic.ini with encoding="locale". On Python 3.11 that is the Windows ANSI code page even under PYTHONUTF8=1, which the desktop shell sets, so the em dashes in the ini's comments raised UnicodeDecodeError inside Config() on cp932/cp936/cp949/cp950 systems. _run_alembic_upgrade treats that as "alembic unavailable": every startup logged "alembic upgrade head skipped: 'cp949' codec can't decode byte 0xe2", never took the pre-migration backup, and never ran a migration -- including the data-healing ones (0006/0007) that the additive column reconcile cannot replace. The ini is now ASCII, with a note saying why, and a test parses it in each of those code pages with the parser alembic builds and pins it ASCII. Same Python 3.11 locale-decoding class as the .pth fix in #1795. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
eaf8bb9538
commit
61f99debb4
+10
-6
@@ -1,25 +1,29 @@
|
||||
# Alembic configuration for VoiceStudio.
|
||||
# Run from anywhere: alembic -c <repo>/alembic.ini <command>
|
||||
# Default commands:
|
||||
# alembic upgrade head — apply all pending migrations
|
||||
# alembic revision -m "…" — create a new migration
|
||||
# alembic current — show current schema version
|
||||
# alembic upgrade head - apply all pending migrations
|
||||
# alembic revision -m "..." - create a new migration
|
||||
# alembic current - show current schema version
|
||||
#
|
||||
# Keep this file ASCII: alembic reads it in the locale code page, which on a
|
||||
# Chinese, Japanese or Korean Windows cannot decode UTF-8 punctuation
|
||||
# (tests/test_alembic_ini_locale.py).
|
||||
#
|
||||
# DB URL is resolved dynamically from core.config (env aware).
|
||||
# See backend/migrations/env.py.
|
||||
|
||||
[alembic]
|
||||
# %(here)s = this file's directory. Alembic resolves bare relative paths
|
||||
# against the process CWD, not the ini — and the app doesn't always start
|
||||
# against the process CWD, not the ini - and the app doesn't always start
|
||||
# from the repo root (`tauri dev` runs the backend with
|
||||
# cwd=frontend/src-tauri), which made startup migrations die with
|
||||
# "Path doesn't exist: backend/migrations" the first time one was pending.
|
||||
script_location = %(here)s/backend/migrations
|
||||
prepend_sys_path = %(here)s/backend
|
||||
# Split multi-path options on os.pathsep, not the legacy space/comma/colon
|
||||
# set — a colon-split would shred "C:\..." absolute paths on Windows.
|
||||
# set - a colon-split would shred "C:\..." absolute paths on Windows.
|
||||
path_separator = os
|
||||
# sqlalchemy.url is set programmatically in env.py — do NOT set it here.
|
||||
# sqlalchemy.url is set programmatically in env.py - do NOT set it here.
|
||||
sqlalchemy.url =
|
||||
|
||||
[loggers]
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""alembic.ini must parse in every Windows ANSI code page.
|
||||
|
||||
alembic reads its ini with ``encoding="locale"`` (``alembic.util.compat.
|
||||
read_config_parser``). On Python 3.11 that is the Windows ANSI code page even
|
||||
with ``PYTHONUTF8=1``, which the desktop shell sets — so on a Chinese, Japanese
|
||||
or Korean Windows the em dashes in the ini's comments raised
|
||||
``UnicodeDecodeError`` inside ``Config()``, and every startup logged
|
||||
"alembic upgrade head skipped" and never ran a migration or took the
|
||||
pre-migration backup.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import configparser
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
_INI = Path(__file__).resolve().parents[1] / "alembic.ini"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("code_page", ["cp932", "cp936", "cp949", "cp950", "cp1252"])
|
||||
def test_alembic_ini_parses_in_the_windows_ansi_code_page(code_page):
|
||||
# The same parser alembic.config.Config builds: `here` is its default.
|
||||
parser = configparser.ConfigParser({"here": str(_INI.parent)})
|
||||
assert parser.read(_INI, encoding=code_page) == [str(_INI)]
|
||||
assert parser.get("alembic", "script_location").endswith("/backend/migrations")
|
||||
|
||||
|
||||
def test_alembic_ini_is_ascii_so_no_locale_can_break_it():
|
||||
data = _INI.read_bytes()
|
||||
offenders = [
|
||||
f"line {n}: {line.decode('utf-8', 'replace').strip()}"
|
||||
for n, line in enumerate(data.splitlines(), start=1)
|
||||
if any(b > 0x7F for b in line)
|
||||
]
|
||||
assert not offenders, (
|
||||
"alembic.ini is read in the locale code page, so it must stay ASCII:\n "
|
||||
+ "\n ".join(offenders)
|
||||
)
|
||||
Reference in New Issue
Block a user