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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python: ['3.9', '3.10', '3.11', '3.12', '3.13']
python: ['3.10', '3.11', '3.12', '3.13']

env:
PYTHON: ${{ matrix.python }}
Expand Down
5 changes: 2 additions & 3 deletions pydantic_settings/sources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
from abc import ABC, abstractmethod
from dataclasses import asdict, is_dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional, cast
from typing import TYPE_CHECKING, Any, cast, get_args

from pydantic import AliasChoices, AliasPath, BaseModel, TypeAdapter
from pydantic._internal._typing_extra import ( # type: ignore[attr-defined]
get_origin,
)
from pydantic._internal._utils import is_model_class
from pydantic.fields import FieldInfo
from typing_extensions import get_args
from typing_inspection import typing_objects
from typing_inspection.introspection import is_union_origin

Expand All @@ -36,7 +35,7 @@

def get_subcommand(
model: PydanticModel, is_required: bool = True, cli_exit_on_error: bool | None = None
) -> Optional[PydanticModel]:
) -> PydanticModel | None:
"""
Get the subcommand from a model.

Expand Down
4 changes: 2 additions & 2 deletions pydantic_settings/sources/providers/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import json
from collections.abc import Mapping
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

from ..utils import parse_env_vars
from .env import EnvSettingsSource
Expand Down Expand Up @@ -57,7 +57,7 @@ def __init__(
env_parse_enums=env_parse_enums,
)

def _load_env_vars(self) -> Mapping[str, Optional[str]]:
def _load_env_vars(self) -> Mapping[str, str | None]:
response = self._secretsmanager_client.get_secret_value(SecretId=self._secret_id) # type: ignore

return parse_env_vars(
Expand Down
6 changes: 3 additions & 3 deletions pydantic_settings/sources/providers/azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations as _annotations

from collections.abc import Iterator, Mapping
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

from pydantic.alias_generators import to_snake
from pydantic.fields import FieldInfo
Expand Down Expand Up @@ -37,7 +37,7 @@ def import_azure_key_vault() -> None:
) from e


class AzureKeyVaultMapping(Mapping[str, Optional[str]]):
class AzureKeyVaultMapping(Mapping[str, str | None]):
_loaded_secrets: dict[str, str | None]
_secret_client: SecretClient
_secret_names: list[str]
Expand Down Expand Up @@ -121,7 +121,7 @@ def __init__(
env_parse_enums=env_parse_enums,
)

def _load_env_vars(self) -> Mapping[str, Optional[str]]:
def _load_env_vars(self) -> Mapping[str, str | None]:
secret_client = SecretClient(vault_url=self._url, credential=self._credential)
return AzureKeyVaultMapping(
secret_client=secret_client,
Expand Down
38 changes: 17 additions & 21 deletions pydantic_settings/sources/providers/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
_SubParsersAction,
)
from collections import defaultdict
from collections.abc import Mapping, Sequence
from collections.abc import Callable, Mapping, Sequence
from enum import Enum
from functools import cached_property
from textwrap import dedent
Expand All @@ -25,14 +25,13 @@
TYPE_CHECKING,
Annotated,
Any,
Callable,
Generic,
Literal,
NoReturn,
Optional,
TypeVar,
Union,
cast,
get_args,
get_origin,
overload,
)

Expand All @@ -43,7 +42,6 @@
from pydantic.dataclasses import is_pydantic_dataclass
from pydantic.fields import FieldInfo
from pydantic_core import PydanticUndefined
from typing_extensions import get_args, get_origin
from typing_inspection import typing_objects
from typing_inspection.introspection import is_union_origin

Expand Down Expand Up @@ -95,21 +93,21 @@ class _CliArg(BaseModel):
arg_prefix: str
case_sensitive: bool
hide_none_type: bool
kebab_case: Optional[Union[bool, Literal['all', 'no_enums']]]
enable_decoding: Optional[bool]
kebab_case: bool | Literal['all', 'no_enums'] | None
enable_decoding: bool | None
env_prefix_len: int
args: list[str] = []
kwargs: dict[str, Any] = {}

_alias_names: tuple[str, ...] = PrivateAttr(())
_alias_paths: dict[str, Optional[int]] = PrivateAttr({})
_alias_paths: dict[str, int | None] = PrivateAttr({})
_is_alias_path_only: bool = PrivateAttr(False)
_field_info: FieldInfo = PrivateAttr()

def __init__(
self,
field_info: FieldInfo,
parser_map: defaultdict[str | FieldInfo, dict[Optional[int] | str, _CliArg]],
parser_map: defaultdict[str | FieldInfo, dict[int | None | str, _CliArg]],
**values: Any,
) -> None:
super().__init__(**values)
Expand All @@ -132,12 +130,12 @@ def __init__(
parser_map[self.field_info][index] = parser_map[alias_path_dest][index]

@classmethod
def get_kebab_case(cls, name: str, kebab_case: Optional[Union[bool, Literal['all', 'no_enums']]]) -> str:
def get_kebab_case(cls, name: str, kebab_case: bool | Literal['all', 'no_enums'] | None) -> str:
return name.replace('_', '-') if kebab_case not in (None, False) else name

@classmethod
def get_enum_names(
cls, annotation: type[Any], kebab_case: Optional[Union[bool, Literal['all', 'no_enums']]]
cls, annotation: type[Any], kebab_case: bool | Literal['all', 'no_enums'] | None
) -> tuple[str, ...]:
enum_names: tuple[str, ...] = ()
annotation = _strip_annotated(annotation)
Expand All @@ -157,7 +155,7 @@ def field_info(self) -> FieldInfo:
return self._field_info

@cached_property
def subcommand_dest(self) -> Optional[str]:
def subcommand_dest(self) -> str | None:
return f'{self.arg_prefix}:subcommand' if _CliSubCommand in self.field_info.metadata else None

@cached_property
Expand Down Expand Up @@ -206,7 +204,7 @@ def alias_names(self) -> tuple[str, ...]:
return self._alias_names

@cached_property
def alias_paths(self) -> dict[str, Optional[int]]:
def alias_paths(self) -> dict[str, int | None]:
return self._alias_paths

@cached_property
Expand Down Expand Up @@ -236,7 +234,7 @@ def is_no_decode(self) -> bool:


T = TypeVar('T')
CliSubCommand = Annotated[Union[T, None], _CliSubCommand]
CliSubCommand = Annotated[T | None, _CliSubCommand]
CliPositionalArg = Annotated[T, _CliPositionalArg]
_CliBoolFlag = TypeVar('_CliBoolFlag', bound=bool)
CliImplicitFlag = Annotated[_CliBoolFlag, _CliImplicitFlag]
Expand Down Expand Up @@ -585,9 +583,7 @@ def _is_nested_alias_path_only_workaround(
return True
return False

def _get_merge_parsed_list_types(
self, parsed_list: list[str], field_name: str
) -> tuple[Optional[type], Optional[type]]:
def _get_merge_parsed_list_types(self, parsed_list: list[str], field_name: str) -> tuple[type | None, type | None]:
merge_type = self._cli_dict_args.get(field_name, list)
if (
merge_type is list
Expand All @@ -606,7 +602,7 @@ def _get_merge_parsed_list_types(

def _merged_list_to_str(self, merged_list: list[str], field_name: str) -> str:
decode_list: list[str] = []
is_use_decode: Optional[bool] = None
is_use_decode: bool | None = None
cli_arg_map = self._parser_map.get(field_name, {})
for index, item in enumerate(merged_list):
cli_arg = cli_arg_map.get(index)
Expand Down Expand Up @@ -867,7 +863,7 @@ def _parse_known_args(*args: Any, **kwargs: Any) -> Namespace:
self._add_subparsers = self._connect_parser_method(add_subparsers_method, 'add_subparsers_method')
self._formatter_class = formatter_class
self._cli_dict_args: dict[str, type[Any] | None] = {}
self._parser_map: defaultdict[str | FieldInfo, dict[Optional[int] | str, _CliArg]] = defaultdict(dict)
self._parser_map: defaultdict[str | FieldInfo, dict[int | None | str, _CliArg]] = defaultdict(dict)
self._add_parser_args(
parser=self.root_parser,
model=self.settings_cls,
Expand All @@ -892,7 +888,7 @@ def _add_parser_args(
is_model_suppressed: bool = False,
) -> ArgumentParser:
subparsers: Any = None
alias_path_args: dict[str, Optional[int]] = {}
alias_path_args: dict[str, int | None] = {}
# Ignore model default if the default is a model and not a subclass of the current model.
model_default = (
None
Expand Down Expand Up @@ -1159,7 +1155,7 @@ def _add_parser_submodels(
def _add_parser_alias_paths(
self,
parser: Any,
alias_path_args: dict[str, Optional[int]],
alias_path_args: dict[str, int | None],
added_args: list[str],
arg_prefix: str,
subcommand_prefix: str,
Expand Down
3 changes: 2 additions & 1 deletion pydantic_settings/sources/providers/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
from typing import (
TYPE_CHECKING,
Any,
get_args,
get_origin,
)

from pydantic import Json, TypeAdapter, ValidationError
from pydantic._internal._utils import deep_update, is_model_class
from pydantic.dataclasses import is_pydantic_dataclass
from pydantic.fields import FieldInfo
from typing_extensions import get_args, get_origin
from typing_inspection.introspection import is_union_origin

from ...utils import _lenient_issubclass
Expand Down
6 changes: 3 additions & 3 deletions pydantic_settings/sources/providers/gcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections.abc import Iterator, Mapping
from functools import cached_property
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

from .env import EnvSettingsSource

Expand Down Expand Up @@ -33,7 +33,7 @@ def import_gcp_secret_manager() -> None:
) from e


class GoogleSecretManagerMapping(Mapping[str, Optional[str]]):
class GoogleSecretManagerMapping(Mapping[str, str | None]):
_loaded_secrets: dict[str, str | None]
_secret_client: SecretManagerServiceClient

Expand Down Expand Up @@ -140,7 +140,7 @@ def __init__(
env_parse_enums=env_parse_enums,
)

def _load_env_vars(self) -> Mapping[str, Optional[str]]:
def _load_env_vars(self) -> Mapping[str, str | None]:
return GoogleSecretManagerMapping(
self._secret_client, project_id=self._project_id, case_sensitive=self.case_sensitive
)
Expand Down
24 changes: 12 additions & 12 deletions pydantic_settings/sources/providers/nested_secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from functools import reduce
from glob import iglob
from pathlib import Path
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
from typing import TYPE_CHECKING, Any, Literal, Optional

from ...exceptions import SettingsError
from ...utils import path_type_label
Expand All @@ -23,17 +23,17 @@
class NestedSecretsSettingsSource(EnvSettingsSource):
def __init__(
self,
file_secret_settings: Union[PydanticBaseSettingsSource, SecretsSettingsSource],
file_secret_settings: PydanticBaseSettingsSource | SecretsSettingsSource,
secrets_dir: Optional['PathType'] = None,
secrets_dir_missing: Optional[Literal['ok', 'warn', 'error']] = None,
secrets_dir_max_size: Optional[int] = None,
secrets_case_sensitive: Optional[bool] = None,
secrets_prefix: Optional[str] = None,
secrets_nested_delimiter: Optional[str] = None,
secrets_nested_subdir: Optional[bool] = None,
secrets_dir_missing: Literal['ok', 'warn', 'error'] | None = None,
secrets_dir_max_size: int | None = None,
secrets_case_sensitive: bool | None = None,
secrets_prefix: str | None = None,
secrets_nested_delimiter: str | None = None,
secrets_nested_subdir: bool | None = None,
# args for compatibility with SecretsSettingsSource, don't use directly
case_sensitive: Optional[bool] = None,
env_prefix: Optional[str] = None,
case_sensitive: bool | None = None,
env_prefix: str | None = None,
) -> None:
# We allow the first argument to be settings_cls like original
# SecretsSettingsSource. However, it is recommended to pass
Expand All @@ -46,7 +46,7 @@ def __init__(
)
# config options
conf = settings_cls.model_config
self.secrets_dir: Optional[PathType] = first_not_none(
self.secrets_dir: PathType | None = first_not_none(
getattr(file_secret_settings, 'secrets_dir', None),
secrets_dir,
conf.get('secrets_dir'),
Expand Down Expand Up @@ -79,7 +79,7 @@ def __init__(
)

# nested options
self.secrets_nested_delimiter: Optional[str] = first_not_none(
self.secrets_nested_delimiter: str | None = first_not_none(
secrets_nested_delimiter,
conf.get('secrets_nested_delimiter'),
conf.get('env_nested_delimiter'),
Expand Down
8 changes: 4 additions & 4 deletions pydantic_settings/sources/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

from collections.abc import Sequence
from pathlib import Path
from typing import TYPE_CHECKING, Any, Union
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from pydantic._internal._dataclasses import PydanticDataclass
from pydantic.main import BaseModel

PydanticModel = Union[PydanticDataclass, BaseModel]
PydanticModel = PydanticDataclass | BaseModel
else:
PydanticModel = Any

Expand All @@ -31,8 +31,8 @@ class ForceDecode:
pass


DotenvType = Union[Path, str, Sequence[Union[Path, str]]]
PathType = Union[Path, str, Sequence[Union[Path, str]]]
DotenvType = Path | str | Sequence[Path | str]
PathType = Path | str | Sequence[Path | str]
DEFAULT_PATH: PathType = Path('')

# This is used as default value for `_env_file` in the `BaseSettings` class and
Expand Down
7 changes: 3 additions & 4 deletions pydantic_settings/sources/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
from collections.abc import Mapping, Sequence
from dataclasses import is_dataclass
from enum import Enum
from typing import Any, Optional, cast
from typing import Any, cast, get_args, get_origin

from pydantic import BaseModel, Json, RootModel, Secret
from pydantic._internal._utils import is_model_class
from pydantic.dataclasses import is_pydantic_dataclass
from typing_extensions import get_args, get_origin
from typing_inspection import typing_objects

from ..exceptions import SettingsError
Expand Down Expand Up @@ -120,7 +119,7 @@ def _strip_annotated(annotation: Any) -> Any:
return annotation


def _annotation_enum_val_to_name(annotation: type[Any] | None, value: Any) -> Optional[str]:
def _annotation_enum_val_to_name(annotation: type[Any] | None, value: Any) -> str | None:
for type_ in (annotation, get_origin(annotation), *get_args(annotation)):
if _lenient_issubclass(type_, Enum):
if value in tuple(val.value for val in type_):
Expand Down Expand Up @@ -149,7 +148,7 @@ def _get_model_fields(model_cls: type[Any]) -> dict[str, Any]:
def _get_alias_names(
field_name: str,
field_info: Any,
alias_path_args: Optional[dict[str, Optional[int]]] = None,
alias_path_args: dict[str, int | None] | None = None,
case_sensitive: bool = True,
) -> tuple[tuple[str, ...], bool]:
"""Get alias names for a field, handling alias paths and case sensitivity."""
Expand Down
Loading