1bebca1211b754f512b9e20639c0e066053362ac
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
, config
: "Config",
44 identifier
: str, canonical_bug_id
: int):
46 self
.identifier
= identifier
47 self
.canonical_bug_id
= canonical_bug_id
49 def __eq__(self
, other
):
50 return self
.identifier
== other
.identifier
53 return hash(self
.identifier
)
56 return f
"Milestone(config=..., " \
57 f
"identifier={self.identifier!r}, " \
58 f
"canonical_bug_id={self.canonical_bug_id})"
62 def __init__(self
, bugzilla_url
: str, people
: Dict
[str, Person
],
63 milestones
: Dict
[str, Milestone
]):
64 self
.bugzilla_url
= bugzilla_url
66 self
.milestones
= milestones
69 return f
"Config(bugzilla_url={self.bugzilla_url!r}, " \
70 f
"people={self.people!r}, " \
71 f
"milestones={self.milestones!r})"
74 def all_names(self
) -> Dict
[str, Person
]:
75 # also checks for any name clashes and raises
76 # ConfigParseError if any are detected
77 retval
= self
.people
.copy()
79 for person
in self
.people
.values():
80 for alias
in person
.aliases
:
81 other_person
= retval
.get(alias
)
82 if other_person
is not None:
83 if alias
in self
.people
:
84 raise ConfigParseError(
85 f
"alias is not allowed to be the same as any"
86 f
" person's identifier: in person entry for "
87 f
"{person.identifier!r}: {alias!r} is also the "
88 f
"identifier for person"
89 f
" {other_person.identifier!r}")
90 raise ConfigParseError(
91 f
"alias is not allowed to be the same as another"
92 f
" person's alias: in person entry for "
93 f
"{person.identifier!r}: {alias!r} is also an alias "
94 f
"for person {other_person.identifier!r}")
95 retval
[alias
] = person
99 def canonical_bug_ids(self
) -> Dict
[int, Milestone
]:
100 # also checks for any bug id clashes and raises
101 # ConfigParseError if any are detected
103 for milestone
in self
.milestones
.values():
104 other_milestone
= retval
.get(milestone
.canonical_bug_id
)
105 if other_milestone
is not None:
106 raise ConfigParseError(
107 f
"canonical_bug_id is not allowed to be the same as "
108 f
"another milestone's canonical_bug_id: in milestone "
109 f
"entry for {milestone.identifier!r}: "
110 f
"{milestone.canonical_bug_id} is also the "
111 f
"canonical_bug_id for milestone "
112 f
"{other_milestone.identifier!r}")
113 retval
[milestone
.canonical_bug_id
] = milestone
116 def _parse_person(self
, identifier
: str, value
: Any
) -> Person
:
117 def raise_aliases_must_be_list_of_strings():
118 raise ConfigParseError(
119 f
"`aliases` field in person entry for {identifier!r} must "
120 f
"be a list of strings")
122 if not isinstance(value
, dict):
123 raise ConfigParseError(
124 f
"person entry for {identifier!r} must be a table")
127 for k
, v
in value
.items():
128 assert isinstance(k
, str)
130 if not isinstance(v
, list):
131 raise_aliases_must_be_list_of_strings()
133 if isinstance(alias
, str):
135 raise ConfigParseError(
136 f
"duplicate alias in person entry for "
137 f
"{identifier!r}: {alias!r}")
140 raise_aliases_must_be_list_of_strings()
142 if not isinstance(v
, str):
143 raise ConfigParseError(
144 f
"`email` field in person entry for {identifier!r} "
148 raise ConfigParseError(
149 f
"unknown field in person entry for {identifier!r}: `{k}`")
150 return Person(config
=self
, identifier
=identifier
,
151 aliases
=aliases
, email
=email
)
153 def _parse_people(self
, people
: Any
):
154 if not isinstance(people
, dict):
155 raise ConfigParseError("`people` field must be a table")
156 for identifier
, value
in people
.items():
157 assert isinstance(identifier
, str)
158 self
.people
[identifier
] = self
._parse
_person
(identifier
, value
)
160 # self.all_names checks for name clashes and raises ConfigParseError
161 # if any are detected, so the following line is needed:
164 def _parse_milestone(self
, identifier
: str, value
: Any
) -> Milestone
:
165 if not isinstance(value
, dict):
166 raise ConfigParseError(
167 f
"milestones entry for {identifier!r} must be a table")
168 canonical_bug_id
= None
169 for k
, v
in value
.items():
170 assert isinstance(k
, str)
171 if k
== "canonical_bug_id":
172 if not isinstance(v
, int):
173 raise ConfigParseError(
174 f
"`canonical_bug_id` field in milestones entry for "
175 f
"{identifier!r} must be an integer")
178 raise ConfigParseError(f
"unknown field in milestones entry "
179 f
"for {identifier!r}: `{k}`")
180 if canonical_bug_id
is None:
181 raise ConfigParseError(f
"`canonical_bug_id` field is missing in "
182 f
"milestones entry for {identifier!r}")
183 return Milestone(config
=self
, identifier
=identifier
,
184 canonical_bug_id
=canonical_bug_id
)
186 def _parse_milestones(self
, milestones
: Any
):
187 if not isinstance(milestones
, dict):
188 raise ConfigParseError("`milestones` field must be a table")
189 for identifier
, value
in milestones
.items():
190 assert isinstance(identifier
, str)
191 self
.milestones
[identifier
] = \
192 self
._parse
_milestone
(identifier
, value
)
194 # self.canonical_bug_ids checks for bug id clashes and raises
195 # ConfigParseError if any are detected, so the following line
197 self
.canonical_bug_ids
200 def _from_toml(parsed_toml
: Dict
[str, Any
]) -> "Config":
204 for k
, v
in parsed_toml
.items():
205 assert isinstance(k
, str)
208 elif k
== "milestones":
210 elif k
== "bugzilla_url":
211 if not isinstance(v
, str):
212 raise ConfigParseError("`bugzilla_url` must be a string")
215 raise ConfigParseError(f
"unknown config entry: `{k}`")
217 if bugzilla_url
is None:
218 raise ConfigParseError("`bugzilla_url` field is missing")
220 config
= Config(bugzilla_url
=bugzilla_url
, people
={}, milestones
={})
223 raise ConfigParseError("`people` table is missing")
225 config
._parse
_people
(people
)
227 if milestones
is None:
228 raise ConfigParseError("`milestones` table is missing")
230 config
._parse
_milestones
(milestones
)
235 def from_str(text
: str) -> "Config":
237 parsed_toml
= toml
.loads(text
)
238 except toml
.TomlDecodeError
as e
:
239 new_err
= ConfigParseError(f
"TOML parse error: {e}")
240 raise new_err
.with_traceback(sys
.exc_info()[2])
241 return Config
._from
_toml
(parsed_toml
)
244 def from_file(file: Any
) -> "Config":
245 if isinstance(file, list):
246 raise TypeError("list is not a valid file or path")
248 parsed_toml
= toml
.load(file)
249 except toml
.TomlDecodeError
as e
:
250 new_err
= ConfigParseError(f
"TOML parse error: {e}")
251 raise new_err
.with_traceback(sys
.exc_info()[2])
252 return Config
._from
_toml
(parsed_toml
)