Permalink
Browse files

Commented and tested all (new) features. Refined existing tests.

  • Loading branch information...
yablokoff committed May 1, 2012
1 parent 456bb37 commit 8f7b190ad58c6fc18de58405dfa2bb74172c3a57
Showing with 120 additions and 94 deletions.
  1. +0 −87 minimongo/config.py
  2. +0 −2 minimongo/exceptions.py
  3. +7 −4 minimongo/model.py
  4. +112 −0 minimongo/tests/test_model.py
  5. +1 −1 minimongo/tests/test_utils.py
View
@@ -1,87 +0,0 @@
-#!/bin/python
-"""
-Provides the 2 symbols MONGODB_HOST and MONGODB_PORT that are used my
-minimingo to connect to the proper database.
-
-Uses the following 3 strategies:
-1. import os.environ['MINIMONGO_SETTINGS_MODULE']
-2. import 'minimongo.app_config'
-3. default values (localhost, 27017)
-"""
-import os
-import sys
-
-#####################################################################
-# DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED #
-# DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED #
-# DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED #
-# DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED DEPRECATED #
-#####################################################################
-
-# Default values for MONGODB_HOST and MONGODB_PORT if no custom config
-# module is specified, or if we're unable to 'from minimongo.app_config import
-# MONGODB_HOST, MONGODB_PORT'
-MONGODB_HOST = 'localhost'
-MONGODB_PORT = 27017
-
-
-# Taken from Django, which says "Taken from Python 2.7..."
-def _resolve_name(name, package, level):
- """Return the absolute name of the module to be imported."""
- if not hasattr(package, 'rindex'):
- raise ValueError("'package' not set to a string")
- dot = len(package)
- for x in xrange(level, 1, -1):
- try:
- dot = package.rindex('.', 0, dot)
- except ValueError:
- raise ValueError("attempted relative import beyond top-level "
- "package")
- return "%s.%s" % (package[:dot], name)
-
-
-def import_module(name, package=None):
- """Import a module.
-
- The 'package' argument is required when performing a relative import. It
- specifies the package to use as the anchor point from which to resolve the
- relative import to an absolute import.
-
- """
- if name.startswith('.'):
- if not package:
- raise TypeError("relative imports require the 'package' argument")
- level = 0
- for character in name:
- if character != '.':
- break
- level += 1
- name = _resolve_name(name[level:], package, level)
- __import__(name)
- return sys.modules[name]
-
-if __name__ != '__main__':
-
- settings_modules = []
-
- try:
- settings_modules.append(os.environ['MINIMONGO_SETTINGS_MODULE'])
- except KeyError, e:
- pass
-
- # Here are the other 2 places that we try to import configs from:
- settings_modules.append('minimongo.app_config')
- settings_modules.append('minimongo_config')
-
- for module_name in settings_modules:
- try:
- module = import_module(module_name)
- MONGODB_HOST = module.MONGODB_HOST
- MONGODB_PORT = module.MONGODB_PORT
- # Once we get a successfull config module import, we break out
- # of the loop above.
- break
- except ImportError, exc:
- # Error importing this modlue, so we continue
- pass
-
View
@@ -1,5 +1,3 @@
-__author__ = 'yablokoff'
-
class ExistingReferencesError(Exception):
def __init__(self, msg):
self.msg = msg
View
@@ -110,7 +110,8 @@ def __init__(self, initial=None, **kwargs):
if initial:
for key, value in initial.iteritems():
# Can't just say self[k] = v here b/c of recursion.
- self.__setitem__(key, value)
+ self[key] = value
+ # self.__setitem__(key, value)
# Process the other arguments (assume they are also default values).
# This is the same behavior as the regular dict constructor.
@@ -167,7 +168,6 @@ class Model(AttrDict):
>>> foo.bar == 42
True
"""
-
__metaclass__ = ModelBase
def __str__(self):
@@ -210,19 +210,22 @@ def dbref(self, with_database=True, **kwargs):
return DBRef(self._meta.collection, self._id, database, **kwargs)
def remove(self):
- """Remove this object from the database."""
+ """ Remove this object from the database.
+ Handle all dependent document before remove itself."""
if self.__class__.backward_references:
self.remove_backward_refs(self.__class__.backward_references)
return self.collection.remove(self._id)
def remove_backward_refs(self, backward_refs):
+ """ Handles all documents in dependent Models.
+ Action accordingly to type of the relation."""
dbref_to_self = self.dbref()
for backward_ref in backward_refs:
field_name, backward_ref_cls = backward_ref[0], backward_ref[1]
if backward_ref[2] == NOTHING:
continue
elif backward_ref[2] == CASCADE:
- docs = backward_ref_cls.collection.remove({field_name:dbref_to_self})
+ backward_ref_cls.collection.remove({field_name:dbref_to_self})
else:
docs = backward_ref_cls.collection.find({field_name:dbref_to_self})
if backward_ref[2] == NULLIFY:
@@ -7,6 +7,8 @@
from bson import DBRef
from minimongo import Collection, Index, Model
+from minimongo.model import CASCADE, DENY, NOTHING, NULLIFY
+from minimongo.exceptions import ExistingReferencesError
from pymongo.errors import DuplicateKeyError
@@ -83,6 +85,42 @@ class Meta:
lambda v: float(v * (4.0 / 3.0))),
)
+class TestReferenceObject(Model):
+ class Meta:
+ database = 'minimongo_test'
+ collection = 'minimongo_ref_obj'
+
+class TestReferenceSubjectNothing(Model):
+ class Meta:
+ database = 'minimongo_test'
+ collection = 'minimongo_ref_subj_nothing'
+ references = (
+ ("ref_field", TestReferenceObject, NOTHING),
+ )
+
+class TestReferenceSubjectDeny(Model):
+ class Meta:
+ database = 'minimongo_test'
+ collection = 'minimongo_ref_subj_deny'
+ references = (
+ ("ref_field", TestReferenceObject, DENY),
+ )
+
+class TestReferenceSubjectNullify(Model):
+ class Meta:
+ database = 'minimongo_test'
+ collection = 'minimongo_ref_subj_nullify'
+ references = (
+ ("ref_field", TestReferenceObject, NULLIFY),
+ )
+
+class TestReferenceSubjectCascade(Model):
+ class Meta:
+ database = 'minimongo_test'
+ collection = 'minimongo_ref_subj_cascade'
+ references = (
+ ("ref_field", TestReferenceObject, CASCADE),
+ )
def setup():
# Make sure we start with a clean, empty DB.
@@ -198,6 +236,17 @@ def test_mongo_update():
assert model.x == 0
assert model.y == 1
+ # this should be done to update local copy after mongo_update
+ model.mongo_update({'$inc': {'counter': 1}})
+ assert model.counter == 11
+ model.load({"counter":1})
+ assert model.counter == 12
+
+ # call mongo_update with no args
+ model_copy = TestModel.collection.find_one({'_id': model._id})
+ model.mongo_update()
+ assert model == model_copy
+
def test_load():
"""Partial loading of documents.x"""
@@ -445,6 +494,54 @@ def test_dbref():
ref_a = object_a.dbref(name="foo")
assert ref_a.name == 'foo'
+def test_consistency_nothing():
+ '''
+ Test consistency methods on delete.
+ '''
+ # Test modifier 'NOTHING'
+ obj1 = TestReferenceObject({'x':1}).save()
+ subj1 = TestReferenceSubjectNothing({"ref_field":obj1.dbref(), 'x':1}).save()
+ subj2 = TestReferenceSubjectNothing({"ref_field":obj1.dbref(), 'x':2}).save()
+ obj1.remove()
+ assert TestReferenceObject.collection.find({"x":1}).count() == 0
+ assert TestReferenceSubjectNothing.collection.find({"x":1})[0] == subj1
+ assert TestReferenceSubjectNothing.collection.find({"x":2})[0] == subj2
+
+def test_consistency_deny():
+ # Test modifier 'DENY'
+ obj2 = TestReferenceObject({'x':1}).save()
+ subj3 = TestReferenceSubjectDeny({"ref_field":obj2.dbref(), 'x':1}).save()
+ subj4 = TestReferenceSubjectDeny({"ref_field":obj2.dbref(), 'x':2}).save()
+ with pytest.raises(ExistingReferencesError):
+ obj2.remove()
+ e = ExistingReferencesError("msg")
+ assert isinstance(e.__str__(), str)
+ assert TestReferenceObject.collection.find({"x":1})[0] == obj2
+ assert TestReferenceSubjectDeny.collection.find({"x":1})[0] == subj3
+ assert TestReferenceSubjectDeny.collection.find({"x":2})[0] == subj4
+ TestReferenceSubjectDeny.collection.remove({"$or":[{"x":1},{"x":2}]})
+ assert TestReferenceSubjectDeny.collection.find().count() == 0
+ obj2.remove()
+ assert TestReferenceObject.collection.find().count() == 0
+
+def test_consistency_cascade():
+ # Test modifier 'CASCADE'
+ obj3 = TestReferenceObject({'x':1}).save()
+ subj5 = TestReferenceSubjectCascade({"ref_field":obj3.dbref(), 'x':1}).save()
+ subj6 = TestReferenceSubjectCascade({"ref_field":obj3.dbref(), 'x':2}).save()
+ obj3.remove()
+ assert TestReferenceObject.collection.find().count() == 0
+ assert TestReferenceSubjectCascade.collection.find().count() == 0
+
+def test_consistency_nullify():
+ # Test modifier 'NULLIFY'
+ obj4 = TestReferenceObject({'x':1}).save()
+ subj7 = TestReferenceSubjectNullify({"ref_field":obj4.dbref(), 'x':1}).save()
+ subj8 = TestReferenceSubjectNullify({"ref_field":obj4.dbref(), 'x':2}).save()
+ obj4.remove()
+ assert TestReferenceObject.collection.find().count() == 0
+ assert TestReferenceSubjectNullify.collection.find().count() == 2
+ assert TestReferenceSubjectNullify.collection.find({"ref_field":{"$exists":True}}).count() == 0
def test_db_and_collection_names():
'''Test the methods that return the current class's DB and
@@ -501,6 +598,14 @@ class Meta:
assert SomeModel.collection.name == 'some_model'
+def test_none_database():
+ """Test when db in None"""
+ with pytest.raises(Exception):
+ class TestNoneDatabase(Model):
+ class Meta:
+ collection = "none_database"
+
+
def test_no_auto_index():
TestNoAutoIndexModel({'x': 1}).save()
@@ -520,6 +625,13 @@ def test_interface_models():
with pytest.raises(Exception):
test_interface_instance.save()
+# with pytest.raises(Exception):
+# test_interface_instance.collection.drop()
+ with pytest.raises(Exception):
+ test_interface_instance.collection.find_one()
+ with pytest.raises(Exception):
+ test_interface_instance.collection.find()
+
test_model_instance = TestModelImplementation()
test_model_instance.x = 123
test_model_instance.save()
@@ -41,7 +41,7 @@ def test_configure():
module = ModuleType('config')
module.MONGODB_FOO = 'bar'
module.NON_MONGO_ATTR = 'bar'
- configure(foo='bar')
+ configure(module)
assert not hasattr(_Options, 'NON_MONGO_ATTR')
assert not hasattr(_Options, 'MONGODB_FOO')
assert hasattr(_Options, 'foo')

0 comments on commit 8f7b190

Please sign in to comment.