Skip to content

Commit 5ce4077

Browse files
authored
Upgrade sphinx-needs to 6.3.0 (#361)
* feat: upgrade sphinx-needs to 6.3.0 Add support for new options (is_import, constraints) introduced in 6.3.0 and remove the plantuml workaround that was only needed for older versions. * feat: added a minimum version requirement for sphinx * refactor: replace NeedsInfoType with NeedItem across multiple files * refactor: remove unused link keys from need function * refactor: reorganize imports in test and source code linker modules * refactor: enhance type hints for better clarity in check_options and test_source_code_link_integration * refactor: improve type casting and validation in _get_normalized function
1 parent d532423 commit 5ce4077

File tree

15 files changed

+274
-213
lines changed

15 files changed

+274
-213
lines changed

docs/conf.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,5 @@
1616
version = "0.1"
1717

1818
extensions = [
19-
# TODO remove plantuml here once docs-as-code is updated to sphinx-needs 6
20-
"sphinxcontrib.plantuml",
2119
"score_sphinx_bundle",
2220
]

src/extensions/score_metamodel/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
from sphinx.application import Sphinx
2020
from sphinx_needs import logging
21-
from sphinx_needs.data import NeedsInfoType, NeedsView, SphinxNeedsData
21+
from sphinx_needs.data import NeedsView, SphinxNeedsData
22+
from sphinx_needs.need_item import NeedItem
2223

2324
from src.extensions.score_metamodel.external_needs import connect_external_needs
2425
from src.extensions.score_metamodel.log import CheckLogger
@@ -39,7 +40,7 @@
3940

4041
logger = logging.get_logger(__name__)
4142

42-
local_check_function = Callable[[Sphinx, NeedsInfoType, CheckLogger], None]
43+
local_check_function = Callable[[Sphinx, NeedItem, CheckLogger], None]
4344
graph_check_function = Callable[[Sphinx, NeedsView, CheckLogger], None]
4445

4546
local_checks: list[local_check_function] = []
@@ -170,7 +171,7 @@ def _remove_prefix(word: str, prefixes: list[str]) -> str:
170171
return word
171172

172173

173-
def _get_need_type_for_need(app: Sphinx, need: NeedsInfoType) -> ScoreNeedType:
174+
def _get_need_type_for_need(app: Sphinx, need: NeedItem) -> ScoreNeedType:
174175
for nt in app.config.needs_types:
175176
if nt["directive"] == need["type"]:
176177
return nt

src/extensions/score_metamodel/checks/attributes_format.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from score_metamodel import CheckLogger, ProhibitedWordCheck, ScoreNeedType, local_check
1818
from sphinx.application import Sphinx
19-
from sphinx_needs.data import NeedsInfoType
19+
from sphinx_needs.need_item import NeedItem
2020

2121

2222
def get_need_type(needs_types: list[ScoreNeedType], directive: str) -> ScoreNeedType:
@@ -29,7 +29,7 @@ def get_need_type(needs_types: list[ScoreNeedType], directive: str) -> ScoreNeed
2929

3030
# req-Id: tool_req__docs_common_attr_id_scheme
3131
@local_check
32-
def check_id_format(app: Sphinx, need: NeedsInfoType, log: CheckLogger):
32+
def check_id_format(app: Sphinx, need: NeedItem, log: CheckLogger):
3333
"""
3434
Checking if the title, directory and feature are included in
3535
the requirement id or not.
@@ -57,7 +57,7 @@ def check_id_format(app: Sphinx, need: NeedsInfoType, log: CheckLogger):
5757

5858

5959
@local_check
60-
def check_id_length(app: Sphinx, need: NeedsInfoType, log: CheckLogger):
60+
def check_id_length(app: Sphinx, need: NeedItem, log: CheckLogger):
6161
"""
6262
Validates that the requirement ID does not exceed the hard limit of 45 characters.
6363
While the recommended limit is 30 characters, this check enforces a strict maximum
@@ -85,7 +85,7 @@ def check_id_length(app: Sphinx, need: NeedsInfoType, log: CheckLogger):
8585

8686

8787
def _check_options_for_prohibited_words(
88-
prohibited_word_checks: ProhibitedWordCheck, need: NeedsInfoType, log: CheckLogger
88+
prohibited_word_checks: ProhibitedWordCheck, need: NeedItem, log: CheckLogger
8989
):
9090
options: list[str] = [
9191
x for x in prohibited_word_checks.option_check if x != "types"
@@ -109,7 +109,7 @@ def _check_options_for_prohibited_words(
109109
# req-Id: tool_req__docs_common_attr_desc_wording
110110
# req-Id: tool_req__docs_common_attr_title
111111
@local_check
112-
def check_for_prohibited_words(app: Sphinx, need: NeedsInfoType, log: CheckLogger):
112+
def check_for_prohibited_words(app: Sphinx, need: NeedItem, log: CheckLogger):
113113
need_options = get_need_type(app.config.needs_types, need["type"])
114114
prohibited_word_checks: list[ProhibitedWordCheck] = (
115115
app.config.prohibited_words_checks

src/extensions/score_metamodel/checks/check_options.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# SPDX-License-Identifier: Apache-2.0
1212
# *******************************************************************************
1313
import re
14+
from typing import cast
1415

1516
from score_metamodel import (
1617
CheckLogger,
@@ -20,7 +21,7 @@
2021
)
2122
from score_metamodel.metamodel_types import AllowedLinksType
2223
from sphinx.application import Sphinx
23-
from sphinx_needs.data import NeedsInfoType
24+
from sphinx_needs.need_item import NeedItem
2425

2526

2627
def get_need_type(needs_types: list[ScoreNeedType], directive: str) -> ScoreNeedType:
@@ -31,9 +32,7 @@ def get_need_type(needs_types: list[ScoreNeedType], directive: str) -> ScoreNeed
3132
raise ValueError(f"Need type {directive} not found in needs_types")
3233

3334

34-
def _get_normalized(
35-
need: NeedsInfoType, key: str, remove_prefix: bool = False
36-
) -> list[str]:
35+
def _get_normalized(need: NeedItem, key: str, remove_prefix: bool = False) -> list[str]:
3736
"""Normalize a raw value into a list of strings."""
3837
raw_value = need.get(key, None)
3938
if not raw_value:
@@ -42,17 +41,23 @@ def _get_normalized(
4241
if remove_prefix:
4342
return [_remove_namespace_prefix_(raw_value)]
4443
return [raw_value]
45-
if isinstance(raw_value, list) and all(isinstance(v, str) for v in raw_value):
44+
if isinstance(raw_value, list):
45+
# Verify all elements are strings
46+
raw_list = cast(list[object], raw_value)
47+
for item in raw_list:
48+
if not isinstance(item, str):
49+
raise ValueError
50+
str_list = cast(list[str], raw_value)
4651
if remove_prefix:
47-
return [_remove_namespace_prefix_(v) for v in raw_value]
48-
return raw_value
52+
return [_remove_namespace_prefix_(v) for v in str_list]
53+
return str_list
4954
raise ValueError
5055

5156

5257
def _validate_value_pattern(
5358
value: str,
5459
pattern: str,
55-
need: NeedsInfoType,
60+
need: NeedItem,
5661
field: str,
5762
):
5863
"""Check if a value matches the given pattern and log the result.
@@ -76,7 +81,7 @@ def _remove_namespace_prefix_(word: str) -> str:
7681
def validate_options(
7782
log: CheckLogger,
7883
need_type: ScoreNeedType,
79-
need: NeedsInfoType,
84+
need: NeedItem,
8085
):
8186
"""
8287
Validates that options in a need match their expected patterns.
@@ -103,7 +108,7 @@ def _validate(attributes_to_allowed_values: dict[str, str], mandatory: bool):
103108
def validate_links(
104109
log: CheckLogger,
105110
need_type: ScoreNeedType,
106-
need: NeedsInfoType,
111+
need: NeedItem,
107112
):
108113
"""
109114
Validates that links in a need match the expected types or regexes.
@@ -156,7 +161,7 @@ def _validate(
156161
@local_check
157162
def check_options(
158163
app: Sphinx,
159-
need: NeedsInfoType,
164+
need: NeedItem,
160165
log: CheckLogger,
161166
):
162167
"""
@@ -172,7 +177,7 @@ def check_options(
172177
@local_check
173178
def check_extra_options(
174179
app: Sphinx,
175-
need: NeedsInfoType,
180+
need: NeedItem,
176181
log: CheckLogger,
177182
):
178183
"""
@@ -224,7 +229,7 @@ def parse_milestone(value: str) -> tuple[int, int, int]:
224229
@local_check
225230
def check_validity_consistency(
226231
app: Sphinx,
227-
need: NeedsInfoType,
232+
need: NeedItem,
228233
log: CheckLogger,
229234
):
230235
"""

src/extensions/score_metamodel/checks/graph_checks.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@
2121
)
2222
from sphinx.application import Sphinx
2323
from sphinx_needs.config import NeedType
24-
from sphinx_needs.data import NeedsInfoType, NeedsView
24+
from sphinx_needs.data import NeedsView
25+
from sphinx_needs.need_item import NeedItem
2526

2627

27-
def eval_need_check(need: NeedsInfoType, check: str, log: CheckLogger) -> bool:
28+
def eval_need_check(need: NeedItem, check: str, log: CheckLogger) -> bool:
2829
"""
2930
Perform a single check on a need:
3031
1. Split the check into its parts
@@ -57,7 +58,7 @@ def eval_need_check(need: NeedsInfoType, check: str, log: CheckLogger) -> bool:
5758

5859

5960
def eval_need_condition(
60-
need: NeedsInfoType, condition: str | dict[str, list[Any]], log: CheckLogger
61+
need: NeedItem, condition: str | dict[str, list[Any]], log: CheckLogger
6162
) -> bool:
6263
"""Evaluate a condition on a need:
6364
1. Check if the condition is only a simple check (e.g. "status == valid")
@@ -101,16 +102,16 @@ def eval_need_condition(
101102

102103
def filter_needs_by_criteria(
103104
needs_types: list[NeedType],
104-
needs: list[NeedsInfoType],
105+
needs: list[NeedItem],
105106
needs_selection_criteria: dict[str, str],
106107
log: CheckLogger,
107-
) -> list[NeedsInfoType]:
108+
) -> list[NeedItem]:
108109
"""Create a list of needs that match the selection criteria.:
109110
- If it is an include selection add the include to the pattern
110111
- If it is an exclude selection add a "^" to the pattern
111112
"""
112113

113-
selected_needs: list[NeedsInfoType] = []
114+
selected_needs: list[NeedItem] = []
114115
pattern: list[str] = []
115116
need_pattern: str = list(needs_selection_criteria.keys())[0]
116117
# Verify Inputs

src/extensions/score_metamodel/checks/id_contains_feature.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
local_check,
1919
)
2020
from sphinx.application import Sphinx
21-
from sphinx_needs.data import NeedsInfoType
21+
from sphinx_needs.need_item import NeedItem
2222

2323

2424
@local_check
25-
def id_contains_feature(app: Sphinx, need: NeedsInfoType, log: CheckLogger):
25+
def id_contains_feature(app: Sphinx, need: NeedItem, log: CheckLogger):
2626
"""
2727
The ID is expected to be in the format '<Req Type>__<feature>__<Title>'.
2828
Most of this is ensured via regex in the metamodel.

src/extensions/score_metamodel/checks/standards.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
# *******************************************************************************
1313
# from sphinx.application import Sphinx
1414

15-
from sphinx_needs.data import NeedsInfoType
15+
from sphinx_needs.need_item import NeedItem
1616

1717
# from score_metamodel import (
1818
# CheckLogger,
1919
# graph_check,
2020
# )
2121

2222

23-
def get_standards_needs(needs: list[NeedsInfoType]) -> dict[str, NeedsInfoType]:
23+
def get_standards_needs(needs: list[NeedItem]) -> dict[str, NeedItem]:
2424
"""
2525
Return a dictionary of all standard requirements from the Sphinx app's needs.
2626
"""
@@ -29,32 +29,32 @@ def get_standards_needs(needs: list[NeedsInfoType]) -> dict[str, NeedsInfoType]:
2929

3030

3131
def get_standards_workproducts(
32-
needs: list[NeedsInfoType],
33-
) -> dict[str, NeedsInfoType]:
32+
needs: list[NeedItem],
33+
) -> dict[str, NeedItem]:
3434
"""
3535
Return a dictionary of standard workproducts from the Sphinx app's needs.
3636
"""
3737

3838
return {need["id"]: need for need in needs if need["type"] == "std_wp"}
3939

4040

41-
def get_workflows(needs: list[NeedsInfoType]) -> dict[str, NeedsInfoType]:
41+
def get_workflows(needs: list[NeedItem]) -> dict[str, NeedItem]:
4242
"""
4343
Return a dictionary of all workflows from the Sphinx app's needs.
4444
"""
4545

4646
return {need["id"]: need for need in needs if need.get("type") == "workflow"}
4747

4848

49-
def get_workproducts(needs: list[NeedsInfoType]) -> dict[str, NeedsInfoType]:
49+
def get_workproducts(needs: list[NeedItem]) -> dict[str, NeedItem]:
5050
"""
5151
Return a dictionary of all workproducts from the Sphinx app's needs.
5252
"""
5353

5454
return {need["id"]: need for need in needs if need.get("type") == "workproduct"}
5555

5656

57-
def get_compliance_req_needs(needs: list[NeedsInfoType]) -> set[str]:
57+
def get_compliance_req_needs(needs: list[NeedItem]) -> set[str]:
5858
"""
5959
Return a set of all compliance_req values from the Sphinx app's needs,
6060
but only if the need type is one of the specified process-related types.
@@ -68,7 +68,7 @@ def get_compliance_req_needs(needs: list[NeedsInfoType]) -> set[str]:
6868
}
6969

7070

71-
def get_compliance_wp_needs(needs: list[NeedsInfoType]) -> set[str]:
71+
def get_compliance_wp_needs(needs: list[NeedItem]) -> set[str]:
7272
"""
7373
Return a set of all compliance_wp values from the Sphinx app's needs,
7474
but only if the need type is "workproduct".
@@ -177,7 +177,7 @@ def get_compliance_wp_needs(needs: list[NeedsInfoType]) -> set[str]:
177177

178178

179179
def my_pie_linked_standard_requirements(
180-
needs: list[NeedsInfoType], results: list[int], **kwargs: str | int | float
180+
needs: list[NeedItem], results: list[int], **kwargs: str | int | float
181181
) -> None:
182182
"""
183183
Function to render the chart of check for standard requirements linked
@@ -210,7 +210,7 @@ def my_pie_linked_standard_requirements(
210210

211211

212212
def my_pie_linked_standard_requirements_by_tag(
213-
needs: list[NeedsInfoType], results: list[int], **kwargs: str | int | float
213+
needs: list[NeedItem], results: list[int], **kwargs: str | int | float
214214
) -> None:
215215
"""
216216
Filter function used for 'needpie' directives.
@@ -258,7 +258,7 @@ def my_pie_linked_standard_requirements_by_tag(
258258

259259

260260
def my_pie_linked_standard_workproducts(
261-
needs: list[NeedsInfoType], results: list[int], **kwargs: str | int | float
261+
needs: list[NeedItem], results: list[int], **kwargs: str | int | float
262262
) -> None:
263263
"""
264264
Function to render the chart of check for standar workproducts linked
@@ -292,7 +292,7 @@ def my_pie_linked_standard_workproducts(
292292

293293

294294
def my_pie_workproducts_contained_in_exactly_one_workflow(
295-
needs: list[NeedsInfoType], results: list[int], **kwargs: str | int | float
295+
needs: list[NeedItem], results: list[int], **kwargs: str | int | float
296296
) -> None:
297297
"""
298298
Function to render the chart of check for workproducts that are contained

src/extensions/score_metamodel/log.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515

1616
from docutils.nodes import Node
1717
from sphinx_needs import logging
18-
from sphinx_needs.data import NeedsInfoType
1918
from sphinx_needs.logging import SphinxLoggerAdapter
19+
from sphinx_needs.need_item import NeedItem
2020

2121
Location = str | tuple[str | None, int | None] | Node | None
2222
NewCheck = tuple[str, Location]
@@ -32,7 +32,7 @@ def __init__(self, log: SphinxLoggerAdapter, prefix: str):
3232
self._new_checks: list[NewCheck] = []
3333

3434
@staticmethod
35-
def _location(need: NeedsInfoType, prefix: str):
35+
def _location(need: NeedItem, prefix: str):
3636
def get(key: str) -> Any:
3737
return need.get(key, None)
3838

@@ -49,15 +49,15 @@ def get(key: str) -> Any:
4949
return None
5050

5151
def warning_for_option(
52-
self, need: NeedsInfoType, option: str, msg: str, is_new_check: bool = False
52+
self, need: NeedItem, option: str, msg: str, is_new_check: bool = False
5353
):
5454
full_msg = f"{need['id']}.{option} ({need.get(option, None)}): {msg}"
5555
location = CheckLogger._location(need, self._prefix)
5656
self._log_message(full_msg, location, is_new_check)
5757

5858
def warning_for_link(
5959
self,
60-
need: NeedsInfoType,
60+
need: NeedItem,
6161
option: str,
6262
problematic_value: str,
6363
allowed_values: list[str],
@@ -75,9 +75,7 @@ def warning_for_link(
7575

7676
self.warning_for_need(need, msg, is_new_check=is_new_check)
7777

78-
def warning_for_need(
79-
self, need: NeedsInfoType, msg: str, is_new_check: bool = False
80-
):
78+
def warning_for_need(self, need: NeedItem, msg: str, is_new_check: bool = False):
8179
full_msg = f"{need['id']}: {msg}"
8280
location = CheckLogger._location(need, self._prefix)
8381
self._log_message(full_msg, location, is_new_check)

0 commit comments

Comments
 (0)