PYTHON-825 BSON API changes and internal options handling.

This change resolves four issues:

PYTHON-826 The new codec_options submodule is moved from pymongo to bson.

PYTHON-827 Use codec_options in BSON APIs.

Functions and methods of the bson module that accepted the options as_class,
tz_aware, and uuid_subtype now accept a codec_options parameter instead.

For example, the function definition for bson.decode_all changes from this:

def decode_all(data, as_class=dict, tz_aware=True,
               uuid_subtype=OLD_UUID_SUBTYPE)

to:

def decode_all(data, codec_options=CodecOptions())

The following functions are changed:

- decode_all
- decode_iter
- decode_file_iter

The following methods are changed:

- BSON.encode
- BSON.decode

This is a breaking change for any application that uses the BSON API directly
and changes any of the named parameter defaults. No changes are required for
applications that use the default values for these options. The behavior
remains the same.

PYTHON-828 Internal BSON module changes to support CodecOptions

The pure Python BSON module passes around a CodecOptions instance instead of
as_class, tz_aware, and uuid_subtype. C extensions pass these values around in
a struct.

PYTHON-801 Rename uuid_subtype to uuid_representation.
This commit is contained in:
A. Jesse Jiryu Davis 2015-02-02 22:00:15 -05:00
parent be0ad8dec3
commit 9da835ab3d
17 changed files with 546 additions and 321 deletions

View File

@ -31,6 +31,7 @@ from bson.binary import (Binary, OLD_UUID_SUBTYPE,
JAVA_LEGACY, CSHARP_LEGACY,
UUIDLegacy)
from bson.code import Code
from bson.codec_options import CodecOptions, DEFAULT_CODEC_OPTIONS
from bson.dbref import DBRef
from bson.errors import (InvalidBSON,
InvalidDocument,
@ -122,7 +123,7 @@ def _get_string(data, position, obj_end, dummy):
def _get_object(data, position, obj_end, opts):
"""Decode a BSON subdocument to as_class or bson.dbref.DBRef."""
"""Decode a BSON subdocument to opts.as_class or bson.dbref.DBRef."""
obj_size = _UNPACK_INT(data[position:position + 4])[0]
end = position + obj_size - 1
if data[end:position + obj_size] != b"\x00":
@ -175,12 +176,12 @@ def _get_binary(data, position, dummy, opts):
end = position + length
if subtype in (3, 4):
# Java Legacy
uuid_subtype = opts[2]
if uuid_subtype == JAVA_LEGACY:
uuid_representation = opts.uuid_representation
if uuid_representation == JAVA_LEGACY:
java = data[position:end]
value = uuid.UUID(bytes=java[0:8][::-1] + java[8:16][::-1])
# C# legacy
elif uuid_subtype == CSHARP_LEGACY:
elif uuid_representation == CSHARP_LEGACY:
value = uuid.UUID(bytes_le=data[position:end])
# Python
else:
@ -213,7 +214,7 @@ def _get_date(data, position, dummy, opts):
diff = ((millis % 1000) + 1000) % 1000
seconds = (millis - diff) / 1000
micros = diff * 1000
if opts[1]:
if opts.tz_aware:
return EPOCH_AWARE + datetime.timedelta(
seconds=seconds, microseconds=micros), end
else:
@ -262,6 +263,11 @@ def _get_int64(data, position, dummy0, dummy1):
return Int64(_UNPACK_LONG(data[position:end])[0]), end
# Each decoder function's signature is:
# - data: bytes
# - position: int, beginning of object in 'data' to decode
# - obj_end: int, end of object to decode in 'data' if variable-length type
# - opts: a CodecOptions
_ELEMENT_GETTER = {
BSONNUM: _get_float,
BSONSTR: _get_string,
@ -297,7 +303,7 @@ def _element_to_dict(data, position, obj_end, opts):
def _elements_to_dict(data, position, obj_end, opts):
"""Decode a BSON document."""
result = opts[0]()
result = opts.as_class()
end = obj_end - 1
while position < end:
(key, value, position) = _element_to_dict(data, position, obj_end, opts)
@ -305,9 +311,8 @@ def _elements_to_dict(data, position, obj_end, opts):
return result
def _bson_to_dict(data, as_class, tz_aware, uuid_subtype):
def _bson_to_dict(data, opts):
"""Decode a BSON string to as_class."""
opts = (as_class, tz_aware, uuid_subtype)
try:
obj_size = _UNPACK_INT(data[:4])[0]
except struct.error as e:
@ -417,38 +422,38 @@ else:
return b"\x02" + name + _PACK_INT(len(value) + 1) + value + b"\x00"
def _encode_mapping(name, value, check_keys, uuid_subtype):
def _encode_mapping(name, value, check_keys, opts):
"""Encode a mapping type."""
data = b"".join([_element_to_bson(key, val, check_keys, uuid_subtype)
data = b"".join([_element_to_bson(key, val, check_keys, opts)
for key, val in iteritems(value)])
return b"\x03" + name + _PACK_INT(len(data) + 5) + data + b"\x00"
def _encode_dbref(name, value, check_keys, uuid_subtype):
def _encode_dbref(name, value, check_keys, opts):
"""Encode bson.dbref.DBRef."""
buf = bytearray(b"\x03" + name + b"\x00\x00\x00\x00")
begin = len(buf) - 4
buf += _name_value_to_bson(b"$ref\x00",
value.collection, check_keys, uuid_subtype)
value.collection, check_keys, opts)
buf += _name_value_to_bson(b"$id\x00",
value.id, check_keys, uuid_subtype)
value.id, check_keys, opts)
if value.database is not None:
buf += _name_value_to_bson(
b"$db\x00", value.database, check_keys, uuid_subtype)
b"$db\x00", value.database, check_keys, opts)
for key, val in iteritems(value._DBRef__kwargs):
buf += _element_to_bson(key, val, check_keys, uuid_subtype)
buf += _element_to_bson(key, val, check_keys, opts)
buf += b"\x00"
buf[begin:begin + 4] = _PACK_INT(len(buf) - begin)
return bytes(buf)
def _encode_list(name, value, check_keys, uuid_subtype):
def _encode_list(name, value, check_keys, opts):
"""Encode a list/tuple."""
lname = gen_list_name()
data = b"".join([_name_value_to_bson(next(lname), item,
check_keys, uuid_subtype)
check_keys, opts)
for item in value])
return b"\x04" + name + _PACK_INT(len(data) + 5) + data + b"\x00"
@ -467,18 +472,19 @@ def _encode_binary(name, value, dummy0, dummy1):
return b"\x05" + name + _PACK_LENGTH_SUBTYPE(len(value), subtype) + value
def _encode_uuid(name, value, dummy, uuid_subtype):
def _encode_uuid(name, value, dummy, opts):
"""Encode uuid.UUID."""
uuid_representation = opts.uuid_representation
# Python Legacy Common Case
if uuid_subtype == OLD_UUID_SUBTYPE:
if uuid_representation == OLD_UUID_SUBTYPE:
return b"\x05" + name + b'\x10\x00\x00\x00\x03' + value.bytes
# Java Legacy
elif uuid_subtype == JAVA_LEGACY:
elif uuid_representation == JAVA_LEGACY:
from_uuid = value.bytes
data = from_uuid[0:8][::-1] + from_uuid[8:16][::-1]
return b"\x05" + name + b'\x10\x00\x00\x00\x03' + data
# C# legacy
elif uuid_subtype == CSHARP_LEGACY:
elif uuid_representation == CSHARP_LEGACY:
# Microsoft GUID representation.
return b"\x05" + name + b'\x10\x00\x00\x00\x03' + value.bytes_le
# New
@ -537,13 +543,13 @@ def _encode_regex(name, value, dummy0, dummy1):
return b"\x0B" + name + _make_c_string_check(value.pattern) + sflags
def _encode_code(name, value, dummy, uuid_subtype):
def _encode_code(name, value, dummy, opts):
"""Encode bson.code.Code."""
cstring = _make_c_string(value)
cstrlen = len(cstring)
if not value.scope:
return b"\x0D" + name + _PACK_INT(cstrlen) + cstring
scope = _dict_to_bson(value.scope, False, uuid_subtype, False)
scope = _dict_to_bson(value.scope, False, opts, False)
full_length = _PACK_INT(8 + cstrlen + len(scope))
return b"\x0F" + name + full_length + _PACK_INT(cstrlen) + cstring + scope
@ -582,6 +588,11 @@ def _encode_maxkey(name, dummy0, dummy1, dummy2):
return b"\x7F" + name
# Each encoder function's signature is:
# - name: utf-8 bytes
# - value: a Python data type, e.g. a Python int for _encode_int
# - check_keys: bool, whether to check for invalid names
# - opts: a CodecOptions
_ENCODERS = {
bool: _encode_bool,
bytes: _encode_bytes,
@ -628,13 +639,13 @@ if not PY3:
_ENCODERS[long] = _encode_long
def _name_value_to_bson(name, value, check_keys, uuid_subtype):
def _name_value_to_bson(name, value, check_keys, opts):
"""Encode a single name, value pair."""
# First see if the type is already cached. KeyError will only ever
# happen once per subtype.
try:
return _ENCODERS[type(value)](name, value, check_keys, uuid_subtype)
return _ENCODERS[type(value)](name, value, check_keys, opts)
except KeyError:
pass
@ -646,7 +657,7 @@ def _name_value_to_bson(name, value, check_keys, uuid_subtype):
func = _MARKERS[marker]
# Cache this type for faster subsequent lookup.
_ENCODERS[type(value)] = func
return func(name, value, check_keys, uuid_subtype)
return func(name, value, check_keys, opts)
# If all else fails test each base type. This will only happen once for
# a subtype of a supported base type.
@ -655,13 +666,13 @@ def _name_value_to_bson(name, value, check_keys, uuid_subtype):
func = _ENCODERS[base]
# Cache this type for faster subsequent lookup.
_ENCODERS[type(value)] = func
return func(name, value, check_keys, uuid_subtype)
return func(name, value, check_keys, opts)
raise InvalidDocument("cannot convert value of type %s to bson" %
type(value))
def _element_to_bson(key, value, check_keys, uuid_subtype):
def _element_to_bson(key, value, check_keys, opts):
"""Encode a single key, value pair."""
if not isinstance(key, string_type):
raise InvalidDocument("documents must have only string keys, "
@ -673,20 +684,20 @@ def _element_to_bson(key, value, check_keys, uuid_subtype):
raise InvalidDocument("key %r must not contain '.'" % (key,))
name = _make_name(key)
return _name_value_to_bson(name, value, check_keys, uuid_subtype)
return _name_value_to_bson(name, value, check_keys, opts)
def _dict_to_bson(doc, check_keys, uuid_subtype, top_level=True):
def _dict_to_bson(doc, check_keys, opts, top_level=True):
"""Encode a document to BSON."""
try:
elements = []
if top_level and "_id" in doc:
elements.append(_name_value_to_bson(b"_id\x00", doc["_id"],
check_keys, uuid_subtype))
check_keys, opts))
for (key, value) in iteritems(doc):
if not top_level or key != "_id":
elements.append(_element_to_bson(key, value,
check_keys, uuid_subtype))
check_keys, opts))
except AttributeError:
raise TypeError("encoder expected a mapping type but got: %r" % (doc,))
@ -696,8 +707,11 @@ if _USE_C:
_dict_to_bson = _cbson._dict_to_bson
def decode_all(data, as_class=dict,
tz_aware=True, uuid_subtype=OLD_UUID_SUBTYPE):
_CODEC_OPTIONS_TYPE_ERROR = TypeError(
"codec_options must be an instance of CodecOptions")
def decode_all(data, codec_options=DEFAULT_CODEC_OPTIONS):
"""Decode BSON data to multiple documents.
`data` must be a string of concatenated, valid, BSON-encoded
@ -705,12 +719,8 @@ def decode_all(data, as_class=dict,
:Parameters:
- `data`: BSON data
- `as_class` (optional): the class to use for the resulting
documents
- `tz_aware` (optional): if ``True``, return timezone-aware
:class:`~datetime.datetime` instances
- `uuid_subtype` (optional): The BSON representation to use for UUIDs.
See the :mod:`bson.binary` module for all options.
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`.
.. versionchanged:: 3.0
Removed `compile_re` option: PyMongo now always represents BSON regular
@ -718,6 +728,9 @@ def decode_all(data, as_class=dict,
:meth:`~bson.regex.Regex.try_compile` to attempt to convert from a
BSON regular expression to a Python regular expression object.
Replaced `as_class`, `tz_aware`, and `uuid_subtype` options with
`codec_options`.
.. versionchanged:: 2.7
Added `compile_re` option. If set to False, PyMongo represented BSON
regular expressions as :class:`~bson.regex.Regex` objects instead of
@ -727,7 +740,9 @@ def decode_all(data, as_class=dict,
.. _PYTHON-500: https://jira.mongodb.org/browse/PYTHON-500
"""
opts = (as_class, tz_aware, uuid_subtype)
if not isinstance(codec_options, CodecOptions):
raise _CODEC_OPTIONS_TYPE_ERROR
docs = []
position = 0
end = len(data) - 1
@ -739,7 +754,10 @@ def decode_all(data, as_class=dict,
obj_end = position + obj_size - 1
if data[obj_end:position + obj_size] != b"\x00":
raise InvalidBSON("bad eoo")
docs.append(_elements_to_dict(data, position + 4, obj_end, opts))
docs.append(_elements_to_dict(data,
position + 4,
obj_end,
codec_options))
position += obj_size
return docs
except InvalidBSON:
@ -754,8 +772,7 @@ if _USE_C:
decode_all = _cbson.decode_all
def decode_iter(data, as_class=dict, tz_aware=True,
uuid_subtype=OLD_UUID_SUBTYPE):
def decode_iter(data, codec_options=DEFAULT_CODEC_OPTIONS):
"""Decode BSON data to multiple documents as a generator.
Works similarly to the decode_all function, but yields one document at a
@ -766,15 +783,18 @@ def decode_iter(data, as_class=dict, tz_aware=True,
:Parameters:
- `data`: BSON data
- `as_class` (optional): the class to use for the resulting
documents
- `tz_aware` (optional): if ``True``, return timezone-aware
:class:`~datetime.datetime` instances
- `uuid_subtype` (optional): The BSON representation to use for UUIDs.
See the :mod:`bson.binary` module for all options.
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`.
.. versionchanged:: 3.0
Replaced `as_class`, `tz_aware`, and `uuid_subtype` options with
`codec_options`.
.. versionadded:: 2.8
"""
if not isinstance(codec_options, CodecOptions):
raise _CODEC_OPTIONS_TYPE_ERROR
position = 0
end = len(data) - 1
while position < end:
@ -782,12 +802,10 @@ def decode_iter(data, as_class=dict, tz_aware=True,
elements = data[position:position + obj_size]
position += obj_size
yield _bson_to_dict(elements, as_class,
tz_aware, uuid_subtype)
yield _bson_to_dict(elements, codec_options)
def decode_file_iter(file_obj, as_class=dict, tz_aware=True,
uuid_subtype=OLD_UUID_SUBTYPE):
def decode_file_iter(file_obj, codec_options=DEFAULT_CODEC_OPTIONS):
"""Decode bson data from a file to multiple documents as a generator.
Works similarly to the decode_all function, but reads from the file object
@ -795,12 +813,12 @@ def decode_file_iter(file_obj, as_class=dict, tz_aware=True,
:Parameters:
- `file_obj`: A file object containing BSON data.
- `as_class` (optional): the class to use for the resulting
documents
- `tz_aware` (optional): if ``True``, return timezone-aware
:class:`~datetime.datetime` instances
- `uuid_subtype` (optional): The BSON representation to use for UUIDs.
See the :mod:`bson.binary` module for all options.
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`.
.. versionchanged:: 3.0
Replaced `as_class`, `tz_aware`, and `uuid_subtype` options with
`codec_options`.
.. versionadded:: 2.8
"""
@ -813,8 +831,7 @@ def decode_file_iter(file_obj, as_class=dict, tz_aware=True,
raise InvalidBSON("cut off in middle of objsize")
obj_size = _UNPACK_INT(size_data)[0] - 4
elements = size_data + file_obj.read(obj_size)
yield _bson_to_dict(elements, as_class,
tz_aware, uuid_subtype)
yield _bson_to_dict(elements, codec_options)
def is_valid(bson):
@ -831,7 +848,7 @@ def is_valid(bson):
raise TypeError("BSON data must be an instance of a subclass of bytes")
try:
_bson_to_dict(bson, dict, True, OLD_UUID_SUBTYPE)
_bson_to_dict(bson, DEFAULT_CODEC_OPTIONS)
return True
except Exception:
return False
@ -842,7 +859,8 @@ class BSON(bytes):
"""
@classmethod
def encode(cls, document, check_keys=False, uuid_subtype=OLD_UUID_SUBTYPE):
def encode(cls, document, check_keys=False,
codec_options=DEFAULT_CODEC_OPTIONS):
"""Encode a document to a new :class:`BSON` instance.
A document can be any mapping type (like :class:`dict`).
@ -858,34 +876,38 @@ class BSON(bytes):
- `check_keys` (optional): check if keys start with '$' or
contain '.', raising :class:`~bson.errors.InvalidDocument` in
either case
- `uuid_subtype` (optional): The BSON representation to use for
UUIDs. See the :mod:`bson.binary` module for all options.
"""
return cls(_dict_to_bson(document, check_keys, uuid_subtype))
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`.
def decode(self, as_class=dict,
tz_aware=False, uuid_subtype=OLD_UUID_SUBTYPE):
.. versionchanged:: 3.0
Replaced `uuid_subtype` option with `codec_options`.
"""
if not isinstance(codec_options, CodecOptions):
raise _CODEC_OPTIONS_TYPE_ERROR
return cls(_dict_to_bson(document, check_keys, codec_options))
def decode(self, codec_options=DEFAULT_CODEC_OPTIONS):
"""Decode this BSON data.
The default type to use for the resultant document is
:class:`dict`. Any other class that supports
:meth:`__setitem__` can be used instead by passing it as the
`as_class` parameter.
By default, returns a BSON document represented as a Python
:class:`dict`. To use a different :class:`MutableMapping` class,
configure a :class:`~bson.codec_options.CodecOptions`::
If `tz_aware` is ``True`` (recommended), any
:class:`~datetime.datetime` instances returned will be
timezone-aware, with their timezone set to
:attr:`bson.tz_util.utc`. Otherwise (default), all
:class:`~datetime.datetime` instances will be naive (but
contain UTC).
>>> import collections # From Python standard library.
>>> import bson
>>> from bson.codec_options import CodecOptions
>>> data = bson.BSON.encode({'a': 1})
>>> decoded_doc = bson.BSON.decode(data)
<type 'dict'>
>>> options = CodecOptions(as_class=collections.OrderedDict)
>>> decoded_doc = bson.BSON.decode(data, codec_options=options)
>>> type(decoded_doc)
<class 'collections.OrderedDict'>
:Parameters:
- `as_class` (optional): the class to use for the resulting
document
- `tz_aware` (optional): if ``True``, return timezone-aware
:class:`~datetime.datetime` instances
- `uuid_subtype` (optional): The BSON representation to use for
UUIDs. See the :mod:`bson.binary` module for all options.
- `codec_options` (optional): An instance of
:class:`~bson.codec_options.CodecOptions`.
.. versionchanged:: 3.0
Removed `compile_re` option: PyMongo now always represents BSON
@ -893,6 +915,9 @@ class BSON(bytes):
:meth:`~bson.regex.Regex.try_compile` to attempt to convert from a
BSON regular expression to a Python regular expression object.
Replaced `as_class`, `tz_aware`, and `uuid_subtype` options with
`codec_options`.
.. versionchanged:: 2.7
Added `compile_re` option. If set to False, PyMongo represented BSON
regular expressions as :class:`~bson.regex.Regex` objects instead of
@ -902,7 +927,10 @@ class BSON(bytes):
.. _PYTHON-500: https://jira.mongodb.org/browse/PYTHON-500
"""
return _bson_to_dict(self, as_class, tz_aware, uuid_subtype)
if not isinstance(codec_options, CodecOptions):
raise _CODEC_OPTIONS_TYPE_ERROR
return _bson_to_dict(self, codec_options)
def has_c():

View File

@ -68,8 +68,14 @@ static struct module_state _state;
/* Maximum number of regex flags */
#define FLAGS_SIZE 7
/* Default UUID representation type code. */
#define PYTHON_LEGACY 3
/* Other UUID representations. */
#define STANDARD 4
#define JAVA_LEGACY 5
#define CSHARP_LEGACY 6
#define BSON_MAX_SIZE 2147483647
/* The smallest possible BSON document, i.e. "{}" */
#define BSON_MIN_SIZE 5
@ -104,15 +110,46 @@ _downcast_and_check(Py_ssize_t size, int extra) {
return (int)size + extra;
}
/* Fill out a codec_options_t* from a CodecOptions object. Use with the "O&"
* format spec in PyArg_ParseTuple.
*
* Return 1 on success. options->as_class is a new reference.
* Return 0 on failure.
*/
int convert_codec_options(PyObject* options_obj, void* p) {
codec_options_t* options = (codec_options_t*)p;
if (!PyArg_ParseTuple(options_obj, "Obb",
&options->as_class,
&options->tz_aware,
&options->uuid_rep)) {
return 0;
}
Py_INCREF(options->as_class);
return 1;
}
/* Fill out a codec_options_t* with default options. */
void default_codec_options(codec_options_t* options) {
options->as_class = (PyObject*)&PyDict_Type;
Py_INCREF(options->as_class);
// TODO: set to "1". PYTHON-526, setting tz_aware=True by default.
options->tz_aware = 0;
options->uuid_rep = PYTHON_LEGACY;
}
void destroy_codec_options(codec_options_t* options) {
Py_CLEAR(options->as_class);
}
static PyObject* elements_to_dict(PyObject* self, const char* string,
unsigned max, PyObject* as_class,
unsigned char tz_aware,
unsigned char uuid_subtype);
unsigned max, const codec_options_t* options);
static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
int type_byte, PyObject* value,
unsigned char check_keys,
unsigned char uuid_subtype);
const codec_options_t* options);
/* Date stuff */
static PyObject* datetime_from_millis(long long millis) {
@ -367,12 +404,12 @@ static int _load_python_objects(PyObject* module) {
static int write_element_to_buffer(PyObject* self, buffer_t buffer,
int type_byte, PyObject* value,
unsigned char check_keys,
unsigned char uuid_subtype) {
const codec_options_t* options) {
int result;
if(Py_EnterRecursiveCall(" while encoding an object to BSON "))
return 0;
result = _write_element_to_buffer(self, buffer, type_byte,
value, check_keys, uuid_subtype);
value, check_keys, options);
Py_LeaveRecursiveCall();
return result;
}
@ -556,7 +593,7 @@ static int _write_regex_to_buffer(
static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
int type_byte, PyObject* value,
unsigned char check_keys,
unsigned char uuid_subtype) {
const codec_options_t* options) {
struct module_state *state = GETSTATE(self);
PyObject* type_marker = NULL;
PyObject* mapping_type;
@ -730,7 +767,7 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
return 0;
}
if (!write_dict(self, buffer, scope, 0, uuid_subtype, 0)) {
if (!write_dict(self, buffer, scope, 0, options, 0)) {
Py_DECREF(scope);
return 0;
}
@ -799,7 +836,7 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
if (!as_doc) {
return 0;
}
if (!write_dict(self, buffer, as_doc, 0, uuid_subtype, 0)) {
if (!write_dict(self, buffer, as_doc, 0, options, 0)) {
Py_DECREF(as_doc);
return 0;
}
@ -879,7 +916,7 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
return 1;
} else if (PyDict_Check(value)) {
*(buffer_get_buffer(buffer) + type_byte) = 0x03;
return write_dict(self, buffer, value, check_keys, uuid_subtype, 0);
return write_dict(self, buffer, value, check_keys, options, 0);
} else if (PyList_Check(value) || PyTuple_Check(value)) {
Py_ssize_t items, i;
int start_position,
@ -923,7 +960,7 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
if (!(item_value = PySequence_GetItem(value, i)))
return 0;
if (!write_element_to_buffer(self, buffer, list_type_byte,
item_value, check_keys, uuid_subtype)) {
item_value, check_keys, options)) {
Py_DECREF(item_value);
return 0;
}
@ -1041,7 +1078,7 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
return 0;
}
*(buffer_get_buffer(buffer) + type_byte) = 0x03;
return write_dict(self, buffer, value, check_keys, uuid_subtype, 0);
return write_dict(self, buffer, value, check_keys, options, 0);
}
uuid_type = _get_object(state->UUID, "uuid", "UUID");
@ -1061,11 +1098,12 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
return 0;
}
if (uuid_subtype == JAVA_LEGACY || uuid_subtype == CSHARP_LEGACY) {
if (options->uuid_rep == JAVA_LEGACY
|| options->uuid_rep == CSHARP_LEGACY) {
subtype = 3;
}
else {
subtype = uuid_subtype;
subtype = options->uuid_rep;
}
*(buffer_get_buffer(buffer) + type_byte) = 0x05;
@ -1076,7 +1114,7 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
return 0;
}
if (uuid_subtype == CSHARP_LEGACY) {
if (options->uuid_rep == CSHARP_LEGACY) {
/* Legacy C# byte order */
bytes = PyObject_GetAttrString(value, "bytes_le");
}
@ -1095,7 +1133,7 @@ static int _write_element_to_buffer(PyObject* self, buffer_t buffer,
Py_DECREF(bytes);
return 0;
}
if (uuid_subtype == JAVA_LEGACY) {
if (options->uuid_rep == JAVA_LEGACY) {
/* Store in legacy java byte order. */
char as_legacy_java[16];
_fix_java(data, as_legacy_java);
@ -1166,7 +1204,7 @@ static int check_key_name(const char* name, int name_length) {
* Returns 0 on failure */
int write_pair(PyObject* self, buffer_t buffer, const char* name, int name_length,
PyObject* value, unsigned char check_keys,
unsigned char uuid_subtype, unsigned char allow_id) {
const codec_options_t* options, unsigned char allow_id) {
int type_byte;
/* Don't write any _id elements unless we're explicitly told to -
@ -1188,7 +1226,7 @@ int write_pair(PyObject* self, buffer_t buffer, const char* name, int name_lengt
return 0;
}
if (!write_element_to_buffer(self, buffer, type_byte,
value, check_keys, uuid_subtype)) {
value, check_keys, options)) {
return 0;
}
return 1;
@ -1197,7 +1235,8 @@ int write_pair(PyObject* self, buffer_t buffer, const char* name, int name_lengt
int decode_and_write_pair(PyObject* self, buffer_t buffer,
PyObject* key, PyObject* value,
unsigned char check_keys,
unsigned char uuid_subtype, unsigned char top_level) {
const codec_options_t* options,
unsigned char top_level) {
PyObject* encoded;
const char* data;
int size;
@ -1310,7 +1349,7 @@ int decode_and_write_pair(PyObject* self, buffer_t buffer,
/* If top_level is True, don't allow writing _id here - it was already written. */
if (!write_pair(self, buffer, data,
size - 1, value, check_keys, uuid_subtype, !top_level)) {
size - 1, value, check_keys, options, !top_level)) {
Py_DECREF(encoded);
return 0;
}
@ -1322,7 +1361,7 @@ int decode_and_write_pair(PyObject* self, buffer_t buffer,
/* returns 0 on failure */
int write_dict(PyObject* self, buffer_t buffer,
PyObject* dict, unsigned char check_keys,
unsigned char uuid_subtype, unsigned char top_level) {
const codec_options_t* options, unsigned char top_level) {
PyObject* key;
PyObject* iter;
char zero = 0;
@ -1390,7 +1429,7 @@ int write_dict(PyObject* self, buffer_t buffer,
return 0;
}
if (!write_pair(self, buffer, "_id", 3,
_id, check_keys, uuid_subtype, 1)) {
_id, check_keys, options, 1)) {
Py_DECREF(_id);
return 0;
}
@ -1410,7 +1449,7 @@ int write_dict(PyObject* self, buffer_t buffer,
return 0;
}
if (!decode_and_write_pair(self, buffer, key, value,
check_keys, uuid_subtype, top_level)) {
check_keys, options, top_level)) {
Py_DECREF(key);
Py_DECREF(value);
Py_DECREF(iter);
@ -1434,22 +1473,23 @@ static PyObject* _cbson_dict_to_bson(PyObject* self, PyObject* args) {
PyObject* dict;
PyObject* result;
unsigned char check_keys;
unsigned char uuid_subtype;
unsigned char top_level = 1;
codec_options_t options;
buffer_t buffer;
if (!PyArg_ParseTuple(args, "Obb|b", &dict,
&check_keys, &uuid_subtype, &top_level)) {
if (!PyArg_ParseTuple(args, "ObO&|b", &dict, &check_keys,
convert_codec_options, &options, &top_level)) {
return NULL;
}
buffer = buffer_new();
if (!buffer) {
destroy_codec_options(&options);
PyErr_NoMemory();
return NULL;
}
if (!write_dict(self, buffer, dict, check_keys, uuid_subtype, top_level)) {
if (!write_dict(self, buffer, dict, check_keys, &options, top_level)) {
destroy_codec_options(&options);
buffer_free(buffer);
return NULL;
}
@ -1462,15 +1502,14 @@ static PyObject* _cbson_dict_to_bson(PyObject* self, PyObject* args) {
result = Py_BuildValue("s#", buffer_get_buffer(buffer),
buffer_get_position(buffer));
#endif
destroy_codec_options(&options);
buffer_free(buffer);
return result;
}
static PyObject* get_value(PyObject* self, const char* buffer,
unsigned* position, unsigned char type,
unsigned max, PyObject* as_class,
unsigned char tz_aware,
unsigned char uuid_subtype) {
unsigned max, const codec_options_t* options) {
struct module_state *state = GETSTATE(self);
PyObject* value = NULL;
@ -1526,8 +1565,7 @@ static PyObject* get_value(PyObject* self, const char* buffer,
goto invalid;
}
value = elements_to_dict(self, buffer + *position + 4,
size - 5, as_class, tz_aware,
uuid_subtype);
size - 5, options);
if (!value) {
goto invalid;
}
@ -1625,8 +1663,7 @@ static PyObject* get_value(PyObject* self, const char* buffer,
goto invalid;
}
to_append = get_value(self, buffer, position, bson_type,
max - (unsigned)key_size,
as_class, tz_aware, uuid_subtype);
max - (unsigned)key_size, options);
Py_LeaveRecursiveCall();
if (!to_append) {
Py_DECREF(value);
@ -1701,13 +1738,13 @@ static PyObject* get_value(PyObject* self, const char* buffer,
* From this point, we hold refs to args, kwargs, and data.
* If anything fails, goto uuiderror to clean them up.
*/
if (uuid_subtype == CSHARP_LEGACY) {
if (options->uuid_rep == CSHARP_LEGACY) {
/* Legacy C# byte order */
if ((PyDict_SetItemString(kwargs, "bytes_le", data)) == -1)
goto uuiderror;
}
else {
if (uuid_subtype == JAVA_LEGACY) {
if (options->uuid_rep == JAVA_LEGACY) {
/* Convert from legacy java byte order */
char big_endian[16];
_fix_java(buffer + *position, big_endian);
@ -1812,7 +1849,7 @@ static PyObject* get_value(PyObject* self, const char* buffer,
memcpy(&millis, buffer + *position, 8);
naive = datetime_from_millis(millis);
*position += 8;
if (!tz_aware) { /* In the naive case, we're done here. */
if (!options->tz_aware) { /* In the naive case, we're done here. */
value = naive;
break;
}
@ -2035,8 +2072,7 @@ static PyObject* get_value(PyObject* self, const char* buffer,
goto invalid;
}
scope = elements_to_dict(self, buffer + *position + 4,
scope_size - 5, (PyObject*)&PyDict_Type,
tz_aware, uuid_subtype);
scope_size - 5, options);
if (!scope) {
Py_DECREF(code);
goto invalid;
@ -2184,11 +2220,10 @@ static PyObject* get_value(PyObject* self, const char* buffer,
}
static PyObject* _elements_to_dict(PyObject* self, const char* string,
unsigned max, PyObject* as_class,
unsigned char tz_aware,
unsigned char uuid_subtype) {
unsigned max,
const codec_options_t* options) {
unsigned position = 0;
PyObject* dict = PyObject_CallObject(as_class, NULL);
PyObject* dict = PyObject_CallObject(options->as_class, NULL);
if (!dict) {
return NULL;
}
@ -2214,7 +2249,7 @@ static PyObject* _elements_to_dict(PyObject* self, const char* string,
}
position += (unsigned)name_length + 1;
value = get_value(self, string, &position, type,
max - position, as_class, tz_aware, uuid_subtype);
max - position, options);
if (!value) {
Py_DECREF(name);
Py_DECREF(dict);
@ -2229,14 +2264,12 @@ static PyObject* _elements_to_dict(PyObject* self, const char* string,
}
static PyObject* elements_to_dict(PyObject* self, const char* string,
unsigned max, PyObject* as_class,
unsigned char tz_aware,
unsigned char uuid_subtype) {
unsigned max,
const codec_options_t* options) {
PyObject* result;
if (Py_EnterRecursiveCall(" while decoding a BSON document"))
return NULL;
result = _elements_to_dict(self, string, max,
as_class, tz_aware, uuid_subtype);
result = _elements_to_dict(self, string, max, options);
Py_LeaveRecursiveCall();
return result;
}
@ -2246,12 +2279,11 @@ static PyObject* _cbson_bson_to_dict(PyObject* self, PyObject* args) {
Py_ssize_t total_size;
const char* string;
PyObject* bson;
PyObject* as_class;
unsigned char tz_aware;
unsigned char uuid_subtype;
codec_options_t options;
PyObject* result;
if (!PyArg_ParseTuple(
args, "OObb", &bson, &as_class, &tz_aware, &uuid_subtype)) {
args, "OO&", &bson, convert_codec_options, &options)) {
return NULL;
}
@ -2262,6 +2294,7 @@ static PyObject* _cbson_bson_to_dict(PyObject* self, PyObject* args) {
if (!PyString_Check(bson)) {
PyErr_SetString(PyExc_TypeError, "argument to _bson_to_dict must be a string");
#endif
destroy_codec_options(&options);
return NULL;
}
#if PY_MAJOR_VERSION >= 3
@ -2276,6 +2309,7 @@ static PyObject* _cbson_bson_to_dict(PyObject* self, PyObject* args) {
"not enough data for a BSON document");
Py_DECREF(InvalidBSON);
}
destroy_codec_options(&options);
return NULL;
}
@ -2285,6 +2319,7 @@ static PyObject* _cbson_bson_to_dict(PyObject* self, PyObject* args) {
string = PyString_AsString(bson);
#endif
if (!string) {
destroy_codec_options(&options);
return NULL;
}
@ -2295,6 +2330,7 @@ static PyObject* _cbson_bson_to_dict(PyObject* self, PyObject* args) {
PyErr_SetString(InvalidBSON, "invalid message size");
Py_DECREF(InvalidBSON);
}
destroy_codec_options(&options);
return NULL;
}
@ -2304,6 +2340,7 @@ static PyObject* _cbson_bson_to_dict(PyObject* self, PyObject* args) {
PyErr_SetString(InvalidBSON, "objsize too large");
Py_DECREF(InvalidBSON);
}
destroy_codec_options(&options);
return NULL;
}
@ -2313,11 +2350,13 @@ static PyObject* _cbson_bson_to_dict(PyObject* self, PyObject* args) {
PyErr_SetString(InvalidBSON, "bad eoo");
Py_DECREF(InvalidBSON);
}
destroy_codec_options(&options);
return NULL;
}
return elements_to_dict(self, string + 4, (unsigned)size - 5,
as_class, tz_aware, uuid_subtype);
result = elements_to_dict(self, string + 4, (unsigned)size - 5, &options);
destroy_codec_options(&options);
return result;
}
static PyObject* _cbson_decode_all(PyObject* self, PyObject* args) {
@ -2327,16 +2366,18 @@ static PyObject* _cbson_decode_all(PyObject* self, PyObject* args) {
PyObject* bson;
PyObject* dict;
PyObject* result;
PyObject* as_class = (PyObject*)&PyDict_Type;
unsigned char tz_aware = 1;
unsigned char uuid_subtype = 3;
codec_options_t options;
if (!PyArg_ParseTuple(
args, "O|Obb",
&bson, &as_class, &tz_aware, &uuid_subtype)) {
args, "O|O&",
&bson, convert_codec_options, &options)) {
return NULL;
}
if (PyTuple_GET_SIZE(args) < 2) {
default_codec_options(&options);
}
#if PY_MAJOR_VERSION >= 3
if (!PyBytes_Check(bson)) {
PyErr_SetString(PyExc_TypeError, "argument to decode_all must be a bytes object");
@ -2357,8 +2398,10 @@ static PyObject* _cbson_decode_all(PyObject* self, PyObject* args) {
return NULL;
}
if (!(result = PyList_New(0)))
if (!(result = PyList_New(0))) {
destroy_codec_options(&options);
return NULL;
}
while (total_size > 0) {
if (total_size < BSON_MIN_SIZE) {
@ -2368,6 +2411,7 @@ static PyObject* _cbson_decode_all(PyObject* self, PyObject* args) {
"not enough data for a BSON document");
Py_DECREF(InvalidBSON);
}
destroy_codec_options(&options);
Py_DECREF(result);
return NULL;
}
@ -2379,6 +2423,7 @@ static PyObject* _cbson_decode_all(PyObject* self, PyObject* args) {
PyErr_SetString(InvalidBSON, "invalid message size");
Py_DECREF(InvalidBSON);
}
destroy_codec_options(&options);
Py_DECREF(result);
return NULL;
}
@ -2389,6 +2434,7 @@ static PyObject* _cbson_decode_all(PyObject* self, PyObject* args) {
PyErr_SetString(InvalidBSON, "objsize too large");
Py_DECREF(InvalidBSON);
}
destroy_codec_options(&options);
Py_DECREF(result);
return NULL;
}
@ -2399,14 +2445,15 @@ static PyObject* _cbson_decode_all(PyObject* self, PyObject* args) {
PyErr_SetString(InvalidBSON, "bad eoo");
Py_DECREF(InvalidBSON);
}
destroy_codec_options(&options);
Py_DECREF(result);
return NULL;
}
dict = elements_to_dict(self, string + 4, (unsigned)size - 5,
as_class, tz_aware, uuid_subtype);
dict = elements_to_dict(self, string + 4, (unsigned)size - 5, &options);
if (!dict) {
Py_DECREF(result);
destroy_codec_options(&options);
return NULL;
}
PyList_Append(result, dict);
@ -2415,6 +2462,7 @@ static PyObject* _cbson_decode_all(PyObject* self, PyObject* args) {
total_size -= size;
}
destroy_codec_options(&options);
return result;
}
@ -2494,6 +2542,8 @@ init_cbson(void)
_cbson_API[_cbson_write_dict_INDEX] = (void *) write_dict;
_cbson_API[_cbson_write_pair_INDEX] = (void *) write_pair;
_cbson_API[_cbson_decode_and_write_pair_INDEX] = (void *) decode_and_write_pair;
_cbson_API[_cbson_convert_codec_options_INDEX] = (void *) convert_codec_options;
_cbson_API[_cbson_destroy_codec_options_INDEX] = (void *) destroy_codec_options;
#if PY_VERSION_HEX >= 0x03010000
/* PyCapsule is new in python 3.1 */

View File

@ -52,6 +52,12 @@ typedef int Py_ssize_t;
#define STRCAT(dest, n, src) strcat((dest), (src))
#endif
typedef struct codec_options_t {
PyObject* as_class;
unsigned char tz_aware;
unsigned char uuid_rep;
} codec_options_t;
/* C API functions */
#define _cbson_buffer_write_bytes_INDEX 0
#define _cbson_buffer_write_bytes_RETURN int
@ -59,18 +65,26 @@ typedef int Py_ssize_t;
#define _cbson_write_dict_INDEX 1
#define _cbson_write_dict_RETURN int
#define _cbson_write_dict_PROTO (PyObject* self, buffer_t buffer, PyObject* dict, unsigned char check_keys, unsigned char uuid_subtype, unsigned char top_level)
#define _cbson_write_dict_PROTO (PyObject* self, buffer_t buffer, PyObject* dict, unsigned char check_keys, const codec_options_t* options, unsigned char top_level)
#define _cbson_write_pair_INDEX 2
#define _cbson_write_pair_RETURN int
#define _cbson_write_pair_PROTO (PyObject* self, buffer_t buffer, const char* name, int name_length, PyObject* value, unsigned char check_keys, unsigned char uuid_subtype, unsigned char allow_id)
#define _cbson_write_pair_PROTO (PyObject* self, buffer_t buffer, const char* name, int name_length, PyObject* value, unsigned char check_keys, const codec_options_t* options, unsigned char allow_id)
#define _cbson_decode_and_write_pair_INDEX 3
#define _cbson_decode_and_write_pair_RETURN int
#define _cbson_decode_and_write_pair_PROTO (PyObject* self, buffer_t buffer, PyObject* key, PyObject* value, unsigned char check_keys, unsigned char uuid_subtype, unsigned char top_level)
#define _cbson_decode_and_write_pair_PROTO (PyObject* self, buffer_t buffer, PyObject* key, PyObject* value, unsigned char check_keys, const codec_options_t* options, unsigned char top_level)
#define _cbson_convert_codec_options_INDEX 4
#define _cbson_convert_codec_options_RETURN int
#define _cbson_convert_codec_options_PROTO (PyObject* options_obj, void* p)
#define _cbson_destroy_codec_options_INDEX 5
#define _cbson_destroy_codec_options_RETURN void
#define _cbson_destroy_codec_options_PROTO (codec_options_t* options)
/* Total number of C API pointers */
#define _cbson_API_POINTER_COUNT 4
#define _cbson_API_POINTER_COUNT 6
#ifdef _CBSON_MODULE
/* This section is used when compiling _cbsonmodule */
@ -83,6 +97,10 @@ static _cbson_write_pair_RETURN write_pair _cbson_write_pair_PROTO;
static _cbson_decode_and_write_pair_RETURN decode_and_write_pair _cbson_decode_and_write_pair_PROTO;
static _cbson_convert_codec_options_RETURN convert_codec_options _cbson_convert_codec_options_PROTO;
static _cbson_destroy_codec_options_RETURN destroy_codec_options _cbson_destroy_codec_options_PROTO;
#else
/* This section is used in modules that use _cbsonmodule's API */
@ -96,6 +114,10 @@ static void **_cbson_API;
#define decode_and_write_pair (*(_cbson_decode_and_write_pair_RETURN (*)_cbson_decode_and_write_pair_PROTO) _cbson_API[_cbson_decode_and_write_pair_INDEX])
#define convert_codec_options (*(_cbson_convert_codec_options_RETURN (*)_cbson_convert_codec_options_PROTO) _cbson_API[_cbson_convert_codec_options_INDEX])
#define destroy_codec_options (*(_cbson_destroy_codec_options_RETURN (*)_cbson_destroy_codec_options_PROTO) _cbson_API[_cbson_destroy_codec_options_INDEX])
#define _cbson_IMPORT _cbson_API = (void **)PyCapsule_Import("_cbson._C_API", 0)
#endif

View File

@ -98,6 +98,11 @@ byte order and binary subtype :data:`OLD_UUID_SUBTYPE`.
ALL_UUID_SUBTYPES = (OLD_UUID_SUBTYPE, UUID_SUBTYPE)
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.

View File

@ -14,12 +14,18 @@
"""Tools for specifying BSON codec options."""
from collections import MutableMapping
from collections import MutableMapping, namedtuple
from bson.binary import ALL_UUID_REPRESENTATIONS, PYTHON_LEGACY
from bson.binary import (ALL_UUID_REPRESENTATIONS,
PYTHON_LEGACY,
UUID_REPRESENTATION_NAMES)
class CodecOptions(object):
_options_base = namedtuple('CodecOptions',
('as_class', 'tz_aware', 'uuid_representation'))
class CodecOptions(_options_base):
"""Encapsulates BSON options used in CRUD operations.
:Parameters:
@ -34,10 +40,8 @@ class CodecOptions(object):
:data:`~bson.binary.PYTHON_LEGACY`.
"""
__slots__ = ("__as_class", "__tz_aware", "__uuid_rep")
def __init__(self, as_class=dict,
tz_aware=False, uuid_representation=PYTHON_LEGACY):
def __new__(cls, as_class=dict,
tz_aware=False, uuid_representation=PYTHON_LEGACY):
if not issubclass(as_class, MutableMapping):
raise TypeError("document_class must be a "
"subclass of MutableMapping")
@ -47,39 +51,29 @@ class CodecOptions(object):
raise ValueError("uuid_representation must be a value "
"from bson.binary.ALL_UUID_REPRESENTATIONS")
self.__as_class = as_class
self.__tz_aware = tz_aware
self.__uuid_rep = uuid_representation
return tuple.__new__(cls, (as_class, tz_aware, uuid_representation))
@property
def as_class(self):
"""Read only property for as_class."""
return self.__as_class
def __repr__(self):
as_class_repr = (
'dict' if self.as_class is dict else repr(self.as_class))
@property
def tz_aware(self):
"""Read only property for tz_aware."""
return self.__tz_aware
uuid_rep_repr = UUID_REPRESENTATION_NAMES.get(self.uuid_representation,
self.uuid_representation)
@property
def uuid_representation(self):
"""Read only property for uuid_representation."""
return self.__uuid_rep
return (
'CodecOptions(as_class=%s, tz_aware=%r, uuid_representation=%s)'
% (as_class_repr, self.tz_aware, uuid_rep_repr))
def __eq__(self, other):
if isinstance(other, CodecOptions):
return (self.__as_class == other.as_class and
self.__tz_aware == other.tz_aware and
self.__uuid_rep == other.uuid_representation)
raise NotImplementedError
def __ne__(self, other):
return self != other
DEFAULT_CODEC_OPTIONS = CodecOptions()
def _parse_codec_options(options):
"""Parse BSON codec options."""
as_class = options.get('document_class', dict)
tz_aware = options.get('tz_aware', False)
uuid_rep = options.get('uuidrepresentation', PYTHON_LEGACY)
return CodecOptions(as_class, tz_aware, uuid_rep)
return CodecOptions(
as_class=options.get(
'document_class', DEFAULT_CODEC_OPTIONS.as_class),
tz_aware=options.get(
'tz_aware', DEFAULT_CODEC_OPTIONS.tz_aware),
uuid_representation=options.get(
'uuidrepresentation', DEFAULT_CODEC_OPTIONS.uuid_representation))

View File

@ -111,6 +111,22 @@ patterns, see `PYTHON-500`_. Use :meth:`~bson.regex.Regex.try_compile` to
attempt to convert from a BSON regular expression to a Python regular
expression object.
The `as_class`, `tz_aware`, and `uuid_subtype` options are removed from all
BSON encoding and decoding methods. Use
:class:`~bson.codec_options.CodecOptions` to configure these options. The
APIs affected are:
- :func:`~bson.decode_all`
- :func:`~bson.decode_iter`
- :func:`~bson.decode_file_iter`
- :meth:`~bson.BSON.encode`
- :meth:`~bson.BSON.decode`
This is a breaking change for any application that uses the BSON API directly
and changes any of the named parameter defaults. No changes are required for
applications that use the default values for these options. The behavior
remains the same.
.. _PYTHON-500: https://jira.mongodb.org/browse/PYTHON-500
Issues Resolved

View File

@ -64,7 +64,8 @@ static PyObject* _error(char* name) {
/* add a lastError message on the end of the buffer.
* returns 0 on failure */
static int add_last_error(PyObject* self, buffer_t buffer,
int request_id, char* ns, int nslen, PyObject* args) {
int request_id, char* ns, int nslen,
codec_options_t* options, PyObject* args) {
struct module_state *state = GETSTATE(self);
int message_start;
@ -110,7 +111,9 @@ static int add_last_error(PyObject* self, buffer_t buffer,
/* getlasterror: 1 */
if (!(one = PyLong_FromLong(1)))
return 0;
if (!write_pair(state->_cbson, buffer, "getlasterror", 12, one, 0, 4, 1)) {
if (!write_pair(state->_cbson, buffer, "getlasterror", 12, one, 0,
options, 1)) {
Py_DECREF(one);
return 0;
}
@ -118,7 +121,8 @@ static int add_last_error(PyObject* self, buffer_t buffer,
/* getlasterror options */
while (PyDict_Next(args, &pos, &key, &value)) {
if (!decode_and_write_pair(state->_cbson, buffer, key, value, 0, 4, 0)) {
if (!decode_and_write_pair(state->_cbson, buffer, key, value, 0,
options, 0)) {
return 0;
}
}
@ -170,42 +174,44 @@ static PyObject* _cbson_insert_message(PyObject* self, PyObject* args) {
PyObject* doc;
PyObject* iterator;
int before, cur_size, max_size = 0;
int options = 0;
int flags = 0;
unsigned char check_keys;
unsigned char safe;
unsigned char continue_on_error;
unsigned char uuid_subtype;
codec_options_t options;
PyObject* last_error_args;
buffer_t buffer;
int length_location, message_length;
PyObject* result;
if (!PyArg_ParseTuple(args, "et#ObbObb",
if (!PyArg_ParseTuple(args, "et#ObbObO&",
"utf-8",
&collection_name,
&collection_name_length,
&docs, &check_keys, &safe,
&last_error_args,
&continue_on_error, &uuid_subtype)) {
&continue_on_error,
convert_codec_options, &options)) {
return NULL;
}
if (continue_on_error) {
options += 1;
flags += 1;
}
buffer = buffer_new();
if (!buffer) {
PyErr_NoMemory();
destroy_codec_options(&options);
PyMem_Free(collection_name);
return NULL;
}
length_location = init_insert_buffer(buffer,
request_id,
options,
flags,
collection_name,
collection_name_length);
if (length_location == -1) {
destroy_codec_options(&options);
PyMem_Free(collection_name);
buffer_free(buffer);
return NULL;
@ -218,15 +224,18 @@ static PyObject* _cbson_insert_message(PyObject* self, PyObject* args) {
PyErr_SetString(InvalidOperation, "input is not iterable");
Py_DECREF(InvalidOperation);
}
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
}
while ((doc = PyIter_Next(iterator)) != NULL) {
before = buffer_get_position(buffer);
if (!write_dict(state->_cbson, buffer, doc, check_keys, uuid_subtype, 1)) {
if (!write_dict(state->_cbson, buffer, doc, check_keys,
&options, 1)) {
Py_DECREF(doc);
Py_DECREF(iterator);
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
@ -238,6 +247,7 @@ static PyObject* _cbson_insert_message(PyObject* self, PyObject* args) {
Py_DECREF(iterator);
if (PyErr_Occurred()) {
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
@ -249,6 +259,7 @@ static PyObject* _cbson_insert_message(PyObject* self, PyObject* args) {
PyErr_SetString(InvalidOperation, "cannot do an empty bulk insert");
Py_DECREF(InvalidOperation);
}
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
@ -259,7 +270,8 @@ static PyObject* _cbson_insert_message(PyObject* self, PyObject* args) {
if (safe) {
if (!add_last_error(self, buffer, request_id, collection_name,
collection_name_length, last_error_args)) {
collection_name_length, &options, last_error_args)) {
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
@ -273,6 +285,7 @@ static PyObject* _cbson_insert_message(PyObject* self, PyObject* args) {
buffer_get_buffer(buffer),
buffer_get_position(buffer),
max_size);
destroy_codec_options(&options);
buffer_free(buffer);
return result;
}
@ -291,31 +304,33 @@ static PyObject* _cbson_update_message(PyObject* self, PyObject* args) {
unsigned char upsert;
unsigned char safe;
unsigned char check_keys;
unsigned char uuid_subtype;
codec_options_t options;
PyObject* last_error_args;
int options;
int flags;
buffer_t buffer;
int length_location, message_length;
PyObject* result;
if (!PyArg_ParseTuple(args, "et#bbOObObb",
if (!PyArg_ParseTuple(args, "et#bbOObObO&",
"utf-8",
&collection_name,
&collection_name_length,
&upsert, &multi, &spec, &doc, &safe,
&last_error_args, &check_keys, &uuid_subtype)) {
&last_error_args, &check_keys,
convert_codec_options, &options)) {
return NULL;
}
options = 0;
flags = 0;
if (upsert) {
options += 1;
flags += 1;
}
if (multi) {
options += 2;
flags += 2;
}
buffer = buffer_new();
if (!buffer) {
destroy_codec_options(&options);
PyErr_NoMemory();
PyMem_Free(collection_name);
return NULL;
@ -324,6 +339,7 @@ static PyObject* _cbson_update_message(PyObject* self, PyObject* args) {
// save space for message length
length_location = buffer_save_space(buffer, 4);
if (length_location == -1) {
destroy_codec_options(&options);
PyMem_Free(collection_name);
PyErr_NoMemory();
return NULL;
@ -337,14 +353,16 @@ static PyObject* _cbson_update_message(PyObject* self, PyObject* args) {
!buffer_write_bytes(buffer,
collection_name,
collection_name_length + 1) ||
!buffer_write_bytes(buffer, (const char*)&options, 4)) {
!buffer_write_bytes(buffer, (const char*)&flags, 4)) {
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
}
before = buffer_get_position(buffer);
if (!write_dict(state->_cbson, buffer, spec, 0, uuid_subtype, 1)) {
if (!write_dict(state->_cbson, buffer, spec, 0, &options, 1)) {
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
@ -352,7 +370,9 @@ static PyObject* _cbson_update_message(PyObject* self, PyObject* args) {
max_size = buffer_get_position(buffer) - before;
before = buffer_get_position(buffer);
if (!write_dict(state->_cbson, buffer, doc, check_keys, uuid_subtype, 1)) {
if (!write_dict(state->_cbson, buffer, doc, check_keys,
&options, 1)) {
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
@ -365,7 +385,8 @@ static PyObject* _cbson_update_message(PyObject* self, PyObject* args) {
if (safe) {
if (!add_last_error(self, buffer, request_id, collection_name,
collection_name_length, last_error_args)) {
collection_name_length, &options, last_error_args)) {
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
@ -379,6 +400,7 @@ static PyObject* _cbson_update_message(PyObject* self, PyObject* args) {
buffer_get_buffer(buffer),
buffer_get_position(buffer),
max_size);
destroy_codec_options(&options);
buffer_free(buffer);
return result;
}
@ -388,31 +410,33 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
struct module_state *state = GETSTATE(self);
int request_id = rand();
unsigned int options;
unsigned int flags;
char* collection_name = NULL;
int collection_name_length;
int begin, cur_size, max_size = 0;
int num_to_skip;
int num_to_return;
PyObject* query;
PyObject* field_selector = Py_None;
unsigned char uuid_subtype = 3;
PyObject* field_selector;
codec_options_t options;
buffer_t buffer;
int length_location, message_length;
PyObject* result;
if (!PyArg_ParseTuple(args, "Iet#iiO|Ob",
&options,
if (!PyArg_ParseTuple(args, "Iet#iiOOO&",
&flags,
"utf-8",
&collection_name,
&collection_name_length,
&num_to_skip, &num_to_return,
&query, &field_selector, &uuid_subtype)) {
&query, &field_selector,
convert_codec_options, &options)) {
return NULL;
}
buffer = buffer_new();
if (!buffer) {
PyErr_NoMemory();
destroy_codec_options(&options);
PyMem_Free(collection_name);
return NULL;
}
@ -420,24 +444,27 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
// save space for message length
length_location = buffer_save_space(buffer, 4);
if (length_location == -1) {
destroy_codec_options(&options);
PyMem_Free(collection_name);
PyErr_NoMemory();
return NULL;
}
if (!buffer_write_bytes(buffer, (const char*)&request_id, 4) ||
!buffer_write_bytes(buffer, "\x00\x00\x00\x00\xd4\x07\x00\x00", 8) ||
!buffer_write_bytes(buffer, (const char*)&options, 4) ||
!buffer_write_bytes(buffer, (const char*)&flags, 4) ||
!buffer_write_bytes(buffer, collection_name,
collection_name_length + 1) ||
!buffer_write_bytes(buffer, (const char*)&num_to_skip, 4) ||
!buffer_write_bytes(buffer, (const char*)&num_to_return, 4)) {
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
}
begin = buffer_get_position(buffer);
if (!write_dict(state->_cbson, buffer, query, 0, uuid_subtype, 1)) {
if (!write_dict(state->_cbson, buffer, query, 0, &options, 1)) {
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
@ -446,7 +473,9 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
if (field_selector != Py_None) {
begin = buffer_get_position(buffer);
if (!write_dict(state->_cbson, buffer, field_selector, 0, uuid_subtype, 1)) {
if (!write_dict(state->_cbson, buffer, field_selector, 0,
&options, 1)) {
destroy_codec_options(&options);
buffer_free(buffer);
PyMem_Free(collection_name);
return NULL;
@ -465,6 +494,7 @@ static PyObject* _cbson_query_message(PyObject* self, PyObject* args) {
buffer_get_buffer(buffer),
buffer_get_position(buffer),
max_size);
destroy_codec_options(&options);
buffer_free(buffer);
return result;
}
@ -550,12 +580,13 @@ _set_document_too_large(int size, long max) {
static PyObject*
_send_insert(PyObject* self, PyObject* client,
PyObject* gle_args, buffer_t buffer,
char* coll_name, int coll_len, int request_id, int safe) {
char* coll_name, int coll_len, int request_id, int safe,
codec_options_t* options) {
PyObject* result;
if (safe) {
if (!add_last_error(self, buffer, request_id,
coll_name, coll_len, gle_args)) {
coll_name, coll_len, options, gle_args)) {
return NULL;
}
}
@ -573,7 +604,7 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
/* NOTE just using a random number as the request_id */
int request_id = rand();
int send_safe, options = 0;
int send_safe, flags = 0;
int length_location, message_length;
int collection_name_length;
char* collection_name = NULL;
@ -588,32 +619,32 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
unsigned char check_keys;
unsigned char safe;
unsigned char continue_on_error;
unsigned char uuid_subtype;
codec_options_t options;
unsigned char empty = 1;
long max_bson_size;
long max_message_size;
buffer_t buffer;
PyObject *exc_type = NULL, *exc_value = NULL, *exc_trace = NULL;
if (!PyArg_ParseTuple(args, "et#ObbObbO",
if (!PyArg_ParseTuple(args, "et#ObbObO&O",
"utf-8",
&collection_name,
&collection_name_length,
&docs, &check_keys, &safe,
&last_error_args,
&continue_on_error,
&uuid_subtype, &client)) {
convert_codec_options, &options,
&client)) {
return NULL;
}
if (continue_on_error) {
options += 1;
flags += 1;
}
/*
* If we are doing unacknowledged writes *and* continue_on_error
* is True it's pointless (and slower) to send GLE.
*/
send_safe = (safe || !continue_on_error);
max_bson_size_obj = PyObject_GetAttrString(client, "max_bson_size");
#if PY_MAJOR_VERSION >= 3
max_bson_size = PyLong_AsLong(max_bson_size_obj);
@ -622,6 +653,7 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
#endif
Py_XDECREF(max_bson_size_obj);
if (max_bson_size == -1) {
destroy_codec_options(&options);
PyMem_Free(collection_name);
return NULL;
}
@ -634,12 +666,14 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
#endif
Py_XDECREF(max_message_size_obj);
if (max_message_size == -1) {
destroy_codec_options(&options);
PyMem_Free(collection_name);
return NULL;
}
buffer = buffer_new();
if (!buffer) {
destroy_codec_options(&options);
PyErr_NoMemory();
PyMem_Free(collection_name);
return NULL;
@ -647,7 +681,7 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
length_location = init_insert_buffer(buffer,
request_id,
options,
flags,
collection_name,
collection_name_length);
if (length_location == -1) {
@ -666,7 +700,8 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
while ((doc = PyIter_Next(iterator)) != NULL) {
int before = buffer_get_position(buffer);
int cur_size;
if (!write_dict(state->_cbson, buffer, doc, check_keys, uuid_subtype, 1)) {
if (!write_dict(state->_cbson, buffer, doc, check_keys,
&options, 1)) {
Py_DECREF(doc);
goto iterfail;
}
@ -682,7 +717,7 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
&message_length, 4);
result = _send_insert(self, client, last_error_args, buffer,
collection_name, collection_name_length,
request_id, send_safe);
request_id, send_safe, &options);
if (!result)
goto iterfail;
Py_DECREF(result);
@ -703,7 +738,7 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
}
message_start = init_insert_buffer(new_buffer,
new_request_id,
options,
flags,
collection_name,
collection_name_length);
if (message_start == -1) {
@ -725,7 +760,7 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
result = _send_insert(self, client, last_error_args, buffer,
collection_name, collection_name_length,
request_id, send_safe);
request_id, send_safe, &options);
buffer_free(buffer);
buffer = new_buffer;
@ -796,7 +831,7 @@ static PyObject* _cbson_do_batched_insert(PyObject* self, PyObject* args) {
/* Send the last (or only) batch */
result = _send_insert(self, client, last_error_args, buffer,
collection_name, collection_name_length,
request_id, safe);
request_id, safe, &options);
PyMem_Free(collection_name);
buffer_free(buffer);
@ -877,7 +912,7 @@ _command_buffer_new(char* ns, int ns_len) {
if (!buffer_write_bytes(buffer,
"\x00\x00\x00\x00" /* responseTo */
"\xd4\x07\x00\x00" /* opcode */
"\x00\x00\x00\x00", /* options */
"\x00\x00\x00\x00", /* flags */
12) ||
!buffer_write_bytes(buffer,
ns, ns_len + 1) || /* namespace */
@ -920,14 +955,15 @@ _cbson_do_batched_write_command(PyObject* self, PyObject* args) {
PyObject* results;
unsigned char op;
unsigned char check_keys;
unsigned char uuid_subtype;
codec_options_t options;
unsigned char empty = 1;
unsigned char errors = 0;
buffer_t buffer;
if (!PyArg_ParseTuple(args, "et#bOObbO", "utf-8",
&ns, &ns_len, &op, &command, &docs,
&check_keys, &uuid_subtype, &client)) {
if (!PyArg_ParseTuple(args, "et#bOObO&O", "utf-8",
&ns, &ns_len, &op, &command, &docs, &check_keys,
convert_codec_options, &options,
&client)) {
return NULL;
}
@ -939,6 +975,7 @@ _cbson_do_batched_write_command(PyObject* self, PyObject* args) {
#endif
Py_XDECREF(max_bson_size_obj);
if (max_bson_size == -1) {
destroy_codec_options(&options);
PyMem_Free(ns);
return NULL;
}
@ -956,6 +993,7 @@ _cbson_do_batched_write_command(PyObject* self, PyObject* args) {
#endif
Py_XDECREF(max_write_batch_size_obj);
if (max_write_batch_size == -1) {
destroy_codec_options(&options);
PyMem_Free(ns);
return NULL;
}
@ -964,11 +1002,13 @@ _cbson_do_batched_write_command(PyObject* self, PyObject* args) {
ordered = !((PyDict_GetItemString(command, "ordered")) == Py_False);
if (!(results = PyList_New(0))) {
destroy_codec_options(&options);
PyMem_Free(ns);
return NULL;
}
if (!(buffer = _command_buffer_new(ns, ns_len))) {
destroy_codec_options(&options);
PyMem_Free(ns);
Py_DECREF(results);
return NULL;
@ -978,7 +1018,8 @@ _cbson_do_batched_write_command(PyObject* self, PyObject* args) {
/* Position of command document length */
cmd_len_loc = buffer_get_position(buffer);
if (!write_dict(state->_cbson, buffer, command, 0, uuid_subtype, 0)) {
if (!write_dict(state->_cbson, buffer, command, 0,
&options, 0)) {
goto cmdfail;
}
@ -1051,7 +1092,7 @@ _cbson_do_batched_write_command(PyObject* self, PyObject* args) {
}
cur_doc_begin = buffer_get_position(buffer);
if (!write_dict(state->_cbson, buffer, doc,
check_keys, uuid_subtype, 1)) {
check_keys, &options, 1)) {
Py_DECREF(doc);
goto cmditerfail;
}
@ -1130,6 +1171,7 @@ _cbson_do_batched_write_command(PyObject* self, PyObject* args) {
Py_DECREF(result);
if (errors && ordered) {
destroy_codec_options(&options);
Py_DECREF(iterator);
buffer_free(buffer);
return results;
@ -1174,11 +1216,13 @@ _cbson_do_batched_write_command(PyObject* self, PyObject* args) {
PyList_Append(results, result);
Py_DECREF(result);
destroy_codec_options(&options);
return results;
cmditerfail:
Py_DECREF(iterator);
cmdfail:
destroy_codec_options(&options);
Py_DECREF(results);
buffer_free(buffer);
return NULL;

View File

@ -506,7 +506,6 @@ class _Bulk(object):
def execute_command(self, generator, write_concern):
"""Execute using write commands.
"""
uuid_representation = self.collection.codec_options.uuid_representation
client = self.collection.database.connection
# nModified is only reported for write commands, not legacy ops.
full_result = {
@ -527,7 +526,7 @@ class _Bulk(object):
results = _do_batched_write_command(
self.namespace, run.op_type, cmd,
run.ops, True, uuid_representation, client)
run.ops, True, self.collection.codec_options, client)
_merge_command(run, full_result, results)
# We're supposed to continue if errors are
@ -584,10 +583,9 @@ class _Bulk(object):
# We have to do this here since Collection.insert
# throws away results and we need to check for jnote.
client = self.collection.database.connection
uuid_representation = self.collection.codec_options.uuid_representation
return client._send_message(
insert(self.name, [operation], True, True,
write_concern, False, uuid_representation), True)
write_concern, False, self.collection.codec_options), True)
def execute_legacy(self, generator, write_concern):
"""Execute using legacy wire protocol ops.

View File

@ -470,7 +470,6 @@ class Collection(common.BaseObject):
.. mongodoc:: insert
"""
client = self.database.connection
uuid_representation = self.codec_options.uuid_representation
docs = doc_or_docs
return_one = False
if isinstance(docs, collections.MutableMapping):
@ -511,14 +510,14 @@ class Collection(common.BaseObject):
command['writeConcern'] = concern
results = message._do_batched_write_command(
self.database.name + ".$cmd", _INSERT, command,
gen(), check_keys, uuid_representation, client)
self.database.name + ".$cmd", _INSERT, command,
gen(), check_keys, self.codec_options, client)
_check_write_command_response(results)
else:
# Legacy batched OP_INSERT
message._do_batched_insert(self.__full_name, gen(), check_keys,
safe, concern, continue_on_error,
uuid_representation, client)
self.codec_options, client)
if return_one:
return ids[0]
@ -641,7 +640,6 @@ class Collection(common.BaseObject):
check_keys = False
client = self.database.connection
uuid_representation = self.codec_options.uuid_representation
if client._writable_max_wire_version() > 1 and safe:
# Update command
command = SON([('update', self.name)])
@ -653,7 +651,7 @@ class Collection(common.BaseObject):
results = message._do_batched_write_command(
self.database.name + '.$cmd', _UPDATE, command,
docs, check_keys, uuid_representation, client)
docs, check_keys, self.codec_options, client)
_check_write_command_response(results)
_, result = results[0]
@ -674,7 +672,7 @@ class Collection(common.BaseObject):
return client._send_message(
message.update(self.__full_name, upsert, multi,
spec, document, safe, concern,
check_keys, uuid_representation), safe)
check_keys, self.codec_options), safe)
def drop(self):
"""Alias for :meth:`~pymongo.database.Database.drop_collection`.
@ -754,7 +752,6 @@ class Collection(common.BaseObject):
safe = concern.get("w") != 0
client = self.database.connection
uuid_representation = self.codec_options.uuid_representation
if client._writable_max_wire_version() > 1 and safe:
# Delete command
command = SON([('delete', self.name)])
@ -765,7 +762,7 @@ class Collection(common.BaseObject):
results = message._do_batched_write_command(
self.database.name + '.$cmd', _DELETE, command,
docs, False, uuid_representation, client)
docs, False, self.codec_options, client)
_check_write_command_response(results)
_, result = results[0]
@ -775,7 +772,7 @@ class Collection(common.BaseObject):
# Legacy OP_DELETE
return client._send_message(
message.delete(self.__full_name, spec_or_id, safe,
concern, uuid_representation,
concern, self.codec_options,
int(not multi)), safe)
def find_one(self, filter=None, *args, **kwargs):

View File

@ -912,7 +912,7 @@ class Cursor(object):
self.__collection.full_name,
self.__skip, ntoreturn,
self.__query_spec(), self.__projection,
self.__codec_options.uuid_representation))
self.__codec_options))
if not self.__id:
self.__killed = True
elif self.__id: # Get More

View File

@ -115,10 +115,7 @@ def _unpack_response(response, cursor_id=None, codec_options=CodecOptions()):
result["cursor_id"] = struct.unpack("<q", response[4:12])[0]
result["starting_from"] = struct.unpack("<i", response[12:16])[0]
result["number_returned"] = struct.unpack("<i", response[16:20])[0]
result["data"] = bson.decode_all(response[20:],
codec_options.as_class,
codec_options.tz_aware,
codec_options.uuid_representation)
result["data"] = bson.decode_all(response[20:], codec_options)
assert len(result["data"]) == result["number_returned"]
return result
@ -192,7 +189,7 @@ def _command(client, namespace, command, read_preference,
query_opts = 4
msg = query(query_opts, namespace, 0, -1,
command, None, codec_options.uuid_representation)
command, None, codec_options)
response = client._send_message_with_response(msg, read_preference)
result = _unpack_response(response.data, None, codec_options)['data'][0]
if check:

View File

@ -24,7 +24,7 @@ import random
import struct
import bson
from bson.binary import OLD_UUID_SUBTYPE
from bson.codec_options import DEFAULT_CODEC_OPTIONS
from bson.py3compat import b, StringIO
from bson.son import SON
try:
@ -62,7 +62,8 @@ def __last_error(namespace, args):
cmd = SON([("getlasterror", 1)])
cmd.update(args)
splitns = namespace.split('.', 1)
return query(0, splitns[0] + '.$cmd', 0, -1, cmd)
return query(0, splitns[0] + '.$cmd', 0, -1, cmd,
None, DEFAULT_CODEC_OPTIONS)
def __pack_message(operation, data):
@ -79,7 +80,7 @@ def __pack_message(operation, data):
def insert(collection_name, docs, check_keys,
safe, last_error_args, continue_on_error, uuid_subtype):
safe, last_error_args, continue_on_error, opts):
"""Get an **insert** message.
Used by the Bulk API to insert into pre-2.6 servers. Collection.insert
@ -90,7 +91,7 @@ def insert(collection_name, docs, check_keys,
options += 1
data = struct.pack("<i", options)
data += bson._make_c_string(collection_name)
encoded = [bson.BSON.encode(doc, check_keys, uuid_subtype) for doc in docs]
encoded = [bson.BSON.encode(doc, check_keys, opts) for doc in docs]
if not encoded:
raise InvalidOperation("cannot do an empty bulk insert")
max_bson_size = max(map(len, encoded))
@ -108,7 +109,7 @@ if _use_c:
def update(collection_name, upsert, multi,
spec, doc, safe, last_error_args, check_keys, uuid_subtype):
spec, doc, safe, last_error_args, check_keys, opts):
"""Get an **update** message.
"""
options = 0
@ -120,8 +121,8 @@ def update(collection_name, upsert, multi,
data = _ZERO_32
data += bson._make_c_string(collection_name)
data += struct.pack("<i", options)
data += bson.BSON.encode(spec, False, uuid_subtype)
encoded = bson.BSON.encode(doc, check_keys, uuid_subtype)
data += bson.BSON.encode(spec, False, opts)
encoded = bson.BSON.encode(doc, check_keys, opts)
data += encoded
if safe:
(_, update_message) = __pack_message(2001, data)
@ -136,19 +137,18 @@ if _use_c:
def query(options, collection_name, num_to_skip,
num_to_return, query, field_selector=None,
uuid_subtype=OLD_UUID_SUBTYPE):
num_to_return, query, field_selector, opts):
"""Get a **query** message.
"""
data = struct.pack("<I", options)
data += bson._make_c_string(collection_name)
data += struct.pack("<i", num_to_skip)
data += struct.pack("<i", num_to_return)
encoded = bson.BSON.encode(query, False, uuid_subtype)
encoded = bson.BSON.encode(query, False, opts)
data += encoded
max_bson_size = len(encoded)
if field_selector is not None:
encoded = bson.BSON.encode(field_selector, False, uuid_subtype)
encoded = bson.BSON.encode(field_selector, False, opts)
data += encoded
max_bson_size = max(len(encoded), max_bson_size)
(request_id, query_message) = __pack_message(2004, data)
@ -170,13 +170,18 @@ if _use_c:
def delete(collection_name, spec, safe,
last_error_args, uuid_subtype, options=0):
last_error_args, opts, flags=0):
"""Get a **delete** message.
`opts` is a CodecOptions. `flags` is a bit vector that may contain
the SingleRemove flag or not:
http://docs.mongodb.org/meta-driver/latest/legacy/mongodb-wire-protocol/#op-delete
"""
data = _ZERO_32
data += bson._make_c_string(collection_name)
data += struct.pack("<I", options)
encoded = bson.BSON.encode(spec, False, uuid_subtype)
data += struct.pack("<I", flags)
encoded = bson.BSON.encode(spec, False, opts)
data += encoded
if safe:
(_, remove_message) = __pack_message(2006, data)
@ -199,7 +204,7 @@ def kill_cursors(cursor_ids):
def _do_batched_insert(collection_name, docs, check_keys,
safe, last_error_args, continue_on_error, uuid_subtype, client):
safe, last_error_args, continue_on_error, opts, client):
"""Insert `docs` using multiple batches.
"""
def _insert_message(insert_message, send_safe):
@ -220,7 +225,7 @@ def _do_batched_insert(collection_name, docs, check_keys,
message_length = begin_loc = data.tell()
has_docs = False
for doc in docs:
encoded = bson.BSON.encode(doc, check_keys, uuid_subtype)
encoded = bson.BSON.encode(doc, check_keys, opts)
encoded_length = len(encoded)
too_large = (encoded_length > client.max_bson_size)
@ -274,7 +279,7 @@ if _use_c:
def _do_batched_write_command(namespace, operation, command,
docs, check_keys, uuid_subtype, client):
docs, check_keys, opts, client):
"""Execute a batch of insert, update, or delete commands.
"""
max_bson_size = client.max_bson_size
@ -350,7 +355,7 @@ def _do_batched_write_command(namespace, operation, command,
has_docs = True
# Encode the current operation
key = b(str(idx))
value = bson.BSON.encode(doc, check_keys, uuid_subtype)
value = bson.BSON.encode(doc, check_keys, opts)
# Send a batch?
enough_data = (buf.tell() + len(key) + len(value) + 2) >= max_cmd_size
enough_documents = (idx >= max_write_batch_size)

View File

@ -16,6 +16,7 @@
import weakref
from bson.codec_options import DEFAULT_CODEC_OPTIONS
from pymongo import common, helpers, message, periodic_executor
from pymongo.server_type import SERVER_TYPE
from pymongo.ismaster import IsMaster
@ -154,7 +155,8 @@ class Monitor(object):
"""
start = _time()
request_id, msg, _ = message.query(
0, 'admin.$cmd', 0, -1, {'ismaster': 1})
0, 'admin.$cmd', 0, -1, {'ismaster': 1},
None, DEFAULT_CODEC_OPTIONS)
sock_info.send_message(msg)
raw_response = sock_info.receive_message(1, request_id)

View File

@ -17,6 +17,7 @@
import select
import struct
from bson.codec_options import DEFAULT_CODEC_OPTIONS
from pymongo import helpers, message
from pymongo.errors import AutoReconnect
@ -32,7 +33,8 @@ def command(sock, dbname, spec):
- `spec`: a command document as a dict, SON, or mapping object
"""
ns = dbname + '.$cmd'
request_id, msg, _ = message.query(0, ns, 0, -1, spec)
request_id, msg, _ = message.query(0, ns, 0, -1, spec,
None, DEFAULT_CODEC_OPTIONS)
sock.sendall(msg)
response = receive_message(sock, 1, request_id)
unpacked = helpers._unpack_response(response)['data'][0]

View File

@ -132,44 +132,55 @@ class TestBinary(unittest.TestCase):
def test_legacy_java_uuid(self):
# Test decoding
data = self.java_data
docs = bson.decode_all(data, SON, False, PYTHON_LEGACY)
docs = bson.decode_all(data, CodecOptions(SON, False, PYTHON_LEGACY))
for d in docs:
self.assertNotEqual(d['newguid'], uuid.UUID(d['newguidstring']))
docs = bson.decode_all(data, SON, False, STANDARD)
docs = bson.decode_all(data, CodecOptions(SON, False, STANDARD))
for d in docs:
self.assertNotEqual(d['newguid'], uuid.UUID(d['newguidstring']))
docs = bson.decode_all(data, SON, False, CSHARP_LEGACY)
docs = bson.decode_all(data, CodecOptions(SON, False, CSHARP_LEGACY))
for d in docs:
self.assertNotEqual(d['newguid'], uuid.UUID(d['newguidstring']))
docs = bson.decode_all(data, SON, False, JAVA_LEGACY)
docs = bson.decode_all(data, CodecOptions(SON, False, JAVA_LEGACY))
for d in docs:
self.assertEqual(d['newguid'], uuid.UUID(d['newguidstring']))
# Test encoding
encoded = b''.join([bson.BSON.encode(doc,
uuid_subtype=PYTHON_LEGACY)
for doc in docs])
encoded = b''.join([
bson.BSON.encode(doc,
False,
CodecOptions(uuid_representation=PYTHON_LEGACY))
for doc in docs])
self.assertNotEqual(data, encoded)
encoded = b''.join([bson.BSON.encode(doc, uuid_subtype=STANDARD)
for doc in docs])
encoded = b''.join(
[bson.BSON.encode(doc,
False,
CodecOptions(uuid_representation=STANDARD))
for doc in docs])
self.assertNotEqual(data, encoded)
encoded = b''.join([bson.BSON.encode(doc, uuid_subtype=CSHARP_LEGACY)
for doc in docs])
encoded = b''.join(
[bson.BSON.encode(doc,
False,
CodecOptions(uuid_representation=CSHARP_LEGACY))
for doc in docs])
self.assertNotEqual(data, encoded)
encoded = b''.join([bson.BSON.encode(doc, uuid_subtype=JAVA_LEGACY)
for doc in docs])
encoded = b''.join(
[bson.BSON.encode(doc,
False,
CodecOptions(uuid_representation=JAVA_LEGACY))
for doc in docs])
self.assertEqual(data, encoded)
@client_context.require_connection
def test_legacy_java_uuid_roundtrip(self):
data = self.java_data
docs = bson.decode_all(data, SON, False, JAVA_LEGACY)
docs = bson.decode_all(data, CodecOptions(SON, False, JAVA_LEGACY))
client_context.client.pymongo_test.drop_collection('java_uuid')
db = client_context.client.pymongo_test
@ -191,44 +202,55 @@ class TestBinary(unittest.TestCase):
data = self.csharp_data
# Test decoding
docs = bson.decode_all(data, SON, False, PYTHON_LEGACY)
docs = bson.decode_all(data, CodecOptions(SON, False, PYTHON_LEGACY))
for d in docs:
self.assertNotEqual(d['newguid'], uuid.UUID(d['newguidstring']))
docs = bson.decode_all(data, SON, False, STANDARD)
docs = bson.decode_all(data, CodecOptions(SON, False, STANDARD))
for d in docs:
self.assertNotEqual(d['newguid'], uuid.UUID(d['newguidstring']))
docs = bson.decode_all(data, SON, False, JAVA_LEGACY)
docs = bson.decode_all(data, CodecOptions(SON, False, JAVA_LEGACY))
for d in docs:
self.assertNotEqual(d['newguid'], uuid.UUID(d['newguidstring']))
docs = bson.decode_all(data, SON, False, CSHARP_LEGACY)
docs = bson.decode_all(data, CodecOptions(SON, False, CSHARP_LEGACY))
for d in docs:
self.assertEqual(d['newguid'], uuid.UUID(d['newguidstring']))
# Test encoding
encoded = b''.join([bson.BSON.encode(doc,
uuid_subtype=PYTHON_LEGACY)
for doc in docs])
encoded = b''.join([
bson.BSON.encode(doc,
False,
CodecOptions(uuid_representation=PYTHON_LEGACY))
for doc in docs])
self.assertNotEqual(data, encoded)
encoded = b''.join([bson.BSON.encode(doc, uuid_subtype=STANDARD)
for doc in docs])
encoded = b''.join([
bson.BSON.encode(doc,
False,
CodecOptions(uuid_representation=STANDARD))
for doc in docs])
self.assertNotEqual(data, encoded)
encoded = b''.join([bson.BSON.encode(doc, uuid_subtype=JAVA_LEGACY)
for doc in docs])
encoded = b''.join(
[bson.BSON.encode(doc,
False,
CodecOptions(uuid_representation=JAVA_LEGACY))
for doc in docs])
self.assertNotEqual(data, encoded)
encoded = b''.join([bson.BSON.encode(doc, uuid_subtype=CSHARP_LEGACY)
for doc in docs])
encoded = b''.join(
[bson.BSON.encode(doc,
False,
CodecOptions(uuid_representation=CSHARP_LEGACY))
for doc in docs])
self.assertEqual(data, encoded)
@client_context.require_connection
def test_legacy_csharp_uuid_roundtrip(self):
data = self.csharp_data
docs = bson.decode_all(data, SON, False, CSHARP_LEGACY)
docs = bson.decode_all(data, CodecOptions(SON, False, CSHARP_LEGACY))
client_context.client.pymongo_test.drop_collection('csharp_uuid')
db = client_context.client.pymongo_test

View File

@ -33,6 +33,7 @@ from bson import (BSON,
Regex)
from bson.binary import Binary, UUIDLegacy
from bson.code import Code
from bson.codec_options import CodecOptions
from bson.int64 import Int64
from bson.objectid import ObjectId
from bson.dbref import DBRef
@ -130,7 +131,8 @@ class TestBSON(unittest.TestCase):
helper({"$field": Code("return function(){ return x; }", scope={'x': False})})
def encode_then_decode(doc):
return doc_class(doc) == BSON.encode(doc).decode(as_class=doc_class)
return doc_class(doc) == BSON.encode(doc).decode(
CodecOptions(as_class=doc_class))
qcheck.check_unittest(self, encode_then_decode,
qcheck.gen_mongo_dict(3))
@ -425,7 +427,8 @@ class TestBSON(unittest.TestCase):
as_utc = (aware - aware.utcoffset()).replace(tzinfo=utc)
self.assertEqual(datetime.datetime(1993, 4, 3, 16, 45, tzinfo=utc),
as_utc)
after = BSON.encode({"date": aware}).decode(tz_aware=True)["date"]
after = BSON.encode({"date": aware}).decode(
CodecOptions(tz_aware=True))["date"]
self.assertEqual(utc, after.tzinfo)
self.assertEqual(as_utc, after)
@ -584,14 +587,19 @@ class TestBSON(unittest.TestCase):
raise
def test_custom_class(self):
self.assertTrue(isinstance(BSON.encode({}).decode(), dict))
self.assertFalse(isinstance(BSON.encode({}).decode(), SON))
self.assertTrue(isinstance(BSON.encode({}).decode(SON), SON))
self.assertIsInstance(BSON.encode({}).decode(), dict)
self.assertNotIsInstance(BSON.encode({}).decode(), SON)
self.assertIsInstance(
BSON.encode({}).decode(CodecOptions(as_class=SON)),
SON)
self.assertEqual(1, BSON.encode({"x": 1}).decode(SON)["x"])
self.assertEqual(
1,
BSON.encode({"x": 1}).decode(CodecOptions(as_class=SON))["x"])
x = BSON.encode({"x": [{"y": 1}]})
self.assertTrue(isinstance(x.decode(SON)["x"][0], SON))
self.assertIsInstance(x.decode(CodecOptions(as_class=SON))["x"][0],
SON)
def test_subclasses(self):
# make sure we can serialize subclasses of native Python types.
@ -620,7 +628,9 @@ class TestBSON(unittest.TestCase):
except ImportError:
raise SkipTest("No OrderedDict")
d = OrderedDict([("one", 1), ("two", 2), ("three", 3), ("four", 4)])
self.assertEqual(d, BSON.encode(d).decode(as_class=OrderedDict))
self.assertEqual(
d,
BSON.encode(d).decode(CodecOptions(as_class=OrderedDict)))
def test_bson_regex(self):
# Invalid Python regex, though valid PCRE.
@ -752,5 +762,41 @@ class TestBSON(unittest.TestCase):
{"_id": {'$oid': "52d0b971b3ba219fdeb4170e"}}, True)
BSON.encode({"_id": {'$oid': "52d0b971b3ba219fdeb4170e"}})
class TestCodecOptions(unittest.TestCase):
def test_as_class(self):
self.assertRaises(TypeError, CodecOptions, as_class=object)
self.assertIs(SON, CodecOptions(as_class=SON).as_class)
def test_tz_aware(self):
self.assertRaises(TypeError, CodecOptions, tz_aware=1)
self.assertFalse(CodecOptions().tz_aware)
self.assertTrue(CodecOptions(tz_aware=True).tz_aware)
def test_uuid_representation(self):
self.assertRaises(ValueError, CodecOptions, uuid_representation=None)
self.assertRaises(ValueError, CodecOptions, uuid_representation=7)
self.assertRaises(ValueError, CodecOptions, uuid_representation=2)
def test_codec_options_repr(self):
r = ('CodecOptions(as_class=dict, tz_aware=False, '
'uuid_representation=PYTHON_LEGACY)')
self.assertEqual(r, repr(CodecOptions()))
def test_decode_all_defaults(self):
# Test decode_all()'s default as_class is dict and tz_aware is False.
# The default uuid_representation is PYTHON_LEGACY but this decodes
# same as STANDARD, so all this test proves about UUID decoding is
# that it's not CSHARP_LEGACY or JAVA_LEGACY.
doc = {'sub_document': {},
'uuid': uuid.uuid4(),
'dt': datetime.datetime.utcnow()}
decoded = bson.decode_all(bson.BSON.encode(doc))[0]
self.assertIsInstance(decoded['sub_document'], dict)
self.assertEqual(decoded['uuid'], doc['uuid'])
self.assertIsNone(decoded['dt'].tzinfo)
if __name__ == "__main__":
unittest.main()

View File

@ -41,9 +41,6 @@ class TestCommon(IntegrationTest):
coll = self.db.uuid
coll.drop()
self.assertRaises(ValueError, CodecOptions, uuid_representation=7)
self.assertRaises(ValueError, CodecOptions, uuid_representation=2)
# Test property
self.assertEqual(PYTHON_LEGACY,
coll.codec_options.uuid_representation)