From aec7cce766173c91f7da40fa1adf684365865973 Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Wed, 25 Mar 2026 09:30:48 +0100 Subject: [PATCH 1/2] Update to Sphinx-Needs 7.0.0 Migrate from deprecated needs_extra_options/needs_extra_links to needs_fields/needs_links replace needs_global_options with needs_default_layout remove obsolete filterwarnings --- pyproject.toml | 11 ------- src/extensions/score_layout/__init__.py | 7 ++--- src/extensions/score_layout/sphinx_options.py | 2 +- src/extensions/score_metamodel/__init__.py | 4 +-- src/extensions/score_metamodel/yaml_parser.py | 30 +++++++++---------- src/requirements.in | 5 ++-- src/requirements.txt | 20 +++++++++++-- 7 files changed, 40 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6aa48c6f3..bd47f5590 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,17 +51,6 @@ junit_family = "xunit1" filterwarnings = [ "ignore::pytest.PytestExperimentalApiWarning", - # Silence third-party deprecations from sphinx_needs targeting Python 3.14 removals. - # We'll drop these ignores once sphinx_needs releases a fix. - "ignore:.*deprecated.*Python 3\\.14.*:DeprecationWarning:sphinx_needs\\..*", - # Docutils is deprecating OptionParser in favor of argparse (0.21+). - # This one originates inside sphinx_needs.layout. - # We'll drop these ignores once sphinx/sphinx_needs releases a fix. - "ignore:^The frontend\\.OptionParser class will be replaced by a subclass of argparse\\.ArgumentParser in Docutils 0\\.21 or later\\.:DeprecationWarning:sphinx_needs\\.layout", - # This one bubbles up from stdlib optparse but is *explicitly* a Docutils message. - # We match the full message to avoid silencing unrelated optparse warnings. - # We'll drop these ignores once sphinx/sphinx_needs releases a fix. - "ignore:^The frontend\\.Option class will be removed in Docutils 0\\.21 or later\\.:DeprecationWarning:optparse", ] pythonpath = [ "src/extensions/", diff --git a/src/extensions/score_layout/__init__.py b/src/extensions/score_layout/__init__.py index ce7b128c6..eeb1eb8f6 100644 --- a/src/extensions/score_layout/__init__.py +++ b/src/extensions/score_layout/__init__.py @@ -42,10 +42,9 @@ def update_config(app: Sphinx, _config: Any): **sphinx_options.needs_layouts, **app.config.needs_layouts, } - app.config.needs_global_options = { - **sphinx_options.needs_global_options, - **app.config.needs_global_options, - } + config_setdefault( + app.config, "needs_default_layout", sphinx_options.needs_default_layout + ) config_setdefault(app.config, "html_theme", html_options.html_theme) app.config.html_context = { **html_options.return_html_context(app), diff --git a/src/extensions/score_layout/sphinx_options.py b/src/extensions/score_layout/sphinx_options.py index 141e075a4..ff4774c96 100644 --- a/src/extensions/score_layout/sphinx_options.py +++ b/src/extensions/score_layout/sphinx_options.py @@ -57,4 +57,4 @@ class SingleLayout(TypedDict): }, } -needs_global_options = {"layout": {"default": "score"}} +needs_default_layout = "score" diff --git a/src/extensions/score_metamodel/__init__.py b/src/extensions/score_metamodel/__init__.py index 220cb7676..1a30eba72 100644 --- a/src/extensions/score_metamodel/__init__.py +++ b/src/extensions/score_metamodel/__init__.py @@ -240,8 +240,8 @@ def setup(app: Sphinx) -> dict[str, str | bool]: # Extend sphinx-needs config rather than overwriting app.config.needs_types += metamodel.needs_types - app.config.needs_extra_links += metamodel.needs_extra_links - app.config.needs_extra_options += metamodel.needs_extra_options + app.config.needs_links.update(metamodel.needs_links) + app.config.needs_fields.update(metamodel.needs_fields) app.config.graph_checks = metamodel.needs_graph_check app.config.prohibited_words_checks = metamodel.prohibited_words_checks diff --git a/src/extensions/score_metamodel/yaml_parser.py b/src/extensions/score_metamodel/yaml_parser.py index 64916a903..3f1cf29d4 100644 --- a/src/extensions/score_metamodel/yaml_parser.py +++ b/src/extensions/score_metamodel/yaml_parser.py @@ -30,8 +30,8 @@ @dataclass class MetaModelData: needs_types: list[ScoreNeedType] - needs_extra_links: list[dict[str, str]] - needs_extra_options: list[str] + needs_links: dict[str, dict[str, str]] + needs_fields: dict[str, dict[str, object]] prohibited_words_checks: list[ProhibitedWordCheck] needs_graph_check: dict[str, object] @@ -147,20 +147,17 @@ def _parse_needs_types( return needs_types -def _parse_links(links_dict: dict[str, dict[str, str]]) -> list[dict[str, str]]: +def _parse_links(links_dict: dict[str, dict[str, str]]) -> dict[str, dict[str, str]]: """ - Generate 'needs_extra_links' for sphinx-needs. - - It has a slightly different structure than in our metamodel.yaml. + Generate 'needs_links' for sphinx-needs. """ - return [ - { - "option": k, + return { + k: { "incoming": v["incoming"], "outgoing": v["outgoing"], } for k, v in links_dict.items() - ] + } def _collect_all_options(needs_types: dict[str, ScoreNeedType]) -> set[str]: @@ -173,13 +170,16 @@ def _collect_all_options(needs_types: dict[str, ScoreNeedType]) -> set[str]: def _collect_all_custom_options( needs_types: dict[str, ScoreNeedType], -): - """Generate 'needs_extra_options' for sphinx-needs.""" +) -> dict[str, dict[str, object]]: + """Generate 'needs_fields' entries for sphinx-needs.""" defaults = default_options() all_options = _collect_all_options(needs_types) - return sorted(all_options - defaults) + return { + name: {"schema": {"type": "string"}, "default": ""} + for name in sorted(all_options - defaults) + } def load_metamodel_data() -> MetaModelData: @@ -209,8 +209,8 @@ def load_metamodel_data() -> MetaModelData: return MetaModelData( needs_types=list(needs_types.values()), - needs_extra_links=_parse_links(data.get("needs_extra_links", {})), - needs_extra_options=_collect_all_custom_options(needs_types), + needs_links=_parse_links(data.get("needs_extra_links", {})), + needs_fields=_collect_all_custom_options(needs_types), prohibited_words_checks=prohibited_words_checks, needs_graph_check=data.get("graph_checks", {}), ) diff --git a/src/requirements.in b/src/requirements.in index 8623b48c7..3cb1eed11 100644 --- a/src/requirements.in +++ b/src/requirements.in @@ -1,8 +1,7 @@ Sphinx>=8.1.3,<9 -# At least 4.2.0, as it fixes a bug in combination with esbonio live preview: -# https://github.com/useblocks/sphinx-needs/issues/1350 -sphinx-needs>=6.3.0,<7 +# At least 7.0.0 for Sphinx-Needs 7 support +sphinx-needs>=7.0.0,<8 # Due to needed bugfix in 0.3.1 sphinx-collections>=0.3.1 diff --git a/src/requirements.txt b/src/requirements.txt index 555dcaf03..bccf8993e 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -806,6 +806,20 @@ mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba # via markdown-it-py +minijinja==2.18.0 \ + --hash=sha256:01cd85bdae15c8ce44afd0cadb07808a0b0f55363f3ee3f9459c4e1663707095 \ + --hash=sha256:0b32f66d7138e6eefeabca39583a9d98e3bf8c8c7ebe0e287250dff436f4234f \ + --hash=sha256:168de945c4fe2a12f17e9190b80780646b68e487c341fcde8cba32680f7869a8 \ + --hash=sha256:1928d1522fb53adf50bf5a781068e8ad1b925f0f03579b48b1e98e7ae7526feb \ + --hash=sha256:1bb6a6be7851961a9192b158d436ad6c7b8b6d58e620c5da9d638a7e2f4a6b6d \ + --hash=sha256:2fb4e8a94e9f8337f894842084570e290a1cc39fb306cab457d405a129cf1fc7 \ + --hash=sha256:42daaf7310956b491f50ea356b751ed12d9ebb23e82e7136871afd19f87aab80 \ + --hash=sha256:8b252458a3a51dc09bef48422f68595684984785e40300b426e318c5916c2704 \ + --hash=sha256:c3ccdc1d48939068c08962e6051541fd9a3af67d20ceb08262a540ea9364dcf7 \ + --hash=sha256:d77e7bce8fe2ddd3dfa13f142b2e8d858c3a1ea77c927337a2c9eade0da227bc \ + --hash=sha256:f2116a9b0f1211d42d6b148bb6abd34695b0351f5c8cfa8533a21e7cf109d090 \ + --hash=sha256:f6e9ac8256fc5453e2c5ab91a44201447ea4975c341535a7aa623447b88b7c4e + # via sphinx-needs myst-parser==5.0.0 \ --hash=sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211 \ --hash=sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a @@ -1242,9 +1256,9 @@ sphinx-design==0.7.0 \ --hash=sha256:d2a3f5b19c24b916adb52f97c5f00efab4009ca337812001109084a740ec9b7a \ --hash=sha256:f82bf179951d58f55dca78ab3706aeafa496b741a91b1911d371441127d64282 # via -r src/requirements.in -sphinx-needs[plotting]==6.3.0 \ - --hash=sha256:761901765844c69f6181580065b099b31016895a86962a25e7860a9f5bea54a2 \ - --hash=sha256:a8a1cccc1525b94551e7a2f9525bf36eaae88654abceb5047b5470d57472b346 +sphinx-needs[plotting]==7.0.0 \ + --hash=sha256:3fa665181c323d0b76f6de0d8565f925f36933e8af4c253d34127755eff07371 \ + --hash=sha256:7ea120a1127cc532ea4610e814c26824b05d296ce8c2f6d046d5f6ed64d85d0f # via # -r src/requirements.in # needs-config-writer From 30bc85c3fae285ee65e7673c3768475cae4f21bf Mon Sep 17 00:00:00 2001 From: Andreas Zwinkau Date: Wed, 25 Mar 2026 10:04:55 +0100 Subject: [PATCH 2/2] Fix tests --- .../score_metamodel/tests/test_metamodel_load.py | 15 +++++++++------ src/extensions/score_metamodel/yaml_parser.py | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/extensions/score_metamodel/tests/test_metamodel_load.py b/src/extensions/score_metamodel/tests/test_metamodel_load.py index 3cb679653..9a660887a 100644 --- a/src/extensions/score_metamodel/tests/test_metamodel_load.py +++ b/src/extensions/score_metamodel/tests/test_metamodel_load.py @@ -52,14 +52,17 @@ def test_load_metamodel_data(): assert result.needs_types[0]["mandatory_links"] == {"link1": "value1"} assert result.needs_types[0]["optional_links"] == {"link2": "value2"} - assert len(result.needs_extra_links) == 1 - assert result.needs_extra_links[0] == { - "option": "link_option1", - "incoming": "incoming1", - "outgoing": "outgoing1", + assert result.needs_links == { + "link_option1": { + "incoming": "incoming1", + "outgoing": "outgoing1", + } } - assert result.needs_extra_options == ["global_opt", "opt1", "opt2", "opt3"] + assert result.needs_fields == { + name: {"schema": {"type": "string"}, "default": ""} + for name in ["global_opt", "opt1", "opt2", "opt3"] + } assert result.prohibited_words_checks[0] == ProhibitedWordCheck( name="title_check", option_check={"title": ["stop_word1"]} diff --git a/src/extensions/score_metamodel/yaml_parser.py b/src/extensions/score_metamodel/yaml_parser.py index 3f1cf29d4..f40556bbf 100644 --- a/src/extensions/score_metamodel/yaml_parser.py +++ b/src/extensions/score_metamodel/yaml_parser.py @@ -31,7 +31,7 @@ class MetaModelData: needs_types: list[ScoreNeedType] needs_links: dict[str, dict[str, str]] - needs_fields: dict[str, dict[str, object]] + needs_fields: dict[str, dict[str, Any]] prohibited_words_checks: list[ProhibitedWordCheck] needs_graph_check: dict[str, object] @@ -170,7 +170,7 @@ def _collect_all_options(needs_types: dict[str, ScoreNeedType]) -> set[str]: def _collect_all_custom_options( needs_types: dict[str, ScoreNeedType], -) -> dict[str, dict[str, object]]: +) -> dict[str, dict[str, Any]]: """Generate 'needs_fields' entries for sphinx-needs.""" defaults = default_options()