From 8c1125bf87d3060dbdd91a4b648d0b4c79b8f390 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 6 Jul 2026 11:14:08 -0500 Subject: [PATCH] PYTHON-5917 Fix document_class generic alias check on PyPy issubclass() does not raise TypeError for generic aliases like dict[str, Any] or SON[str, Any] on all Python implementations (e.g. PyPy), so the exception-based fallback to check __origin__ never ran, causing a spurious TypeError. Resolve __origin__ before the subclass check instead of relying on catching the error. --- bson/codec_options.py | 14 ++++++++------ pymongo/common.py | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/bson/codec_options.py b/bson/codec_options.py index f641efefdf..848f672f54 100644 --- a/bson/codec_options.py +++ b/bson/codec_options.py @@ -377,14 +377,16 @@ def __new__( datetime_conversion: Optional[DatetimeConversion] = DatetimeConversion.DATETIME, ) -> CodecOptions: doc_class = document_class or dict - # issubclass can raise TypeError for generic aliases like SON[str, Any]. - # In that case we can use the base class for the comparison. - is_mapping = False + # Generic aliases like SON[str, Any] or dict[str, Any] aren't classes, so + # resolve to their origin before the subclass check. Whether issubclass() + # raises TypeError or just returns False for such aliases is inconsistent + # across Python implementations (e.g. PyPy vs CPython), so check the + # origin proactively instead of relying on catching the error. + check_class = getattr(doc_class, "__origin__", doc_class) try: - is_mapping = issubclass(doc_class, _MutableMapping) + is_mapping = issubclass(check_class, _MutableMapping) except TypeError: - if hasattr(doc_class, "__origin__"): - is_mapping = issubclass(doc_class.__origin__, _MutableMapping) + is_mapping = False if not (is_mapping or _raw_document_class(doc_class)): raise TypeError( "document_class must be dict, bson.son.SON, " diff --git a/pymongo/common.py b/pymongo/common.py index 08da34c7bf..7baed05d3e 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -482,14 +482,16 @@ def validate_document_class( option: str, value: Any ) -> Union[type[MutableMapping[str, Any]], type[RawBSONDocument]]: """Validate the document_class option.""" - # issubclass can raise TypeError for generic aliases like SON[str, Any]. - # In that case we can use the base class for the comparison. - is_mapping = False + # Generic aliases like SON[str, Any] or dict[str, Any] aren't classes, so + # resolve to their origin before the subclass check. Whether issubclass() + # raises TypeError or just returns False for such aliases is inconsistent + # across Python implementations (e.g. PyPy vs CPython), so check the + # origin proactively instead of relying on catching the error. + check_class = getattr(value, "__origin__", value) try: - is_mapping = issubclass(value, abc.MutableMapping) + is_mapping = issubclass(check_class, abc.MutableMapping) except TypeError: - if hasattr(value, "__origin__"): - is_mapping = issubclass(value.__origin__, abc.MutableMapping) + is_mapping = False if not is_mapping and not issubclass(value, RawBSONDocument): raise TypeError( f"{option} must be dict, bson.son.SON, "