Skip to content

Read plugin-declared settings from the environment, and say whether a missing one has a default - #2501

Merged
Flix6x merged 9 commits into
mainfrom
fix/2492-plugin-settings-from-env
Sep 12, 2026
Merged

Read plugin-declared settings from the environment, and say whether a missing one has a default#2501
Flix6x merged 9 commits into
mainfrom
fix/2492-plugin-settings-from-env

Conversation

@Flix6x

@Flix6x Flix6x commented Sep 9, 2026

Copy link
Copy Markdown
Member

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.

  • Plugin settings that are still unset when the plugin is registered are now looked up in the environment. Plugins are registered after the config file has been read, so a value from the config file keeps precedence and the environment only fills the gaps — the same order read_env_vars() applies to FlexMeasures' own settings.
  • Environment values are interpreted as the parse_as type the plugin declared: int/float as numbers, bool as True for 1/true/yes/on (case-insensitively), list/dict as JSON. A value we cannot convert is passed on unconverted, so the existing type check reports it. Without parse_as, the value stays a string.
  • A __settings__ entry can now declare a default, which is applied when the setting is missing.
  • The message for a missing setting now ends with a statement from FlexMeasures itself: either Falling back to the default declared by the plugin: 'NL'. or No default is declared for it, so it stays unset. A plugin's own message_if_missing can no longer be the last word on whether a setting is optional.
  • Documented the keys of a __settings__ entry, and where those settings can be set.
  • Added changelog item in documentation/changelog.rst

As 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:

__settings__ = {
    "MY_PLUGIN_TOKEN": {"description": "Token used by my plugin.", "level": "error"},
    "MY_PLUGIN_COUNTRY": {"level": "warning", "message_if_missing": "'NL' will be used as a default.", "default": "NL"},
    "MY_PLUGIN_TIMEOUT": {"level": "warning", "parse_as": int},
}

started with MY_PLUGIN_TOKEN=repro-token-value MY_PLUGIN_TIMEOUT=30:

INFO Importing plugin repro_plugin ...
WARNING Missing config setting 'MY_PLUGIN_COUNTRY'. 'NL' will be used as a default. Falling back to the default declared by the plugin: 'NL'.
INFO Loaded plugins: {'repro_plugin': '0.1'}

MY_PLUGIN_TOKEN   = 'repro-token-value'
MY_PLUGIN_COUNTRY = 'NL'
MY_PLUGIN_TIMEOUT = 30

Before this PR, MY_PLUGIN_TOKEN and MY_PLUGIN_TIMEOUT were both None and reported as missing, and the MY_PLUGIN_COUNTRY warning stopped after 'NL' will be used as a default. while MY_PLUGIN_COUNTRY stayed None.

How to test

pytest flexmeasures/utils/tests/test_plugin_utils.py covers env promotion, config-file precedence, the testing-mode exemption, each parse_as conversion, an unparsable value, and the missing-setting message with and without a default.

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

  • Whether read_env_vars() should read all settings from the environment (see above) is left open.

Related Items

Closes #2492


Sign-off

  • I agree to contribute to the project under Apache 2 License.
  • To the best of my knowledge, the proposed patch is not based on code under GPL or other license that is incompatible with FlexMeasures

🤖 Generated with Claude Code

https://claude.ai/code/session_01X2KPuf29L3FLQhfcvdg85x

Flix6x and others added 4 commits September 9, 2026 08:21
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>
@read-the-docs-community

read-the-docs-community Bot commented Sep 9, 2026

Copy link
Copy Markdown

Documentation build overview

📚 flexmeasures | 🛠️ Build #34465422 | 📁 Comparing b453918 against latest (b598863)

  🔍 Preview build  

5 files changed · ± 5 modified

± Modified

Flix6x and others added 2 commits September 9, 2026 09:07
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 default key 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.

Comment thread flexmeasures/utils/plugin_utils.py Outdated
Comment thread flexmeasures/utils/tests/test_plugin_utils.py Outdated
Comment on lines 324 to 326
description = (
f" ({setting_fields['description']})" if "description" in setting_fields else ""
)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Flix6x and others added 2 commits September 9, 2026 09:16
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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

@Flix6x
Flix6x requested a review from nhoening September 9, 2026 07:22
@Flix6x

Flix6x commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

@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 __settings__ are looked up in the environment when the plugin registers, and only if still unset — so the config file keeps precedence, matching the order read_env_vars() uses for FlexMeasures' own settings. I did not go all the way to "read everything from env", since that would pull every environment variable (PATH, cloud credentials, …) into the Flask config where every plugin and template can see it. That broader change is left open in the PR description if you'd rather have it.

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 Falling back to the default declared by the plugin: 'NL'. or No default is declared for it, so it stays unset. A plugin can now declare a real default, which is applied and type-checked like any other value.

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).

@Flix6x Flix6x self-assigned this Sep 9, 2026
@Flix6x Flix6x added this to the 1.1.0 milestone Sep 9, 2026

@nhoening nhoening left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Comment thread flexmeasures/utils/plugin_utils.py Outdated
Co-authored-by: Nicolas Höning <nicolas@seita.nl>
Signed-off-by: Felix Claessen <30658763+Flix6x@users.noreply.github.com>
@Flix6x
Flix6x merged commit 130b7e9 into main Sep 12, 2026
6 of 7 checks passed
@Flix6x
Flix6x deleted the fix/2492-plugin-settings-from-env branch September 12, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plugin-declared settings aren't read from environment variables, and the warning when they're missing says the wrong thing

3 participants