SQLAlchemy 2.0 Migration - Stage 1 & 2: Infrastructure and Helper Functions#307
Merged
SQLAlchemy 2.0 Migration - Stage 1 & 2: Infrastructure and Helper Functions#307
Conversation
- Replace declarative_base() with DeclarativeBase class in models.py - Remove deprecated autocommit=False and autoflush=False from sessionmaker() - Updated 3 files: database_session.py, gam_inventory_service.py, gam_orders_service.py - No breaking changes - all 449 unit tests pass This is the foundation for SQLAlchemy 2.0 migration per issue #304. Future stages will migrate query patterns to select() + scalars() syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Migrated get_or_404() to use select() + session.scalars() - Migrated get_or_create() to use select() + session.scalars() - Added select import to database_session.py - Pattern: session.query(Model).filter_by() → select(Model).filter_by() with scalars() Changes: - Old: instance = session.query(model).filter_by(**kwargs).first() - New: stmt = select(model).filter_by(**kwargs); instance = session.scalars(stmt).first() All 449 unit tests pass ✅ Part of SQLAlchemy 2.0 migration (issue #304) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Document the correct SQLAlchemy 2.0 patterns with clear examples: - ✅ Correct: select() + session.scalars() - ❌ Wrong: session.query() (deprecated) Guidelines for developers: 1. Always use 2.0 patterns for new code 2. Convert legacy code when touching it 3. Common conversion patterns with examples This ensures consistent adoption of SQLAlchemy 2.0 patterns across the codebase as code is written or modified. Related: #304, PR #307 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Oct 7, 2025
bokelley
added a commit
that referenced
this pull request
Oct 7, 2025
Migrated 4 core files with 15 query patterns: - config_loader.py: 4 patterns (tenant lookups, subdomain/vhost resolution) - auth_utils.py: 5 patterns (principal/tenant auth lookups) - strategy.py: 5 patterns (strategy CRUD, simulation state management) - audit_logger.py: 2 patterns (tenant name lookups for notifications) All patterns converted from session.query() to select() + scalars(). Includes delete() pattern in strategy.py for reset() function. Related to PR #307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Oct 7, 2025
Migrated database.py with 1 count pattern: - Converted session.query(Tenant).count() to select(func.count()).select_from(Tenant) - Used scalar() instead of execute() for count queries Total core files migrated: 5 files, 16 patterns converted. Related to PR #307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Oct 7, 2025
…chemy 2.0 Migrated 2 service files with 2 query patterns: - format_metrics_service.py: 1 pattern (upsert query for metrics) - dynamic_pricing_service.py: 1 pattern (query with chained filters) Converted query().filter() to select().where() with scalars().all()/first(). Related to PR #307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Oct 7, 2025
Migrated push_notification_service.py with 5 query patterns: - Converted all .query() calls to select() + scalars() - Patterns include: filter_by, filter with and_(), and in_() clauses - Fixed import order (ruff) All query patterns now use SQLAlchemy 2.0 style with explicit stmt variables. Related to PR #307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Oct 7, 2025
* Stage 5: Migrate src/core/ files to SQLAlchemy 2.0 Migrated 4 core files with 15 query patterns: - config_loader.py: 4 patterns (tenant lookups, subdomain/vhost resolution) - auth_utils.py: 5 patterns (principal/tenant auth lookups) - strategy.py: 5 patterns (strategy CRUD, simulation state management) - audit_logger.py: 2 patterns (tenant name lookups for notifications) All patterns converted from session.query() to select() + scalars(). Includes delete() pattern in strategy.py for reset() function. Related to PR #307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Migrate src/core/database/ files to SQLAlchemy 2.0 Migrated database.py with 1 count pattern: - Converted session.query(Tenant).count() to select(func.count()).select_from(Tenant) - Used scalar() instead of execute() for count queries Total core files migrated: 5 files, 16 patterns converted. Related to PR #307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Migrate format_metrics and dynamic_pricing services to SQLAlchemy 2.0 Migrated 2 service files with 2 query patterns: - format_metrics_service.py: 1 pattern (upsert query for metrics) - dynamic_pricing_service.py: 1 pattern (query with chained filters) Converted query().filter() to select().where() with scalars().all()/first(). Related to PR #307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Migrate push_notification_service to SQLAlchemy 2.0 Migrated push_notification_service.py with 5 query patterns: - Converted all .query() calls to select() + scalars() - Patterns include: filter_by, filter with and_(), and in_() clauses - Fixed import order (ruff) All query patterns now use SQLAlchemy 2.0 style with explicit stmt variables. Related to PR #307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Fix remaining .query() patterns in config_loader and update docs in database_session * Stage 5: Migrate GAM service files to SQLAlchemy 2.0 (24 patterns) Converted all `.query()` patterns in: - src/services/gam_inventory_service.py (17 patterns) - src/services/gam_orders_service.py (7 patterns) Changes: - Added `select`, `delete`, `update` imports - Converted `.query(Model)` → `stmt = select(Model)` - Converted `.filter()` → `.where()` - Converted `.all()/.first()` → `session.scalars(stmt).all()/.first()` - Converted `.update()` → `update(Model).values()` - Converted `.delete()` → `delete(Model).where()` - Converted `.count()` → `session.scalar(select(func.count()))` - Maintained readable formatting with proper variable names All conversions follow SQLAlchemy 2.0 best practices. * Stage 5: Migrate adapter files to SQLAlchemy 2.0 (18 patterns) Converted all `.query()` patterns in: - src/adapters/google_ad_manager.py (1 pattern) - src/adapters/mock_ad_server.py (1 pattern) - src/adapters/xandr.py (1 pattern) - src/adapters/gam_reporting_api.py (8 patterns) - src/adapters/gam/managers/sync.py (6 patterns) - src/adapters/gam/managers/workflow.py (1 pattern) Changes: - Added `select` imports (and `func` where needed for count operations) - Converted `.query(Model)` → `stmt = select(Model)` - Converted `.filter_by()` → `.filter_by()` (still supported in select) - Converted `.where()` for more complex filters - Converted `.all()/.first()` → `session.scalars(stmt).all()/.first()` - Converted `.count()` → `session.scalar(select(func.count()).select_from(Model).where(...))` - Used `replace_all=true` for repeated patterns in gam_reporting_api.py All conversions follow SQLAlchemy 2.0 best practices. * Stage 5: Migrate a2a_server file to SQLAlchemy 2.0 (4 patterns) Converted all `.query()` patterns in: - src/a2a_server/adcp_a2a_server.py (4 patterns) Changes: - Added `select` import from sqlalchemy - Converted `.query(DBPushNotificationConfig)` → `stmt = select(DBPushNotificationConfig)` - Converted `.filter_by()` → `.filter_by()` (chained with select) - Converted `.all()/.first()` → `db.scalars(stmt).all()/.first()` All push notification config queries now use SQLAlchemy 2.0 patterns. * Stage 5: Fix test to use scalars() mock for SQLAlchemy 2.0 Updated test_virtual_host_edge_cases.py to mock scalars() instead of query() to align with SQLAlchemy 2.0 patterns used in config_loader.py. * Stage 5: Complete test fixes for SQLAlchemy 2.0 Updated remaining test assertions in test_virtual_host_edge_cases.py: - Removed outdated mock.query assertion in SQL injection test - Updated corrupted tenant test to mock scalars() chain - Tests now align with SQLAlchemy 2.0 patterns (select + scalars) * Stage 5: Migrate google_ad_manager_original (legacy) to SQLAlchemy 2.0 (13 patterns) Converted all remaining .query() patterns in google_ad_manager_original.py: - Pattern 1 (line 405): Product lookup with filter_by - Pattern 2 (line 1834): CreativeFormat with filter, order_by, and in_() - Pattern 3 (line 2454): WorkflowStep with filter_by - Pattern 4 (line 2774): Tenant lookup with filter_by - Pattern 5 (line 2779): Product lookup with filter_by - Pattern 6 (line 2792): AdapterConfig with filter_by - Pattern 7 (line 2903): Product update with filter_by - Pattern 8 (line 2973): GAMInventory count with where - Pattern 9 (line 2989): GAMInventory all with where and and_() - Pattern 10 (line 3002): GAMInventory all with where and and_() - Pattern 11 (line 3046): GAMInventory all with where, and_(), and limit - Pattern 12 (line 3065): GAMInventory all with where, and_(), and limit - Pattern 13 (line 3089): GAMInventory column select with where and order_by (fixed typo: OrderBy → order_by) All patterns now use SQLAlchemy 2.0 API: - select() instead of query() - where() instead of filter() - scalars().first()/all() for entity queries - execute().first() for column-only queries - scalar() for count queries Note: This file appears to be legacy/deprecated (not imported anywhere). Migrated for completeness. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Fix syntax errors from automated test migration Fixed IndentationError in 5 test files caused by misplaced SQLAlchemy imports: - tests/smoke/test_smoke_critical_paths.py (line 482) - tests/integration/test_create_media_buy_v24.py (line 314) - tests/integration/test_gam_tenant_setup.py (line 227) - tests/integration/test_product_deletion.py (line 407) - tests/integration/test_tenant_management_api_integration.py (line 49) Also fixed SyntaxError in tests/manual/test_gam_automation_real.py: - Changed Product.tenant_id= to Product.tenant_id== in where() clauses Root cause: Automated migration script added imports in wrong location and used assignment operator instead of comparison operator in where(). All files now compile successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
danf-newton
pushed a commit
to Newton-Research-Inc/salesagent
that referenced
this pull request
Nov 24, 2025
…ctions (prebid#307) * Stage 1: SQLAlchemy 2.0 infrastructure migration - Replace declarative_base() with DeclarativeBase class in models.py - Remove deprecated autocommit=False and autoflush=False from sessionmaker() - Updated 3 files: database_session.py, gam_inventory_service.py, gam_orders_service.py - No breaking changes - all 449 unit tests pass This is the foundation for SQLAlchemy 2.0 migration per issue prebid#304. Future stages will migrate query patterns to select() + scalars() syntax. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 2: SQLAlchemy 2.0 helper function migration - Migrated get_or_404() to use select() + session.scalars() - Migrated get_or_create() to use select() + session.scalars() - Added select import to database_session.py - Pattern: session.query(Model).filter_by() → select(Model).filter_by() with scalars() Changes: - Old: instance = session.query(model).filter_by(**kwargs).first() - New: stmt = select(model).filter_by(**kwargs); instance = session.scalars(stmt).first() All 449 unit tests pass ✅ Part of SQLAlchemy 2.0 migration (issue prebid#304) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Add SQLAlchemy 2.0 query patterns to CLAUDE.md Document the correct SQLAlchemy 2.0 patterns with clear examples: - ✅ Correct: select() + session.scalars() - ❌ Wrong: session.query() (deprecated) Guidelines for developers: 1. Always use 2.0 patterns for new code 2. Convert legacy code when touching it 3. Common conversion patterns with examples This ensures consistent adoption of SQLAlchemy 2.0 patterns across the codebase as code is written or modified. Related: prebid#304, PR prebid#307 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
danf-newton
pushed a commit
to Newton-Research-Inc/salesagent
that referenced
this pull request
Nov 24, 2025
* Stage 5: Migrate src/core/ files to SQLAlchemy 2.0 Migrated 4 core files with 15 query patterns: - config_loader.py: 4 patterns (tenant lookups, subdomain/vhost resolution) - auth_utils.py: 5 patterns (principal/tenant auth lookups) - strategy.py: 5 patterns (strategy CRUD, simulation state management) - audit_logger.py: 2 patterns (tenant name lookups for notifications) All patterns converted from session.query() to select() + scalars(). Includes delete() pattern in strategy.py for reset() function. Related to PR prebid#307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Migrate src/core/database/ files to SQLAlchemy 2.0 Migrated database.py with 1 count pattern: - Converted session.query(Tenant).count() to select(func.count()).select_from(Tenant) - Used scalar() instead of execute() for count queries Total core files migrated: 5 files, 16 patterns converted. Related to PR prebid#307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Migrate format_metrics and dynamic_pricing services to SQLAlchemy 2.0 Migrated 2 service files with 2 query patterns: - format_metrics_service.py: 1 pattern (upsert query for metrics) - dynamic_pricing_service.py: 1 pattern (query with chained filters) Converted query().filter() to select().where() with scalars().all()/first(). Related to PR prebid#307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Migrate push_notification_service to SQLAlchemy 2.0 Migrated push_notification_service.py with 5 query patterns: - Converted all .query() calls to select() + scalars() - Patterns include: filter_by, filter with and_(), and in_() clauses - Fixed import order (ruff) All query patterns now use SQLAlchemy 2.0 style with explicit stmt variables. Related to PR prebid#307 - SQLAlchemy 2.0 migration initiative. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Fix remaining .query() patterns in config_loader and update docs in database_session * Stage 5: Migrate GAM service files to SQLAlchemy 2.0 (24 patterns) Converted all `.query()` patterns in: - src/services/gam_inventory_service.py (17 patterns) - src/services/gam_orders_service.py (7 patterns) Changes: - Added `select`, `delete`, `update` imports - Converted `.query(Model)` → `stmt = select(Model)` - Converted `.filter()` → `.where()` - Converted `.all()/.first()` → `session.scalars(stmt).all()/.first()` - Converted `.update()` → `update(Model).values()` - Converted `.delete()` → `delete(Model).where()` - Converted `.count()` → `session.scalar(select(func.count()))` - Maintained readable formatting with proper variable names All conversions follow SQLAlchemy 2.0 best practices. * Stage 5: Migrate adapter files to SQLAlchemy 2.0 (18 patterns) Converted all `.query()` patterns in: - src/adapters/google_ad_manager.py (1 pattern) - src/adapters/mock_ad_server.py (1 pattern) - src/adapters/xandr.py (1 pattern) - src/adapters/gam_reporting_api.py (8 patterns) - src/adapters/gam/managers/sync.py (6 patterns) - src/adapters/gam/managers/workflow.py (1 pattern) Changes: - Added `select` imports (and `func` where needed for count operations) - Converted `.query(Model)` → `stmt = select(Model)` - Converted `.filter_by()` → `.filter_by()` (still supported in select) - Converted `.where()` for more complex filters - Converted `.all()/.first()` → `session.scalars(stmt).all()/.first()` - Converted `.count()` → `session.scalar(select(func.count()).select_from(Model).where(...))` - Used `replace_all=true` for repeated patterns in gam_reporting_api.py All conversions follow SQLAlchemy 2.0 best practices. * Stage 5: Migrate a2a_server file to SQLAlchemy 2.0 (4 patterns) Converted all `.query()` patterns in: - src/a2a_server/adcp_a2a_server.py (4 patterns) Changes: - Added `select` import from sqlalchemy - Converted `.query(DBPushNotificationConfig)` → `stmt = select(DBPushNotificationConfig)` - Converted `.filter_by()` → `.filter_by()` (chained with select) - Converted `.all()/.first()` → `db.scalars(stmt).all()/.first()` All push notification config queries now use SQLAlchemy 2.0 patterns. * Stage 5: Fix test to use scalars() mock for SQLAlchemy 2.0 Updated test_virtual_host_edge_cases.py to mock scalars() instead of query() to align with SQLAlchemy 2.0 patterns used in config_loader.py. * Stage 5: Complete test fixes for SQLAlchemy 2.0 Updated remaining test assertions in test_virtual_host_edge_cases.py: - Removed outdated mock.query assertion in SQL injection test - Updated corrupted tenant test to mock scalars() chain - Tests now align with SQLAlchemy 2.0 patterns (select + scalars) * Stage 5: Migrate google_ad_manager_original (legacy) to SQLAlchemy 2.0 (13 patterns) Converted all remaining .query() patterns in google_ad_manager_original.py: - Pattern 1 (line 405): Product lookup with filter_by - Pattern 2 (line 1834): CreativeFormat with filter, order_by, and in_() - Pattern 3 (line 2454): WorkflowStep with filter_by - Pattern 4 (line 2774): Tenant lookup with filter_by - Pattern 5 (line 2779): Product lookup with filter_by - Pattern 6 (line 2792): AdapterConfig with filter_by - Pattern 7 (line 2903): Product update with filter_by - Pattern 8 (line 2973): GAMInventory count with where - Pattern 9 (line 2989): GAMInventory all with where and and_() - Pattern 10 (line 3002): GAMInventory all with where and and_() - Pattern 11 (line 3046): GAMInventory all with where, and_(), and limit - Pattern 12 (line 3065): GAMInventory all with where, and_(), and limit - Pattern 13 (line 3089): GAMInventory column select with where and order_by (fixed typo: OrderBy → order_by) All patterns now use SQLAlchemy 2.0 API: - select() instead of query() - where() instead of filter() - scalars().first()/all() for entity queries - execute().first() for column-only queries - scalar() for count queries Note: This file appears to be legacy/deprecated (not imported anywhere). Migrated for completeness. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Stage 5: Fix syntax errors from automated test migration Fixed IndentationError in 5 test files caused by misplaced SQLAlchemy imports: - tests/smoke/test_smoke_critical_paths.py (line 482) - tests/integration/test_create_media_buy_v24.py (line 314) - tests/integration/test_gam_tenant_setup.py (line 227) - tests/integration/test_product_deletion.py (line 407) - tests/integration/test_tenant_management_api_integration.py (line 49) Also fixed SyntaxError in tests/manual/test_gam_automation_real.py: - Changed Product.tenant_id= to Product.tenant_id== in where() clauses Root cause: Automated migration script added imports in wrong location and used assignment operator instead of comparison operator in where(). All files now compile successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes Stage 1 and Stage 2 of SQLAlchemy 2.0 migration for issue #304.
This PR migrates the database infrastructure layer to use SQLAlchemy 2.0 patterns without breaking any existing functionality.
Changes
Stage 1: Infrastructure Migration (Commit: a59691c)
declarative_base()toDeclarativeBaseclassautocommit=Falseandautoflush=Falseparameterssrc/core/database/models.py- NewDeclarativeBasestylesrc/core/database/database_session.py- Clean sessionmakersrc/services/gam_inventory_service.py- Clean sessionmakersrc/services/gam_orders_service.py- Clean sessionmakerStage 2: Helper Function Migration (Commit: 6b9ad9a)
get_or_404()to useselect() + session.scalars()get_or_create()to useselect() + session.scalars()selectimport to database_session.pysession.query(Model)→select(Model)withscalars()Documentation Update (Commit: 9aa269f)
CLAUDE.mdMigration Pattern
Before (SQLAlchemy 1.x):
```python
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
instance = session.query(Model).filter_by(field=value).first()
```
After (SQLAlchemy 2.0):
```python
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
SessionLocal = sessionmaker(bind=engine)
stmt = select(Model).filter_by(field=value)
instance = session.scalars(stmt).first()
```
Test Results
Impact
session.query()patterns (future stages)Next Steps (Future PRs)
Mapped[]type annotations and enable mypy pluginRelated
🤖 Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com