-
Notifications
You must be signed in to change notification settings - Fork 331
Description
Issue
EncryptedType
with a JSON column raises ValueError: dictionary update sequence element #0 has length 1; 2 is required
when reading data, due to incorrect handling of decrypted JSON strings in process_result_value
. The method attempts to pass a decrypted JSON string (e.g., '{"key": "value"}'
) to dict()
, expecting a sequence of key-value pairs, instead of parsing it with json.loads
.
Steps to Reproduce
- Save the attached script as
bug.py
. - Install dependencies:
pip install sqlalchemy==2.0.35 sqlalchemy-utils==0.42.3 cryptography==42.0.8
. - Run:
python bug.py
. - Observe
ValueError
when accessing theid
attribute after committing.
Script
import sqlalchemy as sa
from sqlalchemy.orm import declarative_base, sessionmaker
from sqlalchemy_utils import EncryptedType
import os
import json
SECRET_KEY = 'a' * 32
engine = sa.create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
Base = declarative_base()
class BuggyModel(Base):
__tablename__ = 'buggy_table'
id = sa.Column(sa.Integer, primary_key=True)
encrypted_data = sa.Column(EncryptedType(sa.JSON, SECRET_KEY))
Base.metadata.create_all(engine)
def demonstrate_bug():
session = Session()
print("--- Step 1: Writing to the Database ---")
json_payload = {'message': 'This is a test', 'value': 123}
instance = BuggyModel(encrypted_data=json_payload)
session.add(instance)
session.commit()
print(f"✅ Successfully committed the object (ID: {instance.id}).")
print("The data is now encrypted in the database.")
print("-" * 40)
print("\n--- Step 2: Reading from the Database ---")
print("Now, attempting to refresh the object from the database.")
print("This will trigger EncryptedType's 'process_result_value' method...")
try:
session.refresh(instance)
print("\n❌ BUG NOT REPRODUCED. The library may have been updated.")
print(f" Decrypted data: {instance.encrypted_data}")
except ValueError as e:
print(f"\n✅ BUG REPRODUCED SUCCESSFULLY!")
print(f" Error Type: {type(e)}")
print(f" Error Message: {e}")
error_string = "dictionary update sequence element #0 has length 1; 2 is required"
if error_string in str(e):
print("\n Proof: The error message matches the one from our test suite.")
print(" This confirms the library's fallback logic is attempting to run `dict('{\"key\": ...}')`.")
else:
print("\n NOTE: A ValueError occurred, but it was not the one we expected.")
finally:
session.close()
if __name__ == "__main__":
demonstrate_bug()
Output
/home/tommy/src/sqlalchemy-utils-bug/bug.py:26: DeprecationWarning: The 'EncryptedType' class will change implementation from 'LargeBinary' to 'String' in a future version. Use 'StringEncryptedType' to use the 'String' implementation.
encrypted_data = sa.Column(EncryptedType(sa.JSON, SECRET_KEY))
--- Step 1: Writing to the Database ---
Traceback (most recent call last):
File "/home/tommy/src/sqlalchemy-utils-bug/.venv/lib/python3.10/site-packages/sqlalchemy_utils/types/encrypted/encrypted_type.py", line 427, in process_result_value
return self.underlying_type.process_result_value(
AttributeError: 'JSON' object has no attribute 'process_result_value'
[Additional stack trace omitted for brevity]
ValueError: dictionary update sequence element #0 has length 1; 2 is required
Environment
Python: 3.10
SQLAlchemy: 2.0.35
SQLAlchemy-Utils: 0.42.3
Cryptography: 42.0.8
Database: SQLite
Expected Behavior
EncryptedType should parse decrypted JSON strings using json.loads to return a Python dictionary (e.g., {"key": "value"}).
Non-dictionary JSON (e.g., ["invalid"], "invalid") should be returned as-is or handled gracefully.
Actual Behavior
ValueError: dictionary update sequence element #0 has length 1; 2 is required during read operations, as process_result_value attempts dict('{"key": "value"}').
DeprecationWarning suggests StringEncryptedType, but it’s unavailable in 0.42.3.
Previous attempts raised TypeError: LargeBinary.init() got an unexpected keyword argument 'secret_key' and TypeError: argument 'data': Cannot convert "<class 'NoneType'>" instance to a buffer.
Additional Notes
Encountered in a FastAPI app with PostgreSQL JSON column (sqlalchemy.dialects.postgresql.JSON). The bug affects read operations after writing valid JSON data, breaking CRUD workflows.