]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add an example of declarative with hybrid, including why this
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 3 Aug 2010 00:41:47 +0000 (20:41 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 3 Aug 2010 00:41:47 +0000 (20:41 -0400)
is a different use case (i.e. formalized apps vs. quickstart).
- solidified docstrings for ForeignKey

lib/sqlalchemy/ext/declarative.py
lib/sqlalchemy/schema.py

index 712a563c3059452afe821c810976f9c1af7b9ebe..6922fe08cda9edf569828e1893e6a67a06d84754 100755 (executable)
@@ -318,6 +318,9 @@ arguments to be specified as well (usually constraints)::
 Note that the keyword parameters dictionary is required in the tuple
 form even if empty.
 
+Using a Hybrid Approach with __table__
+=======================================
+
 As an alternative to ``__tablename__``, a direct
 :class:`~sqlalchemy.schema.Table` construct may be used.  The
 :class:`~sqlalchemy.schema.Column` objects, which in this case require
@@ -330,6 +333,118 @@ to a table::
             Column('name', String(50))
         )
 
+``__table__`` provides a more focused point of control for establishing
+table metadata, while still getting most of the benefits of using declarative.
+An application that uses reflection might want to load table metadata elsewhere
+and simply pass it to declarative classes::
+    
+    from sqlalchemy.ext.declarative import declarative_base
+    
+    Base = declarative_base()
+    Base.metadata.reflect(some_engine)
+    
+    class User(Base):
+        __table__ = metadata['user']
+    
+    class Address(Base):
+        __table__ = metadata['address']
+
+Example - Formalized Naming Conventions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The data-driven configuration style of :class:`.Table` makes
+it an easy place to drive application conventions including
+standard columns, constraint names, and column names, for a
+large application that can afford to be more constrained and
+formalized in its configuration. For example, an application
+that provides its own ``make_table()`` function, which
+establishes a table with certain columns, naming conventions
+for constraints, as well as column-creating functions that
+also supply naming conventions, are more easily integrated
+into the ``__table__`` approach than pure declarative::
+
+    '''Illustrate a hybrid declarative/Table approach to schema
+    and model specification.'''
+    
+    from sqlalchemy import Table, Column, ForeignKey, \\
+                DateTime, Integer, func, ForeignKeyConstraint
+    from sqlalchemy.ext.declarative import declarative_base
+
+    Base = declarative_base()
+
+    # define some functions for generating
+    # tables and columns with generated naming conventions
+    # for constraints, column names, standard columns
+    def make_table(name, *args, **kw):
+        args += (
+            Column('created', DateTime, default=func.now()),
+        )
+    
+        table = Table(name, Base.metadata, *args, **kw)
+        table.primary_key.name = "pk_%s" % name
+        for const in table.constraints:
+            if isinstance(const, ForeignKeyConstraint):
+                fk = list(const.elements)[0]
+                reftable, refcol = fk.target_fullname.split(".")
+                const.name = "fk_%s_%s_%s" % (
+                        table.name, fk.parent.name, reftable
+                        )
+    
+        return table
+
+    def id_column():
+        return Column('id', Integer, primary_key=True)
+
+    def reference_column(tablename):
+        return Column('%s_id' % tablename, 
+                        Integer, 
+                        ForeignKey('%s.id' % tablename), 
+                        nullable=False)
+
+    # elsewhere, in main model code:
+
+    from mymodel import make_table, id_column, ref_column, Base
+    from sqlalchemy import Column, String
+
+    class Order(Base):
+        __table__ = make_table('order',
+            id_column(),
+            reference_column('item'),
+        )
+
+    class Item(Base):
+        __table__ = make_table('item',
+            id_column(),
+            Column("description", String(200))
+        )
+
+Issuing a :meth:`.MetaData.create` for the above applies our naming 
+and behavioral conventions::
+
+    CREATE TABLE item (
+       id INTEGER NOT NULL, 
+       description VARCHAR(200), 
+       created DATETIME DEFAULT CURRENT_TIMESTAMP, 
+       CONSTRAINT pk_item PRIMARY KEY (id)
+    )
+
+    CREATE TABLE "order" (
+       id INTEGER NOT NULL, 
+       item_id INTEGER NOT NULL, 
+       created DATETIME DEFAULT CURRENT_TIMESTAMP, 
+       CONSTRAINT pk_order PRIMARY KEY (id), 
+       CONSTRAINT fk_order_item_id_item FOREIGN KEY(item_id) REFERENCES item (id)
+    )
+
+The above approach costs more upfront in terms of setting up
+conventions, and is not as "out of the box" as pure
+declarative, not to mention more constrained in its results.
+A similar effect can also be achieved using custom
+metaclasses which subclass :class:`.DeclarativeMeta`, and
+establishing the conventions in the ``__init__`` method of
+the metaclass. The downside there is that metaclasses are
+more tedious to work with.
+
 Mapper Configuration
 ====================
 
index 75bbfd070a010df09163ebbe2557117dfe6f3f78..1630bfb5186fe77adf94e784008321caccbaee5d 100644 (file)
@@ -1019,7 +1019,20 @@ class ForeignKey(SchemaItem):
         return "ForeignKey(%r)" % self._get_colspec()
 
     def copy(self, schema=None):
-        """Produce a copy of this ForeignKey object."""
+        """Produce a copy of this :class:`ForeignKey` object.
+        
+        The new :class:`ForeignKey` will not be bound
+        to any :class:`Column`.
+        
+        This method is usually used by the internal
+        copy procedures of :class:`Column`, :class:`Table`,
+        and :class:`MetaData`.
+        
+        :param schema: The returned :class:`ForeignKey` will
+          reference the original table and column name, qualified
+          by the given string schema name.
+          
+        """
         
         return ForeignKey(
                 self._get_colspec(schema=schema),
@@ -1033,6 +1046,12 @@ class ForeignKey(SchemaItem):
                 )
 
     def _get_colspec(self, schema=None):
+        """Return a string based 'column specification' for this :class:`ForeignKey`.
+        
+        This is usually the equivalent of the string-based "tablename.colname"
+        argument first passed to the object's constructor.
+        
+        """
         if schema:
             return schema + "." + self.column.table.name + \
                                     "." + self.column.key
@@ -1048,15 +1067,16 @@ class ForeignKey(SchemaItem):
     target_fullname = property(_get_colspec)
 
     def references(self, table):
-        """Return True if the given table is referenced by this ForeignKey."""
+        """Return True if the given :class:`Table` is referenced by this :class:`ForeignKey`."""
         
         return table.corresponding_column(self.column) is not None
 
     def get_referent(self, table):
-        """Return the column in the given table referenced by this ForeignKey.
+        """Return the :class:`.Column` in the given :class:`.Table` 
+        referenced by this :class:`ForeignKey`.
 
-        Returns None if this ``ForeignKey`` does not reference the given
-        table.
+        Returns None if this :class:`ForeignKey` does not reference the given
+        :class:`Table`.
 
         """
 
@@ -1064,6 +1084,18 @@ class ForeignKey(SchemaItem):
 
     @util.memoized_property
     def column(self):
+        """Return the target :class:`.Column` referenced by this :class:`.ForeignKey`.
+        
+        If this :class:`ForeignKey` was created using a
+        string-based target column specification, this
+        attribute will on first access initiate a resolution
+        process to locate the referenced remote
+        :class:`.Column`.  The resolution process traverses
+        to the parent :class:`.Column`, :class:`.Table`, and
+        :class:`.MetaData` to proceed - if any of these aren't 
+        yet present, an error is raised.
+        
+        """
         # ForeignKey inits its remote column as late as possible, so tables
         # can be defined without dependencies
         if isinstance(self._colspec, basestring):