Skip to content

Commit e2bfa9a

Browse files
authored
PYTHON-5248 - Drop support for MongoDB 4.0 (#2353)
1 parent 4ea0288 commit e2bfa9a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+292
-1432
lines changed

.evergreen/generated_configs/tasks.yml

Lines changed: 234 additions & 408 deletions
Large diffs are not rendered by default.

.evergreen/generated_configs/variants.yml

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ buildvariants:
146146
COMPRESSOR: zlib
147147
- name: compression-zstd-rhel8
148148
tasks:
149-
- name: .test-standard !.server-4.0
149+
- name: .test-standard !.server-4.2
150150
display_name: Compression zstd RHEL8
151151
run_on:
152152
- rhel87-small
@@ -522,13 +522,6 @@ buildvariants:
522522
PYTHON_BINARY: /opt/python/3.9/bin/python3
523523

524524
# Server version tests
525-
- name: mongodb-v4.0
526-
tasks:
527-
- name: .server-version
528-
display_name: "* MongoDB v4.0"
529-
run_on:
530-
- rhel87-small
531-
tags: [coverage_tag]
532525
- name: mongodb-v4.2
533526
tasks:
534527
- name: .server-version
@@ -664,11 +657,3 @@ buildvariants:
664657
- rhel87-small
665658
expansions:
666659
STORAGE_ENGINE: inmemory
667-
- name: storage-mmapv1-rhel8
668-
tasks:
669-
- name: .test-standard !.sharded_cluster-auth-ssl .server-4.0
670-
display_name: Storage MMAPv1 RHEL8
671-
run_on:
672-
- rhel87-small
673-
expansions:
674-
STORAGE_ENGINE: mmapv1

.evergreen/scripts/generate_config.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
get_task_name,
2626
get_variant_name,
2727
get_versions_from,
28-
get_versions_until,
2928
handle_c_ext,
3029
write_functions_to_file,
3130
write_tasks_to_file,
@@ -196,7 +195,7 @@ def create_compression_variants():
196195
for compressor in "snappy", "zlib", "zstd":
197196
expansions = dict(COMPRESSOR=compressor)
198197
if compressor == "zstd":
199-
tasks = [".test-standard !.server-4.0"]
198+
tasks = [".test-standard !.server-4.2"]
200199
else:
201200
tasks = [".test-standard"]
202201
display_name = get_variant_name(f"Compression {compressor}", host)
@@ -249,16 +248,11 @@ def create_pyopenssl_variants():
249248

250249
def create_storage_engine_variants():
251250
host = DEFAULT_HOST
252-
engines = ["InMemory", "MMAPv1"]
251+
engines = ["InMemory"]
253252
variants = []
254253
for engine in engines:
255254
expansions = dict(STORAGE_ENGINE=engine.lower())
256-
if engine == engines[0]:
257-
tasks = [".test-standard .standalone-noauth-nossl"]
258-
else:
259-
# MongoDB 4.2 drops support for MMAPv1
260-
versions = get_versions_until("4.0")
261-
tasks = [f".test-standard !.sharded_cluster-auth-ssl .server-{v}" for v in versions]
255+
tasks = [".test-standard .standalone-noauth-nossl"]
262256
display_name = get_variant_name(f"Storage {engine}", host)
263257
variant = create_variant(tasks, display_name, host=host, expansions=expansions)
264258
variants.append(variant)

.evergreen/scripts/generate_config_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# Globals
2222
##############
2323

24-
ALL_VERSIONS = ["4.0", "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest"]
24+
ALL_VERSIONS = ["4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest"]
2525
CPYTHONS = ["3.9", "3.10", "3.11", "3.12", "3.13"]
2626
PYPYS = ["pypy3.10"]
2727
ALL_PYTHONS = CPYTHONS + PYPYS

pymongo/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@
6666
MAX_WRITE_BATCH_SIZE = 100000
6767

6868
# What this version of PyMongo supports.
69-
MIN_SUPPORTED_SERVER_VERSION = "4.0"
70-
MIN_SUPPORTED_WIRE_VERSION = 7
69+
MIN_SUPPORTED_SERVER_VERSION = "4.2"
70+
MIN_SUPPORTED_WIRE_VERSION = 8
7171
# MongoDB 8.0
7272
MAX_SUPPORTED_WIRE_VERSION = 25
7373

test/__init__.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -508,19 +508,6 @@ def require_data_lake(self, func):
508508
func=func,
509509
)
510510

