From: Jacob Lifshay Date: Wed, 9 Sep 2020 21:14:27 +0000 (-0700) Subject: add milestones to Config X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=c56e0aa1dc9759132c0bacbe5301ea36f3301eb2;p=utils.git add milestones to Config --- diff --git a/src/budget_sync/config.py b/src/budget_sync/config.py index 56162ba..2f0e0f7 100644 --- a/src/budget_sync/config.py +++ b/src/budget_sync/config.py @@ -39,14 +39,30 @@ class Person: f"aliases={self.aliases!r}, email={self.email!r})" +class Milestone: + def __init__(self, config: "Config", + identifier: str, canonical_bug_id: int): + self.config = config + self.identifier = identifier + self.canonical_bug_id = canonical_bug_id + + def __repr__(self): + return f"Milestone(config=..., " \ + f"identifier={self.identifier!r}, " \ + f"canonical_bug_id={self.canonical_bug_id})" + + class Config: - def __init__(self, bugzilla_url: str, people: Dict[str, Person]): + def __init__(self, bugzilla_url: str, people: Dict[str, Person], + milestones: Dict[str, Milestone]): self.bugzilla_url = bugzilla_url self.people = people + self.milestones = milestones def __repr__(self): return f"Config(bugzilla_url={self.bugzilla_url!r}, " \ - f"people={self.people!r})" + f"people={self.people!r}, " \ + f"milestones={self.milestones!r})" @cached_property def all_names(self) -> Dict[str, Person]: @@ -73,6 +89,24 @@ class Config: retval[alias] = person return retval + @cached_property + def canonical_bug_ids(self) -> Dict[int, Milestone]: + # also checks for any bug id clashes and raises + # ConfigParseError if any are detected + retval = {} + for milestone in self.milestones.values(): + other_milestone = retval.get(milestone.canonical_bug_id) + if other_milestone is not None: + raise ConfigParseError( + f"canonical_bug_id is not allowed to be the same as " + f"another milestone's canonical_bug_id: in milestone " + f"entry for {milestone.identifier!r}: " + f"{milestone.canonical_bug_id} is also the " + f"canonical_bug_id for milestone " + f"{other_milestone.identifier!r}") + retval[milestone.canonical_bug_id] = milestone + return retval + def _parse_person(self, identifier: str, value: Any) -> Person: def raise_aliases_must_be_list_of_strings(): raise ConfigParseError( @@ -121,14 +155,52 @@ class Config: # if any are detected, so the following line is needed: self.all_names + def _parse_milestone(self, identifier: str, value: Any) -> Milestone: + if not isinstance(value, dict): + raise ConfigParseError( + f"milestones entry for {identifier!r} must be a table") + canonical_bug_id = None + for k, v in value.items(): + assert isinstance(k, str) + if k == "canonical_bug_id": + if not isinstance(v, int): + raise ConfigParseError( + f"`canonical_bug_id` field in milestones entry for " + f"{identifier!r} must be an integer") + canonical_bug_id = v + else: + raise ConfigParseError(f"unknown field in milestones entry " + f"for {identifier!r}: `{k}`") + if canonical_bug_id is None: + raise ConfigParseError(f"`canonical_bug_id` field is missing in " + f"milestones entry for {identifier!r}") + return Milestone(config=self, identifier=identifier, + canonical_bug_id=canonical_bug_id) + + def _parse_milestones(self, milestones: Any): + if not isinstance(milestones, dict): + raise ConfigParseError("`milestones` field must be a table") + for identifier, value in milestones.items(): + assert isinstance(identifier, str) + self.milestones[identifier] = \ + self._parse_milestone(identifier, value) + + # self.canonical_bug_ids checks for bug id clashes and raises + # ConfigParseError if any are detected, so the following line + # is needed: + self.canonical_bug_ids + @staticmethod def _from_toml(parsed_toml: Dict[str, Any]) -> "Config": people = None bugzilla_url = None + milestones = None for k, v in parsed_toml.items(): assert isinstance(k, str) if k == "people": people = v + elif k == "milestones": + milestones = v elif k == "bugzilla_url": if not isinstance(v, str): raise ConfigParseError("`bugzilla_url` must be a string") @@ -139,13 +211,18 @@ class Config: if bugzilla_url is None: raise ConfigParseError("`bugzilla_url` field is missing") - config = Config(bugzilla_url=bugzilla_url, people={}) + config = Config(bugzilla_url=bugzilla_url, people={}, milestones={}) if people is None: raise ConfigParseError("`people` table is missing") else: config._parse_people(people) + if milestones is None: + raise ConfigParseError("`milestones` table is missing") + else: + config._parse_milestones(milestones) + return config @staticmethod diff --git a/src/budget_sync/test/test_config.py b/src/budget_sync/test/test_config.py index c16416f..3b203a1 100644 --- a/src/budget_sync/test/test_config.py +++ b/src/budget_sync/test/test_config.py @@ -72,9 +72,16 @@ class TestConfig(unittest.TestCase): aliases = ["a", "a"] """, "duplicate alias in person entry for 'person1': 'a'") + check_error( + """ + bugzilla_url = "" + [people] + """, + "`milestones` table is missing") check( """ bugzilla_url = "" + [milestones] [people."person1"] aliases = ["a"] [people."person2"] @@ -84,7 +91,7 @@ class TestConfig(unittest.TestCase): "'person1': Person(config=..., identifier='person1', " "aliases={'a'}, email=None), " "'person2': Person(config=..., identifier='person2', " - "aliases={'b'}, email=None)})") + "aliases={'b'}, email=None)}, milestones={})") check_error( """ bugzilla_url = "" @@ -96,17 +103,19 @@ class TestConfig(unittest.TestCase): """ bugzilla_url = "" [people] + [milestones] """, - "Config(bugzilla_url='', people={})") + "Config(bugzilla_url='', people={}, milestones={})") check( """ bugzilla_url = "" + [milestones] [people."person1"] email = "email@example.com" """, "Config(bugzilla_url='', people={" "'person1': Person(config=..., identifier='person1', " - "aliases=set(), email='email@example.com')})") + "aliases=set(), email='email@example.com')}, milestones={})") check_error( """ bugzilla_url = "" @@ -117,6 +126,7 @@ class TestConfig(unittest.TestCase): check_error( """ bugzilla_url = "" + [milestones] [people."person1"] [people."person2"] aliases = ["person1"] @@ -127,6 +137,7 @@ class TestConfig(unittest.TestCase): check_error( """ bugzilla_url = "" + [milestones] [people."person1"] aliases = ["a"] [people."person2"] @@ -135,11 +146,63 @@ class TestConfig(unittest.TestCase): "alias is not allowed to be the same as another person's alias: " "in person entry for 'person2': 'a' is also an alias for person " "'person1'") + check_error( + """ + bugzilla_url = "" + [milestones] + "abc" = 1 + [people] + """, + "milestones entry for 'abc' must be a table") + check_error( + """ + bugzilla_url = "" + [milestones] + "abc" = { canonical_bug_id = "abc" } + [people] + """, + "`canonical_bug_id` field in milestones entry for 'abc' must " + "be an integer") + check_error( + """ + bugzilla_url = "" + [milestones] + "abc" = { blah = "def" } + [people] + """, + "unknown field in milestones entry for 'abc': `blah`") + check_error( + """ + bugzilla_url = "" + [milestones] + "abc" = {} + [people] + """, + "`canonical_bug_id` field is missing in milestones entry for 'abc'") + check_error( + """ + bugzilla_url = "" + milestones = 1 + [people] + """, + "`milestones` field must be a table") + check_error( + """ + bugzilla_url = "" + [milestones] + "abc" = { canonical_bug_id = 1 } + "def" = { canonical_bug_id = 1 } + [people] + """, + "canonical_bug_id is not allowed to be the same as another " + "milestone's canonical_bug_id: in milestone entry for 'def': " + "1 is also the canonical_bug_id for milestone 'abc'") def test_all_names(self): config = Config.from_str( """ bugzilla_url = "" + [milestones] [people."person1"] aliases = ["person1_alias1", "alias1"] [people."person2"] @@ -157,6 +220,23 @@ class TestConfig(unittest.TestCase): 'alias2': person2, }) + def test_canonical_bug_ids(self): + config = Config.from_str( + """ + bugzilla_url = "" + [people] + [milestones] + "Milestone 1" = { canonical_bug_id = 1 } + "Milestone 2" = { canonical_bug_id = 2 } + """) + milestone1 = config.milestones['Milestone 1'] + milestone2 = config.milestones['Milestone 2'] + self.assertEqual(config.canonical_bug_ids, + { + 1: milestone1, + 2: milestone2, + }) + def test_from_file(self): def load(text): with io.StringIO(text) as file: @@ -171,6 +251,21 @@ class TestConfig(unittest.TestCase): "^TOML parse error: Empty value is invalid"): load("""bad-toml=""") + self.assertEqual(str(load( + """ + bugzilla_url = "https://bugzilla.example.com/" + [people."person1"] + email = "person1@example.com" + aliases = ["alias1"] + [milestones] + "Milestone 1" = { canonical_bug_id = 123 } + """)), + "Config(bugzilla_url='https://bugzilla.example.com/', " + "people={'person1': Person(config=..., identifier='person1', " + "aliases={'alias1'}, email='person1@example.com')}, " + "milestones={'Milestone 1': Milestone(config=..., " + "identifier='Milestone 1', canonical_bug_id=123)})") + if __name__ == "__main__": unittest.main()