memwal 0.1.8.dev2__tar.gz → 0.1.9.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/CHANGELOG.md +6 -0
  2. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/PKG-INFO +1 -1
  3. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/memwal/__init__.py +1 -1
  4. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/memwal/client.py +11 -1
  5. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/pyproject.toml +1 -1
  6. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/tests/test_client.py +86 -0
  7. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/.gitignore +0 -0
  8. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/README.md +0 -0
  9. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/examples/.env.example +0 -0
  10. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/examples/.gitignore +0 -0
  11. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/examples/async_remember_demo.py +0 -0
  12. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/examples/interactive_demo.py +0 -0
  13. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/examples/verify_credentials.py +0 -0
  14. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/memwal/compatibility.py +0 -0
  15. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/memwal/middleware.py +0 -0
  16. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/memwal/mock.py +0 -0
  17. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/memwal/types.py +0 -0
  18. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/memwal/utils.py +0 -0
  19. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/notebooks/walrus_memory_python_sdk.ipynb +0 -0
  20. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/run_tests.py +0 -0
  21. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/tests/__init__.py +0 -0
  22. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/tests/test_auth_rejected_message.py +0 -0
  23. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/tests/test_env_presets.py +0 -0
  24. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/tests/test_integration.py +0 -0
  25. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/tests/test_middleware.py +0 -0
  26. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/tests/test_mock.py +0 -0
  27. {memwal-0.1.8.dev2 → memwal-0.1.9.dev0}/tests/test_signing.py +0 -0
@@ -1,5 +1,11 @@
1
1
  # memwal
2
2
 
3
+ ## 0.1.9
4
+
5
+ ### Fixed
6
+
7
+ - `remember_bulk_async` rejects an empty `items` list before the request and raises when the relayer returns a `job_ids` length that does not match the batch.
8
+
3
9
  ## 0.1.8
4
10
 
5
11
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: memwal
3
- Version: 0.1.8.dev2
3
+ Version: 0.1.9.dev0
4
4
  Summary: Python SDK for Walrus Memory — Privacy-first AI memory with Ed25519 signing
5
5
  Project-URL: Homepage, https://memory.walrus.xyz
6
6
  Project-URL: Documentation, https://memory.walrus.xyz
@@ -122,4 +122,4 @@ __all__ = [
122
122
  "RecallManualResult",
123
123
  ]
124
124
 
125
- __version__ = "0.1.8.dev2"
125
+ __version__ = "0.1.9.dev0"
@@ -463,6 +463,9 @@ class MemWal:
463
463
  capped at ``MAX_REMEMBER_TEXT_BYTES`` (= 64 KiB).
464
464
  """
465
465
 
466
+ if not items:
467
+ raise ValueError("remember_bulk_async: items must be a non-empty array")
468
+
466
469
  payload_items: List[Dict[str, Any]] = [
467
470
  {
468
471
  "text": item.text,
@@ -476,8 +479,15 @@ class MemWal:
476
479
  {"items": payload_items},
477
480
  accepted_statuses=(200, 202),
478
481
  )
482
+ job_ids = data.get("job_ids")
483
+ returned = len(job_ids) if job_ids else 0
484
+ if not job_ids or returned != len(payload_items):
485
+ raise ValueError(
486
+ f"remember_bulk_async: server returned {returned} job_ids for "
487
+ f"{len(payload_items)} items"
488
+ )
479
489
  return RememberBulkAcceptedResult(
480
- job_ids=list(data.get("job_ids", [])),
490
+ job_ids=list(job_ids),
481
491
  total=int(data.get("total", len(payload_items))),
482
492
  status=data.get("status", "pending"),
483
493
  )
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "memwal"
7
- version = "0.1.8.dev2"
7
+ version = "0.1.9.dev0"
8
8
  description = "Python SDK for Walrus Memory — Privacy-first AI memory with Ed25519 signing"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -27,6 +27,8 @@ from memwal.client import (
27
27
  from memwal.types import (
28
28
  RecallManualOptions,
29
29
  RecallParams,
30
+ RememberBulkAcceptedResult,
31
+ RememberBulkItem,
30
32
  RememberManualOptions,
31
33
  ScoringWeights,
32
34
  )
@@ -343,6 +345,90 @@ class TestRemember:
343
345
  assert result.status == "pending"
344
346
 
345
347
 
348
+ # ============================================================
349
+ # remember_bulk_async() tests
350
+ # ============================================================
351
+
352
+
353
+ class TestRememberBulkAsync:
354
+ @respx.mock
355
+ async def test_empty_items_raises_without_http(self, memwal_client: MemWal) -> None:
356
+ mock_seal_session_prereqs()
357
+ route = respx.post(f"{_TEST_SERVER}/api/remember/bulk").mock(
358
+ return_value=httpx.Response(
359
+ 202,
360
+ json={"job_ids": [], "total": 0, "status": "pending"},
361
+ )
362
+ )
363
+
364
+ with pytest.raises(
365
+ ValueError,
366
+ match="remember_bulk_async: items must be a non-empty array",
367
+ ):
368
+ await memwal_client.remember_bulk_async([])
369
+
370
+ assert not route.called
371
+
372
+ @respx.mock
373
+ async def test_mismatched_job_ids_raises(self, memwal_client: MemWal) -> None:
374
+ mock_seal_session_prereqs()
375
+ respx.post(f"{_TEST_SERVER}/api/remember/bulk").mock(
376
+ return_value=httpx.Response(
377
+ 202,
378
+ json={
379
+ "job_ids": ["job-1"],
380
+ "total": 1,
381
+ "status": "pending",
382
+ },
383
+ )
384
+ )
385
+
386
+ with pytest.raises(
387
+ ValueError,
388
+ match="remember_bulk_async: server returned 1 job_ids for 2 items",
389
+ ):
390
+ await memwal_client.remember_bulk_async(
391
+ [
392
+ RememberBulkItem(text="I love coffee"),
393
+ RememberBulkItem(text="I live in Tokyo"),
394
+ ]
395
+ )
396
+
397
+ @respx.mock
398
+ async def test_matching_job_ids_returns_accepted(
399
+ self, memwal_client: MemWal
400
+ ) -> None:
401
+ mock_seal_session_prereqs()
402
+ route = respx.post(f"{_TEST_SERVER}/api/remember/bulk").mock(
403
+ return_value=httpx.Response(
404
+ 202,
405
+ json={
406
+ "job_ids": ["job-1", "job-2"],
407
+ "total": 2,
408
+ "status": "pending",
409
+ },
410
+ )
411
+ )
412
+
413
+ result = await memwal_client.remember_bulk_async(
414
+ [
415
+ RememberBulkItem(text="I love coffee"),
416
+ RememberBulkItem(text="I live in Tokyo", namespace="profile"),
417
+ ]
418
+ )
419
+
420
+ assert route.called
421
+ body = json.loads(route.calls[0].request.content)
422
+ assert body["items"] == [
423
+ {"text": "I love coffee", "namespace": "default"},
424
+ {"text": "I live in Tokyo", "namespace": "profile"},
425
+ ]
426
+ assert isinstance(result, RememberBulkAcceptedResult)
427
+ assert result.job_ids == ["job-1", "job-2"]
428
+ assert result.total == 2
429
+ assert result.status == "pending"
430
+
431
+
346
432
  # ============================================================
347
433
  # recall() tests
348
434
  # ============================================================
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes