Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ Pylint displays a global evaluation score for the code, rated out of a maximum s

### Set Up Python Environment
```shell
python3 -m venv venv
source venv/bin/activate
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
```

Expand Down
1 change: 0 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from release_notes_generator.action_inputs import ActionInputs
from release_notes_generator.utils.gh_action import set_action_output
from release_notes_generator.utils.logging_config import setup_logging
from release_notes_generator.filter import FilterByRelease

warnings.filterwarnings("ignore", category=InsecureRequestWarning)

Expand Down
4 changes: 0 additions & 4 deletions release_notes_generator/model/commit_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ def is_closed(self) -> bool:
def is_open(self) -> bool:
return False

@property
def labels(self):
return []

@property
def authors(self) -> list[str]:
return [self._commit.author.login] if self._commit.author else []
Expand Down
7 changes: 3 additions & 4 deletions release_notes_generator/model/issue_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def __init__(self, issue: Issue, skip: bool = False):
super().__init__(skip=skip)

self._issue: Issue = issue
self._labels = {label.name for label in self._issue.get_labels()}

self._pull_requests: dict[int, PullRequest] = {}
self._commits: dict[int, dict[str, Commit]] = {}

Expand All @@ -44,10 +46,6 @@ def is_closed(self) -> bool:
def is_open(self) -> bool:
return self._issue.state == self.ISSUE_STATE_OPEN

@property
def labels(self):
return [label.name for label in self._issue.labels]

@property
def authors(self) -> list[str]:
if not self._issue or not self._issue.user:
Expand Down Expand Up @@ -161,6 +159,7 @@ def register_pull_request(self, pull: PullRequest) -> None:
Returns: None
"""
self._pull_requests[pull.number] = pull
self._labels.update({label.name for label in pull.get_labels()})

def register_commit(self, pull: PullRequest, commit: Commit) -> None:
"""
Expand Down
9 changes: 4 additions & 5 deletions release_notes_generator/model/pull_request_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def __init__(self, pull: PullRequest, skip: bool = False):
super().__init__(skip=skip)

self._pull_request: PullRequest = pull
self._labels = {label.name for label in self._pull_request.get_labels()}

self._commits: dict[str, Commit] = {}

# properties - override Record properties
Expand All @@ -45,10 +47,6 @@ def is_closed(self) -> bool:
def is_open(self) -> bool:
return self._pull_request.state == self.PR_STATE_OPEN

@property
def labels(self):
return [label.name for label in self._pull_request.labels]

