mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
docs(context): fix unrunnable ContextGraph docstring example (#921)
* docs(context): fix unrunnable ContextGraph docstring example The module docstring's Example Usage block called add_node/add_edge with keyword arguments they do not accept. add_node(node_id, node_type, ...) takes node_type positionally and has no properties parameter, so the documented call raised TypeError; add_edge's parameter is edge_type, so type= fell through to **properties and polluted edge metadata while appearing to work. Two of the three broken forms failed silently rather than raising, storing a nested properties dict or a stray type key instead of erroring. Add regression tests that execute the documented calls and assert the docstring itself does not reintroduce the invalid kwargs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(context): close two blind spots in the docstring regression guards The guards added in the previous commit could pass while checking nothing. _example_block() terminated the capture at the first "\n\n". The Example Usage block already contains ">>> " spacer lines, so any reformatting that turned one into a bare blank line would truncate the capture -- potentially to empty -- and the guards would then scan a block that no longer held the add_node/add_edge calls they exist to police. Both guards also iterated over re.findall() without asserting a match. Zero matches meant zero assertions and a green test, so the two failure modes compounded: a truncated block produced no matches, and no matches produced a pass. Terminate the block at the next top-level section header (^\S) or end of docstring instead, so blank lines inside the example are harmless, and assert the captured block, the parsed statement list, and each guard's match list are all non-empty. Extract statements with doctest.DocTestParser rather than a line regex. This also catches a call reformatted across "..." continuation lines, which the ">>> graph.add_node(.*" pattern silently skipped, and lets test_documented_calls_execute exec the docstring's own statements instead of a retyped copy that could drift from it. Full doctest.testmod isn't usable here: add_node/add_edge return True and the docs carry no expected-output lines, so it reports 4 spurious failures. Narrow the kwarg check to (?<![\w])type\s*= so a legitimate node_type= or edge_type= in the docs no longer trips a guard aimed at bare type=. Verified by mutating the module docstring and re-running the guards: extra blank lines with a valid example still pass; regressed add_node/add_edge, a type= on a continuation line, deleted calls, and a deleted section all fail; a legitimate node_type= passes. 6 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(context): correct precedent lookup in docstring example --------- Co-authored-by: Pravit Ampapathini <pravitampapathini@Pravits-MacBook-Air-3.local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Sameer Kadam <sskadam6305@gmail.com> Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 5
Pravit Ampapathini
Sameer Kadam
Mohd Kaif
parent
baf8f01f85
commit
5c2901ae27
@@ -72,9 +72,9 @@ Example Usage:
|
||||
... node_embeddings=True)
|
||||
>>>
|
||||
>>> # Basic graph operations
|
||||
>>> graph.add_node("Python", type="language", properties={"popularity": "high"})
|
||||
>>> graph.add_node("Programming", type="concept")
|
||||
>>> graph.add_edge("Python", "Programming", type="related_to")
|
||||
>>> graph.add_node("Python", "language", popularity="high")
|
||||
>>> graph.add_node("Programming", "concept")
|
||||
>>> graph.add_edge("Python", "Programming", "related_to")
|
||||
>>> centrality = graph.get_node_centrality("Python")
|
||||
>>> similar = graph.find_similar_nodes("Python", similarity_type="content")
|
||||
>>> analysis = graph.analyze_graph_with_kg()
|
||||
@@ -88,7 +88,7 @@ Example Usage:
|
||||
... confidence=0.95,
|
||||
... entities=["customer_123", "property_456"]
|
||||
... )
|
||||
>>> precedents = graph.find_precedents("loan_approval", limit=5)
|
||||
>>> precedents = graph.find_precedents(decision_id, limit=5)
|
||||
>>> influence = graph.analyze_decision_influence(decision_id)
|
||||
>>> insights = graph.get_decision_insights()
|
||||
>>> causality = graph.trace_decision_causality(decision_id)
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regression tests for the ContextGraph module docstring example.
|
||||
|
||||
The "Example Usage" block in ``semantica/context/context_graph.py`` previously
|
||||
called ``add_node``/``add_edge`` with keyword arguments those methods do not
|
||||
accept (``type=`` and ``properties=``), so the documented example raised
|
||||
``TypeError`` -- and the near-miss variants silently nested the properties dict
|
||||
instead of failing.
|
||||
|
||||
These tests keep the documented example executable and pin the two behaviours
|
||||
that made the original mistake easy to miss.
|
||||
"""
|
||||
|
||||
import doctest
|
||||
import re
|
||||
from typing import Dict, List
|
||||
|
||||
import pytest
|
||||
|
||||
import semantica.context.context_graph as context_graph_module
|
||||
from semantica.context.context_graph import ContextGraph
|
||||
|
||||
# The example block runs to the next top-level section header (a line starting
|
||||
# in column 0, e.g. "Production Use Cases:") or the end of the docstring.
|
||||
# Terminating on the next header rather than on a blank line keeps the capture
|
||||
# intact when the example gains blank lines or extra paragraphs.
|
||||
_EXAMPLE_BLOCK_RE = re.compile(r"^Example Usage:\n(.*?)(?=^\S|\Z)", re.DOTALL | re.MULTILINE)
|
||||
|
||||
# ``type=`` as its own keyword, but not the legitimate ``node_type=``/``edge_type=``.
|
||||
_BARE_TYPE_KWARG_RE = re.compile(r"(?<![\w])type\s*=")
|
||||
|
||||
|
||||
def _example_block() -> str:
|
||||
"""Return the 'Example Usage' block from the module docstring."""
|
||||
doc = context_graph_module.__doc__ or ""
|
||||
match = _EXAMPLE_BLOCK_RE.search(doc)
|
||||
assert match, "module docstring no longer contains an 'Example Usage:' block"
|
||||
block = match.group(1).strip()
|
||||
assert block, "the 'Example Usage:' block in the module docstring is empty"
|
||||
return block
|
||||
|
||||
|
||||
def _example_statements() -> List[str]:
|
||||
"""Return the documented ``>>>`` statements, continuation lines included."""
|
||||
statements = [example.source for example in doctest.DocTestParser().get_examples(_example_block())]
|
||||
assert statements, "the 'Example Usage:' block no longer contains any '>>>' statements"
|
||||
return statements
|
||||
|
||||
|
||||
def _statements_calling(method: str) -> List[str]:
|
||||
"""Return the documented statements that call ``graph.<method>(``."""
|
||||
return [stmt for stmt in _example_statements() if "graph.{}(".format(method) in stmt]
|
||||
|
||||
|
||||
def _run_example() -> Dict[str, object]:
|
||||
"""Execute the documented example verbatim and return its namespace."""
|
||||
source = "".join(_example_statements())
|
||||
namespace: Dict[str, object] = {}
|
||||
exec(compile(source, "<context_graph module docstring>", "exec"), namespace)
|
||||
return namespace
|
||||
|
||||
|
||||
class TestDocstringExampleIsRunnable:
|
||||
"""The documented example must execute exactly as written."""
|
||||
|
||||
def test_documented_calls_execute(self):
|
||||
# Run the docstring text itself so this test cannot drift from the docs.
|
||||
ns = _run_example()
|
||||
graph = ns["graph"]
|
||||
|
||||
assert "Python" in graph.nodes
|
||||
assert "Programming" in graph.nodes
|
||||
assert graph.nodes["Python"].node_type == "language"
|
||||
assert graph.nodes["Programming"].node_type == "concept"
|
||||
|
||||
neighbors = graph.get_neighbors("Python", hops=1)
|
||||
assert any(n["id"] == "Programming" for n in neighbors)
|
||||
|
||||
# record_decision must return a non-empty string ID.
|
||||
assert isinstance(ns["decision_id"], str) and ns["decision_id"]
|
||||
# find_precedents must be called with that ID and return a list.
|
||||
assert isinstance(ns["precedents"], list)
|
||||
|
||||
def test_node_properties_are_stored_flat(self):
|
||||
"""``popularity`` must land as a top-level property, not nested.
|
||||
|
||||
Passing the previously documented ``properties={...}`` does not raise --
|
||||
it stores a dict *inside* the properties dict, which is why the original
|
||||
docs bug could reach a user's graph unnoticed.
|
||||
"""
|
||||
graph = ContextGraph(advanced_analytics=False)
|
||||
graph.add_node("Python", "language", popularity="high")
|
||||
|
||||
assert graph.nodes["Python"].properties == {"popularity": "high"}
|
||||
assert graph.find_node("Python")["metadata"]["popularity"] == "high"
|
||||
assert "properties" not in graph.nodes["Python"].properties
|
||||
|
||||
def test_edge_type_is_positional_not_a_property(self):
|
||||
"""``related_to`` must be the edge type, not a stray metadata key."""
|
||||
graph = ContextGraph(advanced_analytics=False)
|
||||
graph.add_node("Python", "language")
|
||||
graph.add_node("Programming", "concept")
|
||||
graph.add_edge("Python", "Programming", "related_to")
|
||||
|
||||
edge = graph.edges[0]
|
||||
assert edge.edge_type == "related_to"
|
||||
assert "type" not in edge.metadata
|
||||
|
||||
|
||||
class TestDocstringExampleDoesNotRegress:
|
||||
"""Guard the docstring text itself, not just equivalent code."""
|
||||
|
||||
def test_add_node_example_supplies_node_type_positionally(self):
|
||||
calls = _statements_calling("add_node")
|
||||
assert calls, "the 'Example Usage:' block no longer calls graph.add_node()"
|
||||
for call in calls:
|
||||
assert not _BARE_TYPE_KWARG_RE.search(call), (
|
||||
f"add_node example passes type= as a keyword: {call!r}. "
|
||||
"node_type is positional-required; type= falls through to "
|
||||
"**properties and the call raises TypeError."
|
||||
)
|
||||
assert "properties=" not in call, (
|
||||
f"add_node example passes properties=: {call!r}. "
|
||||
"add_node has no properties parameter; extra properties are "
|
||||
"passed as **kwargs."
|
||||
)
|
||||
|
||||
def test_add_edge_example_supplies_edge_type_positionally(self):
|
||||
calls = _statements_calling("add_edge")
|
||||
assert calls, "the 'Example Usage:' block no longer calls graph.add_edge()"
|
||||
for call in calls:
|
||||
assert not _BARE_TYPE_KWARG_RE.search(call), (
|
||||
f"add_edge example passes type= as a keyword: {call!r}. "
|
||||
"The parameter is edge_type; type= is silently absorbed into "
|
||||
"**properties and pollutes edge metadata."
|
||||
)
|
||||
|
||||
def test_broken_form_still_raises(self):
|
||||
"""Pin the signature contract the example has to respect."""
|
||||
graph = ContextGraph(advanced_analytics=False)
|
||||
with pytest.raises(TypeError, match="node_type"):
|
||||
graph.add_node("Python", type="language", properties={"popularity": "high"})
|
||||
Reference in New Issue
Block a user