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.
573 lines
20 KiB
573 lines
20 KiB
# Copyright 2015-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. |
|
|
|
"""Operation class definitions.""" |
|
from __future__ import annotations |
|
|
|
from typing import ( |
|
TYPE_CHECKING, |
|
Any, |
|
Dict, |
|
Generic, |
|
List, |
|
Mapping, |
|
Optional, |
|
Sequence, |
|
Tuple, |
|
Union, |
|
) |
|
|
|
from bson.raw_bson import RawBSONDocument |
|
from pymongo import helpers |
|
from pymongo.collation import validate_collation_or_none |
|
from pymongo.common import validate_boolean, validate_is_mapping, validate_list |
|
from pymongo.helpers import _gen_index_name, _index_document, _index_list |
|
from pymongo.typings import _CollationIn, _DocumentType, _Pipeline |
|
|
|
if TYPE_CHECKING: |
|
from bson.son import SON |
|
from pymongo.bulk import _Bulk |
|
|
|
# Hint supports index name, "myIndex", or list of either strings or index pairs: [('x', 1), ('y', -1), 'z''] |
|
_IndexList = Sequence[Union[str, Tuple[str, Union[int, str, Mapping[str, Any]]]]] |
|
_IndexKeyHint = Union[str, _IndexList] |
|
|
|
|
|
class InsertOne(Generic[_DocumentType]): |
|
"""Represents an insert_one operation.""" |
|
|
|
__slots__ = ("_doc",) |
|
|
|
def __init__(self, document: _DocumentType) -> None: |
|
"""Create an InsertOne instance. |
|
|
|
For use with :meth:`~pymongo.collection.Collection.bulk_write`. |
|
|
|
:Parameters: |
|
- `document`: The document to insert. If the document is missing an |
|
_id field one will be added. |
|
""" |
|
self._doc = document |
|
|
|
def _add_to_bulk(self, bulkobj: _Bulk) -> None: |
|
"""Add this operation to the _Bulk instance `bulkobj`.""" |
|
bulkobj.add_insert(self._doc) # type: ignore[arg-type] |
|
|
|
def __repr__(self) -> str: |
|
return f"InsertOne({self._doc!r})" |
|
|
|
def __eq__(self, other: Any) -> bool: |
|
if type(other) == type(self): |
|
return other._doc == self._doc |
|
return NotImplemented |
|
|
|
def __ne__(self, other: Any) -> bool: |
|
return not self == other |
|
|
|
|
|
class DeleteOne: |
|
"""Represents a delete_one operation.""" |
|
|
|
__slots__ = ("_filter", "_collation", "_hint") |
|
|
|
def __init__( |
|
self, |
|
filter: Mapping[str, Any], |
|
collation: Optional[_CollationIn] = None, |
|
hint: Optional[_IndexKeyHint] = None, |
|
) -> None: |
|
"""Create a DeleteOne instance. |
|
|
|
For use with :meth:`~pymongo.collection.Collection.bulk_write`. |
|
|
|
:Parameters: |
|
- `filter`: A query that matches the document to delete. |
|
- `collation` (optional): An instance of |
|
:class:`~pymongo.collation.Collation`. |
|
- `hint` (optional): An index to use to support the query |
|
predicate specified either by its string name, or in the same |
|
format as passed to |
|
:meth:`~pymongo.collection.Collection.create_index` (e.g. |
|
``[('field', ASCENDING)]``). This option is only supported on |
|
MongoDB 4.4 and above. |
|
|
|
.. versionchanged:: 3.11 |
|
Added the ``hint`` option. |
|
.. versionchanged:: 3.5 |
|
Added the `collation` option. |
|
""" |
|
if filter is not None: |
|
validate_is_mapping("filter", filter) |
|
if hint is not None and not isinstance(hint, str): |
|
self._hint: Union[str, SON[str, Any], None] = helpers._index_document(hint) |
|
else: |
|
self._hint = hint |
|
self._filter = filter |
|
self._collation = collation |
|
|
|
def _add_to_bulk(self, bulkobj: _Bulk) -> None: |
|
"""Add this operation to the _Bulk instance `bulkobj`.""" |
|
bulkobj.add_delete( |
|
self._filter, |
|
1, |
|
collation=validate_collation_or_none(self._collation), |
|
hint=self._hint, |
|
) |
|
|
|
def __repr__(self) -> str: |
|
return f"DeleteOne({self._filter!r}, {self._collation!r})" |
|
|
|
def __eq__(self, other: Any) -> bool: |
|
if type(other) == type(self): |
|
return (other._filter, other._collation) == (self._filter, self._collation) |
|
return NotImplemented |
|
|
|
def __ne__(self, other: Any) -> bool: |
|
return not self == other |
|
|
|
|
|
class DeleteMany: |
|
"""Represents a delete_many operation.""" |
|
|
|
__slots__ = ("_filter", "_collation", "_hint") |
|
|
|
def __init__( |
|
self, |
|
filter: Mapping[str, Any], |
|
collation: Optional[_CollationIn] = None, |
|
hint: Optional[_IndexKeyHint] = None, |
|
) -> None: |
|
"""Create a DeleteMany instance. |
|
|
|
For use with :meth:`~pymongo.collection.Collection.bulk_write`. |
|
|
|
:Parameters: |
|
- `filter`: A query that matches the documents to delete. |
|
- `collation` (optional): An instance of |
|
:class:`~pymongo.collation.Collation`. |
|
- `hint` (optional): An index to use to support the query |
|
predicate specified either by its string name, or in the same |
|
format as passed to |
|
:meth:`~pymongo.collection.Collection.create_index` (e.g. |
|
``[('field', ASCENDING)]``). This option is only supported on |
|
MongoDB 4.4 and above. |
|
|
|
.. versionchanged:: 3.11 |
|
Added the ``hint`` option. |
|
.. versionchanged:: 3.5 |
|
Added the `collation` option. |
|
""" |
|
if filter is not None: |
|
validate_is_mapping("filter", filter) |
|
if hint is not None and not isinstance(hint, str): |
|
self._hint: Union[str, SON[str, Any], None] = helpers._index_document(hint) |
|
else: |
|
self._hint = hint |
|
self._filter = filter |
|
self._collation = collation |
|
|
|
def _add_to_bulk(self, bulkobj: _Bulk) -> None: |
|
"""Add this operation to the _Bulk instance `bulkobj`.""" |
|
bulkobj.add_delete( |
|
self._filter, |
|
0, |
|
collation=validate_collation_or_none(self._collation), |
|
hint=self._hint, |
|
) |
|
|
|
def __repr__(self) -> str: |
|
return f"DeleteMany({self._filter!r}, {self._collation!r})" |
|
|
|
def __eq__(self, other: Any) -> bool: |
|
if type(other) == type(self): |
|
return (other._filter, other._collation) == (self._filter, self._collation) |
|
return NotImplemented |
|
|
|
def __ne__(self, other: Any) -> bool: |
|
return not self == other |
|
|
|
|
|
class ReplaceOne(Generic[_DocumentType]): |
|
"""Represents a replace_one operation.""" |
|
|
|
__slots__ = ("_filter", "_doc", "_upsert", "_collation", "_hint") |
|
|
|
def __init__( |
|
self, |
|
filter: Mapping[str, Any], |
|
replacement: Union[_DocumentType, RawBSONDocument], |
|
upsert: bool = False, |
|
collation: Optional[_CollationIn] = None, |
|
hint: Optional[_IndexKeyHint] = None, |
|
) -> None: |
|
"""Create a ReplaceOne instance. |
|
|
|
For use with :meth:`~pymongo.collection.Collection.bulk_write`. |
|
|
|
:Parameters: |
|
- `filter`: A query that matches the document to replace. |
|
- `replacement`: The new document. |
|
- `upsert` (optional): If ``True``, perform an insert if no documents |
|
match the filter. |
|
- `collation` (optional): An instance of |
|
:class:`~pymongo.collation.Collation`. |
|
- `hint` (optional): An index to use to support the query |
|
predicate specified either by its string name, or in the same |
|
format as passed to |
|
:meth:`~pymongo.collection.Collection.create_index` (e.g. |
|
``[('field', ASCENDING)]``). This option is only supported on |
|
MongoDB 4.2 and above. |
|
|
|
.. versionchanged:: 3.11 |
|
Added the ``hint`` option. |
|
.. versionchanged:: 3.5 |
|
Added the ``collation`` option. |
|
""" |
|
if filter is not None: |
|
validate_is_mapping("filter", filter) |
|
if upsert is not None: |
|
validate_boolean("upsert", upsert) |
|
if hint is not None and not isinstance(hint, str): |
|
self._hint: Union[str, SON[str, Any], None] = helpers._index_document(hint) |
|
else: |
|
self._hint = hint |
|
self._filter = filter |
|
self._doc = replacement |
|
self._upsert = upsert |
|
self._collation = collation |
|
|
|
def _add_to_bulk(self, bulkobj: _Bulk) -> None: |
|
"""Add this operation to the _Bulk instance `bulkobj`.""" |
|
bulkobj.add_replace( |
|
self._filter, |
|
self._doc, |
|
self._upsert, |
|
collation=validate_collation_or_none(self._collation), |
|
hint=self._hint, |
|
) |
|
|
|
def __eq__(self, other: Any) -> bool: |
|
if type(other) == type(self): |
|
return (other._filter, other._doc, other._upsert, other._collation, other._hint,) == ( |
|
self._filter, |
|
self._doc, |
|
self._upsert, |
|
self._collation, |
|
other._hint, |
|
) |
|
return NotImplemented |
|
|
|
def __ne__(self, other: Any) -> bool: |
|
return not self == other |
|
|
|
def __repr__(self) -> str: |
|
return "{}({!r}, {!r}, {!r}, {!r}, {!r})".format( |
|
self.__class__.__name__, |
|
self._filter, |
|
self._doc, |
|
self._upsert, |
|
self._collation, |
|
self._hint, |
|
) |
|
|
|
|
|
class _UpdateOp: |
|
"""Private base class for update operations.""" |
|
|
|
__slots__ = ("_filter", "_doc", "_upsert", "_collation", "_array_filters", "_hint") |
|
|
|
def __init__( |
|
self, |
|
filter: Mapping[str, Any], |
|
doc: Union[Mapping[str, Any], _Pipeline], |
|
upsert: bool, |
|
collation: Optional[_CollationIn], |
|
array_filters: Optional[List[Mapping[str, Any]]], |
|
hint: Optional[_IndexKeyHint], |
|
): |
|
if filter is not None: |
|
validate_is_mapping("filter", filter) |
|
if upsert is not None: |
|
validate_boolean("upsert", upsert) |
|
if array_filters is not None: |
|
validate_list("array_filters", array_filters) |
|
if hint is not None and not isinstance(hint, str): |
|
self._hint: Union[str, SON[str, Any], None] = helpers._index_document(hint) |
|
else: |
|
self._hint = hint |
|
|
|
self._filter = filter |
|
self._doc = doc |
|
self._upsert = upsert |
|
self._collation = collation |
|
self._array_filters = array_filters |
|
|
|
def __eq__(self, other: object) -> bool: |
|
if isinstance(other, type(self)): |
|
return ( |
|
other._filter, |
|
other._doc, |
|
other._upsert, |
|
other._collation, |
|
other._array_filters, |
|
other._hint, |
|
) == ( |
|
self._filter, |
|
self._doc, |
|
self._upsert, |
|
self._collation, |
|
self._array_filters, |
|
self._hint, |
|
) |
|
return NotImplemented |
|
|
|
def __repr__(self) -> str: |
|
return "{}({!r}, {!r}, {!r}, {!r}, {!r}, {!r})".format( |
|
self.__class__.__name__, |
|
self._filter, |
|
self._doc, |
|
self._upsert, |
|
self._collation, |
|
self._array_filters, |
|
self._hint, |
|
) |
|
|
|
|
|
class UpdateOne(_UpdateOp): |
|
"""Represents an update_one operation.""" |
|
|
|
__slots__ = () |
|
|
|
def __init__( |
|
self, |
|
filter: Mapping[str, Any], |
|
update: Union[Mapping[str, Any], _Pipeline], |
|
upsert: bool = False, |
|
collation: Optional[_CollationIn] = None, |
|
array_filters: Optional[List[Mapping[str, Any]]] = None, |
|
hint: Optional[_IndexKeyHint] = None, |
|
) -> None: |
|
"""Represents an update_one operation. |
|
|
|
For use with :meth:`~pymongo.collection.Collection.bulk_write`. |
|
|
|
:Parameters: |
|
- `filter`: A query that matches the document to update. |
|
- `update`: The modifications to apply. |
|
- `upsert` (optional): If ``True``, perform an insert if no documents |
|
match the filter. |
|
- `collation` (optional): An instance of |
|
:class:`~pymongo.collation.Collation`. |
|
- `array_filters` (optional): A list of filters specifying which |
|
array elements an update should apply. |
|
- `hint` (optional): An index to use to support the query |
|
predicate specified either by its string name, or in the same |
|
format as passed to |
|
:meth:`~pymongo.collection.Collection.create_index` (e.g. |
|
``[('field', ASCENDING)]``). This option is only supported on |
|
MongoDB 4.2 and above. |
|
|
|
.. versionchanged:: 3.11 |
|
Added the `hint` option. |
|
.. versionchanged:: 3.9 |
|
Added the ability to accept a pipeline as the `update`. |
|
.. versionchanged:: 3.6 |
|
Added the `array_filters` option. |
|
.. versionchanged:: 3.5 |
|
Added the `collation` option. |
|
""" |
|
super().__init__(filter, update, upsert, collation, array_filters, hint) |
|
|
|
def _add_to_bulk(self, bulkobj: _Bulk) -> None: |
|
"""Add this operation to the _Bulk instance `bulkobj`.""" |
|
bulkobj.add_update( |
|
self._filter, |
|
self._doc, |
|
False, |
|
self._upsert, |
|
collation=validate_collation_or_none(self._collation), |
|
array_filters=self._array_filters, |
|
hint=self._hint, |
|
) |
|
|
|
|
|
class UpdateMany(_UpdateOp): |
|
"""Represents an update_many operation.""" |
|
|
|
__slots__ = () |
|
|
|
def __init__( |
|
self, |
|
filter: Mapping[str, Any], |
|
update: Union[Mapping[str, Any], _Pipeline], |
|
upsert: bool = False, |
|
collation: Optional[_CollationIn] = None, |
|
array_filters: Optional[List[Mapping[str, Any]]] = None, |
|
hint: Optional[_IndexKeyHint] = None, |
|
) -> None: |
|
"""Create an UpdateMany instance. |
|
|
|
For use with :meth:`~pymongo.collection.Collection.bulk_write`. |
|
|
|
:Parameters: |
|
- `filter`: A query that matches the documents to update. |
|
- `update`: The modifications to apply. |
|
- `upsert` (optional): If ``True``, perform an insert if no documents |
|
match the filter. |
|
- `collation` (optional): An instance of |
|
:class:`~pymongo.collation.Collation`. |
|
- `array_filters` (optional): A list of filters specifying which |
|
array elements an update should apply. |
|
- `hint` (optional): An index to use to support the query |
|
predicate specified either by its string name, or in the same |
|
format as passed to |
|
:meth:`~pymongo.collection.Collection.create_index` (e.g. |
|
``[('field', ASCENDING)]``). This option is only supported on |
|
MongoDB 4.2 and above. |
|
|
|
.. versionchanged:: 3.11 |
|
Added the `hint` option. |
|
.. versionchanged:: 3.9 |
|
Added the ability to accept a pipeline as the `update`. |
|
.. versionchanged:: 3.6 |
|
Added the `array_filters` option. |
|
.. versionchanged:: 3.5 |
|
Added the `collation` option. |
|
""" |
|
super().__init__(filter, update, upsert, collation, array_filters, hint) |
|
|
|
def _add_to_bulk(self, bulkobj: _Bulk) -> None: |
|
"""Add this operation to the _Bulk instance `bulkobj`.""" |
|
bulkobj.add_update( |
|
self._filter, |
|
self._doc, |
|
True, |
|
self._upsert, |
|
collation=validate_collation_or_none(self._collation), |
|
array_filters=self._array_filters, |
|
hint=self._hint, |
|
) |
|
|
|
|
|
class IndexModel: |
|
"""Represents an index to create.""" |
|
|
|
__slots__ = ("__document",) |
|
|
|
def __init__(self, keys: _IndexKeyHint, **kwargs: Any) -> None: |
|
"""Create an Index instance. |
|
|
|
For use with :meth:`~pymongo.collection.Collection.create_indexes`. |
|
|
|
Takes either a single key or a list containing (key, direction) pairs |
|
or keys. If no direction is given, :data:`~pymongo.ASCENDING` will |
|
be assumed. |
|
The key(s) must be an instance of :class:`str`, and the direction(s) must |
|
be one of (:data:`~pymongo.ASCENDING`, :data:`~pymongo.DESCENDING`, |
|
:data:`~pymongo.GEO2D`, :data:`~pymongo.GEOSPHERE`, |
|
:data:`~pymongo.HASHED`, :data:`~pymongo.TEXT`). |
|
|
|
Valid options include, but are not limited to: |
|
|
|
- `name`: custom name to use for this index - if none is |
|
given, a name will be generated. |
|
- `unique`: if ``True``, creates a uniqueness constraint on the index. |
|
- `background`: if ``True``, this index should be created in the |
|
background. |
|
- `sparse`: if ``True``, omit from the index any documents that lack |
|
the indexed field. |
|
- `bucketSize`: for use with geoHaystack indexes. |
|
Number of documents to group together within a certain proximity |
|
to a given longitude and latitude. |
|
- `min`: minimum value for keys in a :data:`~pymongo.GEO2D` |
|
index. |
|
- `max`: maximum value for keys in a :data:`~pymongo.GEO2D` |
|
index. |
|
- `expireAfterSeconds`: <int> Used to create an expiring (TTL) |
|
collection. MongoDB will automatically delete documents from |
|
this collection after <int> seconds. The indexed field must |
|
be a UTC datetime or the data will not expire. |
|
- `partialFilterExpression`: A document that specifies a filter for |
|
a partial index. |
|
- `collation`: An instance of :class:`~pymongo.collation.Collation` |
|
that specifies the collation to use. |
|
- `wildcardProjection`: Allows users to include or exclude specific |
|
field paths from a `wildcard index`_ using the { "$**" : 1} key |
|
pattern. Requires MongoDB >= 4.2. |
|
- `hidden`: if ``True``, this index will be hidden from the query |
|
planner and will not be evaluated as part of query plan |
|
selection. Requires MongoDB >= 4.4. |
|
|
|
See the MongoDB documentation for a full list of supported options by |
|
server version. |
|
|
|
:Parameters: |
|
- `keys`: a single key or a list containing (key, direction) pairs |
|
or keys specifying the index to create. |
|
- `**kwargs` (optional): any additional index creation |
|
options (see the above list) should be passed as keyword |
|
arguments. |
|
|
|
.. versionchanged:: 3.11 |
|
Added the ``hidden`` option. |
|
.. versionchanged:: 3.2 |
|
Added the ``partialFilterExpression`` option to support partial |
|
indexes. |
|
|
|
.. _wildcard index: https://mongodb.com/docs/master/core/index-wildcard/ |
|
""" |
|
keys = _index_list(keys) |
|
if kwargs.get("name") is None: |
|
kwargs["name"] = _gen_index_name(keys) |
|
kwargs["key"] = _index_document(keys) |
|
collation = validate_collation_or_none(kwargs.pop("collation", None)) |
|
self.__document = kwargs |
|
if collation is not None: |
|
self.__document["collation"] = collation |
|
|
|
@property |
|
def document(self) -> Dict[str, Any]: |
|
"""An index document suitable for passing to the createIndexes |
|
command. |
|
""" |
|
return self.__document |
|
|
|
|
|
class SearchIndexModel: |
|
"""Represents a search index to create.""" |
|
|
|
__slots__ = "__document" |
|
|
|
def __init__(self, definition: Mapping[str, Any], name: Optional[str] = None) -> None: |
|
"""Create a Search Index instance. |
|
|
|
For use with :meth:`~pymongo.collection.Collection.create_search_index` and :meth:`~pymongo.collection.Collection.create_search_indexes`. |
|
|
|
:Parameters: |
|
- `definition` - The definition for this index. |
|
- `name` (optional) - The name for this index, if present. |
|
|
|
.. versionadded:: 4.5 |
|
|
|
.. note:: Search indexes require a MongoDB server version 7.0+ Atlas cluster. |
|
""" |
|
if name is not None: |
|
self.__document = dict(name=name, definition=definition) |
|
else: |
|
self.__document = dict(definition=definition) |
|
|
|
@property |
|
def document(self) -> Mapping[str, Any]: |
|
"""The document for this index.""" |
|
return self.__document
|
|
|