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

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""" 

26 

27from collections import OrderedDict 

28 

29import sqlalchemy as sa 

30 

31from wuttjamaican.util import load_object, parse_bool, parse_list 

32 

33 

34def get_engines(config, prefix): 

35 """ 

36 Construct and return all database engines defined for a given 

37 config prefix. 

38 

39 For instance if you have a config file with: 

40 

41 .. code-block:: ini 

42 

43 [wutta.db] 

44 keys = default, host 

45 default.url = sqlite:///tmp/default.sqlite 

46 host.url = sqlite:///tmp/host.sqlite 

47 

48 And then you call this function to get those DB engines:: 

49 

50 get_engines(config, 'wutta.db') 

51 

52 The result of that will be like:: 

53 

54 {'default': Engine(bind='sqlite:///tmp/default.sqlite'), 

55 'host': Engine(bind='sqlite:///tmp/host.sqlite')} 

56 

57 :param config: App config object. 

58 

59 :param prefix: Prefix for the config "section" which contains DB 

60 connection info. 

61 

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'] 

70 

71 make_engine = config.get_engine_maker() 

72 

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 

86 

87 

88def get_setting(session, name): 

89 """ 

90 Get a setting value from the DB. 

91 

92 Note that this assumes (for now?) the DB contains a table named 

93 ``setting`` with ``(name, value)`` columns. 

94 

95 :param session: App DB session. 

96 

97 :param name: Name of the setting to get. 

98 

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() 

103 

104 

105def make_engine_from_config( 

106 config_dict, 

107 prefix='sqlalchemy.', 

108 **kwargs): 

109 """ 

110 Construct a new DB engine from configuration dict. 

111 

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`. 

117 

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: 

121 

122 * ``poolclass`` 

123 * ``pool_pre_ping`` 

124 

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. 

128 

129 An example config file leveraging this feature: 

130 

131 .. code-block:: ini 

132 

133 [wutta.db] 

134 default.url = sqlite:///tmp/default.sqlite 

135 default.poolclass = sqlalchemy.pool:NullPool 

136 default.pool_pre_ping = true 

137 

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) 

142 

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)) 

147 

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)) 

152 

153 engine = sa.engine_from_config(config_dict, prefix, **kwargs) 

154 

155 return engine