From 932cc7191c722153ff3e178c261a5f743a51b6e0 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sun, 10 May 2026 18:38:02 +0000 Subject: [PATCH] fix: use ::text::jsonb to store methodology overrides correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit asyncpg cannot encode a Python list as JSONB directly (expects str). Passing str with ::jsonb causes double-encoding (stored as JSONB string). Solution: json.dumps() the value → pass as text → PostgreSQL parses with ::text::jsonb cast, storing it as the correct JSONB array/object. Co-Authored-By: Claude Sonnet 4.6 --- web/app.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/web/app.py b/web/app.py index 60a521e..1294139 100644 --- a/web/app.py +++ b/web/app.py @@ -3095,14 +3095,15 @@ async def api_update_methodology(category: str, key: str, req: MethodologyUpdate raise HTTPException(422, "content_checklists value must be a non-empty string") pool = await db.get_pool() - # Pass req.value directly — asyncpg serializes Python list/dict to JSONB. - # json.dumps() caused double-encoding: string passed to ::jsonb became a JSONB string, - # not a JSONB array, making the frontend spread it as individual chars. + # json.dumps → text, then PostgreSQL casts text→jsonb. + # Passing a Python list directly causes "expected str, got list" in asyncpg; + # passing a str with ::jsonb causes double-encoding (stored as JSONB string). + # ::text::jsonb bypasses asyncpg's codec and lets PostgreSQL parse the JSON. await pool.execute( "INSERT INTO appeal_type_rules (id, appeal_type, rule_category, rule_key, rule_value) " - "VALUES (gen_random_uuid(), '_global', $1, $2, $3::jsonb) " - "ON CONFLICT (appeal_type, rule_category, rule_key) DO UPDATE SET rule_value = $3::jsonb", - category, key, req.value, + "VALUES (gen_random_uuid(), '_global', $1, $2, $3::text::jsonb) " + "ON CONFLICT (appeal_type, rule_category, rule_key) DO UPDATE SET rule_value = $3::text::jsonb", + category, key, json.dumps(req.value, ensure_ascii=False), ) return {"key": key, "value": req.value, "is_override": True}