Skip to content

Commit 4655747

Browse files
committed
fix(client,demo): limit validation, HybridResult comment, embedded fallthrough
- text_search, hybrid_text_vector_search: validate limit is int >= 1 (reject bool and negative/zero to avoid opaque server-side failures) - HybridResult: fix doc comment — point callers to client.get_node() instead of invalid MATCH (n) WHERE id(n) = ... Cypher - notebook 03: when auto-detected localhost port is open but not serving CoordiNode, close and fall through to LocalClient instead of raising; only raise hard error when COORDINODE_ADDR was explicitly provided
1 parent e581683 commit 4655747

2 files changed

Lines changed: 31 additions & 18 deletions

File tree

coordinode/coordinode/client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ def __init__(self, proto_result: Any) -> None:
122122
self.score: float = proto_result.score
123123
# NOTE: proto HybridResult carries only node_id + score (no embedded Node
124124
# message). A full node is not included by design — the server returns IDs
125-
# for efficiency; callers that need properties should issue a follow-up
126-
# Cypher query: MATCH (n) WHERE id(n) = <node_id> RETURN n.
125+
# for efficiency. Callers that need node properties should use the client
126+
# API: `client.get_node(self.node_id)`, or match on an application-level
127+
# property in Cypher (e.g. WHERE n.id = <value>).
127128

128129
def __repr__(self) -> str:
129130
return f"HybridResult(node_id={self.node_id}, score={self.score:.6f})"
@@ -765,6 +766,8 @@ async def text_search(
765766
or via :meth:`create_text_index`. Nodes written before the index
766767
was created are indexed immediately at DDL execution time.
767768
"""
769+
if not isinstance(limit, int) or isinstance(limit, bool) or limit < 1:
770+
raise ValueError(f"limit must be an integer >= 1, got {limit!r}.")
768771
from coordinode._proto.coordinode.v1.query.text_pb2 import TextSearchRequest # type: ignore[import]
769772

770773
req = TextSearchRequest(label=label, query=query, limit=limit, fuzzy=fuzzy, language=language)
@@ -811,6 +814,8 @@ async def hybrid_text_vector_search(
811814
``CREATE TEXT INDEX`` Cypher statement. Calling this method on a
812815
label without a text index returns an empty list.
813816
"""
817+
if not isinstance(limit, int) or isinstance(limit, bool) or limit < 1:
818+
raise ValueError(f"limit must be an integer >= 1, got {limit!r}.")
814819
from coordinode._proto.coordinode.v1.query.text_pb2 import ( # type: ignore[import]
815820
HybridTextVectorSearchRequest,
816821
)

demo/notebooks/03_langgraph_agent.ipynb

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,10 @@
142142
" return False\n",
143143
"\n",
144144
"\n",
145+
"_use_embedded = True\n",
146+
"\n",
145147
"if os.environ.get(\"COORDINODE_ADDR\"):\n",
148+
" # Explicit address — fail hard if health check fails.\n",
146149
" COORDINODE_ADDR = os.environ[\"COORDINODE_ADDR\"]\n",
147150
" from coordinode import CoordinodeClient\n",
148151
"\n",
@@ -151,6 +154,7 @@
151154
" client.close()\n",
152155
" raise RuntimeError(f\"CoordiNode at {COORDINODE_ADDR} is not serving health checks\")\n",
153156
" print(f\"Connected to {COORDINODE_ADDR}\")\n",
157+
" _use_embedded = False\n",
154158
"else:\n",
155159
" try:\n",
156160
" grpc_port = int(os.environ.get(\"COORDINODE_PORT\", \"7080\"))\n",
@@ -162,23 +166,27 @@
162166
" from coordinode import CoordinodeClient\n",
163167
"\n",
164168
" client = CoordinodeClient(COORDINODE_ADDR)\n",
165-
" if not client.health():\n",
169+
" if client.health():\n",
170+
" print(f\"Connected to {COORDINODE_ADDR}\")\n",
171+
" _use_embedded = False\n",
172+
" else:\n",
173+
" # Port is open but not a CoordiNode server — fall through to embedded.\n",
166174
" client.close()\n",
167-
" raise RuntimeError(f\"CoordiNode at {COORDINODE_ADDR} is not serving health checks\")\n",
168-
" print(f\"Connected to {COORDINODE_ADDR}\")\n",
169-
" else:\n",
170-
" # No server available — use the embedded in-process engine.\n",
171-
" try:\n",
172-
" from coordinode_embedded import LocalClient\n",
173-
" except ImportError as exc:\n",
174-
" raise RuntimeError(\n",
175-
" \"coordinode-embedded is not installed. \"\n",
176-
" f\"Run: pip install {_EMBEDDED_PIP_SPEC}\"\n",
177-
" \" — or start a CoordiNode server and set COORDINODE_ADDR.\"\n",
178-
" ) from exc\n",
179-
"\n",
180-
" client = LocalClient(\":memory:\")\n",
181-
" print(\"Using embedded LocalClient (in-process)\")"
175+
"\n",
176+
"if _use_embedded:\n",
177+
" # No server available — use the embedded in-process engine.\n",
178+
" # Works without Docker or any external service; data is in-memory.\n",
179+
" try:\n",
180+
" from coordinode_embedded import LocalClient\n",
181+
" except ImportError as exc:\n",
182+
" raise RuntimeError(\n",
183+
" \"coordinode-embedded is not installed. \"\n",
184+
" f\"Run: pip install {_EMBEDDED_PIP_SPEC}\"\n",
185+
" \" — or start a CoordiNode server and set COORDINODE_ADDR.\"\n",
186+
" ) from exc\n",
187+
"\n",
188+
" client = LocalClient(\":memory:\")\n",
189+
" print(\"Using embedded LocalClient (in-process)\")\n"
182190
]
183191
},
184192
{

0 commit comments

Comments
 (0)