Advanced topics
===============
Multiple databases
------------------
Sometimes you may need to talk to multiple databases. This requires you to do two
things: define the data model for each database, and configure the connection for
each database. pyramid_sqlalchemy has support for multiple databases build-in.
The first thing you will need to do is specify for each ORM class or table which
connection it is associated with. Each connection is identified by a name. If
you do not specify a name ``default`` is used instead.
.. caution::
It is strongly recommended to enable two-phased transactions when using multiple
databases. Without this there is a risk that the databases can be out of sync.
Here is an example that defines a `user` table in the default database, and a
`transaction` table in a data warehouse:
.. code-block:: python
:linenos:
from pyramid_sqlalchemy import orm_base
class User(orm_base()):
__tablename__ = 'user'
class Transaction(orm_base('warehouse')):
__tablename__ = 'transaction'
You can now see why ``orm_base`` is a function: you can pass it the connection name
and it will do the necessary linking.
The same approach works if you want to use the metadata instance directly, without
using the ORM:
.. code-block:: python
:linenos:
from pyramid_sqlalchemy import get_metadata
from sqlalchemy import Column, Table, Integer, String
transaction = Table('transaction', get_metadata('warehouse),
Column('tx_id', Integer, primary_key=True),
...
)
To connect to a database you must call ``bind_connection`` once for each connection:
.. code-block:: python
:linenos:
from sqlalchemy import create_engine
from pyramid_sqlalchemy import bind_connection
# Use a local PostgreSQL database for each default data
bind_connection(create_engine('postgresql:///myapp'))
# And connect to a remote instance for our data warehouse
bind_connection(create_engine('postgresql://data-warehouse/myapp'), 'warehouse')
When querying or updating a database you can request a session for a specific
connection. Building on our example, this is how you can retrieve the latest
transaction from the data warehouse:
.. code-block:: python
:linenos:
from pyramid_sqlalchemy import get_sql_session
sql_session = get_sql_session('warehouse')
latest_tx = sql_session.query(Transaction)\
.order_by(Transaction.timestamp)\
.limit(1)\
.first()
Since it can be tedious to always pass the name of the connection to
``get_sql_session`` you can also use ``get_sql_query``, which is smart
enough to figure out which session it needs to use.
.. code-block:: python
:linenos:
from pyramid_sqlalchemy import get_sql_query
latest_tx = get_sql_query(Transaction)\
.order_by(Transaction.timestamp)\
.limit(1)\
.first()
When writing tests you will need to define an extra fixture for extra
connections you create. This is easily done via the
:py:func:`pyramid_sqlalchemy.fixtures.sql_session_fixture`
helper.
.. code-block:: python
:linenos:
import pytest
from pyramid_sqlalchemy.fixtures import sql_session_fixture
@pytest.yield_fixture
def warehouse_sql_session():
"""A SQLAlchemy session for the warehouse database.
"""
yield from sql_session_fixture('warehouse', 'sqlite:///')
If you need to support Python versions before 3.3 you can not use ``yield from``
and need to use this more verbose variant:
.. code-block:: python
:linenos:
import pytest
from pyramid_sqlalchemy.fixtures import sql_session_fixture
@pytest.yield_fixture
def warehouse_sql_session():
"""A SQLAlchemy session for the warehouse database.
"""
for f in sql_session_fixture('warehouse', 'sqlite:///'):
yield f
Two-phase transactions
----------------------
If your application uses multiple databases or other transaction-aware systems
such as `repoze.filesafe `_, `AcidFS
`_, `pyramid_mailer
`_ or `ZODB `_
you need to `two-phase commits
`_ to coordinate
transactions. You can enable these using the ``enable_sql_two_phase_commit``
configuration directive.
.. code-block:: python
:linenos:
from pyramid_sqlalchemy import configure_all_connections
configure_all_connections(twophase=True)
For Pyramid application you can also do this through the configurator instance:
.. code-block:: python
:linenos:
def main(global_config, **settings):
config = Configurator(settings=settings)
config.include('pyramid_sqlalchemy')
config.enable_sql_two_phase_commit()
.. warning::
Please note that this is not supported for all SQL servers. PostgreSQL is
the only server where this is guaranteed to work. SQL Server does support
two -phase transactions, but the Python driver support for it is unusable.