memorysync-cli 1.2.1__tar.gz → 1.3.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_cli-1.2.1 → memorysync_cli-1.3.0}/PKG-INFO +1 -1
- memorysync_cli-1.3.0/src/memorysync_cli/_version.py +1 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/commands/memory.py +80 -4
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/registry.json +13 -3
- memorysync_cli-1.2.1/src/memorysync_cli/_version.py +0 -1
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/.gitignore +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/LICENSE +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/README.md +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/pyproject.toml +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/__init__.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/__main__.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/args.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/commands/__init__.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/commands/admin.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/commands/init.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/commands/source.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/commands/tooling.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/completions.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/config.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/credentials.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/errors.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/evaluation.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/http.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/main.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/output.py +0 -0
- {memorysync_cli-1.2.1 → memorysync_cli-1.3.0}/src/memorysync_cli/registry.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.3.0"
|
|
@@ -512,6 +512,8 @@ def import_memories(ctx: dict) -> dict:
|
|
|
512
512
|
records: list[dict] = []
|
|
513
513
|
errors: list[str] = []
|
|
514
514
|
|
|
515
|
+
ref_field = flags.get("ref_field") or None
|
|
516
|
+
|
|
515
517
|
def _validate(number: int, entry: Any) -> None:
|
|
516
518
|
"""Record one candidate as either usable or an error, in line order."""
|
|
517
519
|
if not isinstance(entry, dict):
|
|
@@ -528,6 +530,7 @@ def import_memories(ctx: dict) -> dict:
|
|
|
528
530
|
"text": text.strip(),
|
|
529
531
|
"source": entry.get("source") or "cli-import",
|
|
530
532
|
"line": number,
|
|
533
|
+
"ref": _pick_ref(entry, ref_field),
|
|
531
534
|
}
|
|
532
535
|
if isinstance(entry.get("metadata"), dict):
|
|
533
536
|
record["metadata"] = entry["metadata"]
|
|
@@ -564,12 +567,14 @@ def import_memories(ctx: dict) -> dict:
|
|
|
564
567
|
if flags.get("dry_run"):
|
|
565
568
|
preview = list(errors[:10])
|
|
566
569
|
trailer = len(errors) - len(preview)
|
|
570
|
+
with_ref = sum(1 for record in records if record.get("ref"))
|
|
567
571
|
return {
|
|
568
572
|
"data": {
|
|
569
573
|
"file": source_path,
|
|
570
574
|
"valid": len(records),
|
|
571
575
|
"invalid": len(errors),
|
|
572
576
|
"errors": errors,
|
|
577
|
+
"identified": with_ref,
|
|
573
578
|
},
|
|
574
579
|
"text": lambda: "\n".join(
|
|
575
580
|
line
|
|
@@ -577,6 +582,10 @@ def import_memories(ctx: dict) -> dict:
|
|
|
577
582
|
style.yellow("Dry run. Nothing was sent."),
|
|
578
583
|
f"{style.dim('valid ')} {len(records)}",
|
|
579
584
|
f"{style.dim('invalid ')} {len(errors)}",
|
|
585
|
+
# Shown always, not only under --resume, because knowing the
|
|
586
|
+
# file has usable ids is what tells someone --resume is
|
|
587
|
+
# available to them.
|
|
588
|
+
f"{style.dim('with id ')} {with_ref}",
|
|
580
589
|
*[style.dim(f" {message}") for message in preview],
|
|
581
590
|
style.dim(f" ... and {trailer} more") if trailer > 0 else "",
|
|
582
591
|
style.yellow(
|
|
@@ -599,6 +608,25 @@ def import_memories(ctx: dict) -> dict:
|
|
|
599
608
|
if not records:
|
|
600
609
|
raise usage_error(f"{source_path} contained no records.")
|
|
601
610
|
|
|
611
|
+
# --resume is only honest if every record has a stable identity. Falling back
|
|
612
|
+
# to the line number would break the moment the file is re-exported in a
|
|
613
|
+
# different order, and falling back to a hash of the text would make two
|
|
614
|
+
# genuinely identical records collide, so the second would be reported as
|
|
615
|
+
# already stored and silently dropped. Refusing names the problem instead.
|
|
616
|
+
if flags.get("resume"):
|
|
617
|
+
missing = [record for record in records if not record.get("ref")]
|
|
618
|
+
if missing:
|
|
619
|
+
shown = ", ".join(f"line {record['line']}" for record in missing[:5])
|
|
620
|
+
looked_for = f'"{ref_field}"' if ref_field else "id, _id, uuid"
|
|
621
|
+
extra = "" if ref_field else " and metadata.mem0_id"
|
|
622
|
+
raise usage_error(
|
|
623
|
+
f"--resume needs an id on every record, and {len(missing)} "
|
|
624
|
+
f"{'has' if len(missing) == 1 else 'have'} none.",
|
|
625
|
+
f"First: {shown}. Looked for {looked_for}{extra}. "
|
|
626
|
+
"Pass --ref-field <name> to use a different field, or run without "
|
|
627
|
+
"--resume.",
|
|
628
|
+
)
|
|
629
|
+
|
|
602
630
|
batch_size = _resolve_batch_size(flags)
|
|
603
631
|
batches = [records[i : i + batch_size] for i in range(0, len(records), batch_size)]
|
|
604
632
|
totals = {
|
|
@@ -606,23 +634,31 @@ def import_memories(ctx: dict) -> dict:
|
|
|
606
634
|
"imported": 0,
|
|
607
635
|
"memory_ids": [],
|
|
608
636
|
"skipped": 0,
|
|
637
|
+
# Rows the server had already stored under the same identifier. Counted
|
|
638
|
+
# apart from ``skipped`` because they mean something different to whoever
|
|
639
|
+
# is resuming: nothing was lost, this row is simply already in.
|
|
640
|
+
"already_imported": 0,
|
|
609
641
|
"failed": 0,
|
|
610
642
|
"unreported": 0,
|
|
611
643
|
"failures": [],
|
|
612
644
|
"batches": len(batches),
|
|
613
645
|
"batch_size": batch_size,
|
|
646
|
+
"resume": bool(flags.get("resume")),
|
|
614
647
|
}
|
|
615
648
|
|
|
649
|
+
resume = bool(flags.get("resume"))
|
|
616
650
|
for batch in batches:
|
|
617
651
|
before = totals["failed"]
|
|
618
|
-
items = [
|
|
619
|
-
|
|
652
|
+
items = []
|
|
653
|
+
for record in batch:
|
|
654
|
+
item = {
|
|
620
655
|
key: value
|
|
621
656
|
for key, value in record.items()
|
|
622
657
|
if key in {"text", "source", "metadata"}
|
|
623
658
|
}
|
|
624
|
-
|
|
625
|
-
|
|
659
|
+
if resume and record.get("ref"):
|
|
660
|
+
item["client_ref"] = record["ref"]
|
|
661
|
+
items.append(item)
|
|
626
662
|
try:
|
|
627
663
|
response = api.bulk_add_memories({"items": items}) or {}
|
|
628
664
|
except Exception as error: # noqa: BLE001 — reported, not raised
|
|
@@ -653,6 +689,11 @@ def import_memories(ctx: dict) -> dict:
|
|
|
653
689
|
if status == "created":
|
|
654
690
|
totals["imported"] += 1
|
|
655
691
|
totals["memory_ids"].extend(item.get("memory_ids") or [])
|
|
692
|
+
elif status == "skipped" and item.get("reason") == "already_ingested":
|
|
693
|
+
totals["already_imported"] += 1
|
|
694
|
+
# The ids the first run produced, so a resumed import still ends
|
|
695
|
+
# up knowing every memory its file corresponds to.
|
|
696
|
+
totals["memory_ids"].extend(item.get("memory_ids") or [])
|
|
656
697
|
elif status == "skipped":
|
|
657
698
|
totals["skipped"] += 1
|
|
658
699
|
else:
|
|
@@ -668,6 +709,11 @@ def import_memories(ctx: dict) -> dict:
|
|
|
668
709
|
line
|
|
669
710
|
for line in [
|
|
670
711
|
f"{style.green('Imported')} {totals['imported']} of {totals['total']}",
|
|
712
|
+
style.dim(
|
|
713
|
+
f" {totals['already_imported']} already imported on an earlier run"
|
|
714
|
+
)
|
|
715
|
+
if totals["already_imported"]
|
|
716
|
+
else "",
|
|
671
717
|
style.dim(f" {totals['skipped']} skipped as duplicate or low value")
|
|
672
718
|
if totals["skipped"]
|
|
673
719
|
else "",
|
|
@@ -693,6 +739,36 @@ def import_memories(ctx: dict) -> dict:
|
|
|
693
739
|
SERVER_BULK_MAX = 50
|
|
694
740
|
|
|
695
741
|
|
|
742
|
+
def _pick_ref(entry: dict, ref_field: str | None) -> str | None:
|
|
743
|
+
"""The record's own id, for ``--resume``.
|
|
744
|
+
|
|
745
|
+
Checked in the order an export is likely to carry one. ``metadata.mem0_id``
|
|
746
|
+
is included because the Mem0 migration guide tells people to put it there, so
|
|
747
|
+
a file built by following our own documentation resumes without extra flags.
|
|
748
|
+
Numbers are accepted and stringified; a caller-supplied id is frequently an
|
|
749
|
+
integer and refusing it would be pedantry.
|
|
750
|
+
"""
|
|
751
|
+
metadata = entry.get("metadata") if isinstance(entry.get("metadata"), dict) else {}
|
|
752
|
+
if ref_field:
|
|
753
|
+
candidates = [entry.get(ref_field), metadata.get(ref_field)]
|
|
754
|
+
else:
|
|
755
|
+
candidates = [
|
|
756
|
+
entry.get("id"),
|
|
757
|
+
entry.get("_id"),
|
|
758
|
+
entry.get("uuid"),
|
|
759
|
+
metadata.get("mem0_id"),
|
|
760
|
+
]
|
|
761
|
+
for value in candidates:
|
|
762
|
+
if isinstance(value, str) and value.strip():
|
|
763
|
+
return value.strip()
|
|
764
|
+
# bool is an int subclass and is never a usable identifier.
|
|
765
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
766
|
+
return str(value)
|
|
767
|
+
if isinstance(value, float) and value == int(value):
|
|
768
|
+
return str(int(value))
|
|
769
|
+
return None
|
|
770
|
+
|
|
771
|
+
|
|
696
772
|
def _describe_read_failure(error: OSError) -> str:
|
|
697
773
|
"""Why a file could not be read, worded identically to the Node CLI.
|
|
698
774
|
|
|
@@ -372,8 +372,8 @@
|
|
|
372
372
|
{
|
|
373
373
|
"name": "import",
|
|
374
374
|
"summary": "Bulk load memories from a JSON or JSONL file.",
|
|
375
|
-
"description": "Each record needs a text field, and may carry its own user and metadata. Validates the whole file before sending anything, and reports per-row errors with line numbers rather than failing on the first bad row.",
|
|
376
|
-
"usage": "memorysync import <file> [--user <id>] [--batch-size <n>]",
|
|
375
|
+
"description": "Each record needs a text field, and may carry its own user and metadata. Validates the whole file before sending anything, and reports per-row errors with line numbers rather than failing on the first bad row. Pass --resume to make re-running the same file safe: each record is sent with an identifier the server remembers, so rows that already landed are reported as skipped instead of being stored a second time.",
|
|
376
|
+
"usage": "memorysync import <file> [--user <id>] [--batch-size <n>] [--resume]",
|
|
377
377
|
"flags": [
|
|
378
378
|
{
|
|
379
379
|
"name": "--user",
|
|
@@ -386,6 +386,15 @@
|
|
|
386
386
|
"value": "n",
|
|
387
387
|
"description": "Records per request. Default 50."
|
|
388
388
|
},
|
|
389
|
+
{
|
|
390
|
+
"name": "--resume",
|
|
391
|
+
"description": "Send a per-record identifier so re-running the same file skips rows already stored."
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"name": "--ref-field",
|
|
395
|
+
"value": "name",
|
|
396
|
+
"description": "Field holding each record’s own id, used by --resume. Default: id, then _id, then uuid."
|
|
397
|
+
},
|
|
389
398
|
{
|
|
390
399
|
"name": "--continue-on-error",
|
|
391
400
|
"description": "Keep going after a failed batch."
|
|
@@ -396,7 +405,8 @@
|
|
|
396
405
|
}
|
|
397
406
|
],
|
|
398
407
|
"examples": [
|
|
399
|
-
"memorysync import memories.jsonl --user alice --dry-run"
|
|
408
|
+
"memorysync import memories.jsonl --user alice --dry-run",
|
|
409
|
+
"memorysync import memories.jsonl --user alice --resume"
|
|
400
410
|
],
|
|
401
411
|
"requires_auth": true,
|
|
402
412
|
"consumes_quota": "add_requests"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.2.1"
|
|
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
|