Загрузить файлы в «venv/Lib/site-packages/pydantic_settings/sources/providers»
This commit is contained in:
@@ -0,0 +1,45 @@
|
|||||||
|
"""Package containing individual source implementations."""
|
||||||
|
|
||||||
|
from .aws import AWSSecretsManagerSettingsSource
|
||||||
|
from .azure import AzureKeyVaultSettingsSource
|
||||||
|
from .cli import (
|
||||||
|
CliDualFlag,
|
||||||
|
CliExplicitFlag,
|
||||||
|
CliImplicitFlag,
|
||||||
|
CliMutuallyExclusiveGroup,
|
||||||
|
CliPositionalArg,
|
||||||
|
CliSettingsSource,
|
||||||
|
CliSubCommand,
|
||||||
|
CliSuppress,
|
||||||
|
CliToggleFlag,
|
||||||
|
)
|
||||||
|
from .dotenv import DotEnvSettingsSource
|
||||||
|
from .env import EnvSettingsSource
|
||||||
|
from .gcp import GoogleSecretManagerSettingsSource
|
||||||
|
from .json import JsonConfigSettingsSource
|
||||||
|
from .pyproject import PyprojectTomlConfigSettingsSource
|
||||||
|
from .secrets import SecretsSettingsSource
|
||||||
|
from .toml import TomlConfigSettingsSource
|
||||||
|
from .yaml import YamlConfigSettingsSource
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'AWSSecretsManagerSettingsSource',
|
||||||
|
'AzureKeyVaultSettingsSource',
|
||||||
|
'CliExplicitFlag',
|
||||||
|
'CliImplicitFlag',
|
||||||
|
'CliToggleFlag',
|
||||||
|
'CliDualFlag',
|
||||||
|
'CliMutuallyExclusiveGroup',
|
||||||
|
'CliPositionalArg',
|
||||||
|
'CliSettingsSource',
|
||||||
|
'CliSubCommand',
|
||||||
|
'CliSuppress',
|
||||||
|
'DotEnvSettingsSource',
|
||||||
|
'EnvSettingsSource',
|
||||||
|
'GoogleSecretManagerSettingsSource',
|
||||||
|
'JsonConfigSettingsSource',
|
||||||
|
'PyprojectTomlConfigSettingsSource',
|
||||||
|
'SecretsSettingsSource',
|
||||||
|
'TomlConfigSettingsSource',
|
||||||
|
'YamlConfigSettingsSource',
|
||||||
|
]
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
from __future__ import annotations as _annotations # important for BaseSettings import to work
|
||||||
|
|
||||||
|
import json
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ..utils import parse_env_vars
|
||||||
|
from .env import EnvSettingsSource
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pydantic_settings.main import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
boto3_client = None
|
||||||
|
SecretsManagerClient = None
|
||||||
|
|
||||||
|
|
||||||
|
def import_aws_secrets_manager() -> None:
|
||||||
|
global boto3_client
|
||||||
|
global SecretsManagerClient
|
||||||
|
|
||||||
|
try:
|
||||||
|
from boto3 import client as boto3_client
|
||||||
|
from types_boto3_secretsmanager.client import SecretsManagerClient
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class AWSSecretsManagerSettingsSource(EnvSettingsSource):
|
||||||
|
_secret_id: str
|
||||||
|
_secretsmanager_client: SecretsManagerClient # type: ignore
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
settings_cls: type[BaseSettings],
|
||||||
|
secret_id: str,
|
||||||
|
region_name: str | None = None,
|
||||||
|
endpoint_url: str | None = None,
|
||||||
|
case_sensitive: bool | None = True,
|
||||||
|
env_prefix: str | None = None,
|
||||||
|
env_nested_delimiter: str | None = '--',
|
||||||
|
env_parse_none_str: str | None = None,
|
||||||
|
env_parse_enums: bool | None = None,
|
||||||
|
version_id: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
import_aws_secrets_manager()
|
||||||
|
self._secretsmanager_client = boto3_client('secretsmanager', region_name=region_name, endpoint_url=endpoint_url) # type: ignore
|
||||||
|
self._secret_id = secret_id
|
||||||
|
self._version_id = version_id
|
||||||
|
super().__init__(
|
||||||
|
settings_cls,
|
||||||
|
case_sensitive=case_sensitive,
|
||||||
|
env_prefix=env_prefix,
|
||||||
|
env_nested_delimiter=env_nested_delimiter,
|
||||||
|
env_ignore_empty=False,
|
||||||
|
env_parse_none_str=env_parse_none_str,
|
||||||
|
env_parse_enums=env_parse_enums,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _load_env_vars(self) -> Mapping[str, str | None]:
|
||||||
|
request = {'SecretId': self._secret_id}
|
||||||
|
|
||||||
|
if self._version_id:
|
||||||
|
request['VersionId'] = self._version_id
|
||||||
|
|
||||||
|
response = self._secretsmanager_client.get_secret_value(**request) # type: ignore
|
||||||
|
|
||||||
|
return parse_env_vars(
|
||||||
|
json.loads(response['SecretString']),
|
||||||
|
self.case_sensitive,
|
||||||
|
self.env_ignore_empty,
|
||||||
|
self.env_parse_none_str,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f'{self.__class__.__name__}(secret_id={self._secret_id!r}, '
|
||||||
|
f'env_nested_delimiter={self.env_nested_delimiter!r})'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'AWSSecretsManagerSettingsSource',
|
||||||
|
]
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
"""Azure Key Vault settings source."""
|
||||||
|
|
||||||
|
from __future__ import annotations as _annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterator, Mapping
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from pydantic.alias_generators import to_snake
|
||||||
|
from pydantic.fields import FieldInfo
|
||||||
|
|
||||||
|
from .env import EnvSettingsSource
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from azure.core.credentials import TokenCredential
|
||||||
|
from azure.core.exceptions import ResourceNotFoundError
|
||||||
|
from azure.keyvault.secrets import SecretClient
|
||||||
|
|
||||||
|
from pydantic_settings.main import BaseSettings
|
||||||
|
else:
|
||||||
|
TokenCredential = None
|
||||||
|
ResourceNotFoundError = None
|
||||||
|
SecretClient = None
|
||||||
|
|
||||||
|
|
||||||
|
def import_azure_key_vault() -> None:
|
||||||
|
global TokenCredential
|
||||||
|
global SecretClient
|
||||||
|
global ResourceNotFoundError
|
||||||
|
|
||||||
|
try:
|
||||||
|
from azure.core.credentials import TokenCredential
|
||||||
|
from azure.core.exceptions import ResourceNotFoundError
|
||||||
|
from azure.keyvault.secrets import SecretClient
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class AzureKeyVaultMapping(Mapping[str, str | None]):
|
||||||
|
_loaded_secrets: dict[str, str | None]
|
||||||
|
_secret_client: SecretClient
|
||||||
|
_secret_names: list[str]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
secret_client: SecretClient,
|
||||||
|
case_sensitive: bool,
|
||||||
|
snake_case_conversion: bool,
|
||||||
|
env_prefix: str | None,
|
||||||
|
) -> None:
|
||||||
|
self._loaded_secrets = {}
|
||||||
|
self._secret_client = secret_client
|
||||||
|
self._case_sensitive = case_sensitive
|
||||||
|
self._snake_case_conversion = snake_case_conversion
|
||||||
|
self._env_prefix = env_prefix if env_prefix else ''
|
||||||
|
self._secret_map: dict[str, str] = self._load_remote()
|
||||||
|
|
||||||
|
def _load_remote(self) -> dict[str, str]:
|
||||||
|
secret_names: Iterator[str] = (
|
||||||
|
secret.name for secret in self._secret_client.list_properties_of_secrets() if secret.name and secret.enabled
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._snake_case_conversion:
|
||||||
|
name_map: dict[str, str] = {}
|
||||||
|
for name in secret_names:
|
||||||
|
if name.startswith(self._env_prefix):
|
||||||
|
name_map[f'{self._env_prefix}{to_snake(name[len(self._env_prefix) :])}'] = name
|
||||||
|
else:
|
||||||
|
name_map[to_snake(name)] = name
|
||||||
|
return name_map
|
||||||
|
|
||||||
|
if self._case_sensitive:
|
||||||
|
return {name: name for name in secret_names}
|
||||||
|
|
||||||
|
return {name.lower(): name for name in secret_names}
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> str | None:
|
||||||
|
new_key = key
|
||||||
|
|
||||||
|
if self._snake_case_conversion:
|
||||||
|
if key.startswith(self._env_prefix):
|
||||||
|
new_key = f'{self._env_prefix}{to_snake(key[len(self._env_prefix) :])}'
|
||||||
|
else:
|
||||||
|
new_key = to_snake(key)
|
||||||
|
|
||||||
|
elif not self._case_sensitive:
|
||||||
|
new_key = key.lower()
|
||||||
|
|
||||||
|
if new_key not in self._loaded_secrets:
|
||||||
|
if new_key in self._secret_map:
|
||||||
|
self._loaded_secrets[new_key] = self._secret_client.get_secret(self._secret_map[new_key]).value
|
||||||
|
else:
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
return self._loaded_secrets[new_key]
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return len(self._secret_map)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[str]:
|
||||||
|
return iter(self._secret_map.keys())
|
||||||
|
|
||||||
|
|
||||||
|
class AzureKeyVaultSettingsSource(EnvSettingsSource):
|
||||||
|
_url: str
|
||||||
|
_credential: TokenCredential
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
settings_cls: type[BaseSettings],
|
||||||
|
url: str,
|
||||||
|
credential: TokenCredential,
|
||||||
|
dash_to_underscore: bool = False,
|
||||||
|
case_sensitive: bool | None = None,
|
||||||
|
snake_case_conversion: bool = False,
|
||||||
|
env_prefix: str | None = None,
|
||||||
|
env_parse_none_str: str | None = None,
|
||||||
|
env_parse_enums: bool | None = None,
|
||||||
|
) -> None:
|
||||||
|
import_azure_key_vault()
|
||||||
|
self._url = url
|
||||||
|
self._credential = credential
|
||||||
|
self._dash_to_underscore = dash_to_underscore
|
||||||
|
self._snake_case_conversion = snake_case_conversion
|
||||||
|
super().__init__(
|
||||||
|
settings_cls,
|
||||||
|
case_sensitive=True if snake_case_conversion else case_sensitive,
|
||||||
|
env_prefix=env_prefix,
|
||||||
|
env_nested_delimiter='__' if snake_case_conversion else '--',
|
||||||
|
env_ignore_empty=False,
|
||||||
|
env_parse_none_str=env_parse_none_str,
|
||||||
|
env_parse_enums=env_parse_enums,
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
case_sensitive=self.case_sensitive,
|
||||||
|
snake_case_conversion=self._snake_case_conversion,
|
||||||
|
env_prefix=self.env_prefix,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _extract_field_info(self, field: FieldInfo, field_name: str) -> list[tuple[str, str, bool]]:
|
||||||
|
if self._snake_case_conversion:
|
||||||
|
field_info = list((x[0], x[1], x[2]) for x in super()._extract_field_info(field, field_name))
|
||||||
|
return field_info
|
||||||
|
|
||||||
|
if self._dash_to_underscore:
|
||||||
|
return list((x[0], x[1].replace('_', '-'), x[2]) for x in super()._extract_field_info(field, field_name))
|
||||||
|
|
||||||
|
return super()._extract_field_info(field, field_name)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'{self.__class__.__name__}(url={self._url!r}, env_nested_delimiter={self.env_nested_delimiter!r})'
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['AzureKeyVaultMapping', 'AzureKeyVaultSettingsSource']
|
||||||
1602
venv/Lib/site-packages/pydantic_settings/sources/providers/cli.py
Normal file
1602
venv/Lib/site-packages/pydantic_settings/sources/providers/cli.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,193 @@
|
|||||||
|
"""Dotenv file settings source."""
|
||||||
|
|
||||||
|
from __future__ import annotations as _annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
from collections.abc import Mapping
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
|
from dotenv import dotenv_values
|
||||||
|
from pydantic._internal._typing_extra import ( # type: ignore[attr-defined]
|
||||||
|
get_origin,
|
||||||
|
)
|
||||||
|
from typing_inspection.introspection import is_union_origin
|
||||||
|
|
||||||
|
from ..types import ENV_FILE_SENTINEL, DotenvFiltering, DotenvType, EnvPrefixTarget
|
||||||
|
from ..utils import (
|
||||||
|
_annotation_is_complex,
|
||||||
|
_union_is_complex,
|
||||||
|
parse_env_vars,
|
||||||
|
)
|
||||||
|
from .env import EnvSettingsSource
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pydantic_settings.main import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class DotEnvSettingsSource(EnvSettingsSource):
|
||||||
|
"""
|
||||||
|
Source class for loading settings values from env files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
settings_cls: type[BaseSettings],
|
||||||
|
env_file: DotenvType | None = ENV_FILE_SENTINEL,
|
||||||
|
env_file_encoding: str | None = None,
|
||||||
|
dotenv_filtering: DotenvFiltering | None = None,
|
||||||
|
case_sensitive: bool | None = None,
|
||||||
|
env_prefix: str | None = None,
|
||||||
|
env_prefix_target: EnvPrefixTarget | None = None,
|
||||||
|
env_nested_delimiter: str | None = None,
|
||||||
|
env_nested_max_split: int | None = None,
|
||||||
|
env_ignore_empty: bool | None = None,
|
||||||
|
env_parse_none_str: str | None = None,
|
||||||
|
env_parse_enums: bool | None = None,
|
||||||
|
) -> None:
|
||||||
|
self.env_file = env_file if env_file != ENV_FILE_SENTINEL else settings_cls.model_config.get('env_file')
|
||||||
|
self.env_file_encoding = (
|
||||||
|
env_file_encoding if env_file_encoding is not None else settings_cls.model_config.get('env_file_encoding')
|
||||||
|
)
|
||||||
|
self.dotenv_filtering = (
|
||||||
|
dotenv_filtering if dotenv_filtering is not None else settings_cls.model_config.get('dotenv_filtering')
|
||||||
|
)
|
||||||
|
super().__init__(
|
||||||
|
settings_cls,
|
||||||
|
case_sensitive,
|
||||||
|
env_prefix,
|
||||||
|
env_prefix_target,
|
||||||
|
env_nested_delimiter,
|
||||||
|
env_nested_max_split,
|
||||||
|
env_ignore_empty,
|
||||||
|
env_parse_none_str,
|
||||||
|
env_parse_enums,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _load_env_vars(self) -> Mapping[str, str | None]:
|
||||||
|
return self._read_env_files()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _static_read_env_file(
|
||||||
|
file_path: Path,
|
||||||
|
*,
|
||||||
|
encoding: str | None = None,
|
||||||
|
case_sensitive: bool = False,
|
||||||
|
ignore_empty: bool = False,
|
||||||
|
parse_none_str: str | None = None,
|
||||||
|
) -> Mapping[str, str | None]:
|
||||||
|
file_vars: dict[str, str | None] = dotenv_values(file_path, encoding=encoding or 'utf8')
|
||||||
|
return parse_env_vars(file_vars, case_sensitive, ignore_empty, parse_none_str)
|
||||||
|
|
||||||
|
def _read_env_file(
|
||||||
|
self,
|
||||||
|
file_path: Path,
|
||||||
|
) -> Mapping[str, str | None]:
|
||||||
|
return self._static_read_env_file(
|
||||||
|
file_path,
|
||||||
|
encoding=self.env_file_encoding,
|
||||||
|
case_sensitive=self.case_sensitive,
|
||||||
|
ignore_empty=self.env_ignore_empty,
|
||||||
|
parse_none_str=self.env_parse_none_str,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _read_env_files(self) -> Mapping[str, str | None]:
|
||||||
|
env_files = self.env_file
|
||||||
|
if env_files is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if isinstance(env_files, (str, os.PathLike)):
|
||||||
|
env_files = [env_files]
|
||||||
|
|
||||||
|
dotenv_vars: dict[str, str | None] = {}
|
||||||
|
for env_file in env_files:
|
||||||
|
env_path = Path(env_file).expanduser()
|
||||||
|
if env_path.is_file() or env_path.is_fifo():
|
||||||
|
dotenv_vars.update(self._read_env_file(env_path))
|
||||||
|
|
||||||
|
return dotenv_vars
|
||||||
|
|
||||||
|
def __call__(self) -> dict[str, Any]: # noqa: C901
|
||||||
|
data: dict[str, Any] = super().__call__()
|
||||||
|
if self.dotenv_filtering == 'only_existing':
|
||||||
|
# This case behaves like the EnvSettingsSource, only return existing fields
|
||||||
|
return data
|
||||||
|
if self.dotenv_filtering == 'match_prefix':
|
||||||
|
# In this case add all env vars that match the prefix, stripping the prefix.
|
||||||
|
prefix = self._apply_case_sensitive(self.env_prefix)
|
||||||
|
for env_name, env_value in self.env_vars.items():
|
||||||
|
if env_name.startswith(prefix):
|
||||||
|
normalized_env_name = env_name[len(self.env_prefix) :]
|
||||||
|
if (
|
||||||
|
self.env_nested_delimiter
|
||||||
|
and self.env_nested_delimiter in normalized_env_name
|
||||||
|
and normalized_env_name.partition(self.env_nested_delimiter)[0] in data
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
if normalized_env_name not in data:
|
||||||
|
data[normalized_env_name] = env_value
|
||||||
|
return data
|
||||||
|
|
||||||
|
is_extra_allowed = self.config.get('extra') != 'forbid'
|
||||||
|
|
||||||
|
# As `extra` config is allowed in dotenv settings source, We have to
|
||||||
|
# update data with extra env variables from dotenv file.
|
||||||
|
for env_name, env_value in self.env_vars.items():
|
||||||
|
if not env_value or env_name in data or (self.env_prefix and env_name in self.settings_cls.model_fields):
|
||||||
|
continue
|
||||||
|
env_used = False
|
||||||
|
for field_name, field in self.settings_cls.model_fields.items():
|
||||||
|
for _, field_env_name, _ in self._extract_field_info(field, field_name):
|
||||||
|
if env_name == field_env_name or (
|
||||||
|
(
|
||||||
|
_annotation_is_complex(field.annotation, field.metadata)
|
||||||
|
or (
|
||||||
|
is_union_origin(get_origin(field.annotation))
|
||||||
|
and _union_is_complex(field.annotation, field.metadata)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and env_name.startswith(field_env_name)
|
||||||
|
):
|
||||||
|
env_used = True
|
||||||
|
break
|
||||||
|
if env_used:
|
||||||
|
break
|
||||||
|
if not env_used:
|
||||||
|
if is_extra_allowed and env_name.startswith(self.env_prefix):
|
||||||
|
# env_prefix should be respected and removed from the env_name
|
||||||
|
normalized_env_name = env_name[len(self.env_prefix) :]
|
||||||
|
data[normalized_env_name] = env_value
|
||||||
|
else:
|
||||||
|
data[env_name] = env_value
|
||||||
|
return data
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f'{self.__class__.__name__}(env_file={self.env_file!r}, env_file_encoding={self.env_file_encoding!r}, '
|
||||||
|
f'env_nested_delimiter={self.env_nested_delimiter!r}, env_prefix_len={self.env_prefix_len!r})'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def read_env_file(
|
||||||
|
file_path: Path,
|
||||||
|
*,
|
||||||
|
encoding: str | None = None,
|
||||||
|
case_sensitive: bool = False,
|
||||||
|
ignore_empty: bool = False,
|
||||||
|
parse_none_str: str | None = None,
|
||||||
|
) -> Mapping[str, str | None]:
|
||||||
|
warnings.warn(
|
||||||
|
'read_env_file will be removed in the next version, use DotEnvSettingsSource._static_read_env_file if you must',
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
return DotEnvSettingsSource._static_read_env_file(
|
||||||
|
file_path,
|
||||||
|
encoding=encoding,
|
||||||
|
case_sensitive=case_sensitive,
|
||||||
|
ignore_empty=ignore_empty,
|
||||||
|
parse_none_str=parse_none_str,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['DotEnvSettingsSource', 'read_env_file']
|
||||||
Reference in New Issue
Block a user