Files
shopdb-flask/plugins/computers/models/jsontext.py
Cam Proudlock 7bda8e6d60 Store the policy JSON columns as TEXT for MySQL 5.6
The branch stored scopejson, desiredstate and params as sa.JSON, the only JSON
columns in the product. MySQL 5.6 has no JSON type - it arrived in 5.7 - and the
migration failed there with a bare syntax error near the word JSON.

They are TEXT now, through a JSONText type decorator, so the seventeen call sites
still assign and read dicts. The convention elsewhere is already TEXT:
guides.blocksjson, geenforce.manifestjson.

Spec regenerated: 331 paths, 477 operations across 22 surfaces.
2026-08-28 16:18:34 -04:00

45 lines
1.7 KiB
Python

"""JSON stored in a TEXT column.
MySQL 5.6 has no JSON type - it arrived in 5.7 - and a site is still running
5.6, where `sa.JSON()` fails at migration time with a bare syntax error near the
word JSON. Every other JSON-bearing column in this product is already TEXT for
that reason: guides' `blocksjson`, GE-Enforce's `manifestjson`.
This keeps the Python side identical. A model attribute still holds a dict or a
list, still assigns and reads like one; only the storage changes. The
alternative - json.dumps and json.loads at seventeen call sites - is the same
thing written out seventeen times, with seventeen chances to forget one.
Note the SQLite behaviour is deliberately the same as MySQL's. Tests run on
SQLite and production does not, so a type that behaved differently across the
two would hide exactly the failure this exists to prevent.
"""
import json
from sqlalchemy import Text
from sqlalchemy.types import TypeDecorator
class JSONText(TypeDecorator):
"""A dict or list on the Python side, TEXT in the database."""
impl = Text
cache_ok = True
def process_bind_param(self, value, dialect):
if value is None:
return None
return json.dumps(value, separators=(',', ':'))
def process_result_value(self, value, dialect):
if value is None or value == '':
return None
try:
return json.loads(value)
except (TypeError, ValueError):
# A row written before this column was JSON, or hand-edited. Hand
# back the raw text rather than raising: a policy that cannot be
# parsed should be visible and fixable, not an exception on read.
return value