Marco Ricci commited on 2025-01-21 21:02:31
              Zeige 1 geänderte Dateien mit 183 Einfügungen und 188 Löschungen.
            
Introduce a new workhorse class that provides the validity checking and the falsy value cleanup functionality. The existing top-level functions are now mere facades. The primary reason is to keep the complexity and the nesting level of these functions low-ish: they still trigger linting errors that need to be silenced, but the nesting is tamer, and some common functionality (e.g., tree traversal) can be usefully extracted.
| ... | ... | 
                      @@ -6,13 +6,12 @@  | 
                  
| 6 | 6 | 
                         | 
                    
| 7 | 7 | 
                        from __future__ import annotations  | 
                    
| 8 | 8 | 
                         | 
                    
| 9 | 
                        -import collections  | 
                    |
| 10 | 9 | 
                        import enum  | 
                    
| 11 | 10 | 
                        import json  | 
                    
| 12 | 11 | 
                        import math  | 
                    
| 13 | 12 | 
                        import string  | 
                    
| 14 | 13 | 
                        import warnings  | 
                    
| 15 | 
                        -from typing import TYPE_CHECKING, Generic, TypeVar  | 
                    |
| 14 | 
                        +from typing import TYPE_CHECKING, Generic, TypeVar, cast  | 
                    |
| 16 | 15 | 
                         | 
                    
