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.
|
# Alembic configuration for VoiceStudio.
|
||||||
# Run from anywhere: alembic -c <repo>/alembic.ini <command>
|
# Run from anywhere: alembic -c <repo>/alembic.ini <command>
|
||||||
# Default commands:
|
# Default commands:
|
||||||
# alembic upgrade head — apply all pending migrations
|
# alembic upgrade head - apply all pending migrations
|
||||||
# alembic revision -m "…" — create a new migration
|
# alembic revision -m "..." - create a new migration
|
||||||
# alembic current — show current schema version
|
# 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).
|
# DB URL is resolved dynamically from core.config (env aware).
|
||||||
# See backend/migrations/env.py.
|
# See backend/migrations/env.py.
|
||||||
|
|
||||||
[alembic]
|
[alembic]
|
||||||
# %(here)s = this file's directory. Alembic resolves bare relative paths
|
# %(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
|
# from the repo root (`tauri dev` runs the backend with
|
||||||
# cwd=frontend/src-tauri), which made startup migrations die with
|
# cwd=frontend/src-tauri), which made startup migrations die with
|
||||||
# "Path doesn't exist: backend/migrations" the first time one was pending.
|
# "Path doesn't exist: backend/migrations" the first time one was pending.
|
||||||
script_location = %(here)s/backend/migrations
|
script_location = %(here)s/backend/migrations
|
||||||
prepend_sys_path = %(here)s/backend
|
prepend_sys_path = %(here)s/backend
|
||||||
# Split multi-path options on os.pathsep, not the legacy space/comma/colon
|
# 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
|
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 =
|
sqlalchemy.url =
|
||||||
|
|
||||||
[loggers]
|
[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