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.