Coverage for src/derivepassphrase/types.py: 95.455%
54 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-14 11:39 +0200
« prev ^ index » next coverage.py v7.6.0, created at 2024-07-14 11:39 +0200
1# SPDX-FileCopyrightText: 2024 Marco Ricci <m@the13thletter.info>
2#
3# SPDX-License-Identifier: MIT
5"""Common typing declarations for the parent module.
7"""
9from __future__ import annotations
11from typing_extensions import (
12 Any, NotRequired, Required, TypedDict, TypeGuard,
13)
15import derivepassphrase
17__author__ = derivepassphrase.__author__
18__version__ = derivepassphrase.__version__
20class VaultConfigGlobalSettings(TypedDict, total=False):
21 r"""Configuration for vault: global settings.
23 Attributes:
24 key:
25 The base64-encoded ssh public key to use, overriding the
26 master passphrase. Optional.
27 phrase:
28 The master passphrase. Optional.
30 """
31 key: NotRequired[str]
32 phrase: NotRequired[str]
35class VaultConfigServicesSettings(VaultConfigGlobalSettings, total=False):
36 r"""Configuration for vault: services settings.
38 Attributes:
39 notes:
40 Optional notes for this service, to display to the user when
41 generating the passphrase.
42 length:
43 Desired passphrase length.
44 repeat:
45 The maximum number of immediate character repetitions
46 allowed in the passphrase. Disabled if set to 0.
47 lower:
48 Optional constraint on ASCII lowercase characters. If
49 positive, include this many lowercase characters
50 somewhere in the passphrase. If 0, avoid lowercase
51 characters altogether.
52 upper:
53 Same as `lower`, but for ASCII uppercase characters.
54 number:
55 Same as `lower`, but for ASCII digits.
56 space:
57 Same as `lower`, but for the space character.
58 dash:
59 Same as `lower`, but for the hyphen-minus and underscore
60 characters.
61 symbol:
62 Same as `lower`, but for all other hitherto unlisted
63 ASCII printable characters (except backquote).
65 """
66 notes: NotRequired[str]
67 length: NotRequired[int]
68 repeat: NotRequired[int]
69 lower: NotRequired[int]
70 upper: NotRequired[int]
71 number: NotRequired[int]
72 space: NotRequired[int]
73 dash: NotRequired[int]
74 symbol: NotRequired[int]
77_VaultConfig = TypedDict('_VaultConfig',
78 {'global': NotRequired[VaultConfigGlobalSettings]},
79 total=False)
80class VaultConfig(TypedDict, _VaultConfig, total=False):
81 r"""Configuration for vault.
83 Usually stored as JSON.
85 Attributes:
86 global (NotRequired[VaultConfigGlobalSettings]):
87 Global settings.
88 services (Required[dict[str, VaultConfigServicesSettings]]):
89 Service-specific settings.
91 """
92 services: Required[dict[str, VaultConfigServicesSettings]]
94def is_vault_config(obj: Any) -> TypeGuard[VaultConfig]:
95 """Check if `obj` is a valid vault config, according to typing.
97 Args:
98 obj: The object to test.
100 Returns:
101 True if this is a vault config, false otherwise.
103 """
104 if not isinstance(obj, dict):
105 return False
106 if 'global' in obj:
107 o_global = obj['global']
108 if not isinstance(o_global, dict):
109 return False
110 for key in ('key', 'phrase'):
111 if key in o_global and not isinstance(o_global[key], str):
112 return False
113 if 'key' in o_global and 'phrase' in o_global:
114 return False
115 if not isinstance(obj.get('services'), dict):
116 return False
117 for sv_name, service in obj['services'].items():
118 if not isinstance(sv_name, str):
119 return False
120 if not isinstance(service, dict):
121 return False
122 for key, value in service.items():
123 match key:
124 case 'notes' | 'phrase' | 'key': 124 ↛ 127line 124 didn't jump to line 127 because the pattern on line 124 always matched
125 if not isinstance(value, str): 125 ↛ 122line 125 didn't jump to line 122 because the condition on line 125 was always true
126 return False
127 case 'length':
128 if not isinstance(value, int) or value < 1: 128 ↛ 122line 128 didn't jump to line 122 because the condition on line 128 was always true
129 return False
130 case _:
131 if not isinstance(value, int) or value < 0: 131 ↛ 122line 131 didn't jump to line 122 because the condition on line 131 was always true
132 return False
133 if 'key' in service and 'phrase' in service:
134 return False
135 return True