Coverage for .tox/coverage/lib/python3.11/site-packages/wuttjamaican/db/conf.py: 100%
36 statements
« prev ^ index » next coverage.py v7.3.2, created at 2024-07-04 07:42 -0500
« prev ^ index » next coverage.py v7.3.2, created at 2024-07-04 07:42 -0500
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# WuttJamaican -- Base package for Wutta Framework
5# Copyright © 2023-2024 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24WuttJamaican - database configuration
25"""
27from collections import OrderedDict
29import sqlalchemy as sa
31from wuttjamaican.util import load_object, parse_bool, parse_list
34def get_engines(config, prefix):
35 """
36 Construct and return all database engines defined for a given
37 config prefix.
39 For instance if you have a config file with:
41 .. code-block:: ini
43 [wutta.db]
44 keys = default, host
45 default.url = sqlite:///tmp/default.sqlite
46 host.url = sqlite:///tmp/host.sqlite
48 And then you call this function to get those DB engines::
50 get_engines(config, 'wutta.db')
52 The result of that will be like::
54 {'default': Engine(bind='sqlite:///tmp/default.sqlite'),
55 'host': Engine(bind='sqlite:///tmp/host.sqlite')}
57 :param config: App config object.
59 :param prefix: Prefix for the config "section" which contains DB
60 connection info.
62 :returns: A dictionary of SQLAlchemy engines, with keys matching
63 those found in config.
64 """
65 keys = config.get(f'{prefix}.keys', usedb=False)
66 if keys:
67 keys = parse_list(keys)
68 else:
69 keys = ['default']
71 make_engine = config.get_engine_maker()
73 engines = OrderedDict()
74 cfg = config.get_dict(prefix)
75 for key in keys:
76 key = key.strip()
77 try:
78 engines[key] = make_engine(cfg, prefix=f'{key}.')
79 except KeyError:
80 if key == 'default':
81 try:
82 engines[key] = make_engine(cfg, prefix='sqlalchemy.')
83 except KeyError:
84 pass
85 return engines
88def get_setting(session, name):
89 """
90 Get a setting value from the DB.
92 Note that this assumes (for now?) the DB contains a table named
93 ``setting`` with ``(name, value)`` columns.
95 :param session: App DB session.
97 :param name: Name of the setting to get.
99 :returns: Setting value as string, or ``None``.
100 """
101 sql = sa.text("select value from setting where name = :name")
102 return session.execute(sql, params={'name': name}).scalar()
105def make_engine_from_config(
106 config_dict,
107 prefix='sqlalchemy.',
108 **kwargs):
109 """
110 Construct a new DB engine from configuration dict.
112 This is a wrapper around upstream
113 :func:`sqlalchemy:sqlalchemy.engine_from_config()`. For even
114 broader context of the SQLAlchemy
115 :class:`~sqlalchemy:sqlalchemy.engine.Engine` and their
116 configuration, see :doc:`sqlalchemy:core/engines`.
118 The purpose of the customization is to allow certain attributes of
119 the engine to be driven by config, whereas the upstream function
120 is more limited in that regard. The following in particular:
122 * ``poolclass``
123 * ``pool_pre_ping``
125 If these options are present in the configuration dict, they will
126 be coerced to appropriate Python equivalents and then passed as
127 kwargs to the upstream function.
129 An example config file leveraging this feature:
131 .. code-block:: ini
133 [wutta.db]
134 default.url = sqlite:///tmp/default.sqlite
135 default.poolclass = sqlalchemy.pool:NullPool
136 default.pool_pre_ping = true
138 Note that if present, the ``poolclass`` value must be a "spec"
139 string, as required by :func:`~wuttjamaican.util.load_object()`.
140 """
141 config_dict = dict(config_dict)
143 # convert 'poolclass' arg to actual class
144 key = f'{prefix}poolclass'
145 if key in config_dict and 'poolclass' not in kwargs:
146 kwargs['poolclass'] = load_object(config_dict.pop(key))
148 # convert 'pool_pre_ping' arg to boolean
149 key = f'{prefix}pool_pre_ping'
150 if key in config_dict and 'pool_pre_ping' not in kwargs:
151 kwargs['pool_pre_ping'] = parse_bool(config_dict.pop(key))
153 engine = sa.engine_from_config(config_dict, prefix, **kwargs)
155 return engine