Skip to content

Commit 08cd24c

Browse files
authored
FIX: accept memoryview in Binary() (#741)
1 parent c0eaa4c commit 08cd24c

3 files changed

Lines changed: 47 additions & 16 deletions

File tree

‎mssql_python/mssql_python.pyi‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def Timestamp(
9292
def DateFromTicks(ticks: int) -> datetime.date: ...
9393
def TimeFromTicks(ticks: int) -> datetime.time: ...
9494
def TimestampFromTicks(ticks: int) -> datetime.datetime: ...
95-
def Binary(value: Union[str, bytes, bytearray]) -> bytes: ...
95+
def Binary(value: Union[str, bytes, bytearray, memoryview]) -> bytes: ...
9696

9797
# DB-API 2.0 Exception Hierarchy
9898
# https://www.python.org/dev/peps/pep-0249/#exceptions

‎mssql_python/type.py‎

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import datetime
88
import time
9+
from typing import Union
910

1011

1112
# Type Objects
@@ -120,15 +121,17 @@ def TimestampFromTicks(ticks: int) -> datetime.datetime:
120121
return datetime.datetime.fromtimestamp(ticks)
121122

122123

123-
def Binary(value) -> bytes:
124+
def Binary(value: Union[str, bytes, bytearray, memoryview]) -> bytes:
124125
"""
125-
Converts a string or bytes to bytes for use with binary database columns.
126+
Converts a string or bytes-like object to bytes for binary database columns.
126127
127-
This function follows the DB-API 2.0 specification.
128-
It accepts only str and bytes/bytearray types to ensure type safety.
128+
This function follows the DB-API 2.0 specification. It accepts str,
129+
bytes, bytearray, and memoryview. memoryview is accepted so buffer-protocol
130+
values (for example the memoryview Django hands a BinaryField) round-trip the
131+
same way pyodbc handles them.
129132
130133
Args:
131-
value: A string (str) or bytes-like object (bytes, bytearray)
134+
value: A string (str) or bytes-like object (bytes, bytearray, memoryview)
132135
133136
Returns:
134137
bytes: The input converted to bytes
@@ -137,18 +140,21 @@ def Binary(value) -> bytes:
137140
TypeError: If the input type is not supported
138141
139142
Examples:
140-
Binary("hello") # Returns b"hello"
141-
Binary(b"hello") # Returns b"hello"
142-
Binary(bytearray(b"hi")) # Returns b"hi"
143+
Binary("hello") # Returns b"hello"
144+
Binary(b"hello") # Returns b"hello"
145+
Binary(bytearray(b"hi")) # Returns b"hi"
146+
Binary(memoryview(b"hi")) # Returns b"hi"
143147
"""
144148
if isinstance(value, bytes):
145149
return value
146150
if isinstance(value, bytearray):
147151
return bytes(value)
152+
if isinstance(value, memoryview):
153+
return value.tobytes()
148154
if isinstance(value, str):
149155
return value.encode("utf-8")
150156
# Raise TypeError for unsupported types to improve type safety
151157
raise TypeError(
152158
f"Cannot convert type {type(value).__name__} to bytes. "
153-
f"Binary() only accepts str, bytes, or bytearray objects."
159+
f"Binary() only accepts str, bytes, bytearray, or memoryview objects."
154160
)

‎tests/test_002_types.py‎

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,37 +122,60 @@ def test_binary_string_encoding():
122122
assert result == b"Hello\nWorld\t!", "String with special characters should encode properly"
123123

124124

125+
def test_binary_memoryview():
126+
"""Binary() accepts memoryview (GH-739): Django BinaryField and DB-API buffers."""
127+
result = Binary(memoryview(b"\x01\x02\x03"))
128+
assert isinstance(result, bytes), "memoryview should be converted to bytes"
129+
assert result == b"\x01\x02\x03", "memoryview content should be preserved"
130+
131+
# Empty memoryview
132+
assert Binary(memoryview(b"")) == b""
133+
134+
# memoryview over a bytearray round-trips the same bytes
135+
assert Binary(memoryview(bytearray(b"hello"))) == b"hello"
136+
137+
125138
def test_binary_unsupported_types_error():
126139
"""Test Binary() TypeError for unsupported types (Lines 138-141)."""
127140
# Test integer type
128141
with pytest.raises(TypeError) as exc_info:
129142
Binary(123)
130143
assert "Cannot convert type int to bytes" in str(exc_info.value)
131-
assert "Binary() only accepts str, bytes, or bytearray objects" in str(exc_info.value)
144+
assert "Binary() only accepts str, bytes, bytearray, or memoryview objects" in str(
145+
exc_info.value
146+
)
132147

133148
# Test float type
134149
with pytest.raises(TypeError) as exc_info:
135150
Binary(3.14)
136151
assert "Cannot convert type float to bytes" in str(exc_info.value)
137-
assert "Binary() only accepts str, bytes, or bytearray objects" in str(exc_info.value)
152+
assert "Binary() only accepts str, bytes, bytearray, or memoryview objects" in str(
153+
exc_info.value
154+
)
138155

139156
# Test list type
140157
with pytest.raises(TypeError) as exc_info:
141158
Binary([1, 2, 3])
142159
assert "Cannot convert type list to bytes" in str(exc_info.value)
143-
assert "Binary() only accepts str, bytes, or bytearray objects" in str(exc_info.value)
160+
assert "Binary() only accepts str, bytes, bytearray, or memoryview objects" in str(
161+
exc_info.value
162+
)
144163

145164
# Test dict type
146165
with pytest.raises(TypeError) as exc_info:
147166
Binary({"key": "value"})
148167
assert "Cannot convert type dict to bytes" in str(exc_info.value)
149-
assert "Binary() only accepts str, bytes, or bytearray objects" in str(exc_info.value)
168+
assert "Binary() only accepts str, bytes, bytearray, or memoryview objects" in str(
169+
exc_info.value
170+
)
150171

151172
# Test None type
152173
with pytest.raises(TypeError) as exc_info:
153174
Binary(None)
154175
assert "Cannot convert type NoneType to bytes" in str(exc_info.value)
155-
assert "Binary() only accepts str, bytes, or bytearray objects" in str(exc_info.value)
176+
assert "Binary() only accepts str, bytes, bytearray, or memoryview objects" in str(
177+
exc_info.value
178+
)
156179

157180
# Test custom object type
158181
class CustomObject:
@@ -161,7 +184,9 @@ class CustomObject:
161184
with pytest.raises(TypeError) as exc_info:
162185
Binary(CustomObject())
163186
assert "Cannot convert type CustomObject to bytes" in str(exc_info.value)
164-
assert "Binary() only accepts str, bytes, or bytearray objects" in str(exc_info.value)
187+
assert "Binary() only accepts str, bytes, bytearray, or memoryview objects" in str(
188+
exc_info.value
189+
)
165190

166191

167192
def test_binary_comprehensive_coverage():

0 commit comments

Comments
 (0)