Manual merge conflicts in AUTHORS

This commit is contained in:
Paul-Armand Verhaegen 2015-11-27 23:55:55 +01:00
commit 6133f04841
14 changed files with 268 additions and 41 deletions

View File

@ -5,6 +5,7 @@ python:
- '3.2' - '3.2'
- '3.3' - '3.3'
- '3.4' - '3.4'
- '3.5'
- pypy - pypy
- pypy3 - pypy3
env: env:

View File

@ -229,4 +229,7 @@ that much better:
* Emile Caron (https://github.com/emilecaron) * Emile Caron (https://github.com/emilecaron)
* Amit Lichtenberg (https://github.com/amitlicht) * Amit Lichtenberg (https://github.com/amitlicht)
* Lars Butler (https://github.com/larsbutler) * Lars Butler (https://github.com/larsbutler)
* George Macon (https://github.com/gmacon)
* Ashley Whetter (https://github.com/AWhetter)
* Paul-Armand Verhaegen (https://github.com/paularmand) * Paul-Armand Verhaegen (https://github.com/paularmand)

View File

@ -2,7 +2,21 @@
Changelog Changelog
========= =========
Changes in 0.10.1 - DEV Changes in 0.10.4 - DEV
=======================
- SaveConditionError is now importable from the top level package. #1165
Changes in 0.10.3
=================
- Fix `read_preference` (it had chaining issues with PyMongo 2.x and it didn't work at all with PyMongo 3.x) #1042
Changes in 0.10.2
=================
- Allow shard key to point to a field in an embedded document. #551
- Allow arbirary metadata in fields. #1129
- ReferenceFields now support abstract document types. #837
Changes in 0.10.1
======================= =======================
- Fix infinite recursion with CASCADE delete rules under specific conditions. #1046 - Fix infinite recursion with CASCADE delete rules under specific conditions. #1046
- Fix CachedReferenceField bug when loading cached docs as DBRef but failing to save them. #1047 - Fix CachedReferenceField bug when loading cached docs as DBRef but failing to save them. #1047

View File

@ -172,11 +172,11 @@ arguments can be set on all fields:
class Shirt(Document): class Shirt(Document):
size = StringField(max_length=3, choices=SIZE) size = StringField(max_length=3, choices=SIZE)
:attr:`help_text` (Default: None) :attr:`**kwargs` (Optional)
Optional help text to output with the field -- used by form libraries You can supply additional metadata as arbitrary additional keyword
arguments. You can not override existing attributes, however. Common
:attr:`verbose_name` (Default: None) choices include `help_text` and `verbose_name`, commonly used by form and
Optional human-readable name for the field -- used by form libraries widget libraries.
List fields List fields

View File

@ -14,7 +14,7 @@ import errors
__all__ = (list(document.__all__) + fields.__all__ + connection.__all__ + __all__ = (list(document.__all__) + fields.__all__ + connection.__all__ +
list(queryset.__all__) + signals.__all__ + list(errors.__all__)) list(queryset.__all__) + signals.__all__ + list(errors.__all__))
VERSION = (0, 10, 0) VERSION = (0, 10, 1)
def get_version(): def get_version():

View File

@ -41,8 +41,8 @@ class BaseField(object):
def __init__(self, db_field=None, name=None, required=False, default=None, def __init__(self, db_field=None, name=None, required=False, default=None,
unique=False, unique_with=None, primary_key=False, unique=False, unique_with=None, primary_key=False,
validation=None, choices=None, verbose_name=None, validation=None, choices=None, null=False, sparse=False,
help_text=None, null=False, sparse=False, custom_data=None): **kwargs):
""" """
:param db_field: The database field to store this field in :param db_field: The database field to store this field in
(defaults to the name of the field) (defaults to the name of the field)
@ -60,16 +60,15 @@ class BaseField(object):
field. Generally this is deprecated in favour of the field. Generally this is deprecated in favour of the
`FIELD.validate` method `FIELD.validate` method
:param choices: (optional) The valid choices :param choices: (optional) The valid choices
:param verbose_name: (optional) The verbose name for the field.
Designed to be human readable and is often used when generating
model forms from the document model.
:param help_text: (optional) The help text for this field and is often
used when generating model forms from the document model.
:param null: (optional) Is the field value can be null. If no and there is a default value :param null: (optional) Is the field value can be null. If no and there is a default value
then the default value is set then the default value is set
:param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False` :param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False`
means that uniqueness won't be enforced for `None` values means that uniqueness won't be enforced for `None` values
:param custom_data: (optional) Custom metadata for this field. :param **kwargs: (optional) Arbitrary indirection-free metadata for
this field can be supplied as additional keyword arguments and
accessed as attributes of the field. Must not conflict with any
existing attributes. Common metadata includes `verbose_name` and
`help_text`.
""" """
self.db_field = (db_field or name) if not primary_key else '_id' self.db_field = (db_field or name) if not primary_key else '_id'
@ -83,12 +82,19 @@ class BaseField(object):
self.primary_key = primary_key self.primary_key = primary_key
self.validation = validation self.validation = validation
self.choices = choices self.choices = choices
self.verbose_name = verbose_name
self.help_text = help_text
self.null = null self.null = null
self.sparse = sparse self.sparse = sparse
self._owner_document = None self._owner_document = None
self.custom_data = custom_data
# Detect and report conflicts between metadata and base properties.
conflicts = set(dir(self)) & set(kwargs)
if conflicts:
raise TypeError("%s already has attribute(s): %s" % (
self.__class__.__name__, ', '.join(conflicts) ))
# Assign metadata to the instance
# This efficient method is available because no __slots__ are defined.
self.__dict__.update(kwargs)
# Adjust the appropriate creation counter, and save our local copy. # Adjust the appropriate creation counter, and save our local copy.
if self.db_field == '_id': if self.db_field == '_id':

View File

@ -217,7 +217,7 @@ class Document(BaseDocument):
Returns True if the document has been updated or False if the document Returns True if the document has been updated or False if the document
in the database doesn't match the query. in the database doesn't match the query.
.. note:: All unsaved changes that has been made to the document are .. note:: All unsaved changes that have been made to the document are
rejected if the method returns True. rejected if the method returns True.
:param query: the update will be performed only if the document in the :param query: the update will be performed only if the document in the
@ -341,8 +341,12 @@ class Document(BaseDocument):
select_dict['_id'] = object_id select_dict['_id'] = object_id
shard_key = self.__class__._meta.get('shard_key', tuple()) shard_key = self.__class__._meta.get('shard_key', tuple())
for k in shard_key: for k in shard_key:
actual_key = self._db_field_map.get(k, k) path = self._lookup_field(k.split('.'))
select_dict[actual_key] = doc[actual_key] actual_key = [p.db_field for p in path]
val = doc
for ak in actual_key:
val = val[ak]
select_dict['.'.join(actual_key)] = val
def is_new_object(last_error): def is_new_object(last_error):
if last_error is not None: if last_error is not None:
@ -403,7 +407,7 @@ class Document(BaseDocument):
def cascade_save(self, *args, **kwargs): def cascade_save(self, *args, **kwargs):
"""Recursively saves any references / """Recursively saves any references /
generic references on an objects""" generic references on the document"""
_refs = kwargs.get('_refs', []) or [] _refs = kwargs.get('_refs', []) or []
ReferenceField = _import_class('ReferenceField') ReferenceField = _import_class('ReferenceField')
@ -444,7 +448,12 @@ class Document(BaseDocument):
select_dict = {'pk': self.pk} select_dict = {'pk': self.pk}
shard_key = self.__class__._meta.get('shard_key', tuple()) shard_key = self.__class__._meta.get('shard_key', tuple())
for k in shard_key: for k in shard_key:
select_dict[k] = getattr(self, k) path = self._lookup_field(k.split('.'))
actual_key = [p.db_field for p in path]
val = self
for ak in actual_key:
val = getattr(val, ak)
select_dict['__'.join(actual_key)] = val
return select_dict return select_dict
def update(self, **kwargs): def update(self, **kwargs):

View File

@ -6,7 +6,7 @@ from mongoengine.python_support import txt_type
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError', __all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError', 'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
'OperationError', 'NotUniqueError', 'FieldDoesNotExist', 'OperationError', 'NotUniqueError', 'FieldDoesNotExist',
'ValidationError') 'ValidationError', 'SaveConditionError')
class NotRegistered(Exception): class NotRegistered(Exception):

View File

@ -863,12 +863,11 @@ class ReferenceField(BaseField):
The options are: The options are:
* DO_NOTHING - don't do anything (default). * DO_NOTHING (0) - don't do anything (default).
* NULLIFY - Updates the reference to null. * NULLIFY (1) - Updates the reference to null.
* CASCADE - Deletes the documents associated with the reference. * CASCADE (2) - Deletes the documents associated with the reference.
* DENY - Prevent the deletion of the reference object. * DENY (3) - Prevent the deletion of the reference object.
* PULL - Pull the reference from a :class:`~mongoengine.fields.ListField` * PULL (4) - Pull the reference from a :class:`~mongoengine.fields.ListField` of references
of references
Alternative syntax for registering delete rules (useful when implementing Alternative syntax for registering delete rules (useful when implementing
bi-directional delete rules) bi-directional delete rules)
@ -896,6 +895,10 @@ class ReferenceField(BaseField):
or as the :class:`~pymongo.objectid.ObjectId`.id . or as the :class:`~pymongo.objectid.ObjectId`.id .
:param reverse_delete_rule: Determines what to do when the referring :param reverse_delete_rule: Determines what to do when the referring
object is deleted object is deleted
.. note ::
A reference to an abstract document type is always stored as a
:class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`.
""" """
if not isinstance(document_type, basestring): if not isinstance(document_type, basestring):
if not issubclass(document_type, (Document, basestring)): if not issubclass(document_type, (Document, basestring)):
@ -928,9 +931,14 @@ class ReferenceField(BaseField):
self._auto_dereference = instance._fields[self.name]._auto_dereference self._auto_dereference = instance._fields[self.name]._auto_dereference
# Dereference DBRefs # Dereference DBRefs
if self._auto_dereference and isinstance(value, DBRef): if self._auto_dereference and isinstance(value, DBRef):
value = self.document_type._get_db().dereference(value) if hasattr(value, 'cls'):
# Dereference using the class type specified in the reference
cls = get_document(value.cls)
else:
cls = self.document_type
value = cls._get_db().dereference(value)
if value is not None: if value is not None:
instance._data[self.name] = self.document_type._from_son(value) instance._data[self.name] = cls._from_son(value)
return super(ReferenceField, self).__get__(instance, owner) return super(ReferenceField, self).__get__(instance, owner)
@ -940,21 +948,29 @@ class ReferenceField(BaseField):
return document.id return document.id
return document return document
id_field_name = self.document_type._meta['id_field']
id_field = self.document_type._fields[id_field_name]
if isinstance(document, Document): if isinstance(document, Document):
# We need the id from the saved object to create the DBRef # We need the id from the saved object to create the DBRef
id_ = document.pk id_ = document.pk
if id_ is None: if id_ is None:
self.error('You can only reference documents once they have' self.error('You can only reference documents once they have'
' been saved to the database') ' been saved to the database')
# Use the attributes from the document instance, so that they
# override the attributes of this field's document type
cls = document
else: else:
id_ = document id_ = document
cls = self.document_type
id_field_name = cls._meta['id_field']
id_field = cls._fields[id_field_name]
id_ = id_field.to_mongo(id_) id_ = id_field.to_mongo(id_)
if self.dbref: if self.document_type._meta.get('abstract'):
collection = self.document_type._get_collection_name() collection = cls._get_collection_name()
return DBRef(collection, id_, cls=cls._class_name)
elif self.dbref:
collection = cls._get_collection_name()
return DBRef(collection, id_) return DBRef(collection, id_)
return id_ return id_
@ -983,6 +999,14 @@ class ReferenceField(BaseField):
self.error('You can only reference documents once they have been ' self.error('You can only reference documents once they have been '
'saved to the database') 'saved to the database')
if self.document_type._meta.get('abstract') and \
not isinstance(value, self.document_type):
self.error('%s is not an instance of abstract reference'
' type %s' % (value._class_name,
self.document_type._class_name)
)
def lookup_member(self, member_name): def lookup_member(self, member_name):
return self.document_type._fields.get(member_name) return self.document_type._fields.get(member_name)

View File

@ -930,6 +930,7 @@ class BaseQuerySet(object):
validate_read_preference('read_preference', read_preference) validate_read_preference('read_preference', read_preference)
queryset = self.clone() queryset = self.clone()
queryset._read_preference = read_preference queryset._read_preference = read_preference
queryset._cursor_obj = None # we need to re-create the cursor object whenever we apply read_preference
return queryset return queryset
def scalar(self, *fields): def scalar(self, *fields):
@ -1443,8 +1444,16 @@ class BaseQuerySet(object):
def _cursor(self): def _cursor(self):
if self._cursor_obj is None: if self._cursor_obj is None:
self._cursor_obj = self._collection.find(self._query, # In PyMongo 3+, we define the read preference on a collection
**self._cursor_args) # level, not a cursor level. Thus, we need to get a cloned
# collection object using `with_options` first.
if IS_PYMONGO_3 and self._read_preference is not None:
self._cursor_obj = self._collection\
.with_options(read_preference=self._read_preference)\
.find(self._query, **self._cursor_args)
else:
self._cursor_obj = self._collection.find(self._query,
**self._cursor_args)
# Apply where clauses to cursor # Apply where clauses to cursor
if self._where_clause: if self._where_clause:
where_clause = self._sub_js_fields(self._where_clause) where_clause = self._sub_js_fields(self._where_clause)

View File

@ -484,6 +484,20 @@ class InstanceTest(unittest.TestCase):
doc.reload() doc.reload()
Animal.drop_collection() Animal.drop_collection()
def test_reload_sharded_nested(self):
class SuperPhylum(EmbeddedDocument):
name = StringField()
class Animal(Document):
superphylum = EmbeddedDocumentField(SuperPhylum)
meta = {'shard_key': ('superphylum.name',)}
Animal.drop_collection()
doc = Animal(superphylum=SuperPhylum(name='Deuterostomia'))
doc.save()
doc.reload()
Animal.drop_collection()
def test_reload_referencing(self): def test_reload_referencing(self):
"""Ensures reloading updates weakrefs correctly """Ensures reloading updates weakrefs correctly
""" """
@ -2737,6 +2751,32 @@ class InstanceTest(unittest.TestCase):
self.assertRaises(OperationError, change_shard_key) self.assertRaises(OperationError, change_shard_key)
def test_shard_key_in_embedded_document(self):
class Foo(EmbeddedDocument):
foo = StringField()
class Bar(Document):
meta = {
'shard_key': ('foo.foo',)
}
foo = EmbeddedDocumentField(Foo)
bar = StringField()
foo_doc = Foo(foo='hello')
bar_doc = Bar(foo=foo_doc, bar='world')
bar_doc.save()
self.assertTrue(bar_doc.id is not None)
bar_doc.bar = 'baz'
bar_doc.save()
def change_shard_key():
bar_doc.foo.foo = 'something'
bar_doc.save()
self.assertRaises(OperationError, change_shard_key)
def test_shard_key_primary(self): def test_shard_key_primary(self):
class LogEntry(Document): class LogEntry(Document):
machine = StringField(primary_key=True) machine = StringField(primary_key=True)

View File

@ -2281,6 +2281,81 @@ class FieldTest(unittest.TestCase):
Member.drop_collection() Member.drop_collection()
BlogPost.drop_collection() BlogPost.drop_collection()
def test_reference_class_with_abstract_parent(self):
"""Ensure that a class with an abstract parent can be referenced.
"""
class Sibling(Document):
name = StringField()
meta = {"abstract": True}
class Sister(Sibling):
pass
class Brother(Sibling):
sibling = ReferenceField(Sibling)
Sister.drop_collection()
Brother.drop_collection()
sister = Sister(name="Alice")
sister.save()
brother = Brother(name="Bob", sibling=sister)
brother.save()
self.assertEquals(Brother.objects[0].sibling.name, sister.name)
Sister.drop_collection()
Brother.drop_collection()
def test_reference_abstract_class(self):
"""Ensure that an abstract class instance cannot be used in the
reference of that abstract class.
"""
class Sibling(Document):
name = StringField()
meta = {"abstract": True}
class Sister(Sibling):
pass
class Brother(Sibling):
sibling = ReferenceField(Sibling)
Sister.drop_collection()
Brother.drop_collection()
sister = Sibling(name="Alice")
brother = Brother(name="Bob", sibling=sister)
self.assertRaises(ValidationError, brother.save)
Sister.drop_collection()
Brother.drop_collection()
def test_abstract_reference_base_type(self):
"""Ensure that an an abstract reference fails validation when given a
Document that does not inherit from the abstract type.
"""
class Sibling(Document):
name = StringField()
meta = {"abstract": True}
class Brother(Sibling):
sibling = ReferenceField(Sibling)
class Mother(Document):
name = StringField()
Brother.drop_collection()
Mother.drop_collection()
mother = Mother(name="Carol")
mother.save()
brother = Brother(name="Bob", sibling=mother)
self.assertRaises(ValidationError, brother.save)
Brother.drop_collection()
Mother.drop_collection()
def test_generic_reference(self): def test_generic_reference(self):
"""Ensure that a GenericReferenceField properly dereferences items. """Ensure that a GenericReferenceField properly dereferences items.
""" """

View File

@ -4165,7 +4165,11 @@ class QuerySetTest(unittest.TestCase):
def test_read_preference(self): def test_read_preference(self):
class Bar(Document): class Bar(Document):
pass txt = StringField()
meta = {
'indexes': [ 'txt' ]
}
Bar.drop_collection() Bar.drop_collection()
bars = list(Bar.objects(read_preference=ReadPreference.PRIMARY)) bars = list(Bar.objects(read_preference=ReadPreference.PRIMARY))
@ -4177,9 +4181,51 @@ class QuerySetTest(unittest.TestCase):
error_class = TypeError error_class = TypeError
self.assertRaises(error_class, Bar.objects, read_preference='Primary') self.assertRaises(error_class, Bar.objects, read_preference='Primary')
# read_preference as a kwarg
bars = Bar.objects(read_preference=ReadPreference.SECONDARY_PREFERRED) bars = Bar.objects(read_preference=ReadPreference.SECONDARY_PREFERRED)
self.assertEqual( self.assertEqual(
bars._read_preference, ReadPreference.SECONDARY_PREFERRED) bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(bars._cursor._Cursor__read_preference,
ReadPreference.SECONDARY_PREFERRED)
# read_preference as a query set method
bars = Bar.objects.read_preference(ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(bars._cursor._Cursor__read_preference,
ReadPreference.SECONDARY_PREFERRED)
# read_preference after skip
bars = Bar.objects.skip(1) \
.read_preference(ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(bars._cursor._Cursor__read_preference,
ReadPreference.SECONDARY_PREFERRED)
# read_preference after limit
bars = Bar.objects.limit(1) \
.read_preference(ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(bars._cursor._Cursor__read_preference,
ReadPreference.SECONDARY_PREFERRED)
# read_preference after order_by
bars = Bar.objects.order_by('txt') \
.read_preference(ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(bars._cursor._Cursor__read_preference,
ReadPreference.SECONDARY_PREFERRED)
# read_preference after hint
bars = Bar.objects.hint([('txt', 1)]) \
.read_preference(ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(
bars._read_preference, ReadPreference.SECONDARY_PREFERRED)
self.assertEqual(bars._cursor._Cursor__read_preference,
ReadPreference.SECONDARY_PREFERRED)
def test_json_simple(self): def test_json_simple(self):

View File

@ -1,5 +1,5 @@
[tox] [tox]
envlist = {py26,py27,py32,py33,py34,pypy,pypy3}-{mg27,mg28} envlist = {py26,py27,py32,py33,py34,py35,pypy,pypy3}-{mg27,mg28}
#envlist = {py26,py27,py32,py33,py34,pypy,pypy3}-{mg27,mg28,mg30,mgdev} #envlist = {py26,py27,py32,py33,py34,pypy,pypy3}-{mg27,mg28,mg30,mgdev}
[testenv] [testenv]