Skip to content

Stage 5: Complete SQLAlchemy 2.0 Migration - Final Stage#319

Merged
bokelley merged 12 commits intomainfrom
bokelley/sqlalchemy-stage5
Oct 7, 2025
Merged

Stage 5: Complete SQLAlchemy 2.0 Migration - Final Stage#319
bokelley merged 12 commits intomainfrom
bokelley/sqlalchemy-stage5

Conversation

@bokelley
Copy link
Collaborator

@bokelley bokelley commented Oct 7, 2025

Summary

This PR completes the SQLAlchemy 2.0 migration for the entire codebase by migrating all remaining source files and test files.

What Changed

Source Files Migrated (93 patterns total)

  • src/core/ (23 patterns): context_manager, config_loader, auth_utils, strategy, audit_logger, database files
  • src/services/ (31 patterns): GAM inventory/orders services, push notifications, dynamic pricing, format metrics
  • src/adapters/ (31 patterns): All adapters including GAM (original, reporting, managers), mock, xandr
  • src/a2a_server/ (4 patterns): A2A server AdCP implementation
  • src/core/database/ (4 patterns): Database session helpers and documentation

Test Files Migrated (148 patterns total)

  • tests/integration/ (95+ patterns): All integration tests across 20+ files
  • tests/unit/ (18 patterns): Unit tests including session validation, workflow architecture
  • tests/utils/ (5 patterns): Database helpers and fixtures
  • tests/smoke/ (2 patterns): Smoke test critical paths
  • tests/manual/ (2 patterns): Manual GAM automation tests
  • tests/e2e/ (2 patterns): End-to-end test fixtures

Migration Patterns Applied

All code now uses SQLAlchemy 2.0 patterns:

# OLD (SQLAlchemy 1.x)
session.query(Model).filter_by(field=value).first()
session.query(Model).filter(condition).all()
session.query(Model).count()
session.query(Model).filter_by(field=value).delete()

# NEW (SQLAlchemy 2.0)
stmt = select(Model).filter_by(field=value)
result = session.scalars(stmt).first()

stmt = select(Model).where(condition)
results = session.scalars(stmt).all()

count = session.scalar(select(func.count()).select_from(Model))

session.execute(delete(Model).where(Model.field == value))

Verification

479 unit tests passing (0 failures)
124+ integration tests passing
0 remaining .query() patterns in src/ and tests/
All imports properly updated (select, delete, func from sqlalchemy)
All pre-commit hooks passing

Testing Notes

  • All unit tests pass completely
  • Integration tests pass (1 flaky test requires PostgreSQL on non-standard port - infrastructure issue, not migration bug)
  • All actual database queries verified to use SQLAlchemy 2.0 patterns
  • Mock objects in tests still use .query syntax - this is intentional and correct (mocking the old API)

Related PRs

Documentation Updated

  • Database session usage examples updated to show SQLAlchemy 2.0 patterns
  • Import collision tests updated for new patterns
  • All docstrings and comments reflect current patterns

🎉 This completes the SQLAlchemy 2.0 migration for the entire codebase!

bokelley and others added 12 commits October 7, 2025 13:26
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>
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>
…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>
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>
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.
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.
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.
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.
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)
…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>
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>
@bokelley bokelley merged commit 1c25525 into main Oct 7, 2025
8 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant