From ce242bb262afefeda6485bb9423fa6bd3b227c72 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Fri, 25 Apr 2025 21:39:30 +0200 Subject: [PATCH 01/18] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Sphinx=207.4,=20Pyth?= =?UTF-8?q?on=203.13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yaml | 27 +++++++++++++++++---------- pyproject.toml | 3 ++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 60f1a175e..4f2dbc913 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -23,18 +23,25 @@ jobs: fail-fast: false # Set on "false" to get the results of ALL builds matrix: os: ["ubuntu-latest"] - python-version: ["3.10", "3.12"] - sphinx-version: ["7.0", "8.0"] + python-version: ["3.9", "3.12", "3.13"] + sphinx-version: ["7.4", "8.2"] include: - - os: "ubuntu-latest" - python-version: "3.9" - sphinx-version: "7.0" + # corner cases for Windows - os: "windows-latest" python-version: "3.9" - sphinx-version: "7.0" + sphinx-version: "7.4" - os: "windows-latest" python-version: "3.12" - sphinx-version: "8.0" + sphinx-version: "8.2" + - os: "windows-latest" + python-version: "3.13" + sphinx-version: "8.2" + exclude: + # Sphinx 8.2 only supports py3.11+ + - os: "ubuntu-latest" + python-version: "3.9" + sphinx-version: "8.2" + steps: - uses: actions/checkout@v4 - name: Install graphviz (linux) @@ -75,10 +82,10 @@ jobs: include: - os: "ubuntu-latest" python-version: "3.9" - sphinx-version: "7.0" + sphinx-version: "7.4" - os: "ubuntu-latest" - python-version: "3.12" - sphinx-version: "8.0" + python-version: "3.13" + sphinx-version: "8.2" steps: - uses: actions/checkout@v4 - name: Use Node.js diff --git a/pyproject.toml b/pyproject.toml index baf01189d..c9f4635b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,13 +21,14 @@ classifiers = [ 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Topic :: Documentation', 'Topic :: Utilities', 'Framework :: Sphinx :: Extension', ] requires-python = ">=3.9,<4" dependencies = [ - "sphinx>=7.0,<9", + "sphinx>=7.4,<9", "requests-file~=2.1", # external links "requests~=2.32", # external links "jsonschema>=3.2.0", # needsimport schema validation From 023eed900e1cbe5f4380691dbda7eb985f05c00d Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Tue, 29 Apr 2025 09:12:36 +0200 Subject: [PATCH 02/18] Use posixpath for isabs check --- sphinx_needs/directives/needimport.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx_needs/directives/needimport.py b/sphinx_needs/directives/needimport.py index 66454e2c0..d8a5fb6aa 100644 --- a/sphinx_needs/directives/needimport.py +++ b/sphinx_needs/directives/needimport.py @@ -2,6 +2,7 @@ import json import os +import posixpath import re from collections.abc import Sequence from urllib.parse import urlparse @@ -81,7 +82,7 @@ def run(self) -> Sequence[nodes.Node]: else: logger.info(f"Importing needs from {need_import_path}") - if not os.path.isabs(need_import_path): + if not posixpath.isabs(need_import_path): # Relative path should start from current rst file directory curr_dir = os.path.dirname(self.docname) new_need_import_path = os.path.join( From 85ea2c0c5c4c3c0ec3da27b681120ca1a345304d Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Tue, 29 Apr 2025 21:48:49 +0200 Subject: [PATCH 03/18] Fix abspath for needuml --- sphinx_needs/directives/needuml.py | 3 ++- tests/test_needuml.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinx_needs/directives/needuml.py b/sphinx_needs/directives/needuml.py index 92e15ebf1..ac39f4adf 100644 --- a/sphinx_needs/directives/needuml.py +++ b/sphinx_needs/directives/needuml.py @@ -2,6 +2,7 @@ import html import os +import posixpath import time from collections.abc import Sequence from typing import TYPE_CHECKING, Any, TypedDict @@ -107,7 +108,7 @@ def run(self) -> Sequence[nodes.Node]: save_path = self.options.get("save") plantuml_code_out_path = None if save_path: - if os.path.isabs(save_path): + if posixpath.isabs(save_path): raise NeedumlException( f"Given save path: {save_path}, is not a relative path." ) diff --git a/tests/test_needuml.py b/tests/test_needuml.py index 04b6c5ea3..f4acbc15f 100644 --- a/tests/test_needuml.py +++ b/tests/test_needuml.py @@ -126,6 +126,7 @@ def test_needuml_save_with_abs_path(test_app): srcdir = Path(app.srcdir) out_dir = srcdir / "_build" + # this fails before plantuml is required, so the plantuml path is not provided out = subprocess.run( ["sphinx-build", "-M", "html", srcdir, out_dir], capture_output=True ) From 1d69df9b3bc88d24a26e44a0a1ea08ddc285ac7f Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 13:36:04 +0200 Subject: [PATCH 04/18] Use relfn2path --- sphinx_needs/directives/needimport.py | 32 +++------------------------ 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/sphinx_needs/directives/needimport.py b/sphinx_needs/directives/needimport.py index d8a5fb6aa..39f5ac70d 100644 --- a/sphinx_needs/directives/needimport.py +++ b/sphinx_needs/directives/needimport.py @@ -2,7 +2,6 @@ import json import os -import posixpath import re from collections.abc import Sequence from urllib.parse import urlparse @@ -82,34 +81,9 @@ def run(self) -> Sequence[nodes.Node]: else: logger.info(f"Importing needs from {need_import_path}") - if not posixpath.isabs(need_import_path): - # Relative path should start from current rst file directory - curr_dir = os.path.dirname(self.docname) - new_need_import_path = os.path.join( - self.env.app.srcdir, curr_dir, need_import_path - ) - - correct_need_import_path = new_need_import_path - if not os.path.exists(new_need_import_path): - # Check the old way that calculates relative path starting from conf.py directory - old_need_import_path = os.path.join( - self.env.app.srcdir, need_import_path - ) - if os.path.exists(old_need_import_path): - correct_need_import_path = old_need_import_path - log_warning( - logger, - "Deprecation warning: Relative path must be relative to the current document in future, " - "not to the conf.py location. Use a starting '/', like '/needs.json', to make the path " - "relative to conf.py.", - "deprecated", - location=(self.env.docname, self.lineno), - ) - else: - # Absolute path starts with /, based on the source directory. The / need to be striped - correct_need_import_path = os.path.join( - self.env.app.srcdir, need_import_path[1:] - ) + correct_need_import_path = self.env.relfn2path( + need_import_path, self.env.docname + )[1] if not os.path.exists(correct_need_import_path): raise ReferenceError( From 4dc37efd8ed5eb69eb8e1e30af4edcf197106f1c Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 14:09:52 +0200 Subject: [PATCH 05/18] Adapt TC --- tests/doc_test/import_doc/index.rst | 1 - .../subdoc/deprecated_rel_path_import.rst | 8 -------- tests/test_needimport.py | 13 ++----------- 3 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 tests/doc_test/import_doc/subdoc/deprecated_rel_path_import.rst diff --git a/tests/doc_test/import_doc/index.rst b/tests/doc_test/import_doc/index.rst index 6c2e2f108..0f9bb2122 100644 --- a/tests/doc_test/import_doc/index.rst +++ b/tests/doc_test/import_doc/index.rst @@ -43,7 +43,6 @@ FILTERED subdoc/filter subdoc/abs_path_import subdoc/rel_path_import - subdoc/deprecated_rel_path_import .. req:: Test requirement 1 :id: REQ_1 diff --git a/tests/doc_test/import_doc/subdoc/deprecated_rel_path_import.rst b/tests/doc_test/import_doc/subdoc/deprecated_rel_path_import.rst deleted file mode 100644 index 4de3fc026..000000000 --- a/tests/doc_test/import_doc/subdoc/deprecated_rel_path_import.rst +++ /dev/null @@ -1,8 +0,0 @@ -Deprecated Relative path import test -==================================== - -Test import file with relative path, which based on directory of conf.py. - -.. needimport:: subdoc/needs_test_small.json - :id_prefix: small_depr_rel_path_ - :filter: "impl" in type diff --git a/tests/test_needimport.py b/tests/test_needimport.py index ca76932cc..c25e14267 100644 --- a/tests/test_needimport.py +++ b/tests/test_needimport.py @@ -20,17 +20,8 @@ def test_import_json(test_app): app = test_app app.build() - warnings = app._warning.getvalue() - warnings_list = strip_colors( - warnings.replace(str(app.srcdir) + os.sep + "subdoc" + os.sep, "srcdir/subdoc/") - ).splitlines() - - if os.name != "nt": - assert warnings_list == [ - "srcdir/subdoc/deprecated_rel_path_import.rst:6: WARNING: Deprecation warning: Relative path must be relative to the current document in future, not to the conf.py location. Use a starting '/', like '/needs.json', to make the path relative to conf.py. [needs.deprecated]" - ] - else: - assert "Deprecation" in warnings + assert app.statuscode == 0 + assert not app.warning_list html = Path(app.outdir, "index.html").read_text() assert "TEST IMPORT TITLE" in html From 5a1398f5757981ee329691e02f80970b22db747a Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 14:12:02 +0200 Subject: [PATCH 06/18] Use relfn2path for needuml --- sphinx_needs/directives/needuml.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/sphinx_needs/directives/needuml.py b/sphinx_needs/directives/needuml.py index ac39f4adf..f327720a6 100644 --- a/sphinx_needs/directives/needuml.py +++ b/sphinx_needs/directives/needuml.py @@ -2,7 +2,6 @@ import html import os -import posixpath import time from collections.abc import Sequence from typing import TYPE_CHECKING, Any, TypedDict @@ -108,12 +107,7 @@ def run(self) -> Sequence[nodes.Node]: save_path = self.options.get("save") plantuml_code_out_path = None if save_path: - if posixpath.isabs(save_path): - raise NeedumlException( - f"Given save path: {save_path}, is not a relative path." - ) - else: - plantuml_code_out_path = save_path + plantuml_code_out_path = self.env.relfn2path(save_path, self.env.docname)[1] SphinxNeedsData(env).get_or_create_umls()[targetid] = { "docname": env.docname, From 62eb90bc1d01942c381385330815e87e2ff8fd83 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 14:19:15 +0200 Subject: [PATCH 07/18] Update snapshot --- tests/__snapshots__/test_needimport.ambr | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/tests/__snapshots__/test_needimport.ambr b/tests/__snapshots__/test_needimport.ambr index b73fb2dde..c990d4448 100644 --- a/tests/__snapshots__/test_needimport.ambr +++ b/tests/__snapshots__/test_needimport.ambr @@ -78,7 +78,7 @@ 'external_css': 'external_link', 'id': 'REQ_1', 'layout': '', - 'lineno': 48, + 'lineno': 47, 'section_name': 'FILTERED', 'sections': list([ 'FILTERED', @@ -193,7 +193,7 @@ 'external_css': 'external_link', 'id': 'SPEC_1', 'layout': '', - 'lineno': 51, + 'lineno': 50, 'section_name': 'FILTERED', 'sections': list([ 'FILTERED', @@ -1250,21 +1250,6 @@ 'type': 'req', 'type_name': 'Requirement', }), - 'small_depr_rel_path_TEST_01': dict({ - 'content': 'small_depr_rel_path_TEST_01', - 'docname': 'subdoc/deprecated_rel_path_import', - 'external_css': 'external_link', - 'id': 'small_depr_rel_path_TEST_01', - 'is_import': True, - 'lineno': 6, - 'section_name': 'Deprecated Relative path import test', - 'sections': list([ - 'Deprecated Relative path import test', - ]), - 'title': 'TEST_01 DESCRIPTION', - 'type': 'impl', - 'type_name': 'Implementation', - }), 'small_rel_path_TEST_01': dict({ 'content': 'small_rel_path_TEST_01', 'docname': 'subdoc/rel_path_import', @@ -1635,7 +1620,7 @@ 'type_name': 'Test Case', }), }), - 'needs_amount': 66, + 'needs_amount': 65, 'needs_defaults_removed': True, 'needs_schema': dict({ '$schema': 'http://json-schema.org/draft-07/schema#', From 14a889f1160e43b3fc67adce68a6b2592c64c0b1 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 15:36:03 +0200 Subject: [PATCH 08/18] Use PurePosixPath for save --- sphinx_needs/directives/needuml.py | 8 +++++++- tests/test_needuml.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/sphinx_needs/directives/needuml.py b/sphinx_needs/directives/needuml.py index f327720a6..dfb2282b5 100644 --- a/sphinx_needs/directives/needuml.py +++ b/sphinx_needs/directives/needuml.py @@ -4,6 +4,7 @@ import os import time from collections.abc import Sequence +from pathlib import PurePosixPath from typing import TYPE_CHECKING, Any, TypedDict from docutils import nodes @@ -107,7 +108,12 @@ def run(self) -> Sequence[nodes.Node]: save_path = self.options.get("save") plantuml_code_out_path = None if save_path: - plantuml_code_out_path = self.env.relfn2path(save_path, self.env.docname)[1] + if PurePosixPath(save_path).is_absolute(): + raise NeedumlException( + f"Given save path: {save_path}, is not a relative posix path." + ) + else: + plantuml_code_out_path = save_path SphinxNeedsData(env).get_or_create_umls()[targetid] = { "docname": env.docname, diff --git a/tests/test_needuml.py b/tests/test_needuml.py index f4acbc15f..801281992 100644 --- a/tests/test_needuml.py +++ b/tests/test_needuml.py @@ -134,7 +134,7 @@ def test_needuml_save_with_abs_path(test_app): assert ( "sphinx_needs.directives.needuml.NeedumlException: " - "Given save path: /_out/my_needuml.puml, is not a relative path." + "Given save path: /_out/my_needuml.puml, is not a relative posix path." in out.stderr.decode("utf-8") ) From f4c2b5fdac309132b964043a1ca4bcd31d45c316 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 15:45:14 +0200 Subject: [PATCH 09/18] Fix deprecated path test --- tests/test_needimport.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/test_needimport.py b/tests/test_needimport.py index c25e14267..773e3f5ed 100644 --- a/tests/test_needimport.py +++ b/tests/test_needimport.py @@ -52,12 +52,6 @@ def test_import_json(test_app): rel_path_import_html = Path(app.outdir, "subdoc/rel_path_import.html").read_text() assert "small_rel_path_TEST_01" in rel_path_import_html - # Check deprecated relative path import based on conf.py - deprec_rel_path_import_html = Path( - app.outdir, "subdoc/deprecated_rel_path_import.html" - ).read_text() - assert "small_depr_rel_path_TEST_01" in deprec_rel_path_import_html - @pytest.mark.parametrize( "test_app", From c397e147cb34afb867c604a598720c93ccd13934 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 16:55:05 +0200 Subject: [PATCH 10/18] Absolue needimport path test --- tests/conftest.py | 26 ++++++++- tests/test_needimport.py | 112 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c3c93af8d..a3108410a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -54,6 +54,19 @@ def copy_srcdir_to_tmpdir(srcdir: path, tmp: path) -> path: return tmproot +def create_src_files_in_tmpdir(files: list[tuple[Path, str]], tmp: path) -> path: + """Create source files in a temporary directory under the subdir src.""" + subdir = path("src") + tmproot = tmp.joinpath(generate_random_string()) / subdir + tmproot.makedirs(exist_ok=True) + for file in files: + file_path, content = file + file_abs = tmproot.joinpath(str(file_path)) + file_abs.parent.makedirs(exist_ok=True) + file_abs.write_text(content) + return tmproot + + def get_abspath(relpath: str) -> str: """ Get the absolute path from a relative path. @@ -264,9 +277,18 @@ def test_app(make_app, sphinx_test_tempdir, request): ) sphinx_conf_overrides.update(plantuml=plantuml) - # copy test srcdir to test temporary directory sphinx_test_tempdir srcdir = builder_params.get("srcdir") - src_dir = copy_srcdir_to_tmpdir(srcdir, sphinx_test_tempdir) + files = builder_params.get("files") + if (srcdir is None) == (files is None): + raise ValueError("Exactly one of srcdir, files must not be None") + + if srcdir is not None: + # copy test srcdir to test temporary directory sphinx_test_tempdir + src_dir = copy_srcdir_to_tmpdir(srcdir, sphinx_test_tempdir) + else: + # create given files in tmpdir + src_dir = create_src_files_in_tmpdir(files, sphinx_test_tempdir) + parent_path = Path(str(src_dir.parent.abspath())) if version_info >= (7, 2): diff --git a/tests/test_needimport.py b/tests/test_needimport.py index 773e3f5ed..7b604d41a 100644 --- a/tests/test_needimport.py +++ b/tests/test_needimport.py @@ -1,5 +1,6 @@ import json import os +import sys from pathlib import Path import pytest @@ -53,6 +54,117 @@ def test_import_json(test_app): assert "small_rel_path_TEST_01" in rel_path_import_html +needs_json = """ +{ + "current_version": "1", + "versions": { + "1": { + "needs": { + "TEST_01": { + "id": "TEST_01", + "title": "TEST IMPORT DESCRIPTION", + "type": "impl" + } + }, + "needs_amount": 1 + } + } +} +""" + + +@pytest.mark.parametrize( + "index_content", + [ + ".. needimport:: needs.json", + ".. needimport:: nested/needs.json", + ".. needimport:: /needs.json", + ".. needimport:: /nested/needs.json", + ], +) +@pytest.mark.parametrize( + "test_app", + [ + { + "buildername": "html", + "files": [ + ("conf.py", 'extensions = ["sphinx_needs"]'), + ("needs.json", needs_json), + ("nested/needs.json", needs_json), + ], + "no_plantuml": True, + } + ], + indirect=True, +) +def test_import_rel_abs_sphinx_paths(test_app, index_content): + """Test various relative and absolute import file paths.""" + # write the parametrized index.rst content + index_path = Path(test_app.srcdir, "index.rst") + index_path.write_text(index_content) + + app = test_app + app.build() + assert app.statuscode == 0 + assert not app.warning_list + + +@pytest.mark.parametrize( + "test_app", + [ + { + "buildername": "html", + "files": [ + ("conf.py", 'extensions = ["sphinx_needs"]'), + ("needs.json", needs_json), + ], + "no_plantuml": True, + } + ], + indirect=True, +) +@pytest.mark.skipif(sys.platform != "win32", reason="Test only runs on Windows") +def test_import_abs_paths_win(test_app): + """Test various relative and absolute import file paths.""" + # write the parametrized index.rst content + index_path = Path(test_app.srcdir, "index.rst") + json_abs_path = str((Path(test_app.srcdir).resolve() / "needs.json").absolute()) + index_path.write_text(f".. needimport:: {json_abs_path}") + + app = test_app + app.build() + assert app.statuscode == 0 + assert not app.warning_list + + +@pytest.mark.parametrize( + "test_app", + [ + { + "buildername": "html", + "files": [ + ("conf.py", 'extensions = ["sphinx_needs"]'), + ("needs.json", needs_json), + ], + "no_plantuml": True, + } + ], + indirect=True, +) +@pytest.mark.skipif(sys.platform == "win32", reason="Test only runs on Linux and MacOS") +def test_import_abs_paths_lin_mac(test_app): + """Test various relative and absolute import file paths.""" + # write the parametrized index.rst content + index_path = Path(test_app.srcdir, "index.rst") + json_abs_path = str((Path(test_app.srcdir).resolve() / "needs.json").absolute()) + index_path.write_text(f".. needimport:: /{json_abs_path}") + + app = test_app + app.build() + assert app.statuscode == 0 + assert not app.warning_list + + @pytest.mark.parametrize( "test_app", [ From 37f9680fb5d845bba5c69f818689379fe085b1fd Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 17:01:57 +0200 Subject: [PATCH 11/18] Change slashes on Windows --- tests/test_needimport.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_needimport.py b/tests/test_needimport.py index 7b604d41a..97467ca3d 100644 --- a/tests/test_needimport.py +++ b/tests/test_needimport.py @@ -109,6 +109,7 @@ def test_import_rel_abs_sphinx_paths(test_app, index_content): assert not app.warning_list +@pytest.mark.parametrize("path_sep", ["/", "\\", "\\\\"]) @pytest.mark.parametrize( "test_app", [ @@ -124,11 +125,12 @@ def test_import_rel_abs_sphinx_paths(test_app, index_content): indirect=True, ) @pytest.mark.skipif(sys.platform != "win32", reason="Test only runs on Windows") -def test_import_abs_paths_win(test_app): +def test_import_abs_paths_win(test_app, path_sep): """Test various relative and absolute import file paths.""" # write the parametrized index.rst content index_path = Path(test_app.srcdir, "index.rst") json_abs_path = str((Path(test_app.srcdir).resolve() / "needs.json").absolute()) + json_abs_path = json_abs_path.replace(os.path.sep, path_sep) index_path.write_text(f".. needimport:: {json_abs_path}") app = test_app From 41dd551c15df8e718e6a2024919b2bad42b07f5d Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 17:07:14 +0200 Subject: [PATCH 12/18] Update docs --- docs/directives/needimport.rst | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/directives/needimport.rst b/docs/directives/needimport.rst index e7dd78b42..e314195c5 100644 --- a/docs/directives/needimport.rst +++ b/docs/directives/needimport.rst @@ -26,27 +26,35 @@ The directive argument can be one of the following formats: - A remote URL from which to download the ``needs.json``: .. code-block:: rst - + .. needimport:: https://my_company.com/docs/remote-needs.json - A local path relative to the containing document: .. code-block:: rst - + .. needimport:: needs.json - A local path starting with ``/`` is relative to the Sphinx source directory: .. code-block:: rst - + .. needimport:: /path/to/needs.json -- For an absolute path, make sure to start with two ``//`` (on Linux/OSX): +- For an absolute path on Linux/OSX, make sure to start with two ``//``: .. code-block:: rst - + .. needimport:: //absoulte/path/to/needs.json +- For an absolute path on Windows, just use the normal drive letters with either forward or backward slashes: + + .. code-block:: rst + + .. needimport:: c:/absoulte/path/to/needs.json + + .. needimport:: c:\absoulte\path\to\needs.json + Options ------- From 6b2ae080cb0cf47e0634adafce9bfb19805048a6 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 17:11:50 +0200 Subject: [PATCH 13/18] Fix typo --- docs/directives/needimport.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/directives/needimport.rst b/docs/directives/needimport.rst index e314195c5..afcfa6acb 100644 --- a/docs/directives/needimport.rst +++ b/docs/directives/needimport.rst @@ -45,15 +45,15 @@ The directive argument can be one of the following formats: .. code-block:: rst - .. needimport:: //absoulte/path/to/needs.json + .. needimport:: //absolute/path/to/needs.json - For an absolute path on Windows, just use the normal drive letters with either forward or backward slashes: .. code-block:: rst - .. needimport:: c:/absoulte/path/to/needs.json + .. needimport:: c:/absolute/path/to/needs.json - .. needimport:: c:\absoulte\path\to\needs.json + .. needimport:: c:\absolute\path\to\needs.json Options ------- From 087501bc89bfc9cbf46bf7ebae4c74d772d509b8 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Wed, 30 Apr 2025 17:11:56 +0200 Subject: [PATCH 14/18] Read back html --- tests/test_needimport.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/test_needimport.py b/tests/test_needimport.py index 97467ca3d..14cb018e7 100644 --- a/tests/test_needimport.py +++ b/tests/test_needimport.py @@ -62,7 +62,7 @@ def test_import_json(test_app): "needs": { "TEST_01": { "id": "TEST_01", - "title": "TEST IMPORT DESCRIPTION", + "title": "TEST IMPORT TITLE", "type": "impl" } }, @@ -108,6 +108,10 @@ def test_import_rel_abs_sphinx_paths(test_app, index_content): assert app.statuscode == 0 assert not app.warning_list + html = Path(app.outdir, "index.html").read_text() + assert "TEST IMPORT TITLE" in html + assert "TEST_01" in html + @pytest.mark.parametrize("path_sep", ["/", "\\", "\\\\"]) @pytest.mark.parametrize( @@ -138,6 +142,10 @@ def test_import_abs_paths_win(test_app, path_sep): assert app.statuscode == 0 assert not app.warning_list + html = Path(app.outdir, "index.html").read_text() + assert "TEST IMPORT TITLE" in html + assert "TEST_01" in html + @pytest.mark.parametrize( "test_app", @@ -166,6 +174,10 @@ def test_import_abs_paths_lin_mac(test_app): assert app.statuscode == 0 assert not app.warning_list + html = Path(app.outdir, "index.html").read_text() + assert "TEST IMPORT TITLE" in html + assert "TEST_01" in html + @pytest.mark.parametrize( "test_app", From dda237947f70bda75c4f6357501769579c552b96 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Fri, 2 May 2025 12:29:35 +0200 Subject: [PATCH 15/18] Fix test case for Sphinx < 8.2 --- tests/test_needs_external_needs_build.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/test_needs_external_needs_build.py b/tests/test_needs_external_needs_build.py index 408f21d72..bf5b3c5b5 100644 --- a/tests/test_needs_external_needs_build.py +++ b/tests/test_needs_external_needs_build.py @@ -5,6 +5,8 @@ import pytest import responses from docutils import __version__ as doc_ver +from sphinx import version_info +from sphinx.testing.util import SphinxTestApp from sphinx.util.console import strip_colors @@ -13,13 +15,11 @@ [{"buildername": "html", "srcdir": "doc_test/doc_needs_external_needs"}], indirect=True, ) -def test_doc_build_html(test_app, sphinx_test_tempdir): +def test_doc_build_html(test_app: SphinxTestApp, sphinx_test_tempdir): import subprocess - app = test_app - - src_dir = Path(app.srcdir) - out_dir = Path(app.outdir) + src_dir = Path(test_app.srcdir) + out_dir = Path(test_app.outdir) plantuml = r"java -Djava.awt.headless=true -jar {}".format( os.path.join(sphinx_test_tempdir, "utils", "plantuml.jar") ) @@ -37,7 +37,16 @@ def test_doc_build_html(test_app, sphinx_test_tempdir): ["sphinx-build", "-b", "html", "-D", rf"plantuml={plantuml}", src_dir, out_dir], capture_output=True, ) - assert not output_second.stderr + + # Sphinx 8.2 removed an early return in case no documents were updated in + # https://github.com/sphinx-doc/sphinx/pull/13236 + # which leads to SN warnings not being emitted + if version_info < (8, 2): + expected_warnings = [] + assert ( + strip_colors(output_second.stderr.decode("utf-8")).splitlines() + == expected_warnings + ) # check if incremental build used # first build output From 6abb81ca63bc96ed4dbaa03370cf4fdd9f2576d2 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Fri, 2 May 2025 13:31:42 +0200 Subject: [PATCH 16/18] Make sure get_needs_view is called --- sphinx_needs/needs.py | 13 +++++++++++++ tests/test_needs_external_needs_build.py | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/sphinx_needs/needs.py b/sphinx_needs/needs.py index c684738fa..d88737775 100644 --- a/sphinx_needs/needs.py +++ b/sphinx_needs/needs.py @@ -8,12 +8,14 @@ from docutils import nodes from docutils.parsers.rst import directives from sphinx.application import Sphinx +from sphinx.builders import Builder from sphinx.config import Config from sphinx.environment import BuildEnvironment from sphinx.errors import SphinxError import sphinx_needs.debug as debug # Need to set global var in it for timeing measurements from sphinx_needs import __version__ +from sphinx_needs.api import get_needs_view from sphinx_needs.api.need import _split_list_with_dyn_funcs from sphinx_needs.builder import ( NeedsBuilder, @@ -305,6 +307,8 @@ def setup(app: Sphinx) -> dict[str, Any]: app.connect("doctree-resolved", process_need_nodes) app.connect("doctree-resolved", process_creator(NODE_TYPES)) + app.connect("write-started", ensure_post_process_needs_data) + app.connect("build-finished", process_warnings) app.connect("build-finished", build_needs_json) app.connect("build-finished", build_needs_id_json) @@ -324,6 +328,15 @@ def setup(app: Sphinx) -> dict[str, Any]: } +def ensure_post_process_needs_data(app: Sphinx, builder: Builder) -> None: + """ + Make sure post_process_needs_data is called at least once. + + Warnings are emitted in that step, even when no docs are updated. + """ + get_needs_view(app) + + def process_creator( node_list: _NODE_TYPES_T, doc_category: str = "all" ) -> Callable[[Sphinx, nodes.document, str], None]: diff --git a/tests/test_needs_external_needs_build.py b/tests/test_needs_external_needs_build.py index bf5b3c5b5..8c39da696 100644 --- a/tests/test_needs_external_needs_build.py +++ b/tests/test_needs_external_needs_build.py @@ -27,10 +27,11 @@ def test_doc_build_html(test_app: SphinxTestApp, sphinx_test_tempdir): ["sphinx-build", "-b", "html", "-D", rf"plantuml={plantuml}", src_dir, out_dir], capture_output=True, ) - assert strip_colors(output.stderr.decode("utf-8")).splitlines() == [ + expected_warnings = [ "WARNING: http://my_company.com/docs/v1/index.html#TEST_01: Need 'EXT_TEST_01' has unknown outgoing link 'SPEC_1' in field 'links' [needs.external_link_outgoing]", "WARNING: ../../_build/html/index.html#TEST_01: Need 'EXT_REL_PATH_TEST_01' has unknown outgoing link 'SPEC_1' in field 'links' [needs.external_link_outgoing]", ] + assert strip_colors(output.stderr.decode("utf-8")).splitlines() == expected_warnings # run second time and check output_second = subprocess.run( @@ -40,7 +41,7 @@ def test_doc_build_html(test_app: SphinxTestApp, sphinx_test_tempdir): # Sphinx 8.2 removed an early return in case no documents were updated in # https://github.com/sphinx-doc/sphinx/pull/13236 - # which leads to SN warnings not being emitted + # which leads to some SN warnings not being emitted for incremental builds if version_info < (8, 2): expected_warnings = [] assert ( From f8a1d6295280ba9ec560ec024f22b93aa33a56b1 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Fri, 2 May 2025 13:43:43 +0200 Subject: [PATCH 17/18] Newline to trigger CI --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4f2dbc913..c3f5a729e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,8 +3,8 @@ on: push: branches: [master] pull_request: -jobs: +jobs: lint: name: Lint runs-on: ubuntu-latest From c8d395231fc7617c28c4ab9e54088b0762e64aa6 Mon Sep 17 00:00:00 2001 From: Marco Heinemann Date: Fri, 2 May 2025 14:17:52 +0200 Subject: [PATCH 18/18] Comment --- tests/test_needs_external_needs_build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_needs_external_needs_build.py b/tests/test_needs_external_needs_build.py index 8c39da696..45a95dddf 100644 --- a/tests/test_needs_external_needs_build.py +++ b/tests/test_needs_external_needs_build.py @@ -56,6 +56,7 @@ def test_doc_build_html(test_app: SphinxTestApp, sphinx_test_tempdir): in strip_colors(output.stdout.decode("utf-8")) ) # second build output + # TODO(Marco) check why 3 added configs are expected, should be 0 for incremental builds without changes assert "loading pickled environment" in output_second.stdout.decode("utf-8") assert ( "updating environment: [new config] 3 added, 0 changed, 0 removed"