511-
def require_no_mmap(self, func):
512-
"""Run a test only if the server is not using the MMAPv1 storage
513-
engine. Only works for standalone and replica sets; tests are
514-
run regardless of storage engine on sharded clusters.
515-
"""
516-
517-
def is_not_mmap():
518-
if self.is_mongos:
519-
return True
520-
return self.storage_engine != "mmapv1"
521-
522-
return self._require(is_not_mmap, "Storage engine must not be MMAPv1", func=func)
523-
524511
def require_version_min(self, *ver):
525512
"""Run a test only if the server version is at least ``version``."""
526513
other_version = Version(*ver)
@@ -651,7 +638,7 @@ def require_no_load_balancer(self, func):
651638

652639
def require_change_streams(self, func):
653640
"""Run a test only if the server supports change streams."""
654-
return self.require_no_mmap(self.require_no_standalone(func))
641+
return self.require_no_standalone(func)
655642

656643
def is_topology_type(self, topologies):
657644
unknown = set(topologies) - {
@@ -754,8 +741,6 @@ def require_sessions(self, func):
754741
return self._require(lambda: self.sessions_enabled, "Sessions not supported", func=func)
755742

756743
def supports_retryable_writes(self):
757-
if self.storage_engine == "mmapv1":
758-
return False
759744
if not self.sessions_enabled:
760745
return False
761746
return self.is_mongos or self.is_rs
@@ -769,9 +754,6 @@ def require_retryable_writes(self, func):
769754
)
770755

771756
def supports_transactions(self):
772-
if self.storage_engine == "mmapv1":
773-
return False
774-
775757
if self.version.at_least(4, 1, 8):
776758
return self.is_mongos or self.is_rs
777759

test/asynchronous/__init__.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -508,19 +508,6 @@ def require_data_lake(self, func):
508508
func=func,
509509
)
510510

511-
def require_no_mmap(self, func):
512-
"""Run a test only if the server is not using the MMAPv1 storage
513-
engine. Only works for standalone and replica sets; tests are
514-
run regardless of storage engine on sharded clusters.
515-
"""
516-
517-
def is_not_mmap():
518-
if self.is_mongos:
519-
return True
520-
return self.storage_engine != "mmapv1"
521-
522-
return self._require(is_not_mmap, "Storage engine must not be MMAPv1", func=func)
523-
524511
def require_version_min(self, *ver):
525512
"""Run a test only if the server version is at least ``version``."""
526513
other_version = Version(*ver)
@@ -651,7 +638,7 @@ def require_no_load_balancer(self, func):
651638

652639
def require_change_streams(self, func):
653640
"""Run a test only if the server supports change streams."""
654-
return self.require_no_mmap(self.require_no_standalone(func))
641+
return self.require_no_standalone(func)
655642

656643
async def is_topology_type(self, topologies):
657644
unknown = set(topologies) - {
@@ -754,8 +741,6 @@ def require_sessions(self, func):
754741
return self._require(lambda: self.sessions_enabled, "Sessions not supported", func=func)
755742

756743
def supports_retryable_writes(self):
757-
if self.storage_engine == "mmapv1":
758-
return False
759744
if not self.sessions_enabled:
760745
return False
761746
return self.is_mongos or self.is_rs
@@ -769,9 +754,6 @@ def require_retryable_writes(self, func):
769754
)
770755

771756
def supports_transactions(self):
772-
if self.storage_engine == "mmapv1":
773-
return False
774-
775757
if self.version.at_least(4, 1, 8):
776758
return self.is_mongos or self.is_rs
777759

test/asynchronous/test_bulk.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ async def _test_update_many(self, update):
165165
async def test_update_many(self):
166166
await self._test_update_many({"$set": {"foo": "bar"}})
167167

168-
@async_client_context.require_version_min(4, 1, 11)
168+
@async_client_context.require_version_min(4, 2, 0)
169169
async def test_update_many_pipeline(self):
170170
await self._test_update_many([{"$set": {"foo": "bar"}}])
171171

@@ -206,7 +206,7 @@ async def _test_update_one(self, update):
206206
async def test_update_one(self):
207207
await self._test_update_one({"$set": {"foo": "bar"}})
208208

209-
@async_client_context.require_version_min(4, 1, 11)
209+
@async_client_context.require_version_min(4, 2, 0)
210210
async def test_update_one_pipeline(self):
211211
await self._test_update_one([{"$set": {"foo": "bar"}}])
212212

test/asynchronous/test_change_stream.py

Lines changed: 12 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ async def test_batch_size_is_honored(self):
267267

