Bug Report`SQLConnectionManager` crashes with `AttributeError: 'tuple' object has no attribute 'lower'` on any non-SQLite DB engine (e.g. PostgreSQL)
Describe the bug
Description
Starting any strategy (V1 or V2) with db_mode.db_engine set to anything other than sqlite (tested with postgresql) crashes immediately during SQLConnectionManager.__init__, before the strategy even starts. This happens on a completely stock schema — no custom models required.
Environment
- Hummingbot version: v2.14.0 (confirmed still present, unpatched, on v2.15.0 as well)
- Deployment: Docker (
hummingbot/hummingbot:version-2.14.0) - DB engine: PostgreSQL 16 (also expected to affect MySQL/any dialect with
supports_alter = True) - SQLAlchemy version: 2.0.51
Steps to reproduce
- Configure
conf_client.ymlwith a non-SQLitedb_mode, e.g.:
db_mode:
db_engine: postgresql
db_host: <host>
db_port: 5432
db_username: <user>
db_password: <pass>
db_name: <db>- Start any strategy (V1 or V2 script).
Actual behavior
Strategy fails to start with:
AttributeError: 'tuple' object has no attribute 'lower'Full traceback bottoms out in hummingbot/model/sql_connection_manager.py, in the DropConstraint block of SQLConnectionManager.__init__:
File "hummingbot/model/sql_connection_manager.py", line 92, in __init__
conn.execute(DropConstraint(fk_constraint))
...
File ".../sqlalchemy/sql/compiler.py", line 7735, in _requires_quotes
lc_value = value.lower()
AttributeError: 'tuple' object has no attribute 'lower'Root cause
In sql_connection_manager.py:
for tname, fkcs in reversed(inspector.get_sorted_table_and_fkc_names()):
if fkcs:
if not self._engine.dialect.supports_alter:
continue
for fkc in fkcs:
fk_constraint = ForeignKeyConstraint((), (), name=fkc)
Table(tname, MetaData(), fk_constraint)
conn.execute(DropConstraint(fk_constraint))Inspector.get_sorted_table_and_fkc_names() returns fkcs as a list of (table_name, fk_constraint_name) tuples, not plain constraint-name strings (see SQLAlchemy docs/source: reflection.py, get_sorted_table_and_fkc_names). The code above passes the raw tuple as name=fkc to ForeignKeyConstraint, which later breaks when SQLAlchemy's DDL compiler tries to .lower() the "name" while quoting it.
This code path is skipped entirely for SQLite because sqlite dialect's supports_alter is False, which is why this has likely gone unnoticed — it only triggers for engines like PostgreSQL/MySQL where supports_alter = True.
Note: fkcs is non-empty even for the stock Hummingbot schema — TradeFill.order_id and OrderStatus.order_id both reference Order.id, which is enough to reproduce this. No custom models are needed.
Minimal standalone repro (no Hummingbot app dependencies required)
from sqlalchemy import create_engine, inspect, Column, Text, Integer, ForeignKey, MetaData, Table, ForeignKeyConstraint
from sqlalchemy.schema import DropConstraint
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class Order(Base):
__tablename__ = "Order"
id = Column(Text, primary_key=True, nullable=False)
class OrderStatus(Base):
__tablename__ = "OrderStatus"
id = Column(Integer, primary_key=True, nullable=False)
order_id = Column(Text, ForeignKey("Order.id"), nullable=False)
class TradeFill(Base):
__tablename__ = "TradeFill"
market = Column(Text, primary_key=True, nullable=False)
order_id = Column(Text, ForeignKey("Order.id"), primary_key=True, nullable=False)
exchange_trade_id = Column(Text, primary_key=True, nullable=False)
engine = create_engine("postgresql://postgres:postgres@localhost:5432/hbtest")
Base.metadata.create_all(engine)
# Exact logic from sql_connection_manager.py
with engine.begin() as conn:
inspector = inspect(conn)
for tname, fkcs in reversed(inspector.get_sorted_table_and_fkc_names()):
if fkcs:
if not engine.dialect.supports_alter:
continue
for fkc in fkcs:
fk_constraint = ForeignKeyConstraint((), (), name=fkc)
Table(tname, MetaData(), fk_constraint)
conn.execute(DropConstraint(fk_constraint)) # <-- crashes hereExpected behavior
Strategy starts normally regardless of DB engine, as with SQLite.
Suggested fix
Unpack the tuple to get the actual constraint name:
for _fk_tname, fkc in fkcs:
fk_constraint = ForeignKeyConstraint((), (), name=fkc)
Table(tname, MetaData(), fk_constraint)
conn.execute(DropConstraint(fk_constraint))Release version
2.14, verified on 2.15 as well
Type of installation
Docker
Attach required files
No response
Source: hummingbot/hummingbot