memwal 0.1.11.dev11__tar.gz → 0.1.11.dev12__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 (34) hide show
  1. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/PKG-INFO +1 -1
  2. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/memwal/__init__.py +1 -1
  3. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/memwal/client.py +3 -5
  4. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/memwal/mock.py +5 -4
  5. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/memwal/utils.py +3 -1
  6. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/pyproject.toml +1 -1
  7. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_client.py +47 -1
  8. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_mock.py +5 -0
  9. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_signing.py +9 -0
  10. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/.gitignore +0 -0
  11. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/CHANGELOG.md +0 -0
  12. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/README.md +0 -0
  13. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/examples/.env.example +0 -0
  14. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/examples/.gitignore +0 -0
  15. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/examples/async_remember_demo.py +0 -0
  16. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/examples/interactive_demo.py +0 -0
  17. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/examples/verify_credentials.py +0 -0
  18. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/memwal/compatibility.py +0 -0
  19. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/memwal/middleware.py +0 -0
  20. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/memwal/types.py +0 -0
  21. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/notebooks/walrus_memory_python_sdk.ipynb +0 -0
  22. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/run_tests.py +0 -0
  23. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/__init__.py +0 -0
  24. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_auth_rejected_message.py +0 -0
  25. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_blank_text.py +0 -0
  26. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_env_presets.py +0 -0
  27. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_integration.py +0 -0
  28. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_list_namespaces.py +0 -0
  29. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_middleware.py +0 -0
  30. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_normalize_server_url.py +0 -0
  31. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_polling_delay.py +0 -0
  32. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_remember_derived_key.py +0 -0
  33. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_retry_after_http_date.py +0 -0
  34. {memwal-0.1.11.dev11 → memwal-0.1.11.dev12}/tests/test_transport_retry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: memwal
3
- Version: 0.1.11.dev11
3
+ Version: 0.1.11.dev12
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.1.11.dev11"
131
+ __version__ = "0.1.11.dev12"
@@ -1063,14 +1063,12 @@ class MemWal:
1063
1063
  dropped = int(raw_dropped)
1064
1064
  except (TypeError, ValueError):
1065
1065
  dropped = 0
1066
+ total_count = data.get("total", len(memories))
1066
1067
  if max_distance is not None:
1067
1068
  memories = [m for m in memories if m.distance < max_distance]
1068
- return RecallResult(
1069
- results=memories, total=len(memories), dropped_count=dropped
1070
- )
1071
1069
  return RecallResult(
1072
1070
  results=memories,
1073
- total=data.get("total", len(memories)),
1071
+ total=total_count,
1074
1072
  dropped_count=dropped,
1075
1073
  )
1076
1074
 
@@ -1817,7 +1815,7 @@ class MemWalClockDriftError(MemWalError):
1817
1815
 
1818
1816
  def _redact_internal_urls(text: str) -> str:
1819
1817
  return re.sub(
1820
- r"https?://(?:localhost|127\.0\.0\.1)(?::\d+)?[^\s)\]>'\"]*",
1818
+ r"https?://(?:(?:[\w-]+\.)*localhost|127\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[::1\]|0\.0\.0\.0)(?::\d+)?[^\s)\]>'\"]*",
1821
1819
  "[internal]",
1822
1820
  text,
1823
1821
  flags=re.IGNORECASE,
@@ -278,14 +278,15 @@ class MemWalMock:
278
278
  for memory in self._memories
279
279
  if memory.namespace == resolved_namespace
280
280
  ]
281
- if max_distance is not None:
282
- ranked = [item for item in ranked if item[1] < max_distance]
283
281
  ranked.sort(key=lambda item: (item[1], item[0].sequence))
282
+ page = ranked[:limit]
283
+ if max_distance is not None:
284
+ page = [item for item in page if item[1] < max_distance]
284
285
  results = [
285
286
  RecallMemory(blob_id=memory.blob_id, text=memory.text, distance=score)
286
- for memory, score in ranked[:limit]
287
+ for memory, score in page
287
288
  ]
288
- return RecallResult(results=results, total=len(results))
289
+ return RecallResult(results=results, total=len(ranked[:limit]))
289
290
 