268268
# $changeStream.startAtOperationTime was added in 4.0.0.
269269
@no_type_check
270-
@async_client_context.require_version_min(4, 0, 0)
270+
@async_client_context.require_version_min(4, 2, 0)
271271
async def test_start_at_operation_time(self):
272272
optime = await self.get_start_at_operation_time()
273273

@@ -436,7 +436,7 @@ async def test_change_operations(self):
436436
await self._test_get_invalidate_event(change_stream)
437437

438438
@no_type_check
439-
@async_client_context.require_version_min(4, 1, 1)
439+
@async_client_context.require_version_min(4, 2, 0)
440440
async def test_start_after(self):
441441
resume_token = await self.get_resume_token(invalidate=True)
442442

@@ -452,7 +452,7 @@ async def test_start_after(self):
452452
self.assertEqual(change["fullDocument"], {"_id": 2})
453453

454454
@no_type_check
455-
@async_client_context.require_version_min(4, 1, 1)
455+
@async_client_context.require_version_min(4, 2, 0)
456456
async def test_start_after_resume_process_with_changes(self):
457457
resume_token = await self.get_resume_token(invalidate=True)
458458

@@ -563,27 +563,16 @@ async def _test_update_resume_token(self, expected_rt_getter):
563563
)
564564

565565
# Prose test no. 1
566-
@async_client_context.require_version_min(4, 0, 7)
566+
@async_client_context.require_version_min(4, 2, 0)
567567
async def test_update_resume_token(self):
568568
await self._test_update_resume_token(self._get_expected_resume_token)
569569

570-
# Prose test no. 1
571-
@async_client_context.require_version_max(4, 0, 7)
572-
async def test_update_resume_token_legacy(self):
573-
await self._test_update_resume_token(self._get_expected_resume_token_legacy)
574-
575570
# Prose test no. 2
576-
@async_client_context.require_version_min(4, 1, 8)
571+
@async_client_context.require_version_min(4, 2, 0)
577572
async def test_raises_error_on_missing_id_418plus(self):
578573
# Server returns an error on 4.1.8+
579574
await self._test_raises_error_on_missing_id(OperationFailure)
580575

581-
# Prose test no. 2
582-
@async_client_context.require_version_max(4, 1, 8)
583-
async def test_raises_error_on_missing_id_418minus(self):
584-
# PyMongo raises an error
585-
await self._test_raises_error_on_missing_id(InvalidOperation)
586-
587576
# Prose test no. 3
588577
@no_type_check
589578
async def test_resume_on_error(self):
@@ -642,40 +631,12 @@ def raise_error():
642631
cursor.close = raise_error
643632
await self.insert_one_and_check(change_stream, {"_id": 2})
644633

645-
# Prose test no. 9
646-
@no_type_check
647-
@async_client_context.require_version_min(4, 0, 0)
648-
@async_client_context.require_version_max(4, 0, 7)
649-
async def test_start_at_operation_time_caching(self):
650-
# Case 1: change stream not started with startAtOperationTime
651-
client, listener = self.client_with_listener("aggregate")
652-
async with await self.change_stream_with_client(client) as cs:
653-
await self.kill_change_stream_cursor(cs)
654-
await cs.try_next()
655-
cmd = listener.started_events[-1].command
656-
self.assertIsNotNone(cmd["pipeline"][0]["$changeStream"].get("startAtOperationTime"))
657-
658-
# Case 2: change stream started with startAtOperationTime
659-
listener.reset()
660-
optime = await self.get_start_at_operation_time()
661-
async with await self.change_stream_with_client(
662-
client, start_at_operation_time=optime
663-
) as cs:
664-
await self.kill_change_stream_cursor(cs)
665-
await cs.try_next()
666-
cmd = listener.started_events[-1].command
667-
self.assertEqual(
668-
cmd["pipeline"][0]["$changeStream"].get("startAtOperationTime"),
669-
optime,
670-
str([k.command for k in listener.started_events]),
671-
)
672-
673634
# Prose test no. 10 - SKIPPED
674635
# This test is identical to prose test no. 3.
675636

676637
# Prose test no. 11
677638
@no_type_check
678-
@async_client_context.require_version_min(4, 0, 7)
639+
@async_client_context.require_version_min(4, 2, 0)
679640
async def test_resumetoken_empty_batch(self):
680641
client, listener = await self._client_with_listener("getMore")
681642
async with await self.change_stream_with_client(client) as change_stream:
@@ -687,7 +648,7 @@ async def test_resumetoken_empty_batch(self):
687648

