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.
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/CHANGELOG.md +13 -6
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/PKG-INFO +1 -1
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/__init__.py +1 -1
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/middleware.py +6 -2
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/pyproject.toml +1 -1
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_middleware.py +42 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/.gitignore +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/README.md +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/.env.example +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/.gitignore +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/async_remember_demo.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/interactive_demo.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/examples/verify_credentials.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/client.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/compatibility.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/mock.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/types.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/memwal/utils.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/notebooks/walrus_memory_python_sdk.ipynb +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/run_tests.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/__init__.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_auth_rejected_message.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_blank_text.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_client.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_empty_namespace.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_env_presets.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_integration.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_list_namespaces.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_mock.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_nonfinite_validation.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_normalize_server_url.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_polling_delay.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_recall_weights.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_remember_derived_key.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_retry_after_http_date.py +0 -0
- {memwal-0.2.0.dev1 → memwal-0.2.1.dev0}/tests/test_signing.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
@@ -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
|
|
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
|
|
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
|
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|