Skip to content

Commit eceae5f

Browse files
committed
added few changes
1 parent 6c69703 commit eceae5f

6 files changed

Lines changed: 83 additions & 129 deletions

File tree

Cargo.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/.gitignore

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Test database artifacts — tests must use pytest tmp_path, not hardcoded paths
2+
test_ffi_db/
3+
valoricore_test_db/
4+
valoricore_db/
5+
valori_db/
6+
valori_test_db_debug/
7+
*.db/
8+
9+
# Build / dist
10+
dist/
11+
build/
12+
*.egg-info/
13+
__pycache__/
14+
*.pyc
15+
*.pyo
16+
*.so
17+
*.pyd
18+
19+
# Snapshots written during tests
20+
valoricore_snapshots/

python/tests/test_batch_verify.py

Lines changed: 16 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,32 @@
1-
import pytest
21
#!/usr/bin/env python3
3-
"""Quick test to verify batch insert endpoint after Event Log configuration"""
2+
"""Verify batch insert endpoint after Event Log configuration."""
43

5-
import requests
64
import os
7-
from dotenv import load_dotenv
5+
import pytest
6+
import requests
87

98
pytestmark = pytest.mark.integration
109

11-
# Load environment variables from .env file
12-
load_dotenv()
10+
_BASE_URL = os.getenv("VALORI_URL", "").rstrip("/")
11+
12+
13+
def _base_url() -> str:
14+
if not _BASE_URL:
15+
pytest.skip("VALORI_URL not set — export VALORI_URL=https://your-node or set it in .env")
16+
return _BASE_URL
1317

14-
BASE_URL = os.getenv("VALORI_URL")
15-
if not BASE_URL:
16-
print("❌ ERROR: VALORI_URL environment variable not set")
17-
print("Please set it in .env file or export it:")
18-
print(" export VALORI_URL=https://your-deployment.koyeb.app")
19-
exit(1)
2018

