Skip to content

Commit 439a693

Browse files
committed
fix: use correct interpolation in alembic url
1 parent de30a37 commit 439a693

File tree

3 files changed

+41
-7
lines changed

3 files changed

+41
-7
lines changed

src/memu/database/postgres/migration.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
from __future__ import annotations
22

33
import logging
4+
import re
45
from pathlib import Path
56
from typing import Any, Literal
67

78
from sqlalchemy import create_engine, inspect, text
89

9-
from memu.database.postgres.schema import get_metadata
10-
1110
try: # Optional dependency for Postgres backend
1211
from alembic import command
1312
from alembic.config import Config as AlembicConfig
@@ -18,12 +17,17 @@
1817
logger = logging.getLogger(__name__)
1918

2019
DDLMode = Literal["create", "validate"]
20+
_UNESCAPED_CONFIGPARSER_PERCENT = re.compile(r"(?<!%)%(?!%)")
21+
22+
23+
def _escape_for_config_parser(value: str) -> str:
24+
return _UNESCAPED_CONFIGPARSER_PERCENT.sub("%%", value)
2125

2226

2327
def make_alembic_config(*, dsn: str, scope_model: type[Any]) -> AlembicConfig:
2428
cfg = AlembicConfig()
2529
cfg.set_main_option("script_location", str(Path(__file__).with_name("migrations")))
26-
cfg.set_main_option("sqlalchemy.url", dsn)
30+
cfg.set_main_option("sqlalchemy.url", _escape_for_config_parser(dsn))
2731
cfg.attributes["scope_model"] = scope_model
2832
return cfg
2933

@@ -37,6 +41,8 @@ def run_migrations(*, dsn: str, scope_model: type[Any], ddl_mode: DDLMode = "cre
3741
scope_model: User scope model for scoped tables
3842
ddl_mode: "create" to create missing tables, "validate" to only check schema
3943
"""
44+
from memu.database.postgres.schema import get_metadata
45+
4046
metadata = get_metadata(scope_model)
4147
engine = create_engine(dsn)
4248

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import importlib.util
2+
from pathlib import Path
3+
4+
from pydantic import BaseModel
5+
6+
7+
def _load_migration_module():
8+
module_path = Path(__file__).resolve().parents[1] / "src/memu/database/postgres/migration.py"
9+
spec = importlib.util.spec_from_file_location("memu_postgres_migration", module_path)
10+
if spec is None or spec.loader is None:
11+
raise RuntimeError
12+
module = importlib.util.module_from_spec(spec)
13+
spec.loader.exec_module(module)
14+
return module
15+
16+
17+
class _ScopeModel(BaseModel):
18+
user_id: str
19+
20+
21+
def test_make_alembic_config_escapes_percent_encoded_dsn() -> None:
22+
migration = _load_migration_module()
23+
24+
dsn = "postgresql+psycopg://postgres:%40%23%24%25%5E%26%2A%28%29password@host.docker.internal:5432/memu_dev"
25+
26+
cfg = migration.make_alembic_config(dsn=dsn, scope_model=_ScopeModel)
27+
raw_value = cfg.file_config.get(cfg.config_ini_section, "sqlalchemy.url", raw=True)
28+
29+
assert cfg.get_main_option("sqlalchemy.url") == dsn
30+
assert "%%40%%23%%24%%25%%5E%%26%%2A%%28%%29password" in raw_value
31+
assert raw_value != dsn

uv.lock

Lines changed: 1 addition & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)