Skip to content

FEAT: [mssql-python] Add opt-in mssql-python connection backend - #596

Merged
Gaurav Sharma (bewithgaurav) merged 4 commits into
devfrom
feat/mssql-python-optin-backend
Sep 16, 2026
Merged

Gaurav Sharma (bewithgaurav) merged 4 commits into
devfrom
feat/mssql-python-optin-backend

Conversation

@bewithgaurav

@bewithgaurav Gaurav Sharma (bewithgaurav) commented Sep 8, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Add per-connection mssql-python selection through OPTIONS.python_driver while preserving pyodbc as the default.

  • Require mssql-python >=1.15.0 and provide direct installation guidance.
  • Preserve current dev MARS behavior for pyodbc and avoid injecting reserved driver keywords on the opt-in path.
  • Keep driver selection local to the connection, including reconnects, and use native datetimeoffset conversion for mssql-python.
  • Stop retrying after a permanent connection error and preserve single-argument exceptions.
  • Add regression coverage for loading, connection arguments, driver isolation, and retry behavior. Keep the live MARS opt-out tests explicitly on pyodbc.

Validation

On Python 3.14 / Django 6.1.1 / SQL Server 2025, the owned suite completed with both pyodbc and published mssql-python 1.15.0: 250 tests per run, with 3 existing skips and 3 expected failures.

Live mixed-driver checks covered Decimal and binary values, UTC datetime round-trips, non-UTC datetimeoffset retrieval, commit/savepoint rollback, error translation, and reconnects. Cloud Entra authentication and the full Windows/Linux upstream-Django matrix remain release-integration gates; they were not validated here.

Dependencies

Additional tests, documentation, and packaging remain in #597, #598, and #599. No dependency on the superseded #595.

User Story: AB#47999
Task: AB#48000

Add mssql-python as an opt-in DB-API driver, selected per-connection via
OPTIONS = {"python_driver": "mssql_python"}. pyodbc remains the default
and is unchanged: when the option is absent the connection string, retry
logic, and error handling take exactly the same path as before.

When the option is set, the backend lazily imports mssql-python (so it is
not required unless used), routes the connection through it, and adjusts
for the bundled driver:

- The connection string omits the ODBC-only DRIVER / DSN / SERVERNAME /
  MARS_Connection keywords that the bundled driver rejects, and uses the
  SERVER=host,port form.
- unicode_results (a pyodbc-only connect keyword) is not passed.
- Transient and network error numbers are matched against the exception
  message text, since mssql-python does not expose a SQLSTATE in args.
- self.Database shadows the module-level pyodbc reference per connection so
  Django's error wrapping and the cursor paths use the driver that actually
  connected.

The default pyodbc unit tests are unchanged and continue to pass. Part of
the mssql-django 2.0 opt-in mssql-python work.

AB#

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 8, 2026 19:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There’s a confirmed retry-loop correctness bug plus misleading installation guidance and missing coverage for the new python_driver behavior.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds an opt-in mssql-python DB-API driver path to the SQL Server backend’s DatabaseWrapper so individual Django connections can select an alternative driver via OPTIONS['python_driver'], while keeping pyodbc as the default code path.

Changes:

  • Added lazy mssql_python import and per-connection driver selection/shadowing via self.Database.
  • Adjusted connection-string construction to omit ODBC-only keywords for the mssql-python path and use SERVER=host,port.
  • Updated connect/retry/error-handling and cursor error wrapping to use the per-connection driver module.
File summaries
File Description
mssql/base.py Introduces opt-in mssql-python driver selection, connection-string branching, and per-connection DB-API routing.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread mssql/base.py Outdated
Comment thread mssql/base.py
Comment thread mssql/base.py
Merge current dev while preserving explicit MARS handling.
Require the released driver, isolate connection selection, and fix retry state.
Use native datetimeoffset conversion and add backend regression coverage.

AB#48000

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 15, 2026 08:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Three moderate issues in mssql/base.py affect connection-parameter precedence and MARS capability handling.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

