Skip to content

Commit ff05fce

Browse files
cfsmp3claude
andcommitted
chore(deps): upgrade SQLAlchemy from 1.4.41 to 2.0.45
This PR upgrades SQLAlchemy to version 2.0.45, addressing breaking changes: - Update DeclarativeBase: Replace deprecated declarative_base() with new class-based DeclarativeBase pattern - Fix raw SQL execution: Wrap string SQL with text() in mod_health and tests - Update DeclEnumType: Use String(50) as impl instead of Enum to avoid SQLAlchemy 2.0's stricter enum value validation in TypeDecorator - Fix engine creation: Remove deprecated convert_unicode parameter and use StaticPool for SQLite in-memory databases to ensure connection sharing - Update type hints: Use generic Dialect type instead of SQLite-specific All 402 tests pass with the upgraded SQLAlchemy version. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent abb8ebe commit ff05fce

File tree

4 files changed

+35
-25
lines changed

4 files changed

+35
-25
lines changed

database.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
import re
55
import traceback
66
from abc import ABCMeta
7-
from typing import Any, Dict, Iterator, Tuple, Type, Union
7+
from typing import Any, Dict, Iterator, Optional, Tuple, Type, Union
88

99
from sqlalchemy import create_engine
10-
from sqlalchemy.dialects.sqlite.pysqlite import SQLiteDialect_pysqlite
10+
from sqlalchemy.engine import Dialect
1111
from sqlalchemy.exc import SQLAlchemyError
12-
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
13-
from sqlalchemy.orm import scoped_session, sessionmaker
14-
from sqlalchemy.sql.schema import Column, Table
15-
from sqlalchemy.sql.sqltypes import Enum, SchemaType, TypeDecorator
12+
from sqlalchemy.orm import DeclarativeBase, DeclarativeMeta, scoped_session, sessionmaker
13+
from sqlalchemy.pool import StaticPool
14+
from sqlalchemy.sql.sqltypes import String, TypeDecorator
1615

1716
from exceptions import EnumParsingException, FailedToSpawnDBSession
1817

@@ -23,8 +22,13 @@ class DeclarativeABCMeta(DeclarativeMeta, ABCMeta):
2322
pass
2423

2524

26-
Base = declarative_base(metaclass=DeclarativeMeta)
27-
Base.query = None
25+
class Base(DeclarativeBase):
26+
"""Base class for all models."""
27+
28+
pass
29+
30+
31+
Base.query = None # type: ignore[attr-defined]
2832
db_engine = None
2933

3034

@@ -43,9 +47,18 @@ def create_session(db_string: str, drop_tables: bool = False) -> scoped_session:
4347
global db_engine, Base
4448

4549
try:
46-
# In testing, we want to maintain same memory variable
47-
if db_engine is None or 'TESTING' not in os.environ or os.environ['TESTING'] == 'False':
48-
db_engine = create_engine(db_string, convert_unicode=True)
50+
# Only create engine if it doesn't exist
51+
# For SQLite in-memory, we must reuse the engine to share the database
52+
if db_engine is None:
53+
# For SQLite in-memory databases, use StaticPool to share connection
54+
if db_string == 'sqlite:///:memory:':
55+
db_engine = create_engine(
56+
db_string,
57+
connect_args={"check_same_thread": False},
58+
poolclass=StaticPool
59+
)
60+
else:
61+
db_engine = create_engine(db_string)
4962
db_session = scoped_session(sessionmaker(bind=db_engine))
5063
Base.query = db_session.query_property()
5164

@@ -162,32 +175,27 @@ def db_type(cls) -> DeclEnumType:
162175
return DeclEnumType(cls)
163176

164177

165-
class DeclEnumType(SchemaType, TypeDecorator):
178+
class DeclEnumType(TypeDecorator):
166179
"""Declarative enumeration type."""
167180

168181
cache_ok = True
182+
impl = String(50)
169183

170184
def __init__(self, enum: Any) -> None:
171185
self.enum = enum
172-
self.impl = Enum(
173-
*enum.values(),
174-
name="ck{0}".format(re.sub('([A-Z])', lambda m: "_" + m.group(1).lower(), enum.__name__))
175-
)
176-
177-
def _set_table(self, table: Column, column: Table) -> None:
178-
self.impl._set_table(table, column)
186+
super().__init__()
179187

180-
def copy(self) -> DeclEnumType:
188+
def copy(self, **kwargs: Any) -> DeclEnumType:
181189
"""Get enumeration type of self."""
182190
return DeclEnumType(self.enum)
183191

184-
def process_bind_param(self, value: EnumSymbol, dialect: SQLiteDialect_pysqlite) -> str:
192+
def process_bind_param(self, value: Optional[Any], dialect: Dialect) -> Optional[str]:
185193
"""Get process bind parameter."""
186194
if value is None:
187195
return None
188196
return value.value
189197

190-
def process_result_value(self, value: str, dialect: SQLiteDialect_pysqlite) -> EnumSymbol:
198+
def process_result_value(self, value: Optional[Any], dialect: Dialect) -> Optional[EnumSymbol]:
191199
"""Get process result value."""
192200
if value is None:
193201
return None

mod_health/controllers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Any, Dict, Optional, Tuple
77

88
from flask import Blueprint, current_app, jsonify
9+
from sqlalchemy import text
910

1011
mod_health = Blueprint('health', __name__)
1112

@@ -20,7 +21,7 @@ def check_database() -> Dict[str, Any]:
2021
try:
2122
from database import create_session
2223
db = create_session(current_app.config['DATABASE_URI'])
23-
db.execute('SELECT 1')
24+
db.execute(text('SELECT 1'))
2425
# remove() returns the scoped session's connection to the pool
2526
db.remove()
2627
return {'status': 'ok'}

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
sqlalchemy==1.4.41
1+
sqlalchemy==2.0.45
22
flask==3.1.2
33
passlib==1.7.4
44
pymysql==1.1.2

tests/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from flask import g
99
from flask_testing import TestCase
10+
from sqlalchemy import text
1011
from werkzeug.datastructures import Headers
1112

1213
from database import create_session
@@ -250,7 +251,7 @@ def setUp(self):
250251
g.db = create_session(
251252
self.app.config['DATABASE_URI'], drop_tables=True)
252253
# enable Foreign keys for unit tests
253-
g.db.execute('pragma foreign_keys=on')
254+
g.db.execute(text('pragma foreign_keys=on'))
254255

255256
general_data = [
256257
GeneralData('last_commit', "1978060bf7d2edd119736ba3ba88341f3bec3323"),

0 commit comments

Comments
 (0)