Skip to content

Commit 3bf7062

Browse files
committed
🐛 FIX: NeedImport logic
1 parent 321dcd5 commit 3bf7062

File tree

3 files changed

+56
-58
lines changed

3 files changed

+56
-58
lines changed

sphinx_needs/api/need.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,6 @@ def run():
193193
if needs_config.id_regex and not re.match(needs_config.id_regex, need_id):
194194
raise NeedsInvalidException(f"Given ID '{need_id}' does not match configured regex '{needs_config.id_regex}'")
195195

196-
# Calculate target id, to be able to set a link back
197-
if is_external:
198-
target_node = None
199-
else:
200-
target_node = nodes.target("", "", ids=[need_id], refid=need_id, anonymous="")
201-
external_url = None
202-
203196
# Handle status
204197
# Check if status is in needs_statuses. If not raise an error.
205198
if needs_config.statuses and status not in [stat["name"] for stat in needs_config.statuses]:
@@ -310,7 +303,7 @@ def run():
310303
"doctype": doctype,
311304
"lineno": lineno,
312305
"target_id": need_id,
313-
"external_url": external_url,
306+
"external_url": external_url if is_external else None,
314307
"content_node": None, # gets set after rst parsing
315308
"content_id": None, # gets set after rst parsing
316309
"type": need_type,
@@ -443,8 +436,10 @@ def run():
443436
node_need.line = needs_info["lineno"]
444437

445438
if needs_info["hide"]:
439+
# add node to doctree, so we can later compute the containing section(s)
440+
# (for use with section filters)
446441
node_need["hidden"] = True
447-
return [target_node, node_need]
442+
return [node_need]
448443

449444
node_need_content = _render_template(content, docname, lineno, state)
450445

@@ -485,7 +480,13 @@ def run():
485480
# Create a copy of the content
486481
needs_info["content_node"] = node_need.deepcopy()
487482

488-
return_nodes = [target_node] + [node_need]
483+
return_nodes = [node_need]
484+
if not is_external:
485+
# Calculate target id, to be able to set a link back
486+
target_node = nodes.target("", "", ids=[need_id], refid=need_id, anonymous="")
487+
# TODO add to document?
488+
return_nodes = [target_node, node_need]
489+
489490
if pre_content:
490491
node_need_pre_content = _render_template(pre_content, docname, lineno, state)
491492
return_nodes = node_need_pre_content.children + return_nodes

sphinx_needs/directives/need.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ def remove_hidden_needs(app: Sphinx, doctree: nodes.document, fromdocname: str)
548548
"""Remove hidden needs from the doctree, before it is rendered."""
549549
if fromdocname not in SphinxNeedsData(app.env).get_or_create_docs().get("all", []):
550550
return
551-
for node_need in doctree.findall(Need):
551+
for node_need in list(doctree.findall(Need)):
552552
if node_need.get("hidden"):
553553
node_need.parent.remove(node_need) # type: ignore
554554

sphinx_needs/directives/needimport.py

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import os
33
import re
4-
from typing import Sequence
4+
from typing import Dict, Sequence
55
from urllib.parse import urlparse
66

77
import requests
@@ -12,6 +12,7 @@
1212

1313
from sphinx_needs.api import add_need
1414
from sphinx_needs.config import NEEDS_CONFIG, NeedsSphinxConfig
15+
from sphinx_needs.data import NeedsInfoType
1516
from sphinx_needs.debug import measure_time
1617
from sphinx_needs.filter_common import filter_single_need
1718
from sphinx_needs.needsfile import check_needs_file
@@ -105,12 +106,11 @@ def run(self) -> Sequence[nodes.Node]:
105106
for error in errors.schema:
106107
logger.info(f' {error.message} -> {".".join(error.path)}')
107108

108-
with open(correct_need_import_path) as needs_file:
109-
needs_file_content = needs_file.read()
110109
try:
111-
needs_import_list = json.loads(needs_file_content)
110+
with open(correct_need_import_path) as needs_file:
111+
needs_import_list = json.load(needs_file)
112112
except json.JSONDecodeError as e:
113-
# ToDo: Add exception handling
113+
# TODO: Add exception handling
114114
raise e
115115

116116
if version is None:
@@ -123,8 +123,8 @@ def run(self) -> Sequence[nodes.Node]:
123123
if version not in needs_import_list["versions"].keys():
124124
raise VersionNotFound(f"Version {version} not found in needs import file {correct_need_import_path}")
125125

126-
# TODO type this (it uncovers lots of bugs)
127-
needs_list = needs_import_list["versions"][version]["needs"]
126+
# TODO this is not exactly NeedsInfoType, because the export removes/adds some keys
127+
needs_list: Dict[str, NeedsInfoType] = needs_import_list["versions"][version]["needs"]
128128

129129
# Filter imported needs
130130
needs_list_filtered = {}
@@ -136,7 +136,7 @@ def run(self) -> Sequence[nodes.Node]:
136136

