cinchdb 0.1.8__py3-none-any.whl → 0.1.10__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.
- cinchdb/core/path_utils.py +11 -6
- cinchdb/managers/tenant.py +135 -40
- {cinchdb-0.1.8.dist-info → cinchdb-0.1.10.dist-info}/METADATA +1 -1
- {cinchdb-0.1.8.dist-info → cinchdb-0.1.10.dist-info}/RECORD +7 -7
- {cinchdb-0.1.8.dist-info → cinchdb-0.1.10.dist-info}/WHEEL +0 -0
- {cinchdb-0.1.8.dist-info → cinchdb-0.1.10.dist-info}/entry_points.txt +0 -0
- {cinchdb-0.1.8.dist-info → cinchdb-0.1.10.dist-info}/licenses/LICENSE +0 -0
cinchdb/core/path_utils.py
CHANGED
@@ -144,10 +144,15 @@ def list_tenants(project_root: Path, database: str, branch: str) -> List[str]:
|
|
144
144
|
if not tenants_dir.exists():
|
145
145
|
return []
|
146
146
|
|
147
|
-
#
|
148
|
-
tenants =
|
147
|
+
# List both .db files and .meta files for lazy tenants
|
148
|
+
tenants = set()
|
149
149
|
for f in tenants_dir.iterdir():
|
150
|
-
if f.is_file()
|
151
|
-
|
152
|
-
|
153
|
-
|
150
|
+
if f.is_file():
|
151
|
+
if f.suffix == ".db":
|
152
|
+
tenants.add(f.stem)
|
153
|
+
elif f.suffix == ".meta" and f.name.startswith("."):
|
154
|
+
# Lazy tenant metadata files are named .{tenant_name}.meta
|
155
|
+
tenant_name = f.stem[1:] # Remove leading dot
|
156
|
+
tenants.add(tenant_name)
|
157
|
+
|
158
|
+
return sorted(list(tenants))
|
cinchdb/managers/tenant.py
CHANGED
@@ -52,13 +52,14 @@ class TenantManager:
|
|
52
52
|
return tenants
|
53
53
|
|
54
54
|
def create_tenant(
|
55
|
-
self, tenant_name: str, description: Optional[str] = None
|
55
|
+
self, tenant_name: str, description: Optional[str] = None, lazy: bool = False
|
56
56
|
) -> Tenant:
|
57
57
|
"""Create a new tenant by copying schema from main tenant.
|
58
58
|
|
59
59
|
Args:
|
60
60
|
tenant_name: Name for the new tenant
|
61
61
|
description: Optional description
|
62
|
+
lazy: If True, don't create database file until first use
|
62
63
|
|
63
64
|
Returns:
|
64
65
|
Created Tenant object
|
@@ -74,40 +75,64 @@ class TenantManager:
|
|
74
75
|
# Check maintenance mode
|
75
76
|
check_maintenance_mode(self.project_root, self.database, self.branch)
|
76
77
|
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# Get paths
|
82
|
-
main_db_path = get_tenant_db_path(
|
83
|
-
self.project_root, self.database, self.branch, "main"
|
84
|
-
)
|
78
|
+
# Check if tenant metadata already exists
|
79
|
+
tenants_dir = self.branch_path / "tenants"
|
80
|
+
tenant_meta_file = tenants_dir / f".{tenant_name}.meta"
|
85
81
|
new_db_path = get_tenant_db_path(
|
86
82
|
self.project_root, self.database, self.branch, tenant_name
|
87
83
|
)
|
88
|
-
|
89
|
-
# Copy main tenant database to new tenant
|
90
|
-
shutil.copy2(main_db_path, new_db_path)
|
91
|
-
|
92
|
-
# Clear any data from the copied database (keep schema only)
|
93
|
-
with DatabaseConnection(new_db_path) as conn:
|
94
|
-
# Get all tables
|
95
|
-
result = conn.execute("""
|
96
|
-
SELECT name FROM sqlite_master
|
97
|
-
WHERE type='table'
|
98
|
-
AND name NOT LIKE 'sqlite_%'
|
99
|
-
""")
|
100
|
-
tables = [row["name"] for row in result.fetchall()]
|
101
|
-
|
102
|
-
# Clear data from each table
|
103
|
-
for table in tables:
|
104
|
-
conn.execute(f"DELETE FROM {table}")
|
105
|
-
|
106
|
-
conn.commit()
|
107
84
|
|
108
|
-
#
|
109
|
-
|
110
|
-
|
85
|
+
# Validate tenant doesn't exist (either as file or metadata)
|
86
|
+
if new_db_path.exists() or tenant_meta_file.exists():
|
87
|
+
raise ValueError(f"Tenant '{tenant_name}' already exists")
|
88
|
+
|
89
|
+
if lazy:
|
90
|
+
# Just create metadata file, don't create actual database
|
91
|
+
tenants_dir.mkdir(parents=True, exist_ok=True)
|
92
|
+
import json
|
93
|
+
from datetime import datetime, timezone
|
94
|
+
|
95
|
+
metadata = {
|
96
|
+
"name": tenant_name,
|
97
|
+
"description": description,
|
98
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
99
|
+
"lazy": True
|
100
|
+
}
|
101
|
+
|
102
|
+
with open(tenant_meta_file, 'w') as f:
|
103
|
+
json.dump(metadata, f)
|
104
|
+
else:
|
105
|
+
# Create actual database file (existing behavior)
|
106
|
+
main_db_path = get_tenant_db_path(
|
107
|
+
self.project_root, self.database, self.branch, "main"
|
108
|
+
)
|
109
|
+
|
110
|
+
# Copy main tenant database to new tenant
|
111
|
+
shutil.copy2(main_db_path, new_db_path)
|
112
|
+
|
113
|
+
# Clear any data from the copied database (keep schema only)
|
114
|
+
with DatabaseConnection(new_db_path) as conn:
|
115
|
+
# Get all tables
|
116
|
+
result = conn.execute("""
|
117
|
+
SELECT name FROM sqlite_master
|
118
|
+
WHERE type='table'
|
119
|
+
AND name NOT LIKE 'sqlite_%'
|
120
|
+
""")
|
121
|
+
tables = [row["name"] for row in result.fetchall()]
|
122
|
+
|
123
|
+
# Clear data from each table
|
124
|
+
for table in tables:
|
125
|
+
conn.execute(f"DELETE FROM {table}")
|
126
|
+
|
127
|
+
conn.commit()
|
128
|
+
|
129
|
+
# Vacuum the database to reduce size
|
130
|
+
# Must use raw sqlite3 connection with autocommit mode for VACUUM
|
131
|
+
import sqlite3
|
132
|
+
vacuum_conn = sqlite3.connect(str(new_db_path))
|
133
|
+
vacuum_conn.isolation_level = None # Autocommit mode required for VACUUM
|
134
|
+
vacuum_conn.execute("VACUUM")
|
135
|
+
vacuum_conn.close()
|
111
136
|
|
112
137
|
return Tenant(
|
113
138
|
name=tenant_name,
|
@@ -140,20 +165,90 @@ class TenantManager:
|
|
140
165
|
):
|
141
166
|
raise ValueError(f"Tenant '{tenant_name}' does not exist")
|
142
167
|
|
143
|
-
#
|
168
|
+
# Check for and delete metadata file (for lazy tenants)
|
169
|
+
tenants_dir = self.branch_path / "tenants"
|
170
|
+
meta_file = tenants_dir / f".{tenant_name}.meta"
|
171
|
+
if meta_file.exists():
|
172
|
+
meta_file.unlink()
|
173
|
+
|
174
|
+
# Delete tenant database file and related files (if they exist)
|
144
175
|
db_path = get_tenant_db_path(
|
145
176
|
self.project_root, self.database, self.branch, tenant_name
|
146
177
|
)
|
147
|
-
db_path.
|
178
|
+
if db_path.exists():
|
179
|
+
db_path.unlink()
|
148
180
|
|
149
|
-
|
150
|
-
|
151
|
-
|
181
|
+
# Also remove WAL and SHM files if they exist
|
182
|
+
wal_path = db_path.with_suffix(".db-wal")
|
183
|
+
shm_path = db_path.with_suffix(".db-shm")
|
152
184
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
185
|
+
if wal_path.exists():
|
186
|
+
wal_path.unlink()
|
187
|
+
if shm_path.exists():
|
188
|
+
shm_path.unlink()
|
189
|
+
|
190
|
+
def materialize_tenant(self, tenant_name: str) -> None:
|
191
|
+
"""Materialize a lazy tenant into an actual database file.
|
192
|
+
|
193
|
+
Args:
|
194
|
+
tenant_name: Name of the tenant to materialize
|
195
|
+
|
196
|
+
Raises:
|
197
|
+
ValueError: If tenant doesn't exist or is already materialized
|
198
|
+
"""
|
199
|
+
tenants_dir = self.branch_path / "tenants"
|
200
|
+
tenant_meta_file = tenants_dir / f".{tenant_name}.meta"
|
201
|
+
db_path = get_tenant_db_path(
|
202
|
+
self.project_root, self.database, self.branch, tenant_name
|
203
|
+
)
|
204
|
+
|
205
|
+
# Check if already materialized
|
206
|
+
if db_path.exists():
|
207
|
+
return # Already materialized
|
208
|
+
|
209
|
+
# Check if metadata exists
|
210
|
+
if not tenant_meta_file.exists():
|
211
|
+
raise ValueError(f"Tenant '{tenant_name}' does not exist")
|
212
|
+
|
213
|
+
# Get main tenant path for schema copy
|
214
|
+
main_db_path = get_tenant_db_path(
|
215
|
+
self.project_root, self.database, self.branch, "main"
|
216
|
+
)
|
217
|
+
|
218
|
+
# Copy main tenant database to new tenant
|
219
|
+
shutil.copy2(main_db_path, db_path)
|
220
|
+
|
221
|
+
# Clear any data from the copied database (keep schema only)
|
222
|
+
with DatabaseConnection(db_path) as conn:
|
223
|
+
# Get all tables
|
224
|
+
result = conn.execute("""
|
225
|
+
SELECT name FROM sqlite_master
|
226
|
+
WHERE type='table'
|
227
|
+
AND name NOT LIKE 'sqlite_%'
|
228
|
+
""")
|
229
|
+
tables = [row["name"] for row in result.fetchall()]
|
230
|
+
|
231
|
+
# Clear data from each table
|
232
|
+
for table in tables:
|
233
|
+
conn.execute(f"DELETE FROM {table}")
|
234
|
+
|
235
|
+
conn.commit()
|
236
|
+
|
237
|
+
# Vacuum the database to reduce size
|
238
|
+
import sqlite3
|
239
|
+
vacuum_conn = sqlite3.connect(str(db_path))
|
240
|
+
vacuum_conn.isolation_level = None
|
241
|
+
vacuum_conn.execute("VACUUM")
|
242
|
+
vacuum_conn.close()
|
243
|
+
|
244
|
+
# Update metadata to indicate it's no longer lazy
|
245
|
+
import json
|
246
|
+
with open(tenant_meta_file, 'r') as f:
|
247
|
+
metadata = json.load(f)
|
248
|
+
metadata['lazy'] = False
|
249
|
+
metadata['materialized_at'] = Path(db_path).stat().st_mtime
|
250
|
+
with open(tenant_meta_file, 'w') as f:
|
251
|
+
json.dump(metadata, f)
|
157
252
|
|
158
253
|
def copy_tenant(self, source_tenant: str, target_tenant: str) -> Tenant:
|
159
254
|
"""Copy a tenant to a new tenant.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cinchdb
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.10
|
4
4
|
Summary: A Git-like SQLite database management system with branching and multi-tenancy
|
5
5
|
Project-URL: Homepage, https://github.com/russellromney/cinchdb
|
6
6
|
Project-URL: Documentation, https://russellromney.github.io/cinchdb
|
@@ -22,7 +22,7 @@ cinchdb/core/connection.py,sha256=SlKyEfIpeaDws8M6SfEbvCEVnt26zBY1RYwHtTXj0kY,51
|
|
22
22
|
cinchdb/core/database.py,sha256=yeBxzxgKz20Dtoilzj609GHrD3UL2bWG4ArWrRM2VnA,22090
|
23
23
|
cinchdb/core/initializer.py,sha256=CjnJSMuR1NrHobyFfwL44tUeH8VE62q02bijEtVH3p4,6922
|
24
24
|
cinchdb/core/maintenance.py,sha256=PAgrSL7Cj9p3rKHV0h_L7gupN6nLD0-5eQpJZNiqyEs,2097
|
25
|
-
cinchdb/core/path_utils.py,sha256=
|
25
|
+
cinchdb/core/path_utils.py,sha256=NK9L8qrOz2elCktrjvLxZlhvp_-fvLEuP1OtAFkOqcg,4083
|
26
26
|
cinchdb/managers/__init__.py,sha256=ic61ZUdsg-muq0ETYO6fuZRQWF4j7l920PthTkt2QrE,808
|
27
27
|
cinchdb/managers/branch.py,sha256=FkF2i5vZ8ifldGm9tLcgPNymifFdBbpCrfLorIBYCiE,5330
|
28
28
|
cinchdb/managers/change_applier.py,sha256=cHPhPgbJ9jeyrb6lkfRyumS8IHat0HiWfwZh-n7ButA,14310
|
@@ -35,7 +35,7 @@ cinchdb/managers/index.py,sha256=n9bCXggZP6muJQZXCpTT46JvuvcbbnYgeV3j6iXtTVM,103
|
|
35
35
|
cinchdb/managers/merge_manager.py,sha256=R8S2hLkLJg4hLDpeJTzjVkduZgqPOjXtYgOSJhTXXrE,15690
|
36
36
|
cinchdb/managers/query.py,sha256=pBlbqoovnFsZ36pB7nv8NtzcTFwtT26hp8IlwjIx29Q,7301
|
37
37
|
cinchdb/managers/table.py,sha256=GltELZ465M8JYwZB5xoMDOvyhRYm-HflPJsQQTStD2c,13837
|
38
|
-
cinchdb/managers/tenant.py,sha256=
|
38
|
+
cinchdb/managers/tenant.py,sha256=yarOt-NnTAKnjlIqYK8qjJkzFz6pJC7KsP07_OVh0N0,12657
|
39
39
|
cinchdb/managers/view.py,sha256=v9gYtRufZyxywPKLGvIjvlUXcxYh9CLRArefu9QX6zk,7809
|
40
40
|
cinchdb/models/__init__.py,sha256=cZ-ailJ6qu44Iap5Rq555iB-_w9ufXVDBH3rDH-ojk0,622
|
41
41
|
cinchdb/models/base.py,sha256=7j4rlFTP5K9ZuF8vxwC7lMFEaL7O90NJ47Ig5i7ubcw,1320
|
@@ -49,8 +49,8 @@ cinchdb/models/view.py,sha256=q6j-jYzFJuhRJO87rKt6Uv8hOizHQx8xwoPKoH6XnNY,530
|
|
49
49
|
cinchdb/utils/__init__.py,sha256=yQQhEjndDiB2SUJybUmp9dvEOQKiR-GySe-WiCius5E,490
|
50
50
|
cinchdb/utils/name_validator.py,sha256=dyGX5bjlTFRA9EGrWRQKp6kR__HSV04hLV5VueJs4IQ,4027
|
51
51
|
cinchdb/utils/sql_validator.py,sha256=aWOGlPX0gBkuR6R1EBP2stbP4PHZuI6FUBi2Ljx7JUI,5815
|
52
|
-
cinchdb-0.1.
|
53
|
-
cinchdb-0.1.
|
54
|
-
cinchdb-0.1.
|
55
|
-
cinchdb-0.1.
|
56
|
-
cinchdb-0.1.
|
52
|
+
cinchdb-0.1.10.dist-info/METADATA,sha256=So2FrvVkPam4IzYzsTliYgKeM4L_wMxFapKY7Nf5QJo,6335
|
53
|
+
cinchdb-0.1.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
54
|
+
cinchdb-0.1.10.dist-info/entry_points.txt,sha256=VBOIzvnGbkKudMCCmNORS3885QSyjZUVKJQ-Syqa62w,47
|
55
|
+
cinchdb-0.1.10.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
56
|
+
cinchdb-0.1.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|