From 620675457b0fd75827e1e43afb37498ee8151336 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Sat, 14 Dec 2024 20:22:59 -0800 Subject: [PATCH 1/6] feat: add archive label and active status to ReviewModel --- src/pyosmeta/models/base.py | 1 + src/pyosmeta/models/github.py | 20 +++++++++++++++++++- src/pyosmeta/parse_issues.py | 22 +++++++++++++++++++++- tests/integration/test_parse_issues.py | 17 +++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/pyosmeta/models/base.py b/src/pyosmeta/models/base.py index 63a4a18..2beb88d 100644 --- a/src/pyosmeta/models/base.py +++ b/src/pyosmeta/models/base.py @@ -279,6 +279,7 @@ class ReviewModel(BaseModel): partners: Optional[list[Partnerships]] = None gh_meta: Optional[GhMeta] = None labels: list[str] = Field(default_factory=list) + active: bool = True # To indicate if package is maintained or archived @field_validator( "date_accepted", diff --git a/src/pyosmeta/models/github.py b/src/pyosmeta/models/github.py index 4a35c86..b6df841 100644 --- a/src/pyosmeta/models/github.py +++ b/src/pyosmeta/models/github.py @@ -17,9 +17,10 @@ from __future__ import annotations from datetime import datetime +from enum import Enum from typing import Any, List, Literal, Optional, Union -from pydantic import AnyUrl, BaseModel, ConfigDict, Field +from pydantic import AnyUrl, BaseModel, ConfigDict, Field, model_validator class User(BaseModel): @@ -61,6 +62,12 @@ class ClosedBy(User): ... class Owner(User): ... +class LabelType(str, Enum): + ARCHIVED = "archived" + PYOS_APPROVED = "6/pyOS-approved" + JOSS_APPROVED = "9/joss-approved" + + class Labels(BaseModel): id: Optional[int] = None node_id: Optional[str] = None @@ -69,6 +76,17 @@ class Labels(BaseModel): description: Optional[str] = None color: Optional[str] = None default: Optional[bool] = None + type: Optional[LabelType] = None + + @model_validator(mode="before") + def parse_label_type(cls, data): + """Parse the label type from the name before validation.""" + if "name" in data: + try: + data["type"] = LabelType(data["name"]) + except ValueError: + pass + return data class Issue(BaseModel): diff --git a/src/pyosmeta/parse_issues.py b/src/pyosmeta/parse_issues.py index 5e1696e..37aaeee 100644 --- a/src/pyosmeta/parse_issues.py +++ b/src/pyosmeta/parse_issues.py @@ -7,7 +7,7 @@ from pydantic import ValidationError from pyosmeta.models import ReviewModel, ReviewUser -from pyosmeta.models.github import Issue +from pyosmeta.models.github import Issue, Labels, LabelType from .github_api import GitHubAPI from .utils_clean import clean_date_accepted_key @@ -221,6 +221,25 @@ def _postprocess_meta(self, meta: dict, body: List[str]) -> dict: return meta + def _postprocess_labels(self, meta: dict) -> dict: + """ + Handle specific labels that are model properties + """ + + def _is_archived(label: str) -> bool: + if isinstance(label, str): + return "archived" in label.lower() + elif isinstance(label, Labels): + return label.type == LabelType.ARCHIVED + raise ValueError("Invalid label type") + + # Check if the review has the "archived" label + if "labels" in meta and [ + label for label in meta["labels"] if _is_archived(label) + ]: + meta["active"] = False + return meta + def _parse_field(self, key: str, val: str) -> Any: """ Method dispatcher for parsing specific header fields. @@ -277,6 +296,7 @@ def parse_issue(self, issue: Issue | str) -> ReviewModel: # Finalize review model before casting model = self._postprocess_meta(model, body) + model = self._postprocess_labels(model) return ReviewModel(**model) diff --git a/tests/integration/test_parse_issues.py b/tests/integration/test_parse_issues.py index 237ebfe..5078f2f 100644 --- a/tests/integration/test_parse_issues.py +++ b/tests/integration/test_parse_issues.py @@ -69,3 +69,20 @@ def test_parse_labels(issue_list, process_issues): issue.labels = labels review = process_issues.parse_issue(issue) assert review.labels == ["6/pyOS-approved", "another_label"] + assert review.active + + # Now add an archive label + label_inst = Labels( + id=1196238794, + node_id="MDU6TGFiZWwxMTk2MjM4Nzk0", + url="https://api.github.com/repos/pyOpenSci/software-submission/labels/archived", + name="archived", + description="", + color="006B75", + default=False, + ) + labels = [label_inst, "another_label"] + for issue in issue_list: + issue.labels = labels + review = process_issues.parse_issue(issue) + assert not review.active From b9868eb69224b03a282d27cdc03c28e3ab34ee85 Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Wed, 18 Dec 2024 22:54:57 -0800 Subject: [PATCH 2/6] docstrings --- src/pyosmeta/models/github.py | 21 ++++++++++++++++++--- src/pyosmeta/parse_issues.py | 10 +++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/pyosmeta/models/github.py b/src/pyosmeta/models/github.py index b6df841..e80632c 100644 --- a/src/pyosmeta/models/github.py +++ b/src/pyosmeta/models/github.py @@ -63,9 +63,17 @@ class Owner(User): ... class LabelType(str, Enum): + """Enum for the different types of labels that can be assigned to an issue. + + This enum is not meant to be exhaustive, but rather capture a few important + labels for life cycle of approved reviews. + + For now, this only includes the "archived" label, which is used to mark + packages that are no longer maintained ("inactive"). The "archived" label + corresponds to setting ``active=False`` on the ReviewModel + """ + ARCHIVED = "archived" - PYOS_APPROVED = "6/pyOS-approved" - JOSS_APPROVED = "9/joss-approved" class Labels(BaseModel): @@ -80,7 +88,14 @@ class Labels(BaseModel): @model_validator(mode="before") def parse_label_type(cls, data): - """Parse the label type from the name before validation.""" + """Parse the label type from the name before validation. + + This will parse the label name into an available LabelType enum value. + Not all labels will have a corresponding LabelType, so this will + gracefully fail. This was implemented for assigning the LabelType.ARCHIVED + value to the "archived" label so that we can easily filter out archived + issues. + """ if "name" in data: try: data["type"] = LabelType(data["name"]) diff --git a/src/pyosmeta/parse_issues.py b/src/pyosmeta/parse_issues.py index 37aaeee..0b744c2 100644 --- a/src/pyosmeta/parse_issues.py +++ b/src/pyosmeta/parse_issues.py @@ -223,10 +223,18 @@ def _postprocess_meta(self, meta: dict, body: List[str]) -> dict: def _postprocess_labels(self, meta: dict) -> dict: """ - Handle specific labels that are model properties + Process specific labels for attributes in the review model. + + Presently, this method only checks if the review has the "archived" + (LabelType.ARCHIVED) label and sets the active attribute to False + if it does. We may add more label processing in the future. + + The intention behind this is to assign specific ReviewModel attributes + based on the presence of certain labels in the review issue. """ def _is_archived(label: str) -> bool: + """Internal helper to check if a label is the "archived" label""" if isinstance(label, str): return "archived" in label.lower() elif isinstance(label, Labels): From ff807b942042ad41fd101bf865d7d8eedfefd17f Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Wed, 18 Dec 2024 23:09:22 -0800 Subject: [PATCH 3/6] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd26941..194f7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Notes: it looks like i may have mistakenly bumped to 1.3.7 in august. rather tha ### Added * Add: new repos to track contribs (@lwasser) +* Add: new `active` status under `ReviewModel` which is set to `False` if the `"archived"` label is present on a review to mark the package as inactive (@banesullivan) ### Fixed From ed443bc5eb95dd199d47040c72ab33723b60e33c Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Wed, 18 Dec 2024 23:20:39 -0800 Subject: [PATCH 4/6] Improve test --- src/pyosmeta/models/github.py | 2 +- src/pyosmeta/parse_issues.py | 8 +++----- tests/integration/test_parse_issues.py | 8 ++++++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/pyosmeta/models/github.py b/src/pyosmeta/models/github.py index e80632c..c8a5ccb 100644 --- a/src/pyosmeta/models/github.py +++ b/src/pyosmeta/models/github.py @@ -77,10 +77,10 @@ class LabelType(str, Enum): class Labels(BaseModel): + name: str id: Optional[int] = None node_id: Optional[str] = None url: Optional[AnyUrl] = None - name: Optional[str] = None description: Optional[str] = None color: Optional[str] = None default: Optional[bool] = None diff --git a/src/pyosmeta/parse_issues.py b/src/pyosmeta/parse_issues.py index 0b744c2..1b8372c 100644 --- a/src/pyosmeta/parse_issues.py +++ b/src/pyosmeta/parse_issues.py @@ -233,13 +233,11 @@ def _postprocess_labels(self, meta: dict) -> dict: based on the presence of certain labels in the review issue. """ - def _is_archived(label: str) -> bool: + def _is_archived(label: str | Labels) -> bool: """Internal helper to check if a label is the "archived" label""" - if isinstance(label, str): - return "archived" in label.lower() - elif isinstance(label, Labels): + if isinstance(label, Labels): return label.type == LabelType.ARCHIVED - raise ValueError("Invalid label type") + return "archived" in label.lower() # Check if the review has the "archived" label if "labels" in meta and [ diff --git a/tests/integration/test_parse_issues.py b/tests/integration/test_parse_issues.py index 5078f2f..6eecf8d 100644 --- a/tests/integration/test_parse_issues.py +++ b/tests/integration/test_parse_issues.py @@ -86,3 +86,11 @@ def test_parse_labels(issue_list, process_issues): issue.labels = labels review = process_issues.parse_issue(issue) assert not review.active + + # Handle label with missing details + label_inst = Labels(name="test") + labels = [label_inst, "another_label"] + for issue in issue_list: + issue.labels = labels + review = process_issues.parse_issue(issue) + assert review.labels == ["test", "another_label"] From 8770f49eac7ede81fbcb8c2116a11f87db159c9d Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Wed, 18 Dec 2024 23:24:20 -0800 Subject: [PATCH 5/6] name is required --- src/pyosmeta/models/github.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pyosmeta/models/github.py b/src/pyosmeta/models/github.py index c8a5ccb..6f2c438 100644 --- a/src/pyosmeta/models/github.py +++ b/src/pyosmeta/models/github.py @@ -96,11 +96,10 @@ def parse_label_type(cls, data): value to the "archived" label so that we can easily filter out archived issues. """ - if "name" in data: - try: - data["type"] = LabelType(data["name"]) - except ValueError: - pass + try: + data["type"] = LabelType(data["name"]) + except ValueError: + pass return data From 12669959aba31bad901b603990592b91f4b3772b Mon Sep 17 00:00:00 2001 From: Bane Sullivan Date: Thu, 19 Dec 2024 00:28:39 -0800 Subject: [PATCH 6/6] correct changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 194f7d3..ce70a23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ## [Unreleased] +* Add: new `active` status under `ReviewModel` which is set to `False` if the `"archived"` label is present on a review to mark the package as inactive (@banesullivan) + [v1.4] - 2024-11-22 Notes: it looks like i may have mistakenly bumped to 1.3.7 in august. rather than try to fix on pypi we will just go with it to ensure our release cycles are smooth given no one else uses this package except pyopensci. @@ -11,7 +13,6 @@ Notes: it looks like i may have mistakenly bumped to 1.3.7 in august. rather tha ### Added * Add: new repos to track contribs (@lwasser) -* Add: new `active` status under `ReviewModel` which is set to `False` if the `"archived"` label is present on a review to mark the package as inactive (@banesullivan) ### Fixed