Skip to content

Commit cf5598f

Browse files
authored
👌 Improve warnings for invalid filters (add source location and subtype) (#1128)
1 parent eb81f2e commit cf5598f

File tree

16 files changed

+161
-60
lines changed

16 files changed

+161
-60
lines changed

‎sphinx_needs/api/need.py‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,10 @@ def run():
153153
if need_type not in configured_need_types:
154154
logger.warning(
155155
f"Couldn't create need {id}. Reason: The need-type (i.e. `{need_type}`) is not set "
156-
"in the project's 'need_types' configuration in conf.py. [needs]",
156+
"in the project's 'need_types' configuration in conf.py. [needs.add]",
157157
type="needs",
158+
subtype="add",
159+
location=(docname, lineno) if docname else None,
158160
)
159161

160162
for ntype in types:
@@ -219,9 +221,11 @@ def run():
219221
for i in range(len(tags)):
220222
if len(tags[i]) == 0 or tags[i].isspace():
221223
logger.warning(
222-
f"Scruffy tag definition found in need {need_id}. "
223-
"Defined tag contains spaces only. [needs]",
224+
f"Scruffy tag definition found in need {need_id!r}. "
225+
"Defined tag contains spaces only. [needs.add]",
224226
type="needs",
227+
subtype="add",
228+
location=(docname, lineno) if docname else None,
225229
)
226230
else:
227231
new_tags.append(tags[i])
@@ -256,9 +260,11 @@ def run():
256260
for i in range(len(constraints)):
257261
if len(constraints[i]) == 0 or constraints[i].isspace():
258262
logger.warning(
259-
f"Scruffy tag definition found in need {need_id}. "
260-
"Defined constraint contains spaces only. [needs]",
263+
f"Scruffy constraint definition found in need {need_id!r}. "
264+
"Defined constraint contains spaces only. [needs.add]",
261265
type="needs",
266+
subtype="add",
267+
location=(docname, lineno) if docname else None,
262268
)
263269
else:
264270
new_constraints.append(constraints[i])

‎sphinx_needs/builder.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ def finish(self) -> None:
8585

8686
filter_string = needs_config.builder_filter
8787
filtered_needs: list[NeedsInfoType] = filter_needs(
88-
data.get_or_create_needs().values(), needs_config, filter_string
88+
data.get_or_create_needs().values(),
89+
needs_config,
90+
filter_string,
91+
append_warning="(from need_builder_filter)",
8992
)
9093

9194
for need in filtered_needs:
@@ -182,7 +185,12 @@ def finish(self) -> None:
182185
filter_string = needs_config.builder_filter
183186
from sphinx_needs.filter_common import filter_needs
184187

185-
filtered_needs = filter_needs(needs, needs_config, filter_string)
188+
filtered_needs = filter_needs(
189+
needs,
190+
needs_config,
191+
filter_string,
192+
append_warning="(from need_builder_filter)",
193+
)
186194
needs_build_json_per_id_path = needs_config.build_json_per_id_path
187195
needs_dir = os.path.join(self.outdir, needs_build_json_per_id_path)
188196
if not os.path.exists(needs_dir):

‎sphinx_needs/directives/needbar.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,10 @@ def run(self) -> Sequence[nodes.Node]:
157157

158158
add_doc(env, env.docname)
159159

160-
return [targetnode, Needbar("")]
160+
bar_node = Needbar("")
161+
self.set_source_info(bar_node)
162+
163+
return [targetnode, bar_node]
161164

162165

163166
# Algorithm:
@@ -301,7 +304,9 @@ def process_needbar(
301304
if element.isdigit():
302305
line_number.append(float(element))
303306
else:
304-
result = len(filter_needs(need_list, needs_config, element))
307+
result = len(
308+
filter_needs(need_list, needs_config, element, location=node)
309+
)
305310
line_number.append(float(result))
306311
local_data_number.append(line_number)
307312

‎sphinx_needs/directives/needextend.py‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,12 @@ def extend_needs_data(
9898
logger.info(error)
9999
continue
100100
else:
101-
# a filter string
102-
try:
103-
found_needs = filter_needs(
104-
all_needs.values(), needs_config, need_filter
105-
)
106-
except NeedsInvalidFilter as e:
107-
raise NeedsInvalidFilter(
108-
f"Filter not valid for needextend on page {current_needextend['docname']}:\n{e}"
109-
)
101+
found_needs = filter_needs(
102+
all_needs.values(),
103+
needs_config,
104+
need_filter,
105+
location=(current_needextend["docname"], current_needextend["lineno"]),
106+
)
110107

111108
for found_need in found_needs:
112109
# Work in the stored needs, not on the search result

‎sphinx_needs/directives/needgantt.py‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ def run(self) -> Sequence[nodes.Node]:
124124

125125
add_doc(env, env.docname)
126126

127-
return [targetnode] + [Needgantt("")]
127+
gantt_node = Needgantt("")
128+
self.set_source_info(gantt_node)
129+
130+
return [targetnode, gantt_node]
128131

129132
def get_link_type_option(self, name: str, default: str = "") -> list[str]:
130133
link_types = [
@@ -244,10 +247,17 @@ def process_needgantt(
244247
complete_option = current_needgantt["completion_option"]
245248
complete = need[complete_option] # type: ignore[literal-required]
246249
if not (duration and duration.isdigit()):
250+
need_location = (
251+
f" (located: {need['docname']}:{need['lineno']})"
252+
if need["docname"]
253+
else ""
254+
)
247255
logger.warning(
248256
"Duration not set or invalid for needgantt chart. "
249-
"Need: {}. Duration: {} [needs]".format(need["id"], duration),
257+
f"Need: {need['id']!r}{need_location}. Duration: {duration!r} [needs.gantt]",
250258
type="needs",
259+
subtype="gantt",
260+
location=node,
251261
)
252262
duration = 1
253263
gantt_element = "[{}] as [{}] lasts {} days\n".format(

‎sphinx_needs/directives/needpie.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ def run(self) -> Sequence[nodes.Node]:
102102
}
103103
add_doc(env, env.docname)
104104

105-
return [targetnode, Needpie("")]
105+
pie_node = Needpie("")
106+
self.set_source_info(pie_node)
107+
108+
return [targetnode, pie_node]
106109

107110

108111
@measure_time("needpie")
@@ -162,7 +165,9 @@ def process_needpie(
162165
if line.isdigit():
163166
sizes.append(abs(float(line)))
164167
else:
165-
result = len(filter_needs(need_list, needs_config, line))
168+
result = len(
169+
filter_needs(need_list, needs_config, line, location=node)
170+
)
166171
sizes.append(result)
167172
elif current_needpie["filter_func"] and not content:
168173
try:

‎sphinx_needs/filter_common.py‎

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from types import CodeType
1010
from typing import Any, Iterable, TypedDict, TypeVar
1111

12+
from docutils import nodes
1213
from docutils.parsers.rst import directives
1314
from sphinx.application import Sphinx
1415
from sphinx.util.docutils import SphinxDirective
@@ -180,7 +181,10 @@ def process_filters(
180181
found_needs_by_options.append(need_info)
181182
# Get need by filter string
182183
found_needs_by_string = filter_needs(
183-
all_needs_incl_parts, needs_config, filter_data["filter"]
184+
all_needs_incl_parts,
185+
needs_config,
186+
filter_data["filter"],
187+
location=(filter_data["docname"], filter_data["lineno"]),
184188
)
185189
# Make an intersection of both lists
186190
found_needs = intersection_of_need_results(
@@ -190,7 +194,10 @@ def process_filters(
190194
# There is no other config as the one for filter string.
191195
# So we only need this result.
192196
found_needs = filter_needs(
193-
all_needs_incl_parts, needs_config, filter_data["filter"]
197+
all_needs_incl_parts,
198+
needs_config,
199+
filter_data["filter"],
200+
location=(filter_data["docname"], filter_data["lineno"]),
194201
)
195202
else:
196203
# Provides only a copy of needs to avoid data manipulations.
@@ -296,6 +303,9 @@ def filter_needs(
296303
config: NeedsSphinxConfig,
297304
filter_string: None | str = "",
298305
current_need: NeedsInfoType | None = None,
306+
*,
307+
location: tuple[str, int | None] | nodes.Node | None = None,
308+
append_warning: str = "",
299309
) -> list[V]:
300310
"""
301311
Filters given needs based on a given filter string.
@@ -305,6 +315,8 @@ def filter_needs(
305315
:param config: NeedsSphinxConfig object
306316
:param filter_string: strings, which gets evaluated against each need
307317
:param current_need: current need, which uses the filter.
318+
:param location: source location for error reporting (docname, line number)
319+
:param append_warning: additional text to append to any failed filter warning
308320
309321
:return: list of found needs
310322
"""
@@ -328,13 +340,15 @@ def filter_needs(
328340
):
329341
found_needs.append(filter_need)
330342
except Exception as e:
331-
if not error_reported: # Let's report a filter-problem only onces
332-
location = (
333-
(current_need["docname"], current_need["lineno"])
334-
if current_need
335-
else None
343+
if not error_reported: # Let's report a filter-problem only once
344+
if append_warning:
345+
append_warning = f" {append_warning}"
346+
log.warning(
347+
f"{e}{append_warning} [needs.filter]",
348+
type="needs",
349+
subtype="filter",
350+
location=location,
336351
)
337-
log.warning(str(e) + " [needs]", type="needs", location=location)
338352
error_reported = True
339353

340354
return found_needs
@@ -381,5 +395,5 @@ def filter_single_need(
381395
else:
382396
result = bool(eval(filter_string, filter_context))
383397
except Exception as e:
384-
raise NeedsInvalidFilter(f"Filter {filter_string} not valid. Error: {e}.")
398+
raise NeedsInvalidFilter(f"Filter {filter_string!r} not valid. Error: {e}.")
385399
return result

‎sphinx_needs/functions/common.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,11 @@ def copy(
166166

167167
if filter:
168168
result = filter_needs(
169-
needs.values(), NeedsSphinxConfig(app.config), filter, need
169+
needs.values(),
170+
NeedsSphinxConfig(app.config),
171+
filter,
172+
need,
173+
location=(need["docname"], need["lineno"]),
170174
)
171175
if result:
172176
need = result[0]

‎sphinx_needs/roles/need_count.py‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,28 @@ def process_need_count(
3737
filters = filter.split(" ? ")
3838
if len(filters) == 1:
3939
need_list = prepare_need_list(all_needs) # adds parts to need_list
40-
amount = str(len(filter_needs(need_list, needs_config, filters[0])))
40+
amount = str(
41+
len(
42+
filter_needs(
43+
need_list,
44+
needs_config,
45+
filters[0],
46+
location=node_need_count,
47+
)
48+
)
49+
)
4150
elif len(filters) == 2:
4251
need_list = prepare_need_list(all_needs) # adds parts to need_list
43-
amount_1 = len(filter_needs(need_list, needs_config, filters[0]))
44-
amount_2 = len(filter_needs(need_list, needs_config, filters[1]))
52+
amount_1 = len(
53+
filter_needs(
54+
need_list, needs_config, filters[0], location=node_need_count
55+
)
56+
)
57+
amount_2 = len(
58+
filter_needs(
59+
need_list, needs_config, filters[1], location=node_need_count
60+
)
61+
)
4562
amount = f"{amount_1 / amount_2 * 100:2.1f}"
4663
elif len(filters) > 2:
4764
raise NeedsInvalidFilter(

‎sphinx_needs/warnings.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ def process_warnings(app: Sphinx, exception: Exception | None) -> None:
6262
if isinstance(warning_filter, str):
6363
# filter string used
6464
result = filter_needs(
65-
checked_needs.values(), needs_config, warning_filter
65+
checked_needs.values(),
66+
needs_config,
67+
warning_filter,
68+
append_warning=f"(from warning filter {warning_name!r})",
6669
)
6770
elif callable(warning_filter):
6871
# custom defined filter code used from conf.py

0 commit comments

Comments
 (0)