SQLAlchemy Metadata Column Fix Summary¶
Issue Overview¶
CI/CD pipeline was failing with the following error:
sqlalchemy.exc.InvalidRequestError: Attribute name 'metadata' is reserved when using the Declarative API
Root Cause¶
In SQLAlchemy's Declarative API, the attribute name metadata is reserved for the table metadata object. Our SessionRecord model was using a column named metadata, which conflicted with SQLAlchemy's internal attribute.
Additionally, we encountered datetime serialization issues when saving session data to Redis (JSON) and PostgreSQL (JSONB columns).
Changes Made¶
1. Database Schema Changes¶
fiml/sessions/db.py¶
-
Line 45: Renamed column from
metadatatosession_metadata -
Lines 90-150: Updated
CREATE_TABLES_SQLto usesession_metadatacolumn name
scripts/init-db.sql¶
- Renamed
metadatacolumn tosession_metadatain table creation script
2. Session Store Updates¶
fiml/sessions/store.py¶
-
Line 444: Updated
_record_from_sessionto usesession_metadata -
Line 446: Fixed datetime serialization in history queries
-
Line 470: Updated
_session_from_recordto usesession_metadata
3. Session Models Updates¶
fiml/sessions/models.py¶
- Line 157: Fixed datetime serialization in
to_dict()method
4. Test Infrastructure Updates¶
tests/conftest.py¶
-
Added
init_session_dbfixture with CASCADE drops to ensure clean schema:@pytest_asyncio.fixture(scope="session") async def init_session_db(): """Initialize session database tables before any session tests""" async with get_async_engine().begin() as conn: # Drop existing tables first (with CASCADE to handle dependencies) await conn.execute(text("DROP TABLE IF EXISTS session_metrics CASCADE")) await conn.execute(text("DROP TABLE IF EXISTS sessions CASCADE")) # Create tables with new schema for statement in CREATE_TABLES_SQL.split(";"): if statement.strip(): await conn.execute(text(statement)) -
Updated session tests to depend on
init_session_dbfixture
Technical Details¶
Why model_dump(mode='json')?¶
Pydantic v2's model_dump() method has multiple modes: - Default mode: Returns Python objects (datetime, UUID, etc.) as-is - mode='json': Serializes all values to JSON-compatible types (datetime → ISO string)
When serializing sessions containing QueryRecord objects with timestamp fields, we need mode='json' to convert datetime objects to ISO format strings for: 1. Redis storage (requires JSON-serializable data) 2. PostgreSQL JSONB columns (requires JSON-compatible types)
Database Migration Notes¶
For production environments with existing data: 1. Test environments: Use DROP CASCADE approach (implemented in fixture) 2. Production: Need migration script:
Test Results¶
All 19 session tests now pass:
tests/test_sessions.py::TestSessionModels::test_query_record_creation PASSED
tests/test_sessions.py::TestSessionModels::test_analysis_history PASSED
tests/test_sessions.py::TestSessionModels::test_session_state PASSED
tests/test_sessions.py::TestSessionModels::test_session_creation PASSED
tests/test_sessions.py::TestSessionModels::test_session_expiration PASSED
tests/test_sessions.py::TestSessionModels::test_session_extend PASSED
tests/test_sessions.py::TestSessionModels::test_session_touch PASSED
tests/test_sessions.py::TestSessionModels::test_session_add_query PASSED
tests/test_sessions.py::TestSessionModels::test_session_serialization PASSED
tests/test_sessions.py::TestSessionStore::test_create_session PASSED
tests/test_sessions.py::TestSessionStore::test_get_session PASSED
tests/test_sessions.py::TestSessionStore::test_get_nonexistent_session PASSED
tests/test_sessions.py::TestSessionStore::test_update_session PASSED
tests/test_sessions.py::TestSessionStore::test_delete_session PASSED
tests/test_sessions.py::TestSessionStore::test_extend_session PASSED
tests/test_sessions.py::TestSessionStore::test_list_user_sessions PASSED
tests/test_sessions.py::TestSessionStore::test_archive_session PASSED
tests/test_sessions.py::TestSessionStore::test_cleanup_expired_sessions PASSED
tests/test_sessions.py::TestSessionIntegration::test_multi_query_session_workflow PASSED
=========================================================================== 19 passed, 155 warnings in 3.45s ===========================================================================
Files Modified¶
fiml/sessions/db.py- Column name and SQL schemafiml/sessions/store.py- Column references and datetime serializationfiml/sessions/models.py- JSON serialization modescripts/init-db.sql- SQL schematests/conftest.py- Test fixture with schema initialization
References¶
- SQLAlchemy Reserved Attributes: https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBase.metadata
- Pydantic v2 Serialization: https://docs.pydantic.dev/2.12/concepts/serialization/