Skip to content

Commit 79373c9

Browse files
committed
Merge branch 'main' of https://github.com/microsoft/mssql-python into bewithgaurav/universal_codecov
2 parents 8e5c604 + 8fbf45a commit 79373c9

File tree

11 files changed

+3992
-137
lines changed

11 files changed

+3992
-137
lines changed

PyPI_Description.md

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,46 @@
11
# mssql-python
22

3-
This is a new Python driver for Microsoft SQL Server currently in Public Preview phase.
3+
mssql-python is a new first-party SQL Server driver for Python that has all of the benefits of a fresh start while preserving a familiar experience for developers.
4+
5+
## What makes mssql-python different?
6+
7+
### Powered by DDBC – Direct Database Connectivity
8+
9+
Most Python SQL Server drivers, including pyodbc, route calls through the Driver Manager, which has slightly different implementations across Windows, macOS, and Linux. This results in inconsistent behavior and capabilities across platforms. Additionally, the Driver Manager must be installed separately, creating friction for both new developers and when deploying applications to servers.
10+
11+
At the heart of the driver is DDBC (Direct Database Connectivity) — a lightweight, high-performance C++ layer that replaces the platform’s Driver Manager.
12+
13+
Key Advantages:
14+
15+
- Provides a consistent, cross-platform backend that handles connections, statements, and memory directly.
16+
- Interfaces directly with the native SQL Server drivers.
17+
- Integrates with the same TDS core library that powers the ODBC driver.
18+
19+
### Why is this architecture important?
20+
21+
By simplifying the architecture, DDBC delivers:
22+
23+
- Consistency across platforms
24+
- Lower function call overhead
25+
- Zero external dependencies on Windows (`pip install mssql-python` is all you need)
26+
- Full control over connections, memory, and statement handling
27+
28+
### Built with PyBind11 + Modern C++ for Performance and Safety
29+
30+
To expose the DDBC engine to Python, mssql-python uses PyBind11 – a modern C++ binding library, instead of ctypes. With ctypes, every call between Python and the ODBC driver involved costly type conversions, manual pointer management, resulting in slow and potentially unsafe code.
31+
32+
PyBind11 provides:
33+
34+
- Native-speed execution with automatic type conversions
35+
- Memory-safe bindings
36+
- Clean and Pythonic API, while performance-critical logic remains in robust, maintainable C++.
437

538
## Public Preview Release
639

7-
We are making progress - The Public Preview of our driver is now available! This marks a significant milestone in our development journey. While we saw a few early adopters of our public preview release, we are introducing the following functionalities to support your applications in a more robust and reliable manner.
40+
We are currently in **Public Preview**.
841

9-
### What's Included:
42+
## What's new in v0.10.0
1043

11-
- Everything from previous releases
1244
- **SUSE Linux Support:** Added full support for SUSE and openSUSE distributions alongside existing other Linux distros support, broadening enterprise Linux compatibility.
1345
- **Context Manager Support:** Implemented Python `with` statement support for Connection and Cursor classes with automatic transaction management and resource cleanup.
1446
- **Large Text Streaming:** Added Data At Execution (DAE) support for streaming large text parameters (`NVARCHAR(MAX)`, `VARCHAR(MAX)`), eliminating memory constraints for bulk text `execute()` operations.
@@ -26,11 +58,6 @@ For more information, please visit the project link on Github: https://github.co
2658

2759
If you have any feedback, questions or need support please mail us at [email protected].
2860

29-
### What's Next:
30-
31-
As we continue to develop and refine the driver, you can expect regular updates that will introduce new features, optimizations, and bug fixes. We encourage you to contribute, provide feedback and report any issues you encounter, as this will help us improve the driver for the final release.
32-
33-
### Stay Tuned:
61+
## What's Next
3462

35-
We appreciate your interest and support in this project. Stay tuned for more updates and enhancements as we work towards delivering a robust and fully-featured driver in coming months.
36-
Thank you for being a part of our journey!
63+
As we continue to develop and refine the driver, you can expect regular updates that will introduce new features, optimizations, and bug fixes. We encourage you to contribute, provide feedback and report any issues you encounter, as this will help us improve the driver ahead of General Availability.

