SQLAlchemy dialect for SQL Server linked servers where reflection must use
INFORMATION_SCHEMA (no sp_tables / sp_columns access).
- Python >= 3.11
- SQLAlchemy >= 2.0
pyodbc- Microsoft ODBC Driver 17+ for SQL Server
pip install linked-sqlserver-dialectFor local development from a git checkout:
pip install -e .Provide linked server metadata via URL query parameters:
from sqlalchemy import create_engine, inspect
engine = create_engine(
"linked_mssql+pyodbc://user:pass@host/db"
"?driver=ODBC+Driver+17+for+SQL+Server"
"&linked_server=MyLinkedServer"
"&database=RemoteDb"
"&schema=dbo"
)
insp = inspect(engine)
print(insp.get_table_names()) # uses INFORMATION_SCHEMA.TABLES
print(insp.get_columns("MyTable")) # uses INFORMATION_SCHEMA.COLUMNSNote: if you're running from a source checkout without installing the package,
add import linked_sqlserver_dialect once before create_engine(...) so the
dialect gets registered.
Or via connect_args:
engine = create_engine(/.
"linked_mssql+pyodbc://user:pass@host/db?driver=ODBC+Driver+17+for+SQL+Server",
connect_args={"linked_server": "MyLinkedServer", "database": "RemoteDb", "schema": "dbo"},
)This first release is intentionally minimal (KISS/YAGNI):
- Tables:
get_table_names() - Columns:
get_columns() - Views:
get_view_names() - No PK/FK/index reflection yet
Many linked-server scenarios expose tables/views without primary key metadata. SQLAlchemy can still reflect them, but ORM mapping typically needs a primary key.
You can provide primary key overrides in a few ways:
-
URL query param (string format):
pk_overrides=dbo.example_table=id;dbo.other=col1,col2
-
connect_args(dict or string):connect_args={"pk_overrides": {"dbo.example_table": ["id"]}}connect_args={"pk_overrides": "dbo.example_table=id;dbo.other=col1,col2"}
-
Advanced (runtime mutation): if you already created an engine and want to inject overrides programmatically:
engine.dialect._pk_overrides[("dbo", "example_table")] = ["id"]
Run a real end-to-end check against your environment:
export LINKED_MSSQL_URL='linked_mssql+pyodbc://user:pass@host/db?driver=ODBC+Driver+17+for+SQL+Server&linked_server=MyLinkedServer&database=RemoteDb&schema=dbo'
python scripts/smoke_test.py --table MyTableThis repo includes a local-only Docker Compose setup with:
sql1: primary SQL Server (your app connects here)sql2: remote SQL Server (hosts the schema/data)init: seedssql2and creates a linked server onsql1
From the repo root:
docker compose up -d --buildFrom your host (uses published port 14331):
export LINKED_MSSQL_URL='linked_mssql+pyodbc://sa:YourStrong(!)Password1@localhost:14331/master?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes&linked_server=LS_REMOTE&database=RemoteDb&schema=dbo'
python scripts/smoke_test.py --table example_tableFrom inside the devcontainer (service DNS name sql1):
export LINKED_MSSQL_URL='linked_mssql+pyodbc://sa:YourStrong(!)Password1@sql1/master?driver=ODBC+Driver+18+for+SQL+Server&TrustServerCertificate=yes&linked_server=LS_REMOTE&database=RemoteDb&schema=dbo'
python scripts/smoke_test.py --table example_table- If using ODBC Driver 18, encryption is enabled by default;
TrustServerCertificate=yesis the simplest local-dev setting. - On macOS, you can install the Microsoft driver via Homebrew tap:
brew tap microsoft/mssql-releasebrew install microsoft/mssql-release/msodbcsql18 microsoft/mssql-release/mssql-tools18
- Linked server provider support in SQL Server Linux images can vary. If
initfails while runningsp_addlinkedserver, check theinitcontainer logs and we can adjust the provider/options. - Passwords are hard-coded for local dev only. Change them before sharing this setup.