From 091a3eca867d714c0c146c2c10d05ec11b33c4d5 Mon Sep 17 00:00:00 2001 From: Bob Kline Date: Tue, 24 Feb 2026 11:10:05 -0500 Subject: [PATCH] Support filtering DSN by scope The `pyodbc.dataSources()` function now accepts an optional keyword argument ("scope") to filter the reported DSNs to just the user DSNs or just the system DSNs. Closes #1415 --- src/pyodbc.pyi | 5 ++++- src/pyodbcmodule.cpp | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/pyodbc.pyi b/src/pyodbc.pyi index 685cebf1..223b3f8a 100644 --- a/src/pyodbc.pyi +++ b/src/pyodbc.pyi @@ -945,10 +945,13 @@ class Row: # module functions -def dataSources() -> dict[str, str]: +def dataSources(*, scope: str | None = None) -> dict[str, str]: """Return all available Data Source Names (DSNs), typically from the odbcinst.ini file or the Windows ODBC Data Source Administrator. + Args: + scope: optional filter ("user" or "system") to control which DSNs are returned. + Returns: A dictionary of DSNs and their textual descriptions. """ diff --git a/src/pyodbcmodule.cpp b/src/pyodbcmodule.cpp index ede13fe3..fdcf0783 100644 --- a/src/pyodbcmodule.cpp +++ b/src/pyodbcmodule.cpp @@ -574,10 +574,30 @@ static PyObject* mod_drivers(PyObject* self) } -static PyObject* mod_datasources(PyObject* self) +static PyObject* mod_datasources(PyObject* self, PyObject* args, PyObject* kwargs) { UNUSED(self); + static char* kwlist[] = { "scope", NULL }; + const char* scope = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|$s", kwlist, &scope)) + return 0; + + SQLUSMALLINT nDirectionFirst; + + if (scope == NULL) + nDirectionFirst = SQL_FETCH_FIRST; + else if (strcmp(scope, "user") == 0) + nDirectionFirst = SQL_FETCH_FIRST_USER; + else if (strcmp(scope, "system") == 0) + nDirectionFirst = SQL_FETCH_FIRST_SYSTEM; + else + { + PyErr_SetString(PyExc_ValueError, "scope must be 'user' or 'system'"); + return 0; + } + if (henv == SQL_NULL_HANDLE && !AllocateEnv()) return 0; @@ -597,7 +617,7 @@ static PyObject* mod_datasources(PyObject* self) SWORD cbDSN; SWORD cbDesc; - SQLUSMALLINT nDirection = SQL_FETCH_FIRST; + SQLUSMALLINT nDirection = nDirectionFirst; SQLRETURN ret; @@ -768,9 +788,15 @@ static char drivers_doc[] = "Returns a list of installed drivers."; static char datasources_doc[] = - "dataSources() --> { DSN : Description }\n" \ + "dataSources(*, scope=None) --> { DSN : Description }\n" \ + "\n" \ + "Returns a dictionary mapping available DSNs to their descriptions.\n" \ "\n" \ - "Returns a dictionary mapping available DSNs to their descriptions."; + "scope\n" \ + " An optional keyword-only argument to filter the returned DSNs.\n" \ + " If set to 'user', only user DSNs are returned.\n" \ + " If set to 'system', only system DSNs are returned.\n" \ + " If not set, all DSNs are returned."; static char setdecimalsep_doc[] = "setDecimalSeparator(string) -> None\n" \ @@ -792,7 +818,7 @@ static PyMethodDef pyodbc_methods[] = { "getDecimalSeparator", (PyCFunction)mod_getdecimalsep, METH_NOARGS, getdecimalsep_doc }, { "TimestampFromTicks", (PyCFunction)mod_timestampfromticks, METH_VARARGS, timestampfromticks_doc }, { "drivers", (PyCFunction)mod_drivers, METH_NOARGS, drivers_doc }, - { "dataSources", (PyCFunction)mod_datasources, METH_NOARGS, datasources_doc }, + { "dataSources", (PyCFunction)mod_datasources, METH_VARARGS|METH_KEYWORDS, datasources_doc }, { 0, 0, 0, 0 } };