@property
def authors(self) -> list[str]:
if not self._pull_request or not self._pull_request.user:
Expand Down Expand Up @@ -242,7 +240,8 @@ def _get_rls_notes_code_rabbit(
Parameters:
pull (PullRequest): The pull request from which to extract release notes.
line_marks (list[str]): A list of characters that indicate the start of a release notes section.
cr_detection_regex (re.Pattern[str]): A regex pattern to detect the start of the Code Rabbit release notes section.
cr_detection_regex (re.Pattern[str]): A regex pattern to detect the start of the Code Rabbit release notes
section.
Returns:
str: The extracted release notes as a string. If no release notes are found, returns an empty string.
"""
Expand Down
19 changes: 10 additions & 9 deletions release_notes_generator/model/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def __init__(self, skip: bool = False):
self._present_in_chapters = 0
self._skip = skip
self._is_release_note_detected: Optional[bool] = None
self._labels: set[str] = set()
self._rls_notes: Optional[str] = None # single annotation here

# properties
Expand All @@ -55,6 +56,15 @@ def skip(self) -> bool:
"""Check if the record should be skipped during output generation process."""
return self._skip

@property
def labels(self) -> list[str]:
"""
Gets the labels of the record.
Returns:
list[str]: A list of labels associated with the record.
"""
return list(self._labels)

Comment on lines +59 to +67
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Aug 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make label exposure deterministic to prevent flaky ordering

Returning a list from a set yields non-deterministic order; sorting avoids flaky tests and unstable output.

Apply:

-        return list(self._labels)
+        return sorted(self._labels)

Optionally, if you’d like a public helper to simplify tests and avoid touching _labels, consider:

# outside diff – add to Record
def add_labels(self, labels: list[str]) -> None:
    self._labels.update(labels)
🤖 Prompt for AI Agents
In release_notes_generator/model/record.py around lines 59 to 67, the labels
property returns list(self._labels) which yields non-deterministic ordering;
change the property to return a consistently ordered list by converting the set
to a sorted list (e.g., return sorted(self._labels)); optionally add a public
helper method add_labels(self, labels: list[str]) that updates the internal set
via self._labels.update(labels) to simplify tests without touching the private
attribute.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@miroslavpojer, understood! I'll defer to your judgment on this. The current implementation works fine for your use case.

    🐰
╭─────╮
│ ◕   ◕ │
│   ‿   │
╰─────╯

@property
@abstractmethod
def record_id(self) -> int | str:
Expand Down Expand Up @@ -82,15 +92,6 @@ def is_open(self) -> bool:
bool: True if the record is open, False otherwise.
"""

@property
@abstractmethod
def labels(self) -> list[str]:
"""
Gets the labels of the record.
Returns:
list[str]: A list of labels associated with the record.
"""

@property
@abstractmethod
def authors(self) -> list[str]:
Expand Down
10 changes: 5 additions & 5 deletions release_notes_generator/record/record_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ def generate(github: Github, data: MinedData) -> dict[int | str, Record]:

def register_pull_request(pull: PullRequest, skip_rec: bool) -> None:
detected_issues = extract_issue_numbers_from_body(pull)
logger.debug(f"Detected issues - from body: {detected_issues}")
logger.debug("Detected issues - from body: %s", detected_issues)
detected_issues.update(safe_call(get_issues_for_pr)(pull_number=pull.number))
logger.debug(f"Detected issues - final: {detected_issues}")
logger.debug("Detected issues - final: %s", detected_issues)

for parent_issue_number in detected_issues:
# create an issue record if not present for PR parent
Expand Down Expand Up @@ -98,14 +98,14 @@ def register_pull_request(pull: PullRequest, skip_rec: bool) -> None:

logger.debug("Registering pull requests to records...")
for pull in data.pull_requests:
pull_labels = [label.name for label in pull.labels]
pull_labels = [label.name for label in pull.get_labels()]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Wrap API call to get_labels with rate-limiter.
Direct call may hit rate limits; use the existing safe_call wrapper.

-            pull_labels = [label.name for label in pull.get_labels()]
+            pull_labels = [label.name for label in safe_call(pull.get_labels)()]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pull_labels = [label.name for label in pull.get_labels()]
pull_labels = [label.name for label in safe_call(pull.get_labels)()]
🤖 Prompt for AI Agents
In release_notes_generator/record/record_factory.py around line 101, the direct
call pull.get_labels() can hit API rate limits; replace it by invoking the
existing safe_call wrapper to perform the API call (e.g., call safe_call with
pull.get_labels or a lambda that calls it), assign the returned value to a
variable, and fall back to an empty list if safe_call returns None or raises;
ensure you import/Reference the existing safe_call and use its return value to
build pull_labels = [label.name for label in labels].

skip_record: bool = any(item in pull_labels for item in ActionInputs.get_skip_release_notes_labels())

if not safe_call(get_issues_for_pr)(pull_number=pull.number) and not extract_issue_numbers_from_body(pull):
records[pull.number] = PullRequestRecord(pull, skip=skip_record)
logger.debug("Created record for PR %d: %s", pull.number, pull.title)
else:
logger.debug(f"Registering pull number: {pull.number}, title : {pull.title}")
logger.debug("Registering pull number: %s, title : %s", pull.number, pull.title)
register_pull_request(pull, skip_record)

logger.debug("Registering commits to records...")
Expand Down Expand Up @@ -158,7 +158,7 @@ def _create_record_for_issue(records: dict[int | str, Record], i: Issue) -> None
@return: None
"""
# check for skip labels presence and skip when detected
issue_labels = [label.name for label in i.labels]
issue_labels = [label.name for label in i.get_labels()]
skip_record = any(item in issue_labels for item in ActionInputs.get_skip_release_notes_labels())
records[i.number] = IssueRecord(issue=i, skip=skip_record)

Comment on lines +161 to 164
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Same: wrap issue.get_labels with rate-limiter.
Aligns with other GitHub API calls here.

-        issue_labels = [label.name for label in i.get_labels()]
+        issue_labels = [label.name for label in safe_call(i.get_labels)()]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
issue_labels = [label.name for label in i.get_labels()]
skip_record = any(item in issue_labels for item in ActionInputs.get_skip_release_notes_labels())
records[i.number] = IssueRecord(issue=i, skip=skip_record)
issue_labels = [label.name for label in safe_call(i.get_labels)()]
skip_record = any(item in issue_labels for item in ActionInputs.get_skip_release_notes_labels())
records[i.number] = IssueRecord(issue=i, skip=skip_record)
🤖 Prompt for AI Agents
In release_notes_generator/record/record_factory.py around lines 161 to 164, the
direct call to i.get_labels() should be invoked through the existing GitHub API
rate-limiter like the other calls in this file; replace the direct list
comprehension with a rate-limited call that fetches labels (e.g., call the
module/class rate-limiter wrapper used elsewhere to execute i.get_labels()),
then build issue_labels from that returned value and proceed to compute
skip_record and IssueRecord as before; ensure you use the same rate-limiter
instance/name already used in this file so imports/variables remain consistent.

Expand Down
22 changes: 11 additions & 11 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
pytest==7.4.3
pytest-cov==5.0.0
pytest-mock==3.14.0
pytest==8.4.2
pytest-cov==6.3.0
pytest-mock==3.15.0
PyGithub==1.59.0
pylint==3.2.6
requests==2.31.0
black==24.8.0
pylint==3.3.8
requests==2.32.5
black==25.1.0
PyYAML==6.0.2
semver==3.0.2
mypy==1.15.0
mypy-extensions==1.0.0
types-requests==2.32.0.20250328
types-PyYAML==6.0.2
semver==3.0.4
mypy==1.17.1
mypy-extensions==1.1.0
types-requests==2.32.4.20250809
types-PyYAML==6.0.12.20250822
Loading
Loading