Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

👌 Improve warnings for invalid filters (add source location and subtype) #1128

Merged
merged 2 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions sphinx_needs/api/need.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,10 @@ def run():
if need_type not in configured_need_types:
logger.warning(
f"Couldn't create need {id}. Reason: The need-type (i.e. `{need_type}`) is not set "
"in the project's 'need_types' configuration in conf.py. [needs]",
"in the project's 'need_types' configuration in conf.py. [needs.add]",
type="needs",
subtype="add",
location=(docname, lineno) if docname else None,
)

for ntype in types:
Expand Down Expand Up @@ -219,9 +221,11 @@ def run():
for i in range(len(tags)):
if len(tags[i]) == 0 or tags[i].isspace():
logger.warning(
f"Scruffy tag definition found in need {need_id}. "
"Defined tag contains spaces only. [needs]",
f"Scruffy tag definition found in need {need_id!r}. "
"Defined tag contains spaces only. [needs.add]",
type="needs",
subtype="add",
location=(docname, lineno) if docname else None,
)
else:
new_tags.append(tags[i])
Expand Down Expand Up @@ -256,9 +260,11 @@ def run():
for i in range(len(constraints)):
if len(constraints[i]) == 0 or constraints[i].isspace():
logger.warning(
f"Scruffy tag definition found in need {need_id}. "
"Defined constraint contains spaces only. [needs]",
f"Scruffy constraint definition found in need {need_id!r}. "
"Defined constraint contains spaces only. [needs.add]",
type="needs",
subtype="add",
location=(docname, lineno) if docname else None,
)
else:
new_constraints.append(constraints[i])
Expand Down
12 changes: 10 additions & 2 deletions sphinx_needs/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ def finish(self) -> None:

filter_string = needs_config.builder_filter
filtered_needs: list[NeedsInfoType] = filter_needs(
data.get_or_create_needs().values(), needs_config, filter_string
data.get_or_create_needs().values(),
needs_config,
filter_string,
append_warning="(from need_builder_filter)",
)

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

filtered_needs = filter_needs(needs, needs_config, filter_string)
filtered_needs = filter_needs(
needs,
needs_config,
filter_string,
append_warning="(from need_builder_filter)",
)
needs_build_json_per_id_path = needs_config.build_json_per_id_path
needs_dir = os.path.join(self.outdir, needs_build_json_per_id_path)
if not os.path.exists(needs_dir):
Expand Down
9 changes: 7 additions & 2 deletions sphinx_needs/directives/needbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ def run(self) -> Sequence[nodes.Node]:

add_doc(env, env.docname)

return [targetnode, Needbar("")]
bar_node = Needbar("")
self.set_source_info(bar_node)

return [targetnode, bar_node]


