Skip to content

Commit bf9452b

Browse files
committed
Merge branch 'master' of github.com:mongodb/mongo-python-driver
2 parents 4e84bd4 + 4eeaa4b commit bf9452b

38 files changed

+3722
-1125
lines changed

.evergreen/run-tests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,9 @@ if [ -z "$GREEN_FRAMEWORK" ]; then
257257
# Use --capture=tee-sys so pytest prints test output inline:
258258
# https://docs.pytest.org/en/stable/how-to/capture-stdout-stderr.html
259259
if [ -z "$TEST_SUITES" ]; then
260-
python -m pytest -v --capture=tee-sys --durations=5 --maxfail=10 $TEST_ARGS
260+
python -m pytest -v --capture=tee-sys --durations=5 $TEST_ARGS
261261
else
262-
python -m pytest -v --capture=tee-sys --durations=5 --maxfail=10 -m $TEST_SUITES $TEST_ARGS
262+
python -m pytest -v --capture=tee-sys --durations=5 -m $TEST_SUITES $TEST_ARGS
263263
fi
264264
else
265265
python green_framework_test.py $GREEN_FRAMEWORK -v $TEST_ARGS

.github/workflows/dist.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
# Note: the default manylinux is manylinux2014
6868
run: |
6969
python -m pip install -U pip
70-
python -m pip install "cibuildwheel>=2.17,<3"
70+
python -m pip install "cibuildwheel>=2.20,<3"
7171
7272
- name: Build wheels
7373
env:
@@ -89,6 +89,9 @@ jobs:
8989
ls wheelhouse/*cp310*.whl
9090
ls wheelhouse/*cp311*.whl
9191
ls wheelhouse/*cp312*.whl
92+
ls wheelhouse/*cp313*.whl
93+
# Free-threading builds:
94+
ls wheelhouse/*cp313t*.whl
9295
9396
- uses: actions/upload-artifact@v4
9497
with:

.github/workflows/test-python.yml

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,18 @@ jobs:
5151
strategy:
5252
matrix:
5353
os: [ubuntu-20.04]
54-
python-version: ["3.9", "pypy-3.9", "3.13"]
54+
python-version: ["3.9", "pypy-3.9", "3.13", "3.13t"]
5555
name: CPython ${{ matrix.python-version }}-${{ matrix.os }}
5656
steps:
5757
- uses: actions/checkout@v4
58-
- name: Setup Python
58+
- if: ${{ matrix.python-version == '3.13t' }}
59+
name: Setup free-threaded Python
60+
uses: deadsnakes/[email protected]
61+
with:
62+
python-version: 3.13
63+
nogil: true
64+
- if: ${{ matrix.python-version != '3.13t' }}
65+
name: Setup Python
5966
uses: actions/setup-python@v5
6067
with:
6168
python-version: ${{ matrix.python-version }}
@@ -65,9 +72,13 @@ jobs:
6572
- name: Install dependencies
6673
run: |
6774
pip install -U pip
68-
if [ "${{ matrix.python-version }}" == "3.13" ]; then
75+
if [[ "${{ matrix.python-version }}" == "3.13" ]]; then
6976
pip install --pre cffi setuptools
7077
pip install --no-build-isolation hatch
78+
elif [[ "${{ matrix.python-version }}" == "3.13t" ]]; then
79+
# Hatch can't be installed on 3.13t, use pytest directly.
80+
pip install .
81+
pip install -r requirements/test.txt
7182
else
7283
pip install hatch
7384
fi
@@ -77,7 +88,11 @@ jobs:
7788
mongodb-version: 6.0
7889
- name: Run tests
7990
run: |
80-
hatch run test:test
91+
if [[ "${{ matrix.python-version }}" == "3.13t" ]]; then
92+
pytest -v --durations=5 --maxfail=10
93+
else
94+
hatch run test:test
95+
fi
8196
8297
doctest:
8398
runs-on: ubuntu-latest

bson/_cbsonmodule.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3184,6 +3184,9 @@ static PyModuleDef_Slot _cbson_slots[] = {
31843184
{Py_mod_exec, _cbson_exec},
31853185
#if defined(Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED)
31863186
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED},
3187+
#endif
3188+
#if PY_VERSION_HEX >= 0x030D0000
3189+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
31873190
#endif
31883191
{0, NULL},
31893192
};

doc/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ PyMongo 4.11 brings a number of changes including:
1212

1313
- Dropped support for Python 3.8.
1414
- Dropped support for MongoDB 3.6.
15+
- Added support for free-threaded Python with the GIL disabled. For more information see:
16+
`Free-threaded CPython <https://docs.python.org/3.13/whatsnew/3.13.html#whatsnew313-free-threaded-cpython>`_.
1517

1618
Issues Resolved
1719
...............

pymongo/_cmessagemodule.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,9 @@ static PyModuleDef_Slot _cmessage_slots[] = {
10221022
{Py_mod_exec, _cmessage_exec},
10231023
#ifdef Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED
10241024
{Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED},
1025+
#endif
1026+
#if PY_VERSION_HEX >= 0x030D0000
1027+
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
10251028
#endif
10261029
{0, NULL},
10271030
};

pymongo/asynchronous/encryption.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,20 @@ async def kms_request(self, kms_context: MongoCryptKmsContext) -> None:
180180
while kms_context.bytes_needed > 0:
181181
# CSOT: update timeout.
182182
conn.settimeout(max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0))
183-
data = conn.recv(kms_context.bytes_needed)
183+
if _IS_SYNC:
184+
data = conn.recv(kms_context.bytes_needed)
185+
else:
186+
from pymongo.network_layer import ( # type: ignore[attr-defined]
187+
async_receive_data_socket,
188+
)
189+
190+
data = await async_receive_data_socket(conn, kms_context.bytes_needed)
184191
if not data:
185192
raise OSError("KMS connection closed")
186193
kms_context.feed(data)
194+
# Async raises an OSError instead of returning empty bytes
195+
except OSError as err:
196+
raise OSError("KMS connection closed") from err
187197
except BLOCKING_IO_ERRORS:
188198
raise socket.timeout("timed out") from None
189199
finally:

pymongo/network_layer.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def _is_ready(fut: Future) -> None:
130130
loop.remove_writer(fd)
131131

132132
async def _async_receive_ssl(
133-
conn: _sslConn, length: int, loop: AbstractEventLoop
133+
conn: _sslConn, length: int, loop: AbstractEventLoop, once: Optional[bool] = False
134134
) -> memoryview:
135135
mv = memoryview(bytearray(length))
136136
total_read = 0
@@ -145,6 +145,9 @@ def _is_ready(fut: Future) -> None:
145145
read = conn.recv_into(mv[total_read:])
146146
if read == 0:
147147
raise OSError("connection closed")
148+
# KMS responses update their expected size after the first batch, stop reading after one loop
149+
if once:
150+
return mv[:read]
148151
total_read += read
149152
except BLOCKING_IO_ERRORS as exc:
150153
fd = conn.fileno()
@@ -275,6 +278,28 @@ async def async_receive_data(
275278
sock.settimeout(sock_timeout)
276279

277280

281+
async def async_receive_data_socket(
282+
sock: Union[socket.socket, _sslConn], length: int
283+
) -> memoryview:
284+
sock_timeout = sock.gettimeout()
285+
timeout = sock_timeout
286+
287+
sock.settimeout(0.0)
288+
loop = asyncio.get_event_loop()
289+
try:
290+
if _HAVE_SSL and isinstance(sock, (SSLSocket, _sslConn)):
291+
return await asyncio.wait_for(
292+
_async_receive_ssl(sock, length, loop, once=True), # type: ignore[arg-type]
293+
timeout=timeout,
294+
)
295+
else:
296+
return await asyncio.wait_for(_async_receive(sock, length, loop), timeout=timeout) # type: ignore[arg-type]
297+
except asyncio.TimeoutError as err:
298+
raise socket.timeout("timed out") from err
299+
finally:
300+
sock.settimeout(sock_timeout)
301+
302+
278303
async def _async_receive(conn: socket.socket, length: int, loop: AbstractEventLoop) -> memoryview:
279304
mv = memoryview(bytearray(length))
280305
bytes_read = 0

pymongo/synchronous/encryption.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,20 @@ def kms_request(self, kms_context: MongoCryptKmsContext) -> None:
180180
while kms_context.bytes_needed > 0:
181181
# CSOT: update timeout.
182182
conn.settimeout(max(_csot.clamp_remaining(_KMS_CONNECT_TIMEOUT), 0))
183-
data = conn.recv(kms_context.bytes_needed)
183+
if _IS_SYNC:
184+
data = conn.recv(kms_context.bytes_needed)
185+
else:
186+
from pymongo.network_layer import ( # type: ignore[attr-defined]
187+
receive_data_socket,
188+
)
189+
190+
data = receive_data_socket(conn, kms_context.bytes_needed)
184191
if not data:
185192
raise OSError("KMS connection closed")
186193
kms_context.feed(data)
194+
# Async raises an OSError instead of returning empty bytes
195+
except OSError as err:
196+
raise OSError("KMS connection closed") from err
187197
except BLOCKING_IO_ERRORS:
188198
raise socket.timeout("timed out") from None
189199
finally:

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ partial_branches = ["if (.*and +)*not _use_c( and.*)*:"]
236236
directory = "htmlcov"
237237

238238
[tool.cibuildwheel]
239+
# Enable free-threaded support
240+
free-threaded-support = true
239241
skip = "pp* *-musllinux*"
240242
build-frontend = "build"
241243
test-command = "python {project}/tools/fail_if_no_c.py"

0 commit comments

Comments
 (0)