Connecting to the Database

Creating connection instance

All connection IO is served through a lightcurvedb.core.connection.DB instance. There are a couple of methods of instantiating a DB instance.

Default configuration

Your default configuration is expected to be at ~/.config/lightcurvedb/db.conf. One of the easier forms of getting a database class is by:

1from lightcurvedb import db

Where db is an instantiated database connection object.

Overriding configuration

Sometimes you might want to connect to a different database or provide different runtime connection parameters. The default ``db`` object does not provide this functionality.

Instead lightcurvedb exposes the factory function db_from_config which allows specification of different configuration files or runtime overrides/parameters.

 1from lightcurvedb import db_from_config
 2
 3# Equivalent to previous example
 4db = db_from_config()
 5
 6# Or you can override configs
 7db = db_from_config(config_path="/path/to/config.conf")
 8
 9# You can even provide user-relative paths
10db = db_from_config(config_path="~/some/user/relativepath.conf")

Opening a Connection

Obtaining database instances will return a DB object in a closed state. There are a few ways to open and close connections to the database.

Declaratively

Connections may be manually opened and closed via:

1db.open()
2# Perform queries, inserts, updates, deletions, etc
3foo()
4
5db.close()

In this manner, users are expected to open and close their connections responsibly. Failure to do so might result in their connections timing out due to administrator watch-dog processes.

Contextuals

Maintaining manual connections can be cumbersome, especially when taking into consideration that exceptions may arise and other runtime effects. Generally it’s best to allow python to manage cleaning up resources in a clear manner.

This is accomplished using the with python block.

1with db as open_db:
2    open_db.foo()
3    # Other commands...
4# db is now closed

You may also short-hand this a little further with:

1with db:
2    db.foo()

Existing the with block will always free the resource. Whether that reason is reaching the end of the block, or an exception being raised somewhere within the block, or even a return statement.

Functional Wrappers

with blocks are fine until you notice your code starting to have major indented blocks.

 1with db:
 2    if something:
 3        for x in array:
 4            db.add(x)
 5    models = (
 6        db
 7        .query(Model)
 8        .filter_by(foo=bar)
 9        .limit(20)
10        .all()
11    )
12    for model in models:
13        print(model)
14        model.foo = "not bar"
15    db.commit()

Everywhere inside the block needs an open connection. So all code is indented as to be syntactically inside the block. One could get around this by defining the code-block inside a function.

 1def operation(db):
 2    if something:
 3        for x in array:
 4            db.add(x)
 5    models = (
 6        db
 7        .query(Model)
 8        .filter_by(foo=bar)
 9        .limit(20)
10        .all()
11    )
12    for model in models:
13        print(model)
14        model.foo = "not bar"
15    db.commit()
16
17# ...
18with db:
19    operation(db)

Which is arguably more DRY, you can call this function on any open database connection. But could still result in errors if called without an active connection.

So lightcurvedb defines a decorator which always gives the wrapped function an open database session.

 1from lightcurvedb.io.pipeline import db_scope
 2
 3@db_scope()
 4def operation(session):
 5    if something:
 6        for x in array:
 7            session.add(x)
 8    models = (
 9        session
10        .query(Model)
11        .filter_by(foo=bar)
12        .limit(20)
13        .all()
14    )
15    for model in models:
16        print(model)
17        model.foo = "not bar"
18    session.commit()
19
20# ...
21operation()

The db_scope() decorator automatically provides an open database session as the first positional argument to the wrapped function. The session is properly closed when the function returns, with automatic rollback of any uncommitted changes.

By default, db_scope uses the global LCDB_Session sessionmaker, but you can provide your own:

1from sqlalchemy.orm import sessionmaker
2from lightcurvedb.io.pipeline import db_scope
3
4# Create a custom sessionmaker
5custom_session = sessionmaker(bind=my_engine)
6
7@db_scope(session_factory=custom_session)
8def custom_operation(session):
9    return session.query(Model).all()

You can also pass additional arguments to the session factory:

1@db_scope(info={"task": "data_export"})
2def export_operation(session):
3    # session.info contains {"task": "data_export"}
4    return session.query(Model).all()

The decorator logs the function name for tracking purposes. You can override this with db_scope(application_name="custom_name") for special cases.