diff --git a/sphinx_needs/external_needs.py b/sphinx_needs/external_needs.py index 7f86e0081..496b60855 100644 --- a/sphinx_needs/external_needs.py +++ b/sphinx_needs/external_needs.py @@ -94,12 +94,17 @@ def load_external_needs( data = needs_json["versions"][version] needs = data["needs"] except KeyError: - uri = source.get("json_url", source.get("json_path", "unknown")) - raise NeedsExternalException( - clean_log( - f"Version {version} not found in json file from {uri}: {list(needs_json.get('versions'))}" + if not needs_json.get("versions"): + # The versions dict is empty, so no needs were ever added. + data = {} + needs = {} + else: + uri = source.get("json_url", source.get("json_path", "unknown")) + raise NeedsExternalException( + clean_log( + f"Version {version} not found in json file from {uri}: {list(needs_json.get('versions'))}" + ) ) - ) log.debug(f"Loading {len(needs)} needs.") diff --git a/sphinx_needs/needsfile.py b/sphinx_needs/needsfile.py index 514144381..34e1676d4 100644 --- a/sphinx_needs/needsfile.py +++ b/sphinx_needs/needsfile.py @@ -179,6 +179,7 @@ def wipe_version(self, version: str) -> None: del self.needs_list["versions"][version] def _finalise(self) -> None: + self.update_or_add_version(self.current_version) # We need to rewrite some data, because this kind of data gets overwritten during needs.json import if not self.needs_config.reproducible_json: self.needs_list["created"] = datetime.now().isoformat() diff --git a/tests/__snapshots__/test_needimport.ambr b/tests/__snapshots__/test_needimport.ambr index fb9f874eb..63435e653 100644 --- a/tests/__snapshots__/test_needimport.ambr +++ b/tests/__snapshots__/test_needimport.ambr @@ -2116,6 +2116,12 @@ dict({ 'current_version': '1.0', 'versions': dict({ + '1.0': dict({ + 'needs': dict({ + }), + 'needs_amount': 0, + 'needs_defaults_removed': True, + }), }), }) # --- diff --git a/tests/doc_test/doc_needs_builder_empty/conf.py b/tests/doc_test/doc_needs_builder_empty/conf.py new file mode 100644 index 000000000..80787a07e --- /dev/null +++ b/tests/doc_test/doc_needs_builder_empty/conf.py @@ -0,0 +1,3 @@ +project = "doc_needs_builder_empty" + +extensions = ["sphinx_needs"] diff --git a/tests/doc_test/doc_needs_builder_empty/index.rst b/tests/doc_test/doc_needs_builder_empty/index.rst new file mode 100644 index 000000000..695b53689 --- /dev/null +++ b/tests/doc_test/doc_needs_builder_empty/index.rst @@ -0,0 +1,4 @@ +Empty doc +========= + +There are no needs here. diff --git a/tests/test_external.py b/tests/test_external.py index 5c147265d..d4a910b0e 100644 --- a/tests/test_external.py +++ b/tests/test_external.py @@ -261,3 +261,52 @@ def test_external_allow_type_coercion_false(test_app): assert strip_colors(app._warning.getvalue()).splitlines() == [ "WARNING: External need 'TEST_01' in 'needs.json' could not be added: 'tags' value is invalid: Invalid value for field 'tags': 'a,b,c' [needs.load_external_need]" ] + + +@pytest.mark.parametrize( + "test_app", + [ + { + "buildername": "html", + "files": [ + ("index.rst", "Test\n====\n"), + ( + "conf.py", + """ +extensions = ["sphinx_needs"] +needs_external_needs = [{ + 'json_path': 'needs.json', + 'base_url': 'http://my_company.com/docs/v1/', +}] +needs_build_json = True + """, + ), + ], + "no_plantuml": True, + } + ], + indirect=True, +) +def test_external_empty_versions(test_app): + """Test external needs when the loaded needs.json has an empty versions dict.""" + json_path = Path(test_app.srcdir) / "needs.json" + json_path.write_text( + json.dumps( + { + "current_version": "0.1.0", + "project": "foo", + "project_url": "https://bar", + "versions": {}, + } + ) + ) + + app = test_app + app.build() + assert app.statuscode == 0 + assert not app._warning.getvalue() + + needs_json = Path(test_app.outdir, "needs.json").read_text() + needs = json.loads(needs_json) + # the empty external needs should just be ignored without crashing. + assert "TEST_01" not in needs["versions"][""]["needs"] diff --git a/tests/test_needs_builder.py b/tests/test_needs_builder.py index e0e9aaf54..33a28b1b1 100644 --- a/tests/test_needs_builder.py +++ b/tests/test_needs_builder.py @@ -115,3 +115,23 @@ def test_needs_html_and_json(test_app): need_data_1 = needs_1["versions"]["1.0"]["needs"] need_data_2 = needs_2["versions"]["1.0"]["needs"] assert need_data_1 == need_data_2 + + +@pytest.mark.parametrize( + "test_app", + [{"buildername": "needs", "srcdir": "doc_test/doc_needs_builder_empty"}], + indirect=True, +) +def test_doc_needs_builder_empty(test_app): + app = test_app + app.build() + + needs_list = json.loads(Path(app.outdir, "needs.json").read_text()) + assert "current_version" in needs_list + assert needs_list["current_version"] == "" + + version = needs_list["current_version"] + assert "versions" in needs_list + assert version in needs_list["versions"] + assert needs_list["versions"][version]["needs_amount"] == 0 + assert needs_list["versions"][version]["needs"] == {}