cinchdb 0.1.4__py3-none-any.whl → 0.1.6__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/database.py +55 -10
- cinchdb/managers/data.py +38 -17
- {cinchdb-0.1.4.dist-info → cinchdb-0.1.6.dist-info}/METADATA +24 -6
- {cinchdb-0.1.4.dist-info → cinchdb-0.1.6.dist-info}/RECORD +7 -7
- {cinchdb-0.1.4.dist-info → cinchdb-0.1.6.dist-info}/WHEEL +0 -0
- {cinchdb-0.1.4.dist-info → cinchdb-0.1.6.dist-info}/entry_points.txt +0 -0
- {cinchdb-0.1.4.dist-info → cinchdb-0.1.6.dist-info}/licenses/LICENSE +0 -0
cinchdb/core/database.py
CHANGED
@@ -363,24 +363,69 @@ class CinchDB:
|
|
363
363
|
"POST", "/tables", json={"name": name, "columns": columns_data}
|
364
364
|
)
|
365
365
|
|
366
|
-
def insert(self, table: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
367
|
-
"""Insert
|
366
|
+
def insert(self, table: str, *data: Dict[str, Any]) -> Dict[str, Any] | List[Dict[str, Any]]:
|
367
|
+
"""Insert one or more records into a table.
|
368
368
|
|
369
369
|
Args:
|
370
370
|
table: Table name
|
371
|
-
data:
|
371
|
+
*data: One or more record data dictionaries
|
372
372
|
|
373
373
|
Returns:
|
374
|
-
|
374
|
+
Single record dict if one record inserted, list of dicts if multiple
|
375
|
+
|
376
|
+
Examples:
|
377
|
+
# Single insert
|
378
|
+
db.insert("users", {"name": "John", "email": "john@example.com"})
|
379
|
+
|
380
|
+
# Multiple inserts using star expansion
|
381
|
+
db.insert("users",
|
382
|
+
{"name": "John", "email": "john@example.com"},
|
383
|
+
{"name": "Jane", "email": "jane@example.com"},
|
384
|
+
{"name": "Bob", "email": "bob@example.com"}
|
385
|
+
)
|
386
|
+
|
387
|
+
# Or with a list using star expansion
|
388
|
+
users = [
|
389
|
+
{"name": "Alice", "email": "alice@example.com"},
|
390
|
+
{"name": "Charlie", "email": "charlie@example.com"}
|
391
|
+
]
|
392
|
+
db.insert("users", *users)
|
375
393
|
"""
|
394
|
+
if not data:
|
395
|
+
raise ValueError("At least one record must be provided")
|
396
|
+
|
376
397
|
if self.is_local:
|
377
|
-
|
398
|
+
# Initialize data manager if needed
|
399
|
+
if self._data_manager is None:
|
400
|
+
from cinchdb.managers.data import DataManager
|
401
|
+
self._data_manager = DataManager(
|
402
|
+
self.project_dir, self.database, self.branch, self.tenant
|
403
|
+
)
|
404
|
+
|
405
|
+
# Single record
|
406
|
+
if len(data) == 1:
|
407
|
+
return self._data_manager.create_from_dict(table, data[0])
|
408
|
+
|
409
|
+
# Multiple records - batch insert
|
410
|
+
results = []
|
411
|
+
for record in data:
|
412
|
+
result = self._data_manager.create_from_dict(table, record)
|
413
|
+
results.append(result)
|
414
|
+
return results
|
378
415
|
else:
|
379
|
-
# Remote insert
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
416
|
+
# Remote insert
|
417
|
+
if len(data) == 1:
|
418
|
+
# Single record - use existing endpoint
|
419
|
+
result = self._make_request(
|
420
|
+
"POST", f"/tables/{table}/data", json={"data": data[0]}
|
421
|
+
)
|
422
|
+
return result
|
423
|
+
else:
|
424
|
+
# Multiple records - use bulk endpoint
|
425
|
+
result = self._make_request(
|
426
|
+
"POST", f"/tables/{table}/data/bulk", json={"records": list(data)}
|
427
|
+
)
|
428
|
+
return result
|
384
429
|
|
385
430
|
def update(self, table: str, id: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
386
431
|
"""Update a record in a table.
|
cinchdb/managers/data.py
CHANGED
@@ -93,14 +93,15 @@ class DataManager:
|
|
93
93
|
results = self.select(model_class, limit=1, id=record_id)
|
94
94
|
return results[0] if results else None
|
95
95
|
|
96
|
-
def
|
97
|
-
"""Create a new record.
|
96
|
+
def create_from_dict(self, table_name: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
97
|
+
"""Create a new record from a dictionary.
|
98
98
|
|
99
99
|
Args:
|
100
|
-
|
100
|
+
table_name: Name of the table to insert into
|
101
|
+
data: Dictionary containing the record data
|
101
102
|
|
102
103
|
Returns:
|
103
|
-
|
104
|
+
Dictionary with created record including generated ID and timestamps
|
104
105
|
|
105
106
|
Raises:
|
106
107
|
ValueError: If record with same ID already exists
|
@@ -109,22 +110,20 @@ class DataManager:
|
|
109
110
|
# Check maintenance mode
|
110
111
|
check_maintenance_mode(self.project_root, self.database, self.branch)
|
111
112
|
|
112
|
-
|
113
|
-
|
114
|
-
# Prepare data for insertion
|
115
|
-
data = instance.model_dump()
|
113
|
+
# Make a copy to avoid modifying the original
|
114
|
+
record_data = data.copy()
|
116
115
|
|
117
116
|
# Generate ID if not provided
|
118
|
-
if not
|
119
|
-
|
117
|
+
if not record_data.get("id"):
|
118
|
+
record_data["id"] = str(uuid.uuid4())
|
120
119
|
|
121
120
|
# Set timestamps
|
122
121
|
now = datetime.now()
|
123
|
-
|
124
|
-
|
122
|
+
record_data["created_at"] = now
|
123
|
+
record_data["updated_at"] = now
|
125
124
|
|
126
125
|
# Build INSERT query
|
127
|
-
columns = list(
|
126
|
+
columns = list(record_data.keys())
|
128
127
|
placeholders = [f":{col}" for col in columns]
|
129
128
|
query = f"""
|
130
129
|
INSERT INTO {table_name} ({", ".join(columns)})
|
@@ -133,17 +132,39 @@ class DataManager:
|
|
133
132
|
|
134
133
|
with DatabaseConnection(self.db_path) as conn:
|
135
134
|
try:
|
136
|
-
conn.execute(query,
|
135
|
+
conn.execute(query, record_data)
|
137
136
|
conn.commit()
|
138
137
|
|
139
|
-
# Return
|
140
|
-
return
|
138
|
+
# Return the created record data
|
139
|
+
return record_data
|
141
140
|
except Exception as e:
|
142
141
|
conn.rollback()
|
143
142
|
if "UNIQUE constraint failed" in str(e):
|
144
|
-
raise ValueError(f"Record with ID {
|
143
|
+
raise ValueError(f"Record with ID {record_data['id']} already exists")
|
145
144
|
raise
|
146
145
|
|
146
|
+
def create(self, instance: T) -> T:
|
147
|
+
"""Create a new record from a model instance.
|
148
|
+
|
149
|
+
Args:
|
150
|
+
instance: Model instance to create
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
Created model instance with populated ID and timestamps
|
154
|
+
|
155
|
+
Raises:
|
156
|
+
ValueError: If record with same ID already exists
|
157
|
+
MaintenanceError: If branch is in maintenance mode
|
158
|
+
"""
|
159
|
+
table_name = self._get_table_name(type(instance))
|
160
|
+
data = instance.model_dump()
|
161
|
+
|
162
|
+
# Use the new create_from_dict method
|
163
|
+
created_data = self.create_from_dict(table_name, data)
|
164
|
+
|
165
|
+
# Return updated instance
|
166
|
+
return type(instance)(**created_data)
|
167
|
+
|
147
168
|
def save(self, instance: T) -> T:
|
148
169
|
"""Save (upsert) a record - insert if new, update if exists.
|
149
170
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cinchdb
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.6
|
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
|
@@ -31,9 +31,9 @@ Description-Content-Type: text/markdown
|
|
31
31
|
|
32
32
|
NOTE: CinchDB is in early alpha. This is project to test out an idea. Do not use this in production.
|
33
33
|
|
34
|
-
CinchDB is for projects that need fast queries,
|
34
|
+
CinchDB is for projects that need fast queries, isolated data per-tenant [or even per-user](https://turso.tech/blog/give-each-of-your-users-their-own-sqlite-database-b74445f4), and a branchable database that makes it easy to merge changes between branches.
|
35
35
|
|
36
|
-
On a meta level
|
36
|
+
On a meta level: I made this because I wanted a database structure that I felt comfortable letting AI agents take full control over, safely, and I didn't want to run my own Postgres instance somewhere or pay for it on e.g. Neon - I don't need hyperscaling, I just need super fast queries.
|
37
37
|
|
38
38
|
Because it's so lightweight and its only dependencies are pydantic, requests, and Typer, it makes for a perfect local development database that can be controlled programmatically.
|
39
39
|
|
@@ -58,6 +58,7 @@ cinch branch merge-into-main feature
|
|
58
58
|
cinch tenant create customer_a
|
59
59
|
cinch query "SELECT * FROM users" --tenant customer_a
|
60
60
|
|
61
|
+
# Coming soon
|
61
62
|
# Connect to remote CinchDB instance
|
62
63
|
cinch remote add production https://your-cinchdb-server.com your-api-key
|
63
64
|
cinch remote use production
|
@@ -76,7 +77,7 @@ CinchDB combines SQLite with Git-like workflows for database schema management:
|
|
76
77
|
- **Safe structure changes** - change merges happen atomically with zero rollback risk (seriously)
|
77
78
|
- **Remote connectivity** - Connect to hosted CinchDB instances
|
78
79
|
- **Type-safe SDK** - Python and TypeScript SDKs with full type safety
|
79
|
-
- **Remote-capable** - CLI and SDK can connect to remote instances
|
80
|
+
- **Remote-capable** - coming soon - CLI and SDK can connect to remote instances
|
80
81
|
- **SDK generation from database schema** - Generate a typesafe SDK from your database models for CRUD operations
|
81
82
|
|
82
83
|
## Installation
|
@@ -128,13 +129,30 @@ db.create_table("posts", [
|
|
128
129
|
# Query data
|
129
130
|
results = db.query("SELECT * FROM posts WHERE title LIKE ?", ["%python%"])
|
130
131
|
|
131
|
-
# CRUD operations
|
132
|
+
# CRUD operations - single insert
|
132
133
|
post_id = db.insert("posts", {"title": "Hello World", "content": "First post"})
|
134
|
+
|
135
|
+
# Batch insert - multiple records at once
|
136
|
+
posts = db.insert("posts",
|
137
|
+
{"title": "First", "content": "Content 1"},
|
138
|
+
{"title": "Second", "content": "Content 2"},
|
139
|
+
{"title": "Third", "content": "Content 3"}
|
140
|
+
)
|
141
|
+
|
142
|
+
# Or with a list using star expansion
|
143
|
+
post_list = [
|
144
|
+
{"title": "Post A", "content": "Content A"},
|
145
|
+
{"title": "Post B", "content": "Content B"}
|
146
|
+
]
|
147
|
+
results = db.insert("posts", *post_list)
|
148
|
+
|
133
149
|
db.update("posts", post_id, {"content": "Updated content"})
|
134
150
|
```
|
135
151
|
|
136
152
|
### Remote Connection
|
137
153
|
|
154
|
+
Coming soon.
|
155
|
+
|
138
156
|
```python
|
139
157
|
# Connect to remote instance
|
140
158
|
db = cinchdb.connect("myapp", url="https://your-cinchdb-server.com", api_key="your-api-key")
|
@@ -144,7 +162,7 @@ results = db.query("SELECT * FROM users")
|
|
144
162
|
user_id = db.insert("users", {"username": "alice", "email": "alice@example.com"})
|
145
163
|
```
|
146
164
|
|
147
|
-
## Remote Access
|
165
|
+
## Remote Access - coming soon
|
148
166
|
|
149
167
|
Connect to a remote CinchDB instance:
|
150
168
|
|
@@ -18,7 +18,7 @@ cinchdb/cli/handlers/__init__.py,sha256=f2f-Cc96rSBLbVsiIbf-b4pZCKZoHfmhNEvnZ0Ou
|
|
18
18
|
cinchdb/cli/handlers/codegen_handler.py,sha256=i5we_AbiUW3zfO6pIKWxvtO8OvOqz3H__4xPmTLEuQM,6524
|
19
19
|
cinchdb/core/__init__.py,sha256=iNlT0iO9cM0HLoYwzBavUBoXRh1Tcnz1l_vfbwVxK_Q,246
|
20
20
|
cinchdb/core/connection.py,sha256=SlKyEfIpeaDws8M6SfEbvCEVnt26zBY1RYwHtTXj0kY,5110
|
21
|
-
cinchdb/core/database.py,sha256=
|
21
|
+
cinchdb/core/database.py,sha256=IyMo1wfO3mTnnth0m7eP7Dib6_cEC7bTBo1JhOt6Vlo,19792
|
22
22
|
cinchdb/core/initializer.py,sha256=CjnJSMuR1NrHobyFfwL44tUeH8VE62q02bijEtVH3p4,6922
|
23
23
|
cinchdb/core/maintenance.py,sha256=PAgrSL7Cj9p3rKHV0h_L7gupN6nLD0-5eQpJZNiqyEs,2097
|
24
24
|
cinchdb/core/path_utils.py,sha256=J2UEu1X_NFOqDamcsrPrC7ZitGTg9Y-HFjmx4sHf5j8,3806
|
@@ -29,7 +29,7 @@ cinchdb/managers/change_comparator.py,sha256=08pwybpSt36cFwhZRSIkHynvFMUaLKEVwa8
|
|
29
29
|
cinchdb/managers/change_tracker.py,sha256=U93BPnuGv8xSaO5qr_y5Q8ppKrVXygozdp5zUvLUqwg,5054
|
30
30
|
cinchdb/managers/codegen.py,sha256=1CfIwjgHnNDdjrq4SzQ9VE7DFgnWfk7RtpupBFUTqxk,21804
|
31
31
|
cinchdb/managers/column.py,sha256=YhYq-hnH0o2BqZkyihnsY5KIWEztzs-_iLJNZMdVUkk,20807
|
32
|
-
cinchdb/managers/data.py,sha256=
|
32
|
+
cinchdb/managers/data.py,sha256=zS1HkMGf436m6f8VdFAqQbQFgo4sL5yKJRcRf4A6lIc,16253
|
33
33
|
cinchdb/managers/merge_manager.py,sha256=R8S2hLkLJg4hLDpeJTzjVkduZgqPOjXtYgOSJhTXXrE,15690
|
34
34
|
cinchdb/managers/query.py,sha256=pBlbqoovnFsZ36pB7nv8NtzcTFwtT26hp8IlwjIx29Q,7301
|
35
35
|
cinchdb/managers/table.py,sha256=KWSAfZCJafKZPx-dRG7KwQUGVqVQ56ARMVBllb3VBig,13114
|
@@ -47,8 +47,8 @@ cinchdb/models/view.py,sha256=q6j-jYzFJuhRJO87rKt6Uv8hOizHQx8xwoPKoH6XnNY,530
|
|
47
47
|
cinchdb/utils/__init__.py,sha256=yQQhEjndDiB2SUJybUmp9dvEOQKiR-GySe-WiCius5E,490
|
48
48
|
cinchdb/utils/name_validator.py,sha256=dyGX5bjlTFRA9EGrWRQKp6kR__HSV04hLV5VueJs4IQ,4027
|
49
49
|
cinchdb/utils/sql_validator.py,sha256=aWOGlPX0gBkuR6R1EBP2stbP4PHZuI6FUBi2Ljx7JUI,5815
|
50
|
-
cinchdb-0.1.
|
51
|
-
cinchdb-0.1.
|
52
|
-
cinchdb-0.1.
|
53
|
-
cinchdb-0.1.
|
54
|
-
cinchdb-0.1.
|
50
|
+
cinchdb-0.1.6.dist-info/METADATA,sha256=1vNN2cSZJi_B_ho9yHtnLRjYt3x537gd905QAOtJyK0,6334
|
51
|
+
cinchdb-0.1.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
52
|
+
cinchdb-0.1.6.dist-info/entry_points.txt,sha256=VBOIzvnGbkKudMCCmNORS3885QSyjZUVKJQ-Syqa62w,47
|
53
|
+
cinchdb-0.1.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
54
|
+
cinchdb-0.1.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|