cinchdb 0.1.4__tar.gz → 0.1.6__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.
Files changed (54) hide show
  1. {cinchdb-0.1.4 → cinchdb-0.1.6}/PKG-INFO +24 -6
  2. {cinchdb-0.1.4 → cinchdb-0.1.6}/README.md +23 -5
  3. {cinchdb-0.1.4 → cinchdb-0.1.6}/pyproject.toml +1 -1
  4. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/core/database.py +55 -10
  5. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/data.py +38 -17
  6. {cinchdb-0.1.4 → cinchdb-0.1.6}/.gitignore +0 -0
  7. {cinchdb-0.1.4 → cinchdb-0.1.6}/LICENSE +0 -0
  8. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/__init__.py +0 -0
  9. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/__main__.py +0 -0
  10. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/__init__.py +0 -0
  11. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/__init__.py +0 -0
  12. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/branch.py +0 -0
  13. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/codegen.py +0 -0
  14. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/column.py +0 -0
  15. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/database.py +0 -0
  16. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/query.py +0 -0
  17. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/remote.py +0 -0
  18. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/table.py +0 -0
  19. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/tenant.py +0 -0
  20. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/commands/view.py +0 -0
  21. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/handlers/__init__.py +0 -0
  22. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/handlers/codegen_handler.py +0 -0
  23. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/main.py +0 -0
  24. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/cli/utils.py +0 -0
  25. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/config.py +0 -0
  26. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/core/__init__.py +0 -0
  27. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/core/connection.py +0 -0
  28. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/core/initializer.py +0 -0
  29. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/core/maintenance.py +0 -0
  30. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/core/path_utils.py +0 -0
  31. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/__init__.py +0 -0
  32. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/branch.py +0 -0
  33. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/change_applier.py +0 -0
  34. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/change_comparator.py +0 -0
  35. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/change_tracker.py +0 -0
  36. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/codegen.py +0 -0
  37. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/column.py +0 -0
  38. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/merge_manager.py +0 -0
  39. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/query.py +0 -0
  40. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/table.py +0 -0
  41. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/tenant.py +0 -0
  42. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/managers/view.py +0 -0
  43. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/models/__init__.py +0 -0
  44. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/models/base.py +0 -0
  45. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/models/branch.py +0 -0
  46. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/models/change.py +0 -0
  47. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/models/database.py +0 -0
  48. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/models/project.py +0 -0
  49. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/models/table.py +0 -0
  50. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/models/tenant.py +0 -0
  51. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/models/view.py +0 -0
  52. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/utils/__init__.py +0 -0
  53. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/utils/name_validator.py +0 -0
  54. {cinchdb-0.1.4 → cinchdb-0.1.6}/src/cinchdb/utils/sql_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cinchdb
3
- Version: 0.1.4
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, data 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.
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, 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.
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
 
@@ -4,9 +4,9 @@
4
4
 
5
5
  NOTE: CinchDB is in early alpha. This is project to test out an idea. Do not use this in production.
6
6
 
7
- CinchDB is for projects that need fast queries, data 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.
7
+ 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.
8
8
 
9
- 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.
9
+ 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.
10
10
 
11
11
  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.
12
12
 
@@ -31,6 +31,7 @@ cinch branch merge-into-main feature
31
31
  cinch tenant create customer_a
32
32
  cinch query "SELECT * FROM users" --tenant customer_a
33
33
 
34
+ # Coming soon
34
35
  # Connect to remote CinchDB instance
35
36
  cinch remote add production https://your-cinchdb-server.com your-api-key
36
37
  cinch remote use production
@@ -49,7 +50,7 @@ CinchDB combines SQLite with Git-like workflows for database schema management:
49
50
  - **Safe structure changes** - change merges happen atomically with zero rollback risk (seriously)
50
51
  - **Remote connectivity** - Connect to hosted CinchDB instances
51
52
  - **Type-safe SDK** - Python and TypeScript SDKs with full type safety
52
- - **Remote-capable** - CLI and SDK can connect to remote instances
53
+ - **Remote-capable** - coming soon - CLI and SDK can connect to remote instances
53
54
  - **SDK generation from database schema** - Generate a typesafe SDK from your database models for CRUD operations
54
55
 
55
56
  ## Installation
@@ -101,13 +102,30 @@ db.create_table("posts", [
101
102
  # Query data
102
103
  results = db.query("SELECT * FROM posts WHERE title LIKE ?", ["%python%"])
103
104
 
104
- # CRUD operations
105
+ # CRUD operations - single insert
105
106
  post_id = db.insert("posts", {"title": "Hello World", "content": "First post"})
107
+
108
+ # Batch insert - multiple records at once
109
+ posts = db.insert("posts",
110
+ {"title": "First", "content": "Content 1"},
111
+ {"title": "Second", "content": "Content 2"},
112
+ {"title": "Third", "content": "Content 3"}
113
+ )
114
+
115
+ # Or with a list using star expansion
116
+ post_list = [
117
+ {"title": "Post A", "content": "Content A"},
118
+ {"title": "Post B", "content": "Content B"}
119
+ ]
120
+ results = db.insert("posts", *post_list)
121
+
106
122
  db.update("posts", post_id, {"content": "Updated content"})
107
123
  ```
108
124
 
109
125
  ### Remote Connection
110
126
 
127
+ Coming soon.
128
+
111
129
  ```python
112
130
  # Connect to remote instance
113
131
  db = cinchdb.connect("myapp", url="https://your-cinchdb-server.com", api_key="your-api-key")
@@ -117,7 +135,7 @@ results = db.query("SELECT * FROM users")
117
135
  user_id = db.insert("users", {"username": "alice", "email": "alice@example.com"})
118
136
  ```
119
137
 
120
- ## Remote Access
138
+ ## Remote Access - coming soon
121
139
 
122
140
  Connect to a remote CinchDB instance:
123
141
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cinchdb"
3
- version = "0.1.4"
3
+ version = "0.1.6"
4
4
  description = "A Git-like SQLite database management system with branching and multi-tenancy"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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 a record into a table.
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: Record data as dictionary
371
+ *data: One or more record data dictionaries
372
372
 
373
373
  Returns:
374
- Inserted record with generated fields
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
- return self.data.create(table, data)
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 - use new data CRUD endpoint
380
- result = self._make_request(
381
- "POST", f"/tables/{table}/data", json={"data": data}
382
- )
383
- return result
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.
@@ -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 create(self, instance: T) -> T:
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
- instance: Model instance to create
100
+ table_name: Name of the table to insert into
101
+ data: Dictionary containing the record data
101
102
 
102
103
  Returns:
103
- Created model instance with populated ID and timestamps
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
- table_name = self._get_table_name(type(instance))
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 data.get("id"):
119
- data["id"] = str(uuid.uuid4())
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
- data["created_at"] = now
124
- data["updated_at"] = now
122
+ record_data["created_at"] = now
123
+ record_data["updated_at"] = now
125
124
 
126
125
  # Build INSERT query
127
- columns = list(data.keys())
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, data)
135
+ conn.execute(query, record_data)
137
136
  conn.commit()
138
137
 
139
- # Return updated instance
140
- return type(instance)(**data)
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 {data['id']} already exists")
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
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes