Skip to content

Commit d6db0f9

Browse files
WarpedPixelWarpedPixel
and
WarpedPixel
authored
Deprecate read_env_file and move it to DotEnvSettingsSource (#318)
Co-authored-by: WarpedPixel <WarpedPixel@users.noreply.gihub.com>
1 parent fa17c0a commit d6db0f9

File tree

2 files changed

+72
-16
lines changed

2 files changed

+72
-16
lines changed

pydantic_settings/sources.py

+36-11
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,30 @@ def __init__(
753753
def _load_env_vars(self) -> Mapping[str, str | None]:
754754
return self._read_env_files()
755755

756+
@staticmethod
757+
def _static_read_env_file(
758+
file_path: Path,
759+
*,
760+
encoding: str | None = None,
761+
case_sensitive: bool = False,
762+
ignore_empty: bool = False,
763+
parse_none_str: str | None = None,
764+
) -> Mapping[str, str | None]:
765+
file_vars: dict[str, str | None] = dotenv_values(file_path, encoding=encoding or 'utf8')
766+
return parse_env_vars(file_vars, case_sensitive, ignore_empty, parse_none_str)
767+
768+
def _read_env_file(
769+
self,
770+
file_path: Path,
771+
) -> Mapping[str, str | None]:
772+
return self._static_read_env_file(
773+
file_path,
774+
encoding=self.env_file_encoding,
775+
case_sensitive=self.case_sensitive,
776+
ignore_empty=self.env_ignore_empty,
777+
parse_none_str=self.env_parse_none_str,
778+
)
779+
756780
def _read_env_files(self) -> Mapping[str, str | None]:
757781
env_files = self.env_file
758782
if env_files is None:
@@ -765,15 +789,7 @@ def _read_env_files(self) -> Mapping[str, str | None]:
765789
for env_file in env_files:
766790
env_path = Path(env_file).expanduser()
767791
if env_path.is_file():
768-
dotenv_vars.update(
769-
read_env_file(
770-
env_path,
771-
encoding=self.env_file_encoding,
772-
case_sensitive=self.case_sensitive,
773-
ignore_empty=self.env_ignore_empty,
774-
parse_none_str=self.env_parse_none_str,
775-
)
776-
)
792+
dotenv_vars.update(self._read_env_file(env_path))
777793

778794
return dotenv_vars
779795

@@ -1708,8 +1724,17 @@ def read_env_file(
17081724
ignore_empty: bool = False,
17091725
parse_none_str: str | None = None,
17101726
) -> Mapping[str, str | None]:
1711-
file_vars: dict[str, str | None] = dotenv_values(file_path, encoding=encoding or 'utf8')
1712-
return parse_env_vars(file_vars, case_sensitive, ignore_empty, parse_none_str)
1727+
warnings.warn(
1728+
'read_env_file will be removed in the next version, use DotEnvSettingsSource._static_read_env_file if you must',
1729+
DeprecationWarning,
1730+
)
1731+
return DotEnvSettingsSource._static_read_env_file(
1732+
file_path,
1733+
encoding=encoding,
1734+
case_sensitive=case_sensitive,
1735+
ignore_empty=ignore_empty,
1736+
parse_none_str=parse_none_str,
1737+
)
17131738

17141739

17151740
def _annotation_is_complex(annotation: type[Any] | None, metadata: list[Any]) -> bool:

tests/test_settings.py

+36-5
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from pydantic._internal._repr import Representation
3535
from pydantic.fields import FieldInfo
3636
from pytest_mock import MockerFixture
37-
from typing_extensions import Annotated, Literal
37+
from typing_extensions import Annotated, Literal, override
3838

3939
from pydantic_settings import (
4040
BaseSettings,
@@ -49,7 +49,7 @@
4949
TomlConfigSettingsSource,
5050
YamlConfigSettingsSource,
5151
)
52-
from pydantic_settings.sources import CliPositionalArg, CliSettingsSource, CliSubCommand, SettingsError, read_env_file
52+
from pydantic_settings.sources import CliPositionalArg, CliSettingsSource, CliSubCommand, SettingsError
5353

5454
try:
5555
import dotenv
@@ -1039,15 +1039,15 @@ def test_read_env_file_case_sensitive(tmp_path):
10391039
p = tmp_path / '.env'
10401040
p.write_text('a="test"\nB=123')
10411041

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

10451045

10461046
def test_read_env_file_syntax_wrong(tmp_path):
10471047
p = tmp_path / '.env'
10481048
p.write_text('NOT_AN_ASSIGNMENT')
10491049

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

10521052

10531053
def test_env_file_example(tmp_path):
@@ -1188,6 +1188,37 @@ def test_read_dotenv_vars_when_env_file_is_none():
11881188
)
11891189

11901190

1191+
def test_dotenvsource_override(env):
1192+
class StdinDotEnvSettingsSource(DotEnvSettingsSource):
1193+
@override
1194+
def _read_env_file(self, file_path: Path) -> Dict[str, str]:
1195+
assert str(file_path) == '-'
1196+
return {'foo': 'stdin_foo', 'bar': 'stdin_bar'}
1197+
1198+
@override
1199+
def _read_env_files(self) -> Dict[str, str]:
1200+
return self._read_env_file(Path('-'))
1201+
1202+
source = StdinDotEnvSettingsSource(BaseSettings())
1203+
assert source._read_env_files() == {'foo': 'stdin_foo', 'bar': 'stdin_bar'}
1204+
1205+
1206+
# test that calling read_env_file issues a DeprecationWarning
1207+
# TODO: remove this test once read_env_file is removed
1208+
def test_read_env_file_deprecation(tmp_path):
1209+
from pydantic_settings.sources import read_env_file
1210+
1211+
base_env = tmp_path / '.env'
1212+
base_env.write_text(test_default_env_file)
1213+
1214+
with pytest.deprecated_call():
1215+
assert read_env_file(base_env) == {
1216+
'debug_mode': 'true',
1217+
'host': 'localhost',
1218+
'port': '8000',
1219+
}
1220+
1221+
11911222
@pytest.mark.skipif(yaml, reason='PyYAML is installed')
11921223
def test_yaml_not_installed(tmp_path):
11931224
p = tmp_path / '.env'

0 commit comments

Comments
 (0)