[Checkins] SVN: megrok.rdb/trunk/src/megrok/rdb/ * Add a test for python -> rdb table creation. This is a different formulation

Martijn Faassen faassen at infrae.com
Fri Aug 15 15:30:34 EDT 2008


Log message for revision 89891:
  * Add a test for python -> rdb table creation. This is a different formulation
    of README.txt, and may in fact be more readable, see megrok.rdb.tests.creation
  
  * Add a test for rdb table -> python reflection. The table is already
    defined in the relational database and is reflected onto the Python 
    models if the rdb.reflected() directive is used.
  
  These two new tests are grok-style tests. The grokked code is at the
  bottom, and the tests in the docstring at the top.
  

Changed:
  U   megrok.rdb/trunk/src/megrok/rdb/__init__.py
  U   megrok.rdb/trunk/src/megrok/rdb/directive.py
  U   megrok.rdb/trunk/src/megrok/rdb/meta.py
  A   megrok.rdb/trunk/src/megrok/rdb/setup.py
  A   megrok.rdb/trunk/src/megrok/rdb/testing.py
  A   megrok.rdb/trunk/src/megrok/rdb/tests/
  A   megrok.rdb/trunk/src/megrok/rdb/tests/__init__.py
  A   megrok.rdb/trunk/src/megrok/rdb/tests/creation.py
  A   megrok.rdb/trunk/src/megrok/rdb/tests/reflection.py
  A   megrok.rdb/trunk/src/megrok/rdb/tests/test_rdb.py
  D   megrok.rdb/trunk/src/megrok/rdb/tests.py

-=-
Modified: megrok.rdb/trunk/src/megrok/rdb/__init__.py
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/__init__.py	2008-08-15 18:50:31 UTC (rev 89890)
+++ megrok.rdb/trunk/src/megrok/rdb/__init__.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -1,6 +1,7 @@
 from megrok.rdb.components import Model, Container
 from megrok.rdb.schema import Fields
-from megrok.rdb.directive import key, metadata, tablename
+from megrok.rdb.directive import key, metadata, tablename, reflected
+from megrok.rdb.setup import setupDatabase
 
 from sqlalchemy import MetaData
 

Modified: megrok.rdb/trunk/src/megrok/rdb/directive.py
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/directive.py	2008-08-15 18:50:31 UTC (rev 89890)
+++ megrok.rdb/trunk/src/megrok/rdb/directive.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -1,4 +1,4 @@
-from martian import Directive, CLASS, CLASS_OR_MODULE, ONCE
+from martian import MarkerDirective, Directive, CLASS, CLASS_OR_MODULE, ONCE
 
 # XXX add proper validation logic
 
@@ -16,3 +16,8 @@
     scope = CLASS
     store = ONCE
     default = u''
+
+class reflected(MarkerDirective):
+    scope = CLASS_OR_MODULE
+    store = ONCE
+

Modified: megrok.rdb/trunk/src/megrok/rdb/meta.py
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/meta.py	2008-08-15 18:50:31 UTC (rev 89890)
+++ megrok.rdb/trunk/src/megrok/rdb/meta.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -11,9 +11,17 @@
     martian.component(rdb.Model)
     martian.directive(rdb.tablename, get_default=default_tablename)
     martian.directive(rdb.metadata)
+    martian.directive(rdb.reflected)
     
-    def execute(self, class_, tablename, metadata, **kw):
+    def execute(self, class_, tablename, metadata, reflected, **kw):
         class_.__tablename__ = tablename
+        if reflected:
+            if not hasattr(metadata, '_reflected_registry'):
+                metadata._reflected_registry = {}
+            metadata._reflected_registry[class_] = None
+            # if this table is reflected, don't instrument now but
+            # manually map later
+            return True
         # we associate the _decl_registry with the metadata object
         # to make sure it's unique per metadata. A bit of a hack..
         if not hasattr(metadata, '_decl_registry'):

