From d70578f6500a8876e42a7d67eb8c10073d03557b Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Tue, 26 May 2015 19:23:40 -0700 Subject: [PATCH] PYTHON-881 - Backport CodecOptions class from 3.x. --- bson/binary.py | 46 +++++++++--- bson/codec_options.py | 77 ++++++++++++++++++++ doc/api/bson/binary.rst | 2 + doc/api/bson/codec_options.rst | 6 ++ doc/api/bson/index.rst | 3 +- doc/api/pymongo/collection.rst | 1 + doc/api/pymongo/connection.rst | 1 + doc/api/pymongo/database.rst | 1 + doc/api/pymongo/mongo_client.rst | 1 + doc/api/pymongo/mongo_replica_set_client.rst | 1 + doc/api/pymongo/replica_set_connection.rst | 1 + pymongo/collection.py | 2 +- pymongo/common.py | 29 ++++++-- pymongo/database.py | 2 +- pymongo/master_slave_connection.py | 16 ++-- pymongo/mongo_client.py | 17 +++-- pymongo/mongo_replica_set_client.py | 18 +++-- 17 files changed, 185 insertions(+), 39 deletions(-) create mode 100644 bson/codec_options.py create mode 100644 doc/api/bson/codec_options.rst diff --git a/bson/binary.py b/bson/binary.py index bc7e144df..3f08a8979 100644 --- a/bson/binary.py +++ b/bson/binary.py @@ -67,29 +67,53 @@ change to this in a future release. .. versionadded:: 1.5 """ -JAVA_LEGACY = 5 -"""Used with :attr:`pymongo.collection.Collection.uuid_subtype` -to specify that UUIDs should be stored in the legacy byte order -used by the Java driver. +STANDARD = UUID_SUBTYPE +"""The standard UUID representation. -:class:`uuid.UUID` instances will automatically be encoded -by :mod:`bson` using :data:`OLD_UUID_SUBTYPE`. +:class:`uuid.UUID` instances will automatically be encoded to +and decoded from BSON binary, using RFC-4122 byte order with +binary subtype :data:`UUID_SUBTYPE`. + +.. versionadded:: 2.9 +""" + +PYTHON_LEGACY = OLD_UUID_SUBTYPE +"""The Python legacy UUID representation. + +:class:`uuid.UUID` instances will automatically be encoded to +and decoded from BSON binary, using RFC-4122 byte order with +binary subtype :data:`OLD_UUID_SUBTYPE`. + +.. versionadded:: 2.9 +""" + +JAVA_LEGACY = 5 +"""The Java legacy UUID representation. + +:class:`uuid.UUID` instances will automatically be encoded to +and decoded from BSON binary, using the Java driver's legacy +byte order with binary subtype :data:`OLD_UUID_SUBTYPE`. .. versionadded:: 2.3 """ CSHARP_LEGACY = 6 -"""Used with :attr:`pymongo.collection.Collection.uuid_subtype` -to specify that UUIDs should be stored in the legacy byte order -used by the C# driver. +"""The C#/.net legacy UUID representation. -:class:`uuid.UUID` instances will automatically be encoded -by :mod:`bson` using :data:`OLD_UUID_SUBTYPE`. +:class:`uuid.UUID` instances will automatically be encoded to +and decoded from BSON binary, using the C# driver's legacy +byte order and binary subtype :data:`OLD_UUID_SUBTYPE`. .. versionadded:: 2.3 """ ALL_UUID_SUBTYPES = (OLD_UUID_SUBTYPE, UUID_SUBTYPE, JAVA_LEGACY, CSHARP_LEGACY) +ALL_UUID_REPRESENTATIONS = (STANDARD, PYTHON_LEGACY, JAVA_LEGACY, CSHARP_LEGACY) +UUID_REPRESENTATION_NAMES = { + PYTHON_LEGACY: 'PYTHON_LEGACY', + STANDARD: 'STANDARD', + JAVA_LEGACY: 'JAVA_LEGACY', + CSHARP_LEGACY: 'CSHARP_LEGACY'} MD5_SUBTYPE = 5 """BSON binary subtype for an MD5 hash. diff --git a/bson/codec_options.py b/bson/codec_options.py new file mode 100644 index 000000000..08b22105b --- /dev/null +++ b/bson/codec_options.py @@ -0,0 +1,77 @@ +# Copyright 2014-2015 MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tools for specifying BSON codec options.""" + +from bson.binary import (ALL_UUID_REPRESENTATIONS, + PYTHON_LEGACY, + UUID_REPRESENTATION_NAMES) + + +class CodecOptions(tuple): + """Encapsulates BSON options used in CRUD operations. + + :Parameters: + - `document_class`: BSON documents returned in queries will be decoded + to an instance of this class. + - `tz_aware`: If ``True``, BSON datetimes will be decoded to timezone + aware instances of :class:`~datetime.datetime`. Otherwise they will be + naive. Defaults to ``False``. + - `uuid_representation`: The BSON representation to use when encoding + and decoding instances of :class:`~uuid.UUID`. Defaults to + :data:`~bson.binary.PYTHON_LEGACY`. + """ + + __slots__ = () + + def __new__(cls, document_class=dict, + tz_aware=False, uuid_representation=PYTHON_LEGACY): + if not isinstance(tz_aware, bool): + raise TypeError("tz_aware must be True or False") + if uuid_representation not in ALL_UUID_REPRESENTATIONS: + raise ValueError("uuid_representation must be a value " + "from bson.binary.ALL_UUID_REPRESENTATIONS") + + return tuple.__new__( + cls, (document_class, tz_aware, uuid_representation)) + + def __repr__(self): + if self.document_class is dict: + document_class_repr = 'dict' + else: + document_class_repr = repr(self.document_class) + + uuid_rep_repr = UUID_REPRESENTATION_NAMES.get(self.uuid_representation, + self.uuid_representation) + + return ( + 'CodecOptions(document_class=%s, tz_aware=%r, uuid_representation=' + '%s)' % (document_class_repr, self.tz_aware, uuid_rep_repr)) + + def __getnewargs__(self): + return tuple(self) + + @property + def document_class(self): + return self[0] + + @property + def tz_aware(self): + return self[1] + + @property + def uuid_representation(self): + return self[2] + +DEFAULT_CODEC_OPTIONS = CodecOptions() diff --git a/doc/api/bson/binary.rst b/doc/api/bson/binary.rst index 57b87255f..65d24fec3 100644 --- a/doc/api/bson/binary.rst +++ b/doc/api/bson/binary.rst @@ -9,6 +9,8 @@ .. autodata:: OLD_BINARY_SUBTYPE .. autodata:: OLD_UUID_SUBTYPE .. autodata:: UUID_SUBTYPE + .. autodata:: STANDARD + .. autodata:: PYTHON_LEGACY .. autodata:: JAVA_LEGACY .. autodata:: CSHARP_LEGACY .. autodata:: MD5_SUBTYPE diff --git a/doc/api/bson/codec_options.rst b/doc/api/bson/codec_options.rst new file mode 100644 index 000000000..bea1358f5 --- /dev/null +++ b/doc/api/bson/codec_options.rst @@ -0,0 +1,6 @@ +:mod:`codec_options` -- Tools for specifying BSON codec options +=============================================================== + +.. automodule:: bson.codec_options + :synopsis: Tools for specifying BSON codec options. + :members: diff --git a/doc/api/bson/index.rst b/doc/api/bson/index.rst index 8b8a90504..5f74a32a9 100644 --- a/doc/api/bson/index.rst +++ b/doc/api/bson/index.rst @@ -11,14 +11,15 @@ Sub-modules: :maxdepth: 2 binary - regex code + codec_options dbref errors json_util max_key min_key objectid + regex son timestamp tz_util diff --git a/doc/api/pymongo/collection.rst b/doc/api/pymongo/collection.rst index f9a230d48..ba14b7614 100644 --- a/doc/api/pymongo/collection.rst +++ b/doc/api/pymongo/collection.rst @@ -24,6 +24,7 @@ .. autoattribute:: full_name .. autoattribute:: name .. autoattribute:: database + .. autoattribute:: codec_options .. autoattribute:: read_preference .. autoattribute:: tag_sets .. autoattribute:: secondary_acceptable_latency_ms diff --git a/doc/api/pymongo/connection.rst b/doc/api/pymongo/connection.rst index baf09574a..8601dd1b3 100644 --- a/doc/api/pymongo/connection.rst +++ b/doc/api/pymongo/connection.rst @@ -29,6 +29,7 @@ .. autoattribute:: max_message_size .. autoattribute:: min_wire_version .. autoattribute:: max_wire_version + .. autoattribute:: codec_options .. autoattribute:: read_preference .. autoattribute:: tag_sets .. autoattribute:: secondary_acceptable_latency_ms diff --git a/doc/api/pymongo/database.rst b/doc/api/pymongo/database.rst index 4ffb20174..e8435f565 100644 --- a/doc/api/pymongo/database.rst +++ b/doc/api/pymongo/database.rst @@ -23,6 +23,7 @@ .. note:: Use dictionary style access if `collection_name` is an attribute of the :class:`Database` class eg: db[`collection_name`]. + .. autoattribute:: codec_options .. autoattribute:: read_preference .. autoattribute:: tag_sets .. autoattribute:: secondary_acceptable_latency_ms diff --git a/doc/api/pymongo/mongo_client.rst b/doc/api/pymongo/mongo_client.rst index 6657859a7..11e287ace 100644 --- a/doc/api/pymongo/mongo_client.rst +++ b/doc/api/pymongo/mongo_client.rst @@ -29,6 +29,7 @@ .. autoattribute:: max_message_size .. autoattribute:: min_wire_version .. autoattribute:: max_wire_version + .. autoattribute:: codec_options .. autoattribute:: read_preference .. autoattribute:: tag_sets .. autoattribute:: secondary_acceptable_latency_ms diff --git a/doc/api/pymongo/mongo_replica_set_client.rst b/doc/api/pymongo/mongo_replica_set_client.rst index da28a30ee..3a53a9dd7 100644 --- a/doc/api/pymongo/mongo_replica_set_client.rst +++ b/doc/api/pymongo/mongo_replica_set_client.rst @@ -30,6 +30,7 @@ .. autoattribute:: min_wire_version .. autoattribute:: max_wire_version .. autoattribute:: auto_start_request + .. autoattribute:: codec_options .. autoattribute:: read_preference .. autoattribute:: tag_sets .. autoattribute:: secondary_acceptable_latency_ms diff --git a/doc/api/pymongo/replica_set_connection.rst b/doc/api/pymongo/replica_set_connection.rst index 6a1305eb5..3b9b1042c 100644 --- a/doc/api/pymongo/replica_set_connection.rst +++ b/doc/api/pymongo/replica_set_connection.rst @@ -30,6 +30,7 @@ .. autoattribute:: min_wire_version .. autoattribute:: max_wire_version .. autoattribute:: auto_start_request + .. autoattribute:: codec_options .. autoattribute:: read_preference .. autoattribute:: tag_sets .. autoattribute:: secondary_acceptable_latency_ms diff --git a/pymongo/collection.py b/pymongo/collection.py index 1c5ae0ce7..308f0ca0a 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -93,7 +93,7 @@ class Collection(common.BaseObject): secondary_acceptable_latency_ms=( database.secondary_acceptable_latency_ms), safe=database.safe, - uuidrepresentation=database.uuid_subtype, + codec_options=database.codec_options, **database.write_concern) if not isinstance(name, basestring): diff --git a/pymongo/common.py b/pymongo/common.py index 3b007c6c1..2342b71bc 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -16,13 +16,14 @@ """Functions and classes common to multiple pymongo modules.""" import sys import warnings -from pymongo import read_preferences +from bson.binary import (OLD_UUID_SUBTYPE, UUID_SUBTYPE, + JAVA_LEGACY, CSHARP_LEGACY) +from bson.codec_options import CodecOptions +from pymongo import read_preferences from pymongo.auth import MECHANISMS from pymongo.read_preferences import ReadPreference from pymongo.errors import ConfigurationError -from bson.binary import (OLD_UUID_SUBTYPE, UUID_SUBTYPE, - JAVA_LEGACY, CSHARP_LEGACY) HAS_SSL = True try: @@ -395,12 +396,12 @@ class BaseObject(object): def __init__(self, **options): + self._codec_options = None self.__slave_okay = False self.__read_pref = ReadPreference.PRIMARY self.__tag_sets = [{}] self.__secondary_acceptable_latency_ms = 15 self.__safe = None - self.__uuid_subtype = OLD_UUID_SUBTYPE self.__write_concern = WriteConcern() self.__set_options(options) if (self.__read_pref == ReadPreference.PRIMARY @@ -444,8 +445,8 @@ class BaseObject(object): self.__read_pref = validate_read_preference(option, value) elif option in ('tag_sets', 'readpreferencetags'): self.__tag_sets = validate_tag_sets(option, value) - elif option == 'uuidrepresentation': - self.__uuid_subtype = validate_uuid_subtype(option, value) + elif option == 'codec_options': + self._codec_options = value elif option in ('secondaryacceptablelatencyms', 'secondary_acceptable_latency_ms'): self.__secondary_acceptable_latency_ms = ( @@ -458,6 +459,15 @@ class BaseObject(object): else: self.__set_safe_option(option, value) + @property + def codec_options(self): + """Read only access to the :class:`~bson.codec_options.CodecOptions` + of this instance. + + .. versionadded:: 2.9 + """ + return self._codec_options + def __set_write_concern(self, value): """Property setter for write_concern.""" if not isinstance(value, dict): @@ -622,11 +632,14 @@ class BaseObject(object): subtype 4. It can also be used to force legacy byte order and subtype compatibility with the Java and C# drivers. See the :mod:`bson.binary` module for all options.""" - return self.__uuid_subtype + return self._codec_options.uuid_representation def __set_uuid_subtype(self, value): """Sets the BSON Binary subtype to be used when storing UUIDs.""" - self.__uuid_subtype = validate_uuid_subtype("uuid_subtype", value) + as_class = self._codec_options.document_class + tz_aware = self._codec_options.tz_aware + uuid_rep = validate_uuid_subtype("uuid_subtype", value) + self._codec_options = CodecOptions(as_class, tz_aware, uuid_rep) uuid_subtype = property(__get_uuid_subtype, __set_uuid_subtype) diff --git a/pymongo/database.py b/pymongo/database.py index 4c6ac027a..6750814b6 100644 --- a/pymongo/database.py +++ b/pymongo/database.py @@ -57,7 +57,7 @@ class Database(common.BaseObject): secondary_acceptable_latency_ms=( connection.secondary_acceptable_latency_ms), safe=connection.safe, - uuidrepresentation=connection.uuid_subtype, + codec_options=connection.codec_options, **connection.write_concern) if not isinstance(name, basestring): diff --git a/pymongo/master_slave_connection.py b/pymongo/master_slave_connection.py index 32683d9c8..51ac12e58 100644 --- a/pymongo/master_slave_connection.py +++ b/pymongo/master_slave_connection.py @@ -33,9 +33,10 @@ directly to the master and each slave with instances of import warnings +from bson.codec_options import CodecOptions from pymongo import helpers, thread_util from pymongo import ReadPreference -from pymongo.common import BaseObject +from pymongo.common import BaseObject, validate_boolean from pymongo.mongo_client import MongoClient from pymongo.database import Database from pymongo.errors import AutoReconnect @@ -85,15 +86,16 @@ class MasterSlaveConnection(BaseObject): " removed in PyMongo 3.0.", DeprecationWarning, stacklevel=2) + validate_boolean('tz_aware', tz_aware) + codec_options = CodecOptions(document_class, tz_aware) super(MasterSlaveConnection, self).__init__(read_preference=ReadPreference.SECONDARY, safe=master.safe, + codec_options=codec_options, **master.write_concern) self.__master = master self.__slaves = slaves - self.__document_class = document_class - self.__tz_aware = tz_aware self.__request_counter = thread_util.Counter(master.use_greenlets) @property @@ -122,10 +124,12 @@ class MasterSlaveConnection(BaseObject): return self.master.use_greenlets def get_document_class(self): - return self.__document_class + return self._codec_options.document_class def set_document_class(self, klass): - self.__document_class = klass + tz_aware = self._codec_options.tz_aware + uuid_rep = self._codec_options.uuid_representation + self._codec_options = CodecOptions(klass, tz_aware, uuid_rep) document_class = property(get_document_class, set_document_class, doc="""Default class to use for documents @@ -133,7 +137,7 @@ class MasterSlaveConnection(BaseObject): @property def tz_aware(self): - return self.__tz_aware + return self._codec_options.tz_aware @property def max_bson_size(self): diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index acc645834..388989eb1 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -42,6 +42,8 @@ import threading import time import warnings +from bson.binary import PYTHON_LEGACY +from bson.codec_options import CodecOptions from bson.py3compat import b from pymongo import (auth, common, @@ -292,6 +294,11 @@ class MongoClient(common.BaseObject): options[option] = value options.update(opts) + common.validate_boolean('tz_aware', tz_aware) + uuid_representation = options.pop('uuidrepresentation', PYTHON_LEGACY) + options['codec_options'] = CodecOptions( + document_class, tz_aware, uuid_representation) + self.__max_pool_size = common.validate_positive_integer_or_none( 'max_pool_size', max_pool_size) @@ -356,8 +363,6 @@ class MongoClient(common.BaseObject): self.__event_class = event_class self.__future_member = None - self.__document_class = document_class - self.__tz_aware = common.validate_boolean('tz_aware', tz_aware) self.__auto_start_request = options.get('auto_start_request', False) # cache of existing indexes used by ensure_index ops @@ -606,10 +611,12 @@ class MongoClient(common.BaseObject): return self.__auto_start_request def get_document_class(self): - return self.__document_class + return self._codec_options.document_class def set_document_class(self, klass): - self.__document_class = klass + tz_aware = self._codec_options.tz_aware + uuid_rep = self._codec_options.uuid_representation + self._codec_options = CodecOptions(klass, tz_aware, uuid_rep) document_class = property(get_document_class, set_document_class, doc="""Default class to use for documents @@ -624,7 +631,7 @@ class MongoClient(common.BaseObject): .. versionadded:: 1.8 """ - return self.__tz_aware + return self._codec_options.tz_aware @property def max_bson_size(self): diff --git a/pymongo/mongo_replica_set_client.py b/pymongo/mongo_replica_set_client.py index 12afff060..a9eee746c 100644 --- a/pymongo/mongo_replica_set_client.py +++ b/pymongo/mongo_replica_set_client.py @@ -40,6 +40,8 @@ import time import warnings import weakref +from bson.binary import PYTHON_LEGACY +from bson.codec_options import CodecOptions from bson.py3compat import b from pymongo import (auth, common, @@ -59,7 +61,6 @@ from pymongo.errors import (AutoReconnect, DuplicateKeyError, OperationFailure, InvalidOperation) -from pymongo.read_preferences import ReadPreference from pymongo.thread_util import DummyLock EMPTY = b("") @@ -593,8 +594,6 @@ class MongoReplicaSetClient(common.BaseObject): self.__max_pool_size = common.validate_positive_integer_or_none( 'max_pool_size', max_pool_size) - self.__tz_aware = common.validate_boolean('tz_aware', tz_aware) - self.__document_class = document_class self.__monitor = None self.__closed = False @@ -631,6 +630,11 @@ class MongoReplicaSetClient(common.BaseObject): self.__opts[option] = value self.__opts.update(options) + common.validate_boolean('tz_aware', tz_aware) + uuid_representation = options.pop('uuidrepresentation', PYTHON_LEGACY) + self.__opts['codec_options'] = CodecOptions( + document_class, tz_aware, uuid_representation) + self.__use_greenlets = self.__opts.get('use_greenlets', False) if self.__use_greenlets and not have_gevent: raise ConfigurationError( @@ -927,11 +931,13 @@ class MongoReplicaSetClient(common.BaseObject): def get_document_class(self): """document_class getter""" - return self.__document_class + return self._codec_options.document_class def set_document_class(self, klass): """document_class setter""" - self.__document_class = klass + tz_aware = self._codec_options.tz_aware + uuid_rep = self._codec_options.uuid_representation + self._codec_options = CodecOptions(klass, tz_aware, uuid_rep) document_class = property(get_document_class, set_document_class, doc="""Default class to use for documents @@ -942,7 +948,7 @@ class MongoReplicaSetClient(common.BaseObject): def tz_aware(self): """Does this client return timezone-aware datetimes? """ - return self.__tz_aware + return self._codec_options.tz_aware @property def max_bson_size(self):