2119
def test_batch_insert():
22-
"""Test batch insert endpoint"""
23-
print(f"Testing batch insert on {BASE_URL}")
24-
25-
# Prepare batch of 5 vectors
20+
base = _base_url()
2621
batch = [
2722
[0.1, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
2823
[0.2, 0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
2924
[0.3, 0.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
3025
[0.4, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
3126
[0.5, 0.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
3227
]
33-
34-
payload = {"batch": batch}
35-
36-
try:
37-
resp = requests.post(f"{BASE_URL}/v1/vectors/batch_insert", json=payload, timeout=10)
38-
39-
print(f"\nStatus Code: {resp.status_code}")
40-
print(f"Response: {resp.text}\n")
41-
42-
if resp.status_code == 200:
43-
data = resp.json()
44-
if "ids" in data:
45-
print(f"✅ SUCCESS: Batch inserted {len(data['ids'])} vectors")
46-
print(f" Assigned IDs: {data['ids']}")
47-
return True
48-
else:
49-
print(f"⚠️ Unexpected response format: {data}")
50-
return False
51-
else:
52-
print(f"❌ FAILED: {resp.text}")
53-
if "Event Log" in resp.text:
54-
print("\n💡 Event Log still not initialized. Possible issues:")
55-
print(" 1. Koyeb deployment not restarted yet")
56-
print(" 2. /app/data directory doesn't exist or not writable")
57-
print(" 3. Persistent volume not mounted")
58-
return False
59-
60-
except Exception as e:
61-
print(f"❌ ERROR: {e}")
62-
return False
63-
64-
if __name__ == "__main__":
65-
print("="*60)
66-
print(" Valoricore Batch Insert Verification")
67-
print("="*60)
68-
69-
# First check if service is up
70-
print("\n1. Checking health...")
71-
try:
72-
health = requests.get(f"{BASE_URL}/health", timeout=5)
73-
if health.status_code == 200:
74-
print(" ✅ Service is up")
75-
else:
76-
print(f" ⚠️ Health check returned {health.status_code}")
77-
except Exception as e:
78-
print(f" ❌ Service unreachable: {e}")
79-
print("\n Please restart your Koyeb deployment first!")
80-
exit(1)
81-
82-
# Test batch insert
83-
print("\n2. Testing batch insert...")
84-
success = test_batch_insert()
85-
86-
if success:
87-
print("\n" + "="*60)
88-
print(" ✅ Batch insert is now working!")
89-
print("="*60)
90-
exit(0)
91-
else:
92-
print("\n" + "="*60)
93-
print(" ❌ Batch insert still failing")
94-
print("="*60)
95-
print("\nNext steps:")
96-
print("1. Check Koyeb logs for Event Log initialization message")
97-
print("2. Verify /app/data exists and is writable")
98-
print("3. Check if persistent storage is mounted")
99-
exit(1)
28+
resp = requests.post(f"{base}/v1/vectors/batch_insert", json={"batch": batch}, timeout=10)
29+
assert resp.status_code == 200, f"batch_insert failed ({resp.status_code}): {resp.text}"
30+
data = resp.json()
31+
assert "ids" in data, f"unexpected response format: {data}"
32+
assert len(data["ids"]) == len(batch)

python/valoricore/exceptions.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ class ConnectionError(ValoricoreError):
88
"""Raised when the client cannot connect to a remote Valoricore node."""
99
pass
1010

11-
class ValidationError(ValoricoreError):
12-
"""Raised when input data (e.g., vector dimensions) is invalid."""
11+
class ValidationError(ValoricoreError, ValueError):
12+
"""Raised when input data (e.g. vector dimensions or FXP bounds) is invalid."""
13+
pass
14+
15+
class ProtocolError(ValoricoreError):
16+
"""Raised for protocol-level problems (unexpected server response shape, etc.)."""
1317
pass
1418

1519
class IntegrityError(ValoricoreError):

python/valoricore/protocol.py

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@
66
from typing import Callable, List, Dict, Any, Optional, TypedDict
77

88
from .memory import MemoryClient
9+
from .remote import _BearerAuth
910
from .kinds import NODE_DOCUMENT, NODE_CHUNK, EDGE_PARENT_OF
11+
from .exceptions import (
12+
ValoricoreError,
13+
ValidationError,
14+
AuthenticationError,
15+
ProtocolError,
16+
)
17+
# Backward-compat alias — callers that caught AuthError still work
18+
AuthError = AuthenticationError
1019

1120
EmbedFn = Callable[[str], List[float]]
1221

@@ -53,23 +62,6 @@ def _validate_vector(vector: List[float]) -> None:
5362
f"Embedding value at index {i} ({v}) out of allowed range [{MIN_SAFE_FLOAT}, {MAX_SAFE_FLOAT}] for Q16.16 fixed-point storage."
5463
)
5564

56-
class ValoricoreError(Exception):
57-
"""Base class for all Valoricore exceptions."""
58-
pass
59-
60-
class ProtocolError(ValoricoreError):
61-
"""Raised for protocol-level problems (invalid server response, etc.)."""
62-
pass
63-
64-
class AuthError(ValoricoreError):
65-
"""Raised when authentication fails (401/403)."""
66-
pass
67-
68-
class ValidationError(ValoricoreError, ValueError):
69-
"""Raised when input validation fails (e.g. FXP bounds)."""
70-
pass
71-
72-
7365
def _ensure_keys(d: dict, keys):
7466
missing = [k for k in keys if k not in d]
7567
if missing:
@@ -79,18 +71,6 @@ def _ensure_keys(d: dict, keys):
7971

8072
# H-2: custom auth class that redacts itself in __repr__/__str__ so bearer tokens
8173
# do not appear in tracebacks, Sentry reports, or Python logging output.
82-
class _BearerAuth(requests.auth.AuthBase):
83-
def __init__(self, token: str) -> None:
84-
self._token = token
85-
86-
def __call__(self, r: requests.PreparedRequest) -> requests.PreparedRequest:
87-
r.headers["Authorization"] = f"Bearer {self._token}"
88-
return r
89-
90-
def __repr__(self) -> str:
91-
return "<BearerAuth [REDACTED]>"
92-
93-
9474
class ProtocolRemoteClient:
9575
def __init__(self, base_url: str, embed_fn, expected_dim: int, api_key: Optional[str] = None):
9676
self.base_url = base_url.rstrip("/")
@@ -110,7 +90,7 @@ def _post(self, path: str, json_data: Dict[str, Any], timeout: int = 10) -> Dict
11090
if not resp.ok:
11191
# Handle Auth Errors specifically
11292
if resp.status_code in (401, 403):
113-
raise AuthError(f"Authentication failed ({resp.status_code}): {resp.reason}")
93+
raise AuthenticationError(f"Authentication failed ({resp.status_code}): {resp.reason}")
11494

11595
# Try to parse JSON error message, fallback to text
11696
try:
@@ -185,13 +165,13 @@ def set_metadata(self, target_id: str, metadata: Dict[str, Any]):
185165
"""Set metadata for a memory_id, record_id, or node_id."""
186166
url = f"{self.base_url}/v1/memory/meta/set"
187167
payload = {"target_id": target_id, "metadata": metadata}
188-
resp = self.session.post(url, json=payload, timeout=5)
168+
resp = self.session.post(url, json=payload, auth=self._auth, timeout=5)
189169
resp.raise_for_status()
190-
170+
191171
def get_metadata(self, target_id: str) -> Optional[Dict[str, Any]]:
192172
"""Get metadata for a target_id."""
193173
url = f"{self.base_url}/v1/memory/meta/get"
194-
resp = self.session.get(url, params={"target_id": target_id}, timeout=5)
174+
resp = self.session.get(url, params={"target_id": target_id}, auth=self._auth, timeout=5)
195175
resp.raise_for_status()
196176
data = resp.json()
197177
return data.get("metadata")
@@ -248,7 +228,7 @@ def upsert_text(self, text: str, chunk_size: int = 512, vector: Optional[List[fl
248228

249229
record_ids.append(res["record_id"])
250230
chunk_node_ids.append(res["chunk_node_id"])
251-
proof_hashes.append(res["proof_hash"])
231+
proof_hashes.append(res.get("proof_hash", ""))
252232

253233
memory_ids = [f"rec:{rid}" for rid in record_ids]
254234
return {

python/valoricore/remote.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ def _base_of(final_url: str, path: str) -> Optional[str]:
3737
return None
3838

3939

40+
class _BearerAuth(requests.auth.AuthBase):
41+
"""Per-request auth injector that redacts itself in repr/tracebacks."""
42+
def __init__(self, token: str) -> None:
43+
self._token = token
44+
45+
def __call__(self, r: requests.PreparedRequest) -> requests.PreparedRequest:
46+
r.headers["Authorization"] = f"Bearer {self._token}"
47+
return r
48+
49+
def __repr__(self) -> str:
50+
return "<BearerAuth [REDACTED]>"
51+
52+
4053
class SyncRemoteClient(ValoriClient):
4154
"""Synchronous REST client for a Valoricore node — standalone or clustered.
4255
@@ -64,8 +77,12 @@ def __init__(self, base_url: str, max_retries: int = 3, retry_backoff: float = 0
6477
import re
6578
self.ui_url = re.sub(r":\d+$", ":3001", self.base_url)
6679
self.session = requests.Session()
67-
if token:
68-
self.session.headers["Authorization"] = f"Bearer {token}"
80+
# H-2: use session.auth (not session.headers) so the token is injected
81+
# per-request via __call__ and never sits in the headers dict where it
82+
# would appear in dict(session.headers), logging output, or tracebacks.
83+
# _BearerAuth.__repr__ returns "[REDACTED]" so it's safe in logs.
84+
self._auth = _BearerAuth(token) if token else None
85+
self.session.auth = self._auth
6986
self._auto_snapshot_interval = None
7087
self._insert_count = 0
7188
# M-4: default snapshot dir to ~/.valori/snapshots/ instead of CWD.

0 commit comments

Comments
 (0)