| 17 | 16 | 
                        from typing_extensions import (  | 
                    
| 18 | 17 | 
                        Buffer,  | 
                    
| ... | ... | 
                      @@ -25,7 +24,7 @@ from typing_extensions import (  | 
                  
| 25 | 24 | 
                        )  | 
                    
| 26 | 25 | 
                         | 
                    
| 27 | 26 | 
                        if TYPE_CHECKING:  | 
                    
| 28 | 
                        - from collections.abc import MutableSequence, Sequence  | 
                    |
| 27 | 
                        + from collections.abc import Iterator, Sequence  | 
                    |
| 29 | 28 | 
                        from typing import Literal  | 
                    
| 30 | 29 | 
                         | 
                    
| 31 | 30 | 
                        from typing_extensions import (  | 
                    
| ... | ... | 
                      @@ -226,6 +225,180 @@ def json_path(path: Sequence[str | int], /) -> str:  | 
                  
| 226 | 225 | 
                        return ''.join(chunks)  | 
                    
| 227 | 226 | 
                         | 
                    
| 228 | 227 | 
                         | 
                    
| 228 | 
                        +class _VaultConfigValidator:  | 
                    |
| 229 | 
                        + INVALID_CONFIG_ERROR = 'vault config is invalid'  | 
                    |
| 230 | 
                        +  | 
                    |
| 231 | 
                        + def __init__(self, maybe_config: Any) -> None: # noqa: ANN401  | 
                    |
| 232 | 
                        + self.maybe_config = maybe_config  | 
                    |
| 233 | 
                        +  | 
                    |
| 234 | 
                        + def traverse_path(self, path: tuple[str, ...]) -> Any: # noqa: ANN401  | 
                    |
| 235 | 
                        + obj = self.maybe_config  | 
                    |
| 236 | 
                        + for key in path:  | 
                    |
| 237 | 
                        + obj = obj[key]  | 
                    |
| 238 | 
                        + return obj  | 
                    |
| 239 | 
                        +  | 
                    |
| 240 | 
                        + def walk_subconfigs(  | 
                    |
| 241 | 
                        + self,  | 
                    |
| 242 | 
                        + ) -> Iterator[tuple[tuple[str] | tuple[str, str], str, Any]]:  | 
                    |
| 243 | 
                        +        obj = cast('dict[str, dict[str, Any]]', self.maybe_config)
                       | 
                    |
| 244 | 
                        +        if isinstance(obj.get('global', False), dict):
                       | 
                    |
| 245 | 
                        + for k, v in list(obj['global'].items()):  | 
                    |
| 246 | 
                        +                yield ('global',), k, v
                       | 
                    |
| 247 | 
                        + for sv_name, sv_obj in list(obj['services'].items()):  | 
                    |
| 248 | 
                        + for k, v in list(sv_obj.items()):  | 
                    |
| 249 | 
                        +                yield ('services', sv_name), k, v
                       | 
                    |
| 250 | 
                        +  | 
                    |
| 251 | 
                        + def validate( # noqa: C901,PLR0912  | 
                    |
| 252 | 
                        + self,  | 
                    |
| 253 | 
                        + *,  | 
                    |
| 254 | 
                        + allow_unknown_settings: bool = False,  | 
                    |
| 255 | 
                        + ) -> None:  | 
                    |
| 256 | 
                        + err_obj_not_a_dict = 'vault config is not a dict'  | 
                    |
| 257 | 
                        + err_non_str_service_name = (  | 
                    |
| 258 | 
                        +            'vault config contains non-string service name {sv_name!r}'
                       | 
                    |
| 259 | 
                        + )  | 
                    |
| 260 | 
                        +        err_not_a_dict = 'vault config entry {json_path_str} is not a dict'
                       | 
                    |
| 261 | 
                        +        err_not_a_string = 'vault config entry {json_path_str} is not a string'
                       | 
                    |
| 262 | 
                        +        err_not_an_int = 'vault config entry {json_path_str} is not an integer'
                       | 
                    |
| 263 | 
                        + err_unknown_setting = (  | 
                    |
| 264 | 
                        +            'vault config entry {json_path_str} uses unknown setting {key!r}'
                       | 
                    |
| 265 | 
                        + )  | 
                    |
| 266 | 
                        +        err_bad_number0 = 'vault config entry {json_path_str} is negative'
                       | 
                    |
| 267 | 
                        +        err_bad_number1 = 'vault config entry {json_path_str} is not positive'
                       | 
                    |
| 268 | 
                        +  | 
                    |
| 269 | 
                        +        kwargs: dict[str, Any] = {
                       | 
                    |
| 270 | 
                        + 'allow_unknown_settings': allow_unknown_settings,  | 
                    |
| 271 | 
                        + }  | 
                    |
| 272 | 
                        + if not isinstance(self.maybe_config, dict):  | 
                    |
| 273 | 
                        + raise TypeError(err_obj_not_a_dict.format(**kwargs))  | 
                    |
| 274 | 
                        + if 'global' in self.maybe_config:  | 
                    |
| 275 | 
                        + o_global = self.maybe_config['global']  | 
                    |
| 276 | 
                        + if not isinstance(o_global, dict):  | 
                    |
| 277 | 
                        + kwargs['json_path_str'] = json_path(['global'])  | 
                    |
| 278 | 
                        + raise TypeError(err_not_a_dict.format(**kwargs))  | 
                    |
| 279 | 
                        +        if not isinstance(self.maybe_config.get('services'), dict):
                       | 
                    |
| 280 | 
                        + kwargs['json_path_str'] = json_path(['services'])  | 
                    |
| 281 | 
                        + raise TypeError(err_not_a_dict.format(**kwargs))  | 
                    |
| 282 | 
                        + for sv_name, service in self.maybe_config['services'].items():  | 
                    |
| 283 | 
                        + if not isinstance(sv_name, str):  | 
                    |
| 284 | 
                        + kwargs['sv_name'] = sv_name  | 
                    |
| 285 | 
                        + raise TypeError(err_non_str_service_name.format(**kwargs))  | 
                    |
| 286 | 
                        + if not isinstance(service, dict):  | 
                    |
| 287 | 
                        + kwargs['json_path_str'] = json_path(['services', sv_name])  | 
                    |
| 288 | 
                        + raise TypeError(err_not_a_dict.format(**kwargs))  | 
                    |
| 289 | 
                        + for path, key, value in self.walk_subconfigs():  | 
                    |
| 290 | 
                        + kwargs['path'] = path  | 
                    |
| 291 | 
                        + kwargs['key'] = key  | 
                    |
| 292 | 
                        + kwargs['value'] = value  | 
                    |
| 293 | 
                        + kwargs['json_path_str'] = json_path([*path, key])  | 
                    |
| 294 | 
                        + # Use match/case here once Python 3.9 becomes unsupported.  | 
                    |
| 295 | 
                        +            if key in {'key', 'phrase'}:
                       | 
                    |
| 296 | 
                        + if not isinstance(value, str):  | 
                    |
| 297 | 
                        + raise TypeError(err_not_a_string.format(**kwargs))  | 
                    |
| 298 | 
                        + elif key == 'unicode_normalization_form' and path == (  | 
                    |
| 299 | 
                        + 'global',  | 
                    |
| 300 | 
                        + ):  | 
                    |
| 301 | 
                        + if not isinstance(value, str):  | 
                    |
| 302 | 
                        + raise TypeError(err_not_a_string.format(**kwargs))  | 
                    |
| 303 | 
                        + if not allow_unknown_settings:  | 
                    |
| 304 | 
                        + raise ValueError(err_unknown_setting.format(**kwargs))  | 
                    |
| 305 | 
                        +            elif key == 'notes' and path != ('global',):
                       | 
                    |
| 306 | 
                        + if not isinstance(value, str):  | 
                    |
| 307 | 
                        + raise TypeError(err_not_a_string.format(**kwargs))  | 
                    |
| 308 | 
                        +            elif key in {
                       | 
                    |
| 309 | 
                        + 'length',  | 
                    |
| 310 | 
                        + 'repeat',  | 
                    |
| 311 | 
                        + 'lower',  | 
                    |
| 312 | 
                        + 'upper',  | 
                    |
| 313 | 
                        + 'number',  | 
                    |
| 314 | 
                        + 'space',  | 
                    |
| 315 | 
                        + 'dash',  | 
                    |
| 316 | 
                        + 'symbol',  | 
                    |
| 317 | 
                        + }:  | 
                    |
| 318 | 
                        + if not isinstance(value, int):  | 
                    |
| 319 | 
                        + raise TypeError(err_not_an_int.format(**kwargs))  | 
                    |
| 320 | 
                        + if key == 'length' and value < 1:  | 
                    |
| 321 | 
                        + raise ValueError(err_bad_number1.format(**kwargs))  | 
                    |
| 322 | 
                        + if key != 'length' and value < 0:  | 
                    |
| 323 | 
                        + raise ValueError(err_bad_number0.format(**kwargs))  | 
                    |
| 324 | 
                        + elif not allow_unknown_settings:  | 
                    |
| 325 | 
                        + raise ValueError(err_unknown_setting.format(**kwargs))  | 
                    |
| 326 | 
                        +  | 
                    |
| 327 | 
                        + def clean_up_falsy_values(self) -> Iterator[CleanupStep]: # noqa: C901  | 
                    |
| 328 | 
                        + obj = self.maybe_config  | 
                    |
| 329 | 
                        + if (  | 
                    |
| 330 | 
                        + not isinstance(obj, dict)  | 
                    |
| 331 | 
                        + or 'services' not in obj  | 
                    |
| 332 | 
                        + or not isinstance(obj['services'], dict)  | 
                    |
| 333 | 
                        + ):  | 
                    |
| 334 | 
                        + raise ValueError(self.INVALID_CONFIG_ERROR) # pragma: no cover  | 
                    |
| 335 | 
                        + if 'global' in obj and not isinstance(obj['global'], dict):  | 
                    |
| 336 | 
                        + raise ValueError(self.INVALID_CONFIG_ERROR) # pragma: no cover  | 
                    |
| 337 | 
                        + if not all(  | 
                    |
| 338 | 
                        + isinstance(service_obj, dict)  | 
                    |
| 339 | 
                        + for service_obj in obj['services'].values()  | 
                    |
| 340 | 
                        + ):  | 
                    |
| 341 | 
                        + raise ValueError(self.INVALID_CONFIG_ERROR) # pragma: no cover  | 
                    |
| 342 | 
                        +  | 
                    |
| 343 | 
                        + def falsy(value: Any) -> bool: # noqa: ANN401  | 
                    |
| 344 | 
                        + return not js_truthiness(value)  | 
                    |
| 345 | 
                        +  | 
                    |
| 346 | 
                        + def falsy_but_not_zero(value: Any) -> bool: # noqa: ANN401  | 
                    |
| 347 | 
                        + return not js_truthiness(value) and not (  | 
                    |
| 348 | 
                        + isinstance(value, int) and value == 0  | 
                    |
| 349 | 
                        + )  | 
                    |
| 350 | 
                        +  | 
                    |
| 351 | 
                        + def falsy_but_not_string(value: Any) -> bool: # noqa: ANN401  | 
                    |
| 352 | 
                        + return not js_truthiness(value) and value != '' # noqa: PLC1901  | 
                    |
| 353 | 
                        +  | 
                    |
| 354 | 
                        + for path, key, value in self.walk_subconfigs():  | 
                    |
| 355 | 
                        + service_obj = self.traverse_path(path)  | 
                    |
| 356 | 
                        + # Use match/case here once Python 3.9 becomes unsupported.  | 
                    |
| 357 | 
                        + if key == 'phrase' and falsy_but_not_string(value):  | 
                    |
| 358 | 
                        + yield CleanupStep(  | 
                    |
| 359 | 
                        + (*path, key), service_obj[key], 'replace', ''  | 
                    |
| 360 | 
                        + )  | 
                    |
| 361 | 
                        + service_obj[key] = ''  | 
                    |
| 362 | 
                        + elif key == 'notes' and falsy(value):  | 
                    |
| 363 | 
                        + yield CleanupStep(  | 
                    |
| 364 | 
                        + (*path, key), service_obj[key], 'remove', None  | 
                    |
| 365 | 
                        + )  | 
                    |
| 366 | 
                        + service_obj.pop(key)  | 
                    |
| 367 | 
                        + elif key == 'key' and falsy(value):  | 
                    |
| 368 | 
                        +                if path == ('global',):
                       | 
                    |
| 369 | 
                        + yield CleanupStep(  | 
                    |
| 370 | 
                        + (*path, key), service_obj[key], 'remove', None  | 
                    |
| 371 | 
                        + )  | 
                    |
| 372 | 
                        + service_obj.pop(key)  | 
                    |
| 373 | 
                        + else:  | 
                    |
| 374 | 
                        + yield CleanupStep(  | 
                    |
| 375 | 
                        + (*path, key), service_obj[key], 'replace', ''  | 
                    |
| 376 | 
                        + )  | 
                    |
| 377 | 
                        + service_obj[key] = ''  | 
                    |
| 378 | 
                        + elif key == 'length' and falsy(value):  | 
                    |
| 379 | 
                        + yield CleanupStep(  | 
                    |
| 380 | 
                        + (*path, key), service_obj[key], 'replace', 20  | 
                    |
| 381 | 
                        + )  | 
                    |
| 382 | 
                        + service_obj[key] = 20  | 
                    |
| 383 | 
                        + elif key == 'repeat' and falsy_but_not_zero(value):  | 
                    |
| 384 | 
                        + yield CleanupStep(  | 
                    |
| 385 | 
                        + (*path, key), service_obj[key], 'replace', 0  | 
                    |
| 386 | 
                        + )  | 
                    |
| 387 | 
                        + service_obj[key] = 0  | 
                    |
| 388 | 
                        +            elif key in {
                       | 
                    |
| 389 | 
                        + 'lower',  | 
                    |
| 390 | 
                        + 'upper',  | 
                    |
| 391 | 
                        + 'number',  | 
                    |
| 392 | 
                        + 'space',  | 
                    |
| 393 | 
                        + 'dash',  | 
                    |
| 394 | 
                        + 'symbol',  | 
                    |
| 395 | 
                        + } and falsy_but_not_zero(value):  | 
                    |
| 396 | 
                        + yield CleanupStep(  | 
                    |
| 397 | 
                        + (*path, key), service_obj[key], 'remove', None  | 
                    |
| 398 | 
                        + )  | 
                    |
| 399 | 
                        + service_obj.pop(key)  | 
                    |
| 400 | 
                        +  | 
                    |
| 401 | 
                        +  | 
                    |
| 229 | 402 | 
                        @overload  | 
                    
| 230 | 403 | 
                        @deprecated(  | 
                    
| 231 | 404 | 
                        'allow_derivepassphrase_extensions argument is deprecated since v0.4.0, '  | 
                    
| ... | ... | 
                      @@ -249,7 +422,7 @@ def validate_vault_config(  | 
                  
| 249 | 422 | 
                        ) -> None: ...  | 
                    
| 250 | 423 | 
                         | 
                    
| 251 | 424 | 
                         | 
                    
| 252 | 
                        -def validate_vault_config( # noqa: C901,PLR0912  | 
                    |
| 425 | 
                        +def validate_vault_config(  | 
                    |
| 253 | 426 | 
                        obj: Any,  | 
                    
| 254 | 427 | 
                        /,  | 
                    
| 255 | 428 | 
                        *,  | 
                    
| ... | ... | 
                      @@ -291,96 +464,10 @@ def validate_vault_config( # noqa: C901,PLR0912  | 
                  
| 291 | 464 | 
                        DeprecationWarning,  | 
                    
| 292 | 465 | 
                        stacklevel=2,  | 
                    
| 293 | 466 | 
                        )  | 
                    
| 294 | 
                        - err_obj_not_a_dict = 'vault config is not a dict'  | 
                    |
| 295 | 
                        - err_non_str_service_name = (  | 
                    |
| 296 | 
                        -        'vault config contains non-string service name {!r}'
                       | 
                    |
| 297 | 
                        - )  | 
                    |
| 298 | 
                        -  | 
                    |
| 299 | 
                        - def err_not_a_dict(path: Sequence[str], /) -> str:  | 
                    |
| 300 | 
                        - json_path_str = json_path(path)  | 
                    |
| 301 | 
                        -        return f'vault config entry {json_path_str} is not a dict'
                       | 
                    |
| 302 | 
                        -  | 
                    |
| 303 | 
                        - def err_not_a_string(path: Sequence[str], /) -> str:  | 
                    |
| 304 | 
                        - json_path_str = json_path(path)  | 
                    |
| 305 | 
                        -        return f'vault config entry {json_path_str} is not a string'
                       | 
                    |
| 306 | 467 | 
                         | 
                    
| 307 | 
                        - def err_not_an_int(path: Sequence[str], /) -> str:  | 
                    |
| 308 | 
                        - json_path_str = json_path(path)  | 
                    |
| 309 | 
                        -        return f'vault config entry {json_path_str} is not an integer'
                       | 
                    |
| 310 | 
                        -  | 
                    |
| 311 | 
                        - def err_unknown_setting(key: str, path: Sequence[str], /) -> str:  | 
                    |
| 312 | 
                        - json_path_str = json_path(path)  | 
                    |
| 313 | 
                        - return (  | 
                    |
| 314 | 
                        -            f'vault config entry {json_path_str} uses unknown setting {key!r}'
                       | 
                    |
| 315 | 
                        - )  | 
                    |
| 316 | 
                        -  | 
                    |
| 317 | 
                        - def err_bad_number(  | 
                    |
| 318 | 
                        - key: str,  | 
                    |
| 319 | 
                        - path: Sequence[str],  | 
                    |
| 320 | 
                        - /,  | 
                    |
| 321 | 
                        - *,  | 
                    |
| 322 | 
                        - strictly_positive: bool = False,  | 
                    |
| 323 | 
                        - ) -> str:  | 
                    |
| 324 | 
                        - json_path_str = json_path((*path, key))  | 
                    |
| 325 | 
                        -        return f'vault config entry {json_path_str} is ' + (
                       | 
                    |
| 326 | 
                        - 'not positive' if strictly_positive else 'negative'  | 
                    |
| 327 | 
                        - )  | 
                    |
| 328 | 
                        -  | 
                    |
| 329 | 
                        - if not isinstance(obj, dict):  | 
                    |
| 330 | 
                        - raise TypeError(err_obj_not_a_dict)  | 
                    |
| 331 | 
                        - queue_to_check: list[tuple[dict[str, Any], tuple[str, ...]]] = []  | 
                    |
| 332 | 
                        - if 'global' in obj:  | 
                    |
| 333 | 
                        - o_global = obj['global']  | 
                    |
| 334 | 
                        - if not isinstance(o_global, dict):  | 
                    |
| 335 | 
                        - raise TypeError(err_not_a_dict(['global']))  | 
                    |
| 336 | 
                        -        queue_to_check.append((o_global, ('global',)))
                       | 
                    |
| 337 | 
                        -    if not isinstance(obj.get('services'), dict):
                       | 
                    |
| 338 | 
                        - raise TypeError(err_not_a_dict(['services']))  | 
                    |
| 339 | 
                        - for sv_name, service in obj['services'].items():  | 
                    |
| 340 | 
                        - if not isinstance(sv_name, str):  | 
                    |
| 341 | 
                        - raise TypeError(err_non_str_service_name.format(sv_name))  | 
                    |
| 342 | 
                        - if not isinstance(service, dict):  | 
                    |
| 343 | 
                        - raise TypeError(err_not_a_dict(['services', sv_name]))  | 
                    |
| 344 | 
                        -        queue_to_check.append((service, ('services', sv_name)))
                       | 
                    |
| 345 | 
                        - for settings, path in queue_to_check:  | 
                    |
| 346 | 
                        - for key, value in settings.items():  | 
                    |
| 347 | 
                        - # Use match/case here once Python 3.9 becomes unsupported.  | 
                    |
| 348 | 
                        -            if key in {'key', 'phrase'}:
                       | 
                    |
| 349 | 
                        - if not isinstance(value, str):  | 
                    |
| 350 | 
                        - raise TypeError(err_not_a_string((*path, key)))  | 
                    |
| 351 | 
                        -            elif key == 'unicode_normalization_form' and path == ('global',):
                       | 
                    |
| 352 | 
                        - if not isinstance(value, str):  | 
                    |
| 353 | 
                        - raise TypeError(err_not_a_string((*path, key)))  | 
                    |
| 354 | 
                        - if (  | 
                    |
| 355 | 
                        - not allow_derivepassphrase_extensions  | 
                    |
| 356 | 
                        - and not allow_unknown_settings  | 
                    |
| 357 | 
                        - ):  | 
                    |
| 358 | 
                        - raise ValueError(err_unknown_setting(key, path))  | 
                    |
| 359 | 
                        -            elif key == 'notes' and path != ('global',):
                       | 
                    |
| 360 | 
                        - if not isinstance(value, str):  | 
                    |
| 361 | 
                        - raise TypeError(err_not_a_string((*path, key)))  | 
                    |
| 362 | 
                        -            elif key in {
                       | 
                    |
| 363 | 
                        - 'length',  | 
                    |
| 364 | 
                        - 'repeat',  | 
                    |
| 365 | 
                        - 'lower',  | 
                    |
| 366 | 
                        - 'upper',  | 
                    |
| 367 | 
                        - 'number',  | 
                    |
| 368 | 
                        - 'space',  | 
                    |
| 369 | 
                        - 'dash',  | 
                    |
| 370 | 
                        - 'symbol',  | 
                    |
| 371 | 
                        - }:  | 
                    |
| 372 | 
                        - if not isinstance(value, int):  | 
                    |
| 373 | 
                        - raise TypeError(err_not_an_int((*path, key)))  | 
                    |
| 374 | 
                        - if key == 'length' and value < 1:  | 
                    |
| 375 | 
                        - raise ValueError(  | 
                    |
| 376 | 
                        - err_bad_number(key, path, strictly_positive=True)  | 
                    |
| 377 | 
                        - )  | 
                    |
| 378 | 
                        - if key != 'length' and value < 0:  | 
                    |
| 379 | 
                        - raise ValueError(  | 
                    |
| 380 | 
                        - err_bad_number(key, path, strictly_positive=False)  | 
                    |
| 468 | 
                        + return _VaultConfigValidator(obj).validate(  | 
                    |
| 469 | 
                        + allow_unknown_settings=allow_unknown_settings  | 
                    |
| 381 | 470 | 
                        )  | 
                    
| 382 | 
                        - elif not allow_unknown_settings:  | 
                    |
| 383 | 
                        - raise ValueError(err_unknown_setting(key, path))  | 
                    |
| 384 | 471 | 
                         | 
                    
| 385 | 472 | 
                         | 
                    
| 386 | 473 | 
                        def is_vault_config(obj: Any) -> TypeIs[VaultConfig]: # noqa: ANN401  | 
                    
| ... | ... | 
                      @@ -470,7 +557,7 @@ class CleanupStep(NamedTuple):  | 
                  
| 470 | 557 | 
                        """"""  | 
                    
| 471 | 558 | 
                         | 
                    
| 472 | 559 | 
                         | 
                    
| 473 | 
                        -def clean_up_falsy_vault_config_values( # noqa: C901,PLR0912  | 
                    |
| 560 | 
                        +def clean_up_falsy_vault_config_values(  | 
                    |
| 474 | 561 | 
                        obj: Any, # noqa: ANN401  | 
                    
| 475 | 562 | 
                        ) -> Sequence[CleanupStep] | None:  | 
                    
| 476 | 563 | 
                        """Convert falsy values in a vault config to correct types, in-place.  | 
                    
| ... | ... | 
                      @@ -500,102 +587,10 @@ def clean_up_falsy_vault_config_values( # noqa: C901,PLR0912  | 
                  
| 500 | 587 | 
                        vault configuration, then `None` is returned, directly.  | 
                    
| 501 | 588 | 
                         | 
                    
| 502 | 589 | 
                        """  | 
                    
| 503 | 
                        - if ( # pragma: no cover  | 
                    |
| 504 | 
                        - not isinstance(obj, dict)  | 
                    |
| 505 | 
                        - or 'services' not in obj  | 
                    |
| 506 | 
                        - or not isinstance(obj['services'], dict)  | 
                    |
| 507 | 
                        - ):  | 
                    |
| 508 | 
                        - # config is invalid  | 
                    |
| 509 | 
                        - return None  | 
                    |
| 510 | 
                        - service_objects: MutableSequence[  | 
                    |
| 511 | 
                        - tuple[Sequence[str | int], dict[str, Any]]  | 
                    |
| 512 | 
                        - ] = collections.deque()  | 
                    |
| 513 | 
                        - if 'global' in obj:  | 
                    |
| 514 | 
                        - if isinstance(obj['global'], dict):  | 
                    |
| 515 | 
                        - service_objects.append((['global'], obj['global']))  | 
                    |
| 516 | 
                        - else: # pragma: no cover  | 
                    |
| 517 | 
                        - # config is invalid  | 
                    |
| 518 | 
                        - return None  | 
                    |
| 519 | 
                        - service_objects.extend(  | 
                    |
| 520 | 
                        - (['services', sv], val) for sv, val in obj['services'].items()  | 
                    |
| 521 | 
                        - )  | 
                    |
| 522 | 
                        - if not all( # pragma: no cover  | 
                    |
| 523 | 
                        - isinstance(service_obj, dict) for _, service_obj in service_objects  | 
                    |
| 524 | 
                        - ):  | 
                    |
| 525 | 
                        - # config is invalid  | 
                    |
| 590 | 
                        + try:  | 
                    |
| 591 | 
                        + return list(_VaultConfigValidator(obj).clean_up_falsy_values())  | 
                    |
| 592 | 
                        + except ValueError:  | 
                    |
| 526 | 593 | 
                        return None  | 
                    
| 527 | 
                        - cleanup_completed: MutableSequence[CleanupStep] = collections.deque()  | 
                    |
| 528 | 
                        - for path, service_obj in service_objects:  | 
                    |
| 529 | 
                        - for key, value in list(service_obj.items()):  | 
                    |
| 530 | 
                        - # Use match/case here once Python 3.9 becomes unsupported.  | 
                    |
| 531 | 
                        - if key == 'phrase':  | 
                    |
| 532 | 
                        - if not js_truthiness(value) and value != '': # noqa: PLC1901  | 
                    |
| 533 | 
                        - cleanup_completed.append(  | 
                    |
| 534 | 
                        - CleanupStep(  | 
                    |
| 535 | 
                        - (*path, key), service_obj[key], 'replace', ''  | 
                    |
| 536 | 
                        - )  | 
                    |
| 537 | 
                        - )  | 
                    |
| 538 | 
                        - service_obj[key] = ''  | 
                    |
| 539 | 
                        - elif key == 'notes':  | 
                    |
| 540 | 
                        - if not js_truthiness(value):  | 
                    |
| 541 | 
                        - cleanup_completed.append(  | 
                    |
| 542 | 
                        - CleanupStep(  | 
                    |
| 543 | 
                        - (*path, key), service_obj[key], 'remove', None  | 
                    |
| 544 | 
                        - )  | 
                    |
| 545 | 
                        - )  | 
                    |
| 546 | 
                        - service_obj.pop(key)  | 
                    |
| 547 | 
                        - elif key == 'key':  | 
                    |
| 548 | 
                        - if not js_truthiness(value):  | 
                    |
| 549 | 
                        - if path == ['global']:  | 
                    |
| 550 | 
                        - cleanup_completed.append(  | 
                    |
| 551 | 
                        - CleanupStep(  | 
                    |
| 552 | 
                        - (*path, key), service_obj[key], 'remove', None  | 
                    |
| 553 | 
                        - )  | 
                    |
| 554 | 
                        - )  | 
                    |
| 555 | 
                        - service_obj.pop(key)  | 
                    |
| 556 | 
                        - else:  | 
                    |
| 557 | 
                        - cleanup_completed.append(  | 
                    |
| 558 | 
                        - CleanupStep(  | 
                    |
| 559 | 
                        - (*path, key), service_obj[key], 'replace', ''  | 
                    |
| 560 | 
                        - )  | 
                    |
| 561 | 
                        - )  | 
                    |
| 562 | 
                        - service_obj[key] = ''  | 
                    |
| 563 | 
                        - elif key == 'length':  | 
                    |
| 564 | 
                        - if not js_truthiness(value):  | 
                    |
| 565 | 
                        - cleanup_completed.append(  | 
                    |
| 566 | 
                        - CleanupStep(  | 
                    |
| 567 | 
                        - (*path, key), service_obj[key], 'replace', 20  | 
                    |
| 568 | 
                        - )  | 
                    |
| 569 | 
                        - )  | 
                    |
| 570 | 
                        - service_obj[key] = 20  | 
                    |
| 571 | 
                        - elif key == 'repeat':  | 
                    |
| 572 | 
                        - if not js_truthiness(value) and not (  | 
                    |
| 573 | 
                        - isinstance(value, int) and value == 0  | 
                    |
| 574 | 
                        - ):  | 
                    |
| 575 | 
                        - cleanup_completed.append(  | 
                    |
| 576 | 
                        - CleanupStep(  | 
                    |
| 577 | 
                        - (*path, key), service_obj[key], 'replace', 0  | 
                    |
| 578 | 
                        - )  | 
                    |
| 579 | 
                        - )  | 
                    |
| 580 | 
                        - service_obj[key] = 0  | 
                    |
| 581 | 
                        -            elif key in {  # noqa: SIM102
                       | 
                    |
| 582 | 
                        - 'lower',  | 
                    |
| 583 | 
                        - 'upper',  | 
                    |
| 584 | 
                        - 'number',  | 
                    |
| 585 | 
                        - 'space',  | 
                    |
| 586 | 
                        - 'dash',  | 
                    |
| 587 | 
                        - 'symbol',  | 
                    |
| 588 | 
                        - }:  | 
                    |
| 589 | 
                        - if not js_truthiness(value) and not (  | 
                    |
| 590 | 
                        - isinstance(value, int) and value == 0  | 
                    |
| 591 | 
                        - ):  | 
                    |
| 592 | 
                        - cleanup_completed.append(  | 
                    |
| 593 | 
                        - CleanupStep(  | 
                    |
| 594 | 
                        - (*path, key), service_obj[key], 'remove', None  | 
                    |
| 595 | 
                        - )  | 
                    |
| 596 | 
                        - )  | 
                    |
| 597 | 
                        - service_obj.pop(key)  | 
                    |
| 598 | 
                        - return cleanup_completed  | 
                    |
| 599 | 594 | 
                         | 
                    
| 600 | 595 | 
                         | 
                    
| 601 | 596 | 
                         T_Buffer = TypeVar('T_Buffer', bound=Buffer)
                       | 
                    
| 602 | 597 |