3 from typing
import Set
, Dict
, Any
, Optional
4 from functools
import cached_property
7 class ConfigParseError(Exception):
15 def __init__(self
, config
: "Config", identifier
: str,
16 aliases
: Optional
[Set
[str]] = None,
17 email
: Optional
[str] = None):
19 self
.identifier
= identifier
22 self
.aliases
= aliases
26 def all_names(self
) -> Set
[str]:
27 retval
= self
.aliases
.copy()
28 retval
.add(self
.identifier
)
31 def __eq__(self
, other
):
32 return self
.identifier
== other
.identifier
35 return hash(self
.identifier
)
38 return f
"Person(config=..., identifier={self.identifier!r}, " \
39 f
"aliases={self.aliases!r}, email={self.email!r})"
43 def __init__(self
, bugzilla_url
: str, people
: Dict
[str, Person
]):
44 self
.bugzilla_url
= bugzilla_url
48 return f
"Config(bugzilla_url={self.bugzilla_url!r}, " \
49 f
"people={self.people!r})"
52 def all_names(self
) -> Dict
[str, Person
]:
53 # also checks for any name clashes and raises
54 # ConfigParseError if any are detected
55 retval
= self
.people
.copy()
57 for person
in self
.people
.values():
58 for alias
in person
.aliases
:
59 other_person
= retval
.get(alias
)
60 if other_person
is not None:
61 if alias
in self
.people
:
62 raise ConfigParseError(
63 f
"alias is not allowed to be the same as any"
64 f
" person's identifier: in person entry for "
65 f
"{person.identifier!r}: {alias!r} is also the "
66 f
"identifier for person"
67 f
" {other_person.identifier!r}")
68 raise ConfigParseError(
69 f
"alias is not allowed to be the same as another"
70 f
" person's alias: in person entry for "
71 f
"{person.identifier!r}: {alias!r} is also an alias "
72 f
"for person {other_person.identifier!r}")
73 retval
[alias
] = person
76 def _parse_person(self
, identifier
: str, value
: Any
) -> Person
:
77 def raise_aliases_must_be_list_of_strings():
78 raise ConfigParseError(
79 f
"`aliases` field in person entry for {identifier!r} must "
80 f
"be a list of strings")
82 if not isinstance(value
, dict):
83 raise ConfigParseError(
84 f
"person entry for {identifier!r} must be a table")
87 for k
, v
in value
.items():
88 assert isinstance(k
, str)
90 if not isinstance(v
, list):
91 raise_aliases_must_be_list_of_strings()
93 if isinstance(alias
, str):
95 raise ConfigParseError(
96 f
"duplicate alias in person entry for "
97 f
"{identifier!r}: {alias!r}")
100 raise_aliases_must_be_list_of_strings()
102 if not isinstance(v
, str):
103 raise ConfigParseError(
104 f
"`email` field in person entry for {identifier!r} "
108 raise ConfigParseError(
109 f
"unknown field in person entry for {identifier!r}: `{k}`")
110 return Person(config
=self
, identifier
=identifier
,
111 aliases
=aliases
, email
=email
)
113 def _parse_people(self
, people
: Any
):
114 if not isinstance(people
, dict):
115 raise ConfigParseError("`people` field must be a table")
116 for identifier
, value
in people
.items():
117 assert isinstance(identifier
, str)
118 self
.people
[identifier
] = self
._parse
_person
(identifier
, value
)
120 # self.all_names checks for name clashes and raises ConfigParseError
121 # if any are detected, so the following line is needed:
125 def _from_toml(parsed_toml
: Dict
[str, Any
]) -> "Config":
128 for k
, v
in parsed_toml
.items():
129 assert isinstance(k
, str)
132 elif k
== "bugzilla_url":
133 if not isinstance(v
, str):
134 raise ConfigParseError("`bugzilla_url` must be a string")
137 raise ConfigParseError(f
"unknown config entry: `{k}`")
139 if bugzilla_url
is None:
140 raise ConfigParseError("`bugzilla_url` field is missing")
142 config
= Config(bugzilla_url
=bugzilla_url
, people
={})
145 raise ConfigParseError("`people` table is missing")
147 config
._parse
_people
(people
)
152 def from_str(text
: str) -> "Config":
154 parsed_toml
= toml
.loads(text
)
155 except toml
.TomlDecodeError
as e
:
156 new_err
= ConfigParseError(f
"TOML parse error: {e}")
157 raise new_err
.with_traceback(sys
.exc_info()[2])
158 return Config
._from
_toml
(parsed_toml
)
161 def from_file(file: Any
) -> "Config":
162 if isinstance(file, list):
163 raise TypeError("list is not a valid file or path")
165 parsed_toml
= toml
.load(file)
166 except toml
.TomlDecodeError
as e
:
167 new_err
= ConfigParseError(f
"TOML parse error: {e}")
168 raise new_err
.with_traceback(sys
.exc_info()[2])
169 return Config
._from
_toml
(parsed_toml
)