From e9355cf4ea64b313b221efe79b61446a3e28a0ad Mon Sep 17 00:00:00 2001 From: ~chrstian polzer <~c.polzer@hai-fai.de> Date: Mon, 23 Mar 2026 15:19:45 +0100 Subject: [PATCH 01/10] =?UTF-8?q?=F0=9F=94=A7=20Add=20sphinx-needs=208.0.0?= =?UTF-8?q?=20compatibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace deprecated add_extra_option() with add_field() for sphinx-needs >= 8.0.0, using a version-gated shim that falls back to add_extra_option() for older versions. Also adds sphinx-needs 8.0.0 to the CI and nox test matrices, fixes the warning check in tests to also inspect stderr (where Sphinx writes warnings), adds a test asserting the test-file need node renders correctly, and removes the broken taplo pre-commit hook (taplo 0.9.3 PyPI package fails to build on this platform). --- .github/workflows/ci.yaml | 2 +- .pre-commit-config.yaml | 5 -- noxfile.py | 2 +- sphinxcontrib/test_reports/test_reports.py | 75 +++++++++++----------- tests/test_test_file.py | 23 ++++++- 5 files changed, 62 insertions(+), 45 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7104892..aa15fc7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: os: [ubuntu-latest] python-version: ["3.10", "3.12"] # No "3.10", as nose seem to have problems with it sphinx-version: ["5.0", "8.1.3"] - sphinx_needs-version: ["2.1", "4.2", "5.1", "6.3.0"] + sphinx_needs-version: ["2.1", "4.2", "5.1", "6.3.0", "8.0.0"] steps: - uses: actions/checkout@v2 - name: Set Up Python ${{ matrix.python-version }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 37c987c..3096215 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,11 +16,6 @@ repos: args: [--fix] - id: ruff-format - - repo: https://github.com/ComPWA/taplo-pre-commit - rev: v0.9.3 - hooks: - - id: taplo-format - - repo: https://github.com/google/yamlfmt rev: v0.16.0 hooks: diff --git a/noxfile.py b/noxfile.py index 0844b37..ed49cbb 100644 --- a/noxfile.py +++ b/noxfile.py @@ -3,7 +3,7 @@ PYTHON_VERSIONS = ["3.10", "3.12"] SPHINX_VERSIONS = ["5.0", "7.2.5", "8.1.3"] -SPHINX_NEEDS_VERSIONS = ["2.1", "4.2", "5.1", "6.0.0", "6.3.0"] +SPHINX_NEEDS_VERSIONS = ["2.1", "4.2", "5.1", "6.0.0", "6.3.0", "8.0.0"] def run_tests(session, sphinx, sphinx_needs): diff --git a/sphinxcontrib/test_reports/test_reports.py b/sphinxcontrib/test_reports/test_reports.py index 317fc23..edbe3f5 100644 --- a/sphinxcontrib/test_reports/test_reports.py +++ b/sphinxcontrib/test_reports/test_reports.py @@ -9,7 +9,18 @@ from sphinx.config import Config # from docutils import nodes -from sphinx_needs.api import add_dynamic_function, add_extra_option, add_need_type +from sphinx_needs.api import add_dynamic_function, add_need_type + +try: + from sphinx_needs.api import add_field as _add_field + + def _register_field(app, name, schema=None): + _add_field(name, name, schema=schema) +except ImportError: + from sphinx_needs.api import add_extra_option as _add_extra_option + + def _register_field(app, name, schema=None): + _add_extra_option(app, name, **({} if schema is None else {"schema": schema})) from sphinxcontrib.test_reports.directives.test_case import TestCase, TestCaseDirective from sphinxcontrib.test_reports.directives.test_env import EnvReport, EnvReportDirective @@ -177,44 +188,34 @@ def sphinx_needs_update(app: Sphinx, config: Config) -> None: sphinx-needs configuration """ - # Check sphinx-needs version to determine if schema is needed - try: - needs_version = Version(sphinx_needs.__version__) - use_schema = needs_version >= Version("6.0.0") - except ImportError: - # If we can't determine version, assume older version - use_schema = False - - # Extra options - # For details read - # https://sphinx-needs.readthedocs.io/en/latest/api.html#sphinx_needs.api.configuration.add_extra_option - - add_extra_option(app, getattr(config, "tr_file_option", "file")) - - add_extra_option(app, "suite") - add_extra_option(app, "case") - add_extra_option(app, "case_name") - add_extra_option(app, "case_parameter") - add_extra_option(app, "classname") - # Add schema parameter conditionally based on sphinx-needs version + needs_version = Version(sphinx_needs.__version__) + use_schema = needs_version >= Version("6.0.0") + + _register_field(app, getattr(config, "tr_file_option", "file")) + _register_field(app, "suite") + _register_field(app, "case") + _register_field(app, "case_name") + _register_field(app, "case_parameter") + _register_field(app, "classname") + if use_schema: - add_extra_option(app, "time", schema={"type": "string"}) - add_extra_option(app, "suites", schema={"type": "integer"}) - add_extra_option(app, "cases", schema={"type": "integer"}) - add_extra_option(app, "passed", schema={"type": "integer"}) - add_extra_option(app, "skipped", schema={"type": "integer"}) - add_extra_option(app, "failed", schema={"type": "integer"}) - add_extra_option(app, "errors", schema={"type": "integer"}) - add_extra_option(app, "result", schema={"type": "string"}) + _register_field(app, "time", schema={"type": "string"}) + _register_field(app, "suites", schema={"type": "integer"}) + _register_field(app, "cases", schema={"type": "integer"}) + _register_field(app, "passed", schema={"type": "integer"}) + _register_field(app, "skipped", schema={"type": "integer"}) + _register_field(app, "failed", schema={"type": "integer"}) + _register_field(app, "errors", schema={"type": "integer"}) + _register_field(app, "result", schema={"type": "string"}) else: - add_extra_option(app, "time") - add_extra_option(app, "suites") - add_extra_option(app, "cases") - add_extra_option(app, "passed") - add_extra_option(app, "skipped") - add_extra_option(app, "failed") - add_extra_option(app, "errors") - add_extra_option(app, "result") + _register_field(app, "time") + _register_field(app, "suites") + _register_field(app, "cases") + _register_field(app, "passed") + _register_field(app, "skipped") + _register_field(app, "failed") + _register_field(app, "errors") + _register_field(app, "result") # Extra dynamic functions # For details about usage read # https://sphinx-needs.readthedocs.io/en/latest/api.html#sphinx_needs.api.configuration.add_dynamic_function diff --git a/tests/test_test_file.py b/tests/test_test_file.py index d43d684..886152f 100644 --- a/tests/test_test_file.py +++ b/tests/test_test_file.py @@ -36,5 +36,26 @@ def test_test_file_needs_extra_options_no_warning(test_app): assert out.returncode == 0 - # Check no warnings + # Check no warnings — Sphinx writes warnings to stderr, not stdout assert "WARNING" not in out.stdout.decode("utf-8") + assert "WARNING" not in out.stderr.decode("utf-8") + assert "ERROR" not in out.stderr.decode("utf-8") + + +@pytest.mark.parametrize( + "test_app", + [{"buildername": "html", "srcdir": "doc_test/doc_test_file"}], + indirect=True, +) +def test_test_file_renders_need_with_counts(test_app): + """test-file directive must render a need node with correct count fields. + + Catches regressions where int values (suites, cases, passed, etc.) passed + to add_need() are rejected by newer sphinx-needs versions. + """ + app = test_app + app.build() + assert app.statuscode == 0 + + html = Path(app.outdir, "index.html").read_text() + assert "TESTFILE_1" in html From 81049894f9b2fb0825610ba55fa9b76ac2e35315 Mon Sep 17 00:00:00 2001 From: ~chrstian polzer <~c.polzer@hai-fai.de> Date: Mon, 23 Mar 2026 15:23:25 +0100 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=94=A7=20Restore=20taplo=20pre-comm?= =?UTF-8?q?it=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3096215..37c987c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,6 +16,11 @@ repos: args: [--fix] - id: ruff-format + - repo: https://github.com/ComPWA/taplo-pre-commit + rev: v0.9.3 + hooks: + - id: taplo-format + - repo: https://github.com/google/yamlfmt rev: v0.16.0 hooks: From 35a31033cc1561c7da7051dd86ece3eae4d8102e Mon Sep 17 00:00:00 2001 From: ~chrstian polzer <~c.polzer@hai-fai.de> Date: Mon, 23 Mar 2026 16:31:44 +0100 Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=94=A7=20Fix=20remaining=20sphinx-n?= =?UTF-8?q?eeds=208.0.0=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add schema to string fields in _register_field (moved into use_schema branch for backwards compat with sphinx-needs < 6.0.0). Update all test fixture conf.py files to use needs_fields for sphinx-needs >= 8.0.0 instead of the deprecated needs_extra_options. --- sphinxcontrib/test_reports/test_reports.py | 19 ++++++++++++------- tests/doc_test/basic_doc/conf.py | 8 +++++++- tests/doc_test/doc_test_ctest_file/conf.py | 8 +++++++- tests/doc_test/doc_test_file/conf.py | 8 +++++++- tests/doc_test/json_parser_custom/conf.py | 8 +++++++- tests/doc_test/many_testsuites_doc/conf.py | 8 +++++++- tests/doc_test/needs_linking/conf.py | 8 +++++++- tests/doc_test/testsuites_doc/conf.py | 8 +++++++- 8 files changed, 61 insertions(+), 14 deletions(-) diff --git a/sphinxcontrib/test_reports/test_reports.py b/sphinxcontrib/test_reports/test_reports.py index edbe3f5..4e2c2e8 100644 --- a/sphinxcontrib/test_reports/test_reports.py +++ b/sphinxcontrib/test_reports/test_reports.py @@ -191,14 +191,13 @@ def sphinx_needs_update(app: Sphinx, config: Config) -> None: needs_version = Version(sphinx_needs.__version__) use_schema = needs_version >= Version("6.0.0") - _register_field(app, getattr(config, "tr_file_option", "file")) - _register_field(app, "suite") - _register_field(app, "case") - _register_field(app, "case_name") - _register_field(app, "case_parameter") - _register_field(app, "classname") - if use_schema: + _register_field(app, getattr(config, "tr_file_option", "file"), schema={"type": "string"}) + _register_field(app, "suite", schema={"type": "string"}) + _register_field(app, "case", schema={"type": "string"}) + _register_field(app, "case_name", schema={"type": "string"}) + _register_field(app, "case_parameter", schema={"type": "string"}) + _register_field(app, "classname", schema={"type": "string"}) _register_field(app, "time", schema={"type": "string"}) _register_field(app, "suites", schema={"type": "integer"}) _register_field(app, "cases", schema={"type": "integer"}) @@ -208,6 +207,12 @@ def sphinx_needs_update(app: Sphinx, config: Config) -> None: _register_field(app, "errors", schema={"type": "integer"}) _register_field(app, "result", schema={"type": "string"}) else: + _register_field(app, getattr(config, "tr_file_option", "file")) + _register_field(app, "suite") + _register_field(app, "case") + _register_field(app, "case_name") + _register_field(app, "case_parameter") + _register_field(app, "classname") _register_field(app, "time") _register_field(app, "suites") _register_field(app, "cases") diff --git a/tests/doc_test/basic_doc/conf.py b/tests/doc_test/basic_doc/conf.py index c52a5a3..4776152 100644 --- a/tests/doc_test/basic_doc/conf.py +++ b/tests/doc_test/basic_doc/conf.py @@ -69,7 +69,13 @@ # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] -needs_extra_options = ["more_info"] +import sphinx_needs as _sn +from packaging.version import Version as _V + +if _V(_sn.__version__) >= _V("8.0.0"): + needs_fields = {"more_info": {"nullable": True}} +else: + needs_extra_options = ["more_info"] tr_extra_options = ["more_info"] # The name of the Pygments (syntax highlighting) style to use. diff --git a/tests/doc_test/doc_test_ctest_file/conf.py b/tests/doc_test/doc_test_ctest_file/conf.py index 7018809..869be3f 100644 --- a/tests/doc_test/doc_test_ctest_file/conf.py +++ b/tests/doc_test/doc_test_ctest_file/conf.py @@ -75,7 +75,13 @@ # The master toctree document. master_doc = "index" -needs_extra_options = ["asil", "uses_secure", "more_info"] +import sphinx_needs as _sn +from packaging.version import Version as _V + +if _V(_sn.__version__) >= _V("8.0.0"): + needs_fields = {"asil": {"nullable": True}, "uses_secure": {"nullable": True}, "more_info": {"nullable": True}} +else: + needs_extra_options = ["asil", "uses_secure", "more_info"] tr_extra_options = ["more_info"] # General information about the project. diff --git a/tests/doc_test/doc_test_file/conf.py b/tests/doc_test/doc_test_file/conf.py index 3b85374..d4f513f 100644 --- a/tests/doc_test/doc_test_file/conf.py +++ b/tests/doc_test/doc_test_file/conf.py @@ -75,7 +75,13 @@ # The master toctree document. master_doc = "index" -needs_extra_options = ["asil", "uses_secure", "more_info"] +import sphinx_needs as _sn +from packaging.version import Version as _V + +if _V(_sn.__version__) >= _V("8.0.0"): + needs_fields = {"asil": {"nullable": True}, "uses_secure": {"nullable": True}, "more_info": {"nullable": True}} +else: + needs_extra_options = ["asil", "uses_secure", "more_info"] tr_extra_options = ["more_info"] # General information about the project. diff --git a/tests/doc_test/json_parser_custom/conf.py b/tests/doc_test/json_parser_custom/conf.py index a9624f5..8b21e16 100644 --- a/tests/doc_test/json_parser_custom/conf.py +++ b/tests/doc_test/json_parser_custom/conf.py @@ -32,7 +32,13 @@ extensions = ["sphinx_needs", "sphinxcontrib.test_reports"] -needs_extra_options = ["priority"] +import sphinx_needs as _sn +from packaging.version import Version as _V + +if _V(_sn.__version__) >= _V("8.0.0"): + needs_fields = {"priority": {"nullable": True}} +else: + needs_extra_options = ["priority"] tr_extra_options = ["priority"] tr_json_mapping = { diff --git a/tests/doc_test/many_testsuites_doc/conf.py b/tests/doc_test/many_testsuites_doc/conf.py index 48573a1..e10372a 100644 --- a/tests/doc_test/many_testsuites_doc/conf.py +++ b/tests/doc_test/many_testsuites_doc/conf.py @@ -37,7 +37,13 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -needs_extra_options = ["more_info"] +import sphinx_needs as _sn +from packaging.version import Version as _V + +if _V(_sn.__version__) >= _V("8.0.0"): + needs_fields = {"more_info": {"nullable": True}} +else: + needs_extra_options = ["more_info"] tr_extra_options = ["more_info"] # The suffix(es) of source filenames. diff --git a/tests/doc_test/needs_linking/conf.py b/tests/doc_test/needs_linking/conf.py index bf804a8..550b1ae 100644 --- a/tests/doc_test/needs_linking/conf.py +++ b/tests/doc_test/needs_linking/conf.py @@ -75,7 +75,13 @@ # The master toctree document. master_doc = "index" -needs_extra_options = ["asil", "uses_secure", "references"] +import sphinx_needs as _sn +from packaging.version import Version as _V + +if _V(_sn.__version__) >= _V("8.0.0"): + needs_fields = {"asil": {"nullable": True}, "uses_secure": {"nullable": True}, "references": {"nullable": True}} +else: + needs_extra_options = ["asil", "uses_secure", "references"] # General information about the project. project = "test-report test docs" diff --git a/tests/doc_test/testsuites_doc/conf.py b/tests/doc_test/testsuites_doc/conf.py index 781476d..1186c5b 100644 --- a/tests/doc_test/testsuites_doc/conf.py +++ b/tests/doc_test/testsuites_doc/conf.py @@ -34,7 +34,13 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -needs_extra_options = ["more_info"] +import sphinx_needs as _sn +from packaging.version import Version as _V + +if _V(_sn.__version__) >= _V("8.0.0"): + needs_fields = {"more_info": {"nullable": True}} +else: + needs_extra_options = ["more_info"] tr_extra_options = ["more_info"] # The suffix(es) of source filenames. From 69b1a7e5370af9811a9e6cd434491c3eda9e617c Mon Sep 17 00:00:00 2001 From: ~chrstian polzer <~c.polzer@hai-fai.de> Date: Mon, 23 Mar 2026 16:34:53 +0100 Subject: [PATCH 04/10] =?UTF-8?q?=F0=9F=94=A7=20Fix=20ruff=20linting=20err?= =?UTF-8?q?ors=20in=20conf.py=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move sphinx_needs and Version imports to top of file (fixes E402) and drop incorrect _V alias for Version (fixes N814). --- sphinxcontrib/test_reports/test_reports.py | 4 +++- tests/doc_test/basic_doc/conf.py | 8 ++++---- tests/doc_test/doc_test_ctest_file/conf.py | 14 +++++++++----- tests/doc_test/doc_test_file/conf.py | 14 +++++++++----- tests/doc_test/json_parser_custom/conf.py | 8 ++++---- tests/doc_test/many_testsuites_doc/conf.py | 8 ++++---- tests/doc_test/needs_linking/conf.py | 14 +++++++++----- tests/doc_test/testsuites_doc/conf.py | 8 ++++---- 8 files changed, 46 insertions(+), 32 deletions(-) diff --git a/sphinxcontrib/test_reports/test_reports.py b/sphinxcontrib/test_reports/test_reports.py index 4e2c2e8..cc2aa56 100644 --- a/sphinxcontrib/test_reports/test_reports.py +++ b/sphinxcontrib/test_reports/test_reports.py @@ -192,7 +192,9 @@ def sphinx_needs_update(app: Sphinx, config: Config) -> None: use_schema = needs_version >= Version("6.0.0") if use_schema: - _register_field(app, getattr(config, "tr_file_option", "file"), schema={"type": "string"}) + _register_field( + app, getattr(config, "tr_file_option", "file"), schema={"type": "string"} + ) _register_field(app, "suite", schema={"type": "string"}) _register_field(app, "case", schema={"type": "string"}) _register_field(app, "case_name", schema={"type": "string"}) diff --git a/tests/doc_test/basic_doc/conf.py b/tests/doc_test/basic_doc/conf.py index 4776152..f178b2d 100644 --- a/tests/doc_test/basic_doc/conf.py +++ b/tests/doc_test/basic_doc/conf.py @@ -18,6 +18,9 @@ import os import sys +import sphinx_needs +from packaging.version import Version + sys.path.insert(0, os.path.abspath("../../sphinxcontrib")) # -- General configuration ------------------------------------------------ @@ -69,10 +72,7 @@ # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] -import sphinx_needs as _sn -from packaging.version import Version as _V - -if _V(_sn.__version__) >= _V("8.0.0"): +if Version(sphinx_needs.__version__) >= Version("8.0.0"): needs_fields = {"more_info": {"nullable": True}} else: needs_extra_options = ["more_info"] diff --git a/tests/doc_test/doc_test_ctest_file/conf.py b/tests/doc_test/doc_test_ctest_file/conf.py index 869be3f..7d58f6b 100644 --- a/tests/doc_test/doc_test_ctest_file/conf.py +++ b/tests/doc_test/doc_test_ctest_file/conf.py @@ -18,6 +18,9 @@ import os import sys +import sphinx_needs +from packaging.version import Version + sys.path.insert(0, os.path.abspath("../../sphinxcontrib")) # -- General configuration ------------------------------------------------ @@ -75,11 +78,12 @@ # The master toctree document. master_doc = "index" -import sphinx_needs as _sn -from packaging.version import Version as _V - -if _V(_sn.__version__) >= _V("8.0.0"): - needs_fields = {"asil": {"nullable": True}, "uses_secure": {"nullable": True}, "more_info": {"nullable": True}} +if Version(sphinx_needs.__version__) >= Version("8.0.0"): + needs_fields = { + "asil": {"nullable": True}, + "uses_secure": {"nullable": True}, + "more_info": {"nullable": True}, + } else: needs_extra_options = ["asil", "uses_secure", "more_info"] tr_extra_options = ["more_info"] diff --git a/tests/doc_test/doc_test_file/conf.py b/tests/doc_test/doc_test_file/conf.py index d4f513f..e858a34 100644 --- a/tests/doc_test/doc_test_file/conf.py +++ b/tests/doc_test/doc_test_file/conf.py @@ -18,6 +18,9 @@ import os import sys +import sphinx_needs +from packaging.version import Version + sys.path.insert(0, os.path.abspath("../../sphinxcontrib")) # -- General configuration ------------------------------------------------ @@ -75,11 +78,12 @@ # The master toctree document. master_doc = "index" -import sphinx_needs as _sn -from packaging.version import Version as _V - -if _V(_sn.__version__) >= _V("8.0.0"): - needs_fields = {"asil": {"nullable": True}, "uses_secure": {"nullable": True}, "more_info": {"nullable": True}} +if Version(sphinx_needs.__version__) >= Version("8.0.0"): + needs_fields = { + "asil": {"nullable": True}, + "uses_secure": {"nullable": True}, + "more_info": {"nullable": True}, + } else: needs_extra_options = ["asil", "uses_secure", "more_info"] tr_extra_options = ["more_info"] diff --git a/tests/doc_test/json_parser_custom/conf.py b/tests/doc_test/json_parser_custom/conf.py index 8b21e16..7dc64c4 100644 --- a/tests/doc_test/json_parser_custom/conf.py +++ b/tests/doc_test/json_parser_custom/conf.py @@ -18,6 +18,9 @@ import os import sys +import sphinx_needs +from packaging.version import Version + sys.path.insert(0, os.path.abspath("../../sphinxcontrib")) # -- General configuration ------------------------------------------------ @@ -32,10 +35,7 @@ extensions = ["sphinx_needs", "sphinxcontrib.test_reports"] -import sphinx_needs as _sn -from packaging.version import Version as _V - -if _V(_sn.__version__) >= _V("8.0.0"): +if Version(sphinx_needs.__version__) >= Version("8.0.0"): needs_fields = {"priority": {"nullable": True}} else: needs_extra_options = ["priority"] diff --git a/tests/doc_test/many_testsuites_doc/conf.py b/tests/doc_test/many_testsuites_doc/conf.py index e10372a..9c86fd9 100644 --- a/tests/doc_test/many_testsuites_doc/conf.py +++ b/tests/doc_test/many_testsuites_doc/conf.py @@ -18,6 +18,9 @@ import os import sys +import sphinx_needs +from packaging.version import Version + sys.path.insert(0, os.path.abspath("../../sphinxcontrib")) # -- General configuration ------------------------------------------------ @@ -37,10 +40,7 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -import sphinx_needs as _sn -from packaging.version import Version as _V - -if _V(_sn.__version__) >= _V("8.0.0"): +if Version(sphinx_needs.__version__) >= Version("8.0.0"): needs_fields = {"more_info": {"nullable": True}} else: needs_extra_options = ["more_info"] diff --git a/tests/doc_test/needs_linking/conf.py b/tests/doc_test/needs_linking/conf.py index 550b1ae..b256d83 100644 --- a/tests/doc_test/needs_linking/conf.py +++ b/tests/doc_test/needs_linking/conf.py @@ -18,6 +18,9 @@ import os import sys +import sphinx_needs +from packaging.version import Version + sys.path.insert(0, os.path.abspath("../../sphinxcontrib")) # -- General configuration ------------------------------------------------ @@ -75,11 +78,12 @@ # The master toctree document. master_doc = "index" -import sphinx_needs as _sn -from packaging.version import Version as _V - -if _V(_sn.__version__) >= _V("8.0.0"): - needs_fields = {"asil": {"nullable": True}, "uses_secure": {"nullable": True}, "references": {"nullable": True}} +if Version(sphinx_needs.__version__) >= Version("8.0.0"): + needs_fields = { + "asil": {"nullable": True}, + "uses_secure": {"nullable": True}, + "references": {"nullable": True}, + } else: needs_extra_options = ["asil", "uses_secure", "references"] diff --git a/tests/doc_test/testsuites_doc/conf.py b/tests/doc_test/testsuites_doc/conf.py index 1186c5b..d362ae2 100644 --- a/tests/doc_test/testsuites_doc/conf.py +++ b/tests/doc_test/testsuites_doc/conf.py @@ -18,6 +18,9 @@ import os import sys +import sphinx_needs +from packaging.version import Version + sys.path.insert(0, os.path.abspath("../../sphinxcontrib")) # -- General configuration ------------------------------------------------ @@ -34,10 +37,7 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] -import sphinx_needs as _sn -from packaging.version import Version as _V - -if _V(_sn.__version__) >= _V("8.0.0"): +if Version(sphinx_needs.__version__) >= Version("8.0.0"): needs_fields = {"more_info": {"nullable": True}} else: needs_extra_options = ["more_info"] From 25ef59ca30f4d486c06e6cb02570c896b55a8f2c Mon Sep 17 00:00:00 2001 From: ~chrstian polzer <~c.polzer@hai-fai.de> Date: Mon, 23 Mar 2026 18:51:46 +0100 Subject: [PATCH 05/10] docs_build: handling update to sphinx-needs shimed --- docs/conf.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 7f0afdf..c38d755 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,9 @@ # import os import sys +import sphinx_needs +from packaging.version import Version + sys.path.append(os.path.abspath(".")) from ub_theme.conf import html_theme_options @@ -67,7 +70,13 @@ # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates", "ub_theme/templates"] -needs_extra_options = ["more_info"] +if Version(sphinx_needs.__version__) >= Version("8.0.0"): + needs_fields = { + "more_info": {"nullable": True}, + } +else: + needs_extra_options = ["more_info"] + tr_extra_options = ["more_info"] # Add a custom test report template. Please add a relative path from this conf.py # tr_report_template = "./custom_test_report_template.txt" From 254be6460f84f3a0b42d9f11a09a01da39d465c0 Mon Sep 17 00:00:00 2001 From: Christian Polzer Date: Mon, 13 Apr 2026 22:12:21 +0200 Subject: [PATCH 06/10] =?UTF-8?q?=F0=9F=94=A7=20Add=20proper=20field=20des?= =?UTF-8?q?criptions=20for=20sphinx-needs=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sphinxcontrib/test_reports/test_reports.py | 24 ++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/sphinxcontrib/test_reports/test_reports.py b/sphinxcontrib/test_reports/test_reports.py index cc2aa56..84f2832 100644 --- a/sphinxcontrib/test_reports/test_reports.py +++ b/sphinxcontrib/test_reports/test_reports.py @@ -11,16 +11,36 @@ # from docutils import nodes from sphinx_needs.api import add_dynamic_function, add_need_type +# Field descriptions for better semantics +FIELD_DESCRIPTIONS = { + "file": "Test file name", + "suite": "Test suite name", + "case": "Test case name", + "case_name": "Test case display name", + "case_parameter": "Test case parameter", + "classname": "Test class name", + "time": "Test execution time", + "suites": "Number of test suites", + "cases": "Number of test cases", + "passed": "Number of passed tests", + "skipped": "Number of skipped tests", + "failed": "Number of failed tests", + "errors": "Number of test errors", + "result": "Test result status", +} + try: from sphinx_needs.api import add_field as _add_field def _register_field(app, name, schema=None): - _add_field(name, name, schema=schema) + description = FIELD_DESCRIPTIONS.get(name, name) + _add_field(name, description, schema=schema) except ImportError: from sphinx_needs.api import add_extra_option as _add_extra_option def _register_field(app, name, schema=None): - _add_extra_option(app, name, **({} if schema is None else {"schema": schema})) + description = FIELD_DESCRIPTIONS.get(name, name) + _add_extra_option(app, name, description=description, **({} if schema is None else {"schema": schema})) from sphinxcontrib.test_reports.directives.test_case import TestCase, TestCaseDirective from sphinxcontrib.test_reports.directives.test_env import EnvReport, EnvReportDirective From 5e42308f14e73cd920cb7c9a46038d637e346ba4 Mon Sep 17 00:00:00 2001 From: Christian Polzer Date: Mon, 13 Apr 2026 22:14:01 +0200 Subject: [PATCH 07/10] =?UTF-8?q?=F0=9F=94=A7=20Add=20explicit=20packaging?= =?UTF-8?q?=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a0814b0..f60ac30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ ] keywords = ["sphinx", "documentation", "test-reports"] requires-python = ">=3.10" -dependencies = ["sphinx>4.0", "lxml", "sphinx-needs>=1.0.1"] +dependencies = ["sphinx>4.0", "lxml", "sphinx-needs>=1.0.1", "packaging>=20.0"] [project.optional-dependencies] test = [ From 5d3935074e95c5cf9d54207077c3c64875cf887c Mon Sep 17 00:00:00 2001 From: Christian Polzer Date: Mon, 13 Apr 2026 22:17:59 +0200 Subject: [PATCH 08/10] =?UTF-8?q?=F0=9F=94=A7=20Enhance=20test=20to=20veri?= =?UTF-8?q?fy=20integer=20count=20fields=20are=20rendered?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_test_file.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_test_file.py b/tests/test_test_file.py index 886152f..c84d468 100644 --- a/tests/test_test_file.py +++ b/tests/test_test_file.py @@ -59,3 +59,20 @@ def test_test_file_renders_need_with_counts(test_app): html = Path(app.outdir, "index.html").read_text() assert "TESTFILE_1" in html + + # Test 1: Verify HTML contains the need with expected structure + from bs4 import BeautifulSoup + soup = BeautifulSoup(html, 'html.parser') + need = soup.find(class_='need', attrs={'data-need-id': 'TESTFILE_1'}) + assert need is not None + + # Test 2: Verify data attributes for integer fields exist + required_fields = ['suites', 'cases', 'passed', 'skipped', 'failed', 'errors'] + for field in required_fields: + assert f'data-{field}' in need.attrs, f"Missing data-{field} attribute" + value = need[f'data-{field}'] + assert value.isdigit(), f"data-{field} should be numeric, got: {value}" + + # Test 3: Optional - Verify actual values are reasonable + assert int(need['data-suites']) >= 0 # Should be non-negative + assert int(need['data-cases']) >= 0 # Should be non-negative From 8bb0f9ac75c7ab101bf5d485e13c1ad415cfd6f1 Mon Sep 17 00:00:00 2001 From: Christian Polzer Date: Mon, 13 Apr 2026 22:20:02 +0200 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=94=A7=20Fix=20warning=20check=20lo?= =?UTF-8?q?gic=20in=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 1 + sphinxcontrib/test_reports/test_reports.py | 72 ++++++++++++---------- tests/test_test_file.py | 48 +++++++++------ 3 files changed, 71 insertions(+), 50 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f60ac30..c7f87bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ dependencies = ["sphinx>4.0", "lxml", "sphinx-needs>=1.0.1", "packaging>=20.0"] [project.optional-dependencies] test = [ + "beautifulsoup4", "nox>=2025.2.9", "pytest-xdist", "pytest>=7.0", diff --git a/sphinxcontrib/test_reports/test_reports.py b/sphinxcontrib/test_reports/test_reports.py index 84f2832..ec30890 100644 --- a/sphinxcontrib/test_reports/test_reports.py +++ b/sphinxcontrib/test_reports/test_reports.py @@ -1,4 +1,5 @@ # fmt: off +import inspect import os import sphinx @@ -11,37 +12,6 @@ # from docutils import nodes from sphinx_needs.api import add_dynamic_function, add_need_type -# Field descriptions for better semantics -FIELD_DESCRIPTIONS = { - "file": "Test file name", - "suite": "Test suite name", - "case": "Test case name", - "case_name": "Test case display name", - "case_parameter": "Test case parameter", - "classname": "Test class name", - "time": "Test execution time", - "suites": "Number of test suites", - "cases": "Number of test cases", - "passed": "Number of passed tests", - "skipped": "Number of skipped tests", - "failed": "Number of failed tests", - "errors": "Number of test errors", - "result": "Test result status", -} - -try: - from sphinx_needs.api import add_field as _add_field - - def _register_field(app, name, schema=None): - description = FIELD_DESCRIPTIONS.get(name, name) - _add_field(name, description, schema=schema) -except ImportError: - from sphinx_needs.api import add_extra_option as _add_extra_option - - def _register_field(app, name, schema=None): - description = FIELD_DESCRIPTIONS.get(name, name) - _add_extra_option(app, name, description=description, **({} if schema is None else {"schema": schema})) - from sphinxcontrib.test_reports.directives.test_case import TestCase, TestCaseDirective from sphinxcontrib.test_reports.directives.test_env import EnvReport, EnvReportDirective from sphinxcontrib.test_reports.directives.test_file import TestFile, TestFileDirective @@ -70,6 +40,46 @@ def _register_field(app, name, schema=None): VERSION = "1.3.2" +# Field descriptions for better semantics +FIELD_DESCRIPTIONS = { + "file": "Test file name", + "suite": "Test suite name", + "case": "Test case name", + "case_name": "Test case display name", + "case_parameter": "Test case parameter", + "classname": "Test class name", + "time": "Test execution time", + "suites": "Number of test suites", + "cases": "Number of test cases", + "passed": "Number of passed tests", + "skipped": "Number of skipped tests", + "failed": "Number of failed tests", + "errors": "Number of test errors", + "result": "Test result status", +} + +try: + from sphinx_needs.api import add_field as _add_field + + def _register_field(app, name, schema=None): + description = FIELD_DESCRIPTIONS.get(name, name) + _add_field(name, description, schema=schema) + +except ImportError: + from sphinx_needs.api import add_extra_option as _add_extra_option + + _add_extra_option_supports_description = ( + "description" in inspect.signature(_add_extra_option).parameters + ) + + def _register_field(app, name, schema=None): + kwargs = {} + if _add_extra_option_supports_description: + kwargs["description"] = FIELD_DESCRIPTIONS.get(name, name) + if schema is not None: + kwargs["schema"] = schema + _add_extra_option(app, name, **kwargs) + def setup(app: Sphinx): """ diff --git a/tests/test_test_file.py b/tests/test_test_file.py index c84d468..dfc4c9b 100644 --- a/tests/test_test_file.py +++ b/tests/test_test_file.py @@ -1,6 +1,7 @@ from pathlib import Path import pytest +from bs4 import BeautifulSoup @pytest.mark.parametrize( @@ -37,9 +38,15 @@ def test_test_file_needs_extra_options_no_warning(test_app): assert out.returncode == 0 # Check no warnings — Sphinx writes warnings to stderr, not stdout - assert "WARNING" not in out.stdout.decode("utf-8") - assert "WARNING" not in out.stderr.decode("utf-8") - assert "ERROR" not in out.stderr.decode("utf-8") + stderr = out.stderr.decode("utf-8") + stdout = out.stdout.decode("utf-8") + + # stdout should not contain warnings (Sphinx doesn't write them there) + assert "WARNING" not in stdout, f"Found warnings in stdout (unexpected): {stdout}" + + # stderr should not contain warnings or errors + assert "WARNING" not in stderr, f"Found warnings in stderr: {stderr}" + assert "ERROR" not in stderr, f"Found errors in stderr: {stderr}" @pytest.mark.parametrize( @@ -59,20 +66,23 @@ def test_test_file_renders_need_with_counts(test_app): html = Path(app.outdir, "index.html").read_text() assert "TESTFILE_1" in html - - # Test 1: Verify HTML contains the need with expected structure - from bs4 import BeautifulSoup - soup = BeautifulSoup(html, 'html.parser') - need = soup.find(class_='need', attrs={'data-need-id': 'TESTFILE_1'}) - assert need is not None - - # Test 2: Verify data attributes for integer fields exist - required_fields = ['suites', 'cases', 'passed', 'skipped', 'failed', 'errors'] + + # Verify HTML contains the need table and integer count fields were rendered + # sphinx-needs renders needs as with field values in + # elements. + soup = BeautifulSoup(html, "html.parser") + need_table = soup.find("table", id="TESTFILE_1") + assert need_table is not None, "Need table with id='TESTFILE_1' not found in HTML" + + # Verify integer count fields are present and contain numeric values + required_fields = ["suites", "cases", "passed", "skipped", "failed", "errors"] for field in required_fields: - assert f'data-{field}' in need.attrs, f"Missing data-{field} attribute" - value = need[f'data-{field}'] - assert value.isdigit(), f"data-{field} should be numeric, got: {value}" - - # Test 3: Optional - Verify actual values are reasonable - assert int(need['data-suites']) >= 0 # Should be non-negative - assert int(need['data-cases']) >= 0 # Should be non-negative + span = need_table.find(class_=f"needs_{field}") + assert span is not None, f"Missing in need table" + value = span.get_text(strip=True).replace(f"{field}:", "").strip() + try: + int(value) + except ValueError: + raise AssertionError( + f"needs_{field} value should be numeric, got: {value!r}" + ) from None From 6ec6a7ffb8978281104c1e87c47ec18fc85d9740 Mon Sep 17 00:00:00 2001 From: Christian Polzer Date: Thu, 7 May 2026 22:55:32 +0200 Subject: [PATCH 10/10] fix: Replace broken sphinx-needs.com links with ReadTheDocs --- docs/changelog.rst | 2 +- docs/index.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index ffee589..09bf4e0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -107,7 +107,7 @@ introduces several maintenance improvements. ----- :Released: 26.09.2022 -* Improvement: Supporting `Sphinx-Needs `__ ``>= 1.01`` only. +* Improvement: Supporting `Sphinx-Needs `__ ``>= 1.01`` only. * Improvement: Migrated nosetests to pytest. 0.3.7 diff --git a/docs/index.rst b/docs/index.rst index 928a625..daa9dfe 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -92,7 +92,7 @@ In the last years, we have created additional information and extensions, which .. grid-item-card:: :columns: 12 6 6 6 - :link: https://sphinx-needs.com + :link: https://sphinx-needs.readthedocs.io/en/latest/ :img-top: /_static/sphinx-needs-card.png :class-card: border @@ -103,7 +103,7 @@ In the last years, we have created additional information and extensions, which Also, it is a good entry point to understand the benefits and get an idea about the complete ecosystem of Sphinx-Needs. +++ - .. button-link:: https://sphinx-needs.com + .. button-link:: https://sphinx-needs.readthedocs.io/en/latest/ :color: primary :outline: :align: center