Read plugin-declared settings from the environment, and say whether a missing one has a default - #2501
Conversation
Context: - Issue #2492: a plugin setting set as an environment variable never reached the app config, because read_env_vars() only promotes a fixed list of FlexMeasures' own settings and plugins are registered after the config has been read. - The warning for a missing setting repeated whatever the plugin claimed in 'message_if_missing', so a promise of a default could stand for a setting that had none. Change: - Look up plugin-declared settings that are still unset in the environment, when registering the plugin. The config file is read first, so it keeps precedence. - Interpret such a value as the declared 'parse_as' type (JSON for list/dict). - Support a 'default' key, applied when the setting is missing, and say in the log message whether a missing setting falls back to a default or stays unset. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x Signed-off-by: F.N. Claessen <felix@seita.nl>
Context: - Issue #2492: plugin settings from the environment and the wording of the missing-setting message were untested. Change: - Add tests for env promotion, config precedence, the testing-mode exemption, parse_as conversion, and the missing-setting message with and without a default. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x Signed-off-by: F.N. Claessen <felix@seita.nl>
Context: - Issue #2492: nothing near the __settings__ documentation said how a plugin setting is actually set, or what each of its keys does. Change: - Describe the keys of a __settings__ entry, including the new 'default'. - Explain that plugin settings can come from the config file or the environment, which of the two wins, and how environment values are parsed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x Signed-off-by: F.N. Claessen <felix@seita.nl>
Context: - Issue #2492, PR #2501. Change: - Add an Infrastructure / Support entry, as this targets plugin developers and hosts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x Signed-off-by: F.N. Claessen <felix@seita.nl>
Documentation build overview
5 files changed ·
|
…onment Context: - The bool branch of parse_setting_from_env repeated the truthy-value list that config_utils.parse_bool_env already holds for FlexMeasures' own settings. Change: - Call parse_bool_env, so both paths read a boolean environment variable alike. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x Signed-off-by: F.N. Claessen <felix@seita.nl>
Context: - The note claimed FlexMeasures reads no settings at all from the environment while testing or building the docs. That holds for plugin settings, but not for its own: FLEXMEASURES_ENV, SECRET_KEY, SECURITY_TOTP_SECRETS and SQLALCHEMY_TEST_DATABASE_URI are read outside read_env_vars() and are not gated. Change: - Say that both modes run on defaults, reading neither the config file nor the environment, and name the exceptions. - Point plugin authors at setting their settings on the app config in their own tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x Signed-off-by: F.N. Claessen <felix@seita.nl>
There was a problem hiding this comment.
🟡 Changes recommended
It drops all existing register_plugins regression tests (not found elsewhere) and has a correctness gap where default values aren’t type-validated when parse_as is declared.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR improves plugin configuration handling by allowing plugin-declared __settings__ to be populated from environment variables (with config-file precedence), adds optional plugin-declared defaults, and makes the “missing setting” log message explicitly state whether a default is applied.
Changes:
- Read unset plugin-declared settings from environment variables, parsing values according to
parse_as. - Support a
defaultkey for plugin settings, and append an explicit default/unset statement to missing-setting logs. - Update plugin documentation and add a main changelog entry describing the new behavior.
File summaries
| File | Description |
|---|---|
| flexmeasures/utils/plugin_utils.py | Reads plugin settings from env, applies defaults, and clarifies missing-setting logging |
| flexmeasures/utils/tests/test_plugin_utils.py | Replaces prior plugin-loader tests with new tests for plugin setting env/default behavior |
| documentation/plugin/customisation.rst | Documents __settings__ keys and where plugin settings can be set (config vs env) |
| documentation/changelog.rst | Adds a changelog entry for plugin settings env/default support |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| description = ( | ||
| f" ({setting_fields['description']})" if "description" in setting_fields else "" | ||
| ) |
There was a problem hiding this comment.
Leaving this one. The ({description}). format predates this PR, so plugins that write a description ending in a period already get (... .). today; changing it would alter the log output of every plugin, which is beyond what this PR is about. Happy to do it as a follow-up if maintainers want it.
Context: - Copilot review on PR #2501: defaults were applied after the type scan had run, so a default of the wrong type (default "30" for parse_as int) was set silently. Change: - Report what is missing and apply defaults first, then check the type of every setting that holds a value, defaults included. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x Signed-off-by: F.N. Claessen <felix@seita.nl>
…ests to them Context: - Copilot review on PR #2501: an earlier commit in this PR overwrote this module, dropping the eleven plugin loader tests it held, among them the regression tests for GH issue #2415 and for the comma-separated FLEXMEASURES_PLUGINS hint. Change: - Restore the module as it is on main, and append the settings tests after it. - Cover the documentation build alongside testing mode, and a declared default whose type does not match parse_as. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x Signed-off-by: F.N. Claessen <felix@seita.nl>
There was a problem hiding this comment.
🟢 Approval recommended
The behavior change is well-scoped, matches the PR/issue requirements, and is covered by targeted tests and updated documentation.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
@nhoening ready for review. Summary of where this landed, including your two design questions on the issue: Should plugin settings be readable from env? Yes, but scoped: settings a plugin declares in Should a plugin be able to claim a default it doesn't have? It still can, but it no longer gets the last word: FlexMeasures now appends its own statement, either Copilot reviewed twice; its first pass caught that I had overwritten this repo's plugin-loader tests, which are restored (the file's diff against main is additions only). |
Co-authored-by: Nicolas Höning <nicolas@seita.nl> Signed-off-by: Felix Claessen <30658763+Flix6x@users.noreply.github.com>
Description
Plugin-declared settings could only be set in a config file. Setting one as an environment variable — the natural thing to do in a container — had no effect, and FlexMeasures still reported it as missing. On top of that, the "missing setting" message repeated whatever the plugin put in
message_if_missing, so a plugin promising"'NL' will be used as a default."made a setting that has no fallback read as optional.read_env_vars()applies to FlexMeasures' own settings.parse_astype the plugin declared:int/floatas numbers,boolasTruefor1/true/yes/on(case-insensitively),list/dictas JSON. A value we cannot convert is passed on unconverted, so the existing type check reports it. Withoutparse_as, the value stays a string.__settings__entry can now declare adefault, which is applied when the setting is missing.Falling back to the default declared by the plugin: 'NL'.orNo default is declared for it, so it stays unset.A plugin's ownmessage_if_missingcan no longer be the last word on whether a setting is optional.__settings__entry, and where those settings can be set.documentation/changelog.rstAs before, nothing is read from the environment while testing or while building the documentation, which both run on defaults.
On reading everything from the environment
The issue discussion raises whether
read_env_vars()should simply promote every environment variable. This PR does not do that: the app config would then absorb every variable in the environment (PATH, cloud credentials, whatever the deployment happens to export), and Flask config values are visible to every plugin and template. Promoting exactly the settings a plugin declared gets the reported use case working without that. If we do want the broader change, it seems worth a separate discussion.Look & Feel
A plugin declaring:
started with
MY_PLUGIN_TOKEN=repro-token-value MY_PLUGIN_TIMEOUT=30:Before this PR,
MY_PLUGIN_TOKENandMY_PLUGIN_TIMEOUTwere bothNoneand reported as missing, and theMY_PLUGIN_COUNTRYwarning stopped after'NL' will be used as a default.whileMY_PLUGIN_COUNTRYstayedNone.How to test
pytest flexmeasures/utils/tests/test_plugin_utils.pycovers env promotion, config-file precedence, the testing-mode exemption, eachparse_asconversion, an unparsable value, and the missing-setting message with and without adefault.The issue's Docker repro is the end-to-end check: build the one-setting plugin image, set the setting as a container environment variable, and the server now boots without reporting it missing.
Further Improvements
read_env_vars()should read all settings from the environment (see above) is left open.Related Items
Closes #2492
Sign-off
🤖 Generated with Claude Code
https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x