You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
8.4 KiB
260 lines
8.4 KiB
# Copyright 2011-present 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. |
|
|
|
|
|
# Modifications copyright (C) 2018 davidlatwe |
|
# |
|
# Assembling crucial classes and functions form pymongo module, |
|
# some of them may modified by needs. |
|
|
|
|
|
from collections import ( |
|
OrderedDict |
|
) |
|
from collections.abc import MutableMapping |
|
from .types import ( |
|
abc, |
|
iteritems, |
|
integer_types, |
|
string_types, |
|
bson, |
|
) |
|
|
|
ASCENDING = 1 |
|
DESCENDING = -1 |
|
|
|
|
|
def validate_is_document_type(option, value): |
|
"""Validate the type of method arguments that expect a MongoDB document.""" |
|
if not isinstance(value, (MutableMapping, bson.RawBSONDocument)): |
|
raise TypeError("%s must be an instance of dict, bson.son.SON, " |
|
"bson.raw_bson.RawBSONDocument, or " |
|
"a type that inherits from " |
|
"collections.MutableMapping" % (option,)) |
|
|
|
|
|
def validate_boolean(option, value): |
|
"""Validates that 'value' is True or False.""" |
|
if isinstance(value, bool): |
|
return value |
|
raise TypeError("%s must be True or False" % (option,)) |
|
|
|
|
|
def validate_list(option, value): |
|
"""Validates that 'value' is a list.""" |
|
if not isinstance(value, list): |
|
raise TypeError("%s must be a list" % (option,)) |
|
return value |
|
|
|
|
|
def validate_list_or_none(option, value): |
|
"""Validates that 'value' is a list or None.""" |
|
if value is None: |
|
return value |
|
return validate_list(option, value) |
|
|
|
|
|
def validate_is_mapping(option, value): |
|
"""Validate the type of method arguments that expect a document.""" |
|
if not isinstance(value, abc.Mapping): |
|
raise TypeError("%s must be an instance of dict, bson.son.SON, or " |
|
"other type that inherits from " |
|
"collections.Mapping" % (option,)) |
|
|
|
|
|
def validate_ok_for_update(update): |
|
"""Validate an update document.""" |
|
validate_is_mapping("update", update) |
|
# Update can not be {} |
|
if not update: |
|
raise ValueError('update only works with $ operators') |
|
first = next(iter(update)) |
|
if not first.startswith('$'): |
|
raise ValueError('update only works with $ operators') |
|
|
|
|
|
def validate_ok_for_replace(replacement): |
|
"""Validate a replacement document.""" |
|
validate_is_mapping("replacement", replacement) |
|
# Replacement can be {} |
|
if replacement and not isinstance(replacement, bson.RawBSONDocument): |
|
first = next(iter(replacement)) |
|
if first.startswith('$'): |
|
raise ValueError('replacement can not include $ operators') |
|
|
|
|
|
def _fields_list_to_dict(fields, option_name): |
|
"""Takes a sequence of field names and returns a matching dictionary. |
|
["a", "b"] becomes {"a": 1, "b": 1} |
|
and |
|
["a.b.c", "d", "a.c"] becomes {"a.b.c": 1, "d": 1, "a.c": 1} |
|
""" |
|
if isinstance(fields, abc.Mapping): |
|
return fields |
|
|
|
if isinstance(fields, (abc.Sequence, abc.Set)): |
|
if not all(isinstance(field, string_types) for field in fields): |
|
raise TypeError("%s must be a list of key names, each an " |
|
"instance of %s" % (option_name, |
|
string_types.__name__)) |
|
return dict.fromkeys(fields, 1) |
|
|
|
raise TypeError("%s must be a mapping or " |
|
"list of key names" % (option_name,)) |
|
|
|
|
|
def _index_list(key_or_list, direction=None): |
|
"""Helper to generate a list of (key, direction) pairs. |
|
|
|
Takes such a list, or a single key, or a single key and direction. |
|
""" |
|
if direction is not None: |
|
return [(key_or_list, direction)] |
|
else: |
|
if isinstance(key_or_list, string_types): |
|
return [(key_or_list, ASCENDING)] |
|
elif not isinstance(key_or_list, (list, tuple)): |
|
raise TypeError("if no direction is specified, " |
|
"key_or_list must be an instance of list") |
|
return key_or_list |
|
|
|
|
|
def _index_document(index_list): |
|
"""Helper to generate an index specifying document. |
|
|
|
Takes a list of (key, direction) pairs. |
|
""" |
|
if isinstance(index_list, abc.Mapping): |
|
raise TypeError("passing a dict to sort/create_index/hint is not " |
|
"allowed - use a list of tuples instead. did you " |
|
"mean %r?" % list(iteritems(index_list))) |
|
elif not isinstance(index_list, (list, tuple)): |
|
raise TypeError("must use a list of (key, direction) pairs, " |
|
"not: " + repr(index_list)) |
|
if not len(index_list): |
|
raise ValueError("key_or_list must not be the empty list") |
|
|
|
index = OrderedDict() |
|
for (key, value) in index_list: |
|
if not isinstance(key, string_types): |
|
raise TypeError("first item in each key pair must be a string") |
|
if not isinstance(value, (string_types, int, abc.Mapping)): |
|
raise TypeError("second item in each key pair must be 1, -1, " |
|
"'2d', 'geoHaystack', or another valid MongoDB " |
|
"index specifier.") |
|
index[key] = value |
|
return index |
|
|
|
|
|
class WriteConcern(object): |
|
"""MontyWriteConcern |
|
""" |
|
|
|
__slots__ = ("_document") |
|
|
|
def __init__(self, wtimeout=None): |
|
self._document = {} |
|
|
|
if wtimeout is not None: |
|
if not isinstance(wtimeout, integer_types): |
|
raise TypeError("wtimeout must be an integer") |
|
self._document["wtimeout"] = wtimeout |
|
|
|
@property |
|
def document(self): |
|
"""The document representation of this write concern. |
|
""" |
|
return self._document.copy() |
|
|
|
def __repr__(self): |
|
return ("MontyWriteConcern({})".format( |
|
", ".join("%s=%s" % kvt for kvt in self.document.items()),)) |
|
|
|
def __eq__(self, other): |
|
return self.document == other.document |
|
|
|
def __ne__(self, other): |
|
return self.document != other.document |
|
|
|
def __bool__(self): |
|
return bool(self.document) |
|
|
|
|
|
def _parse_write_concern(options): |
|
"""Parse write concern options.""" |
|
wtimeout = options.get('wtimeout') |
|
return WriteConcern(wtimeout) |
|
|
|
|
|
class ClientOptions(object): |
|
"""ClientOptions""" |
|
|
|
def __init__(self, options, storage_wconcern=None): |
|
self.__options = options |
|
self.__codec_options = bson.parse_codec_options(options) |
|
|
|
if storage_wconcern is not None: |
|
self.__write_concern = storage_wconcern |
|
else: |
|
self.__write_concern = _parse_write_concern(options) |
|
|
|
@property |
|
def _options(self): |
|
"""The original options used to create this ClientOptions.""" |
|
return self.__options # pragma: no cover |
|
|
|
@property |
|
def codec_options(self): |
|
"""A :class:`~bson.codec_options.CodecOptions` instance.""" |
|
return self.__codec_options |
|
|
|
@property |
|
def write_concern(self): |
|
"""A :class:`~montydb.base.WriteConcern` instance.""" |
|
return self.__write_concern |
|
|
|
|
|
class BaseObject(object): |
|
"""A base class that provides attributes and methods common |
|
to multiple montydb classes. |
|
|
|
SHOULD NOT BE USED BY DEVELOPERS EXTERNAL TO MONTYDB. |
|
""" |
|
|
|
def __init__(self, codec_options, write_concern): |
|
|
|
if not isinstance(codec_options, bson.CodecOptions): |
|
raise TypeError("codec_options must be an " # pragma: no cover |
|
"instance of bson.codec_options.CodecOptions") |
|
self.__codec_options = codec_options |
|
|
|
if not isinstance(write_concern, WriteConcern): |
|
raise TypeError(f"write_concern must be an " # pragma: no cover |
|
f"instance of montydb.base.WriteConcern. " |
|
f"Got {type(write_concern)}") |
|
self.__write_concern = write_concern |
|
|
|
@property |
|
def codec_options(self): |
|
"""Read only access to the :class:`~bson.codec_options.CodecOptions` |
|
of this instance. |
|
""" |
|
return self.__codec_options |
|
|
|
@property |
|
def write_concern(self): |
|
"""Read only access to the :class:`~montydb.base.WriteConcern` |
|
of this instance. |
|
""" |
|
return self.__write_concern
|
|
|