API Reference
=============

Database
--------

Setting up the database
+++++++++++++++++++++++

The get or create the database utility, simply call::

    db = risclog.sqlalchemy.db.get_database()
    db.register_engine('postgresql://user:@localhost/dbname')

This creates an `risclog.sqlalchemy.interfaces.IDatabase` utility and registers
it at the ``Zope Component Architecture`` (ZCA). It is able to coordinate the
access to multiple databases::

    db = zope.component.getUtility(
        risclog.sqlalchemy.interfaces.IDatabase)
    db.register_engine('postgresql://user:@localhost/otherdb',
                       name='otherdb')

.. function:: risclog.sqlalchemy.db.get_database(testing=False)

    Also takes an optional ``testing`` parameter and ensures, that the testing
    status matches :attr:`testing`.


DataBase API
++++++++++++

.. class:: risclog.sqlalchemy.db.DataBase(testing=False)

    Initialize the database in ``testing`` mode.

    .. method:: register_engine(dsn, engine_args={}, name=_BLANK)

        Register a new engine with the database utility.

    .. method:: get_engine(name=_BLANK)

        Get a registered engine by its name.

    .. method:: get_all_engines()

        Get all registered engines.

    .. method:: drop_engine(name=_BLANK)

        Disposes an engine. It gets unregistered from the database utility.

    .. method:: create_all(engine_name=_BLANK, create_defaults=True)

        Create all tables etc. for an engine.

    .. method:: setup_utility()

        Register the database as a ZCA utility.

        Also verifies that the testing status of the database and this utility
        match.

    .. method:: query()

        Query the session.

    .. method:: empty(engine, table_names=None)

        Truncate any tables passed, or all tables found in the engine.

    .. attribute:: session

        Return the database session.

    .. attribute:: testing

        Marker if database is in testing mode or in production mode.

Database migrations with alemic
+++++++++++++++++++++++++++++++

If you are using alemic to migrate your database, you can pass the location of
you alembic setup to ``register_engine``::

    db = risclog.sqlalchemy.db.get_database()
    db.register_engine('postgresql://user:@localhost/dbname',
                    alembic_location='my.package:alembic')

The database utility ensures, that your database revision matches the current
head and refuses initialization if your database is not migrated.


ObjectBase - the base for your models
-------------------------------------

Creating a model with risclog.sqlalchemy
++++++++++++++++++++++++++++++++++++++++

Each table in your database is mapped to a python class (the model). There are
two ways you can create those models: relected and non reflected. Reflected
models are initialized on the database, so you do not need to crate the tables
via sql commands. Non reflected models expect the tables to be present on the
database.

You create a non reflected ObjectBase for your models via::

    import risclog.sqlalchemy.model

    Object = risclog.sqlalchemy.model.declarative_base(
        risclog.sqlalchemy.model.ObjectBase)

Reflected Objects are created with the ``ReflectedObjectBase``::

    Object = risclog.sqlalchemy.model.declarative_base(
        risclog.sqlalchemy.model.ReflectedObjectBase)

If you need to create your own ReflectedObject as declarative_base you may
call::

    Object = risclog.sqlalchemy.model.declarative_base(
        cls=risclog.sqlalchemy.model.ReflectedObjectBase,
        metaclass=risclog.sqlalchemy.model.EnsureDeferredReflection)

If you want to use `create_defaults` on a model, you must enable you own
``class_registry`` by calling::

    class_registry = {}
    Object = risclog.sqlalchemy.model.declarative_base(
        risclog.sqlalchemy.model.ObjectBase,
        class_registry=class_registry)

Now you can define your models with one of the above Objects::

    from sqlalchemy import Column, String

    class MyModel(Object):
        name = Column(String(30))


ObjectBase API
++++++++++++++

Your models created with ``risclog.sqlalchemy`` have the following attributes
and methods.

.. class:: risclog.sqlalchemy.model.ObjectBase()

    .. attribute:: _engine_name

        Change this if you want to use multiple databases. This is the name you
        specified on engine registration (:func:`register_engine`).

        Default value: ``_BLANK``

    .. attribute:: __tablename__

        Change this if your table is different from the models class name.

    .. classmethod:: create(cls)

        Canonical way to create an object of your model::

            mymodel = MyModel.create(name='foo')

    .. classmethod:: find_or_create(kwargs)

        Return an instance of your model if found by kwargs or create a new one from
        kwargs::

            mymodel = MyModel.find_or_create(name='foo')

    .. classmethod:: query()

        Return a query instance for your model::

            mymodel = MyModel.query().filter(MyModel.name == 'foo').one()

    .. method:: __json__(request)

        Returns json serializable representation of this object::

            import json
            json.dumps(model)
            '{"id": 1, "name": "foo"}'

    .. method:: persist()

        Add make the newly created object known to the database::

            mymodel.persist()

    .. method:: delete()

        Remove the object from the database::

            mymodel.delete()


Testing
-------

``risclog.sqlalchemy`` needs ``gocept.testdb`` for the basic database handling
in testing environments.

Testing with ``plone.testing``
++++++++++++++++++++++++++++++

``risclog.sqlalchemy`` provides a DatabaseLayer for your testing environment.
You should define your own via::

    import gocept.testdb
    import plone.testing
    import risclog.kravagportal.model
    import risclog.sqlalchemy.testlayer

    class DatabaseLayer(plone.testing.Layer):

        defaultBases = [
            risclog.sqlalchemy.testlayer.DatabaseLayer(
                risclog.kravagportal.model.ENGINE_NAME,
                gocept.testdb.PostgreSQL)]

    LAYER = DatabaseLayer()

Using this ;ayer in your tests will create a testing database with
``gocept.testdb`` and allow you to create your test data via
``mymodel.create()``.

Testing with ``py.test``
++++++++++++++++++++++++

If you like to test using pytest, there are some database fixtures that you can
use for your testing configuration. A minimum contest setup might look like::

    import pytest
    import risclog.sqlalchemy.testing

    @pytest.fixture(scope='session')
    def database_session(request):
        """Set up and tear down the database."""
        return risclog.sqlalchemy.testing.database_fixture_factory(
            request, 'my.package', create_all=True)

    @pytest.fixture(scope='function')
    def database(request, database_session):
        """Perform database setup and tear down for test function."""
        (risclog.sqlalchemy.testing.
         database_test_livecycle_fixture_factory(request))
        return database_session
