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, "