Added: megrok.rdb/trunk/src/megrok/rdb/setup.py
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/setup.py	                        (rev 0)
+++ megrok.rdb/trunk/src/megrok/rdb/setup.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -0,0 +1,51 @@
+from zope import component
+
+from sqlalchemy.orm import mapper
+from sqlalchemy.ext.declarative import instrument_declarative
+
+from z3c.saconfig.interfaces import IEngineFactory
+
+def setupDatabase(metadata):
+    """Set up of ORM for engine in current site.
+
+    This will:
+
+    * reflect any reflected tables that need to be reflected from the database
+      into classes.
+
+    * create any tables in the database that haven't been yet reflected.    
+    """
+    reflectTables(metadata)
+    createTables(metadata)
+
+def reflectTables(metadata):
+    """Reflect tables into ORM.
+    """
+    if getattr(metadata, '_reflected_completed', False):
+        # XXX thread safety?
+        return
+    if not getattr(metadata, '_reflected_registry', {}):
+        # nothing to reflect
+        return
+    # first reflect database-defined schemas into metadata
+    engine = Engine()
+    metadata.reflect(bind=engine)
+    if not hasattr(metadata, '_decl_registry'):
+        metadata._decl_registry = {}
+    # now declaratively set up any reflected classes
+    for class_ in metadata._reflected_registry.keys():
+        instrument_declarative(class_, metadata._decl_registry, metadata)
+    # XXX thread safety?
+    metadata._reflected_completed = True
+
+def createTables(metadata):
+    """Create class-specified tables.
+    """
+    engine = Engine()
+    metadata.create_all(engine)
+
+def Engine():
+    """Get the engine in the current session.
+    """
+    engine_factory = component.getUtility(IEngineFactory)
+    return engine_factory()

Added: megrok.rdb/trunk/src/megrok/rdb/testing.py
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/testing.py	                        (rev 0)
+++ megrok.rdb/trunk/src/megrok/rdb/testing.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -0,0 +1,21 @@
+from zope import component
+
+from z3c.saconfig import EngineFactory
+from z3c.saconfig.interfaces import IEngineFactory
+
+from z3c.saconfig import GloballyScopedSession
+from z3c.saconfig.interfaces import IScopedSession
+
+def configureEngine(TEST_DSN='sqlite:///:memory:'):
+    """Utility function for tests to set up an in-memory test database.
+
+    Returns engine object.
+    """
+    engine_factory = EngineFactory(TEST_DSN)
+    component.provideUtility(engine_factory, provides=IEngineFactory)
+
+    scoped_session = GloballyScopedSession()
+    component.provideUtility(scoped_session, provides=IScopedSession)
+    
+    return engine_factory()
+

Added: megrok.rdb/trunk/src/megrok/rdb/tests/__init__.py
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/tests/__init__.py	                        (rev 0)
+++ megrok.rdb/trunk/src/megrok/rdb/tests/__init__.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -0,0 +1,2 @@
+#
+

