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
63 changes: 54 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ jobs:
run: |
uv sync --group testing --all-extras

- run: mkdir coverage

- name: test
run: make test
env:
COVERAGE_FILE: .coverage.${{ runner.os }}-py${{ matrix.python }}
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python }}
CONTEXT: ${{ runner.os }}-py${{ matrix.python }}

- name: uninstall deps
Expand All @@ -69,20 +71,63 @@ jobs:
- name: test without deps
run: make test
env:
COVERAGE_FILE: .coverage.${{ runner.os }}-py${{ matrix.python }}-without-deps
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python }}-without-deps
CONTEXT: ${{ runner.os }}-py${{ matrix.python }}-without-deps

- run: uv run coverage combine
- run: uv run coverage xml

- uses: codecov/codecov-action@v4
- name: store coverage files
uses: actions/upload-artifact@v4
with:
file: ./coverage.xml
env_vars: PYTHON,OS
name: coverage-${{ matrix.python }}-${{ runner.os }}
path: coverage
include-hidden-files: true

coverage:
runs-on: ubuntu-latest
needs: [test]
steps:
- uses: actions/checkout@v4
with:
# needed for diff-cover
fetch-depth: 0

- name: get coverage files
uses: actions/download-artifact@v4
with:
merge-multiple: true
path: coverage

- uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- run: uv sync --group testing --all-extras

- run: uv run coverage combine coverage

- run: uv run coverage html --show-contexts --title "Pydantic Settings coverage for ${{ github.sha }}"

- name: Store coverage html
uses: actions/upload-artifact@v4
with:
name: coverage-html
path: htmlcov
include-hidden-files: true

- run: uv run coverage xml

- run: uv run diff-cover coverage.xml --html-report index.html

- name: Store diff coverage html
uses: actions/upload-artifact@v4
with:
name: diff-coverage-html
path: index.html

- run: uv run coverage report --fail-under 98

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

outputs:
Expand Down
2 changes: 1 addition & 1 deletion pydantic_settings/sources/providers/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def import_aws_secrets_manager() -> None:
try:
from boto3 import client as boto3_client
from mypy_boto3_secretsmanager.client import SecretsManagerClient
except ImportError as e:
except ImportError as e: # pragma: no cover
raise ImportError(
'AWS Secrets Manager dependencies are not installed, run `pip install pydantic-settings[aws-secrets-manager]`'
) from e
Expand Down
2 changes: 1 addition & 1 deletion pydantic_settings/sources/providers/azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def import_azure_key_vault() -> None:
from azure.core.credentials import TokenCredential
from azure.core.exceptions import ResourceNotFoundError
from azure.keyvault.secrets import SecretClient
except ImportError as e:
except ImportError as e: # pragma: no cover
raise ImportError(
'Azure Key Vault dependencies are not installed, run `pip install pydantic-settings[azure-key-vault]`'
) from e
Expand Down
2 changes: 1 addition & 1 deletion pydantic_settings/sources/providers/gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def import_gcp_secret_manager() -> None:
from google.auth import default as google_auth_default
from google.auth.credentials import Credentials
from google.cloud.secretmanager import SecretManagerServiceClient
except ImportError as e:
except ImportError as e: # pragma: no cover
raise ImportError(
'GCP Secret Manager dependencies are not installed, run `pip install pydantic-settings[gcp-secret-manager]`'
) from e
Expand Down
2 changes: 1 addition & 1 deletion pydantic_settings/sources/providers/toml.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def import_toml() -> None:
return
try:
import tomli
except ImportError as e:
except ImportError as e: # pragma: no cover
raise ImportError('tomli is not installed, run `pip install pydantic-settings[toml]`') from e
else:
if tomllib is not None:
Expand Down
2 changes: 1 addition & 1 deletion pydantic_settings/sources/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def _annotation_is_complex(annotation: type[Any] | None, metadata: list[Any]) ->
if annotation is not None and _lenient_issubclass(annotation, RootModel) and annotation is not RootModel:
annotation = cast('type[RootModel[Any]]', annotation)
root_annotation = annotation.model_fields['root'].annotation
if root_annotation is not None:
if root_annotation is not None: # pragma: no branch
annotation = root_annotation

if any(isinstance(md, Json) for md in metadata): # type: ignore[misc]
Expand Down
2 changes: 1 addition & 1 deletion pydantic_settings/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def path_type_label(p: Path) -> str:
if method(p):
return name

return 'unknown'
return 'unknown' # pragma: no cover


# TODO remove and replace usage by `isinstance(cls, type) and issubclass(cls, class_or_tuple)`
Expand Down
23 changes: 20 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ testing = [
"pytest-mock",
"pytest-pretty",
"moto[secretsmanager]",
"diff-cover>=9.2.0",
]

[tool.pytest.ini_options]
Expand All @@ -89,19 +90,35 @@ filterwarnings = [
'ignore::DeprecationWarning:botocore.*:',
]

# https://coverage.readthedocs.io/en/latest/config.html#run
[tool.coverage.run]
source = ['pydantic_settings']
include = [
"pydantic_settings/**/*.py",
"tests/**/*.py",
]
branch = true
context = '${CONTEXT}'

# https://coverage.readthedocs.io/en/latest/config.html#report
[tool.coverage.report]
skip_covered = true
show_missing = true
ignore_errors = true
precision = 2
exclude_lines = [
'pragma: no cover',
'raise NotImplementedError',
'raise NotImplemented',
'if TYPE_CHECKING:',
'if typing.TYPE_CHECKING:',
'@overload',
'@deprecated',
'@typing.overload',
'@abstractmethod',
'\(Protocol\):$',
'typing.assert_never',
'$\s*assert_never\(',
'if __name__ == .__main__.:',
'except ImportError as _import_error:',
'$\s*pass$',
]

[tool.coverage.paths]
Expand Down
8 changes: 8 additions & 0 deletions tests/test_source_aws_secrets_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@
class TestAWSSecretsManagerSettingsSource:
"""Test AWSSecretsManagerSettingsSource."""

@mock_aws
def test_repr(self) -> None:
client = boto3.client('secretsmanager')
client.create_secret(Name='test-secret', SecretString='{}')

source = AWSSecretsManagerSettingsSource(BaseSettings, 'test-secret')
assert repr(source) == "AWSSecretsManagerSettingsSource(secret_id='test-secret', env_nested_delimiter='--')"

@mock_aws
def test___init__(self) -> None:
"""Test __init__."""
Expand Down
57 changes: 55 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.