Skip to content

Commit 5f2cab3

Browse files
authored
refactor: fix todos (#44)
* fix: fixing TODO * chore: use enum for annotation type * fix: use configuration exception for coverage extract * fix: use folder to save annotation
1 parent eb63eee commit 5f2cab3

File tree

12 files changed

+124
-105
lines changed

12 files changed

+124
-105
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ Note: Either `GITHUB_PR_NUMBER` or `GITHUB_REF` is required. `GITHUB_PR_NUMBER`
9090
- `BRANCH_COVERAGE`: Show branch coverage in the report. Default is False.
9191
- `SKIP_COVERAGE`: Skip coverage reporting as github comment and generate only annotaions. Default is False.
9292
- `ANNOTATIONS_DATA_BRANCH`: The branch to store the annotations. Read more about this [here](./docs/annotations.md).
93-
- `ANNOTATIONS_OUTPUT_PATH`: The path where the annotaions should be stored. Should be a .json file.
93+
- `ANNOTATIONS_OUTPUT_PATH`: The path where the annotaions should be stored. Should be a path to folder.
9494
- `ANNOTATE_MISSING_LINES`: Whether to annotate missing lines in the coverage report. Default is False.
95-
- `ANNOTATION_TYPE`: The type of annotation to use for missing lines. Default is 'warning'.
95+
- `ANNOTATION_TYPE`: The type of annotation to use for missing lines. 'notice' or 'warning' or 'error'. Default is 'warning'.
9696
- `MAX_FILES_IN_COMMENT`: The maximum number of files to include in the coverage report comment. Default is 25.
9797
- `COMPLETE_PROJECT_REPORT`: Whether to include the complete project coverage report in the comment. Default is False.
9898
- `COVERAGE_REPORT_URL`: URL of the full coverage report to mention in the comment.

codecov/config.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
import inspect
66
import pathlib
77
from collections.abc import MutableMapping
8+
from enum import Enum
89
from typing import Any, Callable
910

10-
from codecov.exceptions import InvalidAnnotationType, MissingEnvironmentVariable
11-
12-
# TODO: Rename this file to config.py
11+
from codecov.exceptions import MissingEnvironmentVariable
1312

1413

1514
def path_below(path_str: str | pathlib.Path) -> pathlib.Path:
@@ -26,8 +25,14 @@ def str_to_bool(value: str) -> bool:
2625
return value.lower() in ('1', 'true', 'yes')
2726

2827

28+
class AnnotationType(Enum):
29+
NOTICE = 'notice'
30+
WARNING = 'warning'
31+
ERROR = 'error'
32+
33+
2934
# pylint: disable=invalid-name, too-many-instance-attributes
30-
@dataclasses.dataclass
35+
@dataclasses.dataclass(kw_only=True)
3136
class Config:
3237
"""This object defines the environment variables"""
3338

@@ -44,8 +49,7 @@ class Config:
4449
BRANCH_COVERAGE: bool = False
4550
SKIP_COVERAGE: bool = False
4651
ANNOTATE_MISSING_LINES: bool = False
47-
# TODO: Make it enum
48-
ANNOTATION_TYPE: str = 'warning'
52+
ANNOTATION_TYPE: AnnotationType = AnnotationType.WARNING
4953
ANNOTATIONS_OUTPUT_PATH: pathlib.Path | None = None
5054
ANNOTATIONS_DATA_BRANCH: str | None = None
5155
MAX_FILES_IN_COMMENT: int = 25
@@ -88,12 +92,8 @@ def clean_debug(cls, value: str) -> bool:
8892
return str_to_bool(value)
8993

9094
@classmethod
91-
def clean_annotation_type(cls, value: str) -> str:
92-
if value not in {'notice', 'warning', 'error'}:
93-
raise InvalidAnnotationType(
94-
f'The annotation type {value} is not valid. Please choose from notice, warning or error'
95-
)
96-
return value
95+
def clean_annotation_type(cls, value: str) -> AnnotationType:
96+
return AnnotationType(value)
9797

9898
@classmethod
9999
def clean_github_pr_number(cls, value: str) -> int:
@@ -105,7 +105,10 @@ def clean_coverage_path(cls, value: str) -> pathlib.Path:
105105

106106
@classmethod
107107
def clean_annotations_output_path(cls, value: str) -> pathlib.Path:
108-
return pathlib.Path(value)
108+
path = pathlib.Path(value)
109+
if path.exists() or path.is_dir():
110+
return path
111+
raise ValueError
109112

110113
# We need to type environ as a MutableMapping because that's what
111114
# os.environ is, and `dict[str, str]` is not enough

codecov/coverage/base.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from collections import deque
1010
from collections.abc import Sequence
1111

12+
from codecov.exceptions import ConfigurationException
1213
from codecov.log import log
1314

1415

@@ -86,16 +87,15 @@ def compute_coverage(self, num_covered: int, num_total: int) -> decimal.Decimal:
8687
return decimal.Decimal(num_covered) / decimal.Decimal(num_total)
8788

8889
def get_coverage_info(self, coverage_path: pathlib.Path) -> Coverage:
89-
# TODO: Write a custom exception here and handle it in the main script
9090
try:
9191
with coverage_path.open() as coverage_data:
9292
json_coverage = json.loads(coverage_data.read())
93-
except FileNotFoundError:
93+
except FileNotFoundError as exc:
9494
log.error('Coverage report file not found: %s', coverage_path)
95-
raise
96-
except json.JSONDecodeError:
95+
raise ConfigurationException from exc
96+
except json.JSONDecodeError as exc:
9797
log.error('Invalid JSON format in coverage report file: %s', coverage_path)
98-
raise
98+
raise ConfigurationException from exc
9999

100100
return self.extract_info(data=json_coverage)
101101

codecov/exceptions.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ class MissingEnvironmentVariable(ConfigurationException):
5050
pass
5151

5252

53-
class InvalidAnnotationType(ConfigurationException):
54-
pass
55-
56-
5753
class TemplateBaseException(CoreBaseException):
5854
pass
5955

codecov/github.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,8 @@ def post_comment(self, contents: str, marker: str) -> None:
120120
log.error('Comment exceeds allowed size(65536)')
121121
raise CannotPostComment
122122

123-
# TODO: Try and see if you can refactor this to use single request
124-
# issue_comments_path.patch(body=contents)
125-
# TODO: Make scope only for pull request not for issues
123+
# Pull request review comments are comments made on a portion of the unified diff during a pull request review.
124+
# Issue comments are comments on the entire pull request. We need issue comments.
126125
issue_comments_path = self.client.repos(self.repository).issues(self.pr_number).comments
127126
comments_path = self.client.repos(self.repository).issues.comments
128127
for comment in issue_comments_path.get():

codecov/github_client.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from typing import Any
4+
35
import httpx
46

57
from codecov.exceptions import ApiError, ConfigurationException, Conflict, Forbidden, NotFound, ValidationFailed
@@ -80,7 +82,7 @@ def __getattr__(self, attr):
8082

8183
def _http(self, method: str, path: str, *, use_bytes: bool = False, use_text: bool = False, **kw):
8284
_method = method.lower()
83-
requests_kwargs = {}
85+
requests_kwargs: dict[Any, Any] = {}
8486
headers = kw.pop('headers', {})
8587
if _method == 'get' and kw:
8688
requests_kwargs = {'params': kw}
@@ -106,14 +108,16 @@ def _http(self, method: str, path: str, *, use_bytes: bool = False, use_text: bo
106108
try:
107109
response.raise_for_status()
108110
except httpx.HTTPStatusError as exc:
109-
# TODO: Use `match` Statement
110-
cls: type[ApiError] = {
111-
403: Forbidden,
112-
404: NotFound,
113-
409: Conflict,
114-
422: ValidationFailed,
115-
}.get(exc.response.status_code, ApiError)
116-
117-
raise cls(str(contents)) from exc
111+
exc_cls = ApiError
112+
match exc.response.status_code:
113+
case 403:
114+
exc_cls = Forbidden
115+
case 404:
116+
exc_cls = NotFound
117+
case 409:
118+
exc_cls = Conflict
119+
case 422:
120+
exc_cls = ValidationFailed
121+
raise exc_cls(str(contents)) from exc
118122

119123
return contents

codecov/main.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from codecov.config import Config
66
from codecov.coverage import PytestCoverage
77
from codecov.coverage.base import Coverage, DiffCoverage
8-
from codecov.exceptions import CoreProcessingException, MissingMarker, TemplateException
8+
from codecov.exceptions import ConfigurationException, CoreProcessingException, MissingMarker, TemplateException
99
from codecov.github import Github
1010
from codecov.github_client import GitHubClient
1111
from codecov.log import log, setup as log_setup
@@ -51,7 +51,12 @@ def run(self):
5151

5252
def _process_coverage(self):
5353
log.info('Processing coverage data')
54-
coverage = self.coverage_module.get_coverage_info(coverage_path=self.config.COVERAGE_PATH)
54+
try:
55+
coverage = self.coverage_module.get_coverage_info(coverage_path=self.config.COVERAGE_PATH)
56+
except ConfigurationException as e:
57+
log.error('Error processing coverage data.')
58+
raise CoreProcessingException from e
59+
5560
if self.config.BRANCH_COVERAGE:
5661
coverage = diff_grouper.fill_branch_missing_groups(coverage=coverage)
5762
added_lines = self.coverage_module.parse_diff_output(diff=self.github.pr_diff)
@@ -64,7 +69,7 @@ def _process_pr(self):
6469
log.info('Skipping coverage report generation.')
6570
return
6671

67-
log.info('Generating comment for PR')
72+
log.info('Generating comment for PR #%s', self.github.pr_number)
6873
marker = template.get_marker(marker_id=self.config.SUBPROJECT_ID)
6974
files_info, count_files, changed_files_info = template.select_changed_files(
7075
coverage=self.coverage,
@@ -99,18 +104,15 @@ def _process_pr(self):
99104
)
100105
except MissingMarker as e:
101106
log.error(
102-
'Marker not found. This error can happen if you defined a custom comment '
103-
"template that doesn't inherit the base template and you didn't include "
104-
'``{{ marker }}``. The marker is necessary for this action to recognize '
105-
"its own comment and avoid making new comments or overwriting someone else's "
106-
'comment.'
107+
'``{{ %s }}`` marker not found. The marker is necessary for this action to recognize '
108+
"its own comment and avoid making new comments or overwriting someone else's comment.",
109+
marker,
107110
)
108111
raise CoreProcessingException from e
109112
except TemplateException as e:
110113
log.error(
111114
'There was a rendering error when computing the text of the comment to post '
112-
"on the PR. Please see the traceback, in particular if you're using a custom "
113-
'template.'
115+
'on the PR. Please see the traceback for more information.'
114116
)
115117
raise CoreProcessingException from e
116118

@@ -125,7 +127,7 @@ def _generate_annotations(self):
125127
log.info('Generating annotations for missing lines.')
126128
annotations = diff_grouper.get_diff_missing_groups(coverage=self.coverage, diff_coverage=self.diff_coverage)
127129
formatted_annotations = groups.create_missing_coverage_annotations(
128-
annotation_type=self.config.ANNOTATION_TYPE,
130+
annotation_type=self.config.ANNOTATION_TYPE.value,
129131
annotations=annotations,
130132
)
131133

@@ -136,7 +138,7 @@ def _generate_annotations(self):
136138
)
137139
formatted_annotations.extend(
138140
groups.create_missing_coverage_annotations(
139-
annotation_type=self.config.ANNOTATION_TYPE,
141+
annotation_type=self.config.ANNOTATION_TYPE.value,
140142
annotations=branch_annotations,
141143
branch=True,
142144
)
@@ -155,12 +157,13 @@ def _generate_annotations(self):
155157
print(reset, end='')
156158

157159
# Save to file
158-
# TODO: Take the folder path instead of the file path
160+
file_name = f'{self.github.pr_number}-annotations.json'
159161
if self.config.ANNOTATIONS_OUTPUT_PATH:
160-
log.info('Writing annotations to file.')
161-
with self.config.ANNOTATIONS_OUTPUT_PATH.open('w+') as annotations_file:
162+
log.info('Writing annotations to file %s', file_name)
163+
with self.config.ANNOTATIONS_OUTPUT_PATH.joinpath(file_name).open('w+') as annotations_file:
162164
json.dump(formatted_annotations, annotations_file, cls=groups.AnnotationEncoder)
163165

166+
# Write to branch
164167
if self.config.ANNOTATIONS_DATA_BRANCH:
165168
log.info('Writing annotations to branch.')
166169
self.github.write_annotations_to_branch(annotations=formatted_annotations)

docs/annotations.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ By default, these annotations are written to the console, but you can also choos
88

99
## Storing Annotations
1010

11-
1. **To a File**:
12-
- Set the file path in `ANNOTATIONS_OUTPUT_PATH`.
11+
1. **To a Folder**:
12+
- Set the folder path in `ANNOTATIONS_OUTPUT_PATH`.
1313

1414
2. **To a Branch**:
1515
- Set the branch name in `ANNOTATIONS_DATA_BRANCH`.

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,14 @@ def _(code: str, has_branches: bool = True) -> Coverage:
9292
)
9393
if any(
9494
x in line
95-
for x in {
95+
for x in (
9696
'line covered',
9797
'line missing',
9898
'line excluded',
9999
'branch covered',
100100
'branch missing',
101101
'branch partial',
102-
}
102+
)
103103
):
104104
coverage_obj.files[current_file].info.num_statements += 1
105105
coverage_obj.info.num_statements += 1

tests/coverage/test_base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from codecov.coverage import Coverage, DiffCoverage, FileDiffCoverage
1212
from codecov.coverage.base import BaseCoverage
13+
from codecov.exceptions import ConfigurationException
1314

1415

1516
class BaseCoverageDemo(BaseCoverage):
@@ -54,10 +55,10 @@ def test_get_coverage_info(self, coverage_json):
5455

5556
with patch('pathlib.Path.open') as mock_open:
5657
mock_open.return_value.__enter__.return_value.read.return_value = b'invalid json'
57-
with pytest.raises(json.JSONDecodeError):
58+
with pytest.raises(ConfigurationException):
5859
BaseCoverageDemo().get_coverage_info(pathlib.Path(tempfile.mkstemp(suffix='.json')[1]))
5960

60-
with pytest.raises(FileNotFoundError):
61+
with pytest.raises(ConfigurationException):
6162
BaseCoverageDemo().get_coverage_info(pathlib.Path('path/to/file.json'))
6263

6364
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)