Added: megrok.rdb/trunk/src/megrok/rdb/tests/creation.py
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/tests/creation.py	                        (rev 0)
+++ megrok.rdb/trunk/src/megrok/rdb/tests/creation.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -0,0 +1,76 @@
+"""
+A model by default also defines a relationa l database table in Python
+(as opposed to reflecting it from the database schema).
+
+Let's first grok things::
+
+  >>> from grok.testing import grok
+  >>> grok('megrok.rdb.meta')
+  >>> grok(__name__)
+
+We need to set up an engine::
+
+  >>> from megrok.rdb.testing import configureEngine
+  >>> engine = configureEngine()
+
+We now need to create the tables we defined in our database::
+
+  >>> rdb.setupDatabase(metadata)
+
+Let's start using the database now::
+
+  >>> session = rdb.Session()
+  >>> philosophy = Department(name='Philosophy')
+  >>> session.add(philosophy)
+  >>> logic = Course(name='Logic')
+  >>> ethics = Course(name='Ethics')
+  >>> metaphysics = Course(name='Metaphysics')
+  >>> session.add_all([logic, ethics, metaphysics])
+  
+Let's now add them to the courses container::
+
+  >>> philosophy.courses.set(logic)
+  >>> philosophy.courses.set(ethics)
+  >>> philosophy.courses.set(metaphysics)
+
+We can now verify that the courses are there::
+
+  >>> [(course.id, course.name, course.department_id) for course in
+  ... session.query(Course)]
+  [(1, 'Logic', 1), (2, 'Ethics', 1), (3, 'Metaphysics', 1)]
+
+  >>> for key, value in sorted(philosophy.courses.items()):
+  ...   print key, value.name, value.department.name
+  1 Logic Philosophy
+  2 Ethics Philosophy
+  3 Metaphysics Philosophy
+"""
+
+
+import grok
+from megrok import rdb
+
+from sqlalchemy import Column, ForeignKey
+from sqlalchemy.types import Integer, String
+from sqlalchemy.orm import relation
+
+metadata = rdb.MetaData()
+
+rdb.metadata(metadata)
+
+class Courses(rdb.Container):
+    pass
+
+class Department(rdb.Model):
+    id = Column('id', Integer, primary_key=True)
+    name = Column('name', String(50))
+    courses = relation('Course', 
+                       backref='department',
+                       collection_class=Courses)
+
+class Course(rdb.Model):
+    id = Column('id', Integer, primary_key=True)
+    department_id = Column('department_id', Integer, 
+                           ForeignKey('department.id'))
+    name = Column('name', String(50))
+

Added: megrok.rdb/trunk/src/megrok/rdb/tests/reflection.py
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/tests/reflection.py	                        (rev 0)
+++ megrok.rdb/trunk/src/megrok/rdb/tests/reflection.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -0,0 +1,92 @@
+"""
+A reflected model is a model that defined by the table in the database
+(as opposed to defining the database table in Python).
+
+Let's first grok things::
+
+  >>> from grok.testing import grok
+  >>> grok('megrok.rdb.meta')
+  >>> grok(__name__)
+
+We need to set up an engine::
+
+  >>> from megrok.rdb.testing import configureEngine
+  >>> engine = configureEngine()
+
+Let's set up the tables::
+
+  >>> from sqlalchemy.sql import text
+  >>> conn = engine.connect()
+  >>> s = text('''
+  ...   create table department (
+  ...     id integer,
+  ...     name char(50),
+  ...     primary key (id))
+  ... ''')
+  >>> result = conn.execute(s)
+  >>> s = text('''
+  ...   create table course (
+  ...     id integer,
+  ...     name char(50),
+  ...     department_id integer,
+  ...     primary key (id),
+  ...     foreign key (department_id) references department(id))
+  ... ''')
+  >>> result = conn.execute(s)
+  
+We now need to reflect the tables in our database to our classes::
+
+  >>> rdb.setupDatabase(metadata)
+
+Let's start using the database now::
+
+  >>> session = rdb.Session()
+  >>> philosophy = Department(name='Philosophy')
+  >>> session.add(philosophy)
+  >>> logic = Course(name='Logic')
+  >>> ethics = Course(name='Ethics')
+  >>> metaphysics = Course(name='Metaphysics')
+  >>> session.add_all([logic, ethics, metaphysics])
+  
+Let's now add them to the courses container::
+
+  >>> philosophy.courses.set(logic)
+  >>> philosophy.courses.set(ethics)
+  >>> philosophy.courses.set(metaphysics)
+
+We can now verify that the courses are there::
+
+  >>> [(course.id, course.name, course.department_id) for course in
+  ... session.query(Course)]
+  [(1, 'Logic', 1), (2, 'Ethics', 1), (3, 'Metaphysics', 1)]
+
+  >>> for key, value in sorted(philosophy.courses.items()):
+  ...   print key, value.name, value.department.name
+  1 Logic Philosophy
+  2 Ethics Philosophy
+  3 Metaphysics Philosophy
+"""
+
+
+import grok
+from megrok import rdb
+
+from sqlalchemy import Column, ForeignKey
+from sqlalchemy.types import Integer, String
+from sqlalchemy.orm import relation
+
+metadata = rdb.MetaData()
+
+rdb.metadata(metadata)
+
+class Courses(rdb.Container):
+    pass
+
+class Department(rdb.Model):
+    rdb.reflected()
+    courses = relation('Course', 
+                       backref='department',
+                       collection_class=Courses)
+
+class Course(rdb.Model):
+    rdb.reflected()

