mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
Change defaults for BaseSettings (#747)
* Change defaults for BaseSettings * Update docs and fix build * Minor documentation fixes * Fix lowercase issues * Update docs and fix build * Fix formatting * Try with monkeypatched test * Fix doublequotes * Change case_insensitive to case_sensitive * more change details.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
**Breaking Change:** modify default config settings for ``BaseSettings``; ``case_insensitive`` renamed to ``case_sensitive``,
|
||||
default changed to ``case_sensitive = False``, ``env_prefix`` default changed to ``''`` - e.g. no prefix.
|
||||
+1
-1
@@ -5,4 +5,4 @@ class Settings(BaseSettings):
|
||||
redis_host = 'localhost'
|
||||
|
||||
class Config:
|
||||
case_insensitive = True
|
||||
case_sensitive = True
|
||||
+16
-7
@@ -864,24 +864,33 @@ environment variables or keyword arguments (e.g. in unit tests).
|
||||
(This script is complete, it should run "as is")
|
||||
|
||||
Here ``redis_port`` could be modified via ``export MY_PREFIX_REDIS_PORT=6380`` or ``auth_key`` by
|
||||
``export my_api_key=6380``.
|
||||
``export my_api_key=6380``. By default, environment variables are treated as case-insensitive, so
|
||||
``export my_prefix_redis_port=6380`` would work as well.
|
||||
(Aliases are always sensitive to case, so ``export MY_API_KEY=6380`` would not work.)
|
||||
|
||||
By default ``BaseSettings`` considers field values in the following priority (where 3. has the highest priority
|
||||
and overrides the other two):
|
||||
|
||||
1. The default values set in your ``Settings`` class
|
||||
2. Environment variables eg. ``MY_PREFIX_REDIS_PORT`` as described above.
|
||||
3. Argument passed to the ``Settings`` class on initialisation.
|
||||
1. The default values set in your ``Settings`` class.
|
||||
2. Environment variables, e.g. ``MY_PREFIX_REDIS_PORT`` as described above.
|
||||
3. Arguments passed to the ``Settings`` class on initialisation.
|
||||
|
||||
This behaviour can be changed by overriding the ``_build_values`` method on ``BaseSettings``.
|
||||
|
||||
Complex types like ``list``, ``set``, ``dict`` and submodels can be set by using JSON environment variables.
|
||||
|
||||
Environment variables can be read in a case insensitive manner:
|
||||
Case-sensitivity can be turned on through the ``Config``:
|
||||
|
||||
.. literalinclude:: examples/settings_case_insensitive.py
|
||||
.. literalinclude:: examples/settings_case_sensitive.py
|
||||
|
||||
When ``case_sensitive`` is ``True``, the environment variable must be in all-caps,
|
||||
so in this example ``redis_host`` could only be modified via ``export REDIS_HOST``.
|
||||
|
||||
.. note::
|
||||
|
||||
On Windows, python's `os` module always treats environment variables as case-insensitive, so the
|
||||
``case_sensitive`` config setting will have no effect -- settings will always be updated ignoring case.
|
||||
|
||||
Here ``redis_port`` could be modified via ``export APP_REDIS_HOST``, ``export app_redis_host``, ``export app_REDIS_host``, etc.
|
||||
|
||||
Dynamic model creation
|
||||
......................
|
||||
|
||||
@@ -13,9 +13,6 @@ class BaseSettings(BaseModel):
|
||||
"""
|
||||
Base class for settings, allowing values to be overridden by environment variables.
|
||||
|
||||
By default environment variables must be upper case and prefixed by APP_ by default. Eg. to override foobar,
|
||||
`export APP_FOOBAR="whatever"`. To change this behaviour set Config options case_insensitive and env_prefix.
|
||||
|
||||
This is useful in production for secrets you do not wish to save in code, it plays nicely with docker(-compose),
|
||||
Heroku and any 12 factor app design.
|
||||
"""
|
||||
@@ -33,10 +30,10 @@ class BaseSettings(BaseModel):
|
||||
"""
|
||||
d: Dict[str, Optional[str]] = {}
|
||||
|
||||
if self.__config__.case_insensitive:
|
||||
env_vars = {k.lower(): v for k, v in os.environ.items()}
|
||||
else:
|
||||
if self.__config__.case_sensitive:
|
||||
env_vars = cast(Dict[str, str], os.environ)
|
||||
else:
|
||||
env_vars = {k.lower(): v for k, v in os.environ.items()}
|
||||
|
||||
for field in self.__fields__.values():
|
||||
if field.has_alias:
|
||||
@@ -44,7 +41,7 @@ class BaseSettings(BaseModel):
|
||||
else:
|
||||
env_name = self.__config__.env_prefix + field.name.upper()
|
||||
|
||||
env_name_ = env_name.lower() if self.__config__.case_insensitive else env_name
|
||||
env_name_ = env_name if self.__config__.case_sensitive else env_name.lower()
|
||||
env_val = env_vars.get(env_name_, None)
|
||||
|
||||
if env_val:
|
||||
@@ -57,10 +54,10 @@ class BaseSettings(BaseModel):
|
||||
return d
|
||||
|
||||
class Config:
|
||||
env_prefix = 'APP_'
|
||||
env_prefix = ''
|
||||
validate_all = True
|
||||
extra = Extra.forbid
|
||||
arbitrary_types_allowed = True
|
||||
case_insensitive = False
|
||||
case_sensitive = False
|
||||
|
||||
__config__: Config # type: ignore
|
||||
|
||||
+31
-7
@@ -1,14 +1,19 @@
|
||||
import os
|
||||
from typing import List, Set
|
||||
|
||||
import pytest
|
||||
|
||||
from pydantic import BaseModel, BaseSettings, NoneStr, ValidationError, dataclasses
|
||||
from pydantic import BaseModel, BaseSettings, NoneStr, Schema, ValidationError, dataclasses
|
||||
from pydantic.env_settings import SettingsError
|
||||
|
||||
|
||||
class SimpleSettings(BaseSettings):
|
||||
apple: str
|
||||
|
||||
class Config:
|
||||
env_prefix = 'APP_'
|
||||
case_sensitive = True
|
||||
|
||||
|
||||
def test_sub_env(env):
|
||||
env.set('APP_APPLE', 'hello')
|
||||
@@ -54,6 +59,10 @@ class ComplexSettings(BaseSettings):
|
||||
carrots: dict = {}
|
||||
date: DateModel = DateModel()
|
||||
|
||||
class Config:
|
||||
env_prefix = 'APP_'
|
||||
case_sensitive = True
|
||||
|
||||
|
||||
def test_list(env):
|
||||
env.set('APP_APPLES', '["russet", "granny smith"]')
|
||||
@@ -84,7 +93,7 @@ def test_required_sub_model(env):
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
Settings()
|
||||
env.set('APP_FOOBAR', '{"pips": "TRUE"}')
|
||||
env.set('FOOBAR', '{"pips": "TRUE"}')
|
||||
s = Settings()
|
||||
assert s.foobar.pips is True
|
||||
|
||||
@@ -93,7 +102,7 @@ def test_non_class(env):
|
||||
class Settings(BaseSettings):
|
||||
foobar: NoneStr
|
||||
|
||||
env.set('APP_FOOBAR', 'xxx')
|
||||
env.set('FOOBAR', 'xxx')
|
||||
s = Settings()
|
||||
assert s.foobar == 'xxx'
|
||||
|
||||
@@ -116,7 +125,8 @@ def test_case_insensitive(env):
|
||||
bAR: str
|
||||
|
||||
class Config:
|
||||
case_insensitive = True
|
||||
env_prefix = 'APP_'
|
||||
case_sensitive = False
|
||||
|
||||
env.set('apP_foO', 'foo')
|
||||
env.set('app_bar', 'bar')
|
||||
@@ -125,6 +135,20 @@ def test_case_insensitive(env):
|
||||
assert s.bAR == 'bar'
|
||||
|
||||
|
||||
def test_case_sensitive(monkeypatch):
|
||||
class Settings(BaseSettings):
|
||||
foo: str = Schema(..., alias='foo')
|
||||
|
||||
class Config:
|
||||
case_sensitive = True
|
||||
|
||||
# Need to patch os.environ to get build to work on Windows, where os.environ is case insensitive
|
||||
monkeypatch.setattr(os, 'environ', value={'Foo': 'foo'})
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
Settings()
|
||||
assert exc_info.value.errors() == [{'loc': ('foo',), 'msg': 'field required', 'type': 'value_error.missing'}]
|
||||
|
||||
|
||||
def test_nested_dataclass(env):
|
||||
@dataclasses.dataclass
|
||||
class MyDataclass:
|
||||
@@ -134,7 +158,7 @@ def test_nested_dataclass(env):
|
||||
class Settings(BaseSettings):
|
||||
n: MyDataclass
|
||||
|
||||
env.set('APP_N', '[123, "bar value"]')
|
||||
env.set('N', '[123, "bar value"]')
|
||||
s = Settings()
|
||||
assert isinstance(s.n, MyDataclass)
|
||||
assert s.n.foo == 123
|
||||
@@ -149,7 +173,7 @@ def test_config_file_settings(env):
|
||||
def _build_values(self, init_kwargs):
|
||||
return {**init_kwargs, **self._build_environ()}
|
||||
|
||||
env.set('APP_BAR', 'env setting')
|
||||
env.set('BAR', 'env setting')
|
||||
|
||||
s = Settings(foo='123', bar='argument')
|
||||
assert s.foo == 123
|
||||
@@ -170,7 +194,7 @@ def test_config_file_settings_nornir(env):
|
||||
config_settings = init_kwargs.pop('__config_settings__')
|
||||
return {**config_settings, **init_kwargs, **self._build_environ()}
|
||||
|
||||
env.set('APP_C', 'env setting c')
|
||||
env.set('C', 'env setting c')
|
||||
|
||||
config = {'a': 'config a', 'b': 'config b', 'c': 'config c'}
|
||||
s = Settings(__config_settings__=config, b='argument b', c='argument c')
|
||||
|
||||
Reference in New Issue
Block a user