# Algorithm:
Expand Down Expand Up @@ -301,7 +304,9 @@ def process_needbar(
if element.isdigit():
line_number.append(float(element))
else:
result = len(filter_needs(need_list, needs_config, element))
result = len(
filter_needs(need_list, needs_config, element, location=node)
)
line_number.append(float(result))
local_data_number.append(line_number)

Expand Down
15 changes: 6 additions & 9 deletions sphinx_needs/directives/needextend.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,12 @@ def extend_needs_data(
logger.info(error)
continue
else:
# a filter string
try:
found_needs = filter_needs(
all_needs.values(), needs_config, need_filter
)
except NeedsInvalidFilter as e:
raise NeedsInvalidFilter(
f"Filter not valid for needextend on page {current_needextend['docname']}:\n{e}"
)
found_needs = filter_needs(
all_needs.values(),
needs_config,
need_filter,
location=(current_needextend["docname"], current_needextend["lineno"]),
)

for found_need in found_needs:
# Work in the stored needs, not on the search result
Expand Down
14 changes: 12 additions & 2 deletions sphinx_needs/directives/needgantt.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,10 @@

add_doc(env, env.docname)

return [targetnode] + [Needgantt("")]
gantt_node = Needgantt("")
self.set_source_info(gantt_node)

return [targetnode, gantt_node]

def get_link_type_option(self, name: str, default: str = "") -> list[str]:
link_types = [
Expand Down Expand Up @@ -244,10 +247,17 @@
complete_option = current_needgantt["completion_option"]
complete = need[complete_option] # type: ignore[literal-required]
if not (duration and duration.isdigit()):
need_location = (

Check warning on line 250 in sphinx_needs/directives/needgantt.py

View check run for this annotation

Codecov / codecov/patch

sphinx_needs/directives/needgantt.py#L250

Added line #L250 was not covered by tests
f" (located: {need['docname']}:{need['lineno']})"
if need["docname"]
else ""
)
logger.warning(
"Duration not set or invalid for needgantt chart. "
"Need: {}. Duration: {} [needs]".format(need["id"], duration),
f"Need: {need['id']!r}{need_location}. Duration: {duration!r} [needs.gantt]",
type="needs",
subtype="gantt",
location=node,
)
duration = 1
gantt_element = "[{}] as [{}] lasts {} days\n".format(
Expand Down
9 changes: 7 additions & 2 deletions sphinx_needs/directives/needpie.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ def run(self) -> Sequence[nodes.Node]:
}
add_doc(env, env.docname)

return [targetnode, Needpie("")]
pie_node = Needpie("")
self.set_source_info(pie_node)

return [targetnode, pie_node]


@measure_time("needpie")
Expand Down Expand Up @@ -162,7 +165,9 @@ def process_needpie(
if line.isdigit():
sizes.append(abs(float(line)))
else:
result = len(filter_needs(need_list, needs_config, line))
result = len(
filter_needs(need_list, needs_config, line, location=node)
)
sizes.append(result)
elif current_needpie["filter_func"] and not content:
try:
Expand Down
32 changes: 23 additions & 9 deletions sphinx_needs/filter_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from types import CodeType
from typing import Any, Iterable, TypedDict, TypeVar

from docutils import nodes
from docutils.parsers.rst import directives
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
Expand Down Expand Up @@ -180,7 +181,10 @@
found_needs_by_options.append(need_info)
# Get need by filter string
found_needs_by_string = filter_needs(
all_needs_incl_parts, needs_config, filter_data["filter"]
all_needs_incl_parts,
needs_config,
filter_data["filter"],
location=(filter_data["docname"], filter_data["lineno"]),
)
# Make an intersection of both lists
found_needs = intersection_of_need_results(
Expand All @@ -190,7 +194,10 @@
# There is no other config as the one for filter string.
# So we only need this result.
found_needs = filter_needs(
all_needs_incl_parts, needs_config, filter_data["filter"]
all_needs_incl_parts,
needs_config,
filter_data["filter"],
location=(filter_data["docname"], filter_data["lineno"]),
)
else:
# Provides only a copy of needs to avoid data manipulations.
Expand Down Expand Up @@ -296,6 +303,9 @@
config: NeedsSphinxConfig,
filter_string: None | str = "",
current_need: NeedsInfoType | None = None,
*,
location: tuple[str, int | None] | nodes.Node | None = None,
append_warning: str = "",
) -> list[V]:
"""
Filters given needs based on a given filter string.
Expand All @@ -305,6 +315,8 @@
:param config: NeedsSphinxConfig object
:param filter_string: strings, which gets evaluated against each need
:param current_need: current need, which uses the filter.
:param location: source location for error reporting (docname, line number)
:param append_warning: additional text to append to any failed filter warning

:return: list of found needs
"""
Expand All @@ -328,13 +340,15 @@
):
found_needs.append(filter_need)
except Exception as e:
if not error_reported: # Let's report a filter-problem only onces
location = (
(current_need["docname"], current_need["lineno"])
if current_need
else None
if not error_reported: # Let's report a filter-problem only once
if append_warning:
append_warning = f" {append_warning}"

Check warning on line 345 in sphinx_needs/filter_common.py

View check run for this annotation

Codecov / codecov/patch

sphinx_needs/filter_common.py#L345

Added line #L345 was not covered by tests
log.warning(
f"{e}{append_warning} [needs.filter]",
type="needs",
subtype="filter",
location=location,
)
log.warning(str(e) + " [needs]", type="needs", location=location)
error_reported = True

return found_needs
Expand Down Expand Up @@ -381,5 +395,5 @@
else:
result = bool(eval(filter_string, filter_context))
except Exception as e:
raise NeedsInvalidFilter(f"Filter {filter_string} not valid. Error: {e}.")
raise NeedsInvalidFilter(f"Filter {filter_string!r} not valid. Error: {e}.")
return result
6 changes: 5 additions & 1 deletion sphinx_needs/functions/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,11 @@ def copy(

if filter:
result = filter_needs(
needs.values(), NeedsSphinxConfig(app.config), filter, need
needs.values(),
NeedsSphinxConfig(app.config),
filter,
need,
location=(need["docname"], need["lineno"]),
)
if result:
need = result[0]
Expand Down
23 changes: 20 additions & 3 deletions sphinx_needs/roles/need_count.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,28 @@
filters = filter.split(" ? ")
if len(filters) == 1:
need_list = prepare_need_list(all_needs) # adds parts to need_list
amount = str(len(filter_needs(need_list, needs_config, filters[0])))
amount = str(
len(
filter_needs(
need_list,
needs_config,
filters[0],
location=node_need_count,
)
)
)
elif len(filters) == 2:
need_list = prepare_need_list(all_needs) # adds parts to need_list
amount_1 = len(filter_needs(need_list, needs_config, filters[0]))
amount_2 = len(filter_needs(need_list, needs_config, filters[1]))
amount_1 = len(

Check warning on line 52 in sphinx_needs/roles/need_count.py

View check run for this annotation

Codecov / codecov/patch

sphinx_needs/roles/need_count.py#L52

Added line #L52 was not covered by tests
filter_needs(
need_list, needs_config, filters[0], location=node_need_count
)
)
amount_2 = len(

Check warning on line 57 in sphinx_needs/roles/need_count.py

View check run for this annotation

Codecov / codecov/patch

sphinx_needs/roles/need_count.py#L57

Added line #L57 was not covered by tests
filter_needs(
need_list, needs_config, filters[1], location=node_need_count
)
)
amount = f"{amount_1 / amount_2 * 100:2.1f}"
elif len(filters) > 2:
raise NeedsInvalidFilter(
Expand Down
5 changes: 4 additions & 1 deletion sphinx_needs/warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ def process_warnings(app: Sphinx, exception: Exception | None) -> None:
if isinstance(warning_filter, str):
# filter string used
result = filter_needs(
checked_needs.values(), needs_config, warning_filter
checked_needs.values(),
needs_config,
warning_filter,
append_warning=f"(from warning filter {warning_name!r})",
)
elif callable(warning_filter):
# custom defined filter code used from conf.py
Expand Down
15 changes: 9 additions & 6 deletions tests/doc_test/filter_doc/filter_all.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,33 @@ filter_all
==========

.. req:: req_a_not
:tags: 1;
:tags: 1
:status: open
:hide:
:duration: 1

.. req:: req_b_found
:tags: 2;
:tags: 2
:status: closed

.. req:: req_c_not
:tags: 1;2;
:tags: 1;2
:status: open
:hide:
:duration: 1

.. req:: req_d_found
:tags: 1;2;
:tags: 1;2
:status: closed
:duration: 1

.. story:: story_1_not
:tags: 3;
:tags: 3
:status: none
:hide:

.. story:: story_2_found
:tags: 2;
:tags: 2
:status: none

.. test:: my_test
Expand Down
5 changes: 3 additions & 2 deletions tests/doc_test/filter_doc/filter_no_needs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ filter_no_needs

.. req:: filter_warning_req_a
:id: FILTER_001
:tags: 1;
:tags: 1
:status: open
:hide:
:duration: 1

.. req:: filter_warning_req_b
:id: FILTER_002
:tags: 2;
:tags: 2
:status: closed
:hide:

Expand Down
Loading
Loading