##// END OF EJS Templates
chore(sync-up): synced libs/settings maker from vcsserver
super-admin -
r5337:363e5238 default
parent child Browse files
Show More
@@ -1,166 +1,184 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import os
20 20 import textwrap
21 21 import string
22 22 import functools
23 23 import logging
24 24 import tempfile
25 25 import logging.config
26
26 27 from rhodecode.lib.type_utils import str2bool, aslist
27 28
28 29 log = logging.getLogger(__name__)
29 30
30 31
31 32 # skip keys, that are set here, so we don't double process those
32 33 set_keys = {
33 34 '__file__': ''
34 35 }
35 36
36 37
37 class SettingsMaker(object):
38 class SettingsMaker:
38 39
39 40 def __init__(self, app_settings):
40 41 self.settings = app_settings
41 42
42 43 @classmethod
43 44 def _bool_func(cls, input_val):
45 if isinstance(input_val, bytes):
46 # decode to str
47 input_val = input_val.decode('utf8')
44 48 return str2bool(input_val)
45 49
46 50 @classmethod
47 51 def _int_func(cls, input_val):
48 52 return int(input_val)
49 53
50 54 @classmethod
51 55 def _float_func(cls, input_val):
52 56 return float(input_val)
53 57
54 58 @classmethod
55 59 def _list_func(cls, input_val, sep=','):
56 60 return aslist(input_val, sep=sep)
57 61
58 62 @classmethod
59 63 def _string_func(cls, input_val, lower=True):
60 64 if lower:
61 65 input_val = input_val.lower()
62 66 return input_val
63 67
64 68 @classmethod
69 def _string_no_quote_func(cls, input_val, lower=True):
70 """
71 Special case string function that detects if value is set to empty quote string
72 e.g.
73
74 core.binar_dir = ""
75 """
76
77 input_val = cls._string_func(input_val, lower=lower)
78 if input_val in ['""', "''"]:
79 return ''
80
81 @classmethod
65 82 def _dir_func(cls, input_val, ensure_dir=False, mode=0o755):
66 83
67 84 # ensure we have our dir created
68 85 if not os.path.isdir(input_val) and ensure_dir:
69 os.makedirs(input_val, mode=mode)
86 os.makedirs(input_val, mode=mode, exist_ok=True)
70 87
71 88 if not os.path.isdir(input_val):
72 89 raise Exception(f'Dir at {input_val} does not exist')
73 90 return input_val
74 91
75 92 @classmethod
76 93 def _file_path_func(cls, input_val, ensure_dir=False, mode=0o755):
77 94 dirname = os.path.dirname(input_val)
78 95 cls._dir_func(dirname, ensure_dir=ensure_dir)
79 96 return input_val
80 97
81 98 @classmethod
82 99 def _key_transformator(cls, key):
83 100 return "{}_{}".format('RC'.upper(), key.upper().replace('.', '_').replace('-', '_'))
84 101
85 102 def maybe_env_key(self, key):
86 103 # now maybe we have this KEY in env, search and use the value with higher priority.
87 104 transformed_key = self._key_transformator(key)
88 105 envvar_value = os.environ.get(transformed_key)
89 106 if envvar_value:
90 107 log.debug('using `%s` key instead of `%s` key for config', transformed_key, key)
91 108
92 109 return envvar_value
93 110
94 111 def env_expand(self):
95 112 replaced = {}
96 113 for k, v in self.settings.items():
97 114 if k not in set_keys:
98 115 envvar_value = self.maybe_env_key(k)
99 116 if envvar_value:
100 117 replaced[k] = envvar_value
101 118 set_keys[k] = envvar_value
102 119
103 120 # replace ALL keys updated
104 121 self.settings.update(replaced)
105 122
106 123 def enable_logging(self, logging_conf=None, level='INFO', formatter='generic'):
107 124 """
108 125 Helper to enable debug on running instance
109 126 :return:
110 127 """
111 128
112 129 if not str2bool(self.settings.get('logging.autoconfigure')):
113 130 log.info('logging configuration based on main .ini file')
114 131 return
115 132
116 133 if logging_conf is None:
117 134 logging_conf = self.settings.get('logging.logging_conf_file') or ''
118 135
119 136 if not os.path.isfile(logging_conf):
120 137 log.error('Unable to setup logging based on %s, '
121 138 'file does not exist.... specify path using logging.logging_conf_file= config setting. ', logging_conf)
122 139 return
123 140
124 141 with open(logging_conf, 'rt') as f:
125 142 ini_template = textwrap.dedent(f.read())
126 143 ini_template = string.Template(ini_template).safe_substitute(
127 144 RC_LOGGING_LEVEL=os.environ.get('RC_LOGGING_LEVEL', '') or level,
128 145 RC_LOGGING_FORMATTER=os.environ.get('RC_LOGGING_FORMATTER', '') or formatter
129 146 )
130 147
131 148 with tempfile.NamedTemporaryFile(prefix='rc_logging_', suffix='.ini', delete=False) as f:
132 149 log.info('Saved Temporary LOGGING config at %s', f.name)
133 150 f.write(ini_template)
134 151
135 152 logging.config.fileConfig(f.name)
136 153 os.remove(f.name)
137 154
138 155 def make_setting(self, key, default, lower=False, default_when_empty=False, parser=None):
139 156 input_val = self.settings.get(key, default)
140 157
141 158 if default_when_empty and not input_val:
142 159 # use default value when value is set in the config but it is empty
143 160 input_val = default
144 161
145 162 parser_func = {
146 163 'bool': self._bool_func,
147 164 'int': self._int_func,
148 165 'float': self._float_func,
149 166 'list': self._list_func,
150 167 'list:newline': functools.partial(self._list_func, sep='/n'),
151 168 'list:spacesep': functools.partial(self._list_func, sep=' '),
152 169 'string': functools.partial(self._string_func, lower=lower),
170 'string:noquote': functools.partial(self._string_no_quote_func, lower=lower),
153 171 'dir': self._dir_func,
154 172 'dir:ensured': functools.partial(self._dir_func, ensure_dir=True),
155 173 'file': self._file_path_func,
156 174 'file:ensured': functools.partial(self._file_path_func, ensure_dir=True),
157 175 None: lambda i: i
158 176 }[parser]
159 177
160 178 envvar_value = self.maybe_env_key(key)
161 179 if envvar_value:
162 180 input_val = envvar_value
163 181 set_keys[key] = input_val
164 182
165 183 self.settings[key] = parser_func(input_val)
166 184 return self.settings[key]
@@ -1,169 +1,183 b''
1 1 # Copyright (C) 2011-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import typing
20 20 import base64
21 21 import logging
22 22 from unidecode import unidecode
23 23
24 24 import rhodecode
25 25 from rhodecode.lib.type_utils import aslist
26 26
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 def safe_int(val, default=None) -> int:
32 32 """
33 33 Returns int() of val if val is not convertable to int use default
34 34 instead
35 35
36 36 :param val:
37 37 :param default:
38 38 """
39 39
40 40 try:
41 41 val = int(val)
42 42 except (ValueError, TypeError):
43 43 val = default
44 44
45 45 return val
46 46
47 47
48 48 def safe_float(val, default=None) -> float:
49 49 """
50 50 Returns float() of val if val is not convertable to float use default
51 51 instead
52 52
53 53 :param val:
54 54 :param default:
55 55 """
56 56
57 57 try:
58 58 val = float(val)
59 59 except (ValueError, TypeError):
60 60 val = default
61 61
62 62 return val
63 63
64 64
65 65 def base64_to_str(text: str | bytes) -> str:
66 66 return safe_str(base64.encodebytes(safe_bytes(text))).strip()
67 67
68 68
69 69 def get_default_encodings() -> list[str]:
70 70 return aslist(rhodecode.CONFIG.get('default_encoding', 'utf8'), sep=',')
71 71
72 72
73 73 DEFAULT_ENCODINGS = get_default_encodings()
74 74
75 75
76 76 def safe_str(str_, to_encoding=None) -> str:
77 77 """
78 78 safe str function. Does few trick to turn unicode_ into string
79 79
80 80 :param str_: str to encode
81 81 :param to_encoding: encode to this type UTF8 default
82 82 """
83 83 if isinstance(str_, str):
84 84 return str_
85 85
86 86 # if it's bytes cast to str
87 87 if not isinstance(str_, bytes):
88 88 return str(str_)
89 89
90 90 to_encoding = to_encoding or DEFAULT_ENCODINGS
91 91 if not isinstance(to_encoding, (list, tuple)):
92 92 to_encoding = [to_encoding]
93 93
94 94 for enc in to_encoding:
95 95 try:
96 96 return str(str_, enc)
97 97 except UnicodeDecodeError:
98 98 pass
99 99
100 100 return str(str_, to_encoding[0], 'replace')
101 101
102 102
103 103 def safe_bytes(str_, from_encoding=None) -> bytes:
104 104 """
105 105 safe bytes function. Does few trick to turn str_ into bytes string:
106 106
107 107 :param str_: string to decode
108 108 :param from_encoding: encode from this type UTF8 default
109 109 """
110 110 if isinstance(str_, bytes):
111 111 return str_
112 112
113 113 if not isinstance(str_, str):
114 114 raise ValueError(f'safe_bytes cannot convert other types than str: got: {type(str_)}')
115 115
116 116 from_encoding = from_encoding or get_default_encodings()
117 117 if not isinstance(from_encoding, (list, tuple)):
118 118 from_encoding = [from_encoding]
119 119
120 120 for enc in from_encoding:
121 121 try:
122 122 return str_.encode(enc)
123 123 except UnicodeDecodeError:
124 124 pass
125 125
126 126 return str_.encode(from_encoding[0], 'replace')
127 127
128 128
129 129 def ascii_bytes(str_, allow_bytes=False) -> bytes:
130 130 """
131 131 Simple conversion from str to bytes, with assumption that str_ is pure ASCII.
132 132 Fails with UnicodeError on invalid input.
133 133 This should be used where encoding and "safe" ambiguity should be avoided.
134 134 Where strings already have been encoded in other ways but still are unicode
135 135 string - for example to hex, base64, json, urlencoding, or are known to be
136 136 identifiers.
137 137 """
138 138 if allow_bytes and isinstance(str_, bytes):
139 139 return str_
140 140
141 141 if not isinstance(str_, str):
142 142 raise ValueError(f'ascii_bytes cannot convert other types than str: got: {type(str_)}')
143 143 return str_.encode('ascii')
144 144
145 145
146 146 def ascii_str(str_) -> str:
147 147 """
148 148 Simple conversion from bytes to str, with assumption that str_ is pure ASCII.
149 149 Fails with UnicodeError on invalid input.
150 150 This should be used where encoding and "safe" ambiguity should be avoided.
151 151 Where strings are encoded but also in other ways are known to be ASCII, and
152 152 where a unicode string is wanted without caring about encoding. For example
153 153 to hex, base64, urlencoding, or are known to be identifiers.
154 154 """
155 155
156 156 if not isinstance(str_, bytes):
157 157 raise ValueError(f'ascii_str cannot convert other types than bytes: got: {type(str_)}')
158 158 return str_.decode('ascii')
159 159
160 160
161 161 def convert_special_chars(str_) -> str:
162 162 """
163 163 trie to replace non-ascii letters to their ascii representation eg::
164 164
165 165 `żołw` converts into `zolw`
166 166 """
167 167 value = safe_str(str_)
168 168 converted_value = unidecode(value)
169 169 return converted_value
170
171
172 def splitnewlines(text: bytes):
173 """
174 like splitlines, but only split on newlines.
175 """
176
177 lines = [_l + b'\n' for _l in text.split(b'\n')]
178 if lines:
179 if lines[-1] == b'\n':
180 lines.pop()
181 else:
182 lines[-1] = lines[-1][:-1]
183 return lines
General Comments 0
You need to be logged in to leave comments. Login now