Skip to content

Commit

Permalink
Deprecate read_env_file and move it to DotEnvSettingsSource (#318)
Browse files Browse the repository at this point in the history
Co-authored-by: WarpedPixel <WarpedPixel@users.noreply.gihub.com>
  • Loading branch information
WarpedPixel and WarpedPixel authored Jul 2, 2024
1 parent fa17c0a commit d6db0f9
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 16 deletions.
47 changes: 36 additions & 11 deletions pydantic_settings/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,30 @@ def __init__(
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:
Expand All @@ -765,15 +789,7 @@ def _read_env_files(self) -> Mapping[str, str | None]:
for env_file in env_files:
env_path = Path(env_file).expanduser()
if env_path.is_file():
dotenv_vars.update(
read_env_file(
env_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,
)
)
dotenv_vars.update(self._read_env_file(env_path))

return dotenv_vars

Expand Down Expand Up @@ -1708,8 +1724,17 @@ def read_env_file(
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)
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,
)


def _annotation_is_complex(annotation: type[Any] | None, metadata: list[Any]) -> bool:
Expand Down
41 changes: 36 additions & 5 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from pydantic._internal._repr import Representation
from pydantic.fields import FieldInfo
from pytest_mock import MockerFixture
from typing_extensions import Annotated, Literal
from typing_extensions import Annotated, Literal, override

from pydantic_settings import (
BaseSettings,
Expand All @@ -49,7 +49,7 @@
TomlConfigSettingsSource,
YamlConfigSettingsSource,
)
from pydantic_settings.sources import CliPositionalArg, CliSettingsSource, CliSubCommand, SettingsError, read_env_file
from pydantic_settings.sources import CliPositionalArg, CliSettingsSource, CliSubCommand, SettingsError

try:
import dotenv
Expand Down Expand Up @@ -1039,15 +1039,15 @@ def test_read_env_file_case_sensitive(tmp_path):
p = tmp_path / '.env'
p.write_text('a="test"\nB=123')

assert read_env_file(p) == {'a': 'test', 'b': '123'}
assert read_env_file(p, case_sensitive=True) == {'a': 'test', 'B': '123'}
assert DotEnvSettingsSource._static_read_env_file(p) == {'a': 'test', 'b': '123'}
assert DotEnvSettingsSource._static_read_env_file(p, case_sensitive=True) == {'a': 'test', 'B': '123'}


def test_read_env_file_syntax_wrong(tmp_path):
p = tmp_path / '.env'
p.write_text('NOT_AN_ASSIGNMENT')

assert read_env_file(p, case_sensitive=True) == {'NOT_AN_ASSIGNMENT': None}
assert DotEnvSettingsSource._static_read_env_file(p, case_sensitive=True) == {'NOT_AN_ASSIGNMENT': None}


def test_env_file_example(tmp_path):
Expand Down Expand Up @@ -1188,6 +1188,37 @@ def test_read_dotenv_vars_when_env_file_is_none():
)


def test_dotenvsource_override(env):
class StdinDotEnvSettingsSource(DotEnvSettingsSource):
@override
def _read_env_file(self, file_path: Path) -> Dict[str, str]:
assert str(file_path) == '-'
return {'foo': 'stdin_foo', 'bar': 'stdin_bar'}

@override
def _read_env_files(self) -> Dict[str, str]:
return self._read_env_file(Path('-'))

source = StdinDotEnvSettingsSource(BaseSettings())
assert source._read_env_files() == {'foo': 'stdin_foo', 'bar': 'stdin_bar'}


# test that calling read_env_file issues a DeprecationWarning
# TODO: remove this test once read_env_file is removed
def test_read_env_file_deprecation(tmp_path):
from pydantic_settings.sources import read_env_file

base_env = tmp_path / '.env'
base_env.write_text(test_default_env_file)

with pytest.deprecated_call():
assert read_env_file(base_env) == {
'debug_mode': 'true',
'host': 'localhost',
'port': '8000',
}


@pytest.mark.skipif(yaml, reason='PyYAML is installed')
def test_yaml_not_installed(tmp_path):
p = tmp_path / '.env'
Expand Down

0 comments on commit d6db0f9

Please sign in to comment.