290
291
  async def analyze(
291
292
  self,
@@ -299,7 +299,9 @@ def normalize_private_key(key: str) -> str:
299
299
  candidate = key.strip()
300
300
  if candidate.lower().startswith("suiprivkey1"):
301
301
  return bytes_to_hex(decode_sui_private_key(candidate))
302
- return candidate[2:] if candidate.startswith("0x") else candidate
302
+ if candidate.startswith(("0x", "0X")):
303
+ return candidate[2:]
304
+ return candidate
303
305
 
304
306
 
305
307
  def encode_sui_private_key(seed_bytes: bytes) -> str:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "memwal"
7
- version = "0.1.11.dev11"
7
+ version = "0.1.11.dev12"
8
8
  description = "Python SDK for Walrus Memory — Privacy-first AI memory with Ed25519 signing"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -935,9 +935,33 @@ class TestRecall:
935
935
  body = json.loads(route.calls[0].request.content)
936
936
  assert body["limit"] == 10
937
937
  assert len(result.results) == 1
938
- assert result.total == 1
938
+ assert result.total == 2
939
939
  assert result.results[0].blob_id == "b1"
940
940
 
941
+ @respx.mock
942
+ async def test_max_distance_filters_all_preserves_total(
943
+ self, memwal_client: MemWal
944
+ ) -> None:
945
+ """When all matches exceed max_distance, results is empty but total
946
+ preserves matches (#1066)."""
947
+ mock_seal_session_prereqs()
948
+ respx.post(f"{_TEST_SERVER}/api/recall").mock(
949
+ return_value=httpx.Response(
950
+ 200,
951
+ json={
952
+ "results": [
953
+ {"blob_id": "b1", "text": "I love coffee", "distance": 0.8},
954
+ {"blob_id": "b2", "text": "I live in Tokyo", "distance": 0.9},
955
+ ],
956
+ "total": 2,
957
+ },
958
+ )
959
+ )
960
+
961
+ result = await memwal_client.recall("coffee", limit=10, max_distance=0.5)
962
+ assert len(result.results) == 0
963
+ assert result.total == 2
964
+
941
965
  @respx.mock
942
966
  async def test_get_signed_request_uses_empty_body_hash_and_no_wire_body(
943
967
  self, memwal_client: MemWal
@@ -1583,3 +1607,25 @@ class TestSealSession:
1583
1607
  assert exported["ttlMin"] == 5
1584
1608
  assert exported["sessionKey"].startswith("suiprivkey")
1585
1609
  assert exported["personalMessageSignature"]
1610
+
1611
+
1612
+ class TestRedactInternalUrls:
1613
+ @pytest.mark.parametrize(
1614
+ "raw, expected",
1615
+ [
1616
+ ("Error at http://localhost:8080/path", "Error at [internal]"),
1617
+ ("Error at http://127.0.0.1:3000/api", "Error at [internal]"),
1618
+ ("Error at http://127.0.0.2:8000/status", "Error at [internal]"),
1619
+ ("Error at http://127.128.0.1:9000/info", "Error at [internal]"),
1620
+ ("Error at http://[::1]:9000/seal/encrypt", "Error at [internal]"),
1621
+ ("Error at http://0.0.0.0:8000/health", "Error at [internal]"),
1622
+ ("Error at http://sidecar.localhost:8080/v1", "Error at [internal]"),
1623
+ ("Error at http://sub.domain.localhost:3000/test", "Error at [internal]"),
1624
+ ("Error at https://api.wal.app/v1/remember", "Error at https://api.wal.app/v1/remember"),
1625
+ ],
1626
+ )
1627
+ def test_redact_internal_urls(self, raw: str, expected: str) -> None:
1628
+ from memwal.client import _redact_internal_urls
1629
+
1630
+ assert _redact_internal_urls(raw) == expected
1631
+
@@ -57,6 +57,11 @@ async def test_mock_isolates_namespaces_and_honors_max_distance():
57
57
  assert [memory.text for memory in alice.results] == ["Alice likes ramen"]
58
58
  assert [memory.text for memory in bob.results] == ["Bob likes tacos"]
59
59
  assert empty.total == 0
60
+ dropped = await mock.recall(
61
+ RecallParams(query="likes", namespace="user-a", max_distance=0)
62
+ )
63
+ assert dropped.results == []
64
+ assert dropped.total == 1
60
65
 
61
66
 
62
67
  @pytest.mark.asyncio
@@ -324,11 +324,20 @@ class TestPrivateKeyFormats:
324
324
  for form in (
325
325
  self._SEED_HEX,
326
326
  f"0x{self._SEED_HEX}",
327
+ f"0X{self._SEED_HEX}",
328
+ f" 0X{self._SEED_HEX} ",
327
329
  encode_sui_private_key(seed),
328
330
  f" {encode_sui_private_key(seed)} ",
329
331
  ):
330
332
  assert normalize_private_key(form) == self._SEED_HEX
331
333
 
334
+ def test_uppercase_0x_prefix_key_produces_valid_client(self) -> None:
335
+ from memwal.client import MemWal
336
+
337
+ upper_0x = f"0X{self._SEED_HEX}"
338
+ client = MemWal.create(key=upper_0x, account_id="0xtest")
339
+ assert client._private_key_hex == self._SEED_HEX
340
+
332
341
  def test_bech32_key_produces_the_same_client_identity(self) -> None:
333
342
  from memwal.client import MemWal
334
343
  from memwal.utils import encode_sui_private_key
File without changes
File without changes