[Checkins] SVN: z3c.sqlalchemy/branches/custom_relations/ Added option to specify foreign keys and relations for reflected tables which does not define them.

Radim Novotny novotny.radim at gmail.com
Wed Dec 1 07:56:43 EST 2010


Log message for revision 118655:
  Added option to specify foreign keys and relations for reflected tables which does not define them.
  

Changed:
  U   z3c.sqlalchemy/branches/custom_relations/README.txt
  U   z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/mapper.py
  U   z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/model.py
  U   z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/tests/testSQLAlchemy.py

-=-
Modified: z3c.sqlalchemy/branches/custom_relations/README.txt
===================================================================
--- z3c.sqlalchemy/branches/custom_relations/README.txt	2010-12-01 11:06:13 UTC (rev 118654)
+++ z3c.sqlalchemy/branches/custom_relations/README.txt	2010-12-01 12:56:42 UTC (rev 118655)
@@ -60,7 +60,7 @@
 Usage
 =====
 
-Basic usage: 
+Basic usage::
 
    from z3c.sqlalchemy import createSAWrapper
    wrapper = createSAWrapper('postgres://postgres:postgres@host/someDB')

Modified: z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/mapper.py
===================================================================
--- z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/mapper.py	2010-12-01 11:06:13 UTC (rev 118654)
+++ z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/mapper.py	2010-12-01 12:56:42 UTC (rev 118655)
@@ -158,6 +158,8 @@
             if table is None:
 
                 table_name = self._model.get(name, {}).get('table_name') or name
+                table_args = self._model.get(name, {}).get('table_args') or ()
+                table_kwargs = self._model.get(name, {}).get('table_kwargs') or {}
 
                 # check for 'schema.tablename'
                 if '.' in table_name:
@@ -168,7 +170,10 @@
                 table = Table(tablename, 
                               self._metadata, 
                               schema=schema,
-                              autoload=True)
+                              autoload=True,
+                              *table_args,
+                              **table_kwargs
+                              )
 
             # check if the model contains an optional mapper class
             mapper_class = None
@@ -202,12 +207,25 @@
             properties = {}
 
             # find all dependent tables (referencing the current table)
-            for table_refname in dependent_table_names:
+            for cfg in dependent_table_names:
+                relation_kwargs = dict()
+                if isinstance(cfg, basestring):
+                    cfg = dict(refname = cfg)
+                # referenced table
+                table_ref = cfg.get('table', cfg.get('refname'))
+                # reference name
+                table_refname = cfg.get('refname', cfg.get('table'))
+                
+                relation_kwargs['cascade']=self._model.get(name, {}).get('cascade')
+                if cfg.get('backref'):
+                    relation_kwargs['backref']=cfg.get('backref')
                 # create or get a mapper for the referencing table
-                table_ref_mapper = self.getMapper(table_refname)
+                table_ref_mapper = self.getMapper(table_ref)
 
                 # add the mapper as relation to the properties dict
-                properties[table_refname] = relation(table_ref_mapper, cascade=self._model.get(name, {}).get('cascade'))
+                properties[table_refname] = relation(table_ref_mapper, 
+                                                     **relation_kwargs
+                                                     )
 
             # create a mapper and cache it 
             if mapper_class and mapper_class.__dict__.has_key('c'):

Modified: z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/model.py
===================================================================
--- z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/model.py	2010-12-01 11:06:13 UTC (rev 118654)
+++ z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/model.py	2010-12-01 12:56:42 UTC (rev 118655)
@@ -40,7 +40,7 @@
             self.add(**d)
 
 
-    def add(self, name, table=None, mapper_class=None, relations=None, autodetect_relations=False, table_name=None, cascade=None):
+    def add(self, name, table=None, mapper_class=None, relations=None, autodetect_relations=False, table_name=None, cascade=None, table_args=(), table_kwargs={}):
         """ 'name'  -- name of table (no schema support so far!)
 
             'table' -- a sqlalchemy.Table instance (None, for autoloading)
@@ -73,9 +73,16 @@
                     raise TypeError('relations must be specified a sequence of strings')    
 
             for r in relations:
-                if not isinstance(r, str):
-                    raise TypeError('relations must be specified a sequence of strings')    
+                if not isinstance(r, (str, dict)):
+                    raise TypeError('relations must be specified a sequence of strings or dicts')    
 
+        if not isinstance(table_args, (tuple, list)):
+            raise TypeError('table_args must be specified as a sequence')    
+
+        if not isinstance(table_kwargs, (dict)):
+            raise TypeError('table_kwargs must be specified as a dictionary')    
+
+
         if relations is not None and autodetect_relations == True:
             raise ValueError("'relations' and 'autodetect_relations' can't be specified at the same time")
 
@@ -88,6 +95,8 @@
                       'autodetect_relations' : autodetect_relations,
                       'cascade' : cascade,
                       'table_name' : table_name,
+                      'table_args': table_args,
+                      'table_kwargs': table_kwargs
                      }
 
 

Modified: z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/tests/testSQLAlchemy.py
===================================================================
--- z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/tests/testSQLAlchemy.py	2010-12-01 11:06:13 UTC (rev 118654)
+++ z3c.sqlalchemy/branches/custom_relations/src/z3c/sqlalchemy/tests/testSQLAlchemy.py	2010-12-01 12:56:42 UTC (rev 118655)
@@ -22,7 +22,7 @@
 from sqlalchemy import MetaData, Integer, String, Column, Table
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import relation
-from sqlalchemy.schema import ForeignKey
+from sqlalchemy.schema import ForeignKeyConstraint
 
 
 from zope.interface.verify import verifyClass
@@ -51,7 +51,8 @@
                       Column('lastname', String(255)))
 
         skill = Table('skills', metadata,
-                      Column('user_id', Integer, primary_key=True),
+                      Column('id', Integer, primary_key=True),
+                      Column('user_id', Integer),
                       Column('name', String(255)))
 
         metadata.create_all()
@@ -247,7 +248,62 @@
 
         rows = session.query(Foo).all()
         self.assertEqual(len(rows), 2)
+        
+    def testRelations(self):
+        M = Model()
+        M.add(name='users')
+        M.add(name='skills', relations=('users',),
+                             table_args=(ForeignKeyConstraint(['user_id'], ['users.id']),)
+                            )
+        db = createSAWrapper(self.dsn, model=M)
+        User = db.getMapper('users')
+        Skill = db.getMapper('skills')
+        session = db.session
+        # add data
+        session.add(User(id=1, firstname='udo', lastname='juergens'))
+        session.add(User(id=2, firstname='heino', lastname='n/a'))
+        session.add(Skill(id=1, user_id=1, name='Skill 1-1'))
+        session.add(Skill(id=2, user_id=1, name='Skill 1-2'))
+        session.flush()
+        # query
+        skills = session.query(Skill).all()
+        self.assertEqual(len(skills), 2) # two skills, both for same user
+        # checking "users" relation - "users" is related table name taken
+        # from model.add(relation='users') param.
+        self.assertEqual(skills[0].users.firstname, u'udo')
+        self.assertEqual(skills[1].users.firstname, u'udo')
+        
+    def testRelations_named(self):
+        M = Model()
+        M.add(name='users')
+        M.add(name='skills', relations=(dict(
+                                        table='users',
+                                        refname='user',
+                                        backref='skills'
+                                        ),),
+                             table_args=(ForeignKeyConstraint(['user_id'], ['users.id']),)
+                            )
+        db = createSAWrapper(self.dsn, model=M)
+        User = db.getMapper('users')
+        Skill = db.getMapper('skills')
+        session = db.session
+        # add data
+        session.add(User(id=1, firstname='udo', lastname='juergens'))
+        session.add(User(id=2, firstname='heino', lastname='n/a'))
+        session.add(Skill(id=1, user_id=1, name='Skill 1-1'))
+        session.add(Skill(id=2, user_id=1, name='Skill 1-2'))
+        session.flush()
+        # query
+        skill = session.query(Skill).filter(User.id==1).first()
+        # 'user' is relation name from model.add()
+        self.assertEqual(skill.user.firstname, u'udo')
+        # let's find user's skills
+        user = session.query(User).filter(User.id==1).first()
+        self.assertEqual(len(user.skills), 2)
+        self.assertEqual(user.skills[0].name, 'Skill 1-1')
+        self.assertEqual(user.skills[1].name, 'Skill 1-2')
 
+
 def test_suite():
     from unittest import TestSuite, makeSuite
     suite = TestSuite()



More information about the checkins mailing list