dsw-config 4.27.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dsw/config/__init__.py +4 -0
- dsw/config/build_info.py +17 -0
- dsw/config/keys.py +268 -0
- dsw/config/logging.py +51 -0
- dsw/config/model.py +82 -0
- dsw/config/parser.py +137 -0
- dsw/config/py.typed +0 -0
- dsw/config/sentry.py +60 -0
- dsw_config-4.27.0.dist-info/METADATA +43 -0
- dsw_config-4.27.0.dist-info/RECORD +11 -0
- dsw_config-4.27.0.dist-info/WHEEL +4 -0
dsw/config/__init__.py
ADDED
dsw/config/build_info.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated file
|
|
2
|
+
# - do not overwrite
|
|
3
|
+
# - do not include in git
|
|
4
|
+
from collections import namedtuple
|
|
5
|
+
|
|
6
|
+
BuildInfo = namedtuple(
|
|
7
|
+
'BuildInfo',
|
|
8
|
+
['version', 'built_at', 'sha', 'branch', 'tag'],
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
BUILD_INFO = BuildInfo(
|
|
12
|
+
version='v4.27.0~8ec71bd',
|
|
13
|
+
built_at='2026-02-03 08:44:06Z',
|
|
14
|
+
sha='8ec71bd85dfbea66adedb6590f7d76ae5143bbaa',
|
|
15
|
+
branch='HEAD',
|
|
16
|
+
tag='v4.27.0',
|
|
17
|
+
)
|
dsw/config/keys.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# pylint: disable=too-few-public-methods
|
|
2
|
+
import collections
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
T = typing.TypeVar('T')
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def cast_bool(value: typing.Any) -> bool:
|
|
10
|
+
return bool(value)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cast_optional_bool(value: typing.Any) -> bool | None:
|
|
14
|
+
if value is None:
|
|
15
|
+
return None
|
|
16
|
+
return bool(value)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def cast_int(value: typing.Any) -> int:
|
|
20
|
+
return int(value)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def cast_optional_int(value: typing.Any) -> int | None:
|
|
24
|
+
if value is None:
|
|
25
|
+
return None
|
|
26
|
+
return int(value)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def cast_float(value: typing.Any) -> float:
|
|
30
|
+
return float(value)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def cast_optional_float(value: typing.Any) -> float | None:
|
|
34
|
+
if value is None:
|
|
35
|
+
return None
|
|
36
|
+
return float(value)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def cast_str(value: typing.Any) -> str:
|
|
40
|
+
return str(value)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def cast_optional_str(value: typing.Any) -> str | None:
|
|
44
|
+
if value is None:
|
|
45
|
+
return None
|
|
46
|
+
return str(value)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cast_optional_dict(value: typing.Any) -> dict | None:
|
|
50
|
+
if not isinstance(value, dict):
|
|
51
|
+
return None
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ConfigKey[T]:
|
|
56
|
+
|
|
57
|
+
def __init__(self, *, yaml_path: list[str],
|
|
58
|
+
cast: typing.Callable[[typing.Any], T],
|
|
59
|
+
var_names=None, default=None, required=False):
|
|
60
|
+
self.yaml_path = yaml_path
|
|
61
|
+
self.var_names = var_names or [] # type: list[str]
|
|
62
|
+
self.default = default
|
|
63
|
+
self.required = required
|
|
64
|
+
self.cast = cast
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
return f'ConfigKey: {".".join(self.yaml_path)}'
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ConfigKeysMeta(type):
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
# pylint: disable-next=unused-argument
|
|
74
|
+
def __prepare__(mcs, name: str, bases: tuple,
|
|
75
|
+
/, **kwargs) -> collections.abc.MutableMapping[str, object]:
|
|
76
|
+
return collections.OrderedDict()
|
|
77
|
+
|
|
78
|
+
def __init__(cls, name, bases, namespace):
|
|
79
|
+
cls._config_keys = []
|
|
80
|
+
for attr in namespace:
|
|
81
|
+
if attr.startswith('_'):
|
|
82
|
+
continue
|
|
83
|
+
value = getattr(cls, attr)
|
|
84
|
+
if isinstance(value, ConfigKey):
|
|
85
|
+
cls._config_keys.append(value)
|
|
86
|
+
keys = getattr(value, '_config_keys', None)
|
|
87
|
+
if keys is not None and isinstance(keys, list):
|
|
88
|
+
cls._config_keys.extend(keys)
|
|
89
|
+
super().__init__(name, bases, namespace)
|
|
90
|
+
|
|
91
|
+
def __iter__(cls):
|
|
92
|
+
return iter(cls._config_keys)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ConfigKeysContainer(metaclass=ConfigKeysMeta):
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class _GeneralKeys(ConfigKeysContainer):
|
|
100
|
+
environment = ConfigKey(
|
|
101
|
+
yaml_path=['general', 'environment'],
|
|
102
|
+
var_names=['GENERAL_ENVIRONMENT'],
|
|
103
|
+
default='Production',
|
|
104
|
+
cast=cast_str,
|
|
105
|
+
)
|
|
106
|
+
client_url = ConfigKey(
|
|
107
|
+
yaml_path=['general', 'clientUrl'],
|
|
108
|
+
var_names=['GENERAL_CLIENT_URL'],
|
|
109
|
+
default='http://localhost:8080',
|
|
110
|
+
cast=cast_str,
|
|
111
|
+
)
|
|
112
|
+
secret = ConfigKey(
|
|
113
|
+
yaml_path=['general', 'secret'],
|
|
114
|
+
var_names=['GENERAL_SECRET'],
|
|
115
|
+
default='',
|
|
116
|
+
cast=cast_str,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class _LoggingKeys(ConfigKeysContainer):
|
|
121
|
+
level = ConfigKey(
|
|
122
|
+
yaml_path=['logging', 'level'],
|
|
123
|
+
var_names=['LOGGING_ENVIRONMENT'],
|
|
124
|
+
default='INFO',
|
|
125
|
+
cast=cast_str,
|
|
126
|
+
)
|
|
127
|
+
global_level = ConfigKey(
|
|
128
|
+
yaml_path=['logging', 'globalLevel'],
|
|
129
|
+
var_names=['LOGGING_CLIENT_URL'],
|
|
130
|
+
default='WARNING',
|
|
131
|
+
cast=cast_str,
|
|
132
|
+
)
|
|
133
|
+
format = ConfigKey(
|
|
134
|
+
yaml_path=['logging', 'format'],
|
|
135
|
+
var_names=['LOGGING_FORMAT'],
|
|
136
|
+
default='%(asctime)s | %(levelname)8s | %(name)s: [T:%(traceId)s] %(message)s',
|
|
137
|
+
cast=cast_str,
|
|
138
|
+
)
|
|
139
|
+
dict_config = ConfigKey(
|
|
140
|
+
yaml_path=['logging', 'dict_config'],
|
|
141
|
+
var_names=[],
|
|
142
|
+
default=None,
|
|
143
|
+
cast=cast_optional_dict,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class _CloudKeys(ConfigKeysContainer):
|
|
148
|
+
enabled = ConfigKey(
|
|
149
|
+
yaml_path=['cloud', 'enabled'],
|
|
150
|
+
var_names=['CLOUD_ENABLED'],
|
|
151
|
+
default=False,
|
|
152
|
+
cast=cast_bool,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class _SentryKeys(ConfigKeysContainer):
|
|
157
|
+
enabled = ConfigKey(
|
|
158
|
+
yaml_path=['sentry', 'enabled'],
|
|
159
|
+
var_names=['SENTRY_ENABLED'],
|
|
160
|
+
default=False,
|
|
161
|
+
cast=cast_bool,
|
|
162
|
+
)
|
|
163
|
+
worker_dsn = ConfigKey(
|
|
164
|
+
yaml_path=['sentry', 'workersDsn'],
|
|
165
|
+
var_names=['SENTRY_WORKER_DSN', 'SENTRY_DSN'],
|
|
166
|
+
default='',
|
|
167
|
+
cast=cast_str,
|
|
168
|
+
)
|
|
169
|
+
traces_sample_rate = ConfigKey(
|
|
170
|
+
yaml_path=['sentry', 'tracesSampleRate'],
|
|
171
|
+
var_names=['SENTRY_TRACES_SAMPLE_RATE'],
|
|
172
|
+
default='',
|
|
173
|
+
cast=cast_str,
|
|
174
|
+
)
|
|
175
|
+
max_breadcrumbs = ConfigKey(
|
|
176
|
+
yaml_path=['sentry', 'maxBreadcrumbs'],
|
|
177
|
+
var_names=['SENTRY_MAX_BREADCRUMBS'],
|
|
178
|
+
default='',
|
|
179
|
+
cast=cast_str,
|
|
180
|
+
)
|
|
181
|
+
environment = ConfigKey(
|
|
182
|
+
yaml_path=['sentry', 'environment'],
|
|
183
|
+
var_names=['SENTRY_ENVIRONMENT'],
|
|
184
|
+
default='production',
|
|
185
|
+
cast=cast_str,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class _DatabaseKeys(ConfigKeysContainer):
|
|
190
|
+
connection_string = ConfigKey(
|
|
191
|
+
yaml_path=['database', 'connectionString'],
|
|
192
|
+
var_names=['DATABASE_CONNECTION_STRING'],
|
|
193
|
+
default='postgresql://postgres:postgres@postgres:5432/engine-wizard',
|
|
194
|
+
cast=cast_str,
|
|
195
|
+
)
|
|
196
|
+
connection_timeout = ConfigKey(
|
|
197
|
+
yaml_path=['database', 'connectionTimeout'],
|
|
198
|
+
var_names=['DATABASE_CONNECTION_TIMEOUT'],
|
|
199
|
+
default=30000,
|
|
200
|
+
cast=cast_int,
|
|
201
|
+
)
|
|
202
|
+
queue_timeout = ConfigKey(
|
|
203
|
+
yaml_path=['database', 'queueTimeout'],
|
|
204
|
+
var_names=['DATABASE_QUEUE_TIMEOUT'],
|
|
205
|
+
default=180,
|
|
206
|
+
cast=cast_int,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class _S3Keys(ConfigKeysContainer):
|
|
211
|
+
url = ConfigKey(
|
|
212
|
+
yaml_path=['s3', 'url'],
|
|
213
|
+
var_names=['S3_URL'],
|
|
214
|
+
default='http://minio:9000',
|
|
215
|
+
cast=cast_str,
|
|
216
|
+
)
|
|
217
|
+
bucket = ConfigKey(
|
|
218
|
+
yaml_path=['s3', 'bucket'],
|
|
219
|
+
var_names=['S3_BUCKET'],
|
|
220
|
+
default='engine-wizard',
|
|
221
|
+
cast=cast_str,
|
|
222
|
+
)
|
|
223
|
+
region = ConfigKey(
|
|
224
|
+
yaml_path=['s3', 'region'],
|
|
225
|
+
var_names=['S3_REGION'],
|
|
226
|
+
default='eu-central-1',
|
|
227
|
+
cast=cast_str,
|
|
228
|
+
)
|
|
229
|
+
username = ConfigKey(
|
|
230
|
+
yaml_path=['s3', 'username'],
|
|
231
|
+
var_names=['S3_USERNAME'],
|
|
232
|
+
default='minio',
|
|
233
|
+
cast=cast_str,
|
|
234
|
+
)
|
|
235
|
+
password = ConfigKey(
|
|
236
|
+
yaml_path=['s3', 'password'],
|
|
237
|
+
var_names=['S3_PASSWORD'],
|
|
238
|
+
default='minioPassword',
|
|
239
|
+
cast=cast_str,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class _AWSKeys(ConfigKeysContainer):
|
|
244
|
+
access_key_id = ConfigKey(
|
|
245
|
+
yaml_path=['aws', 'awsAccessKeyId'],
|
|
246
|
+
var_names=['AWS_AWS_ACCESS_KEY_ID'],
|
|
247
|
+
cast=cast_optional_str,
|
|
248
|
+
)
|
|
249
|
+
secret_access_key = ConfigKey(
|
|
250
|
+
yaml_path=['aws', 'awsSecretAccessKey'],
|
|
251
|
+
var_names=['AWS_AWS_SECRET_ACCESS_KEY'],
|
|
252
|
+
cast=cast_optional_str,
|
|
253
|
+
)
|
|
254
|
+
region = ConfigKey(
|
|
255
|
+
yaml_path=['aws', 'awsRegion'],
|
|
256
|
+
var_names=['AWS_AWS_REGION'],
|
|
257
|
+
cast=cast_optional_str,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class ConfigKeys(ConfigKeysContainer):
|
|
262
|
+
aws = _AWSKeys
|
|
263
|
+
cloud = _CloudKeys
|
|
264
|
+
database = _DatabaseKeys
|
|
265
|
+
general = _GeneralKeys
|
|
266
|
+
logging = _LoggingKeys
|
|
267
|
+
s3 = _S3Keys
|
|
268
|
+
sentry = _SentryKeys
|
dsw/config/logging.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import logging.config
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DSWLogFilter(logging.Filter):
|
|
7
|
+
|
|
8
|
+
def __init__(self, extras=None):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.extras = extras or {'traceId': ''}
|
|
11
|
+
|
|
12
|
+
def set_extra(self, key: str, value: str):
|
|
13
|
+
self.extras[key] = value
|
|
14
|
+
|
|
15
|
+
def filter(self, record: logging.LogRecord):
|
|
16
|
+
record.__dict__.update(self.extras)
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
LOG_FILTER = DSWLogFilter()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DSWLogger(logging.Logger):
|
|
24
|
+
|
|
25
|
+
def __init__(self, *args, **kwargs):
|
|
26
|
+
super().__init__(*args, **kwargs)
|
|
27
|
+
self.addFilter(LOG_FILTER)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def prepare_logging(logging_cfg):
|
|
31
|
+
# pylint: disable-next=no-member
|
|
32
|
+
logger_dict = logging.root.manager.loggerDict
|
|
33
|
+
if logging_cfg.dict_config is not None:
|
|
34
|
+
logging.config.dictConfig(logging_cfg.dict_config)
|
|
35
|
+
else:
|
|
36
|
+
logging.basicConfig(
|
|
37
|
+
stream=sys.stdout,
|
|
38
|
+
level=logging_cfg.global_level,
|
|
39
|
+
format=logging_cfg.message_format,
|
|
40
|
+
)
|
|
41
|
+
dsw_loggers = (logging.getLogger(name) for name in logger_dict
|
|
42
|
+
if name.lower().startswith('dsw'))
|
|
43
|
+
for logger in dsw_loggers:
|
|
44
|
+
logger.setLevel(logging_cfg.level)
|
|
45
|
+
# Set for all existing loggers
|
|
46
|
+
logging.getLogger().addFilter(filter=LOG_FILTER)
|
|
47
|
+
loggers = (logging.getLogger(name) for name in logger_dict)
|
|
48
|
+
for logger in loggers:
|
|
49
|
+
logger.addFilter(filter=LOG_FILTER)
|
|
50
|
+
# Set for any future loggers
|
|
51
|
+
logging.setLoggerClass(DSWLogger)
|
dsw/config/model.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
|
|
3
|
+
from .logging import LOG_FILTER, prepare_logging
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _config_to_string(config: object):
|
|
7
|
+
lines = [f'{type(config).__name__}']
|
|
8
|
+
fields = (f for f in config.__dict__ if not f.startswith('_'))
|
|
9
|
+
for field in fields:
|
|
10
|
+
v = str(getattr(config, field))
|
|
11
|
+
t = type(getattr(config, field)).__name__
|
|
12
|
+
lines.append(f'- {field} = {v} [{t}]')
|
|
13
|
+
return '\n'.join(lines)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigModel:
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return _config_to_string(self)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclasses.dataclass
|
|
23
|
+
class GeneralConfig(ConfigModel):
|
|
24
|
+
environment: str
|
|
25
|
+
client_url: str
|
|
26
|
+
secret: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclasses.dataclass
|
|
30
|
+
class SentryConfig(ConfigModel):
|
|
31
|
+
enabled: bool
|
|
32
|
+
workers_dsn: str | None
|
|
33
|
+
traces_sample_rate: float | None
|
|
34
|
+
max_breadcrumbs: int | None
|
|
35
|
+
environment: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclasses.dataclass
|
|
39
|
+
class DatabaseConfig(ConfigModel):
|
|
40
|
+
connection_string: str
|
|
41
|
+
connection_timeout: int
|
|
42
|
+
queue_timeout: int
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclasses.dataclass
|
|
46
|
+
class S3Config(ConfigModel):
|
|
47
|
+
url: str
|
|
48
|
+
username: str
|
|
49
|
+
password: str
|
|
50
|
+
bucket: str
|
|
51
|
+
region: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclasses.dataclass
|
|
55
|
+
class LoggingConfig(ConfigModel):
|
|
56
|
+
level: str
|
|
57
|
+
global_level: str
|
|
58
|
+
message_format: str
|
|
59
|
+
dict_config: dict | None = None
|
|
60
|
+
|
|
61
|
+
def apply(self):
|
|
62
|
+
prepare_logging(self)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def set_logging_extra(key: str, value: str):
|
|
66
|
+
LOG_FILTER.set_extra(key, value)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclasses.dataclass
|
|
70
|
+
class AWSConfig(ConfigModel):
|
|
71
|
+
access_key_id: str | None
|
|
72
|
+
secret_access_key: str | None
|
|
73
|
+
region: str | None
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def has_credentials(self) -> bool:
|
|
77
|
+
return self.access_key_id is not None and self.secret_access_key is not None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclasses.dataclass
|
|
81
|
+
class CloudConfig(ConfigModel):
|
|
82
|
+
multi_tenant: bool
|
dsw/config/parser.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from . import model
|
|
7
|
+
from .keys import ConfigKey, ConfigKeys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MissingConfigurationError(Exception):
|
|
11
|
+
|
|
12
|
+
def __init__(self, missing: list[str]):
|
|
13
|
+
self.missing = missing
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DSWConfigParser:
|
|
17
|
+
|
|
18
|
+
def __init__(self, keys=ConfigKeys):
|
|
19
|
+
self.cfg = {}
|
|
20
|
+
self.keys = keys
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def can_read(content: str):
|
|
24
|
+
try:
|
|
25
|
+
yaml.safe_load(content)
|
|
26
|
+
return True
|
|
27
|
+
except Exception:
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
def read_file(self, fp: typing.IO):
|
|
31
|
+
self.cfg = yaml.safe_load(fp) or self.cfg
|
|
32
|
+
|
|
33
|
+
def read_string(self, content: str):
|
|
34
|
+
self.cfg = yaml.safe_load(content) or self.cfg
|
|
35
|
+
|
|
36
|
+
def has_value_for_path(self, yaml_path: list[str]):
|
|
37
|
+
x = self.cfg
|
|
38
|
+
for p in yaml_path:
|
|
39
|
+
if not hasattr(x, 'keys') or p not in x:
|
|
40
|
+
return False
|
|
41
|
+
x = x[p]
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def _prefix_var(var_name: str) -> str:
|
|
46
|
+
return f'DSW_{var_name}'
|
|
47
|
+
|
|
48
|
+
def has_value_for_key(self, key: ConfigKey):
|
|
49
|
+
if self.has_value_for_path(key.yaml_path):
|
|
50
|
+
return True
|
|
51
|
+
for var_name in key.var_names:
|
|
52
|
+
if var_name in os.environ or self._prefix_var(var_name) in os.environ:
|
|
53
|
+
return True
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
def get_or_default(self, key: ConfigKey):
|
|
57
|
+
x: typing.Any = self.cfg
|
|
58
|
+
for p in key.yaml_path:
|
|
59
|
+
if not hasattr(x, 'keys') or p not in x:
|
|
60
|
+
return key.default
|
|
61
|
+
x = x[p]
|
|
62
|
+
return x
|
|
63
|
+
|
|
64
|
+
def get(self, key: ConfigKey):
|
|
65
|
+
for var_name in key.var_names:
|
|
66
|
+
if var_name in os.environ:
|
|
67
|
+
return key.cast(os.environ[var_name])
|
|
68
|
+
if self._prefix_var(var_name) in os.environ:
|
|
69
|
+
return key.cast(os.environ[self._prefix_var(var_name)])
|
|
70
|
+
return key.cast(self.get_or_default(key))
|
|
71
|
+
|
|
72
|
+
def validate(self):
|
|
73
|
+
missing = [
|
|
74
|
+
'.'.join(key.yaml_path) for key in self.keys
|
|
75
|
+
if key.required and not self.has_value_for_key(key)
|
|
76
|
+
]
|
|
77
|
+
if len(missing) > 0:
|
|
78
|
+
raise MissingConfigurationError(missing)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def db(self) -> model.DatabaseConfig:
|
|
82
|
+
return model.DatabaseConfig(
|
|
83
|
+
connection_string=self.get(self.keys.database.connection_string),
|
|
84
|
+
connection_timeout=self.get(self.keys.database.connection_timeout),
|
|
85
|
+
queue_timeout=self.get(self.keys.database.queue_timeout),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def s3(self) -> model.S3Config:
|
|
90
|
+
return model.S3Config(
|
|
91
|
+
url=self.get(self.keys.s3.url),
|
|
92
|
+
username=self.get(self.keys.s3.username),
|
|
93
|
+
password=self.get(self.keys.s3.password),
|
|
94
|
+
bucket=self.get(self.keys.s3.bucket),
|
|
95
|
+
region=self.get(self.keys.s3.region),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def logging(self) -> model.LoggingConfig:
|
|
100
|
+
return model.LoggingConfig(
|
|
101
|
+
level=self.get(self.keys.logging.level),
|
|
102
|
+
global_level=self.get(self.keys.logging.global_level),
|
|
103
|
+
message_format=self.get(self.keys.logging.format),
|
|
104
|
+
dict_config=self.get(self.keys.logging.dict_config),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def cloud(self) -> model.CloudConfig:
|
|
109
|
+
return model.CloudConfig(
|
|
110
|
+
multi_tenant=self.get(self.keys.cloud.enabled),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def sentry(self) -> model.SentryConfig:
|
|
115
|
+
return model.SentryConfig(
|
|
116
|
+
enabled=self.get(self.keys.sentry.enabled),
|
|
117
|
+
workers_dsn=self.get(self.keys.sentry.worker_dsn),
|
|
118
|
+
traces_sample_rate=self.get(self.keys.sentry.traces_sample_rate),
|
|
119
|
+
max_breadcrumbs=self.get(self.keys.sentry.max_breadcrumbs),
|
|
120
|
+
environment=self.get(self.keys.sentry.environment),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def general(self) -> model.GeneralConfig:
|
|
125
|
+
return model.GeneralConfig(
|
|
126
|
+
environment=self.get(self.keys.general.environment),
|
|
127
|
+
client_url=self.get(self.keys.general.client_url),
|
|
128
|
+
secret=self.get(self.keys.general.secret),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def aws(self) -> model.AWSConfig:
|
|
133
|
+
return model.AWSConfig(
|
|
134
|
+
access_key_id=self.get(self.keys.aws.access_key_id),
|
|
135
|
+
secret_access_key=self.get(self.keys.aws.secret_access_key),
|
|
136
|
+
region=self.get(self.keys.aws.region),
|
|
137
|
+
)
|
dsw/config/py.typed
ADDED
|
File without changes
|
dsw/config/sentry.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import typing
|
|
3
|
+
|
|
4
|
+
import sentry_sdk
|
|
5
|
+
from sentry_sdk.integrations.logging import LoggingIntegration
|
|
6
|
+
from sentry_sdk.types import Event, Hint
|
|
7
|
+
|
|
8
|
+
from .model import SentryConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
EventProcessor = typing.Callable[[Event, Hint], Event | None]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SentryReporter:
|
|
15
|
+
report = False
|
|
16
|
+
filters = [] # type: list[EventProcessor]
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def initialize(cls, *, config: SentryConfig, prog_name: str, release: str,
|
|
20
|
+
breadcrumb_level: int | None = logging.INFO,
|
|
21
|
+
event_level: int | None = logging.ERROR):
|
|
22
|
+
cls.report = config.enabled and config.workers_dsn is not None
|
|
23
|
+
if cls.report:
|
|
24
|
+
def before_send(event, hint):
|
|
25
|
+
for f in cls.filters:
|
|
26
|
+
if not f(event, hint):
|
|
27
|
+
return None
|
|
28
|
+
return event
|
|
29
|
+
|
|
30
|
+
sentry_sdk.init(
|
|
31
|
+
dsn=config.workers_dsn,
|
|
32
|
+
traces_sample_rate=config.traces_sample_rate or 1.0,
|
|
33
|
+
max_breadcrumbs=config.max_breadcrumbs or sentry_sdk.consts.DEFAULT_MAX_BREADCRUMBS,
|
|
34
|
+
release=release,
|
|
35
|
+
environment=config.environment,
|
|
36
|
+
before_send=before_send,
|
|
37
|
+
default_integrations=False,
|
|
38
|
+
integrations=[
|
|
39
|
+
LoggingIntegration(
|
|
40
|
+
level=breadcrumb_level,
|
|
41
|
+
event_level=event_level,
|
|
42
|
+
),
|
|
43
|
+
],
|
|
44
|
+
)
|
|
45
|
+
sentry_sdk.set_tag('component', prog_name)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def capture_exception(cls, *args, **kwargs):
|
|
49
|
+
if cls.report:
|
|
50
|
+
sentry_sdk.capture_exception(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def capture_message(cls, *args, **kwargs):
|
|
54
|
+
if cls.report:
|
|
55
|
+
sentry_sdk.capture_message(*args, **kwargs)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def set_tags(cls, **tags):
|
|
59
|
+
if cls.report:
|
|
60
|
+
sentry_sdk.set_tags(tags)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dsw-config
|
|
3
|
+
Version: 4.27.0
|
|
4
|
+
Summary: Library for DSW config manipulation
|
|
5
|
+
Keywords: dsw,config,yaml,parser
|
|
6
|
+
Author: Marek Suchánek
|
|
7
|
+
Author-email: Marek Suchánek <marek.suchanek@ds-wizard.org>
|
|
8
|
+
License: Apache License 2.0
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Text Processing
|
|
15
|
+
Classifier: Topic :: Utilities
|
|
16
|
+
Requires-Dist: pyyaml
|
|
17
|
+
Requires-Dist: sentry-sdk
|
|
18
|
+
Requires-Python: >=3.12, <4
|
|
19
|
+
Project-URL: Homepage, https://ds-wizard.org
|
|
20
|
+
Project-URL: Repository, https://github.com/ds-wizard/engine-tools
|
|
21
|
+
Project-URL: Documentation, https://guide.ds-wizard.org
|
|
22
|
+
Project-URL: Issues, https://github.com/ds-wizard/ds-wizard/issues
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Data Stewardship Wizard: Config
|
|
26
|
+
|
|
27
|
+
[](https://github.com/ds-wizard/engine-tools/releases)
|
|
28
|
+
[](https://pypi.org/project/dsw-config/)
|
|
29
|
+
[](LICENSE)
|
|
30
|
+
[](https://bestpractices.coreinfrastructure.org/projects/4975)
|
|
31
|
+
[](https://python.org)
|
|
32
|
+
|
|
33
|
+
*Library for working with DSW configuration*
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
Currently, this library is intended for internal use of DSW tooling only.
|
|
38
|
+
Enhancements for use in custom scripts are planned for future development.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
This project is licensed under the Apache License v2.0 - see the
|
|
43
|
+
[LICENSE](LICENSE) file for more details.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
dsw/config/__init__.py,sha256=xHLvInMTcWvmRwZJIM6ZvhSIKE3J35MgejsM7-6ALoc,124
|
|
2
|
+
dsw/config/build_info.py,sha256=q1VZ-03xJkIRGFaYomTfIG49Of9XRQnbk34em4NhKeE,381
|
|
3
|
+
dsw/config/keys.py,sha256=KdWsKoqlw4UjPA-cHGH6D3NvA54k4sPDORMXthpPAmc,6952
|
|
4
|
+
dsw/config/logging.py,sha256=OMoOlQsBaWkLAxAPdtDZiK05e_-hgEhqyiGhmGBONoQ,1480
|
|
5
|
+
dsw/config/model.py,sha256=j_5IuCgDsbxKorYX29gaoh_ChWI1pgR6-9jrRY2Q_-8,1753
|
|
6
|
+
dsw/config/parser.py,sha256=RKKSMiuYwycdc-IVTrRpjKg5PJuzyfa2QdMTQqFE4XI,4363
|
|
7
|
+
dsw/config/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
dsw/config/sentry.py,sha256=2Jo0FWvGweso5dagLVYltS3aRXIdCPMlJWc22-H3L7c,1930
|
|
9
|
+
dsw_config-4.27.0.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
10
|
+
dsw_config-4.27.0.dist-info/METADATA,sha256=U8UWJhRcQZMHLTAkZTK4vQZbrGV3MVHPH8dBWqHSSI0,1866
|
|
11
|
+
dsw_config-4.27.0.dist-info/RECORD,,
|