From f54a3b255a5f93b44b7530d89f6d17e3938150f4 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 11 Jan 2012 16:35:34 -0500 Subject: [PATCH] add examples for multi metadata under __abstract__, custom vertical partitioning --- doc/build/orm/session.rst | 61 ++++++++++++++++++++++++++++++- lib/sqlalchemy/ext/declarative.py | 21 +++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/doc/build/orm/session.rst b/doc/build/orm/session.rst index 1c1189507b..675b2136f1 100644 --- a/doc/build/orm/session.rst +++ b/doc/build/orm/session.rst @@ -1219,6 +1219,8 @@ autocommit is disabled, it is still useful in that it forces the :class:`.Sessio into a "pending rollback" state, as a failed flush cannot be resumed in mid-operation, where the end user still maintains the "scope" of the transaction overall. +.. _session_twophase: + Enabling Two-Phase Commit ------------------------- @@ -1507,8 +1509,8 @@ Contextual Session API Partitioning Strategies ======================= -Vertical Partitioning ---------------------- +Simple Vertical Partitioning +---------------------------- Vertical partitioning places different kinds of objects, or different tables, across multiple databases:: @@ -1523,6 +1525,61 @@ across multiple databases:: session = Session() +Above, operations against either class will make usage of the :class:`.Engine` +linked to that class. Upon a flush operation, similar rules take place +to ensure each class is written to the right database. + +The transactions among the multiple databases can optionally be coordinated +via two phase commit, if the underlying backend supports it. See +:ref:`session_twophase` for an example. + +Custom Vertical Partitioning +---------------------------- + +More comprehensive rule-based class-level partitioning can be built by +overriding the :meth:`.Session.get_bind` method. Below we illustrate +a custom :class:`.Session` which delivers the following rules: + +1. Flush operations are delivered to the engine named ``master``. + +2. Operations on objects that subclass ``MyOtherClass`` all + occur on the ``other`` engine. + +3. Read operations for all other classes occur on a random + choice of the ``slave1`` or ``slave2`` database. + +:: + + engines = { + 'master':create_engine("sqlite:///master.db"), + 'other':create_engine("sqlite:///other.db"), + 'slave1':create_engine("sqlite:///slave1.db"), + 'slave2':create_engine("sqlite:///slave2.db"), + } + + from sqlalchemy.orm import Session, sessionmaker + import random + + class RoutingSession(Session): + def get_bind(self, mapper=None, clause=None): + if mapper and issubclass(mapper.class_, MyOtherClass): + return engines['other'] + elif self._flushing: + return engines['master'] + else: + return engines[ + random.choice(['slave1','slave2']) + ] + +The above :class:`.Session` class is plugged in using the ``class_`` +argument to :func:`.sessionmaker`:: + + Session = sessionmaker(class_=RoutingSession) + +This approach can be combined with multiple :class:`.MetaData` objects, +using an approach such as that of using the declarative ``__abstract__`` +keyword, described at :ref:`declarative_abstract`. + Horizontal Partitioning ----------------------- diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 61ec533439..bd655a7213 100755 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -906,6 +906,7 @@ has finished:: "" # do something with mappings +.. _declarative_abstract: ``__abstract__`` ~~~~~~~~~~~~~~~~~~~ @@ -927,6 +928,26 @@ just from the special class:: class MyMappedClass(SomeAbstractBase): "" + +One possible use of ``__abstract__`` is to use a distinct :class:`.MetaData` for different +bases:: + + Base = declarative_base() + + class DefaultBase(Base): + __abstract__ = True + metadata = MetaData() + + class OtherBase(Base): + __abstract__ = True + metadata = MetaData() + +Above, classes which inherit from ``DefaultBase`` will use one :class:`.MetaData` as the +registry of tables, and those which inherit from ``OtherBase`` will use a different one. +The tables themselves can then be created perhaps within distinct databases:: + + DefaultBase.metadata.create_all(some_engine) + OtherBase.metadata_create_all(some_other_engine) Class Constructor ================= -- 2.47.2