From 56dbb8add9bddbf9a58b9ec481be6a44e20f969e Mon Sep 17 00:00:00 2001 From: Gaurav Sharma Date: Wed, 9 Sep 2026 07:58:26 +0530 Subject: [PATCH 1/4] FEAT: Expose SQL Server type constants at module level for pyodbc parity Promote SQL_SS_TIME2 (-154), SQL_SS_XML (-152), and SQL_SS_VARIANT (-150) from ConstantsDDBC to module-level public API so mssql_python matches pyodbc, which exposes them as mssql_python.SQL_SS_TIME2 etc. Consumers such as Django's SQL Server backend read Database.SQL_SS_TIME2 off the driver module and otherwise need a getattr fallback. Adds the constants to _DDBC_PUBLIC_API, re-exports them in __init__ and __all__, declares them in the type stub, and adds a public-API parity test. Values already existed in ConstantsDDBC; no new values introduced. GitHub Issue: #763 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 5 +++++ mssql_python/__init__.py | 8 ++++++++ mssql_python/constants.py | 4 ++++ mssql_python/mssql_python.pyi | 4 ++++ tests/test_003_connection.py | 22 ++++++++++++++++++++++ 5 files changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4ebfb816..dbd29c476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] ### Added +- Module-level `SQL_SS_TIME2` (-154), `SQL_SS_XML` (-152), and `SQL_SS_VARIANT` + (-150) type constants, exposing the SQL Server-specific ODBC type codes at the + package level for pyodbc parity (e.g. `mssql_python.SQL_SS_TIME2`). These were + already present in `ConstantsDDBC` but not re-exported, so consumers such as + Django's SQL Server backend had to fall back to a hard-coded literal. - New feature: Support for macOS and Linux. - Documentation: Added API documentation in the Wiki. - New `token_provider=` parameter on `connect()` / `Connection` for Microsoft diff --git a/mssql_python/__init__.py b/mssql_python/__init__.py index b5a4fe84d..752a0820d 100644 --- a/mssql_python/__init__.py +++ b/mssql_python/__init__.py @@ -179,6 +179,10 @@ def _cleanup_connections(): SQL_TYPE_TIMESTAMP, SQL_GUID, SQL_XML, + # SQL Server-specific type constants (pyodbc parity) + SQL_SS_TIME2, + SQL_SS_XML, + SQL_SS_VARIANT, # Connection attribute constants SQL_ATTR_ACCESS_MODE, SQL_ATTR_CONNECTION_TIMEOUT, @@ -388,6 +392,10 @@ def _cleanup_connections(): "SQL_TYPE_TIMESTAMP", "SQL_GUID", "SQL_XML", + # SQL Server-specific type constants (pyodbc parity) + "SQL_SS_TIME2", + "SQL_SS_XML", + "SQL_SS_VARIANT", # Connection attribute constants "SQL_ATTR_ACCESS_MODE", "SQL_ATTR_CONNECTION_TIMEOUT", diff --git a/mssql_python/constants.py b/mssql_python/constants.py index 01b8d413f..fecb9257c 100644 --- a/mssql_python/constants.py +++ b/mssql_python/constants.py @@ -594,6 +594,10 @@ def get_info_constants() -> Dict[str, int]: "SQL_TYPE_TIMESTAMP", "SQL_GUID", "SQL_XML", + # SQL Server-specific type constants (pyodbc parity) + "SQL_SS_TIME2", + "SQL_SS_XML", + "SQL_SS_VARIANT", # Connection attribute constants (ODBC-standard, driver-independent only) "SQL_ATTR_ACCESS_MODE", "SQL_ATTR_CONNECTION_TIMEOUT", diff --git a/mssql_python/mssql_python.pyi b/mssql_python/mssql_python.pyi index c8cc076d9..a69cc0ff4 100644 --- a/mssql_python/mssql_python.pyi +++ b/mssql_python/mssql_python.pyi @@ -383,6 +383,10 @@ SQL_LONGVARBINARY: int SQL_DATE: int SQL_TIME: int SQL_TIMESTAMP: int +# SQL Server-specific type constants (pyodbc parity) +SQL_SS_TIME2: int +SQL_SS_XML: int +SQL_SS_VARIANT: int SQL_WMETADATA: int # Connection Attribute Constants diff --git a/tests/test_003_connection.py b/tests/test_003_connection.py index 0058d4d10..f7f8ee5e9 100644 --- a/tests/test_003_connection.py +++ b/tests/test_003_connection.py @@ -3550,6 +3550,28 @@ def test_set_attr_constants_access(): assert not hasattr(mssql_python, const_name), f"{const_name} should NOT be public API" +def test_sql_server_type_constants_public_api(): + """SQL Server-specific ODBC type constants must be exposed at the package level. + + pyodbc exposes SQL_SS_TIME2, SQL_SS_XML, and SQL_SS_VARIANT as module-level + attributes. mssql_python must match so drop-in consumers (e.g. Django's SQL + Server backend, which reads Database.SQL_SS_TIME2) don't need a fallback. + """ + expected = { + "SQL_SS_TIME2": -154, + "SQL_SS_XML": -152, + "SQL_SS_VARIANT": -150, + } + for const_name, const_value in expected.items(): + assert hasattr( + mssql_python, const_name + ), f"{const_name} should be public API (pyodbc parity)" + assert ( + getattr(mssql_python, const_name) == const_value + ), f"{const_name} should equal {const_value}" + assert const_name in mssql_python.__all__, f"{const_name} should be in __all__" + + def test_set_attr_basic_functionality(db_connection): """Test basic set_attr functionality with ODBC-standard attributes.""" try: From 0c5a42584d1340b466c477a0ddae72818929c7e9 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma <223556219+Copilot@users.noreply.github.com> Date: Wed, 9 Sep 2026 09:46:17 +0530 Subject: [PATCH 2/4] CHORE: consolidate public constants coverage Fold the SQL Server type constant assertions into the existing public constants test to remove duplicate setup and checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/test_003_connection.py | 44 ++++++++++++------------------------ 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/tests/test_003_connection.py b/tests/test_003_connection.py index f7f8ee5e9..03d1697e7 100644 --- a/tests/test_003_connection.py +++ b/tests/test_003_connection.py @@ -3493,11 +3493,11 @@ def test_connection_searchescape_consistency(db_connection): def test_set_attr_constants_access(): - """Test that only relevant connection attribute constants are accessible. + """Test that only supported constants are accessible. This test distinguishes between driver-independent (ODBC standard) and driver-manager–dependent (may not be supported everywhere) constants. - Only ODBC-standard, cross-platform constants should be public API. + ODBC-standard and supported SQL Server-specific constants should be public API. """ # ODBC-standard, driver-independent constants (should be public) odbc_attr_constants = [ @@ -3516,6 +3516,11 @@ def test_set_attr_constants_access(): "SQL_MODE_READ_WRITE", "SQL_MODE_READ_ONLY", ] + sql_server_type_constants = { + "SQL_SS_TIME2": -154, + "SQL_SS_XML": -152, + "SQL_SS_VARIANT": -150, + } # Driver-manager–dependent or rarely supported constants (should NOT be public API) dm_attr_constants = [ @@ -3537,41 +3542,22 @@ def test_set_attr_constants_access(): ] dm_value_constants = ["SQL_CD_TRUE", "SQL_CD_FALSE", "SQL_RESET_CONNECTION_YES"] - # Check ODBC-standard constants are present and int - for const_name in odbc_attr_constants + odbc_value_constants: - assert hasattr( - mssql_python, const_name - ), f"{const_name} should be available (ODBC standard)" + # Check supported constants are present and int + public_constants = odbc_attr_constants + odbc_value_constants + list(sql_server_type_constants) + for const_name in public_constants: + assert hasattr(mssql_python, const_name), f"{const_name} should be available" const_value = getattr(mssql_python, const_name) assert isinstance(const_value, int), f"{const_name} should be an integer" + if const_name in sql_server_type_constants: + expected_value = sql_server_type_constants[const_name] + assert const_value == expected_value, f"{const_name} should equal {expected_value}" + assert const_name in mssql_python.__all__, f"{const_name} should be in __all__" # Check driver-manager–dependent constants are NOT present for const_name in dm_attr_constants + dm_value_constants: assert not hasattr(mssql_python, const_name), f"{const_name} should NOT be public API" -def test_sql_server_type_constants_public_api(): - """SQL Server-specific ODBC type constants must be exposed at the package level. - - pyodbc exposes SQL_SS_TIME2, SQL_SS_XML, and SQL_SS_VARIANT as module-level - attributes. mssql_python must match so drop-in consumers (e.g. Django's SQL - Server backend, which reads Database.SQL_SS_TIME2) don't need a fallback. - """ - expected = { - "SQL_SS_TIME2": -154, - "SQL_SS_XML": -152, - "SQL_SS_VARIANT": -150, - } - for const_name, const_value in expected.items(): - assert hasattr( - mssql_python, const_name - ), f"{const_name} should be public API (pyodbc parity)" - assert ( - getattr(mssql_python, const_name) == const_value - ), f"{const_name} should equal {const_value}" - assert const_name in mssql_python.__all__, f"{const_name} should be in __all__" - - def test_set_attr_basic_functionality(db_connection): """Test basic set_attr functionality with ODBC-standard attributes.""" try: From ac9df3317a6e8fa852f41cf70f2cd7e955cebbcb Mon Sep 17 00:00:00 2001 From: Gaurav Sharma <223556219+Copilot@users.noreply.github.com> Date: Wed, 9 Sep 2026 09:52:48 +0530 Subject: [PATCH 3/4] CHORE: address constants test review feedback Rename the expanded constants test and assert that non-parity SQL Server type constants remain internal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/test_003_connection.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_003_connection.py b/tests/test_003_connection.py index 03d1697e7..d3bd9a9e7 100644 --- a/tests/test_003_connection.py +++ b/tests/test_003_connection.py @@ -3492,7 +3492,7 @@ def test_connection_searchescape_consistency(db_connection): # ==================== SET_ATTR TEST CASES ==================== -def test_set_attr_constants_access(): +def test_constants_access(): """Test that only supported constants are accessible. This test distinguishes between driver-independent (ODBC standard) and @@ -3541,6 +3541,7 @@ def test_set_attr_constants_access(): "SQL_CUR_USE_DRIVER", ] dm_value_constants = ["SQL_CD_TRUE", "SQL_CD_FALSE", "SQL_RESET_CONNECTION_YES"] + internal_type_constants = ["SQL_SS_UDT", "SQL_DATETIMEOFFSET"] # Check supported constants are present and int public_constants = odbc_attr_constants + odbc_value_constants + list(sql_server_type_constants) @@ -3553,8 +3554,8 @@ def test_set_attr_constants_access(): assert const_value == expected_value, f"{const_name} should equal {expected_value}" assert const_name in mssql_python.__all__, f"{const_name} should be in __all__" - # Check driver-manager–dependent constants are NOT present - for const_name in dm_attr_constants + dm_value_constants: + # Check unsupported or intentionally internal constants are NOT present + for const_name in dm_attr_constants + dm_value_constants + internal_type_constants: assert not hasattr(mssql_python, const_name), f"{const_name} should NOT be public API" From 905c7e5d6906992e1a51e218c2238e0fdb94eb49 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma <223556219+Copilot@users.noreply.github.com> Date: Wed, 9 Sep 2026 10:05:35 +0530 Subject: [PATCH 4/4] CHORE: remove changelog entry Keep this compatibility export out of the release changelog. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbd29c476..f4ebfb816 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] ### Added -- Module-level `SQL_SS_TIME2` (-154), `SQL_SS_XML` (-152), and `SQL_SS_VARIANT` - (-150) type constants, exposing the SQL Server-specific ODBC type codes at the - package level for pyodbc parity (e.g. `mssql_python.SQL_SS_TIME2`). These were - already present in `ConstantsDDBC` but not re-exported, so consumers such as - Django's SQL Server backend had to fall back to a hard-coded literal. - New feature: Support for macOS and Linux. - Documentation: Added API documentation in the Wiki. - New `token_provider=` parameter on `connect()` / `Connection` for Microsoft