diff --git a/mongomap/__init__.py b/mongomap/__init__.py index e69de29b..214f0fa4 100644 --- a/mongomap/__init__.py +++ b/mongomap/__init__.py @@ -0,0 +1,8 @@ +from document import Document +import fields +from fields import * + +__all__ = fields.__all__ + ['Document'] + +__author__ = 'Harry Marr' +__version__ = '0.1' diff --git a/mongomap/base.py b/mongomap/base.py index b6542d8a..cbcb8b8f 100644 --- a/mongomap/base.py +++ b/mongomap/base.py @@ -65,6 +65,10 @@ class DocumentMetaclass(type): """ def __new__(cls, name, bases, attrs): + metaclass = attrs.get('__metaclass__') + if metaclass and issubclass(metaclass, DocumentMetaclass): + return type.__new__(cls, name, bases, attrs) + doc_fields = {} # Include all fields present in superclasses @@ -83,6 +87,31 @@ class DocumentMetaclass(type): return type.__new__(cls, name, bases, attrs) +class TopLevelDocumentMetaclass(DocumentMetaclass): + """Metaclass for top-level documents (i.e. documents that have their own + collection in the database. + """ + + def __new__(cls, name, bases, attrs): + # Classes defined in this package are abstract and should not have + # their own metadata with DB collection, etc. + if attrs.get('__metaclass__') == TopLevelDocumentMetaclass: + return DocumentMetaclass.__new__(cls, name, bases, attrs) + + collection = name.lower() + # Subclassed documents inherit collection from superclass + for base in bases: + if hasattr(base, '_meta') and 'collection' in base._meta: + collection = base._meta['collection'] + + meta = { + 'collection': collection, + } + meta.update(attrs.get('meta', {})) + attrs['_meta'] = meta + return DocumentMetaclass.__new__(cls, name, bases, attrs) + + class BaseDocument(object): def __init__(self, **values): @@ -99,3 +128,19 @@ class BaseDocument(object): # Use _data rather than _fields as iterator only looks at names so # values don't need to be converted to Python types return iter(self._data) + + def __getitem__(self, name): + """Dictionary-style field access, return a field's value if present. + """ + try: + return getattr(self, name) + except AttributeError: + raise KeyError(name) + + def __setitem__(self, name, value): + """Dictionary-style field access, set a field's value. + """ + # Ensure that the field exists before settings its value + if name not in self._fields: + raise KeyError(name) + return setattr(self, name, value) diff --git a/mongomap/document.py b/mongomap/document.py index 96d43003..3fe2bb31 100644 --- a/mongomap/document.py +++ b/mongomap/document.py @@ -1,30 +1,7 @@ -from base import DocumentMetaclass, BaseDocument +from base import DocumentMetaclass, TopLevelDocumentMetaclass, BaseDocument #import pymongo -class TopLevelDocumentMetaclass(DocumentMetaclass): - """Metaclass for top-level documents (i.e. documents that have their own - collection in the database. - """ - - def __new__(cls, name, bases, attrs): - # Classes defined in this module are abstract and should not have - # their own metadata with DB collection, etc. - if attrs['__module__'] != __name__: - collection = name.lower() - # Subclassed documents inherit collection from superclass - for base in bases: - if hasattr(base, '_meta') and 'collection' in base._meta: - collection = base._meta['collection'] - - meta = { - 'collection': collection, - } - meta.update(attrs.get('meta', {})) - attrs['_meta'] = meta - return DocumentMetaclass.__new__(cls, name, bases, attrs) - - class EmbeddedDocument(BaseDocument): __metaclass__ = DocumentMetaclass diff --git a/tests/document.py b/tests/document.py index dc0c759b..17365a1d 100644 --- a/tests/document.py +++ b/tests/document.py @@ -1,10 +1,15 @@ import unittest -from mongomap.document import Document -from mongomap.fields import StringField, IntField +from mongomap import Document, StringField, IntField class DocumentTest(unittest.TestCase): + + def setUp(self): + class Person(Document): + name = StringField() + age = IntField() + self.Person = Person def test_definition(self): """Ensure that document may be defined using fields. @@ -23,32 +28,40 @@ class DocumentTest(unittest.TestCase): # Test iteration over fields fields = list(Person()) self.assertTrue('name' in fields and 'age' in fields) + # Ensure Document isn't treated like an actual document + self.assertFalse(hasattr(Document, '_fields')) def test_inheritance(self): """Ensure that document may inherit fields from a superclass document. """ - class Person(Document): - name = StringField() - - class Employee(Person): + class Employee(self.Person): salary = IntField() self.assertTrue('name' in Employee._fields) self.assertTrue('salary' in Employee._fields) self.assertEqual(Employee._meta['collection'], - Person._meta['collection']) + self.Person._meta['collection']) def test_creation(self): """Ensure that document may be created using keyword arguments. """ - class Person(Document): - name = StringField() - age = IntField() - - person = Person(name="Test User", age=30) + person = self.Person(name="Test User", age=30) self.assertEqual(person.name, "Test User") self.assertEqual(person.age, 30) + def test_dictionary_access(self): + """Ensure that dictionary-style field access works properly. + """ + person = self.Person(name='Test User', age=30) + self.assertEquals(person['name'], 'Test User') + + self.assertRaises(KeyError, person.__getitem__, 'salary') + self.assertRaises(KeyError, person.__setitem__, 'salary', 50) + + person['name'] = 'Another User' + self.assertEquals(person['name'], 'Another User') + + if __name__ == '__main__': unittest.main() diff --git a/tests/fields.py b/tests/fields.py index dbb58492..1b2d6f72 100644 --- a/tests/fields.py +++ b/tests/fields.py @@ -1,7 +1,6 @@ import unittest -from mongomap.document import Document -from mongomap.fields import * +from mongomap import * class FieldTest(unittest.TestCase):