Skip to content

Commit 8ad874b

Browse files
authored
🔧 Store NeedPartData.backlinks as NeedLink instead of str (useblocks#1679)
Change `NeedPartData.backlinks` from `dict[str, list[str]]` to `dict[str, list[NeedLink]]`, aligning it with how `NeedItem` already stores its backlinks internally. Previously, part backlinks were stored as raw strings and converted to `NeedLink` on read (e.g. in `NeedPartItem.get_backlinks(as_str=False)`). Now they are stored as `NeedLink` from the start, avoiding redundant re-parsing and making the internal representation consistent across needs and parts. **Changes:** - `NeedPartData.backlinks` type: `dict[str, list[str]]` → `dict[str, list[NeedLink]]` - `resolve_links()`: append `NeedLink(id=key)` instead of raw `str` to part backlinks - `_recompute()`, `NeedPartItem.__init__`: serialize via `to_filter_string()` for dict-like access and JSON output (unchanged external behavior) - `NeedPartItem.get_backlinks`/`iter_backlinks_items`: return stored `NeedLink` directly instead of re-parsing from strings - `_validate_part`: validate `NeedLink` instances instead of `str` No change in external behavior — dict-like access (`item["links_back"]`), JSON serialization, and all public APIs continue to return the same values.
1 parent dce9f99 commit 8ad874b

3 files changed

Lines changed: 15 additions & 9 deletions

File tree

‎sphinx_needs/directives/need.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ def resolve_links(
479479
if linked_part := linked_need.get_part(need_link.part):
480480
if link_type not in linked_part.backlinks:
481481
linked_part.backlinks[link_type] = []
482-
linked_part.backlinks[link_type].append(key)
482+
linked_part.backlinks[link_type].append(NeedLink(id=key))
483483
else:
484484
dead_links.append((link_type, need_link))
485485
else:

‎sphinx_needs/need_item.py‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ class NeedPartData:
183183

184184
id: str
185185
content: str
186-
backlinks: dict[str, list[str]] = field(default_factory=dict)
186+
backlinks: dict[str, list[NeedLink]] = field(default_factory=dict)
187187

188188

189189
@dataclass(slots=True, frozen=True, kw_only=True)
@@ -423,7 +423,11 @@ def _recompute(self) -> None:
423423
"id": p.id,
424424
"content": p.content,
425425
**(
426-
{f"{k}_back": v for k, v in p.backlinks.items() if v} # type: ignore[typeddict-item]
426+
{
427+
f"{k}_back": [li.to_filter_string() for li in v]
428+
for k, v in p.backlinks.items()
429+
if v
430+
} # type: ignore[typeddict-item]
427431
if p.backlinks is not None
428432
else {}
429433
),
@@ -916,11 +920,11 @@ def _validate_part(self, part: NeedPartData) -> None:
916920
if not isinstance(part.backlinks, dict) or any(
917921
not isinstance(k, str)
918922
or not isinstance(v, list)
919-
or any(not isinstance(i, str) for i in v)
923+
or any(not isinstance(i, NeedLink) for i in v)
920924
for k, v in part.backlinks.items()
921925
):
922926
raise ValueError(
923-
f"Part {part.id!r} backlinks must be a dictionary of lists of strings."
927+
f"Part {part.id!r} backlinks must be a dictionary of lists of NeedLink instances."
924928
)
925929
if unknown_part_links := (set(part.backlinks) - set(self._links)):
926930
raise ValueError(
@@ -1020,7 +1024,7 @@ def __init__(self, need: NeedItem, part_id: str) -> None:
10201024
**{
10211025
f"{name}_back": []
10221026
if part.backlinks is None or not (blinks := part.backlinks.get(name))
1023-
else blinks
1027+
else [li.to_filter_string() for li in blinks]
10241028
for name in need.iter_links_keys()
10251029
},
10261030
}
@@ -1270,7 +1274,7 @@ def get_backlinks(
12701274
return self[f"{link_type}_back"] # type: ignore[no-any-return]
12711275
part = self._need.get_part(self.part_id)
12721276
assert part is not None
1273-
return [NeedLink.from_string(v) for v in part.backlinks.get(link_type, [])]
1277+
return part.backlinks.get(link_type, [])
12741278

12751279
@overload
12761280
def iter_backlinks_items(
@@ -1295,5 +1299,5 @@ def iter_backlinks_items(
12951299
assert part is not None
12961300
yield (
12971301
key,
1298-
[NeedLink.from_string(v) for v in part.backlinks.get(key, [])],
1302+
part.backlinks.get(key, []),
12991303
)

‎tests/test_need_item_api.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,9 @@ def test_need_part_item(snapshot):
366366
item = NeedItem(
367367
core=core(),
368368
parts=(
369-
NeedPartData(id="part1", content="Part 1", backlinks={"links": ["ref3"]}),
369+
NeedPartData(
370+
id="part1", content="Part 1", backlinks={"links": [NeedLink(id="ref3")]}
371+
),
370372
),
371373
content=content("Need 1"),
372374
extras={

0 commit comments

Comments
 (0)