memwal 0.1.11.dev9__tar.gz → 0.1.11.dev10__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.1.11.dev9 → memwal-0.1.11.dev10}/CHANGELOG.md +5 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/PKG-INFO +2 -1
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/README.md +1 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/memwal/__init__.py +1 -1
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/memwal/client.py +95 -2
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/pyproject.toml +1 -1
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_client.py +148 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/.gitignore +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/examples/.env.example +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/examples/.gitignore +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/examples/async_remember_demo.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/examples/interactive_demo.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/examples/verify_credentials.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/memwal/compatibility.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/memwal/middleware.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/memwal/mock.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/memwal/types.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/memwal/utils.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/notebooks/walrus_memory_python_sdk.ipynb +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/run_tests.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/__init__.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_auth_rejected_message.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_blank_text.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_env_presets.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_integration.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_list_namespaces.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_middleware.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_mock.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_normalize_server_url.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_polling_delay.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_remember_derived_key.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_retry_after_http_date.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_signing.py +0 -0
- {memwal-0.1.11.dev9 → memwal-0.1.11.dev10}/tests/test_transport_retry.py +0 -0
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `get_remember_status(job_id)` on `MemWal` and `MemWalSync` returns `RememberJobStatus` from `GET /api/remember/{job_id}`, accepting 200 and 404 like the TypeScript SDK. (WALM-832, #1096)
|
|
8
|
+
|
|
5
9
|
### Fixed
|
|
6
10
|
|
|
11
|
+
- `get_remember_status` and `wait_for_remember_job` reject a job id outside `[A-Za-z0-9_-]+` before the request. `.`, `..`, a backslash, and encoded dot-dot used to pass a denylist and be rewritten after signing. An empty id used to call `GET /api/remember/` and raise `json.JSONDecodeError`. An unparseable 404 is `not_found` / `MemWalRememberJobNotFound`, not a JSON error. `uuid.UUID` is coerced with `str()`. Valid UUID job ids are unchanged. (WALM-833, #1100, #1216)
|
|
7
12
|
- `remember`, `remember_bulk`, `analyze`, and `embed` reject whitespace-only text before the request. A blank body used to be accepted and then sit until the caller timed out. (#1129)
|
|
8
13
|
- A 401 `AUTH_REJECTED` now says a wallet address is not a MemWal account ID. (#1132)
|
|
9
14
|
- `wait_for_remember_jobs` / `remember_bulk_and_wait` no longer return a batch they could not read as if it were still uploading. When every status poll is rate-limited, the wait makes one confirming read and raises `MemWalRateLimited` (`status` 429, `job_ids`, `retry_after`) if that is refused too. An item still unsettled at the deadline names its last known status, or says no read got through. (WALM-671, #967)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: memwal
|
|
3
|
-
Version: 0.1.11.
|
|
3
|
+
Version: 0.1.11.dev10
|
|
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
|
|
@@ -240,6 +240,7 @@ Create a new async client.
|
|
|
240
240
|
| Method | Description |
|
|
241
241
|
|--------|-------------|
|
|
242
242
|
| `await remember(text, namespace?)` | Accept a background remember job and return `job_id` |
|
|
243
|
+
| `await get_remember_status(job_id)` | Read one remember job without waiting |
|
|
243
244
|
| `await wait_for_remember_job(job_id, ...)` | Poll one remember job until it is searchable |
|
|
244
245
|
| `await remember_and_wait(text, namespace?, ...)` | Store a memory and wait until it is searchable |
|
|
245
246
|
| `await remember_bulk(items)` | Accept several background remember jobs |
|
|
@@ -201,6 +201,7 @@ Create a new async client.
|
|
|
201
201
|
| Method | Description |
|
|
202
202
|
|--------|-------------|
|
|
203
203
|
| `await remember(text, namespace?)` | Accept a background remember job and return `job_id` |
|
|
204
|
+
| `await get_remember_status(job_id)` | Read one remember job without waiting |
|
|
204
205
|
| `await wait_for_remember_job(job_id, ...)` | Poll one remember job until it is searchable |
|
|
205
206
|
| `await remember_and_wait(text, namespace?, ...)` | Store a memory and wait until it is searchable |
|
|
206
207
|
| `await remember_bulk(items)` | Accept several background remember jobs |
|
|
@@ -69,6 +69,7 @@ from .types import (
|
|
|
69
69
|
RememberBulkResult,
|
|
70
70
|
RememberBulkStatusItem,
|
|
71
71
|
RememberBulkStatusResult,
|
|
72
|
+
RememberJobStatus,
|
|
72
73
|
RememberManualOptions,
|
|
73
74
|
RememberManualResult,
|
|
74
75
|
RememberResult,
|
|
@@ -156,6 +157,36 @@ def _derived_idempotency_key(request_identity: str) -> str:
|
|
|
156
157
|
return f"r1-{hashlib.sha256(payload).hexdigest()}"
|
|
157
158
|
|
|
158
159
|
|
|
160
|
+
def _require_remember_job_id(job_id: object) -> str:
|
|
161
|
+
"""String form of a remember job id.
|
|
162
|
+
|
|
163
|
+
``uuid.UUID`` used to interpolate into the path and still does. Any other
|
|
164
|
+
non-string is a type mistake: ``not_found`` would hide a job that exists.
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
if isinstance(job_id, uuid.UUID):
|
|
168
|
+
return str(job_id)
|
|
169
|
+
if not isinstance(job_id, str):
|
|
170
|
+
raise TypeError(
|
|
171
|
+
f"remember job id must be a string, got {type(job_id).__name__}"
|
|
172
|
+
)
|
|
173
|
+
return job_id
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _remember_status_path(job_id: str) -> Optional[str]:
|
|
177
|
+
"""``/api/remember/{job_id}`` when the id is one safe segment, else None.
|
|
178
|
+
|
|
179
|
+
httpx rewrites the path after it is signed. ``.`` requests
|
|
180
|
+
``/api/remember``, ``..`` requests ``/api``, and a NUL raises
|
|
181
|
+
``InvalidURL``. An allowlist keeps those from leaving the client. A
|
|
182
|
+
normal UUID is unchanged. (#1100, #1216)
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
if re.fullmatch(r"[A-Za-z0-9_-]+", job_id) is None:
|
|
186
|
+
return None
|
|
187
|
+
return f"/api/remember/{job_id}"
|
|
188
|
+
|
|
189
|
+
|
|
159
190
|
def normalize_server_url(url: str) -> str:
|
|
160
191
|
"""Strip a trailing slash and warn on plaintext HTTP to a remote host.
|
|
161
192
|
|
|
@@ -524,6 +555,55 @@ class MemWal:
|
|
|
524
555
|
remaining_s = (deadline_ms - _now_ms()) / 1000.0
|
|
525
556
|
return max(0.001, min(60.0, remaining_s))
|
|
526
557
|
|
|
558
|
+
async def get_remember_status(self, job_id: str) -> RememberJobStatus:
|
|
559
|
+
"""One-shot lookup of a remember job. Mirrors TS ``getRememberStatus``.
|
|
560
|
+
|
|
561
|
+
``GET /api/remember/{job_id}``, accepting 200 and 404. A 404, or a
|
|
562
|
+
body with no ``status``, is ``not_found`` — not an exception. A job
|
|
563
|
+
id outside ``[A-Za-z0-9_-]+`` is ``not_found`` without a request.
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
job_id = _require_remember_job_id(job_id)
|
|
567
|
+
path = _remember_status_path(job_id)
|
|
568
|
+
if path is None:
|
|
569
|
+
return RememberJobStatus(
|
|
570
|
+
job_id=job_id,
|
|
571
|
+
status="not_found",
|
|
572
|
+
error=f"Job {job_id} not found",
|
|
573
|
+
)
|
|
574
|
+
try:
|
|
575
|
+
data = await self._signed_request(
|
|
576
|
+
"GET",
|
|
577
|
+
path,
|
|
578
|
+
{},
|
|
579
|
+
accepted_statuses=(200, 404),
|
|
580
|
+
# Status is metadata. No SEAL decrypt, so no session. Matches TS.
|
|
581
|
+
include_seal_session=False,
|
|
582
|
+
)
|
|
583
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
584
|
+
data = None
|
|
585
|
+
if not isinstance(data, dict):
|
|
586
|
+
return RememberJobStatus(
|
|
587
|
+
job_id=job_id,
|
|
588
|
+
status="not_found",
|
|
589
|
+
error=f"remember job not found: {job_id}",
|
|
590
|
+
)
|
|
591
|
+
status = data.get("status")
|
|
592
|
+
if not status:
|
|
593
|
+
return RememberJobStatus(
|
|
594
|
+
job_id=data.get("job_id") or job_id,
|
|
595
|
+
status="not_found",
|
|
596
|
+
error=data.get("error") or f"remember job not found: {job_id}",
|
|
597
|
+
)
|
|
598
|
+
return RememberJobStatus(
|
|
599
|
+
job_id=data.get("job_id", job_id),
|
|
600
|
+
status=status,
|
|
601
|
+
owner=data.get("owner"),
|
|
602
|
+
namespace=data.get("namespace"),
|
|
603
|
+
blob_id=data.get("blob_id"),
|
|
604
|
+
error=data.get("error"),
|
|
605
|
+
)
|
|
606
|
+
|
|
527
607
|
async def wait_for_remember_job(
|
|
528
608
|
self,
|
|
529
609
|
job_id: str,
|
|
@@ -542,6 +622,11 @@ class MemWal:
|
|
|
542
622
|
- First poll is immediate; later polls grow 1.5x toward 5s.
|
|
543
623
|
"""
|
|
544
624
|
|
|
625
|
+
job_id = _require_remember_job_id(job_id)
|
|
626
|
+
path = _remember_status_path(job_id)
|
|
627
|
+
if path is None:
|
|
628
|
+
raise MemWalRememberJobNotFound(job_id)
|
|
629
|
+
|
|
545
630
|
deadline_ms = _now_ms() + timeout_ms
|
|
546
631
|
attempt = 0
|
|
547
632
|
saw_rate_limit = False
|
|
@@ -563,7 +648,7 @@ class MemWal:
|
|
|
563
648
|
try:
|
|
564
649
|
data = await self._signed_request(
|
|
565
650
|
"GET",
|
|
566
|
-
|
|
651
|
+
path,
|
|
567
652
|
{},
|
|
568
653
|
accepted_statuses=(200, 404),
|
|
569
654
|
timeout=poll_timeout,
|
|
@@ -572,12 +657,14 @@ class MemWal:
|
|
|
572
657
|
if "unexpected keyword argument" in str(err) and "timeout" in str(err):
|
|
573
658
|
data = await self._signed_request(
|
|
574
659
|
"GET",
|
|
575
|
-
|
|
660
|
+
path,
|
|
576
661
|
{},
|
|
577
662
|
accepted_statuses=(200, 404),
|
|
578
663
|
)
|
|
579
664
|
else:
|
|
580
665
|
raise
|
|
666
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
667
|
+
raise MemWalRememberJobNotFound(job_id) from None
|
|
581
668
|
except _HttpStatusError as err:
|
|
582
669
|
if _is_transient_polling_status(err.status):
|
|
583
670
|
last_refusal = err.status
|
|
@@ -592,6 +679,8 @@ class MemWal:
|
|
|
592
679
|
last_refusal = 0
|
|
593
680
|
continue
|
|
594
681
|
|
|
682
|
+
if not isinstance(data, dict):
|
|
683
|
+
raise MemWalRememberJobNotFound(job_id)
|
|
595
684
|
status_str = data.get("status")
|
|
596
685
|
if status_str is None or status_str == "not_found":
|
|
597
686
|
raise MemWalRememberJobNotFound(job_id)
|
|
@@ -1964,6 +2053,10 @@ class MemWalSync:
|
|
|
1964
2053
|
) -> RememberAcceptedResult:
|
|
1965
2054
|
return self._run(self._inner.remember_async(text, namespace, idempotency_key))
|
|
1966
2055
|
|
|
2056
|
+
def get_remember_status(self, job_id: str) -> RememberJobStatus:
|
|
2057
|
+
"""Synchronous version of :meth:`MemWal.get_remember_status`."""
|
|
2058
|
+
return self._run(self._inner.get_remember_status(job_id))
|
|
2059
|
+
|
|
1967
2060
|
def wait_for_remember_job(
|
|
1968
2061
|
self,
|
|
1969
2062
|
job_id: str,
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import base64
|
|
11
11
|
import json
|
|
12
12
|
import time
|
|
13
|
+
import uuid
|
|
13
14
|
from datetime import datetime, timedelta, timezone
|
|
14
15
|
from typing import Any, Sequence
|
|
15
16
|
|
|
@@ -24,6 +25,7 @@ from memwal.client import (
|
|
|
24
25
|
MemWalCompatibilityError,
|
|
25
26
|
MemWalError,
|
|
26
27
|
MemWalRateLimited,
|
|
28
|
+
MemWalRememberJobNotFound,
|
|
27
29
|
MemWalRememberJobTimeout,
|
|
28
30
|
MemWalSync,
|
|
29
31
|
_HttpStatusError,
|
|
@@ -36,6 +38,7 @@ from memwal.types import (
|
|
|
36
38
|
RememberBulkOptions,
|
|
37
39
|
RememberBulkStatusItem,
|
|
38
40
|
RememberBulkStatusResult,
|
|
41
|
+
RememberJobStatus,
|
|
39
42
|
RememberManualOptions,
|
|
40
43
|
ScoringWeights,
|
|
41
44
|
)
|
|
@@ -642,6 +645,151 @@ class TestRememberJobTimeoutDetail:
|
|
|
642
645
|
)
|
|
643
646
|
|
|
644
647
|
|
|
648
|
+
# ============================================================
|
|
649
|
+
# one-job remember status
|
|
650
|
+
# ============================================================
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
_NORMAL_JOB_ID = "550e8400-e29b-41d4-a716-446655440000"
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
class TestRememberJobStatus:
|
|
657
|
+
@respx.mock
|
|
658
|
+
async def test_unsafe_job_id_is_not_requested(self, memwal_client: MemWal) -> None:
|
|
659
|
+
route = respx.route().mock(return_value=httpx.Response(500, text="nope"))
|
|
660
|
+
for job_id in ("", "abc/def?x=1", "job 1", "a\tb", "?", "/", ".", "..", "%2e%2e", "a\\b"):
|
|
661
|
+
status = await memwal_client.get_remember_status(job_id)
|
|
662
|
+
assert status == RememberJobStatus(
|
|
663
|
+
job_id=job_id,
|
|
664
|
+
status="not_found",
|
|
665
|
+
error=f"Job {job_id} not found",
|
|
666
|
+
)
|
|
667
|
+
with pytest.raises(MemWalRememberJobNotFound) as exc:
|
|
668
|
+
await memwal_client.wait_for_remember_job(
|
|
669
|
+
job_id, poll_interval_ms=0, timeout_ms=5_000
|
|
670
|
+
)
|
|
671
|
+
assert exc.value.status == 404
|
|
672
|
+
assert exc.value.job_id == job_id
|
|
673
|
+
assert not isinstance(exc.value, json.JSONDecodeError)
|
|
674
|
+
assert route.call_count == 0
|
|
675
|
+
|
|
676
|
+
async def test_non_string_job_id_is_a_type_error(self, memwal_client: MemWal) -> None:
|
|
677
|
+
with pytest.raises(TypeError):
|
|
678
|
+
await memwal_client.get_remember_status(1) # type: ignore[arg-type]
|
|
679
|
+
with pytest.raises(TypeError):
|
|
680
|
+
await memwal_client.wait_for_remember_job(1, poll_interval_ms=0, timeout_ms=5_000) # type: ignore[arg-type]
|
|
681
|
+
|
|
682
|
+
@respx.mock
|
|
683
|
+
async def test_uuid_object_reads_the_string_path(self, memwal_client: MemWal) -> None:
|
|
684
|
+
_mock_version()
|
|
685
|
+
job = uuid.UUID(_NORMAL_JOB_ID)
|
|
686
|
+
route = respx.get(f"{_TEST_SERVER}/api/remember/{job}").mock(
|
|
687
|
+
return_value=httpx.Response(200, json={"job_id": str(job), "status": "running"})
|
|
688
|
+
)
|
|
689
|
+
status = await memwal_client.get_remember_status(job) # type: ignore[arg-type]
|
|
690
|
+
assert status.status == "running"
|
|
691
|
+
assert status.job_id == str(job)
|
|
692
|
+
assert route.call_count == 1
|
|
693
|
+
|
|
694
|
+
@respx.mock
|
|
695
|
+
async def test_non_object_404_body_is_not_found(self, memwal_client: MemWal) -> None:
|
|
696
|
+
mock_seal_session_prereqs()
|
|
697
|
+
route = respx.get(f"{_TEST_SERVER}/api/remember/job-1")
|
|
698
|
+
for body in (b"null", b"[]", b'"Not Found"', b"<html>nope</html>"):
|
|
699
|
+
route.mock(return_value=httpx.Response(404, content=body))
|
|
700
|
+
status = await memwal_client.get_remember_status("job-1")
|
|
701
|
+
assert status.status == "not_found"
|
|
702
|
+
assert status.job_id == "job-1"
|
|
703
|
+
with pytest.raises(MemWalRememberJobNotFound) as exc:
|
|
704
|
+
await memwal_client.wait_for_remember_job(
|
|
705
|
+
"job-1", poll_interval_ms=0, timeout_ms=5_000
|
|
706
|
+
)
|
|
707
|
+
assert exc.value.status == 404
|
|
708
|
+
|
|
709
|
+
@respx.mock
|
|
710
|
+
async def test_empty_404_body_is_not_found(self, memwal_client: MemWal) -> None:
|
|
711
|
+
mock_seal_session_prereqs()
|
|
712
|
+
respx.get(f"{_TEST_SERVER}/api/remember/job-1").mock(
|
|
713
|
+
return_value=httpx.Response(404, content=b"")
|
|
714
|
+
)
|
|
715
|
+
status = await memwal_client.get_remember_status("job-1")
|
|
716
|
+
assert status.status == "not_found"
|
|
717
|
+
assert status.job_id == "job-1"
|
|
718
|
+
with pytest.raises(MemWalRememberJobNotFound) as exc:
|
|
719
|
+
await memwal_client.wait_for_remember_job(
|
|
720
|
+
"job-1", poll_interval_ms=0, timeout_ms=5_000
|
|
721
|
+
)
|
|
722
|
+
assert exc.value.status == 404
|
|
723
|
+
|
|
724
|
+
@respx.mock
|
|
725
|
+
async def test_reads_a_normal_job_id(self, memwal_client: MemWal) -> None:
|
|
726
|
+
mock_seal_session_prereqs()
|
|
727
|
+
route = respx.get(f"{_TEST_SERVER}/api/remember/{_NORMAL_JOB_ID}").mock(
|
|
728
|
+
return_value=httpx.Response(
|
|
729
|
+
200,
|
|
730
|
+
json={
|
|
731
|
+
"job_id": _NORMAL_JOB_ID,
|
|
732
|
+
"status": "done",
|
|
733
|
+
"blob_id": "blob-1",
|
|
734
|
+
"owner": "0xowner",
|
|
735
|
+
"namespace": "profile",
|
|
736
|
+
},
|
|
737
|
+
)
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
status = await memwal_client.get_remember_status(_NORMAL_JOB_ID)
|
|
741
|
+
assert status == RememberJobStatus(
|
|
742
|
+
job_id=_NORMAL_JOB_ID,
|
|
743
|
+
status="done",
|
|
744
|
+
owner="0xowner",
|
|
745
|
+
namespace="profile",
|
|
746
|
+
blob_id="blob-1",
|
|
747
|
+
)
|
|
748
|
+
assert "x-seal-session" not in route.calls[0].request.headers
|
|
749
|
+
|
|
750
|
+
result = await memwal_client.wait_for_remember_job(
|
|
751
|
+
_NORMAL_JOB_ID, poll_interval_ms=0, timeout_ms=5_000
|
|
752
|
+
)
|
|
753
|
+
assert result.blob_id == "blob-1"
|
|
754
|
+
assert result.namespace == "profile"
|
|
755
|
+
assert [str(call.request.url) for call in route.calls] == [
|
|
756
|
+
f"{_TEST_SERVER}/api/remember/{_NORMAL_JOB_ID}",
|
|
757
|
+
f"{_TEST_SERVER}/api/remember/{_NORMAL_JOB_ID}",
|
|
758
|
+
]
|
|
759
|
+
|
|
760
|
+
@respx.mock
|
|
761
|
+
def test_sync_reads_a_normal_job_id(self) -> None:
|
|
762
|
+
_mock_version()
|
|
763
|
+
route = respx.get(f"{_TEST_SERVER}/api/remember/{_NORMAL_JOB_ID}").mock(
|
|
764
|
+
return_value=httpx.Response(
|
|
765
|
+
200,
|
|
766
|
+
json={
|
|
767
|
+
"job_id": _NORMAL_JOB_ID,
|
|
768
|
+
"status": "running",
|
|
769
|
+
"namespace": "profile",
|
|
770
|
+
},
|
|
771
|
+
)
|
|
772
|
+
)
|
|
773
|
+
client = MemWalSync.create(
|
|
774
|
+
key=_TEST_KEY_HEX,
|
|
775
|
+
account_id=_TEST_ACCOUNT_ID,
|
|
776
|
+
server_url=_TEST_SERVER,
|
|
777
|
+
)
|
|
778
|
+
try:
|
|
779
|
+
missing = client.get_remember_status("")
|
|
780
|
+
assert missing.status == "not_found"
|
|
781
|
+
with pytest.raises(MemWalRememberJobNotFound):
|
|
782
|
+
client.wait_for_remember_job("abc/def?x=1", poll_interval_ms=0, timeout_ms=5_000)
|
|
783
|
+
status = client.get_remember_status(_NORMAL_JOB_ID)
|
|
784
|
+
assert status.status == "running"
|
|
785
|
+
assert status.namespace == "profile"
|
|
786
|
+
assert str(route.calls[0].request.url) == (
|
|
787
|
+
f"{_TEST_SERVER}/api/remember/{_NORMAL_JOB_ID}"
|
|
788
|
+
)
|
|
789
|
+
finally:
|
|
790
|
+
client.close()
|
|
791
|
+
|
|
792
|
+
|
|
645
793
|
# ============================================================
|
|
646
794
|
# recall() tests
|
|
647
795
|
# ============================================================
|
|
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
|