Skip to content

FIX: accept timeout=0 in bulkcopy as no timeout - #698

Open
Gaurav Sharma (bewithgaurav) wants to merge 5 commits into
mainfrom
bewithgaurav/fix-697-bulkcopy-timeout-zero
Open

FIX: accept timeout=0 in bulkcopy as no timeout#698
Gaurav Sharma (bewithgaurav) wants to merge 5 commits into
mainfrom
bewithgaurav/fix-697-bulkcopy-timeout-zero

Conversation

@bewithgaurav

Copy link
Copy Markdown
Collaborator

Work Item / Issue Reference

GitHub Issue: #697


Summary

bulkcopy() rejected timeout=0 with "timeout must be positive", but the BCP API spec documents 0 as no timeout and mssql-py-core implements it that way (BulkCopyTimeoutState::from_seconds maps 0 to an infinite deadline). the guard has been there since the feature shipped, so the documented value never worked.

relaxes the bound to < 0, matching what batch_size already does. bool is excluded explicitly since it is an int subclass and False would otherwise slip through as 0 and silently disable the timeout. negatives stay rejected since py-core takes an unsigned value.

three tests: a live copy with timeout=0, a mocked assertion that 0 reaches py-core unchanged rather than being swapped for the 30s default, and rejection cases for negatives, floats and bools.

the BCP API spec documents timeout 0 as no timeout, and mssql-py-core implements it that way (BulkCopyTimeoutState::from_seconds maps 0 to an infinite deadline). the python layer rejected it with a 'timeout must be positive' guard that has been in place since the feature shipped, so the documented value never worked.

relaxes the bound to '< 0', matching what batch_size already does. bool is excluded explicitly since it is an int subclass and False would otherwise slip through as 0 and silently disable the timeout. negatives stay rejected, py-core takes an unsigned value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the pr-size: small Minimal code update label Aug 4, 2026
Comment thread tests/test_019_bulkcopy.py Fixed
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

100%


🎯 Overall Coverage

82%


📈 Total Lines Covered: 7368 out of 8963
📁 Project: mssql-python


Diff Coverage

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

  • mssql_python/cursor.py (100%)

Summary

  • Total: 4 lines
  • Missing: 0 lines
  • Coverage: 100%

📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 59.9%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 76.6%
mssql_python.__init__.py: 77.6%
mssql_python.row.py: 77.6%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 81.4%
mssql_python.pybind.connection.connection.cpp: 84.3%
mssql_python.logging.py: 85.5%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

resolves the cursor.py conflict from #665, which moved the shared bulkcopy
validation into _bulkcopy_core_and_validate. the timeout fix now lives in that
single helper, so it covers bulkcopy and bulkcopy_arrow at once.

also updates test_024's timeout_non_positive, which asserted the old behaviour,
and adds _token_provider to the new mock cursor for the #603 token_provider path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added pr-size: medium Moderate update size and removed pr-size: small Minimal code update labels Aug 11, 2026
the mock only asserted that 0 reached py-core, which is plumbing rather than
behaviour. a slow generator makes the real thing testable: the same source
raises a timeout error at timeout=1 and copies all 30 rows at timeout=0, so the
only variable is the timeout value. the source paces itself with sleeps, so it
outlives the 1s timeout regardless of machine speed.

verified all three timeout tests fail when the fix is reverted.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bewithgaurav
Gaurav Sharma (bewithgaurav) marked this pull request as ready for review August 11, 2026 06:08
Copilot AI lite review requested due to automatic review settings August 11, 2026 06:08

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.

Pull request overview

Fixes the Python-layer bulk copy timeout validator to accept timeout=0 as “no timeout”, aligning Cursor.bulkcopy() / Cursor.bulkcopy_arrow() with the documented BCP semantics and the underlying mssql_py_core behavior.

Changes:

  • Relax timeout validation from <= 0 to < 0, and explicitly reject bool (since it’s an int subclass).
  • Update bulkcopy / bulkcopy_arrow docstrings to document 0 as disabling the operation timeout.
  • Add/adjust tests to cover timeout=0 behavior and rejection of invalid timeout values.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
mssql_python/cursor.py Updates shared bulk copy timeout validation to allow 0 and reject bool; documents 0 semantics in both public APIs.
tests/test_019_bulkcopy.py Adds integration coverage for timeout=0 acceptance/behavior and invalid timeout rejection cases.
tests/test_024_bulkcopy_arrow.py Updates unit validation tests for bulkcopy_arrow to reflect timeout=0 acceptance and new rejection cases.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/test_024_bulkcopy_arrow.py Outdated
Comment on lines +224 to +226
with pytest.raises(Exception) as exc:
_bare_cursor().bulkcopy_arrow("t", pa.table({"a": [1]}), timeout=0)
assert "timeout" not in str(exc.value).lower()
Copilot AI and others added 2 commits August 11, 2026 11:52
the old test asserted that an unrelated downstream error message did not
contain the word 'timeout', which is a weak proxy for 'validation passed' and
breaks if that message ever changes. a live copy with timeout=0 asserts the
behaviour directly, matching how the classic path is covered in test_019.

verified it fails when the fix is reverted.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: medium Moderate update size

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants