From: Mike Bayer Date: Tue, 3 Aug 2010 00:41:47 +0000 (-0400) Subject: - add an example of declarative with hybrid, including why this X-Git-Tag: rel_0_6_4~61 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b862b384f7a1804bb52e10cbbe8a568b4a2f6fff;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - add an example of declarative with hybrid, including why this is a different use case (i.e. formalized apps vs. quickstart). - solidified docstrings for ForeignKey --- diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 712a563c30..6922fe08cd 100755 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -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 ==================== diff --git a/lib/sqlalchemy/schema.py b/lib/sqlalchemy/schema.py index 75bbfd070a..1630bfb518 100644 --- a/lib/sqlalchemy/schema.py +++ b/lib/sqlalchemy/schema.py @@ -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):