Coverage for .nox/test-3-9/lib/python3.9/site-packages/nskit/common/configuration/sources.py: 94%
51 statements
« prev ^ index » next coverage.py v7.4.2, created at 2024-02-25 17:38 +0000
« prev ^ index » next coverage.py v7.4.2, created at 2024-02-25 17:38 +0000
1"""Add settings sources."""
2from __future__ import annotations as _annotations
4from pathlib import Path
5from typing import Any
7from pydantic.config import ExtraValues
8from pydantic_settings import BaseSettings
9from pydantic_settings.sources import (
10 DotEnvSettingsSource as _DotEnvSettingsSource,
11 DotenvType,
12 ENV_FILE_SENTINEL,
13 JsonConfigSettingsSource as _JsonConfigSettingsSource,
14 TomlConfigSettingsSource as _TomlConfigSettingsSource,
15 YamlConfigSettingsSource as _YamlConfigSettingsSource,
16)
18from nskit.common.io import json, toml, yaml
21class JsonConfigSettingsSource(_JsonConfigSettingsSource):
22 """Use the nskit.common.io.json loading to load settings from a json file."""
23 def _read_file(self, file_path: Path) -> dict[str, Any]:
24 encoding = self.json_file_encoding or 'utf-8'
25 file_contents = file_path.read_text(encoding)
26 return json.loads(file_contents)
28 def __call__(self):
29 """Make the file reading at the source instantiation."""
30 self.init_kwargs = self._read_files(self.json_file_path)
31 return super().__call__()
34class TomlConfigSettingsSource(_TomlConfigSettingsSource):
35 """Use the nskit.common.io.toml loading to load settings from a toml file."""
36 def _read_file(self, file_path: Path) -> dict[str, Any]:
37 file_contents = file_path.read_text()
38 return toml.loads(file_contents)
40 def __call__(self):
41 """Make the file reading at the source instantiation."""
42 self.init_kwargs = self._read_files(self.toml_file_path)
43 return super().__call__()
46class YamlConfigSettingsSource(_YamlConfigSettingsSource):
47 """Use the nskit.common.io.yaml loading to load settings from a yaml file."""
48 def _read_file(self, file_path: Path) -> dict[str, Any]:
49 encoding = self.yaml_file_encoding or 'utf-8'
50 file_contents = file_path.read_text(encoding)
51 return yaml.loads(file_contents)
53 def __call__(self):
54 """Make the file reading at the source instantiation."""
55 self.init_kwargs = self._read_files(self.yaml_file_path)
56 return super().__call__()
59class DotEnvSettingsSource(_DotEnvSettingsSource):
60 """Fixes change of behaviour in pydantic-settings 2.2.0 with extra allowed handling.
62 Adds dotenv_extra variable that is set to replicate previous behaviour (ignore).
63 """
65 def __init__(
66 self,
67 settings_cls: type[BaseSettings],
68 env_file: DotenvType | None = ENV_FILE_SENTINEL,
69 env_file_encoding: str | None = None,
70 case_sensitive: bool | None = None,
71 env_prefix: str | None = None,
72 env_nested_delimiter: str | None = None,
73 env_ignore_empty: bool | None = None,
74 env_parse_none_str: str | None = None,
75 dotenv_extra: ExtraValues | None = 'ignore'
76 ) -> None:
77 """Wrapper for init function to add dotenv_extra handling."""
78 self.dotenv_extra = dotenv_extra
79 super().__init__(
80 settings_cls,
81 env_file,
82 env_file_encoding,
83 case_sensitive,
84 env_prefix,
85 env_nested_delimiter,
86 env_ignore_empty,
87 env_parse_none_str
88 )
90 def __call__(self) -> dict[str, Any]:
91 """Wraps call logic introduced in 2.2.0, but is backwards compatible to 2.1.0 and earlier versions."""
92 data: dict[str, Any] = super().__call__()
93 to_pop = []
94 for key in data.keys():
95 matched = False
96 for field_name, field in self.settings_cls.model_fields.items():
97 for field_alias, field_env_name, _ in self._extract_field_info(field, field_name):
98 if key == field_env_name or key == field_alias:
99 matched = True
100 break
101 if matched:
102 break
103 if not matched and self.dotenv_extra == 'ignore':
104 to_pop.append(key)
105 for key in to_pop:
106 data.pop(key)
107 return data