memwal 0.2.0.dev1__tar.gz → 0.2.1.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 (37) hide show
  1. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/CHANGELOG.md +13 -6
  2. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/PKG-INFO +1 -1
  3. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/__init__.py +1 -1
  4. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/middleware.py +6 -2
  5. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/pyproject.toml +1 -1
  6. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_middleware.py +42 -0
  7. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/.gitignore +0 -0
  8. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/README.md +0 -0
  9. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/.env.example +0 -0
  10. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/.gitignore +0 -0
  11. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/async_remember_demo.py +0 -0
  12. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/interactive_demo.py +0 -0
  13. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/verify_credentials.py +0 -0
  14. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/client.py +0 -0
  15. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/compatibility.py +0 -0
  16. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/mock.py +0 -0
  17. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/types.py +0 -0
  18. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/utils.py +0 -0
  19. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/notebooks/walrus_memory_python_sdk.ipynb +0 -0
  20. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/run_tests.py +0 -0
  21. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/__init__.py +0 -0
  22. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_auth_rejected_message.py +0 -0
  23. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_blank_text.py +0 -0
  24. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_client.py +0 -0
  25. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_empty_namespace.py +0 -0
  26. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_env_presets.py +0 -0
  27. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_integration.py +0 -0
  28. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_list_namespaces.py +0 -0
  29. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_mock.py +0 -0
  30. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_nonfinite_validation.py +0 -0
  31. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_normalize_server_url.py +0 -0
  32. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_polling_delay.py +0 -0
  33. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_recall_weights.py +0 -0
  34. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_remember_derived_key.py +0 -0
  35. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_retry_after_http_date.py +0 -0
  36. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_signing.py +0 -0
  37. {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_transport_retry.py +0 -0
@@ -1,9 +1,21 @@
1
1
  # memwal
2
2
 
3
- ## 0.2.0
3
+ ## 0.2.1
4
4
 
5
5
  Unreleased.
6
6
 
7
+ ### Fixed
8
+
9
+ - `with_memwal_*` gains `save_mode` and confirmable saves: `memwal_wait_for_saves` (async and sync) polls every job the middleware enqueued, and restores the job ids if that poll raises so a retry can still confirm them. (WALM-307, #410, #411, #412)
10
+ - `wait_for_remember_jobs` chunks its status reads to the relayer's 20-id `MAX_BULK_ITEMS` cap. Above it the relayer answers a non-transient 400, so a wait over 21+ jobs raised on every attempt and could never confirm — reachable from `save_mode="remember"` after 21 turns, or from two fat `analyze` extracts. (WALM-307)
11
+
12
+ - Save confirmation preserves unconfirmed job IDs when an async wait is cancelled or a synchronous wait is interrupted, then propagates the cancellation or interrupt. (#1303)
13
+ - Reject explicitly empty namespaces instead of silently writing to the default namespace. (#1265)
14
+ - Reject non-finite vectors and invalid limits before signing requests. (#1264)
15
+
16
+ ## 0.2.0
17
+
18
+
7
19
  ### Changed
8
20
 
9
21
  - Python 3.11 or later is required. Python 3.9 and 3.10 have reached end of life; recreate existing virtual environments on a supported patched interpreter before upgrading.
@@ -12,11 +24,6 @@ Unreleased.
12
24
 
13
25
  - Require certifi 2026.10.19 or newer to remove trust in the compromised emSign certificate chain, and AnyIO 4.14.2 or newer for its TLS hostname fix. Update pip separately to 26.2.2 or newer; the SDK cannot replace pip's vendored trust store.
14
26
 
15
- ### Fixed
16
-
17
- - `with_memwal_*` gains `save_mode` and confirmable saves: `memwal_wait_for_saves` (async and sync) polls every job the middleware enqueued, and restores the job ids if that poll raises so a retry can still confirm them. (WALM-307, #410, #411, #412)
18
- - `wait_for_remember_jobs` chunks its status reads to the relayer's 20-id `MAX_BULK_ITEMS` cap. Above it the relayer answers a non-transient 400, so a wait over 21+ jobs raised on every attempt and could never confirm — reachable from `save_mode="remember"` after 21 turns, or from two fat `analyze` extracts. (WALM-307)
19
-
20
27
  ## 0.1.12
21
28
 
22
29
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: memwal
3
- Version: 0.2.0.dev1
3
+ Version: 0.2.1.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
@@ -128,4 +128,4 @@ __all__ = [
128
128
  "RecallManualResult",
129
129
  ]
130
130
 
131
- __version__ = "0.2.0.dev1"
131
+ __version__ = "0.2.1.dev0"
@@ -438,7 +438,9 @@ def _expose_memwal_controls(obj: Any, memwal: MemWal, pending: _PendingSaves) ->
438
438
  job_ids = pending.drain_job_ids()
439
439
  try:
440
440
  return await memwal.wait_for_remember_jobs(job_ids, opts)
441
- except Exception:
441
+ except BaseException:
442
+ # Cancellation and interrupts must leave unconfirmed jobs available
443
+ # to the next wait, while still propagating to the caller.
442
444
  pending.track_job_ids(job_ids)
443
445
  raise
444
446
 
@@ -455,7 +457,9 @@ def _expose_memwal_controls(obj: Any, memwal: MemWal, pending: _PendingSaves) ->
455
457
  memwal, memwal.wait_for_remember_jobs(job_ids, opts)
456
458
  )
457
459
  )
458
- except Exception:
460
+ except BaseException:
461
+ # Cancellation and interrupts must leave unconfirmed jobs available
462
+ # to the next wait, while still propagating to the caller.
459
463
  pending.track_job_ids(job_ids)
460
464
  raise
461
465
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "memwal"
7
- version = "0.2.0.dev1"
7
+ version = "0.2.1.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"
@@ -18,15 +18,18 @@ from __future__ import annotations
18
18
  import asyncio
19
19
  import json
20
20
  import threading
21
+ from types import SimpleNamespace
21
22
  from unittest.mock import AsyncMock, MagicMock, patch
22
23
 
23
24
  import httpx
24
25
  import nacl.signing
26
+ import pytest
25
27
  import respx
26
28
 
27
29
  from memwal.client import MemWal
28
30
  from memwal.middleware import (
29
31
  _MAX_TRACKED_JOB_IDS,
32
+ _expose_memwal_controls,
30
33
  _find_last_user_message,
31
34
  _format_memories,
32
35
  _inject_openai_memory,
@@ -1584,6 +1587,45 @@ class TestConfirmableSaves:
1584
1587
  assert result.succeeded == 1
1585
1588
  assert result.results[0].blob_id == "blob-abc"
1586
1589
 
1590
+ async def test_cancelled_wait_preserves_jobs_and_propagates_cancellation(self) -> None:
1591
+ pending = _PendingSaves()
1592
+ pending.track_job_ids(["job-1"])
1593
+ entered = asyncio.Event()
1594
+
1595
+ async def poll_until_cancelled(job_ids, opts):
1596
+ entered.set()
1597
+ await asyncio.Event().wait()
1598
+
1599
+ memwal = MagicMock()
1600
+ memwal.wait_for_remember_jobs = AsyncMock(side_effect=poll_until_cancelled)
1601
+ wrapper = SimpleNamespace()
1602
+ _expose_memwal_controls(wrapper, memwal, pending)
1603
+ task = asyncio.create_task(wrapper.memwal_wait_for_saves())
1604
+ await entered.wait()
1605
+ # A save accepted while polling must survive alongside the restored batch.
1606
+ pending.track_job_ids(["job-2"])
1607
+ task.cancel()
1608
+ with pytest.raises(asyncio.CancelledError):
1609
+ _ = await task
1610
+
1611
+ memwal.wait_for_remember_jobs.side_effect = None
1612
+ memwal.wait_for_remember_jobs.return_value = "confirmed"
1613
+ assert await wrapper.memwal_wait_for_saves() == "confirmed"
1614
+ memwal.wait_for_remember_jobs.assert_awaited_with(["job-2", "job-1"], None)
1615
+ assert pending.drain_job_ids() == []
1616
+
1617
+ def test_interrupted_sync_wait_preserves_jobs_and_propagates_interrupt(self) -> None:
1618
+ pending = _PendingSaves()
1619
+ pending.track_job_ids(["job-1"])
1620
+ wrapper = SimpleNamespace()
1621
+ _expose_memwal_controls(wrapper, MagicMock(), pending)
1622
+
1623
+ with patch("memwal.middleware._run_blocking", side_effect=KeyboardInterrupt):
1624
+ with pytest.raises(KeyboardInterrupt):
1625
+ wrapper.memwal_wait_for_saves_sync()
1626
+
1627
+ assert pending.drain_job_ids() == ["job-1"]
1628
+
1587
1629
  @respx.mock
1588
1630
  async def test_wait_confirms_more_jobs_than_the_relayer_bulk_cap(self) -> None:
1589
1631
  """21 tracked jobs must confirm, not 400-loop.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes