memorysync 1.7.3__tar.gz → 1.9.0__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.
- {memorysync-1.7.3 → memorysync-1.9.0}/PKG-INFO +1 -1
- memorysync-1.9.0/src/memorysync/_version.py +1 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/src/memorysync/client.py +156 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/src/memorysync/types.py +10 -0
- memorysync-1.7.3/src/memorysync/_version.py +0 -1
- {memorysync-1.7.3 → memorysync-1.9.0}/.gitignore +0 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/LICENSE +0 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/README.md +0 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/pyproject.toml +0 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/src/memorysync/__init__.py +0 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/src/memorysync/connections.py +0 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/src/memorysync/control_plane.py +0 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/src/memorysync/control_plane_types.py +0 -0
- {memorysync-1.7.3 → memorysync-1.9.0}/src/memorysync/errors.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.9.0"
|
|
@@ -193,6 +193,7 @@ class _RequestBuilder:
|
|
|
193
193
|
session_id: Optional[str],
|
|
194
194
|
metadata: Optional[Dict[str, Any]],
|
|
195
195
|
end_user_id: Optional[str],
|
|
196
|
+
client_ref: Optional[str] = None,
|
|
196
197
|
) -> Dict[str, Any]:
|
|
197
198
|
return _strip_none(
|
|
198
199
|
{
|
|
@@ -203,6 +204,7 @@ class _RequestBuilder:
|
|
|
203
204
|
"session_id": session_id,
|
|
204
205
|
"metadata": metadata,
|
|
205
206
|
"end_user_id": end_user_id,
|
|
207
|
+
"client_ref": client_ref,
|
|
206
208
|
}
|
|
207
209
|
)
|
|
208
210
|
|
|
@@ -364,7 +366,18 @@ class MemorySyncClient:
|
|
|
364
366
|
session_id: Optional[str] = None,
|
|
365
367
|
metadata: Optional[Dict[str, Any]] = None,
|
|
366
368
|
end_user_id: Optional[str] = None,
|
|
369
|
+
client_ref: Optional[str] = None,
|
|
367
370
|
) -> AddResult:
|
|
371
|
+
"""Store one memory candidate.
|
|
372
|
+
|
|
373
|
+
Pass ``client_ref`` -- your own identifier for the record, at most 128
|
|
374
|
+
characters -- and re-sending it is recognised rather than stored a second
|
|
375
|
+
time: the call returns an :class:`AddSkippedResponse` with reason
|
|
376
|
+
``already_ingested`` and the ids the first write produced. That makes an
|
|
377
|
+
interrupted write safe to retry without first checking whether it landed.
|
|
378
|
+
Near-duplicate detection is always on, but it compares *meaning*, so it is
|
|
379
|
+
a filter and not a guarantee.
|
|
380
|
+
"""
|
|
368
381
|
body = _RequestBuilder.add(
|
|
369
382
|
text,
|
|
370
383
|
source=source,
|
|
@@ -373,6 +386,7 @@ class MemorySyncClient:
|
|
|
373
386
|
session_id=session_id,
|
|
374
387
|
metadata=metadata,
|
|
375
388
|
end_user_id=end_user_id,
|
|
389
|
+
client_ref=client_ref,
|
|
376
390
|
)
|
|
377
391
|
raw = self._request("POST", "/memory/add", json=body, end_user_override=end_user_id)
|
|
378
392
|
skipped = _as_skipped(raw, default_reason="no_high_value_content")
|
|
@@ -598,6 +612,81 @@ class MemorySyncClient:
|
|
|
598
612
|
return skipped
|
|
599
613
|
return Memory.from_dict(raw)
|
|
600
614
|
|
|
615
|
+
# ── Bulk import ──────────────────────────────────────────────────
|
|
616
|
+
|
|
617
|
+
def create_import(
|
|
618
|
+
self,
|
|
619
|
+
file: Union[bytes, "IO[bytes]"],
|
|
620
|
+
*,
|
|
621
|
+
filename: str,
|
|
622
|
+
end_user_id: Optional[str] = None,
|
|
623
|
+
resume: bool = False,
|
|
624
|
+
continue_on_error: bool = False,
|
|
625
|
+
) -> Dict[str, Any]:
|
|
626
|
+
"""Upload a payload and get an import job back, without waiting for it.
|
|
627
|
+
|
|
628
|
+
``bulk_add`` accepts fifty records per call because each one runs the
|
|
629
|
+
extraction pipeline. For a migration out of another system, upload the
|
|
630
|
+
whole file here and poll :meth:`get_import`.
|
|
631
|
+
|
|
632
|
+
``resume`` requires ``client_ref`` on every record and is what makes
|
|
633
|
+
re-running the same file safe: records a previous run already stored come
|
|
634
|
+
back counted as ``already_imported`` rather than stored again.
|
|
635
|
+
|
|
636
|
+
The payload is JSONL, or JSON when ``filename`` ends in ``.json``. The
|
|
637
|
+
extension is how the server picks the reader, so it is required.
|
|
638
|
+
"""
|
|
639
|
+
if not filename or not filename.strip():
|
|
640
|
+
raise ValidationError("filename is required so the server can pick a reader")
|
|
641
|
+
form = _strip_none(
|
|
642
|
+
{
|
|
643
|
+
"end_user_id": end_user_id,
|
|
644
|
+
"resume": "true" if resume else "false",
|
|
645
|
+
"continue_on_error": "true" if continue_on_error else "false",
|
|
646
|
+
}
|
|
647
|
+
)
|
|
648
|
+
return (
|
|
649
|
+
self._request(
|
|
650
|
+
"POST",
|
|
651
|
+
"/imports",
|
|
652
|
+
files={"file": (filename, file)},
|
|
653
|
+
data=form,
|
|
654
|
+
end_user_override=end_user_id,
|
|
655
|
+
)
|
|
656
|
+
or {}
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
def get_import(self, job_id: str) -> Dict[str, Any]:
|
|
660
|
+
"""One import job's status, progress and per-outcome counters.
|
|
661
|
+
|
|
662
|
+
``progress_percentage`` stays below 100 until the job is genuinely
|
|
663
|
+
finished, so reaching 100 means done.
|
|
664
|
+
"""
|
|
665
|
+
if not job_id or not str(job_id).strip():
|
|
666
|
+
raise ValidationError("job_id is required")
|
|
667
|
+
return self._request("GET", f"/imports/{job_id}") or {}
|
|
668
|
+
|
|
669
|
+
def list_imports(
|
|
670
|
+
self, *, status: Optional[str] = None, limit: Optional[int] = None
|
|
671
|
+
) -> Dict[str, Any]:
|
|
672
|
+
"""Recent import jobs, newest first."""
|
|
673
|
+
return (
|
|
674
|
+
self._request(
|
|
675
|
+
"GET", "/imports", params={"status": status, "limit": limit}
|
|
676
|
+
)
|
|
677
|
+
or {}
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
def cancel_import(self, job_id: str) -> Dict[str, Any]:
|
|
681
|
+
"""Ask the worker to stop between batches.
|
|
682
|
+
|
|
683
|
+
Records already imported stay imported, and the counters report how far it
|
|
684
|
+
reached. Cancelling a finished job is a no-op rather than an error.
|
|
685
|
+
"""
|
|
686
|
+
if not job_id or not str(job_id).strip():
|
|
687
|
+
raise ValidationError("job_id is required")
|
|
688
|
+
return self._request("POST", f"/imports/{job_id}/cancel") or {}
|
|
689
|
+
|
|
601
690
|
# ── Bulk edit ────────────────────────────────────────────────────
|
|
602
691
|
|
|
603
692
|
def batch_update(
|
|
@@ -1057,7 +1146,18 @@ class AsyncMemorySyncClient:
|
|
|
1057
1146
|
session_id: Optional[str] = None,
|
|
1058
1147
|
metadata: Optional[Dict[str, Any]] = None,
|
|
1059
1148
|
end_user_id: Optional[str] = None,
|
|
1149
|
+
client_ref: Optional[str] = None,
|
|
1060
1150
|
) -> AddResult:
|
|
1151
|
+
"""Store one memory candidate.
|
|
1152
|
+
|
|
1153
|
+
Pass ``client_ref`` -- your own identifier for the record, at most 128
|
|
1154
|
+
characters -- and re-sending it is recognised rather than stored a second
|
|
1155
|
+
time: the call returns an :class:`AddSkippedResponse` with reason
|
|
1156
|
+
``already_ingested`` and the ids the first write produced. That makes an
|
|
1157
|
+
interrupted write safe to retry without first checking whether it landed.
|
|
1158
|
+
Near-duplicate detection is always on, but it compares *meaning*, so it is
|
|
1159
|
+
a filter and not a guarantee.
|
|
1160
|
+
"""
|
|
1061
1161
|
body = _RequestBuilder.add(
|
|
1062
1162
|
text,
|
|
1063
1163
|
source=source,
|
|
@@ -1066,6 +1166,7 @@ class AsyncMemorySyncClient:
|
|
|
1066
1166
|
session_id=session_id,
|
|
1067
1167
|
metadata=metadata,
|
|
1068
1168
|
end_user_id=end_user_id,
|
|
1169
|
+
client_ref=client_ref,
|
|
1069
1170
|
)
|
|
1070
1171
|
raw = await self._request("POST", "/memory/add", json=body, end_user_override=end_user_id)
|
|
1071
1172
|
skipped = _as_skipped(raw, default_reason="no_high_value_content")
|
|
@@ -1270,6 +1371,61 @@ class AsyncMemorySyncClient:
|
|
|
1270
1371
|
|
|
1271
1372
|
# ── Bulk edit ────────────────────────────────────────────────────
|
|
1272
1373
|
|
|
1374
|
+
# ── Bulk import ──────────────────────────────────────────────────
|
|
1375
|
+
|
|
1376
|
+
async def create_import(
|
|
1377
|
+
self,
|
|
1378
|
+
file: Union[bytes, "IO[bytes]"],
|
|
1379
|
+
*,
|
|
1380
|
+
filename: str,
|
|
1381
|
+
end_user_id: Optional[str] = None,
|
|
1382
|
+
resume: bool = False,
|
|
1383
|
+
continue_on_error: bool = False,
|
|
1384
|
+
) -> Dict[str, Any]:
|
|
1385
|
+
"""Async twin of :meth:`MemorySyncClient.create_import`."""
|
|
1386
|
+
if not filename or not filename.strip():
|
|
1387
|
+
raise ValidationError("filename is required so the server can pick a reader")
|
|
1388
|
+
form = _strip_none(
|
|
1389
|
+
{
|
|
1390
|
+
"end_user_id": end_user_id,
|
|
1391
|
+
"resume": "true" if resume else "false",
|
|
1392
|
+
"continue_on_error": "true" if continue_on_error else "false",
|
|
1393
|
+
}
|
|
1394
|
+
)
|
|
1395
|
+
return (
|
|
1396
|
+
await self._request(
|
|
1397
|
+
"POST",
|
|
1398
|
+
"/imports",
|
|
1399
|
+
files={"file": (filename, file)},
|
|
1400
|
+
data=form,
|
|
1401
|
+
end_user_override=end_user_id,
|
|
1402
|
+
)
|
|
1403
|
+
or {}
|
|
1404
|
+
)
|
|
1405
|
+
|
|
1406
|
+
async def get_import(self, job_id: str) -> Dict[str, Any]:
|
|
1407
|
+
"""Async twin of :meth:`MemorySyncClient.get_import`."""
|
|
1408
|
+
if not job_id or not str(job_id).strip():
|
|
1409
|
+
raise ValidationError("job_id is required")
|
|
1410
|
+
return await self._request("GET", f"/imports/{job_id}") or {}
|
|
1411
|
+
|
|
1412
|
+
async def list_imports(
|
|
1413
|
+
self, *, status: Optional[str] = None, limit: Optional[int] = None
|
|
1414
|
+
) -> Dict[str, Any]:
|
|
1415
|
+
"""Async twin of :meth:`MemorySyncClient.list_imports`."""
|
|
1416
|
+
return (
|
|
1417
|
+
await self._request(
|
|
1418
|
+
"GET", "/imports", params={"status": status, "limit": limit}
|
|
1419
|
+
)
|
|
1420
|
+
or {}
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
async def cancel_import(self, job_id: str) -> Dict[str, Any]:
|
|
1424
|
+
"""Async twin of :meth:`MemorySyncClient.cancel_import`."""
|
|
1425
|
+
if not job_id or not str(job_id).strip():
|
|
1426
|
+
raise ValidationError("job_id is required")
|
|
1427
|
+
return await self._request("POST", f"/imports/{job_id}/cancel") or {}
|
|
1428
|
+
|
|
1273
1429
|
async def batch_update(
|
|
1274
1430
|
self,
|
|
1275
1431
|
items: Sequence[Union[BatchUpdateItem, Dict[str, Any]]],
|
|
@@ -79,6 +79,14 @@ class BulkAddItem:
|
|
|
79
79
|
metadata: Optional[Dict[str, Any]] = None
|
|
80
80
|
importance: Optional[float] = None
|
|
81
81
|
end_user_id: Optional[str] = None
|
|
82
|
+
#: Your own identifier for this record, at most 128 characters.
|
|
83
|
+
#:
|
|
84
|
+
#: Send it and re-submitting the record is recognised rather than stored a
|
|
85
|
+
#: second time: the item comes back ``skipped`` with reason
|
|
86
|
+
#: ``already_ingested`` and the ids the first submission produced. This is
|
|
87
|
+
#: what makes a large import safe to re-run. Near-duplicate detection is
|
|
88
|
+
#: always on but compares *meaning*, so it is a filter and not a guarantee.
|
|
89
|
+
client_ref: Optional[str] = None
|
|
82
90
|
|
|
83
91
|
def to_dict(self) -> Dict[str, Any]:
|
|
84
92
|
out: Dict[str, Any] = {"text": self.text}
|
|
@@ -94,6 +102,8 @@ class BulkAddItem:
|
|
|
94
102
|
out["importance"] = self.importance
|
|
95
103
|
if self.end_user_id is not None:
|
|
96
104
|
out["end_user_id"] = self.end_user_id
|
|
105
|
+
if self.client_ref is not None:
|
|
106
|
+
out["client_ref"] = self.client_ref
|
|
97
107
|
return out
|
|
98
108
|
|
|
99
109
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.7.3"
|
|
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
|