137137
# Support both ways of addressing the description, as "description" is used in json file, but
138138
# "content" is the sphinx internal name for this kind of information
139-
filter_context["content"] = need["description"]
139+
filter_context["content"] = need["description"] # type: ignore[typeddict-item]
140140
try:
141141
if filter_single_need(self.env.app, filter_context, filter_string):
142142
needs_list_filtered[key] = need
@@ -158,70 +158,67 @@ def run(self) -> Sequence[nodes.Node]:
158158
for id in needs_list:
159159
# Manipulate links in all link types
160160
for extra_link in extra_links:
161-
if extra_link["option"] in need and id in need[extra_link["option"]]:
162-
for n, link in enumerate(need[extra_link["option"]]):
161+
if extra_link["option"] in need and id in need[extra_link["option"]]: # type: ignore[literal-required]
162+
for n, link in enumerate(need[extra_link["option"]]): # type: ignore[literal-required]
163163
if id == link:
164-
need[extra_link["option"]][n] = "".join([id_prefix, id])
164+
need[extra_link["option"]][n] = "".join([id_prefix, id]) # type: ignore[literal-required]
165165
# Manipulate descriptions
166166
# ToDo: Use regex for better matches.
167-
need["description"] = need["description"].replace(id, "".join([id_prefix, id]))
167+
need["description"] = need["description"].replace(id, "".join([id_prefix, id])) # type: ignore[typeddict-item]
168168

169169
# tags update
170170
for need in needs_list.values():
171171
need["tags"] = need["tags"] + tags
172172

173+
known_options = (
174+
"title",
175+
"status",
176+
"content",
177+
"id",
178+
"tags",
179+
"hide",
180+
"template",
181+
"pre_template",
182+
"post_template",
183+
"collapse",
184+
"style",
185+
"layout",
186+
"need_type",
187+
*[x["option"] for x in extra_links],
188+
*NEEDS_CONFIG.extra_options,
189+
)
173190
need_nodes = []
174191
for need in needs_list.values():
175192
# Set some values based on given option or value from imported need.
176-
need["template"] = self.options.get("template", getattr(need, "template", None))
177-
need["pre_template"] = self.options.get("pre_template", getattr(need, "pre_template", None))
178-
need["post_template"] = self.options.get("post_template", getattr(need, "post_template", None))
179-
need["layout"] = self.options.get("layout", getattr(need, "layout", None))
180-
need["style"] = self.options.get("style", getattr(need, "style", None))
181-
need["style"] = self.options.get("style", getattr(need, "style", None))
193+
need["template"] = self.options.get("template", need.get("template"))
194+
need["pre_template"] = self.options.get("pre_template", need.get("pre_template"))
195+
need["post_template"] = self.options.get("post_template", need.get("post_template"))
196+
need["layout"] = self.options.get("layout", need.get("layout"))
197+
need["style"] = self.options.get("style", need.get("style"))
198+
182199
if "hide" in self.options:
183200
need["hide"] = True
184201
else:
185-
need["hide"] = getattr(need, "hide", None)
186-
need["collapse"] = self.options.get("collapse", getattr(need, "collapse", None))
202+
need["hide"] = need.get("hide", False)
203+
need["collapse"] = self.options.get("collapse", need.get("collapse"))
187204

188205
# The key needs to be different for add_need() api call.
189-
need["need_type"] = need["type"]
206+
need["need_type"] = need["type"] # type: ignore[typeddict-unknown-key]
190207

191208
# Replace id, to get unique ids
192209
need["id"] = id_prefix + need["id"]
193210

194-
need["content"] = need["description"]
211+
need["content"] = need["description"] # type: ignore[typeddict-item]
212+
195213
# Remove unknown options, as they may be defined in source system, but not in this sphinx project
196-
extra_link_keys = [x["option"] for x in extra_links]
197-
extra_option_keys = list(NEEDS_CONFIG.extra_options)
198-
default_options = [
199-
"title",
200-
"status",
201-
"content",
202-
"id",
203-
"tags",
204-
"hide",
205-
"template",
206-
"pre_template",
207-
"post_template",
208-
"collapse",
209-
"style",
210-
"layout",
211-
"need_type",
212-
]
213-
delete_options = []
214-
for option in need.keys():
215-
if option not in default_options + extra_link_keys + extra_option_keys:
216-
delete_options.append(option)
217-
218-
for option in delete_options:
219-
del need[option]
214+
for option in list(need):
215+
if option not in known_options:
216+
del need[option] # type: ignore
220217

221218
need["docname"] = self.docname
222219
need["lineno"] = self.lineno
223220

224-
nodes = add_need(self.env.app, self.state, **need)
221+
nodes = add_need(self.env.app, self.state, **need) # type: ignore[call-arg]
225222
need_nodes.extend(nodes)
226223

227224
add_doc(self.env, self.env.docname)

0 commit comments

Comments
 (0)