688649
# Prose test no. 11
689650
@no_type_check
690-
@async_client_context.require_version_min(4, 0, 7)
651+
@async_client_context.require_version_min(4, 2, 0)
691652
async def test_resumetoken_exhausted_batch(self):
692653
client, listener = await self._client_with_listener("getMore")
693654
async with await self.change_stream_with_client(client) as change_stream:
@@ -697,38 +658,6 @@ async def test_resumetoken_exhausted_batch(self):
697658
response = listener.succeeded_events[-1].reply
698659
self.assertEqual(resume_token, response["cursor"]["postBatchResumeToken"])
699660

700-
# Prose test no. 12
701-
@no_type_check
702-
@async_client_context.require_version_max(4, 0, 7)
703-
async def test_resumetoken_empty_batch_legacy(self):
704-
resume_point = await self.get_resume_token()
705-
706-
# Empty resume token when neither resumeAfter or startAfter specified.
707-
async with await self.change_stream() as change_stream:
708-
await change_stream.try_next()
709-
self.assertIsNone(change_stream.resume_token)
710-
711-
# Resume token value is same as resumeAfter.
712-
async with await self.change_stream(resume_after=resume_point) as change_stream:
713-
await change_stream.try_next()
714-
resume_token = change_stream.resume_token
715-
self.assertEqual(resume_token, resume_point)
716-
717-
# Prose test no. 12
718-
@no_type_check
719-
@async_client_context.require_version_max(4, 0, 7)
720-
async def test_resumetoken_exhausted_batch_legacy(self):
721-
# Resume token is _id of last change.
722-
async with await self.change_stream() as change_stream:
723-
change = await self._populate_and_exhaust_change_stream(change_stream)
724-
self.assertEqual(change_stream.resume_token, change["_id"])
725-
resume_point = change["_id"]
726-
727-
# Resume token is _id of last change even if resumeAfter is specified.
728-
async with await self.change_stream(resume_after=resume_point) as change_stream:
729-
change = await self._populate_and_exhaust_change_stream(change_stream)
730-
self.assertEqual(change_stream.resume_token, change["_id"])
731-
732661
# Prose test no. 13
733662
@no_type_check
734663
async def test_resumetoken_partially_iterated_batch(self):
@@ -770,13 +699,13 @@ async def test_resumetoken_uniterated_nonempty_batch_resumeafter(self):
770699
# Prose test no. 14
771700
@no_type_check
772701
@async_client_context.require_no_mongos
773-
@async_client_context.require_version_min(4, 1, 1)
702+
@async_client_context.require_version_min(4, 2, 0)
774703
async def test_resumetoken_uniterated_nonempty_batch_startafter(self):
775704
await self._test_resumetoken_uniterated_nonempty_batch("start_after")
776705

777706
# Prose test no. 17
778707
@no_type_check
779-
@async_client_context.require_version_min(4, 1, 1)
708+
@async_client_context.require_version_min(4, 2, 0)
780709
async def test_startafter_resume_uses_startafter_after_empty_getMore(self):
781710
# Resume should use startAfter after no changes have been returned.
782711
resume_point = await self.get_resume_token()
@@ -796,7 +725,7 @@ async def test_startafter_resume_uses_startafter_after_empty_getMore(self):
796725

797726
# Prose test no. 18
798727
@no_type_check
799-
@async_client_context.require_version_min(4, 1, 1)
728+
@async_client_context.require_version_min(4, 2, 0)
800729
async def test_startafter_resume_uses_resumeafter_after_nonempty_getMore(self):
801730
# Resume should use resumeAfter after some changes have been returned.
802731
resume_point = await self.get_resume_token()
@@ -843,7 +772,7 @@ async def test_split_large_change(self):
843772
class TestClusterAsyncChangeStream(TestAsyncChangeStreamBase, APITestsMixin):
844773
dbs: list
845774

846-
@async_client_context.require_version_min(4, 0, 0, -1)
775+
@async_client_context.require_version_min(4, 2, 0)
847776
@async_client_context.require_change_streams
848777
async def asyncSetUp(self) -> None:
849778
await super().asyncSetUp()
@@ -903,7 +832,7 @@ async def test_full_pipeline(self):
903832

904833

905834
class TestAsyncDatabaseAsyncChangeStream(TestAsyncChangeStreamBase, APITestsMixin):
906-
@async_client_context.require_version_min(4, 0, 0, -1)
835+
@async_client_context.require_version_min(4, 2, 0)
907836
@async_client_context.require_change_streams
908837
async def asyncSetUp(self) -> None:
909838
await super().asyncSetUp()

0 commit comments

Comments
 (0)