Kaleb King
12/11/2025, 5:22 PMfrom prefect import flow, task, get_run_logger
from prefect.blocks.system import Secret
import sqlalchemy as sa
import pandas as pd
@task(name = "Query SQL Database")
def query_database(block_name: str, sql_query: str):
logger = get_run_logger()
<http://logger.info|logger.info>("Getting database credentials from Prefect Secret block...")
try:
credentials = Secret.load(block_name).get()
except Exception as e:
logger.error(f"Failed to get credentials: {e}")
raise
<http://logger.info|logger.info>("Connecting to database...")
try:
engine = sa.create_engine(credentials)
except Exception as e:
logger.error(f"Failed to connect to database: {e}")
raise
<http://logger.info|logger.info>("Running query on the database...")
try:
with engine.connect() as connection:
result = pd.read_sql(sql_query, connection)
except Exception as e:
logger.error(f"Failed to run query: {e}")
raise
<http://logger.info|logger.info>(f"Query result: {result}")
return result
@flow(name = "Simple Database Query Flow")
def flow(block_name: str, sql_query: str):
result = query_database(block_name, sql_query)
return result
Failed to run query: (pyodbc.InterfaceError) ('IM002', '[IM002] [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified (0) (SQLDriverConnect)')
(Background on this error at: <https://sqlalche.me/e/20/rvf5>)Marvin
12/11/2025, 5:22 PMMarvin
12/11/2025, 5:23 PMSecret likely contains either:
- a DSN name that isn’t defined in the environment running the flow, or
- a SQLAlchemy URL that uses mssql+pyodbc but doesn’t specify a valid installed driver.
Why this happens with Prefect flows
- Your flow runs wherever your worker/agent runs (local machine, Docker image, Kubernetes pod, etc.). If that environment doesn’t have the ODBC driver (e.g., “ODBC Driver 17/18 for SQL Server”) installed or the DSN configured, pyodbc raises IM002.
- If your secret is just DSN=Something;... but there is no DSN named “Something” in that environment, you’ll get the same error.
- If you use a URL like <mssql+pyodbc://user:pass@host/db> without ?driver=..., pyodbc will also complain because there’s “no default driver specified.”
How to fix it
1) Ensure the ODBC driver is installed in the runtime environment
- Linux (e.g., in a Docker image): install Microsoft’s ODBC driver (17/18) and unixODBC.
Example (Debian/Ubuntu):
RUN apt-get update && apt-get install -y curl gnupg apt-transport-https
RUN curl <https://packages.microsoft.com/keys/microsoft.asc> | apt-key add -
RUN curl <https://packages.microsoft.com/config/debian/12/prod.list> > /etc/apt/sources.list.d/mssql-release.list
RUN apt-get update && ACCEPT_EULA=Y apt-get install -y msodbcsql18 unixodbc unixodbc-dev
Docs: Install Microsoft ODBC Driver for SQL Server
- macOS: brew install --cask msodbcsql17 (or 18) and brew install unixodbc.
- Windows: install “ODBC Driver 17/18 for SQL Server” from Microsoft.
2) Use a full SQLAlchemy URL with a driver parameter (avoid DSNs if you can)
- Store a connection string like this in your Prefect Secret:
<mssql+pyodbc://username:password@server:1433/database?driver=ODBC+Driver+18+for+SQL+Server&Encrypt=yes&TrustServerCertificate=yes>
Note:
- Spaces in the driver name must be encoded with +
- Driver name must match exactly what’s installed (e.g., “ODBC Driver 18 for SQL Server”)
- Add `Encrypt`/`TrustServerCertificate` as needed for your environment
SQLAlchemy docs: SQL Server + pyodbc connection strings
3) If you must use a DSN, define it in the environment that runs the flow
- You’ll need odbc.ini and odbcinst.ini (Linux) or a System DSN (Windows) inside the container/host where the Prefect worker runs. Your Secret must then reference that DSN, e.g., mssql+pyodbc://@MyDsn.
A couple of smaller notes on your snippet
- It looks like Slack mangled your logging lines; make sure they’re actually <http://logger.info|logger.info>("...") in your code and not <http://logger.info|logger.info>(...).
- Naming your flow function flow works but shadows the decorator name. Consider renaming it to avoid confusion:
@flow(name="Simple Database Query Flow")
def simple_db_query_flow(block_name: str, sql_query: str):
return query_database(block_name, sql_query)
If you share:
- what database you’re connecting to (SQL Server/Postgres/etc.),
- the exact connection string format stored in your Secret (feel free to redact credentials), and
- where the flow is running (local, Docker image, Kubernetes, OS),
I can help you craft the correct connection URL and, if needed, a Dockerfile snippet to install the right drivers.