Source code for pydantic_configmanager.main

from os import environ
from typing import Any, TypeVar, Union, AbstractSet, List, Dict, Type, Sequence, Optional, Any

from pydantic import BaseSettings, BaseConfig, Extra, BaseModel
from pydantic.errors import DecimalMaxDigitsError
from pydantic.fields import FieldInfo, ModelField
from pydantic.typing import display_as_type
from pydantic.utils import sequence_like
import warnings


T = TypeVar('T', bound='EnvironmentBaseModel')


class EnvFieldInfo(BaseModel):
    names: List[str]
    type: str
    description: Optional[str]
    default: Any


def get_model_env_info(model: Type[BaseModel]):
    fields: Dict[str, ModelField] = model.__fields__
    env_vars: List[EnvFieldInfo] = []
    for fieldname, field in fields.items():
        _ = fieldname
        env_field_info = EnvFieldInfo(
            names=field.field_info.extra.get('env_names', {}),
            type=field.type_.__name__,
            description=field.field_info.description or fieldname,
            default=field.default
        )
        env_vars.append(env_field_info)
    return env_vars

def env_translation(model: Type[BaseSettings]) -> Dict[str, str]:
    fields: Dict[str, ModelField] = model.__fields__
    translation_map: Dict[str, str] = dict()
    for fieldname, field in fields.items():
        names = field.field_info.extra.get('env_names',dict())
        for name in names:
            translation_map[name] = fieldname
    return translation_map



[docs]class SettingsConfig(BaseConfig): allow_population_by_field_name = True env_prefix = '' name_prefix = '' env_postfix = '' env_file = None env_file_encoding = None secrets_dir = None validate_all = True extra = Extra.allow arbitrary_types_allowed = True case_sensitive = False
[docs] @classmethod def env_alias_generator(cls, fieldname: str) -> str: """Create environment names with a name prefix, env prefix and a postfix.""" env = str(cls.env_prefix) name = str(cls.name_prefix) if env and not env.endswith("_"): env = f"{env}_" if name and not name.endswith("_"): name = f"{name}_" return f'{cls.name_prefix}{env}{fieldname}{cls.env_postfix}'.upper()
[docs] @classmethod def prepare_field(cls, field: ModelField) -> None: env_names: Union[List[str], AbstractSet[str]] field_info_from_config = cls.get_field_info(field.name) env = field_info_from_config.get('env') or field.field_info.extra.get('env') if env is None: if field.has_alias: warnings.warn( 'aliases are no longer used by BaseSettings to define which environment variables to read. ' 'Instead use the "env" field setting. ' 'See https://pydantic-docs.helpmanual.io/usage/settings/#environment-variable-names', FutureWarning, ) env_names = {cls.env_alias_generator(field.name)} elif isinstance(env, str): env_names = {env} elif isinstance(env, (set, frozenset)): env_names = env elif sequence_like(env): env_names = list(env) else: raise TypeError(f'invalid field env: {env!r} ({display_as_type(env)}); should be string, list or set') if not cls.case_sensitive: env_names = env_names.__class__(n.upper() for n in env_names) field.field_info.extra['env_names'] = env_names
[docs]class EnvironmentBaseModel(BaseSettings): """ Pydantic BaseSettings class with extended environment variable generation and template output. This is useful in production for secrets you do not wish to save in code, it plays nicely with docker(-compose), Heroku, Kubernetes, Openshift and any 12 factor app design. """
[docs] class Config(SettingsConfig): """Configuratio for the Config class via a... Config class."""
[docs] @classmethod def env_config(cls, by_alias: bool = True, include_export: bool = False) -> str: """Return settings template as they would go into environment.""" export = '' env_fields = get_model_env_info(cls) if include_export: export = "export " schema = cls.schema(by_alias) output: str = f"# {schema.get('description','')}\n" output += '# -----\n' output += '# These are the supported environment configuration variables.\n' output += '# Set these up in your environment.\n' output += '\n' for field in env_fields: type_value = field.type if type_value: # if a type is set, format the output: type_value = f"; type: {type_value}" default_value = field.default default_repr = '' # if default_value: default_repr = f"{default_value!a}" description = f"{field.description}" comment = f" # {description}{type_value}" name = field.names[0] output_line = f"{export}{name}={default_repr}{comment}\n" output += output_line return output
[docs] @classmethod def from_env(cls, **data: Any) -> T: """Initialize object from environmnent variables.""" data = data or dict() env_mapping = env_translation(cls) for env_name, field_name in env_mapping.items(): if env_name in environ and not data.get(field_name, None): data[field_name] = environ.get(env_name) return cls(**data) # type: ignore