diff --git a/bson/__init__.py b/bson/__init__.py index ba57cde2a..4ad98237b 100644 --- a/bson/__init__.py +++ b/bson/__init__.py @@ -65,7 +65,6 @@ type. """ import calendar -import collections import datetime import itertools import re @@ -91,7 +90,8 @@ from bson.int64 import Int64 from bson.max_key import MaxKey from bson.min_key import MinKey from bson.objectid import ObjectId -from bson.py3compat import (b, +from bson.py3compat import (abc, + b, PY3, iteritems, text_type, @@ -726,7 +726,7 @@ _ENCODERS = { UUIDLegacy: _encode_binary, Decimal128: _encode_decimal128, # Special case. This will never be looked up directly. - collections.Mapping: _encode_mapping, + abc.Mapping: _encode_mapping, } diff --git a/bson/_cbsonmodule.c b/bson/_cbsonmodule.c index dbd655d7b..634748e72 100644 --- a/bson/_cbsonmodule.c +++ b/bson/_cbsonmodule.c @@ -362,7 +362,11 @@ static int _load_python_objects(PyObject* module) { _load_object(&state->BSONInt64, "bson.int64", "Int64") || _load_object(&state->Decimal128, "bson.decimal128", "Decimal128") || _load_object(&state->UUID, "uuid", "UUID") || +#if PY_MAJOR_VERSION >= 3 + _load_object(&state->Mapping, "collections.abc", "Mapping") || +#else _load_object(&state->Mapping, "collections", "Mapping") || +#endif _load_object(&state->CodecOptions, "bson.codec_options", "CodecOptions")) { return 1; } @@ -1199,7 +1203,11 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer, * Try Mapping and UUID last since we have to import * them if we're in a sub-interpreter. */ +#if PY_MAJOR_VERSION >= 3 + mapping_type = _get_object(state->Mapping, "collections.abc", "Mapping"); +#else mapping_type = _get_object(state->Mapping, "collections", "Mapping"); +#endif if (mapping_type && PyObject_IsInstance(value, mapping_type)) { Py_DECREF(mapping_type); /* PyObject_IsInstance returns -1 on error */ @@ -1497,8 +1505,13 @@ int write_dict(PyObject* self, buffer_t buffer, int length; int length_location; struct module_state *state = GETSTATE(self); +#if PY_MAJOR_VERSION >= 3 + PyObject* mapping_type = _get_object(state->Mapping, + "collections.abc", "Mapping"); +#else PyObject* mapping_type = _get_object(state->Mapping, "collections", "Mapping"); +#endif if (mapping_type) { if (!PyObject_IsInstance(dict, mapping_type)) { diff --git a/bson/code.py b/bson/code.py index 43b3ae3fa..3f6e50435 100644 --- a/bson/code.py +++ b/bson/code.py @@ -14,9 +14,8 @@ """Tools for representing JavaScript code in BSON. """ -import collections -from bson.py3compat import string_type, PY3, text_type +from bson.py3compat import abc, string_type, PY3, text_type class Code(str): @@ -65,7 +64,7 @@ class Code(str): self.__scope = None if scope is not None: - if not isinstance(scope, collections.Mapping): + if not isinstance(scope, abc.Mapping): raise TypeError("scope must be an instance of dict") if self.__scope is not None: self.__scope.update(scope) diff --git a/bson/codec_options.py b/bson/codec_options.py index f318020cc..93281088e 100644 --- a/bson/codec_options.py +++ b/bson/codec_options.py @@ -16,9 +16,9 @@ import datetime -from collections import MutableMapping, namedtuple +from collections import namedtuple -from bson.py3compat import string_type +from bson.py3compat import abc, string_type from bson.binary import (ALL_UUID_REPRESENTATIONS, PYTHON_LEGACY, UUID_REPRESENTATION_NAMES) @@ -69,7 +69,7 @@ class CodecOptions(_options_base): tz_aware=False, uuid_representation=PYTHON_LEGACY, unicode_decode_error_handler="strict", tzinfo=None): - if not (issubclass(document_class, MutableMapping) or + if not (issubclass(document_class, abc.MutableMapping) or _raw_document_class(document_class)): raise TypeError("document_class must be dict, bson.son.SON, " "bson.raw_bson.RawBSONDocument, or a " diff --git a/bson/py3compat.py b/bson/py3compat.py index 5cade483d..40d36b47c 100644 --- a/bson/py3compat.py +++ b/bson/py3compat.py @@ -22,6 +22,13 @@ if PY3: import codecs import _thread as thread from io import BytesIO as StringIO + + try: + import collections.abc as abc + except ImportError: + # PyPy3 (based on CPython 3.2) + import collections as abc + MAXSIZE = sys.maxsize imap = map @@ -53,6 +60,7 @@ if PY3: string_type = str integer_types = int else: + import collections as abc import thread from itertools import imap diff --git a/bson/raw_bson.py b/bson/raw_bson.py index d2b7f0056..f99017c96 100644 --- a/bson/raw_bson.py +++ b/bson/raw_bson.py @@ -15,16 +15,14 @@ """Tools for representing raw BSON documents. """ -import collections - from bson import _UNPACK_INT, _iterate_elements -from bson.py3compat import iteritems +from bson.py3compat import abc, iteritems from bson.codec_options import ( DEFAULT_CODEC_OPTIONS as DEFAULT, _RAW_BSON_DOCUMENT_MARKER) from bson.errors import InvalidBSON -class RawBSONDocument(collections.Mapping): +class RawBSONDocument(abc.Mapping): """Representation for a MongoDB document that provides access to the raw BSON bytes that compose it. diff --git a/bson/son.py b/bson/son.py index 5d7aa398a..701cb2318 100644 --- a/bson/son.py +++ b/bson/son.py @@ -18,11 +18,10 @@ Regular dictionaries can be used instead of SON objects, but not when the order of keys is important. A SON object can be used just like a normal Python dictionary.""" -import collections import copy import re -from bson.py3compat import iteritems +from bson.py3compat import abc, iteritems # This sort of sucks, but seems to be as good as it gets... @@ -179,7 +178,7 @@ class SON(dict): def transform_value(value): if isinstance(value, list): return [transform_value(v) for v in value] - elif isinstance(value, collections.Mapping): + elif isinstance(value, abc.Mapping): return dict([ (k, transform_value(v)) for k, v in iteritems(value)]) diff --git a/gridfs/__init__.py b/gridfs/__init__.py index 2979bb969..7f7bd2811 100644 --- a/gridfs/__init__.py +++ b/gridfs/__init__.py @@ -20,8 +20,7 @@ The :mod:`gridfs` package is an implementation of GridFS on top of .. mongodoc:: gridfs """ -from collections import Mapping - +from bson.py3compat import abc from gridfs.errors import NoFile from gridfs.grid_file import (GridIn, GridOut, @@ -288,7 +287,7 @@ class GridFS(object): .. versionchanged:: 3.6 Added ``session`` parameter. """ - if filter is not None and not isinstance(filter, Mapping): + if filter is not None and not isinstance(filter, abc.Mapping): filter = {"_id": filter} for f in self.find(filter, *args, session=session, **kwargs): diff --git a/pymongo/client_session.py b/pymongo/client_session.py index 70e97cebf..695634ca6 100644 --- a/pymongo/client_session.py +++ b/pymongo/client_session.py @@ -48,6 +48,7 @@ import uuid from bson.binary import Binary from bson.int64 import Int64 +from bson.py3compat import abc from bson.timestamp import Timestamp from pymongo import monotonic @@ -152,7 +153,7 @@ class ClientSession(object): :data:`~pymongo.client_session.ClientSession.cluster_time` from another `ClientSession` instance. """ - if not isinstance(cluster_time, collections.Mapping): + if not isinstance(cluster_time, abc.Mapping): raise TypeError( "cluster_time must be a subclass of collections.Mapping") if not isinstance(cluster_time.get("clusterTime"), Timestamp): diff --git a/pymongo/collection.py b/pymongo/collection.py index ec825102f..ae341e8dd 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -21,6 +21,7 @@ import warnings from bson.code import Code from bson.objectid import ObjectId from bson.py3compat import (_unicode, + abc, integer_types, string_type) from bson.raw_bson import RawBSONDocument @@ -592,7 +593,7 @@ class Collection(common.BaseObject): manipulate=False, write_concern=None, op_id=None, bypass_doc_val=False, session=None): """Internal insert helper.""" - if isinstance(docs, collections.Mapping): + if isinstance(docs, abc.Mapping): return self._insert_one( docs, ordered, check_keys, manipulate, write_concern, op_id, bypass_doc_val, session) @@ -723,7 +724,7 @@ class Collection(common.BaseObject): .. versionadded:: 3.0 """ - if not isinstance(documents, collections.Iterable) or not documents: + if not isinstance(documents, abc.Iterable) or not documents: raise TypeError("documents must be a non-empty list") inserted_ids = [] def gen(): @@ -1217,7 +1218,7 @@ class Collection(common.BaseObject): >>> collection.find_one(max_time_ms=100) """ if (filter is not None and not - isinstance(filter, collections.Mapping)): + isinstance(filter, abc.Mapping)): filter = {"_id": filter} cursor = self.find(filter, *args, **kwargs) @@ -2499,7 +2500,7 @@ class Collection(common.BaseObject): .. mongodoc:: mapreduce """ - if not isinstance(out, (string_type, collections.Mapping)): + if not isinstance(out, (string_type, abc.Mapping)): raise TypeError("'out' must be an instance of " "%s or a mapping" % (string_type.__name__,)) @@ -2983,7 +2984,7 @@ class Collection(common.BaseObject): "instead.", DeprecationWarning, stacklevel=2) if spec_or_id is None: spec_or_id = {} - if not isinstance(spec_or_id, collections.Mapping): + if not isinstance(spec_or_id, abc.Mapping): spec_or_id = {"_id": spec_or_id} write_concern = None collation = validate_collation_or_none(kwargs.pop('collation', None)) diff --git a/pymongo/common.py b/pymongo/common.py index a86d5a6ec..17ff2e708 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -23,7 +23,7 @@ from bson import SON from bson.binary import (STANDARD, PYTHON_LEGACY, JAVA_LEGACY, CSHARP_LEGACY) from bson.codec_options import CodecOptions -from bson.py3compat import string_type, integer_types, iteritems +from bson.py3compat import abc, integer_types, iteritems, string_type from bson.raw_bson import RawBSONDocument from pymongo.auth import MECHANISMS from pymongo.errors import ConfigurationError @@ -396,7 +396,7 @@ def validate_auth_mechanism_properties(option, value): def validate_document_class(option, value): """Validate the document_class option.""" - if not issubclass(value, (collections.MutableMapping, RawBSONDocument)): + if not issubclass(value, (abc.MutableMapping, RawBSONDocument)): raise TypeError("%s must be dict, bson.son.SON, " "bson.raw_bson.RawBSONDocument, or a " "sublass of collections.MutableMapping" % (option,)) @@ -419,7 +419,7 @@ def validate_list_or_none(option, value): def validate_is_mapping(option, value): """Validate the type of method arguments that expect a document.""" - if not isinstance(value, collections.Mapping): + if not isinstance(value, abc.Mapping): raise TypeError("%s must be an instance of dict, bson.son.SON, or " "other type that inherits from " "collections.Mapping" % (option,)) @@ -427,7 +427,7 @@ def validate_is_mapping(option, value): def validate_is_document_type(option, value): """Validate the type of method arguments that expect a MongoDB document.""" - if not isinstance(value, (collections.MutableMapping, RawBSONDocument)): + if not isinstance(value, (abc.MutableMapping, RawBSONDocument)): raise TypeError("%s must be an instance of dict, bson.son.SON, " "bson.raw_bson.RawBSONDocument, or " "a type that inherits from " diff --git a/pymongo/helpers.py b/pymongo/helpers.py index 225956ef4..c53ebbcbe 100644 --- a/pymongo/helpers.py +++ b/pymongo/helpers.py @@ -14,11 +14,10 @@ """Bits and pieces used by the driver that don't really fit elsewhere.""" -import collections import sys import traceback -from bson.py3compat import string_type, iteritems, itervalues +from bson.py3compat import abc, iteritems, itervalues, string_type from bson.son import SON from pymongo import ASCENDING from pymongo.errors import (CursorNotFound, @@ -59,7 +58,7 @@ def _index_document(index_list): Takes a list of (key, direction) pairs. """ - if isinstance(index_list, collections.Mapping): + if isinstance(index_list, abc.Mapping): raise TypeError("passing a dict to sort/create_index/hint is not " "allowed - use a list of tuples instead. did you " "mean %r?" % list(iteritems(index_list))) @@ -73,7 +72,7 @@ def _index_document(index_list): for (key, value) in index_list: if not isinstance(key, string_type): raise TypeError("first item in each key pair must be a string") - if not isinstance(value, (string_type, int, collections.Mapping)): + if not isinstance(value, (string_type, int, abc.Mapping)): raise TypeError("second item in each key pair must be 1, -1, " "'2d', 'geoHaystack', or another valid MongoDB " "index specifier.") @@ -232,10 +231,10 @@ def _fields_list_to_dict(fields, option_name): ["a.b.c", "d", "a.c"] becomes {"a.b.c": 1, "d": 1, "a.c": 1} """ - if isinstance(fields, collections.Mapping): + if isinstance(fields, abc.Mapping): return fields - if isinstance(fields, (collections.Sequence, collections.Set)): + if isinstance(fields, (abc.Sequence, abc.Set)): if not all(isinstance(field, string_type) for field in fields): raise TypeError("%s must be a list of key names, each an " "instance of %s" % (option_name, diff --git a/pymongo/monitoring.py b/pymongo/monitoring.py index 62aa28461..c3eab383a 100644 --- a/pymongo/monitoring.py +++ b/pymongo/monitoring.py @@ -137,7 +137,9 @@ will not add that listener to existing client instances. import sys import traceback -from collections import namedtuple, Sequence +from collections import namedtuple + +from bson.py3compat import abc from pymongo.helpers import _handle_exception _Listeners = namedtuple('Listeners', @@ -290,7 +292,7 @@ def _to_micros(dur): def _validate_event_listeners(option, listeners): """Validate event listeners""" - if not isinstance(listeners, Sequence): + if not isinstance(listeners, abc.Sequence): raise TypeError("%s must be a list or tuple" % (option,)) for listener in listeners: if not isinstance(listener, _EventListener): diff --git a/pymongo/read_preferences.py b/pymongo/read_preferences.py index 39ec6a7a6..f4425acaa 100644 --- a/pymongo/read_preferences.py +++ b/pymongo/read_preferences.py @@ -14,9 +14,7 @@ """Utilities for choosing which member of a replica set to read from.""" -from collections import Mapping - -from bson.py3compat import integer_types +from bson.py3compat import abc, integer_types from pymongo import max_staleness_selectors from pymongo.errors import ConfigurationError from pymongo.server_selectors import (member_with_tags_server_selector, @@ -54,7 +52,7 @@ def _validate_tag_sets(tag_sets): " tags") % (tag_sets,)) for tags in tag_sets: - if not isinstance(tags, Mapping): + if not isinstance(tags, abc.Mapping): raise TypeError( "Tag set %r invalid, must be an instance of dict, " "bson.son.SON or other type that inherits from " diff --git a/pymongo/son_manipulator.py b/pymongo/son_manipulator.py index c99f8a4ce..f470d6f33 100644 --- a/pymongo/son_manipulator.py +++ b/pymongo/son_manipulator.py @@ -33,10 +33,9 @@ the modern methods :meth:`~pymongo.collection.Collection.bulk_write`, :meth:`~pymongo.collection.Collection.find_one_and_update`. """ -import collections - from bson.dbref import DBRef from bson.objectid import ObjectId +from bson.py3compat import abc from bson.son import SON @@ -155,7 +154,7 @@ class AutoReference(SONManipulator): """ def transform_value(value): - if isinstance(value, collections.MutableMapping): + if isinstance(value, abc.MutableMapping): if "_id" in value and "_ns" in value: return DBRef(value["_ns"], transform_value(value["_id"])) else: @@ -180,7 +179,7 @@ class AutoReference(SONManipulator): return self.database.dereference(value) elif isinstance(value, list): return [transform_value(v) for v in value] - elif isinstance(value, collections.MutableMapping): + elif isinstance(value, abc.MutableMapping): return transform_dict(SON(value)) return value diff --git a/test/test_bson.py b/test/test_bson.py index 4f6ba5e72..b5291c5ad 100644 --- a/test/test_bson.py +++ b/test/test_bson.py @@ -38,7 +38,7 @@ from bson.codec_options import CodecOptions from bson.int64 import Int64 from bson.objectid import ObjectId from bson.dbref import DBRef -from bson.py3compat import PY3, text_type, iteritems, StringIO +from bson.py3compat import abc, iteritems, PY3, StringIO, text_type from bson.son import SON from bson.timestamp import Timestamp from bson.tz_util import FixedOffset @@ -56,7 +56,7 @@ if PY3: long = int -class NotADict(collections.MutableMapping): +class NotADict(abc.MutableMapping): """Non-dict type that implements the mapping protocol.""" def __init__(self, initial=None): @@ -81,7 +81,7 @@ class NotADict(collections.MutableMapping): return len(self._dict) def __eq__(self, other): - if isinstance(other, collections.Mapping): + if isinstance(other, abc.Mapping): return all(self.get(k) == other.get(k) for k in self) return NotImplemented