mssql/base.py:515

  • The opt-in path now emits SERVER unconditionally, but extra_params is appended verbatim below this method. An explicit extra_params='SERVER=...' therefore produces duplicate server keywords, so the user's connection target may be ignored or rejected by the mssql-python parser. Treat an explicitly supplied server key as authoritative before adding the default.
        if use_python_driver:
            # mssql-python bundles its own SQL Server driver and rejects the
            # ODBC-only DRIVER / DSN / SERVERNAME keywords, so none are emitted.
            if port:
                host = ','.join((host, str(port)))
            cstr_parts['SERVER'] = host
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread mssql/base.py
Comment thread mssql/base.py
AB#48000

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 15, 2026 19:03
@bewithgaurav
Gaurav Sharma (bewithgaurav) marked this pull request as ready for review September 15, 2026 19:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Critical driver error-handling and UUID conversion issues remain, along with moderate alias and test-ordering issues.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (2)

mssql/base.py:562

  • This exact-key filter does not handle mssql-python's connection-string aliases. For example, Address=override is normalized by that driver to the same canonical Server key as the generated SERVER, but the generated value appears first and wins, so the documented "explicit extras take precedence" behavior silently connects to the wrong host. Canonicalize aliases before removing generated keys (and cover the equivalent authentication/database aliases).
            extra_params = self._parse_extra_params(options_extra_params)
            cstr_parts = {
                key: value for key, value in cstr_parts.items()
                if key.lower() not in extra_params
            }

testapp/tests/test_mssql_python_backend.py:248

  • The query has no outer ORDER BY, but the assertions require rows to arrive as 1..2000. SQL Server does not guarantee result order without an outer ordering clause, so this live regression test can fail when the execution plan changes or runs in parallel. Add an alias for the row number and ORDER BY that alias before asserting the sequence.
                SELECT TOP (2000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
                    CAST(REPLICATE(N'x', 1024) AS nvarchar(1024))
                FROM d a CROSS JOIN d b CROSS JOIN d c CROSS JOIN d e
            """)
  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread mssql/base.py Outdated
Comment thread mssql/base.py Outdated
@github-actions

github-actions Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

100%


🎯 Overall Coverage

86.02%


📈 Total Lines Covered: 2659 out of 3091
📁 Project: mssql-django


Diff Coverage

Diff: dev...HEAD, staged and unstaged changes

No lines with coverage information in this diff.


📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
- mssql/__init__.py: 50.0%  (2 lines)
- mssql/creation.py: 58.1%  (74 lines)
- mssql/management/commands/inspectdb.py: 75.0%  (12 lines)
- mssql/operations.py: 78.8%  (396 lines)
- mssql/compiler.py: 83.4%  (644 lines)
- mssql/client.py: 85.4%  (41 lines)
- mssql/functions.py: 86.9%  (428 lines)
- mssql/schema.py: 88.3%  (719 lines)
- mssql/introspection.py: 90.2%  (133 lines)
- mssql/base.py: 91.7%  (554 lines)

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

AB#48000

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 16, 2026 04:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Two moderate findings remain unresolved, and the backend changes require final human review.

Review details

Suppressed comments (2)

testapp/tests/test_base.py:767

  • connection.copy() only shallow-copies the settings dictionary, so mutating this nested OPTIONS mapping also changes the default alias. In a mssql-python test run this forces the shared default connection back to pyodbc, making later tests/aliases use the wrong driver; copy OPTIONS before setting the per-alias override.
        options["python_driver"] = "pyodbc"

testapp/tests/test_mssql_python_backend.py:274

  • The copied wrapper shares the original alias's nested OPTIONS, so this mutation leaks MARS_Connection=no into the default connection settings. That makes subsequent tests reconnect with a different connection string and can invalidate MARS/driver-isolation coverage; clone the OPTIONS mapping before changing extra_params.
            options["extra_params"] = "MARS_Connection=no;" + (options.get("extra_params") or "")
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The version check incorrectly accepts prerelease builds below the required 1.15.0 release.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread mssql/base.py
Comment thread mssql/base.py
@bewithgaurav
Gaurav Sharma (bewithgaurav) merged commit 23b6361 into dev Sep 16, 2026
26 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants