Skip to content
Browse files

Initial version

  • Loading branch information...
1 parent ad5f1da commit b679587e155477d8e5a0419b44f1a04417eecb7a @vmalloc committed Mar 8, 2012
Showing with 268 additions and 0 deletions.
  1. +24 −0 LICENSE
  2. 0 README.rst
  3. +75 −0 mongomock/__init__.py
  4. +1 −0 mongomock/__version__.py
  5. +16 −0 mongomock/object_id.py
  6. +23 −0 setup.py
  7. +108 −0 tests/test__mongomock.py
  8. +10 −0 tests/test__readme_doctest.py
  9. +11 −0 tox.ini
View
24 LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2012, Rotem Yaari <vmalloc@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of organization nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY Rotem Yaari ''AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL Rotem Yaari BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
0 README.rst
No changes.
View
75 mongomock/__init__.py
@@ -0,0 +1,75 @@
+from .__version__ import __version__
+from .object_id import ObjectId
+from sentinels import NOTHING
+
+__all__ = ['Connection', 'Database', 'Collection', 'ObjectId']
+
+class Connection(object):
+ def __init__(self):
+ super(Connection, self).__init__()
+ self._databases = {}
+ def __getitem__(self, db_name):
+ db = self._databases.get(db_name, None)
+ if db is None:
+ db = self._databases[db_name] = Database(self)
+ return db
+ def __getattr__(self, attr):
+ return self[attr]
+
+class Database(object):
+ def __init__(self, conn):
+ super(Database, self).__init__()
+ self._collections = {'system.indexes' : Collection(self)}
+ def __getitem__(self, db_name):
+ db = self._collections.get(db_name, None)
+ if db is None:
+ db = self._collections[db_name] = Collection(self)
+ return db
+ def __getattr__(self, attr):
+ return self[attr]
+ def collection_names(self):
+ return list(self._collections.keys())
+
+class Collection(object):
+ def __init__(self, db):
+ super(Collection, self).__init__()
+ self._documents = {}
+ def insert(self, data):
+ if isinstance(data, list):
+ return [self._insert(element) for element in data]
+ return self._insert(data)
+ def _insert(self, data):
+ object_id = ObjectId()
+ assert object_id not in self._documents
+ self._documents[object_id] = dict(data, _id=object_id)
+ return object_id
+ def update(self, spec, document):
+ for existing_document in self._iter_documents(spec):
+ document_id = existing_document['_id']
+ existing_document.clear()
+ existing_document.update(document)
+ existing_document['_id'] = document_id
+ def find(self, filter=None):
+ dataset = (document.copy() for document in self._iter_documents(filter))
+ return Cursor(dataset)
+ def _iter_documents(self, filter=None):
+ return (document for document in self._documents.itervalues() if self._filter_applies(filter, document))
+ def find_one(self, filter=None):
+ try:
+ return next(self.find(filter))
+ except StopIteration:
+ return None
+ def _filter_applies(self, filter, document):
+ if filter is None:
+ return True
+ return all(filter.get(key, NOTHING) == document.get(key, NOTHING)
+ for key in filter.keys())
+
+class Cursor(object):
+ def __init__(self, dataset):
+ super(Cursor, self).__init__()
+ self._dataset = dataset
+ def __iter__(self):
+ return self
+ def next(self):
+ return next(self._dataset)
View
1 mongomock/__version__.py
@@ -0,0 +1 @@
+__version__ = "0.0.1"
View
16 mongomock/object_id.py
@@ -0,0 +1,16 @@
+import uuid
+
+class ObjectId(object):
+ def __init__(self):
+ super(ObjectId, self).__init__()
+ self._id = uuid.uuid1()
+ def __eq__(self, other):
+ return isinstance(other, ObjectId) and other._id == self._id
+ def __ne__(self, other):
+ return not (self == other)
+ def __hash__(self):
+ return hash(self._id)
+ def __repr__(self):
+ return 'ObjectId({})'.format(self._id)
+ def __str__(self):
+ return self._id
View
23 setup.py
@@ -0,0 +1,23 @@
+import os
+import itertools
+from setuptools import setup, find_packages
+
+with open(os.path.join(os.path.dirname(__file__), "mongomock", "__version__.py")) as version_file:
+ exec(version_file.read())
+
+setup(name="mongomock",
+ classifiers = [
+ "Programming Language :: Python :: 2.7",
+ ],
+ description="Fake pymongo stub for testing simple MongoDB-dependent code",
+ license="BSD",
+ author="Rotem Yaari",
+ author_email="vmalloc@gmail.com",
+ version=__version__,
+ packages=find_packages(exclude=["tests"]),
+ install_requires=[
+ "sentinels",
+ ],
+ scripts=[],
+ namespace_packages=[]
+ )
View
108 tests/test__mongomock.py
@@ -0,0 +1,108 @@
+import copy
+from unittest import TestCase
+from mongomock import Connection, Database, Collection, ObjectId
+
+class FakePymongoConnectionTest(TestCase):
+ def setUp(self):
+ super(FakePymongoConnectionTest, self).setUp()
+ self.conn = Connection()
+
+class DatabaseGettingTest(FakePymongoConnectionTest):
+ def test__getting_database_via_getattr(self):
+ db1 = self.conn.some_database_here
+ db2 = self.conn.some_database_here
+ self.assertIs(db1, db2)
+ self.assertIs(db1, self.conn['some_database_here'])
+ self.assertIsInstance(db1, Database)
+ def test__getting_database_via_getitem(self):
+ db1 = self.conn['some_database_here']
+ db2 = self.conn['some_database_here']
+ self.assertIs(db1, db2)
+ self.assertIs(db1, self.conn.some_database_here)
+ self.assertIsInstance(db1, Database)
+
+class FakePymongoDatabaseTest(FakePymongoConnectionTest):
+ def setUp(self):
+ super(FakePymongoDatabaseTest, self).setUp()
+ self.db = self.conn['somedb']
+
+class FakePymongoDatabaseAPITest(FakePymongoDatabaseTest):
+ def test__get_collection_names(self):
+ self.db.a
+ self.db.b
+ self.assertItemsEqual(self.db.collection_names(), ['a', 'b', 'system.indexes'])
+
+class CollectionGettingTest(FakePymongoDatabaseTest):
+ def test__getting_collection_via_getattr(self):
+ col1 = self.db.some_collection_here
+ col2 = self.db.some_collection_here
+ self.assertIs(col1, col2)
+ self.assertIs(col1, self.db['some_collection_here'])
+ self.assertIsInstance(col1, Collection)
+ def test__getting_collection_via_getitem(self):
+ col1 = self.db['some_collection_here']
+ col2 = self.db['some_collection_here']
+ self.assertIs(col1, col2)
+ self.assertIs(col1, self.db.some_collection_here)
+ self.assertIsInstance(col1, Collection)
+
+class CollectionTest(FakePymongoDatabaseTest):
+ def setUp(self):
+ super(CollectionTest, self).setUp()
+ self.collection = self.db.collection
+ def test__inserting(self):
+ data = dict(a=1, b=2, c="data")
+ object_id = self.collection.insert(data)
+ self.assertIsInstance(object_id, ObjectId)
+ def test__inserted_document(self):
+ data = dict(a=1, b=2)
+ data_before_insertion = data.copy()
+ object_id = self.collection.insert(data)
+ retrieved = self.collection.find_one(dict(_id=object_id))
+ self.assertEquals(retrieved, dict(data, _id=object_id))
+ self.assertIsNot(data, retrieved)
+ self.assertEquals(data, data_before_insertion)
+ def test__bulk_insert(self):
+ objects = [dict(a=2), dict(a=3, b=5), dict(name="bla")]
+ original_objects = copy.deepcopy(objects)
+ ids = self.collection.insert(objects)
+ expected_objects = [dict(obj, _id=id) for id, obj in zip(ids, original_objects)]
+ self.assertEquals(sorted(self.collection.find()), sorted(expected_objects))
+ # make sure objects were not changed in-place
+ self.assertEquals(objects, original_objects)
+
+class DocumentTest(FakePymongoDatabaseTest):
+ def setUp(self):
+ super(DocumentTest, self).setUp()
+ self.collection = self.db.collection
+ data = dict(a=1, b=2, c="blap")
+ self.document_id = self.collection.insert(dict(a=1, b=2, c="blap"))
+ self.document = dict(data, _id=self.document_id)
+
+class FindTest(DocumentTest):
+ def test__find_returns_cursors(self):
+ self.assertNotIsInstance(self.collection.find(), list)
+ self.assertNotIsInstance(self.collection.find(), tuple)
+ def test__find_single_document(self):
+ self.assertEquals(
+ [self.document],
+ list(self.collection.find()),
+ )
+ def test__find_returns_new_object(self):
+ list(self.collection.find())[0]['new_field'] = 20
+ self.test__find_single_document()
+ def test__find_by_attributes(self):
+ self.collection.insert(dict(name="new"))
+ self.collection.insert(dict(name="another new"))
+ self.assertEquals(len(list(self.collection.find())), 3)
+ self.assertEquals(
+ list(self.collection.find(dict(_id=self.document['_id']))),
+ [self.document]
+ )
+
+class UpdateTest(DocumentTest):
+ def test__update(self):
+ new_document = dict(new_attr=2)
+ self.collection.update(dict(a=self.document['a']), new_document)
+ expected_new_document = dict(new_document, _id=self.document_id)
+ self.assertEquals(list(self.collection.find()), [expected_new_document])
View
10 tests/test__readme_doctest.py
@@ -0,0 +1,10 @@
+from unittest import TestCase
+import os
+import doctest
+
+class ReadMeDocTest(TestCase):
+ def test__readme_doctests(self):
+ readme_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "README.rst"))
+ self.assertTrue(os.path.exists(readme_path))
+ result = doctest.testfile(readme_path, module_relative=False)
+ self.assertEquals(result.failed, 0, "%s tests failed!" % result.failed)
View
11 tox.ini
@@ -0,0 +1,11 @@
+[tox]
+envlist = py26,py27,py32
+
+[testenv]
+deps=nose
+commands=nosetests -w {toxinidir}/tests
+changedir = {envdir}
+
+[testenv:py26]
+deps=unittest2
+ nose

0 comments on commit b679587

Please sign in to comment.
Something went wrong with that request. Please try again.