diff options
-rw-r--r-- | doc/build/orm/tutorial.rst | 375 | ||||
-rw-r--r-- | doc/build/static/docs.css | 6 | ||||
-rw-r--r-- | doc/build/testdocs.py | 2 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/__init__.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/engine/base.py | 6 | ||||
-rwxr-xr-x | lib/sqlalchemy/ext/declarative.py | 65 |
6 files changed, 277 insertions, 183 deletions
diff --git a/doc/build/orm/tutorial.rst b/doc/build/orm/tutorial.rst index 29525cc63..3132cf8ef 100644 --- a/doc/build/orm/tutorial.rst +++ b/doc/build/orm/tutorial.rst @@ -54,33 +54,130 @@ A quick check to verify that we are on at least **version 0.7** of SQLAlchemy:: Connecting ========== -For this tutorial we will use an in-memory-only SQLite database. To connect we use :func:`~sqlalchemy.create_engine`:: +For this tutorial we will use an in-memory-only SQLite database. To connect we +use :func:`~sqlalchemy.create_engine`:: >>> from sqlalchemy import create_engine >>> engine = create_engine('sqlite:///:memory:', echo=True) -The ``echo`` flag is a shortcut to setting up SQLAlchemy logging, which is accomplished via Python's standard ``logging`` module. With it enabled, we'll see all the generated SQL produced. If you are working through this tutorial and want less output generated, set it to ``False``. This tutorial will format the SQL behind a popup window so it doesn't get in our way; just click the "SQL" links to see what's being generated. +The ``echo`` flag is a shortcut to setting up SQLAlchemy logging, which is +accomplished via Python's standard ``logging`` module. With it enabled, we'll +see all the generated SQL produced. If you are working through this tutorial +and want less output generated, set it to ``False``. This tutorial will format +the SQL behind a popup window so it doesn't get in our way; just click the +"SQL" links to see what's being generated. -Define and Create a Table -========================== -Next we want to tell SQLAlchemy about our tables. We will start with just a single table called ``users``, which will store records for the end-users using our application (lets assume it's a website). We define our tables within a catalog called :class:`~sqlalchemy.schema.MetaData`, using the :class:`~sqlalchemy.schema.Table` construct, which is used in a manner similar to SQL's CREATE TABLE syntax:: - - >>> from sqlalchemy import Table, Column, Integer, String, MetaData, ForeignKey - >>> metadata = MetaData() - >>> users_table = Table('users', metadata, - ... Column('id', Integer, primary_key=True), - ... Column('name', String), - ... Column('fullname', String), - ... Column('password', String) - ... ) +The return value of :func:`.create_engine` is an instance of :class:`.Engine`, and it represents +the core interface to the database, adapted through a **dialect** that handles the details +of the database and DBAPI in use, in this case the SQLite dialect. It has not +actually tried to connect to the database yet; that happens the first time the :class:`.Engine` +is actually used to do something. + +Normally, the :class:`.Engine` is passed off to the ORM where it is used behind the scenes. +We can execute SQL directly from it however, as we illustrate here: + +.. sourcecode:: python+sql + + {sql}>>> engine.execute("select 1").scalar() + select 1 + () + {stop}1 + +We have now tested that the :class:`.Engine` is in fact able to connect to the database. + +Declare a Mapping +================= + +When using the ORM, two key steps which occur before communicating with the +database are to tell SQLAlchemy about our tables, as well as about the classes +we're defining in our application and how they map to those tables. SQLAlchemy +refers to these two steps as **defining table metadata** and **mapping +classes**. In modern SQLAlchemy usage, these two tasks are performed together, +using a system known as :ref:`declarative_toplevel`, which allows us to create our own +mapped classes which also define how they will be mapped to an actual database +table. + +We define our classes in terms of a base class which maintains a catalog of classes and +tables relative to that base - this is known as the **declarative base class**. Our +application will usually have just one instance of this base in a commonly +imported module. We create this class using the :func:`.declarative_base` +function, as follows:: + + >>> from sqlalchemy.ext.declarative import declarative_base + + >>> Base = declarative_base() + +Now that we have a "base", we can define any number of mapped classes in terms +of it. We will start with just a single table called ``users``, which will store +records for the end-users using our application (lets assume it's a website). +A new class called ``User`` will be the class to which we map this table. The +imports we'll need to accomplish this include objects that represent the components +of our table, including the :class:`.Column` class which represents a column +in a table, as well as the :class:`.Integer` and :class:`.String` type objects that +represent basic datatypes used in columns:: + + >>> from sqlalchemy import Column, Integer, String + >>> class User(Base): + ... __tablename__ = 'users' + ... + ... id = Column(Integer, primary_key=True) + ... name = Column(String) + ... fullname = Column(String) + ... password = Column(String) + ... + ... def __init__(self, name, fullname, password): + ... self.name = name + ... self.fullname = fullname + ... self.password = password + ... + ... def __repr__(self): + ... return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password) -:ref:`metadata_toplevel` covers all about how to define :class:`~sqlalchemy.schema.Table` objects, as well as how to load their definition from an existing database (known as **reflection**). +.. topic:: The Non Opinionated Philosophy + + Above, the ``__tablename__`` attribute represents the name of the table in the + database to which we are mapping, and the :class:`.Column` object referenced by the + name ``id`` defines the **primary key** column of our table; the usage of :class:`.Integer` + states that it should be of type ``INT``. SQLAlchemy never makes + assumptions about decisions like these - the developer using SQLAlchemy must + always decide on the specific conventions to be used. However, that doesn't mean the + task can't be automated. While this tutorial will keep things explicit, developers are + encouraged to make usage of helper functions as well as "Declarative Mixins" to + automate their tasks in large scale applications. The section :ref:`declarative_mixins` + introduces many of these techniques. + +With our ``User`` class constructed via the Declarative system, we have defined **table metadata** as well as a +**mapped class**. This configuration is shorthand for what in SQLAlchemy is +called a "classical mapping", +which would have us first create an object representing the 'users' table using a class known as +:class:`.Table`, and then creating a mapping to this table through the usage of a function called +:func:`.mapper`. Declarative instead performs these steps for us, making available the +:class:`.Table` it has created for us via the ``__table__`` attribute:: + + >>> User.__table__ # doctest: +NORMALIZE_WHITESPACE + Table('users', MetaData(None), + Column('id', Integer(), table=<users>, primary_key=True, nullable=False), + Column('name', String(), table=<users>), + Column('fullname', String(), table=<users>), + Column('password', String(), table=<users>), schema=None) + +and while rarely needed, making available the :func:`.mapper` object via the ``__mapper__`` attribute:: + + >>> User.__mapper__ # doctest: +ELLIPSIS + <Mapper at 0x...; User> -Next, we can issue CREATE TABLE statements derived from our table metadata, by calling :func:`~sqlalchemy.schema.MetaData.create_all` and passing it the ``engine`` instance which points to our database. This will check for the presence of a table first before creating, so it's safe to call multiple times: +The Declarative base class also contains a catalog of all the :class:`.Table` objects +that have been defined called :class:`.MetaData`, available via the ``.metadata`` +attribute. In this example, we are defining +new tables that have yet to be created in our SQLite database, so one helpful feature +the :class:`.MetaData` object offers is the ability to issue CREATE TABLE statements +to the database for all tables that don't yet exist. We illustrate this +by calling the :meth:`.MetaData.create_all` method, passing in our :class:`.Engine` +as a source of database connectivity: .. sourcecode:: python+sql - {sql}>>> metadata.create_all(engine) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE + {sql}>>> Base.metadata.create_all(engine) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE PRAGMA table_info("users") () CREATE TABLE users ( @@ -100,7 +197,7 @@ Next, we can issue CREATE TABLE statements derived from our table metadata, by c issue CREATE TABLE, a "length" may be provided to the :class:`~sqlalchemy.types.String` type as below:: - Column('name', String(50)) + Column(String(50)) The length field on :class:`~sqlalchemy.types.String`, as well as similar precision/scale fields available on :class:`~sqlalchemy.types.Integer`, :class:`~sqlalchemy.types.Numeric`, etc. are not referenced by @@ -111,59 +208,37 @@ Next, we can issue CREATE TABLE statements derived from our table metadata, by c without being instructed. For that, you use the :class:`~sqlalchemy.schema.Sequence` construct:: from sqlalchemy import Sequence - Column('id', Integer, Sequence('user_id_seq'), primary_key=True) + Column(Integer, Sequence('user_id_seq'), primary_key=True) - A full, foolproof :class:`~sqlalchemy.schema.Table` is therefore:: + A full, foolproof :class:`~sqlalchemy.schema.Table` generated via our declarative + mapping is therefore:: - users_table = Table('users', metadata, - Column('id', Integer, Sequence('user_id_seq'), primary_key=True), - Column('name', String(50)), - Column('fullname', String(50)), - Column('password', String(12)) - ) + class User(Base): + __tablename__ = 'users' + id = Column(Integer, Sequence('user_id_seq'), primary_key=True) + name = Column(String(50)) + fullname = Column(String(50)) + password = Column(String(12)) - We include this more verbose :class:`~.schema.Table` construct separately + def __init__(self, name, fullname, password): + self.name = name + self.fullname = fullname + self.password = password + + def __repr__(self): + return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password) + + We include this more verbose table definition separately to highlight the difference between a minimal construct geared primarily towards in-Python usage only, versus one that will be used to emit CREATE TABLE statements on a particular set of backends with more stringent requirements. -Define a Python Class to be Mapped -=================================== -While the :class:`~sqlalchemy.schema.Table` object defines information about -our database, it does not say anything about the definition or behavior of the -business objects used by our application; SQLAlchemy views this as a separate -concern. To correspond to our ``users`` table, let's create a rudimentary -``User`` class. It only need subclass Python's built-in ``object`` class (i.e. -it's a new style class):: +Create an Instance of the Mapped Class +====================================== - >>> class User(object): - ... def __init__(self, name, fullname, password): - ... self.name = name - ... self.fullname = fullname - ... self.password = password - ... - ... def __repr__(self): - ... return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password) - -The class has an ``__init__()`` and a ``__repr__()`` method for convenience. -These methods are both entirely optional, and can be of any form. SQLAlchemy -never calls ``__init__()`` directly. - -Setting up the Mapping -====================== -With our ``users_table`` and ``User`` class, we now want to map the two -together. That's where the SQLAlchemy ORM package comes in. We'll use the -:func:`~.orm.mapper` function to create a **mapping** between ``users_table`` and -``User``:: - - >>> from sqlalchemy.orm import mapper - >>> mapper(User, users_table) # doctest:+ELLIPSIS,+NORMALIZE_WHITESPACE - <Mapper at 0x...; User> - -The :func:`~.orm.mapper` function creates a new :class:`~sqlalchemy.orm.mapper.Mapper` -object and stores it away for future reference, associated with our class. -Let's now create and inspect a ``User`` object:: +With our fully specified ``User`` class and associated table metadata, +let's now create and inspect a ``User`` object:: >>> ed_user = User('ed', 'Ed Jones', 'edspassword') >>> ed_user.name @@ -174,10 +249,12 @@ Let's now create and inspect a ``User`` object:: 'None' The ``id`` attribute, which while not defined by our ``__init__()`` method, -exists due to the ``id`` column present within the ``users_table`` object. By -default, the :func:`~.orm.mapper` creates class attributes for all columns present -within the :class:`~sqlalchemy.schema.Table`. These class attributes exist as -Python descriptors, and define **instrumentation** for the mapped class. The +exists with a value of ``None`` on our ``User`` instance due to the ``id`` +column we declared in our mapping. By +default, the ORM creates class attributes for all columns present +in the table being mapped. These class attributes exist as +`Python descriptors <http://docs.python.org/howto/descriptor.html>`_, and +define **instrumentation** for the mapped class. The functionality of this instrumentation is very rich and includes the ability to track modifications and automatically load new data from the database when needed. @@ -186,63 +263,6 @@ Since we have not yet told SQLAlchemy to persist ``Ed Jones`` within the database, its id is ``None``. When we persist the object later, this attribute will be populated with a newly generated value. -Creating Table, Class and Mapper All at Once Declaratively -=========================================================== -The preceding approach to configuration involved a -:class:`~sqlalchemy.schema.Table`, a user-defined class, and -a call to :func:`~.orm.mapper`. This illustrates classical SQLAlchemy usage, which values -the highest separation of concerns possible. -A large number of applications don't require this degree of -separation, and for those SQLAlchemy offers an alternate "shorthand" -configurational style called :mod:`~.sqlalchemy.ext.declarative`. -For many applications, this is the only style of configuration needed. -Our above example using this style is as follows:: - - >>> from sqlalchemy.ext.declarative import declarative_base - - >>> Base = declarative_base() - >>> class User(Base): - ... __tablename__ = 'users' - ... - ... id = Column(Integer, primary_key=True) - ... name = Column(String) - ... fullname = Column(String) - ... password = Column(String) - ... - ... def __init__(self, name, fullname, password): - ... self.name = name - ... self.fullname = fullname - ... self.password = password - ... - ... def __repr__(self): - ... return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password) - -Above, the :func:`~sqlalchemy.ext.declarative.declarative_base` function defines a new class which -we name ``Base``, from which all of our ORM-enabled classes will -derive. Note that we define :class:`~sqlalchemy.schema.Column` -objects with no "name" field, since it's inferred from the given -attribute name. - -The underlying :class:`~sqlalchemy.schema.Table` object created by our -:func:`~sqlalchemy.ext.declarative.declarative_base` version of ``User`` is accessible via the -``__table__`` attribute:: - - >>> users_table = User.__table__ - -The owning :class:`~sqlalchemy.schema.MetaData` object is available as well:: - - >>> metadata = Base.metadata - - -:mod:`~sqlalchemy.ext.declarative` is covered at :ref:`declarative_toplevel` -as well as throughout :ref:`mapper_config_toplevel`. - -Yet another "declarative" method is available for SQLAlchemy as a third party -library called `Elixir <http://elixir.ematia.de/>`_. This is a full-featured -configurational product which also includes many higher level mapping -configurations built in. Like declarative, once classes and mappings are -defined, ORM usage is the same as with a classical SQLAlchemy configuration. - Creating a Session ================== @@ -313,8 +333,8 @@ added: SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users WHERE users.name = ? - LIMIT 1 OFFSET 0 - ('ed',) + LIMIT ? OFFSET ? + ('ed', 1, 0) {stop}>>> our_user <User('ed','Ed Jones', 'edspassword')> @@ -363,7 +383,7 @@ and that three new ``User`` objects are pending: .. sourcecode:: python+sql - >>> session.new # doctest: +NORMALIZE_WHITESPACE + >>> session.new # doctest: +SKIP IdentitySet([<User('wendy','Wendy Williams', 'foobar')>, <User('mary','Mary Contrary', 'xxg527')>, <User('fred','Fred Flinstone', 'blah')>]) @@ -545,8 +565,12 @@ scalar attributes and :class:`~.orm.aliased` for class constructs: >>> user_alias = aliased(User, name='user_alias') {sql}>>> for row in session.query(user_alias, user_alias.name.label('name_label')).all(): #doctest: +NORMALIZE_WHITESPACE ... print row.user_alias, row.name_label - SELECT users_1.id AS users_1_id, users_1.name AS users_1_name, users_1.fullname AS users_1_fullname, users_1.password AS users_1_password, users_1.name AS name_label - FROM users AS users_1 + SELECT user_alias.id AS user_alias_id, + user_alias.name AS user_alias_name, + user_alias.fullname AS user_alias_fullname, + user_alias.password AS user_alias_password, + user_alias.name AS name_label + FROM users AS user_alias (){stop} <User('ed','Ed Jones', 'f8s7ccs')> ed <User('wendy','Wendy Williams', 'foobar')> wendy @@ -563,8 +587,8 @@ conjunction with ORDER BY: ... print u SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users ORDER BY users.id - LIMIT 2 OFFSET 1 - (){stop} + LIMIT ? OFFSET ? + (2, 1){stop} <User('wendy','Wendy Williams', 'foobar')> <User('mary','Mary Contrary', 'xxg527')> @@ -695,8 +719,8 @@ the first result as a scalar: SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, users.password AS users_password FROM users WHERE users.name LIKE ? ORDER BY users.id - LIMIT 1 OFFSET 0 - ('%ed',) + LIMIT ? OFFSET ? + ('%ed', 1, 0) {stop}<User('ed','Ed Jones', 'f8s7ccs')> :meth:`~sqlalchemy.orm.query.Query.one()`, fully fetches all rows, and if not @@ -795,62 +819,65 @@ completely "raw", using string names to identify desired columns: Counting -------- -:class:`~sqlalchemy.orm.query.Query` includes a convenience method for counting called :meth:`~sqlalchemy.orm.query.Query.count()`: +:class:`~sqlalchemy.orm.query.Query` includes a convenience method for +counting called :meth:`~sqlalchemy.orm.query.Query.count()`: .. sourcecode:: python+sql {sql}>>> session.query(User).filter(User.name.like('%ed')).count() #doctest: +NORMALIZE_WHITESPACE - SELECT count(1) AS count_1 - FROM users - WHERE users.name LIKE ? + SELECT count(*) AS count_1 + FROM (SELECT users.id AS users_id, + users.name AS users_name, + users.fullname AS users_fullname, + users.password AS users_password + FROM users + WHERE users.name LIKE ?) AS anon_1 ('%ed',) {stop}2 -The :meth:`~sqlalchemy.orm.query.Query.count()` method is used to determine -how many rows the SQL statement would return, and is mainly intended to return -a simple count of a single type of entity, in this case ``User``. For more -complicated sets of columns or entities where the "thing to be counted" needs -to be indicated more specifically, :meth:`~sqlalchemy.orm.query.Query.count()` -is probably not what you want. Below, a query for individual columns does -return the expected result: +The :meth:`~.Query.count()` method is used to determine +how many rows the SQL statement would return. Looking +at the generated SQL above, SQLAlchemy always places whatever it is we are +querying into a subquery, then counts the rows from that. In some cases +this can be reduced to a simpler ``SELECT count(*) FROM table``, however +modern versions of SQLAlchemy don't try to guess when this is appropriate, +as the exact SQL can be emitted using more explicit means. + +For situations where the "thing to be counted" needs +to be indicated specifically, we can specify the "count" function +directly using the expression ``func.count()``, available from the +:attr:`~sqlalchemy.sql.expression.func` construct. Below we +use it to return the count of each distinct user name: .. sourcecode:: python+sql - {sql}>>> session.query(User.id, User.name).filter(User.name.like('%ed')).count() #doctest: +NORMALIZE_WHITESPACE - SELECT count(1) AS count_1 - FROM (SELECT users.id AS users_id, users.name AS users_name - FROM users - WHERE users.name LIKE ?) AS anon_1 - ('%ed',) - {stop}2 + >>> from sqlalchemy import func + {sql}>>> session.query(func.count(User.name), User.name).group_by(User.name).all() #doctest: +NORMALIZE_WHITESPACE + SELECT count(users.name) AS count_1, users.name AS users_name + FROM users GROUP BY users.name + () + {stop}[(1, u'ed'), (1, u'fred'), (1, u'mary'), (1, u'wendy')] -...but if you look at the generated SQL, SQLAlchemy saw that we were placing -individual column expressions and decided to wrap whatever it was we were -doing in a subquery, so as to be assured that it returns the "number of rows". -This defensive behavior is not really needed here and in other cases is not -what we want at all, such as if we wanted a grouping of counts per name: +To achieve our simple ``SELECT count(*) FROM table``, we can apply it as: .. sourcecode:: python+sql - {sql}>>> session.query(User.name).group_by(User.name).count() #doctest: +NORMALIZE_WHITESPACE - SELECT count(1) AS count_1 - FROM (SELECT users.name AS users_name - FROM users GROUP BY users.name) AS anon_1 - () + {sql}>>> session.query(func.count('*')).select_from(User).scalar() + SELECT count(?) AS count_1 + FROM users + ('*',) {stop}4 -We don't want the number ``4``, we wanted some rows back. So for detailed -queries where you need to count something specific, use the ``func.count()`` -function as a column expression: +The usage of :meth:`~.Query.select_from` can be removed if we express the count in terms +of the ``User`` primary key directly: .. sourcecode:: python+sql - >>> from sqlalchemy import func - {sql}>>> session.query(func.count(User.name), User.name).group_by(User.name).all() #doctest: +NORMALIZE_WHITESPACE - SELECT count(users.name) AS count_1, users.name AS users_name - FROM users GROUP BY users.name - {stop}() - [(1, u'ed'), (1, u'fred'), (1, u'mary'), (1, u'wendy')] + {sql}>>> session.query(func.count(User.id)).scalar() #doctest: +NORMALIZE_WHITESPACE + SELECT count(users.id) AS count_1 + FROM users + () + {stop}4 Building a Relationship ======================= @@ -933,7 +960,7 @@ already been created: .. sourcecode:: python+sql - {sql}>>> metadata.create_all(engine) # doctest: +NORMALIZE_WHITESPACE + {sql}>>> Base.metadata.create_all(engine) # doctest: +NORMALIZE_WHITESPACE PRAGMA table_info("users") () PRAGMA table_info("addresses") @@ -948,6 +975,14 @@ already been created: () COMMIT +One extra step here is to make the ORM aware that the mapping for ``User`` +has changed. This is normally not necessary, except that we've already worked with +instances of ``User`` - so here we call :func:`.configure_mappers` so that the +``User.addresses`` attribute can be established:: + + >>> from sqlalchemy.orm import configure_mappers + >>> configure_mappers() + Working with Related Objects ============================= diff --git a/doc/build/static/docs.css b/doc/build/static/docs.css index 84a24771f..230cbc1af 100644 --- a/doc/build/static/docs.css +++ b/doc/build/static/docs.css @@ -303,6 +303,10 @@ div.note, div.warning, p.deprecated { background-color:#EEFFEF; } +div.topic { + background-color:#D5F0EE; +} + div.admonition, div.topic, p.deprecated { border:1px solid #CCCCCC; margin:5px 5px 5px 5px; @@ -314,7 +318,7 @@ div.warning .admonition-title { color:#FF0000; } -div.admonition .admonition-title { +div.admonition .admonition-title, div.topic .topic-title { font-weight:bold; } diff --git a/doc/build/testdocs.py b/doc/build/testdocs.py index 6aeee488a..cc6037969 100644 --- a/doc/build/testdocs.py +++ b/doc/build/testdocs.py @@ -60,7 +60,7 @@ def replace_file(s, newfile): raise ValueError("Couldn't find suitable create_engine call to replace '%s' in it" % oldfile) return s -for filename in ('orm/tutorial', 'core/tutorial',): +for filename in 'orm/tutorial',: # 'core/tutorial',: filename = '%s.rst' % filename s = open(filename).read() #s = replace_file(s, ':memory:') diff --git a/lib/sqlalchemy/engine/__init__.py b/lib/sqlalchemy/engine/__init__.py index eab462032..0d091bb11 100644 --- a/lib/sqlalchemy/engine/__init__.py +++ b/lib/sqlalchemy/engine/__init__.py @@ -130,6 +130,12 @@ def create_engine(*args, **kwargs): will establish the first actual DBAPI connection when this request is received. The :func:`.create_engine` call itself does **not** establish any actual DBAPI connections directly. + + See also: + + :ref:`engines_toplevel` + + :ref:`connections_toplevel` :param assert_unicode: Deprecated. A warning is raised in all cases when a non-Unicode object is passed when SQLAlchemy would coerce into an encoding diff --git a/lib/sqlalchemy/engine/base.py b/lib/sqlalchemy/engine/base.py index 04c14c2b7..a5f5ae223 100644 --- a/lib/sqlalchemy/engine/base.py +++ b/lib/sqlalchemy/engine/base.py @@ -2068,6 +2068,12 @@ class Engine(Connectable, log.Identified): An :class:`.Engine` object is instantiated publically using the :func:`~sqlalchemy.create_engine` function. + See also: + + :ref:`engines_toplevel` + + :ref:`connections_toplevel` + """ _execution_options = util.immutabledict() diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 15e7ba34f..434cc9584 100755 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -512,20 +512,32 @@ using the declarative form, via a special base class that defers the creation of the mapper. That recipe is available at `DeclarativeAbstractConcreteBase <http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DeclarativeAbstractConcreteBase>`_ -Mixin Classes -============== +.. _declarative_mixins: -A common need when using :mod:`~sqlalchemy.ext.declarative` is to -share some functionality, often a set of columns, across many -classes. The normal Python idiom would be to put this common code into -a base class and have all the other classes subclass this class. +Mixin and Custom Base Classes +============================== -When using :mod:`~sqlalchemy.ext.declarative`, this need is met by -using a "mixin class". A mixin class is one that isn't mapped to a -table and doesn't subclass the declarative :class:`.Base`. For example:: +A common need when using :mod:`~sqlalchemy.ext.declarative` is to +share some functionality, such as a set of common columns, some common +table options, or other mapped properties, across many +classes. The standard Python idioms for this is to have the classes +inherit from a base which includes these common features. + +When using :mod:`~sqlalchemy.ext.declarative`, this idiom is allowed +via the usage of a custom declarative base class, as well as a "mixin" class +which is inherited from in addition to the primary base. Declarative +includes several helper features to make this work in terms of how +mappings are declared. An example of some commonly mixed-in +idioms is below:: + from sqlalchemy.ext.declarative import declared_attr + class MyMixin(object): + @declared_attr + def __tablename__(cls): + return cls.__name__.lower() + __table_args__ = {'mysql_engine': 'InnoDB'} __mapper_args__= {'always_refresh': True} @@ -538,8 +550,39 @@ table and doesn't subclass the declarative :class:`.Base`. For example:: name = Column(String(1000)) Where above, the class ``MyModel`` will contain an "id" column -as well as ``__table_args__`` and ``__mapper_args__`` defined -by the ``MyMixin`` mixin class. +as the primary key, a ``__tablename__`` attribute that derives +from the name of the class itself, as well as ``__table_args__`` +and ``__mapper_args__`` defined by the ``MyMixin`` mixin class. + +Augmenting the Base +~~~~~~~~~~~~~~~~~~~ + +In addition to using a pure mixin, most of the techniques in this +section can also be applied to the base class itself, for patterns that +should apply to all classes derived from a particular base. This +is achieved using the ``cls`` argument of the :func:`.declarative_base` function:: + + from sqlalchemy.ext.declarative import declared_attr + + class Base(object): + @declared_attr + def __tablename__(cls): + return cls.__name__.lower() + + __table_args__ = {'mysql_engine': 'InnoDB'} + + id = Column(Integer, primary_key=True) + + from sqlalchemy.ext.declarative import declarative_base + + Base = declarative_base(cls=Base) + + class MyModel(Base): + name = Column(String(1000)) + +Where above, ``MyModel`` and all other classes that derive from ``Base`` will have +a table name derived from the class name, an ``id`` primary key column, as well as +the "InnoDB" engine for MySQL. Mixing in Columns ~~~~~~~~~~~~~~~~~ |