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 ADDED
@@ -0,0 +1,4 @@
1
+ from .parser import DSWConfigParser, MissingConfigurationError
2
+
3
+
4
+ __all__ = ['DSWConfigParser', 'MissingConfigurationError']
@@ -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
+ [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/ds-wizard/engine-tools)](https://github.com/ds-wizard/engine-tools/releases)
28
+ [![PyPI](https://img.shields.io/pypi/v/dsw-config)](https://pypi.org/project/dsw-config/)
29
+ [![LICENSE](https://img.shields.io/github/license/ds-wizard/engine-tools)](LICENSE)
30
+ [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4975/badge)](https://bestpractices.coreinfrastructure.org/projects/4975)
31
+ [![Python Version](https://img.shields.io/badge/Python-%E2%89%A5%203.7-blue)](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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.28
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any