mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-08-31 20:56:41 +00:00
fix: default server settings prevent env var config (#1234)
Change configuration source priorities to: - cli - environment vars - dotenv settings - config file settings - init settings By this the environment vars supersede any configuration var provided by the configuration file or by the initialisation with pydantic. The test_config.py::test_computed_path was fixed to to not use the defaul env var overwrite defined by conftest.py. This seemed to indicate non working env vars, but in fact was a test setupt fault. Besides this fix there are other fixes and changes added: * fix: exclude computed fields when merging settings Pydantic may overwrite settings by values given for computed fields and use these values instead of re-computing the field. Avoid computed fields in merging settings. * chore: improve Windows compatability of development setup Improve scripts to better run also on Windows. When doing path checks keep compatibility also to Windows pathes. A lot of changes to avoid the famous Windows CRLF handling and keep line endings to LF. * chore: add development hint for Windows Windows developers should set core.autocrlf to false. * chore: update version Signed-off-by: b0661 <b0661n0e17e@gmail.com>
This commit is contained in:
@@ -21,7 +21,7 @@ class NodeREDAdapterCommonSettings(SettingsBaseModel):
|
||||
|
||||
This is the example flow:
|
||||
|
||||
[HTTP In \\<URL\\>] -> [Function (parse payload)] -> [Debug] -> [HTTP Response]
|
||||
`[HTTP In <URL>] -> [Function (parse payload)] -> [Debug] -> [HTTP Response]`
|
||||
|
||||
There are two URLs that are used:
|
||||
|
||||
|
||||
@@ -509,6 +509,8 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
def lazy_config_file_settings() -> dict:
|
||||
"""Config file settings.
|
||||
|
||||
Resolves/ creates config file path.
|
||||
|
||||
This function runs at **instance creation**, not class definition. Ensures if ConfigEOS
|
||||
is recreated this function is run.
|
||||
"""
|
||||
@@ -552,13 +554,13 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
is recreated this function is run.
|
||||
"""
|
||||
# Updates path to the data directory.
|
||||
data_folder_settings = {
|
||||
settings = {
|
||||
"general": {
|
||||
"data_folder_path": default_data_folder_path(),
|
||||
},
|
||||
}
|
||||
|
||||
return data_folder_settings
|
||||
return settings
|
||||
|
||||
def lazy_init_settings() -> dict:
|
||||
"""Init settings.
|
||||
@@ -584,7 +586,9 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
logger.debug("Config initialisation with env settings is disabled.")
|
||||
return {}
|
||||
|
||||
return env_settings()
|
||||
settings = env_settings()
|
||||
|
||||
return settings
|
||||
|
||||
def lazy_dotenv_settings() -> dict:
|
||||
"""Dotenv settings.
|
||||
@@ -596,7 +600,9 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
logger.debug("Config initialisation with dotenv settings is disabled.")
|
||||
return {}
|
||||
|
||||
return dotenv_settings()
|
||||
settings = dotenv_settings()
|
||||
|
||||
return settings
|
||||
|
||||
def lazy_file_settings() -> dict:
|
||||
"""File settings.
|
||||
@@ -640,18 +646,20 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
logger.debug("Config initialisation with file secret settings is disabled.")
|
||||
return {}
|
||||
|
||||
return file_secret_settings()
|
||||
settings = file_secret_settings()
|
||||
|
||||
return settings
|
||||
|
||||
# All the settings sources in priority sequence
|
||||
# The settings are all lazyly evaluated at instance creation time to allow for
|
||||
# runtime configuration.
|
||||
setting_sources = [
|
||||
lazy_config_cli_settings, # Prio high
|
||||
lazy_config_file_settings,
|
||||
lazy_init_settings,
|
||||
lazy_env_settings,
|
||||
lazy_dotenv_settings,
|
||||
lazy_file_settings,
|
||||
lazy_config_file_settings, # resolves/creates config file path
|
||||
lazy_file_settings, # actually loads JSON config values
|
||||
lazy_init_settings,
|
||||
lazy_data_folder_path_settings,
|
||||
lazy_file_secret_settings, # Prio low
|
||||
]
|
||||
@@ -713,7 +721,11 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
logger.error(error_msg)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
self.merge_settings_from_dict(settings.model_dump(exclude_none=True, exclude_unset=True))
|
||||
# Exclude None, unset and computed fields from generating settings.
|
||||
# Pydantic may use even provided computed field values instead of recalculating.
|
||||
self.merge_settings_from_dict(
|
||||
settings.model_dump(exclude_none=True, exclude_unset=True, exclude_computed_fields=True)
|
||||
)
|
||||
|
||||
def merge_settings_from_dict(self, data: dict) -> None:
|
||||
"""Merges the provided dictionary data into the current instance.
|
||||
@@ -737,7 +749,12 @@ class ConfigEOS(SingletonMixin, SettingsEOSDefaults):
|
||||
config.merge_settings_from_dict(new_data)
|
||||
|
||||
"""
|
||||
self._setup(**merge_models(self, data))
|
||||
merged = merge_models(
|
||||
self,
|
||||
data,
|
||||
)
|
||||
|
||||
self._setup(**merged)
|
||||
|
||||
def reset_settings(self) -> None:
|
||||
"""Reset all changed settings to environment/config file defaults.
|
||||
|
||||
@@ -66,6 +66,9 @@ def merge_models(source: BaseModel, update_dict: dict[str, Any]) -> dict[str, An
|
||||
Nested dictionaries are merged recursively.
|
||||
Lists in update_dict replace source lists entirely.
|
||||
|
||||
Computed fields are excluded from the source model because they represent
|
||||
derived state rather than configuration input.
|
||||
|
||||
Args:
|
||||
source (BaseModel): Pydantic model instance serving as the source.
|
||||
update_dict (dict[str, Any]): Dictionary with updates to apply.
|
||||
@@ -91,7 +94,10 @@ def merge_models(source: BaseModel, update_dict: dict[str, Any]) -> dict[str, An
|
||||
# For other types or if update_data is None, override source_data
|
||||
return update_data
|
||||
|
||||
source_dict = source.model_dump(exclude_unset=True)
|
||||
source_dict = source.model_dump(
|
||||
exclude_unset=True,
|
||||
exclude_computed_fields=True,
|
||||
)
|
||||
merged_result = deep_merge(source_dict, deepcopy(update_dict))
|
||||
return merged_result
|
||||
|
||||
|
||||
@@ -276,7 +276,7 @@ def _version_date_hash() -> tuple[datetime, str]:
|
||||
Returns:
|
||||
lattest commit date and SHA256 hash of the project files
|
||||
"""
|
||||
if not str(DIR_PACKAGE_ROOT).endswith("src/akkudoktoreos"):
|
||||
if DIR_PACKAGE_ROOT.parts[-2:] != ("src", "akkudoktoreos"): # check path Windows friendly
|
||||
error_msg = f"DIR_PACKAGE_ROOT does not end with src/akkudoktoreos: {DIR_PACKAGE_ROOT}"
|
||||
raise ValueError(error_msg)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user