son_manipulator.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. # Copyright 2009-2015 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. """Manipulators that can edit SON objects as they enter and exit a database.
  15. New manipulators should be defined as subclasses of SONManipulator and can be
  16. installed on a database by calling
  17. `pymongo.database.Database.add_son_manipulator`."""
  18. import collections
  19. from bson.dbref import DBRef
  20. from bson.objectid import ObjectId
  21. from bson.son import SON
  22. class SONManipulator(object):
  23. """A base son manipulator.
  24. This manipulator just saves and restores objects without changing them.
  25. """
  26. def will_copy(self):
  27. """Will this SON manipulator make a copy of the incoming document?
  28. Derived classes that do need to make a copy should override this
  29. method, returning True instead of False. All non-copying manipulators
  30. will be applied first (so that the user's document will be updated
  31. appropriately), followed by copying manipulators.
  32. """
  33. return False
  34. def transform_incoming(self, son, collection):
  35. """Manipulate an incoming SON object.
  36. :Parameters:
  37. - `son`: the SON object to be inserted into the database
  38. - `collection`: the collection the object is being inserted into
  39. """
  40. if self.will_copy():
  41. return SON(son)
  42. return son
  43. def transform_outgoing(self, son, collection):
  44. """Manipulate an outgoing SON object.
  45. :Parameters:
  46. - `son`: the SON object being retrieved from the database
  47. - `collection`: the collection this object was stored in
  48. """
  49. if self.will_copy():
  50. return SON(son)
  51. return son
  52. class ObjectIdInjector(SONManipulator):
  53. """A son manipulator that adds the _id field if it is missing.
  54. .. versionchanged:: 2.7
  55. ObjectIdInjector is no longer used by PyMongo, but remains in this
  56. module for backwards compatibility.
  57. """
  58. def transform_incoming(self, son, collection):
  59. """Add an _id field if it is missing.
  60. """
  61. if not "_id" in son:
  62. son["_id"] = ObjectId()
  63. return son
  64. # This is now handled during BSON encoding (for performance reasons),
  65. # but I'm keeping this here as a reference for those implementing new
  66. # SONManipulators.
  67. class ObjectIdShuffler(SONManipulator):
  68. """A son manipulator that moves _id to the first position.
  69. """
  70. def will_copy(self):
  71. """We need to copy to be sure that we are dealing with SON, not a dict.
  72. """
  73. return True
  74. def transform_incoming(self, son, collection):
  75. """Move _id to the front if it's there.
  76. """
  77. if not "_id" in son:
  78. return son
  79. transformed = SON({"_id": son["_id"]})
  80. transformed.update(son)
  81. return transformed
  82. class NamespaceInjector(SONManipulator):
  83. """A son manipulator that adds the _ns field.
  84. """
  85. def transform_incoming(self, son, collection):
  86. """Add the _ns field to the incoming object
  87. """
  88. son["_ns"] = collection.name
  89. return son
  90. class AutoReference(SONManipulator):
  91. """Transparently reference and de-reference already saved embedded objects.
  92. This manipulator should probably only be used when the NamespaceInjector is
  93. also being used, otherwise it doesn't make too much sense - documents can
  94. only be auto-referenced if they have an *_ns* field.
  95. NOTE: this will behave poorly if you have a circular reference.
  96. TODO: this only works for documents that are in the same database. To fix
  97. this we'll need to add a DatabaseInjector that adds *_db* and then make
  98. use of the optional *database* support for DBRefs.
  99. """
  100. def __init__(self, db):
  101. self.database = db
  102. def will_copy(self):
  103. """We need to copy so the user's document doesn't get transformed refs.
  104. """
  105. return True
  106. def transform_incoming(self, son, collection):
  107. """Replace embedded documents with DBRefs.
  108. """
  109. def transform_value(value):
  110. if isinstance(value, collections.MutableMapping):
  111. if "_id" in value and "_ns" in value:
  112. return DBRef(value["_ns"], transform_value(value["_id"]))
  113. else:
  114. return transform_dict(SON(value))
  115. elif isinstance(value, list):
  116. return [transform_value(v) for v in value]
  117. return value
  118. def transform_dict(object):
  119. for (key, value) in object.items():
  120. object[key] = transform_value(value)
  121. return object
  122. return transform_dict(SON(son))
  123. def transform_outgoing(self, son, collection):
  124. """Replace DBRefs with embedded documents.
  125. """
  126. def transform_value(value):
  127. if isinstance(value, DBRef):
  128. return self.database.dereference(value)
  129. elif isinstance(value, list):
  130. return [transform_value(v) for v in value]
  131. elif isinstance(value, collections.MutableMapping):
  132. return transform_dict(SON(value))
  133. return value
  134. def transform_dict(object):
  135. for (key, value) in object.items():
  136. object[key] = transform_value(value)
  137. return object
  138. return transform_dict(SON(son))
  139. # TODO make a generic translator for custom types. Take encode, decode,
  140. # should_encode and should_decode functions and just encode and decode where
  141. # necessary. See examples/custom_type.py for where this would be useful.
  142. # Alternatively it could take a should_encode, to_binary, from_binary and
  143. # binary subtype.