Skip to content

Commit 67f9ae1

Browse files
authored
Fix coverage report (#619)
1 parent 7a04132 commit 67f9ae1

File tree

10 files changed

+143
-20
lines changed

10 files changed

+143
-20
lines changed

.github/workflows/ci.yml

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,12 @@ jobs:
5757
run: |
5858
uv sync --group testing --all-extras
5959
60+
- run: mkdir coverage
61+
6062
- name: test
6163
run: make test
6264
env:
63-
COVERAGE_FILE: .coverage.${{ runner.os }}-py${{ matrix.python }}
65+
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python }}
6466
CONTEXT: ${{ runner.os }}-py${{ matrix.python }}
6567

6668
- name: uninstall deps
@@ -69,20 +71,63 @@ jobs:
6971
- name: test without deps
7072
run: make test
7173
env:
72-
COVERAGE_FILE: .coverage.${{ runner.os }}-py${{ matrix.python }}-without-deps
74+
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python }}-without-deps
7375
CONTEXT: ${{ runner.os }}-py${{ matrix.python }}-without-deps
7476

75-
- run: uv run coverage combine
76-
- run: uv run coverage xml
77-
78-
- uses: codecov/codecov-action@v4
77+
- name: store coverage files
78+
uses: actions/upload-artifact@v4
7979
with:
80-
file: ./coverage.xml
81-
env_vars: PYTHON,OS
80+
name: coverage-${{ matrix.python }}-${{ runner.os }}
81+
path: coverage
82+
include-hidden-files: true
83+
84+
coverage:
85+
runs-on: ubuntu-latest
86+
needs: [test]
87+
steps:
88+
- uses: actions/checkout@v4
89+
with:
90+
# needed for diff-cover
91+
fetch-depth: 0
92+
93+
- name: get coverage files
94+
uses: actions/download-artifact@v4
95+
with:
96+
merge-multiple: true
97+
path: coverage
98+
99+
- uses: astral-sh/setup-uv@v5
100+
with:
101+
enable-cache: true
102+
103+
- run: uv sync --group testing --all-extras
104+
105+
- run: uv run coverage combine coverage
106+
107+
- run: uv run coverage html --show-contexts --title "Pydantic Settings coverage for ${{ github.sha }}"
108+
109+
- name: Store coverage html
110+
uses: actions/upload-artifact@v4
111+
with:
112+
name: coverage-html
113+
path: htmlcov
114+
include-hidden-files: true
115+
116+
- run: uv run coverage xml
117+
118+
- run: uv run diff-cover coverage.xml --html-report index.html
119+
120+
- name: Store diff coverage html
121+
uses: actions/upload-artifact@v4
122+
with:
123+
name: diff-coverage-html
124+
path: index.html
125+
126+
- run: uv run coverage report --fail-under 98
82127

83128
check: # This job does nothing and is only used for the branch protection
84129
if: always()
85-
needs: [lint, test]
130+
needs: [lint, test, coverage]
86131
runs-on: ubuntu-latest
87132

88133
outputs:

pydantic_settings/sources/providers/aws.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def import_aws_secrets_manager() -> None:
2121
try:
2222
from boto3 import client as boto3_client
2323
from mypy_boto3_secretsmanager.client import SecretsManagerClient
24-
except ImportError as e:
24+
except ImportError as e: # pragma: no cover
2525
raise ImportError(
2626
'AWS Secrets Manager dependencies are not installed, run `pip install pydantic-settings[aws-secrets-manager]`'
2727
) from e

pydantic_settings/sources/providers/azure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def import_azure_key_vault() -> None:
3030
from azure.core.credentials import TokenCredential
3131
from azure.core.exceptions import ResourceNotFoundError
3232
from azure.keyvault.secrets import SecretClient
33-
except ImportError as e:
33+
except ImportError as e: # pragma: no cover
3434
raise ImportError(
3535
'Azure Key Vault dependencies are not installed, run `pip install pydantic-settings[azure-key-vault]`'
3636
) from e

