fix(config): enable documentation mode only explicitly

ConfigEOS.documentation_mode() was true as soon as Sphinx was imported
anywhere in the process. A test importing Sphinx therefore switched all
later tests into documentation mode, and test_config_md_current failed
depending on test order. Documentation mode is now enabled only by the
documentation generators (_force_documentation_mode) or by the Sphinx
configuration, which sets sys._called_from_sphinx in docs/conf.py.

Declare _force_documentation_mode as a ClassVar. Without the annotation
pydantic turned it into a private attribute, and the class attribute was
a truthy descriptor object.

Behavior change: because of that descriptor, every running EOS was in
documentation mode, so the get_adapter/config/measurement/prediction/ems/
database/resource_registry() guards never raised "access before init" and
created the singletons lazily on first access. They now raise for every
access before initialization. The worker process of uvicorn's auto-reload
(server.reload, make run-dev) relied on that: the app's lifespan now sets up
the configuration and initializes the singletons that do not exist yet.

Leave runtime state out of the documentation: the Home Assistant entity ID
properties and OptimizationCommonSettings.keys return an empty list in
documentation mode, so entity IDs and solution keys of a running system
no longer reach the generated configuration docs.

Keep the tests isolated: test_doc sets documentation mode on the class
and test_docstringrst restores sys._called_from_sphinx after reading the
Sphinx configuration. Add tests for how documentation mode is enabled and
for the runtime values it leaves out.

Additional changes
- Generate openapi.json in documentation mode, as the config docs.
- Share the documentation mode check as coreabc.documentation_mode().
- Move the logging and server address setup of run_eos() to setup_eos_config().
- Test that EOS starts with server.reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bobby Noelte
2026-11-09 12:40:15 +01:00
co-authored by Claude Opus 5.5
parent a0e57a18b7
commit 57132df44e
12 changed files with 293 additions and 29 deletions
+39
View File
@@ -885,3 +885,42 @@ class TestConfigProviderValidation:
for section, value in providers.items():
assert getattr(config, section).provider == value["provider"]
class TestDocumentationMode:
"""Documentation mode is enabled explicitly, never by an imported Sphinx."""
def test_imported_sphinx_does_not_enable_it(self, monkeypatch):
import sphinx # noqa: F401 # the module is in sys.modules now
monkeypatch.delattr("sys._called_from_sphinx", raising=False)
monkeypatch.setattr(ConfigEOS, "_force_documentation_mode", False)
assert not ConfigEOS.documentation_mode()
def test_sphinx_configuration_enables_it(self, monkeypatch):
monkeypatch.setattr("sys._called_from_sphinx", True, raising=False)
monkeypatch.setattr(ConfigEOS, "_force_documentation_mode", False)
assert ConfigEOS.documentation_mode()
def test_documentation_generators_enable_it(self, monkeypatch):
monkeypatch.delattr("sys._called_from_sphinx", raising=False)
monkeypatch.setattr(ConfigEOS, "_force_documentation_mode", True)
assert ConfigEOS.documentation_mode()
def test_optimization_solution_keys_are_left_out(self, monkeypatch):
"""Keys of a solution of a previous run do not reach the documentation."""
from akkudoktoreos.optimization import optimization
from akkudoktoreos.optimization.optimization import OptimizationCommonSettings
solution = Mock()
solution.solution.to_dataframe.return_value.columns.tolist.return_value = ["b", "a"]
ems = Mock()
ems.optimization_solution.return_value = solution
monkeypatch.setattr(optimization, "get_ems", lambda: ems)
monkeypatch.delattr("sys._called_from_sphinx", raising=False)
settings = OptimizationCommonSettings()
monkeypatch.setattr(ConfigEOS, "_force_documentation_mode", False)
assert settings.keys == ["a", "b"]
monkeypatch.setattr(ConfigEOS, "_force_documentation_mode", True)
assert settings.keys == []