fixed merge conflict in BaseField.__init__

This commit is contained in:
blackbrrr
2010-01-12 12:19:30 -06:00
12 changed files with 497 additions and 34 deletions

View File

@@ -12,7 +12,7 @@ __all__ = (document.__all__ + fields.__all__ + connection.__all__ +
__author__ = 'Harry Marr'
VERSION = (0, 1, 3)
VERSION = (0, 2, 1)
def get_version():
version = '%s.%s' % (VERSION[0], VERSION[1])

View File

@@ -13,13 +13,13 @@ class BaseField(object):
"""
def __init__(self, name=None, required=False, default=None, unique=False,
unique_with=None):
self.name = name
self.required = required
unique_with=None, primary_key=False):
self.name = name if not primary_key else '_id'
self.required = required or primary_key
self.default = default
self.unique = bool(unique or unique_with)
self.unique_with = unique_with
self._loaded = []
self.primary_key = primary_key
def __get__(self, instance, owner):
"""Descriptor for retrieving a value from a field in a document. Do
@@ -73,7 +73,7 @@ class ObjectIdField(BaseField):
def to_mongo(self, value):
if not isinstance(value, pymongo.objectid.ObjectId):
return pymongo.objectid.ObjectId(value)
return pymongo.objectid.ObjectId(str(value))
return value
def prepare_query_value(self, value):
@@ -140,6 +140,8 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
collection = name.lower()
simple_class = True
id_field = None
base_indexes = []
# Subclassed documents inherit collection from superclass
for base in bases:
@@ -154,17 +156,23 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
simple_class = False
collection = base._meta['collection']
id_field = id_field or base._meta.get('id_field')
base_indexes += base._meta.get('indexes', [])
meta = {
'collection': collection,
'allow_inheritance': True,
'max_documents': None,
'max_size': None,
'ordering': [], # default ordering applied at runtime
'indexes': [] # indexes to be ensured at runtime
'indexes': [], # indexes to be ensured at runtime
'id_field': id_field,
}
# Apply document-defined meta options
meta.update(attrs.get('meta', {}))
meta['indexes'] += base_indexes
# Only simple classes - direct subclasses of Document - may set
# allow_inheritance to False
@@ -173,16 +181,14 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
'"allow_inheritance" to False')
attrs['_meta'] = meta
attrs['id'] = ObjectIdField(name='_id')
# Set up collection manager, needs the class to have fields so use
# DocumentMetaclass before instantiating CollectionManager object
new_class = super_new(cls, name, bases, attrs)
new_class.objects = QuerySetManager()
# Generate a list of indexes needed by uniqueness constraints
unique_indexes = []
for field_name, field in new_class._fields.items():
# Generate a list of indexes needed by uniqueness constraints
if field.unique:
field.required = True
unique_fields = [field_name]
@@ -205,10 +211,25 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
unique_fields += unique_with
# Add the new index to the list
index = [(field, pymongo.ASCENDING) for field in unique_fields]
index = [(f, pymongo.ASCENDING) for f in unique_fields]
unique_indexes.append(index)
# Check for custom primary key
if field.primary_key:
if not new_class._meta['id_field']:
new_class._meta['id_field'] = field_name
# Make 'Document.id' an alias to the real primary key field
new_class.id = field
#new_class._fields['id'] = field
else:
raise ValueError('Cannot override primary key field')
new_class._meta['unique_indexes'] = unique_indexes
if not new_class._meta['id_field']:
new_class._meta['id_field'] = 'id'
new_class.id = new_class._fields['id'] = ObjectIdField(name='_id')
return new_class
@@ -271,6 +292,18 @@ class BaseDocument(object):
def __len__(self):
return len(self._data)
def __repr__(self):
try:
u = unicode(self)
except (UnicodeEncodeError, UnicodeDecodeError):
u = '[Bad Unicode data]'
return u'<%s: %s>' % (self.__class__.__name__, u)
def __str__(self):
if hasattr(self, '__unicode__'):
return unicode(self).encode('utf-8')
return '%s object' % self.__class__.__name__
def to_mongo(self):
"""Return data dictionary ready for use with MongoDB.
"""

View File

@@ -0,0 +1,63 @@
from django.contrib.sessions.backends.base import SessionBase, CreateError
from django.core.exceptions import SuspiciousOperation
from django.utils.encoding import force_unicode
from mongoengine.document import Document
from mongoengine import fields
from mongoengine.queryset import OperationError
from datetime import datetime
class MongoSession(Document):
session_key = fields.StringField(primary_key=True, max_length=40)
session_data = fields.StringField()
expire_date = fields.DateTimeField()
meta = {'collection': 'django_session', 'allow_inheritance': False}
class SessionStore(SessionBase):
"""A MongoEngine-based session store for Django.
"""
def load(self):
try:
s = MongoSession.objects(session_key=self.session_key,
expire_date__gt=datetime.now())[0]
return self.decode(force_unicode(s.session_data))
except (IndexError, SuspiciousOperation):
self.create()
return {}
def exists(self, session_key):
return bool(MongoSession.objects(session_key=session_key).first())
def create(self):
while True:
self.session_key = self._get_new_session_key()
try:
self.save(must_create=True)
except CreateError:
continue
self.modified = True
self._session_cache = {}
return
def save(self, must_create=False):
s = MongoSession(session_key=self.session_key)
s.session_data = self.encode(self._get_session(no_load=must_create))
s.expire_date = self.get_expiry_date()
try:
s.save(force_insert=must_create, safe=True)
except OperationError:
if must_create:
raise CreateError
raise
def delete(self, session_key=None):
if session_key is None:
if self.session_key is None:
return
session_key = self.session_key
MongoSession.objects(session_key=session_key).delete()

View File

@@ -56,31 +56,54 @@ class Document(BaseDocument):
__metaclass__ = TopLevelDocumentMetaclass
def save(self, safe=True):
def save(self, safe=True, force_insert=False):
"""Save the :class:`~mongoengine.Document` to the database. If the
document already exists, it will be updated, otherwise it will be
created.
If ``safe=True`` and the operation is unsuccessful, an
:class:`~mongoengine.OperationError` will be raised.
:param safe: check if the operation succeeded before returning
:param force_insert: only try to create a new document, don't allow
updates of existing documents
"""
self.validate()
doc = self.to_mongo()
try:
object_id = self.__class__.objects._collection.save(doc, safe=safe)
collection = self.__class__.objects._collection
if force_insert:
object_id = collection.insert(doc, safe=safe)
else:
object_id = collection.save(doc, safe=safe)
except pymongo.errors.OperationFailure, err:
raise OperationError('Tried to save duplicate unique keys (%s)'
% str(err))
self.id = self._fields['id'].to_python(object_id)
message = 'Could not save document (%s)'
if 'duplicate key' in str(err):
message = 'Tried to save duplicate unique keys (%s)'
raise OperationError(message % str(err))
id_field = self._meta['id_field']
self[id_field] = self._fields[id_field].to_python(object_id)
def delete(self):
def delete(self, safe=False):
"""Delete the :class:`~mongoengine.Document` from the database. This
will only take effect if the document has been previously saved.
:param safe: check if the operation succeeded before returning
"""
object_id = self._fields['id'].to_mongo(self.id)
self.__class__.objects(id=object_id).delete()
id_field = self._meta['id_field']
object_id = self._fields[id_field].to_mongo(self[id_field])
try:
self.__class__.objects(**{id_field: object_id}).delete(safe=safe)
except pymongo.errors.OperationFailure, err:
raise OperationError('Could not delete document (%s)' % str(err))
def reload(self):
"""Reloads all attributes from the database.
.. versionadded:: 0.1.2
"""
obj = self.__class__.objects(id=self.id).first()
id_field = self._meta['id_field']
obj = self.__class__.objects(**{id_field: self[id_field]}).first()
for field in self._fields:
setattr(self, field, obj[field])

View File

