cinchdb 0.1.0__tar.gz → 0.1.2__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 (69) hide show
  1. {cinchdb-0.1.0 → cinchdb-0.1.2}/.gitignore +3 -1
  2. {cinchdb-0.1.0 → cinchdb-0.1.2}/PKG-INFO +17 -19
  3. {cinchdb-0.1.0 → cinchdb-0.1.2}/README.md +16 -15
  4. {cinchdb-0.1.0 → cinchdb-0.1.2}/pyproject.toml +1 -5
  5. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/branch.py +9 -0
  6. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/database.py +9 -0
  7. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/tenant.py +17 -0
  8. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/branch.py +5 -0
  9. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/tenant.py +13 -0
  10. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/models/branch.py +9 -1
  11. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/models/database.py +9 -1
  12. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/models/tenant.py +9 -1
  13. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/utils/__init__.py +11 -1
  14. cinchdb-0.1.2/src/cinchdb/utils/name_validator.py +129 -0
  15. cinchdb-0.1.0/src/cinchdb/api/__init__.py +0 -5
  16. cinchdb-0.1.0/src/cinchdb/api/app.py +0 -76
  17. cinchdb-0.1.0/src/cinchdb/api/auth.py +0 -290
  18. cinchdb-0.1.0/src/cinchdb/api/main.py +0 -137
  19. cinchdb-0.1.0/src/cinchdb/api/routers/__init__.py +0 -25
  20. cinchdb-0.1.0/src/cinchdb/api/routers/auth.py +0 -135
  21. cinchdb-0.1.0/src/cinchdb/api/routers/branches.py +0 -368
  22. cinchdb-0.1.0/src/cinchdb/api/routers/codegen.py +0 -164
  23. cinchdb-0.1.0/src/cinchdb/api/routers/columns.py +0 -290
  24. cinchdb-0.1.0/src/cinchdb/api/routers/data.py +0 -479
  25. cinchdb-0.1.0/src/cinchdb/api/routers/databases.py +0 -177
  26. cinchdb-0.1.0/src/cinchdb/api/routers/projects.py +0 -133
  27. cinchdb-0.1.0/src/cinchdb/api/routers/query.py +0 -156
  28. cinchdb-0.1.0/src/cinchdb/api/routers/tables.py +0 -349
  29. cinchdb-0.1.0/src/cinchdb/api/routers/tenants.py +0 -216
  30. cinchdb-0.1.0/src/cinchdb/api/routers/views.py +0 -219
  31. {cinchdb-0.1.0 → cinchdb-0.1.2}/LICENSE +0 -0
  32. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/__init__.py +0 -0
  33. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/__main__.py +0 -0
  34. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/__init__.py +0 -0
  35. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/__init__.py +0 -0
  36. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/codegen.py +0 -0
  37. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/column.py +0 -0
  38. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/query.py +0 -0
  39. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/remote.py +0 -0
  40. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/table.py +0 -0
  41. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/commands/view.py +0 -0
  42. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/handlers/__init__.py +0 -0
  43. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/handlers/codegen_handler.py +0 -0
  44. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/main.py +0 -0
  45. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/cli/utils.py +0 -0
  46. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/config.py +0 -0
  47. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/core/__init__.py +0 -0
  48. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/core/connection.py +0 -0
  49. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/core/database.py +0 -0
  50. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/core/maintenance.py +0 -0
  51. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/core/path_utils.py +0 -0
  52. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/__init__.py +0 -0
  53. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/change_applier.py +0 -0
  54. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/change_comparator.py +0 -0
  55. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/change_tracker.py +0 -0
  56. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/codegen.py +0 -0
  57. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/column.py +0 -0
  58. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/data.py +0 -0
  59. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/merge_manager.py +0 -0
  60. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/query.py +0 -0
  61. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/table.py +0 -0
  62. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/managers/view.py +0 -0
  63. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/models/__init__.py +0 -0
  64. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/models/base.py +0 -0
  65. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/models/change.py +0 -0
  66. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/models/project.py +0 -0
  67. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/models/table.py +0 -0
  68. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/models/view.py +0 -0
  69. {cinchdb-0.1.0 → cinchdb-0.1.2}/src/cinchdb/utils/sql_validator.py +0 -0