Copied: megrok.rdb/trunk/src/megrok/rdb/tests/test_rdb.py (from rev 89886, megrok.rdb/trunk/src/megrok/rdb/tests.py)
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/tests/test_rdb.py	                        (rev 0)
+++ megrok.rdb/trunk/src/megrok/rdb/tests/test_rdb.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -0,0 +1,56 @@
+import unittest
+import doctest
+from zope.testing import cleanup
+from zope.testing import module
+import zope.component.eventtesting
+from zope import component
+from megrok import rdb
+    
+from z3c.saconfig.interfaces import IEngineFactory, IScopedSession
+
+def moduleSetUp(test):
+    # using zope.testing.module.setUp to work around
+    # __module__ being '__builtin__' by default
+    module.setUp(test, '__main__')
+    
+def moduleTearDown(test):
+    # make sure scope func is empty before we tear down component architecture
+    rdb.Session.remove()
+    
+    module.tearDown(test)
+    cleanup.cleanUp()
+    
+def zopeSetUp(test):
+    zope.component.eventtesting.setUp(test)
+
+def zopeTearDown(test):
+    rdb.Session.remove()
+    cleanup.cleanUp()
+    
+def test_suite():
+    optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
+    globs = {}
+    
+    suite = unittest.TestSuite()
+    
+    suite.addTest(doctest.DocFileSuite(
+        '../README.txt',
+        optionflags=optionflags,
+        setUp=moduleSetUp,
+        tearDown=moduleTearDown,
+        globs=globs))
+    suite.addTest(doctest.DocTestSuite(
+        'megrok.rdb.tests.creation',
+        setUp=zopeSetUp,
+        tearDown=zopeTearDown,
+        optionflags=optionflags))
+    suite.addTest(doctest.DocTestSuite(
+        'megrok.rdb.tests.reflection',
+        setUp=zopeSetUp,
+        tearDown=zopeTearDown,
+        optionflags=optionflags))
+    suite.addTest(doctest.DocFileSuite(
+        '../schema.txt',
+        optionflags=optionflags,
+        ))
+    return suite

Deleted: megrok.rdb/trunk/src/megrok/rdb/tests.py
===================================================================
--- megrok.rdb/trunk/src/megrok/rdb/tests.py	2008-08-15 18:50:31 UTC (rev 89890)
+++ megrok.rdb/trunk/src/megrok/rdb/tests.py	2008-08-15 19:30:34 UTC (rev 89891)
@@ -1,33 +0,0 @@
-import unittest
-import doctest
-from zope.testing import cleanup
-from zope.testing import module
-
-def setUp(test):
-    # using zope.testing.module.setUp to work around
-    # __module__ being '__builtin__' by default
-    module.setUp(test, '__main__')
-    
-def tearDown(test):
-    module.tearDown(test)
-    cleanup.cleanUp()
-
-    # XXX clean up SQLAlchemy?
-
-def test_suite():
-    optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
-    globs = {}
-    
-    suite = unittest.TestSuite()
-    
-    suite.addTest(doctest.DocFileSuite(
-            'README.txt',
-            optionflags=optionflags,
-            setUp=setUp,
-            tearDown=tearDown,
-            globs=globs))
-    suite.addTest(doctest.DocFileSuite(
-            'schema.txt',
-            optionflags=optionflags,
-            ))
-    return suite



More information about the Checkins mailing list