@@ -82,6 +82,8 @@ class FloatField(BaseField):
class BooleanField(BaseField):
"""A boolean field type.
.. versionadded:: 0.1.2
"""
def to_python(self, value):
@@ -131,6 +133,9 @@ class EmbeddedDocumentField(BaseField):
def lookup_member(self, member_name):
return self.document._fields.get(member_name)
def prepare_query_value(self, value):
return self.to_mongo(value)
class ListField(BaseField):
"""A list field that wraps a standard field, allowing multiple instances
@@ -163,6 +168,9 @@ class ListField(BaseField):
raise ValidationError('All items in a list field must be of the '
'specified type')
def prepare_query_value(self, value):
return self.field.to_mongo(value)
def lookup_member(self, member_name):
return self.field.lookup_member(member_name)

View File

@@ -1,9 +1,14 @@
from connection import _get_db
import pymongo
import copy
__all__ = ['queryset_manager', 'InvalidQueryError', 'InvalidCollectionError']
__all__ = ['queryset_manager', 'Q', 'InvalidQueryError',
'InvalidCollectionError']
# The maximum number of items to display in a QuerySet.__repr__
REPR_OUTPUT_SIZE = 20
class InvalidQueryError(Exception):
@@ -14,6 +19,88 @@ class OperationError(Exception):
pass
class Q(object):
OR = '||'
AND = '&&'
OPERATORS = {
'eq': 'this.%(field)s == %(value)s',
'neq': 'this.%(field)s != %(value)s',
'gt': 'this.%(field)s > %(value)s',
'gte': 'this.%(field)s >= %(value)s',
'lt': 'this.%(field)s < %(value)s',
'lte': 'this.%(field)s <= %(value)s',
'lte': 'this.%(field)s <= %(value)s',
'in': 'this.%(field)s.indexOf(%(value)s) != -1',
'nin': 'this.%(field)s.indexOf(%(value)s) == -1',
'mod': '%(field)s %% %(value)s',
'all': ('%(value)s.every(function(a){'
'return this.%(field)s.indexOf(a) != -1 })'),
'size': 'this.%(field)s.length == %(value)s',
'exists': 'this.%(field)s != null',
}
def __init__(self, **query):
self.query = [query]
def _combine(self, other, op):
obj = Q()
obj.query = ['('] + copy.deepcopy(self.query) + [op]
obj.query += copy.deepcopy(other.query) + [')']
return obj
def __or__(self, other):
return self._combine(other, self.OR)
def __and__(self, other):
return self._combine(other, self.AND)
def as_js(self, document):
js = []
js_scope = {}
for i, item in enumerate(self.query):
if isinstance(item, dict):
item_query = QuerySet._transform_query(document, **item)
# item_query will values will either be a value or a dict
js.append(self._item_query_as_js(item_query, js_scope, i))
else:
js.append(item)
return pymongo.code.Code(' '.join(js), js_scope)
def _item_query_as_js(self, item_query, js_scope, item_num):
# item_query will be in one of the following forms
# {'age': 25, 'name': 'Test'}
# {'age': {'$lt': 25}, 'name': {'$in': ['Test', 'Example']}
# {'age': {'$lt': 25, '$gt': 18}}
js = []
for i, (key, value) in enumerate(item_query.items()):
op = 'eq'
# Construct a variable name for the value in the JS
value_name = 'i%sf%s' % (item_num, i)
if isinstance(value, dict):
# Multiple operators for this field
for j, (op, value) in enumerate(value.items()):
# Create a custom variable name for this operator
op_value_name = '%so%s' % (value_name, j)
# Update the js scope with the value for this op
js_scope[op_value_name] = value
# Construct the JS that uses this op
operation_js = Q.OPERATORS[op.strip('$')] % {
'field': key,
'value': op_value_name
}
js.append(operation_js)
else:
js_scope[value_name] = value
# Construct the JS for this field
field_js = Q.OPERATORS[op.strip('$')] % {
'field': key,
'value': value_name
}
js.append(field_js)
return ' && '.join(js)
class QuerySet(object):
"""A set of results returned from a query. Wraps a MongoDB cursor,
providing :class:`~mongoengine.Document` objects as the results.
@@ -24,6 +111,7 @@ class QuerySet(object):
self._collection_obj = collection
self._accessed_collection = False
self._query = {}
self._where_clauses = []
# If inheritance is allowed, only return instances and instances of
# subclasses of the class being used
@@ -33,6 +121,10 @@ class QuerySet(object):
def ensure_index(self, key_or_list):
"""Ensure that the given indexes are in place.
:param key_or_list: a single index key or a list of index keys (to
construct a multi-field index); keys may be prefixed with a **+**
or a **-** to determine the index ordering
"""
if isinstance(key_or_list, basestring):
key_or_list = [key_or_list]
@@ -55,10 +147,15 @@ class QuerySet(object):
self._collection.ensure_index(index_list)
return self
def __call__(self, **query):
def __call__(self, *q_objs, **query):
"""Filter the selected documents by calling the
:class:`~mongoengine.QuerySet` with a query.
:param q_objs: :class:`~mongoengine.Q` objects to be used in the query
:param query: Django-style query keyword arguments
"""
for q in q_objs:
self._where_clauses.append(q.as_js(self._document))
query = QuerySet._transform_query(_doc_cls=self._document, **query)
self._query.update(query)
return self
@@ -92,6 +189,9 @@ class QuerySet(object):
def _cursor(self):
if not self._cursor_obj:
self._cursor_obj = self._collection.find(self._query)
# Apply where clauses to cursor
for js in self._where_clauses:
self._cursor_obj.where(js)
# apply default ordering
if self._document._meta['ordering']:
@@ -179,11 +279,13 @@ class QuerySet(object):
def with_id(self, object_id):
"""Retrieve the object matching the id provided.
"""
if not isinstance(object_id, pymongo.objectid.ObjectId):
object_id = pymongo.objectid.ObjectId(str(object_id))
result = self._collection.find_one(object_id)
:param object_id: the value for the id of the document to look up
"""
id_field = self._document._meta['id_field']
object_id = self._document._fields[id_field].to_mongo(object_id)
result = self._collection.find_one({'_id': object_id})
if result is not None:
result = self._document._from_son(result)
return result
@@ -204,6 +306,8 @@ class QuerySet(object):
def limit(self, n):
"""Limit the number of returned documents to `n`. This may also be
achieved using array-slicing syntax (e.g. ``User.objects[:5]``).
:param n: the maximum number of objects to return
"""
self._cursor.limit(n)
# Return self to allow chaining
@@ -212,6 +316,8 @@ class QuerySet(object):
def skip(self, n):
"""Skip `n` documents before returning the results. This may also be
achieved using array-slicing syntax (e.g. ``User.objects[5:]``).
:param n: the number of objects to skip before returning results
"""
self._cursor.skip(n)
return self
@@ -232,6 +338,9 @@ class QuerySet(object):
"""Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The
order may be specified by prepending each of the keys by a + or a -.
Ascending order is assumed.
:param keys: fields to order the query results by; keys may be
prefixed with **+** or **-** to determine the ordering direction
"""
key_list = []
for key in keys:
@@ -248,6 +357,8 @@ class QuerySet(object):
def explain(self, format=False):
"""Return an explain plan record for the
:class:`~mongoengine.queryset.QuerySet`\ 's cursor.
:param format: format the plan before returning it
"""
plan = self._cursor.explain()
@@ -256,10 +367,12 @@ class QuerySet(object):
plan = pprint.pformat(plan)
return plan
def delete(self):
def delete(self, safe=False):
"""Delete the documents matched by the query.
:param safe: check if the operation succeeded before returning
"""
self._collection.remove(self._query)
self._collection.remove(self._query, safe=safe)
@classmethod
def _transform_update(cls, _doc_cls=None, **update):
@@ -312,6 +425,11 @@ class QuerySet(object):
def update(self, safe_update=True, **update):
"""Perform an atomic update on the fields matched by the query.
:param safe: check if the operation succeeded before returning
:param update: Django-style update keyword arguments
.. versionadded:: 0.2
"""
if pymongo.version < '1.1.1':
raise OperationError('update() method requires PyMongo 1.1.1+')
@@ -327,6 +445,11 @@ class QuerySet(object):
def update_one(self, safe_update=True, **update):
"""Perform an atomic update on first field matched by the query.
:param safe: check if the operation succeeded before returning
:param update: Django-style update keyword arguments
.. versionadded:: 0.2
"""
update = QuerySet._transform_update(self._document, **update)
try:
@@ -352,6 +475,12 @@ class QuerySet(object):
collection in use; ``query``, which is an object representing the
current query; and ``options``, which is an object containing any
options specified as keyword arguments.
:param code: a string of Javascript code to execute
:param fields: fields that you will be using in your function, which
will be passed in to your function as arguments
:param options: options that you want available to the function
(accessed in Javascript through the ``options`` object)
"""
fields = [QuerySet._translate_field_name(self._document, f)
for f in fields]
@@ -368,6 +497,9 @@ class QuerySet(object):
def sum(self, field):
"""Sum over the values of the specified field.
:param field: the field to sum over; use dot-notation to refer to
embedded document fields
"""
sum_func = """
function(sumField) {
@@ -382,6 +514,9 @@ class QuerySet(object):
def average(self, field):
"""Average over the values of the specified field.
:param field: the field to average over; use dot-notation to refer to
embedded document fields
"""
average_func = """
function(averageField) {
@@ -402,6 +537,9 @@ class QuerySet(object):
"""Returns a dictionary of all items present in a list field across
the whole queried set of documents, and their corresponding frequency.
This is useful for generating tag clouds, or searching documents.
:param list_field: the list field to use
:param normalize: normalize the results so they add to 1.0
"""
freq_func = """
function(listField) {
@@ -427,6 +565,11 @@ class QuerySet(object):
"""
return self.exec_js(freq_func, list_field, normalize=normalize)
def __repr__(self):
data = list(self[:REPR_OUTPUT_SIZE + 1])
if len(data) > REPR_OUTPUT_SIZE:
data[-1] = "...(remaining elements truncated)..."
return repr(data)
class InvalidCollectionError(Exception):
pass