@@ -66,4 +66,6 @@ coverage/
66
66
  # OS
67
67
  Thumbs.db
68
68
 
69
- site/
69
+ site/
70
+
71
+ saas/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cinchdb
3
- Version: 0.1.0
3
+ Version: 0.1.2
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
@@ -18,14 +18,11 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Requires-Python: >=3.10
21
- Requires-Dist: fastapi>=0.115.0
22
21
  Requires-Dist: pydantic>=2.0.0
23
- Requires-Dist: python-multipart>=0.0.12
24
22
  Requires-Dist: requests>=2.28.0
25
23
  Requires-Dist: rich>=13.0.0
26
24
  Requires-Dist: toml>=0.10.0
27
25
  Requires-Dist: typer>=0.9.0
28
- Requires-Dist: uvicorn>=0.32.0
29
26
  Description-Content-Type: text/markdown
30
27
 
31
28
  # CinchDB
@@ -34,11 +31,11 @@ Description-Content-Type: text/markdown
34
31
 
35
32
  NOTE: CinchDB is in early alpha. This is project to test out an idea. Do not use this in production.
36
33
 
37
- CinchDB is for projects that need to isolate data per-tenant [or even per-user](https://turso.tech/blog/give-each-of-your-users-their-own-sqlite-database-b74445f4) and/or want to test out changes to structure safely in branchces. Plus, queries can be super, super fast because data is separated per tenant so it's <data_size>/<n_tenants> instead of <data_size>
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.
38
35
 
39
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.
40
37
 
41
- Because it's so lightweight and its only dependencies are FastAPI, pydantic, requests, and Typer, it is super cheap to run on a small VM like from [Fly.io](https://fly.io) and I don't need to SSH into it to connect - I can just store an API key in my local projects.
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.
42
39
 
43
40
 
44
41
  ```bash
@@ -61,9 +58,9 @@ cinch branch merge-into-main feature
61
58
  cinch tenant create customer_a
62
59
  cinch query "SELECT * FROM users" --tenant customer_a
63
60
 
64
- # API server
65
- uv pip install cinchdb[server]
66
- cinch-server serve
61
+ # Connect to remote CinchDB instance
62
+ cinch remote add production https://your-cinchdb-server.com your-api-key
63
+ cinch remote use production
67
64
 
68
65
  # Autogenerate Python SDK from database
69
66
  cinch codegen generate python cinchdb_models/
@@ -77,9 +74,9 @@ CinchDB combines SQLite with Git-like workflows for database schema management:
77
74
  - **Multi-tenant isolation** - shared schema, isolated data per tenant
78
75
  - **Automatic change tracking** - all schema changes tracked and mergeable
79
76
  - **Safe structure changes** - change merges happen atomically with zero rollback risk (seriously)
80
- - **Remote deployment** - FastAPI server with UUID authentication
77
+ - **Remote connectivity** - Connect to hosted CinchDB instances
81
78
  - **Type-safe SDK** - Python and TypeScript SDKs with full type safety
82
- - **API server for remote hosting** - Useful for running many web projects
79
+ - **Remote-capable** - CLI and SDK can connect to remote instances
83
80
  - **SDK generation from database schema** - Generate a typesafe SDK from your database models for CRUD operations
84
81
 
85
82
  ## Installation
@@ -136,24 +133,25 @@ post_id = db.insert("posts", {"title": "Hello World", "content": "First post"})
136
133
  db.update("posts", post_id, {"content": "Updated content"})
137
134
  ```
138
135
 
139
- ### Remote API
136
+ ### Remote Connection
140
137
 
141
138
  ```python
142
- # Connect to remote API
143
- db = cinchdb.connect_api("https://api.example.com", "your-api-key", "myapp")
139
+ # Connect to remote instance
140
+ db = cinchdb.connect("myapp", url="https://your-cinchdb-server.com", api_key="your-api-key")
144
141
 
145
142
  # Same interface as local
146
143
  results = db.query("SELECT * FROM users")
147
144
  user_id = db.insert("users", {"username": "alice", "email": "alice@example.com"})
148
145
  ```
149
146
 
150
- ## API Server
147
+ ## Remote Access
151
148
 
152
- Start the server:
149
+ Connect to a remote CinchDB instance:
153
150
 
154
151
  ```bash
155
- cinch-server serve --create-key
156
- # Creates API key (works after shutdown as well) and starts server on http://localhost:8000
152
+ cinch remote add production https://your-cinchdb-server.com your-api-key
153
+ cinch remote use production
154
+ # Now all commands will use the remote instance
157
155
  ```
158
156
 
159
157
  Interactive docs at `/docs`, health check at `/health`.
@@ -162,7 +160,7 @@ Interactive docs at `/docs`, health check at `/health`.
162
160
 
163
161
  - **Python SDK**: Core functionality (local + remote)
164
162
  - **CLI**: Full-featured command-line interface
165
- - **FastAPI Server**: REST API with authentication
163
+ - **Remote Access**: Connect to hosted CinchDB instances
166
164
  - **TypeScript SDK**: Browser and Node.js client
167
165
 
168
166
  ## Development
@@ -4,11 +4,11 @@
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 to isolate data per-tenant [or even per-user](https://turso.tech/blog/give-each-of-your-users-their-own-sqlite-database-b74445f4) and/or want to test out changes to structure safely in branchces. Plus, queries can be super, super fast because data is separated per tenant so it's <data_size>/<n_tenants> instead of <data_size>
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.
8
8
 
9
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
- Because it's so lightweight and its only dependencies are FastAPI, pydantic, requests, and Typer, it is super cheap to run on a small VM like from [Fly.io](https://fly.io) and I don't need to SSH into it to connect - I can just store an API key in my local projects.
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
 
13
13
 
14
14
  ```bash
@@ -31,9 +31,9 @@ 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
- # API server
35
- uv pip install cinchdb[server]
36
- cinch-server serve
34
+ # Connect to remote CinchDB instance
35
+ cinch remote add production https://your-cinchdb-server.com your-api-key
36
+ cinch remote use production
37
37
 
38
38
  # Autogenerate Python SDK from database
39
39
  cinch codegen generate python cinchdb_models/
@@ -47,9 +47,9 @@ CinchDB combines SQLite with Git-like workflows for database schema management:
47
47
  - **Multi-tenant isolation** - shared schema, isolated data per tenant
48
48
  - **Automatic change tracking** - all schema changes tracked and mergeable
49
49
  - **Safe structure changes** - change merges happen atomically with zero rollback risk (seriously)
50
- - **Remote deployment** - FastAPI server with UUID authentication
50
+ - **Remote connectivity** - Connect to hosted CinchDB instances
51
51
  - **Type-safe SDK** - Python and TypeScript SDKs with full type safety
52
- - **API server for remote hosting** - Useful for running many web projects
52
+ - **Remote-capable** - CLI and SDK can connect to remote instances
53
53
  - **SDK generation from database schema** - Generate a typesafe SDK from your database models for CRUD operations
54
54
 
55
55
  ## Installation
@@ -106,24 +106,25 @@ post_id = db.insert("posts", {"title": "Hello World", "content": "First post"})
106
106
  db.update("posts", post_id, {"content": "Updated content"})
107
107
  ```
108
108
 
109
- ### Remote API
109
+ ### Remote Connection
110
110
 
111
111
  ```python
112
- # Connect to remote API
113
- db = cinchdb.connect_api("https://api.example.com", "your-api-key", "myapp")
112
+ # Connect to remote instance
113
+ db = cinchdb.connect("myapp", url="https://your-cinchdb-server.com", api_key="your-api-key")
114
114
 
115
115
  # Same interface as local
116
116
  results = db.query("SELECT * FROM users")
117
117
  user_id = db.insert("users", {"username": "alice", "email": "alice@example.com"})
118
118
  ```
119
119
 
120
- ## API Server
120
+ ## Remote Access
121
121
 
122
- Start the server:
122
+ Connect to a remote CinchDB instance:
123
123
 
124
124
  ```bash
125
- cinch-server serve --create-key
126
- # Creates API key (works after shutdown as well) and starts server on http://localhost:8000
125
+ cinch remote add production https://your-cinchdb-server.com your-api-key
126
+ cinch remote use production
127
+ # Now all commands will use the remote instance
127
128
  ```
128
129
 
129
130
  Interactive docs at `/docs`, health check at `/health`.
@@ -132,7 +133,7 @@ Interactive docs at `/docs`, health check at `/health`.
132
133
 
133
134
  - **Python SDK**: Core functionality (local + remote)
134
135
  - **CLI**: Full-featured command-line interface
135
- - **FastAPI Server**: REST API with authentication
136
+ - **Remote Access**: Connect to hosted CinchDB instances
136
137
  - **TypeScript SDK**: Browser and Node.js client
137
138
 
138
139
  ## Development
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cinchdb"
3
- version = "0.1.0"
3
+ version = "0.1.2"
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"
@@ -23,9 +23,6 @@ dependencies = [
23
23
  "toml>=0.10.0",
24
24
  "typer>=0.9.0",
25
25
  "rich>=13.0.0",
26
- "fastapi>=0.115.0",
27
- "uvicorn>=0.32.0",
28
- "python-multipart>=0.0.12",
29
26
  "requests>=2.28.0",
30
27
  ]
31
28
 
@@ -37,7 +34,6 @@ Issues = "https://github.com/russellromney/cinchdb/issues"
37
34
 
38
35
  [project.scripts]
39
36
  cinch = "cinchdb.cli.main:app"
40
- cinch-server = "cinchdb.api.main:cli"
41
37
 
42
38
  [build-system]
43
39
  requires = ["hatchling"]
@@ -14,6 +14,7 @@ from cinchdb.cli.utils import (
14
14
  set_active_branch,
15
15
  validate_required_arg,
16
16
  )
17
+ from cinchdb.utils.name_validator import validate_name, InvalidNameError
17
18
 
18
19
  app = typer.Typer(help="Branch management commands", invoke_without_command=True)
19
20
  console = Console()
@@ -80,6 +81,14 @@ def create(
80
81
  ):
81
82
  """Create a new branch."""
82
83
  name = validate_required_arg(name, "name", ctx)
84
+
85
+ # Validate branch name
86
+ try:
87
+ validate_name(name, "branch")
88
+ except InvalidNameError as e:
89
+ console.print(f"[red]❌ {e}[/red]")
90
+ raise typer.Exit(1)
91
+
83
92
  config, config_data = get_config_with_data()
84
93
  db_name = config_data.active_database
85
94
  source_branch = source or config_data.active_branch
@@ -12,6 +12,7 @@ from cinchdb.cli.utils import (
12
12
  set_active_branch,
13
13
  validate_required_arg,
14
14
  )
15
+ from cinchdb.utils.name_validator import validate_name, InvalidNameError
15
16
 
16
17
  app = typer.Typer(help="Database management commands", invoke_without_command=True)
17
18
  console = Console()
@@ -64,6 +65,14 @@ def create(
64
65
  ):
65
66
  """Create a new database."""
66
67
  name = validate_required_arg(name, "name", ctx)
68
+
69
+ # Validate database name
70
+ try:
71
+ validate_name(name, "database")
72
+ except InvalidNameError as e:
73
+ console.print(f"[red]❌ {e}[/red]")
74
+ raise typer.Exit(1)
75
+
67
76
  config, config_data = get_config_with_data()
68
77
 
69
78
  # Create database directory structure
@@ -10,6 +10,7 @@ from cinchdb.config import Config
10
10
  from cinchdb.core.path_utils import get_project_root
11
11
  from cinchdb.managers.tenant import TenantManager
12
12
  from cinchdb.cli.utils import get_config_with_data, validate_required_arg
13
+ from cinchdb.utils.name_validator import validate_name, InvalidNameError
13
14
 
14
15
  app = typer.Typer(help="Tenant management commands", invoke_without_command=True)
15
16
  console = Console()
@@ -70,6 +71,14 @@ def create(
70
71
  ):
71
72
  """Create a new tenant."""
72
73
  name = validate_required_arg(name, "name", ctx)
74
+
75
+ # Validate tenant name
76
+ try:
77
+ validate_name(name, "tenant")
78
+ except InvalidNameError as e:
79
+ console.print(f"[red]❌ {e}[/red]")
80
+ raise typer.Exit(1)
81
+
73
82
  config, config_data = get_config_with_data()
74
83
  db_name = config_data.active_database
75
84
  branch_name = config_data.active_branch
@@ -155,6 +164,14 @@ def rename(
155
164
  """Rename a tenant."""
156
165
  old_name = validate_required_arg(old_name, "old_name", ctx)
157
166
  new_name = validate_required_arg(new_name, "new_name", ctx)
167
+
168
+ # Validate new tenant name
169
+ try:
170
+ validate_name(new_name, "tenant")
171
+ except InvalidNameError as e:
172
+ console.print(f"[red]❌ {e}[/red]")
173
+ raise typer.Exit(1)
174
+
158
175
  config, config_data = get_config_with_data()
159
176
  db_name = config_data.active_database
160
177
  branch_name = config_data.active_branch
@@ -13,6 +13,7 @@ from cinchdb.core.path_utils import (
13
13
  get_branch_path,
14
14
  list_branches,
15
15
  )
16
+ from cinchdb.utils.name_validator import validate_name
16
17
 
17
18
 
18
19
  class BranchManager:
@@ -63,7 +64,11 @@ class BranchManager:
63
64
 
64
65
  Raises:
65
66
  ValueError: If source doesn't exist or new branch already exists
67
+ InvalidNameError: If new branch name is invalid
66
68
  """
69
+ # Validate new branch name
70
+ validate_name(new_branch_name, "branch")
71
+
67
72
  # Validate source branch exists
68
73
  if source_branch not in list_branches(self.project_root, self.database):
69
74
  raise ValueError(f"Source branch '{source_branch}' does not exist")
@@ -12,6 +12,7 @@ from cinchdb.core.path_utils import (
12
12
  )
13
13
  from cinchdb.core.connection import DatabaseConnection
14
14
  from cinchdb.core.maintenance import check_maintenance_mode
15
+ from cinchdb.utils.name_validator import validate_name
15
16
 
16
17
 
17
18
  class TenantManager:
@@ -64,8 +65,12 @@ class TenantManager:
64
65
 
65
66
  Raises:
66
67
  ValueError: If tenant already exists
68
+ InvalidNameError: If tenant name is invalid
67
69
  MaintenanceError: If branch is in maintenance mode
68
70
  """
71
+ # Validate tenant name
72
+ validate_name(tenant_name, "tenant")
73
+
69
74
  # Check maintenance mode
70
75
  check_maintenance_mode(self.project_root, self.database, self.branch)
71
76
 
@@ -158,8 +163,12 @@ class TenantManager:
158
163
 
159
164
  Raises:
160
165
  ValueError: If source doesn't exist or target already exists
166
+ InvalidNameError: If target tenant name is invalid
161
167
  MaintenanceError: If branch is in maintenance mode
162
168
  """
169
+ # Validate target tenant name
170
+ validate_name(target_tenant, "tenant")
171
+
163
172
  # Check maintenance mode
164
173
  check_maintenance_mode(self.project_root, self.database, self.branch)
165
174
 
@@ -200,7 +209,11 @@ class TenantManager:
200
209
 
201
210
  Raises:
202
211
  ValueError: If old doesn't exist, new already exists, or trying to rename main
212
+ InvalidNameError: If new tenant name is invalid
203
213
  """
214
+ # Validate new tenant name
215
+ validate_name(new_name, "tenant")
216
+
204
217
  # Can't rename main tenant
205
218
  if old_name == "main":
206
219
  raise ValueError("Cannot rename the main tenant")
@@ -1,8 +1,9 @@
1
1
  """Branch model for CinchDB."""
2
2
 
3
3
  from typing import List, Optional, Dict, Any
4
- from pydantic import Field
4
+ from pydantic import Field, field_validator
5
5
  from .base import CinchDBBaseModel
6
+ from ..utils.name_validator import validate_name
6
7
 
7
8
 
8
9
  class Branch(CinchDBBaseModel):
@@ -21,6 +22,13 @@ class Branch(CinchDBBaseModel):
21
22
  )
22
23
  is_main: bool = Field(default=False, description="Whether this is the main branch")
23
24
 
25
+ @field_validator('name')
26
+ @classmethod
27
+ def validate_name_field(cls, v: str) -> str:
28
+ """Validate branch name meets naming requirements."""
29
+ validate_name(v, "branch")
30
+ return v
31
+
24
32
  def can_delete(self) -> bool:
25
33
  """Check if this branch can be deleted."""
26
34
  return self.name != "main" and not self.is_main
@@ -1,8 +1,9 @@
1
1
  """Database model for CinchDB."""
2
2
 
3
3
  from typing import List, Optional
4
- from pydantic import Field
4
+ from pydantic import Field, field_validator
5
5
  from .base import CinchDBBaseModel
6
+ from ..utils.name_validator import validate_name
6
7
 
7
8
 
8
9
  class Database(CinchDBBaseModel):
@@ -15,6 +16,13 @@ class Database(CinchDBBaseModel):
15
16
  active_branch: str = Field(default="main", description="Currently active branch")
16
17
  description: Optional[str] = Field(default=None, description="Database description")
17
18
 
19
+ @field_validator('name')
20
+ @classmethod
21
+ def validate_name_field(cls, v: str) -> str:
22
+ """Validate database name meets naming requirements."""
23
+ validate_name(v, "database")
24
+ return v
25
+
18
26
  def can_delete(self) -> bool:
19
27
  """Check if this database can be deleted."""
20
28
  return self.name != "main"
@@ -1,8 +1,9 @@
1
1
  """Tenant model for CinchDB."""
2
2
 
3
3
  from typing import Optional
4
- from pydantic import Field
4
+ from pydantic import Field, field_validator
5
5
  from .base import CinchDBBaseModel
6
+ from ..utils.name_validator import validate_name
6
7
 
7
8
 
8
9
  class Tenant(CinchDBBaseModel):
@@ -14,6 +15,13 @@ class Tenant(CinchDBBaseModel):
14
15
  description: Optional[str] = Field(default=None, description="Tenant description")
15
16
  is_main: bool = Field(default=False, description="Whether this is the main tenant")
16
17
 
18
+ @field_validator('name')
19
+ @classmethod
20
+ def validate_name_field(cls, v: str) -> str:
21
+ """Validate tenant name meets naming requirements."""
22
+ validate_name(v, "tenant")
23
+ return v
24
+
17
25
  def can_delete(self) -> bool:
18
26
  """Check if this tenant can be deleted."""
19
27
  return self.name != "main" and not self.is_main
@@ -6,10 +6,20 @@ from cinchdb.utils.sql_validator import (
6
6
  SQLValidationError,
7
7
  SQLOperation
8
8
  )
9
+ from cinchdb.utils.name_validator import (
10
+ validate_name,
11
+ clean_name,
12
+ is_valid_name,
13
+ InvalidNameError
14
+ )
9
15
 
10
16
  __all__ = [
11
17
  "validate_sql_query",
12
18
  "validate_query_safe",
13
19
  "SQLValidationError",
14
- "SQLOperation"
20
+ "SQLOperation",
21
+ "validate_name",
22
+ "clean_name",
23
+ "is_valid_name",
24
+ "InvalidNameError"
15
25
  ]
@@ -0,0 +1,129 @@
1
+ """Name validation utilities for CinchDB entities.
2
+
3
+ Ensures branch, database, and tenant names are safe for filesystem operations
4
+ and follow consistent naming conventions.
5
+ """
6
+
7
+ import re
8
+ from typing import Optional
9
+
10
+
11
+ # Regex pattern for valid names: lowercase letters, numbers, dash, underscore, period
12
+ VALID_NAME_PATTERN = re.compile(r'^[a-z0-9][a-z0-9\-._]*[a-z0-9]$|^[a-z0-9]$')
13
+
14
+ # Reserved names that cannot be used
15
+ RESERVED_NAMES = {'con', 'prn', 'aux', 'nul', 'com1', 'com2', 'com3', 'com4',
16
+ 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2',
17
+ 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9'}
18
+
19
+
20
+ class InvalidNameError(ValueError):
21
+ """Raised when a name doesn't meet validation requirements."""
22
+ pass
23
+
24
+
25
+ def validate_name(name: str, entity_type: str = "entity") -> None:
26
+ """Validate that a name meets CinchDB naming requirements.
27
+
28
+ Valid names must:
29
+ - Contain only lowercase letters (a-z), numbers (0-9), dash (-), underscore (_), and period (.)
30
+ - Start and end with alphanumeric characters
31
+ - Be at least 1 character long
32
+ - Not exceed 255 characters (filesystem limit)
33
+ - Not be a reserved name
34
+
35
+ Args:
36
+ name: The name to validate
37
+ entity_type: Type of entity (branch, database, tenant) for error messages
38
+
39
+ Raises:
40
+ InvalidNameError: If the name is invalid
41
+ """
42
+ if not name:
43
+ raise InvalidNameError(f"{entity_type.capitalize()} name cannot be empty")
44
+
45
+ if len(name) > 255:
46
+ raise InvalidNameError(
47
+ f"{entity_type.capitalize()} name cannot exceed 255 characters"
48
+ )
49
+
50
+ # Check for lowercase requirement
51
+ if name != name.lower():
52
+ raise InvalidNameError(
53
+ f"{entity_type.capitalize()} name must be lowercase. "
54
+ f"Use '{name.lower()}' instead of '{name}'"
55
+ )
56
+
57
+ # Check pattern
58
+ if not VALID_NAME_PATTERN.match(name):
59
+ raise InvalidNameError(
60
+ f"Invalid {entity_type} name '{name}'. "
61
+ f"Names must contain only lowercase letters (a-z), numbers (0-9), "
62
+ f"dash (-), underscore (_), and period (.). "
63
+ f"Names must start and end with alphanumeric characters."
64
+ )
65
+
66
+ # Check for consecutive special characters
67
+ if '..' in name or '--' in name or '__' in name or '.-' in name or '-.' in name:
68
+ raise InvalidNameError(
69
+ f"Invalid {entity_type} name '{name}'. "
70
+ f"Names cannot contain consecutive special characters."
71
+ )
72
+
73
+ # Check reserved names
74
+ if name.lower() in RESERVED_NAMES:
75
+ raise InvalidNameError(
76
+ f"'{name}' is a reserved name and cannot be used as a {entity_type} name"
77
+ )
78
+
79
+
80
+ def clean_name(name: str) -> str:
81
+ """Clean a name to make it valid if possible.
82
+
83
+ This performs basic cleaning:
84
+ - Convert to lowercase
85
+ - Replace spaces with dashes
86
+ - Remove invalid characters
87
+
88
+ Args:
89
+ name: The name to clean
90
+
91
+ Returns:
92
+ Cleaned name
93
+
94
+ Note:
95
+ This is a best-effort cleaning. The result should still be validated
96
+ with validate_name() before use.
97
+ """
98
+ # Convert to lowercase
99
+ cleaned = name.lower()
100
+
101
+ # Replace spaces with dashes
102
+ cleaned = cleaned.replace(' ', '-')
103
+
104
+ # Remove invalid characters
105
+ cleaned = re.sub(r'[^a-z0-9\-._]', '', cleaned)
106
+
107
+ # Remove consecutive special characters
108
+ cleaned = re.sub(r'[-._]{2,}', '-', cleaned)
109
+
110
+ # Remove leading/trailing special characters
111
+ cleaned = cleaned.strip('-._')
112
+
113
+ return cleaned
114
+
115
+
116
+ def is_valid_name(name: str) -> bool:
117
+ """Check if a name is valid without raising an exception.
118
+
119
+ Args:
120
+ name: The name to check
121
+
122
+ Returns:
123
+ True if valid, False otherwise
124
+ """
125
+ try:
126
+ validate_name(name)
127
+ return True
128
+ except InvalidNameError:
129
+ return False
@@ -1,5 +0,0 @@
1
- """CinchDB API package."""
2
-
3
- from cinchdb.api.app import app
4
-
5
- __all__ = ["app"]