mempalace-code 1.0.0__py3-none-any.whl
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.
- mempalace/README.md +40 -0
- mempalace/__init__.py +6 -0
- mempalace/__main__.py +5 -0
- mempalace/cli.py +811 -0
- mempalace/config.py +149 -0
- mempalace/convo_miner.py +415 -0
- mempalace/dialect.py +1075 -0
- mempalace/entity_detector.py +853 -0
- mempalace/entity_registry.py +639 -0
- mempalace/export.py +378 -0
- mempalace/general_extractor.py +521 -0
- mempalace/knowledge_graph.py +410 -0
- mempalace/layers.py +515 -0
- mempalace/mcp_server.py +873 -0
- mempalace/migrate.py +153 -0
- mempalace/miner.py +1285 -0
- mempalace/normalize.py +328 -0
- mempalace/onboarding.py +489 -0
- mempalace/palace_graph.py +225 -0
- mempalace/py.typed +0 -0
- mempalace/room_detector_local.py +310 -0
- mempalace/searcher.py +305 -0
- mempalace/spellcheck.py +269 -0
- mempalace/split_mega_files.py +309 -0
- mempalace/storage.py +807 -0
- mempalace/version.py +3 -0
- mempalace_code-1.0.0.dist-info/METADATA +489 -0
- mempalace_code-1.0.0.dist-info/RECORD +32 -0
- mempalace_code-1.0.0.dist-info/WHEEL +4 -0
- mempalace_code-1.0.0.dist-info/entry_points.txt +2 -0
- mempalace_code-1.0.0.dist-info/licenses/LICENSE +192 -0
- mempalace_code-1.0.0.dist-info/licenses/NOTICE +17 -0
mempalace/migrate.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""
|
|
2
|
+
migrate.py — ChromaDB → LanceDB palace migration
|
|
3
|
+
=================================================
|
|
4
|
+
|
|
5
|
+
Provides migrate_chroma_to_lance() to copy all drawers from a
|
|
6
|
+
ChromaDB palace to a LanceDB palace. Used by the 'mempalace migrate-storage'
|
|
7
|
+
CLI subcommand.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import tarfile
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class VerificationError(Exception):
|
|
19
|
+
"""Raised when post-migration count verification fails."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def migrate_chroma_to_lance(
|
|
23
|
+
src_path: str,
|
|
24
|
+
dst_path: str,
|
|
25
|
+
backup_dir: Optional[str] = None,
|
|
26
|
+
force: bool = False,
|
|
27
|
+
embed_model: Optional[str] = None,
|
|
28
|
+
verify: bool = False,
|
|
29
|
+
no_backup: bool = False,
|
|
30
|
+
) -> tuple[int, int]:
|
|
31
|
+
"""
|
|
32
|
+
Copy all drawers from a ChromaDB palace to a LanceDB palace.
|
|
33
|
+
|
|
34
|
+
Returns (src_count, dst_count).
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
src_path: Path to the source ChromaDB palace directory.
|
|
38
|
+
dst_path: Path to the destination LanceDB palace directory.
|
|
39
|
+
backup_dir: Directory where the source backup tar.gz is written.
|
|
40
|
+
Defaults to the parent directory of src_path.
|
|
41
|
+
force: If True, allow writing to a non-empty destination
|
|
42
|
+
(appends rather than refusing).
|
|
43
|
+
embed_model: Sentence-transformers model name for the destination
|
|
44
|
+
LanceDB store. None = LanceStore default (all-MiniLM-L6-v2).
|
|
45
|
+
verify: If True, compare accumulated per-wing source counts against
|
|
46
|
+
the destination counts after migration. Raises VerificationError
|
|
47
|
+
on any mismatch.
|
|
48
|
+
no_backup: If True, skip creating a backup of the source palace.
|
|
49
|
+
Intended for tests; not exposed in the public CLI.
|
|
50
|
+
"""
|
|
51
|
+
from .storage import ChromaStore, LanceStore
|
|
52
|
+
|
|
53
|
+
# Open source ChromaDB palace — wrap chromadb ImportError with a helpful message.
|
|
54
|
+
try:
|
|
55
|
+
src_store = ChromaStore(src_path, create=False)
|
|
56
|
+
except ImportError:
|
|
57
|
+
raise RuntimeError("chromadb not installed — run: pip install mempalace[chroma]")
|
|
58
|
+
|
|
59
|
+
if src_store._col is None:
|
|
60
|
+
raise RuntimeError(f"No ChromaDB collection found at {src_path}")
|
|
61
|
+
|
|
62
|
+
src_total = src_store.count()
|
|
63
|
+
if src_total == 0:
|
|
64
|
+
print("Source palace is empty — nothing to migrate.")
|
|
65
|
+
return (0, 0)
|
|
66
|
+
|
|
67
|
+
# Open destination LanceDB palace.
|
|
68
|
+
os.makedirs(dst_path, exist_ok=True)
|
|
69
|
+
dst_store = LanceStore(dst_path, create=True, embed_model=embed_model)
|
|
70
|
+
|
|
71
|
+
# Guard against writing to a non-empty destination.
|
|
72
|
+
if dst_store.count() > 0 and not force:
|
|
73
|
+
raise RuntimeError(
|
|
74
|
+
f"Destination palace at {dst_path!r} already contains rows. "
|
|
75
|
+
"Pass --force to append to it."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Backup source palace.
|
|
79
|
+
if not no_backup:
|
|
80
|
+
ts = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
81
|
+
backup_filename = f"chroma-pre-migrate-{ts}.tar.gz"
|
|
82
|
+
if backup_dir is None:
|
|
83
|
+
effective_backup_dir = str(os.path.dirname(os.path.abspath(src_path)))
|
|
84
|
+
else:
|
|
85
|
+
effective_backup_dir = backup_dir
|
|
86
|
+
os.makedirs(effective_backup_dir, exist_ok=True)
|
|
87
|
+
backup_path = os.path.join(effective_backup_dir, backup_filename)
|
|
88
|
+
print(f"Backing up {src_path} → {backup_path} ...")
|
|
89
|
+
with tarfile.open(backup_path, "w:gz") as tar:
|
|
90
|
+
tar.add(src_path, arcname=os.path.basename(src_path))
|
|
91
|
+
print(f"Backup created: {backup_path}")
|
|
92
|
+
|
|
93
|
+
# Migrate in pages of 1000.
|
|
94
|
+
BATCH_SIZE = 1000
|
|
95
|
+
offset = 0
|
|
96
|
+
running_total = 0
|
|
97
|
+
src_wing_counts: dict[str, int] = {}
|
|
98
|
+
|
|
99
|
+
print(f"Migrating {src_total} drawers from ChromaDB → LanceDB ...")
|
|
100
|
+
|
|
101
|
+
while offset < src_total:
|
|
102
|
+
try:
|
|
103
|
+
batch = src_store.get(
|
|
104
|
+
limit=BATCH_SIZE,
|
|
105
|
+
offset=offset,
|
|
106
|
+
include=["documents", "metadatas"],
|
|
107
|
+
)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
raise RuntimeError(
|
|
110
|
+
f"Error reading batch at offset {offset}: {e}. "
|
|
111
|
+
f"Migration aborted. {running_total} drawers written so far."
|
|
112
|
+
) from e
|
|
113
|
+
|
|
114
|
+
batch_ids = batch.get("ids", [])
|
|
115
|
+
batch_docs = batch.get("documents", [])
|
|
116
|
+
batch_metas = batch.get("metadatas", [])
|
|
117
|
+
|
|
118
|
+
if not batch_ids:
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
# Accumulate per-wing counts from source metadata (free — we already read them).
|
|
122
|
+
for meta in batch_metas:
|
|
123
|
+
wing = (meta.get("wing", "") if meta else "") or ""
|
|
124
|
+
src_wing_counts[wing] = src_wing_counts.get(wing, 0) + 1
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
dst_store.add(ids=batch_ids, documents=batch_docs, metadatas=batch_metas)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
raise RuntimeError(
|
|
130
|
+
f"Error writing batch at offset {offset}: {e}. "
|
|
131
|
+
f"Migration aborted. {running_total} drawers written so far."
|
|
132
|
+
) from e
|
|
133
|
+
|
|
134
|
+
running_total += len(batch_ids)
|
|
135
|
+
print(f"Migrated {running_total}/{src_total} drawers...", flush=True)
|
|
136
|
+
offset += BATCH_SIZE
|
|
137
|
+
|
|
138
|
+
dst_total = dst_store.count()
|
|
139
|
+
|
|
140
|
+
# Optional per-wing verification.
|
|
141
|
+
if verify:
|
|
142
|
+
dst_wing_counts = dst_store.count_by("wing")
|
|
143
|
+
mismatches = []
|
|
144
|
+
for wing, src_cnt in src_wing_counts.items():
|
|
145
|
+
dst_cnt = dst_wing_counts.get(wing, 0)
|
|
146
|
+
if src_cnt != dst_cnt:
|
|
147
|
+
mismatches.append(f" wing={wing!r}: src={src_cnt}, dst={dst_cnt}")
|
|
148
|
+
if mismatches:
|
|
149
|
+
diff = "\n".join(mismatches)
|
|
150
|
+
raise VerificationError(f"Per-wing count mismatch after migration:\n{diff}")
|
|
151
|
+
|
|
152
|
+
print(f"Migration complete: {src_total} drawers migrated.")
|
|
153
|
+
return (src_total, dst_total)
|