mssql_python/__init__.py

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,104 @@
33
Licensed under the MIT license.
44
This module initializes the mssql_python package.
55
"""
6+
import threading
7+
import locale
68

79
# Exceptions
810
# https://www.python.org/dev/peps/pep-0249/#exceptions
11+
12+
# GLOBALS
13+
# Read-Only
14+
apilevel = "2.0"
15+
paramstyle = "qmark"
16+
threadsafety = 1
17+
18+
# Initialize the locale setting only once at module import time
19+
# This avoids thread-safety issues with locale
20+
_DEFAULT_DECIMAL_SEPARATOR = "."
21+
try:
22+
# Get the locale setting once during module initialization
23+
_locale_separator = locale.localeconv()['decimal_point']
24+
if _locale_separator and len(_locale_separator) == 1:
25+
_DEFAULT_DECIMAL_SEPARATOR = _locale_separator
26+
except (AttributeError, KeyError, TypeError, ValueError):
27+
pass # Keep the default "." if locale access fails
28+
29+
class Settings:
30+
def __init__(self):
31+
self.lowercase = False
32+
# Use the pre-determined separator - no locale access here
33+
self.decimal_separator = _DEFAULT_DECIMAL_SEPARATOR
34+
35+
# Global settings instance
36+
_settings = Settings()
37+
_settings_lock = threading.Lock()
38+
39+
def get_settings():
40+
"""Return the global settings object"""
41+
with _settings_lock:
42+
_settings.lowercase = lowercase
43+
return _settings
44+
45+
lowercase = _settings.lowercase # Default is False
46+
47+
# Set the initial decimal separator in C++
48+
from .ddbc_bindings import DDBCSetDecimalSeparator
49+
DDBCSetDecimalSeparator(_settings.decimal_separator)
50+
51+
# New functions for decimal separator control
52+
def setDecimalSeparator(separator):
53+
"""
54+
Sets the decimal separator character used when parsing NUMERIC/DECIMAL values
55+
from the database, e.g. the "." in "1,234.56".
56+
57+
The default is to use the current locale's "decimal_point" value when the module
58+
was first imported, or "." if the locale is not available. This function overrides
59+
the default.
60+
61+
Args:
62+
separator (str): The character to use as decimal separator
63+
64+
Raises:
65+
ValueError: If the separator is not a single character string
66+
"""
67+
# Type validation
68+
if not isinstance(separator, str):
69+
raise ValueError("Decimal separator must be a string")
70+
71+
# Length validation
72+
if len(separator) == 0:
73+
raise ValueError("Decimal separator cannot be empty")
74+
75+
if len(separator) > 1:
76+
raise ValueError("Decimal separator must be a single character")
77+
78+
# Character validation
79+
if separator.isspace():
80+
raise ValueError("Whitespace characters are not allowed as decimal separators")
81+
82+
# Check for specific disallowed characters
83+
if separator in ['\t', '\n', '\r', '\v', '\f']:
84+
raise ValueError(f"Control character '{repr(separator)}' is not allowed as a decimal separator")
85+
86+
# Set in Python side settings
87+
_settings.decimal_separator = separator
88+
89+
# Update the C++ side
90+
from .ddbc_bindings import DDBCSetDecimalSeparator
91+
DDBCSetDecimalSeparator(separator)
92+
93+
def getDecimalSeparator():
94+
"""
95+
Returns the decimal separator character used when parsing NUMERIC/DECIMAL values
96+
from the database.
97+
98+
Returns:
99+
str: The current decimal separator character
100+
"""
101+
return _settings.decimal_separator
102+
103+
# Import necessary modules
9104
from .exceptions import (
10105
Warning,
11106
Error,
@@ -52,12 +147,6 @@
52147
SQL_WCHAR = ConstantsDDBC.SQL_WCHAR.value
53148
SQL_WMETADATA = -99
54149

55-
# GLOBALS
56-
# Read-Only
57-
apilevel = "2.0"
58-
paramstyle = "qmark"
59-
threadsafety = 1
60-
61150
from .pooling import PoolingManager
62151
def pooling(max_size=100, idle_timeout=600, enabled=True):
63152
# """
@@ -76,3 +165,18 @@ def pooling(max_size=100, idle_timeout=600, enabled=True):
76165
PoolingManager.disable()
77166
else:
78167
PoolingManager.enable(max_size, idle_timeout)
168+
169+
import sys
170+
_original_module_setattr = sys.modules[__name__].__setattr__
171+
172+
def _custom_setattr(name, value):
173+
if name == 'lowercase':
174+
with _settings_lock:
175+
_settings.lowercase = bool(value)
176+
# Update the module's lowercase variable
177+
_original_module_setattr(name, _settings.lowercase)
178+
else:
179+
_original_module_setattr(name, value)
180+
181+
# Replace the module's __setattr__ with our custom version
182+
sys.modules[__name__].__setattr__ = _custom_setattr

0 commit comments

Comments
 (0)