[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