]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Start coverage for Class.prop = Column(), promote nameless Columns
authorJason Kirtland <jek@discorporate.us>
Tue, 18 Mar 2008 02:16:15 +0000 (02:16 +0000)
committerJason Kirtland <jek@discorporate.us>
Tue, 18 Mar 2008 02:16:15 +0000 (02:16 +0000)
lib/sqlalchemy/ext/declarative.py
test/ext/declarative.py

index 333b1eaa40cb2f36ea7bd8740f75375547d5d4ff..7f30a7374e79379163238e77cc646dcd4c8f9dcf 100644 (file)
@@ -2,62 +2,73 @@
 
 SQLAlchemy object-relational configuration involves the usage of Table,
 mapper(), and class objects to define the three areas of configuration.
-declarative moves these three types of configuration underneath the 
-individual mapped class.   Regular SQLAlchemy schema and ORM 
-constructs are used in most cases::
+declarative moves these three types of configuration underneath the
+individual mapped class.  Regular SQLAlchemy schema and ORM constructs are
+used in most cases::
 
     from sqlalchemy.ext.declarative import declarative_base
-    
+
     engine = create_engine('sqlite://')
     Base = declarative_base(engine)
-    
+
     class SomeClass(Base):
         __tablename__ = 'some_table'
         id = Column('id', Integer, primary_key=True)
         name =  Column('name', String(50))
 
-Above, the ``declarative_base`` callable produces a new base class from which all
-mapped classes inherit from.  When the class definition is completed, a new 
-``Table`` and ``mapper()`` have been generated, accessible via the ``__table__``
-and ``__mapper__`` attributes on the ``SomeClass`` class.
+Above, the ``declarative_base`` callable produces a new base class from
+which all mapped classes inherit from.  When the class definition is
+completed, a new ``Table`` and ``mapper()`` have been generated, accessible
+via the ``__table__`` and ``__mapper__`` attributes on the ``SomeClass``
+class.
+
+You may omit the names from the Column definitions.  Declarative will fill
+them in for you.
+
+    class SomeClass(Base):
+        __tablename__ = 'some_table'
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
 
 Attributes may be added to the class after its construction, and they will
-be added to the underlying ``Table`` and ``mapper()`` definitions as appropriate::
+be added to the underlying ``Table`` and ``mapper()`` definitions as
+appropriate::
 
     SomeClass.data = Column('data', Unicode)
     SomeClass.related = relation(RelatedInfo)
 
-Classes which are mapped explicitly using ``mapper()`` can interact freely with 
-declarative classes.  The ``declarative_base`` base class contains a ``MetaData`` 
-object as well as a dictionary of all classes created against the base.  
-So to access the above metadata and create tables we can say::
+Classes which are mapped explicitly using ``mapper()`` can interact freely
+with declarative classes.  The ``declarative_base`` base class contains a
+``MetaData`` object as well as a dictionary of all classes created against
+the base.  So to access the above metadata and create tables we can say::
 
     Base.metadata.create_all()
-    
-The ``declarative_base`` can also receive a pre-created ``MetaData`` object::
+
+The ``declarative_base`` can also receive a pre-created ``MetaData``
+object::
 
     mymetadata = MetaData()
     Base = declarative_base(metadata=mymetadata)
 
 Relations to other classes are done in the usual way, with the added feature
-that the class specified to ``relation()`` may be a string name.  The 
-"class registry" associated with ``Base`` is used at mapper compilation time
-to resolve the name into the actual class object, which is expected to have been
-defined once the mapper configuration is used::
+that the class specified to ``relation()`` may be a string name.  The "class
+registry" associated with ``Base`` is used at mapper compilation time to
+resolve the name into the actual class object, which is expected to have
+been defined once the mapper configuration is used::
 
     class User(Base):
         __tablename__ = 'users'
 
-        id = Column('id', Integer, primary_key=True)
-        name = Column('name', String(50))
+        id = Column(Integer, primary_key=True)
+        name = Column(String(50))
         addresses = relation("Address", backref="user")
-    
+
     class Address(Base):
         __tablename__ = 'addresses'
 
-        id = Column('id', Integer, primary_key=True)
-        email = Column('email', String(50))
-        user_id = Column('user_id', Integer, ForeignKey('users.id'))
+        id = Column(Integer, primary_key=True)
+        email = Column(String(50))
+        user_id = Column(Integer, ForeignKey('users.id'))
 
 Column constructs, since they are just that, are immediately usable, as
 below where we define a primary join condition on the ``Address`` class
@@ -66,39 +77,41 @@ using them::
     class Address(Base)
         __tablename__ = 'addresses'
 
-        id = Column('id', Integer, primary_key=True)
-        email = Column('email', String(50))
-        user_id = Column('user_id', Integer, ForeignKey('users.id'))
+        id = Column(Integer, primary_key=True)
+        email = Column(String(50))
+        user_id = Column(Integer, ForeignKey('users.id'))
         user = relation(User, primaryjoin=user_id==User.id)
 
-Synonyms are one area where ``declarative`` needs to slightly change the usual
-SQLAlchemy configurational syntax.  To define a getter/setter which proxies
-to an underlying attribute, use ``synonym`` with the ``instruments`` argument::
+Synonyms are one area where ``declarative`` needs to slightly change the
+usual SQLAlchemy configurational syntax.  To define a getter/setter which
+proxies to an underlying attribute, use ``synonym`` with the ``instruments``
+argument::
 
     class MyClass(Base):
         __tablename__ = 'sometable'
-        
+
         _attr = Column('attr', String)
-        
+
         def _get_attr(self):
             return self._some_attr
         def _set_attr(self, attr)
             self._some_attr = attr
         attr = synonym('_attr', instruments=property(_get_attr, _set_attr))
-        
-The above synonym is then usable as an instance attribute as well as a class-level
-expression construct::
+
+The above synonym is then usable as an instance attribute as well as a
+class-level expression construct::
 
     x = MyClass()
     x.attr = "some value"
     session.query(MyClass).filter(MyClass.attr == 'some other value').all()
-        
-As an alternative to ``__tablename__``, a direct ``Table`` construct may be used::
+
+As an alternative to ``__tablename__``, a direct ``Table`` construct may be
+used::
 
     class MyClass(Base):
         __table__ = Table('my_table', Base.metadata,
-            Column('id', Integer, primary_key=True),
-            Column('name', String(50))
+            Column(Integer, primary_key=True),
+            Column(String(50))
         )
 
 This is the preferred approach when using reflected tables, as below::
@@ -106,42 +119,44 @@ This is the preferred approach when using reflected tables, as below::
     class MyClass(Base):
         __table__ = Table('my_table', Base.metadata, autoload=True)
 
-Mapper arguments are specified using the ``__mapper_args__`` class variable.  
-Note that the column objects declared on the class are immediately usable, as 
-in this joined-table inheritance example::
+Mapper arguments are specified using the ``__mapper_args__`` class variable.
+Note that the column objects declared on the class are immediately usable,
+as in this joined-table inheritance example::
 
     class Person(Base):
         __tablename__ = 'people'
-        id = Column('id', Integer, primary_key=True)
-        discriminator = Column('type', String(50))
+        id = Column(Integer, primary_key=True)
+        discriminator = Column(String(50))
         __mapper_args__ = {'polymorphic_on':discriminator}
-    
+
     class Engineer(Person):
         __tablename__ = 'engineers'
         __mapper_args__ = {'polymorphic_identity':'engineer'}
-        id = Column('id', Integer, ForeignKey('people.id'), primary_key=True)
-        primary_language = Column('primary_language', String(50))
-        
+        id = Column(Integer, ForeignKey('people.id'), primary_key=True)
+        primary_language = Column(String(50))
+
 For single-table inheritance, the ``__tablename__`` and ``__table__`` class
-variables are optional on a class when the class inherits from another mapped
-class.
+variables are optional on a class when the class inherits from another
+mapped class.
 
-As a convenience feature, the ``declarative_base()`` sets a default constructor
-on classes which takes keyword arguments, and assigns them to the named attributes::
+As a convenience feature, the ``declarative_base()`` sets a default
+constructor on classes which takes keyword arguments, and assigns them to
+the named attributes::
 
     e = Engineer(primary_language='python')
 
-Note that ``declarative`` has no integration built in with sessions, and is only
-intended as an optional syntax for the regular usage of mappers and Table objects.
-A typical application setup using ``scoped_session`` might look like::
+Note that ``declarative`` has no integration built in with sessions, and is
+only intended as an optional syntax for the regular usage of mappers and
+Table objects.  A typical application setup using ``scoped_session`` might
+look like::
 
     engine = create_engine('postgres://scott:tiger@localhost/test')
     Session = scoped_session(sessionmaker(transactional=True, autoflush=False, bind=engine))
     Base = declarative_base()
-    
-Mapped instances then make usage of ``Session`` in the usual way.
 
+Mapped instances then make usage of ``Session`` in the usual way.
 """
+
 from sqlalchemy.schema import Table, SchemaItem, Column, MetaData
 from sqlalchemy.orm import synonym as _orm_synonym, mapper, comparable_property
 from sqlalchemy.orm.interfaces import MapperProperty
@@ -151,11 +166,12 @@ from sqlalchemy import util
 __all__ = ['declarative_base', 'synonym_for', 'comparable_using',
            'declared_synonym']
 
+
 class DeclarativeMeta(type):
     def __init__(cls, classname, bases, dict_):
         if '_decl_class_registry' in cls.__dict__:
             return type.__init__(cls, classname, bases, dict_)
-        
+
         cls._decl_class_registry[classname] = cls
         our_stuff = util.OrderedDict()
         for k in dict_:
@@ -170,7 +186,7 @@ class DeclarativeMeta(type):
                 continue
             prop = _deferred_relation(cls, value)
             our_stuff[k] = prop
-        
+
         table = None
         if '__table__' not in cls.__dict__:
             if '__tablename__' in cls.__dict__:
@@ -182,28 +198,25 @@ class DeclarativeMeta(type):
                     table_kw = {}
                 cols = []
                 for key, c in our_stuff.iteritems():
-                    if not isinstance(c, Column):
-                        continue
-                    if c.key is None:
-                        c.key = key
-                    if c.name is None:
-                        c.name = key
-                    cols.append(c)
+                    if isinstance(c, Column):
+                        _undefer_column_name(key, c)
+                        cols.append(c)
                 cls.__table__ = table = Table(tablename, cls.metadata,
                                               *cols, **table_kw)
         else:
             table = cls.__table__
-        
+
         inherits = cls.__mro__[1]
         inherits = cls._decl_class_registry.get(inherits.__name__, None)
         mapper_args = getattr(cls, '__mapper_args__', {})
-        
+
         cls.__mapper__ = mapper(cls, table, inherits=inherits, properties=our_stuff, **mapper_args)
         return type.__init__(cls, classname, bases, dict_)
-    
+
     def __setattr__(cls, key, value):
         if '__mapper__' in cls.__dict__:
             if isinstance(value, Column):
+                _undefer_column_name(key, value)
                 cls.__table__.append_column(value)
                 cls.__mapper__.add_property(key, value)
             elif isinstance(value, MapperProperty):
@@ -224,13 +237,13 @@ def _deferred_relation(cls, prop):
 
 def declared_synonym(prop, name):
     """deprecated.  use synonym(name, instrument=prop)."""
-    
+
     return _orm_synonym(name, instrument=prop)
 declared_synonym = util.deprecated(declared_synonym)
 
 def synonym_for(name, map_column=False):
     """Decorator, make a Python @property a query synonym for a column.
-    
+
     A decorator version of [sqlalchemy.orm#synonym()].  The function being
     decoratred is the 'instrument', otherwise passes its arguments through
     to synonym().
@@ -242,7 +255,7 @@ def synonym_for(name, map_column=False):
 
     The regular ``synonym()`` is also usable directly in a declarative
     setting and may be convenient for read/write properties::
-    
+
       prop = synonym('col', instrument=property(_read_prop, _write_prop))
 
     """
@@ -287,3 +300,8 @@ def declarative_base(engine=None, metadata=None):
                 setattr(self, k, kwargs[k])
     return Base
 
+def _undefer_column_name(key, column):
+    if column.key is None:
+        column.key = key
+    if column.name is None:
+        column.name = key
index 99686dbe778a73c1a625d9be544c8a8b676bb6c8..44f34cb446e4f12450251efeb6cb98fdd8556c97 100644 (file)
@@ -13,10 +13,10 @@ class DeclarativeTest(TestBase):
     def setUp(self):
         global Base
         Base = declarative_base(testing.db)
-        
+
     def tearDown(self):
         Base.metadata.drop_all()
-        
+
     def test_basic(self):
         class User(Base, Fixture):
             __tablename__ = 'users'
@@ -27,12 +27,12 @@ class DeclarativeTest(TestBase):
 
         class Address(Base, Fixture):
             __tablename__ = 'addresses'
-            
+
             id = Column(Integer, primary_key=True)
             email = Column(String(50), key='_email')
             user_id = Column('user_id', Integer, ForeignKey('users.id'),
                              key='_user_id')
-            
+
         Base.metadata.create_all()
 
         assert Address.__table__.c['id'].name == 'id'
@@ -47,12 +47,12 @@ class DeclarativeTest(TestBase):
         sess.save(u1)
         sess.flush()
         sess.clear()
-        
+
         self.assertEquals(sess.query(User).all(), [User(name='u1', addresses=[
             Address(email='one'),
             Address(email='two'),
         ])])
-        
+
         a1 = sess.query(Address).filter(Address.email=='two').one()
         self.assertEquals(a1, Address(email='two'))
         self.assertEquals(a1.user, User(name='u1'))
@@ -79,7 +79,7 @@ class DeclarativeTest(TestBase):
             id = Column('id', Integer, primary_key=True)
             name = Column('name', String(50))
             addresses = relation("Address", backref="user")
-            
+
         class Address(Base, Fixture):
             __tablename__ = 'addresses'
 
@@ -105,6 +105,30 @@ class DeclarativeTest(TestBase):
             Address(email='two'),
         ])])
 
+    def test_column(self):
+        class User(Base, Fixture):
+            __tablename__ = 'users'
+
+            id = Column('id', Integer, primary_key=True)
+            name = Column('name', String(50))
+
+        User.a = Column('a', String(10))
+        User.b = Column(String(10))
+
+        Base.metadata.create_all()
+
+        print User.a
+        print User.c
+
+        u1 = User(name='u1', a='a', b='b')
+        sess = create_session()
+        sess.save(u1)
+        sess.flush()
+        sess.clear()
+
+        self.assertEquals(sess.query(User).all(),
+                          [User(name='u1', a='a', b='b')])
+
     def test_synonym_inline(self):
         class User(Base, Fixture):
             __tablename__ = 'users'
@@ -116,9 +140,9 @@ class DeclarativeTest(TestBase):
             def _get_name(self):
                 return self._name
             name = synonym('_name', instrument=property(_get_name, _set_name))
-            
+
         Base.metadata.create_all()
-        
+
         sess = create_session()
         u1 = User(name='someuser')
         assert u1.name == "SOMENAME someuser", u1.name
@@ -192,14 +216,14 @@ class DeclarativeTest(TestBase):
         sess.save(u1)
         sess.flush()
         self.assertEquals(sess.query(User).filter(User.name=="SOMENAME someuser").one(), u1)
-    
+
     def test_joined_inheritance(self):
         class Company(Base, Fixture):
             __tablename__ = 'companies'
             id = Column('id', Integer, primary_key=True)
             name = Column('name', String(50))
             employees = relation("Person")
-            
+
         class Person(Base, Fixture):
             __tablename__ = 'people'
             id = Column('id', Integer, primary_key=True)
@@ -207,7 +231,7 @@ class DeclarativeTest(TestBase):
             name = Column('name', String(50))
             discriminator = Column('type', String(50))
             __mapper_args__ = {'polymorphic_on':discriminator}
-            
+
         class Engineer(Person):
             __tablename__ = 'engineers'
             __mapper_args__ = {'polymorphic_identity':'engineer'}
@@ -219,7 +243,7 @@ class DeclarativeTest(TestBase):
             __mapper_args__ = {'polymorphic_identity':'manager'}
             id = Column('id', Integer, ForeignKey('people.id'), primary_key=True)
             golf_swing = Column('golf_swing', String(50))
-        
+
         Base.metadata.create_all()
 
         sess = create_session()
@@ -237,7 +261,7 @@ class DeclarativeTest(TestBase):
         sess.save(c2)
         sess.flush()
         sess.clear()
-        
+
         self.assertEquals(sess.query(Company).filter(Company.employees.of_type(Engineer).any(Engineer.primary_language=='cobol')).first(), c2)
 
     def test_relation_reference(self):
@@ -280,7 +304,7 @@ class DeclarativeTest(TestBase):
             id = Column('id', Integer, primary_key=True)
             name = Column('name', String(50))
             employees = relation("Person")
-            
+
         class Person(Base, Fixture):
             __tablename__ = 'people'
             id = Column('id', Integer, primary_key=True)
@@ -290,13 +314,13 @@ class DeclarativeTest(TestBase):
             primary_language = Column('primary_language', String(50))
             golf_swing = Column('golf_swing', String(50))
             __mapper_args__ = {'polymorphic_on':discriminator}
-            
+
         class Engineer(Person):
             __mapper_args__ = {'polymorphic_identity':'engineer'}
 
         class Manager(Person):
             __mapper_args__ = {'polymorphic_identity':'manager'}
-        
+
         Base.metadata.create_all()
 
         sess = create_session()
@@ -314,7 +338,7 @@ class DeclarativeTest(TestBase):
         sess.save(c2)
         sess.flush()
         sess.clear()
-        
+
         self.assertEquals(sess.query(Person).filter(Engineer.primary_language=='cobol').first(), Engineer(name='vlad'))
         self.assertEquals(sess.query(Company).filter(Company.employees.of_type(Engineer).any(Engineer.primary_language=='cobol')).first(), c2)