#8360·hummingbot

Bug Report`SQLConnectionManager` crashes with `AttributeError: 'tuple' object has no attribute 'lower'` on any non-SQLite DB engine (e.g. PostgreSQL)

Author: fredbeaudoinCreated Jul 15, 2026Updated Sep 6, 2026
Labelsbug

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

  1. Configure conf_client.yml with a non-SQLite db_mode, e.g.:
yaml
   db_mode:
     db_engine: postgresql
     db_host: <host>
     db_port: 5432
     db_username: <user>
     db_password: <pass>
     db_name: <db>
  1. 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:

python
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)

python
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 here

Expected behavior

Strategy starts normally regardless of DB engine, as with SQLite.

Suggested fix

Unpack the tuple to get the actual constraint name:

python
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