son_manipulator.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. # Copyright 2009-present MongoDB, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """**DEPRECATED**: Manipulators that can edit SON objects as they enter and exit
  15. a database.
  16. The :class:`~pymongo.son_manipulator.SONManipulator` API has limitations as a
  17. technique for transforming your data. Instead, it is more flexible and
  18. straightforward to transform outgoing documents in your own code before passing
  19. them to PyMongo, and transform incoming documents after receiving them from
  20. PyMongo. SON Manipulators will be removed from PyMongo in 4.0.
  21. PyMongo does **not** apply SON manipulators to documents passed to
  22. the modern methods :meth:`~pymongo.collection.Collection.bulk_write`,
  23. :meth:`~pymongo.collection.Collection.insert_one`,
  24. :meth:`~pymongo.collection.Collection.insert_many`,
  25. :meth:`~pymongo.collection.Collection.update_one`, or
  26. :meth:`~pymongo.collection.Collection.update_many`. SON manipulators are
  27. **not** applied to documents returned by the modern methods
  28. :meth:`~pymongo.collection.Collection.find_one_and_delete`,
  29. :meth:`~pymongo.collection.Collection.find_one_and_replace`, and
  30. :meth:`~pymongo.collection.Collection.find_one_and_update`.
  31. """
  32. from bson.dbref import DBRef
  33. from bson.objectid import ObjectId
  34. from bson.py3compat import abc
  35. from bson.son import SON
  36. class SONManipulator(object):
  37. """A base son manipulator.
  38. This manipulator just saves and restores objects without changing them.
  39. """
  40. def will_copy(self):
  41. """Will this SON manipulator make a copy of the incoming document?
  42. Derived classes that do need to make a copy should override this
  43. method, returning True instead of False. All non-copying manipulators
  44. will be applied first (so that the user's document will be updated
  45. appropriately), followed by copying manipulators.
  46. """
  47. return False
  48. def transform_incoming(self, son, collection):
  49. """Manipulate an incoming SON object.
  50. :Parameters:
  51. - `son`: the SON object to be inserted into the database
  52. - `collection`: the collection the object is being inserted into
  53. """
  54. if self.will_copy():
  55. return SON(son)
  56. return son
  57. def transform_outgoing(self, son, collection):
  58. """Manipulate an outgoing SON object.
  59. :Parameters:
  60. - `son`: the SON object being retrieved from the database
  61. - `collection`: the collection this object was stored in
  62. """
  63. if self.will_copy():
  64. return SON(son)
  65. return son
  66. class ObjectIdInjector(SONManipulator):
  67. """A son manipulator that adds the _id field if it is missing.
  68. .. versionchanged:: 2.7
  69. ObjectIdInjector is no longer used by PyMongo, but remains in this
  70. module for backwards compatibility.
  71. """
  72. def transform_incoming(self, son, collection):
  73. """Add an _id field if it is missing.
  74. """
  75. if not "_id" in son:
  76. son["_id"] = ObjectId()
  77. return son
  78. # This is now handled during BSON encoding (for performance reasons),
  79. # but I'm keeping this here as a reference for those implementing new
  80. # SONManipulators.
  81. class ObjectIdShuffler(SONManipulator):
  82. """A son manipulator that moves _id to the first position.
  83. """
  84. def will_copy(self):
  85. """We need to copy to be sure that we are dealing with SON, not a dict.
  86. """
  87. return True
  88. def transform_incoming(self, son, collection):
  89. """Move _id to the front if it's there.
  90. """
  91. if not "_id" in son:
  92. return son
  93. transformed = SON({"_id": son["_id"]})
  94. transformed.update(son)
  95. return transformed
  96. class NamespaceInjector(SONManipulator):
  97. """A son manipulator that adds the _ns field.
  98. """
  99. def transform_incoming(self, son, collection):
  100. """Add the _ns field to the incoming object
  101. """
  102. son["_ns"] = collection.name
  103. return son
  104. class AutoReference(SONManipulator):
  105. """Transparently reference and de-reference already saved embedded objects.
  106. This manipulator should probably only be used when the NamespaceInjector is
  107. also being used, otherwise it doesn't make too much sense - documents can
  108. only be auto-referenced if they have an *_ns* field.
  109. NOTE: this will behave poorly if you have a circular reference.
  110. TODO: this only works for documents that are in the same database. To fix
  111. this we'll need to add a DatabaseInjector that adds *_db* and then make
  112. use of the optional *database* support for DBRefs.
  113. """
  114. def __init__(self, db):
  115. self.database = db
  116. def will_copy(self):
  117. """We need to copy so the user's document doesn't get transformed refs.
  118. """
  119. return True
  120. def transform_incoming(self, son, collection):
  121. """Replace embedded documents with DBRefs.
  122. """
  123. def transform_value(value):
  124. if isinstance(value, abc.MutableMapping):
  125. if "_id" in value and "_ns" in value:
  126. return DBRef(value["_ns"], transform_value(value["_id"]))
  127. else:
  128. return transform_dict(SON(value))
  129. elif isinstance(value, list):
  130. return [transform_value(v) for v in value]
  131. return value
  132. def transform_dict(object):
  133. for (key, value) in object.items():
  134. object[key] = transform_value(value)
  135. return object
  136. return transform_dict(SON(son))
  137. def transform_outgoing(self, son, collection):
  138. """Replace DBRefs with embedded documents.
  139. """
  140. def transform_value(value):
  141. if isinstance(value, DBRef):
  142. return self.database.dereference(value)
  143. elif isinstance(value, list):
  144. return [transform_value(v) for v in value]
  145. elif isinstance(value, abc.MutableMapping):
  146. return transform_dict(SON(value))
  147. return value
  148. def transform_dict(object):
  149. for (key, value) in object.items():
  150. object[key] = transform_value(value)
  151. return object
  152. return transform_dict(SON(son))