pydantic_settings/sources/providers/gcp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def import_gcp_secret_manager() -> None:
2727
from google.auth import default as google_auth_default
2828
from google.auth.credentials import Credentials
2929
from google.cloud.secretmanager import SecretManagerServiceClient
30-
except ImportError as e:
30+
except ImportError as e: # pragma: no cover
3131
raise ImportError(
3232
'GCP Secret Manager dependencies are not installed, run `pip install pydantic-settings[gcp-secret-manager]`'
3333
) from e

pydantic_settings/sources/providers/toml.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def import_toml() -> None:
3333
return
3434
try:
3535
import tomli
36-
except ImportError as e:
36+
except ImportError as e: # pragma: no cover
3737
raise ImportError('tomli is not installed, run `pip install pydantic-settings[toml]`') from e
3838
else:
3939
if tomllib is not None:

pydantic_settings/sources/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def _annotation_is_complex(annotation: type[Any] | None, metadata: list[Any]) ->
4646
if annotation is not None and _lenient_issubclass(annotation, RootModel) and annotation is not RootModel:
4747
annotation = cast('type[RootModel[Any]]', annotation)
4848
root_annotation = annotation.model_fields['root'].annotation
49-
if root_annotation is not None:
49+
if root_annotation is not None: # pragma: no branch
5050
annotation = root_annotation
5151

5252
if any(isinstance(md, Json) for md in metadata): # type: ignore[misc]

pydantic_settings/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def path_type_label(p: Path) -> str:
2626
if method(p):
2727
return name
2828

29-
return 'unknown'
29+
return 'unknown' # pragma: no cover
3030

3131

3232
# TODO remove and replace usage by `isinstance(cls, type) and issubclass(cls, class_or_tuple)`

pyproject.toml

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ testing = [
7979
"pytest-mock",
8080
"pytest-pretty",
8181
"moto[secretsmanager]",
82+
"diff-cover>=9.2.0",
8283
]
8384

8485
[tool.pytest.ini_options]
@@ -89,19 +90,35 @@ filterwarnings = [
8990
'ignore::DeprecationWarning:botocore.*:',
9091
]
9192

93+
# https://coverage.readthedocs.io/en/latest/config.html#run
9294
[tool.coverage.run]
93-
source = ['pydantic_settings']
95+
include = [
96+
"pydantic_settings/**/*.py",
97+
"tests/**/*.py",
98+
]
9499
branch = true
95-
context = '${CONTEXT}'
96100

101+
# https://coverage.readthedocs.io/en/latest/config.html#report
97102
[tool.coverage.report]
103+
skip_covered = true
104+
show_missing = true
105+
ignore_errors = true
98106
precision = 2
99107
exclude_lines = [
100108
'pragma: no cover',
101109
'raise NotImplementedError',
102-
'raise NotImplemented',
103110
'if TYPE_CHECKING:',
111+
'if typing.TYPE_CHECKING:',
104112
'@overload',
113+
'@deprecated',
114+
'@typing.overload',
115+
'@abstractmethod',
116+
'\(Protocol\):$',
117+
'typing.assert_never',
118+
'$\s*assert_never\(',
119+
'if __name__ == .__main__.:',
120+
'except ImportError as _import_error:',
121+
'$\s*pass$',
105122
]
106123

107124
[tool.coverage.paths]

tests/test_source_aws_secrets_manager.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@
4343
class TestAWSSecretsManagerSettingsSource:
4444
"""Test AWSSecretsManagerSettingsSource."""
4545

46+
@mock_aws
47+
def test_repr(self) -> None:
48+
client = boto3.client('secretsmanager')
49+
client.create_secret(Name='test-secret', SecretString='{}')
50+
51+
source = AWSSecretsManagerSettingsSource(BaseSettings, 'test-secret')
52+
assert repr(source) == "AWSSecretsManagerSettingsSource(secret_id='test-secret', env_nested_delimiter='--')"
53+
4654
@mock_aws
4755
def test___init__(self) -> None:
4856
"""Test __init__."""

uv.lock

Lines changed: 55 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)