datastructures.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. import weakref
  2. from bson import DBRef
  3. import six
  4. from mongoengine.common import _import_class
  5. from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
  6. __all__ = ('BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference')
  7. class BaseDict(dict):
  8. """A special dict so we can watch any changes."""
  9. _dereferenced = False
  10. _instance = None
  11. _name = None
  12. def __init__(self, dict_items, instance, name):
  13. BaseDocument = _import_class('BaseDocument')
  14. if isinstance(instance, BaseDocument):
  15. self._instance = weakref.proxy(instance)
  16. self._name = name
  17. super(BaseDict, self).__init__(dict_items)
  18. def __getitem__(self, key, *args, **kwargs):
  19. value = super(BaseDict, self).__getitem__(key)
  20. EmbeddedDocument = _import_class('EmbeddedDocument')
  21. if isinstance(value, EmbeddedDocument) and value._instance is None:
  22. value._instance = self._instance
  23. elif isinstance(value, dict) and not isinstance(value, BaseDict):
  24. value = BaseDict(value, None, '%s.%s' % (self._name, key))
  25. super(BaseDict, self).__setitem__(key, value)
  26. value._instance = self._instance
  27. elif isinstance(value, list) and not isinstance(value, BaseList):
  28. value = BaseList(value, None, '%s.%s' % (self._name, key))
  29. super(BaseDict, self).__setitem__(key, value)
  30. value._instance = self._instance
  31. return value
  32. def __setitem__(self, key, value, *args, **kwargs):
  33. self._mark_as_changed(key)
  34. return super(BaseDict, self).__setitem__(key, value)
  35. def __delete__(self, *args, **kwargs):
  36. self._mark_as_changed()
  37. return super(BaseDict, self).__delete__(*args, **kwargs)
  38. def __delitem__(self, key, *args, **kwargs):
  39. self._mark_as_changed(key)
  40. return super(BaseDict, self).__delitem__(key)
  41. def __delattr__(self, key, *args, **kwargs):
  42. self._mark_as_changed(key)
  43. return super(BaseDict, self).__delattr__(key)
  44. def __getstate__(self):
  45. self.instance = None
  46. self._dereferenced = False
  47. return self
  48. def __setstate__(self, state):
  49. self = state
  50. return self
  51. def clear(self, *args, **kwargs):
  52. self._mark_as_changed()
  53. return super(BaseDict, self).clear()
  54. def pop(self, *args, **kwargs):
  55. self._mark_as_changed()
  56. return super(BaseDict, self).pop(*args, **kwargs)
  57. def popitem(self, *args, **kwargs):
  58. self._mark_as_changed()
  59. return super(BaseDict, self).popitem()
  60. def setdefault(self, *args, **kwargs):
  61. self._mark_as_changed()
  62. return super(BaseDict, self).setdefault(*args, **kwargs)
  63. def update(self, *args, **kwargs):
  64. self._mark_as_changed()
  65. return super(BaseDict, self).update(*args, **kwargs)
  66. def _mark_as_changed(self, key=None):
  67. if hasattr(self._instance, '_mark_as_changed'):
  68. if key:
  69. self._instance._mark_as_changed('%s.%s' % (self._name, key))
  70. else:
  71. self._instance._mark_as_changed(self._name)
  72. class BaseList(list):
  73. """A special list so we can watch any changes."""
  74. _dereferenced = False
  75. _instance = None
  76. _name = None
  77. def __init__(self, list_items, instance, name):
  78. BaseDocument = _import_class('BaseDocument')
  79. if isinstance(instance, BaseDocument):
  80. self._instance = weakref.proxy(instance)
  81. self._name = name
  82. super(BaseList, self).__init__(list_items)
  83. def __getitem__(self, key, *args, **kwargs):
  84. value = super(BaseList, self).__getitem__(key)
  85. EmbeddedDocument = _import_class('EmbeddedDocument')
  86. if isinstance(value, EmbeddedDocument) and value._instance is None:
  87. value._instance = self._instance
  88. elif isinstance(value, dict) and not isinstance(value, BaseDict):
  89. value = BaseDict(value, None, '%s.%s' % (self._name, key))
  90. super(BaseList, self).__setitem__(key, value)
  91. value._instance = self._instance
  92. elif isinstance(value, list) and not isinstance(value, BaseList):
  93. value = BaseList(value, None, '%s.%s' % (self._name, key))
  94. super(BaseList, self).__setitem__(key, value)
  95. value._instance = self._instance
  96. return value
  97. def __iter__(self):
  98. for v in super(BaseList, self).__iter__():
  99. yield v
  100. def __setitem__(self, key, value, *args, **kwargs):
  101. if isinstance(key, slice):
  102. self._mark_as_changed()
  103. else:
  104. self._mark_as_changed(key)
  105. return super(BaseList, self).__setitem__(key, value)
  106. def __delitem__(self, key):
  107. self._mark_as_changed()
  108. return super(BaseList, self).__delitem__(key)
  109. def __setslice__(self, *args, **kwargs):
  110. self._mark_as_changed()
  111. return super(BaseList, self).__setslice__(*args, **kwargs)
  112. def __delslice__(self, *args, **kwargs):
  113. self._mark_as_changed()
  114. return super(BaseList, self).__delslice__(*args, **kwargs)
  115. def __getstate__(self):
  116. self.instance = None
  117. self._dereferenced = False
  118. return self
  119. def __setstate__(self, state):
  120. self = state
  121. return self
  122. def __iadd__(self, other):
  123. self._mark_as_changed()
  124. return super(BaseList, self).__iadd__(other)
  125. def __imul__(self, other):
  126. self._mark_as_changed()
  127. return super(BaseList, self).__imul__(other)
  128. def append(self, *args, **kwargs):
  129. self._mark_as_changed()
  130. return super(BaseList, self).append(*args, **kwargs)
  131. def extend(self, *args, **kwargs):
  132. self._mark_as_changed()
  133. return super(BaseList, self).extend(*args, **kwargs)
  134. def insert(self, *args, **kwargs):
  135. self._mark_as_changed()
  136. return super(BaseList, self).insert(*args, **kwargs)
  137. def pop(self, *args, **kwargs):
  138. self._mark_as_changed()
  139. return super(BaseList, self).pop(*args, **kwargs)
  140. def remove(self, *args, **kwargs):
  141. self._mark_as_changed()
  142. return super(BaseList, self).remove(*args, **kwargs)
  143. def reverse(self):
  144. self._mark_as_changed()
  145. return super(BaseList, self).reverse()
  146. def sort(self, *args, **kwargs):
  147. self._mark_as_changed()
  148. return super(BaseList, self).sort(*args, **kwargs)
  149. def _mark_as_changed(self, key=None):
  150. if hasattr(self._instance, '_mark_as_changed'):
  151. if key:
  152. self._instance._mark_as_changed(
  153. '%s.%s' % (self._name, key % len(self))
  154. )
  155. else:
  156. self._instance._mark_as_changed(self._name)
  157. class EmbeddedDocumentList(BaseList):
  158. @classmethod
  159. def __match_all(cls, embedded_doc, kwargs):
  160. """Return True if a given embedded doc matches all the filter
  161. kwargs. If it doesn't return False.
  162. """
  163. for key, expected_value in kwargs.items():
  164. doc_val = getattr(embedded_doc, key)
  165. if doc_val != expected_value and six.text_type(doc_val) != expected_value:
  166. return False
  167. return True
  168. @classmethod
  169. def __only_matches(cls, embedded_docs, kwargs):
  170. """Return embedded docs that match the filter kwargs."""
  171. if not kwargs:
  172. return embedded_docs
  173. return [doc for doc in embedded_docs if cls.__match_all(doc, kwargs)]
  174. def __init__(self, list_items, instance, name):
  175. super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
  176. self._instance = instance
  177. def filter(self, **kwargs):
  178. """
  179. Filters the list by only including embedded documents with the
  180. given keyword arguments.
  181. This method only supports simple comparison (e.g: .filter(name='John Doe'))
  182. and does not support operators like __gte, __lte, __icontains like queryset.filter does
  183. :param kwargs: The keyword arguments corresponding to the fields to
  184. filter on. *Multiple arguments are treated as if they are ANDed
  185. together.*
  186. :return: A new ``EmbeddedDocumentList`` containing the matching
  187. embedded documents.
  188. Raises ``AttributeError`` if a given keyword is not a valid field for
  189. the embedded document class.
  190. """
  191. values = self.__only_matches(self, kwargs)
  192. return EmbeddedDocumentList(values, self._instance, self._name)
  193. def exclude(self, **kwargs):
  194. """
  195. Filters the list by excluding embedded documents with the given
  196. keyword arguments.
  197. :param kwargs: The keyword arguments corresponding to the fields to
  198. exclude on. *Multiple arguments are treated as if they are ANDed
  199. together.*
  200. :return: A new ``EmbeddedDocumentList`` containing the non-matching
  201. embedded documents.
  202. Raises ``AttributeError`` if a given keyword is not a valid field for
  203. the embedded document class.
  204. """
  205. exclude = self.__only_matches(self, kwargs)
  206. values = [item for item in self if item not in exclude]
  207. return EmbeddedDocumentList(values, self._instance, self._name)
  208. def count(self):
  209. """
  210. The number of embedded documents in the list.
  211. :return: The length of the list, equivalent to the result of ``len()``.
  212. """
  213. return len(self)
  214. def get(self, **kwargs):
  215. """
  216. Retrieves an embedded document determined by the given keyword
  217. arguments.
  218. :param kwargs: The keyword arguments corresponding to the fields to
  219. search on. *Multiple arguments are treated as if they are ANDed
  220. together.*
  221. :return: The embedded document matched by the given keyword arguments.
  222. Raises ``DoesNotExist`` if the arguments used to query an embedded
  223. document returns no results. ``MultipleObjectsReturned`` if more
  224. than one result is returned.
  225. """
  226. values = self.__only_matches(self, kwargs)
  227. if len(values) == 0:
  228. raise DoesNotExist(
  229. '%s matching query does not exist.' % self._name
  230. )
  231. elif len(values) > 1:
  232. raise MultipleObjectsReturned(
  233. '%d items returned, instead of 1' % len(values)
  234. )
  235. return values[0]
  236. def first(self):
  237. """Return the first embedded document in the list, or ``None``
  238. if empty.
  239. """
  240. if len(self) > 0:
  241. return self[0]
  242. def create(self, **values):
  243. """
  244. Creates a new embedded document and saves it to the database.
  245. .. note::
  246. The embedded document changes are not automatically saved
  247. to the database after calling this method.
  248. :param values: A dictionary of values for the embedded document.
  249. :return: The new embedded document instance.
  250. """
  251. name = self._name
  252. EmbeddedClass = self._instance._fields[name].field.document_type_obj
  253. self._instance[self._name].append(EmbeddedClass(**values))
  254. return self._instance[self._name][-1]
  255. def save(self, *args, **kwargs):
  256. """
  257. Saves the ancestor document.
  258. :param args: Arguments passed up to the ancestor Document's save
  259. method.
  260. :param kwargs: Keyword arguments passed up to the ancestor Document's
  261. save method.
  262. """
  263. self._instance.save(*args, **kwargs)
  264. def delete(self):
  265. """
  266. Deletes the embedded documents from the database.
  267. .. note::
  268. The embedded document changes are not automatically saved
  269. to the database after calling this method.
  270. :return: The number of entries deleted.
  271. """
  272. values = list(self)
  273. for item in values:
  274. self._instance[self._name].remove(item)
  275. return len(values)
  276. def update(self, **update):
  277. """
  278. Updates the embedded documents with the given replacement values. This
  279. function does not support mongoDB update operators such as ``inc__``.
  280. .. note::
  281. The embedded document changes are not automatically saved
  282. to the database after calling this method.
  283. :param update: A dictionary of update values to apply to each
  284. embedded document.
  285. :return: The number of entries updated.
  286. """
  287. if len(update) == 0:
  288. return 0
  289. values = list(self)
  290. for item in values:
  291. for k, v in update.items():
  292. setattr(item, k, v)
  293. return len(values)
  294. class StrictDict(object):
  295. __slots__ = ()
  296. _special_fields = {'get', 'pop', 'iteritems', 'items', 'keys', 'create'}
  297. _classes = {}
  298. def __init__(self, **kwargs):
  299. for k, v in kwargs.iteritems():
  300. setattr(self, k, v)
  301. def __getitem__(self, key):
  302. key = '_reserved_' + key if key in self._special_fields else key
  303. try:
  304. return getattr(self, key)
  305. except AttributeError:
  306. raise KeyError(key)
  307. def __setitem__(self, key, value):
  308. key = '_reserved_' + key if key in self._special_fields else key
  309. return setattr(self, key, value)
  310. def __contains__(self, key):
  311. return hasattr(self, key)
  312. def get(self, key, default=None):
  313. try:
  314. return self[key]
  315. except KeyError:
  316. return default
  317. def pop(self, key, default=None):
  318. v = self.get(key, default)
  319. try:
  320. delattr(self, key)
  321. except AttributeError:
  322. pass
  323. return v
  324. def iteritems(self):
  325. for key in self:
  326. yield key, self[key]
  327. def items(self):
  328. return [(k, self[k]) for k in iter(self)]
  329. def iterkeys(self):
  330. return iter(self)
  331. def keys(self):
  332. return list(iter(self))
  333. def __iter__(self):
  334. return (key for key in self.__slots__ if hasattr(self, key))
  335. def __len__(self):
  336. return len(list(self.iteritems()))
  337. def __eq__(self, other):
  338. return self.items() == other.items()
  339. def __ne__(self, other):
  340. return self.items() != other.items()
  341. @classmethod
  342. def create(cls, allowed_keys):
  343. allowed_keys_tuple = tuple(('_reserved_' + k if k in cls._special_fields else k) for k in allowed_keys)
  344. allowed_keys = frozenset(allowed_keys_tuple)
  345. if allowed_keys not in cls._classes:
  346. class SpecificStrictDict(cls):
  347. __slots__ = allowed_keys_tuple
  348. def __repr__(self):
  349. return '{%s}' % ', '.join('"{0!s}": {1!r}'.format(k, v) for k, v in self.items())
  350. cls._classes[allowed_keys] = SpecificStrictDict
  351. return cls._classes[allowed_keys]
  352. class LazyReference(DBRef):
  353. __slots__ = ('_cached_doc', 'passthrough', 'document_type')
  354. def fetch(self, force=False):
  355. if not self._cached_doc or force:
  356. self._cached_doc = self.document_type.objects.get(pk=self.pk)
  357. if not self._cached_doc:
  358. raise DoesNotExist('Trying to dereference unknown document %s' % (self))
  359. return self._cached_doc
  360. @property
  361. def pk(self):
  362. return self.id
  363. def __init__(self, document_type, pk, cached_doc=None, passthrough=False):
  364. self.document_type = document_type
  365. self._cached_doc = cached_doc
  366. self.passthrough = passthrough
  367. super(LazyReference, self).__init__(self.document_type._get_collection_name(), pk)
  368. def __getitem__(self, name):
  369. if not self.passthrough:
  370. raise KeyError()
  371. document = self.fetch()
  372. return document[name]
  373. def __getattr__(self, name):
  374. if not object.__getattribute__(self, 'passthrough'):
  375. raise AttributeError()
  376. document = self.fetch()
  377. try:
  378. return document[name]
  379. except KeyError:
  380. raise AttributeError()
  381. def __repr__(self):
  382. return "<LazyReference(%s, %r)>" % (self.document_type, self.pk)