cinchdb 0.1.1__tar.gz → 0.1.3__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.1 → cinchdb-0.1.3}/.gitignore +3 -1
  2. {cinchdb-0.1.1 → cinchdb-0.1.3}/PKG-INFO +16 -18
  3. {cinchdb-0.1.1 → cinchdb-0.1.3}/README.md +15 -14
  4. {cinchdb-0.1.1 → cinchdb-0.1.3}/pyproject.toml +1 -5
  5. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/utils/name_validator.py +6 -6
  6. cinchdb-0.1.1/src/cinchdb/api/__init__.py +0 -5
  7. cinchdb-0.1.1/src/cinchdb/api/app.py +0 -76
  8. cinchdb-0.1.1/src/cinchdb/api/auth.py +0 -290
  9. cinchdb-0.1.1/src/cinchdb/api/main.py +0 -137
  10. cinchdb-0.1.1/src/cinchdb/api/routers/__init__.py +0 -25
  11. cinchdb-0.1.1/src/cinchdb/api/routers/auth.py +0 -135
  12. cinchdb-0.1.1/src/cinchdb/api/routers/branches.py +0 -368
  13. cinchdb-0.1.1/src/cinchdb/api/routers/codegen.py +0 -164
  14. cinchdb-0.1.1/src/cinchdb/api/routers/columns.py +0 -290
  15. cinchdb-0.1.1/src/cinchdb/api/routers/data.py +0 -479
  16. cinchdb-0.1.1/src/cinchdb/api/routers/databases.py +0 -184
  17. cinchdb-0.1.1/src/cinchdb/api/routers/projects.py +0 -133
  18. cinchdb-0.1.1/src/cinchdb/api/routers/query.py +0 -156
  19. cinchdb-0.1.1/src/cinchdb/api/routers/tables.py +0 -349
  20. cinchdb-0.1.1/src/cinchdb/api/routers/tenants.py +0 -216
  21. cinchdb-0.1.1/src/cinchdb/api/routers/views.py +0 -219
  22. {cinchdb-0.1.1 → cinchdb-0.1.3}/LICENSE +0 -0
  23. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/__init__.py +0 -0
  24. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/__main__.py +0 -0
  25. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/__init__.py +0 -0
  26. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/__init__.py +0 -0
  27. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/branch.py +0 -0
  28. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/codegen.py +0 -0
  29. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/column.py +0 -0
  30. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/database.py +0 -0
  31. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/query.py +0 -0
  32. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/remote.py +0 -0
  33. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/table.py +0 -0
  34. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/tenant.py +0 -0
  35. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/commands/view.py +0 -0
  36. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/handlers/__init__.py +0 -0
  37. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/handlers/codegen_handler.py +0 -0
  38. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/main.py +0 -0
  39. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/cli/utils.py +0 -0
  40. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/config.py +0 -0
  41. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/core/__init__.py +0 -0
  42. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/core/connection.py +0 -0
  43. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/core/database.py +0 -0
  44. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/core/maintenance.py +0 -0
  45. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/core/path_utils.py +0 -0
  46. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/__init__.py +0 -0
  47. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/branch.py +0 -0
  48. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/change_applier.py +0 -0
  49. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/change_comparator.py +0 -0
  50. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/change_tracker.py +0 -0
  51. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/codegen.py +0 -0
  52. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/column.py +0 -0
  53. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/data.py +0 -0
  54. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/merge_manager.py +0 -0
  55. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/query.py +0 -0
  56. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/table.py +0 -0
  57. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/tenant.py +0 -0
  58. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/managers/view.py +0 -0
  59. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/models/__init__.py +0 -0
  60. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/models/base.py +0 -0
  61. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/models/branch.py +0 -0
  62. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/models/change.py +0 -0
  63. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/models/database.py +0 -0
  64. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/models/project.py +0 -0
  65. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/models/table.py +0 -0
  66. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/models/tenant.py +0 -0
  67. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/models/view.py +0 -0
  68. {cinchdb-0.1.1 → cinchdb-0.1.3}/src/cinchdb/utils/__init__.py +0 -0
  69. {cinchdb-0.1.1 → cinchdb-0.1.3}/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.1
3
+ Version: 0.1.3
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
@@ -38,7 +35,7 @@ CinchDB is for projects that need fast queries, data isolated data per-tenant [o
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 make changes - I can just store an API key in my local projects and hit the remote instance.
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
@@ -8,7 +8,7 @@ CinchDB is for projects that need fast queries, data isolated data per-tenant [o
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 make changes - I can just store an API key in my local projects and hit the remote instance.
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.1"
3
+ version = "0.1.3"
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"]
@@ -9,7 +9,7 @@ from typing import Optional
9
9
 
10
10
 
11
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]$')
12
+ VALID_NAME_PATTERN = re.compile(r'^[a-z0-9][a-z0-9\-_\.]*[a-z0-9]$|^[a-z0-9]$')
13
13
 
14
14
  # Reserved names that cannot be used
15
15
  RESERVED_NAMES = {'con', 'prn', 'aux', 'nul', 'com1', 'com2', 'com3', 'com4',
@@ -26,7 +26,7 @@ def validate_name(name: str, entity_type: str = "entity") -> None:
26
26
  """Validate that a name meets CinchDB naming requirements.
27
27
 
28
28
  Valid names must:
29
- - Contain only lowercase letters (a-z), numbers (0-9), dash (-), underscore (_), and period (.)
29
+ - Contain only lowercase letters (a-z), numbers (0-9), dash (-), and underscore (_)
30
30
  - Start and end with alphanumeric characters
31
31
  - Be at least 1 character long
32
32
  - Not exceed 255 characters (filesystem limit)
@@ -64,7 +64,7 @@ def validate_name(name: str, entity_type: str = "entity") -> None:
64
64
  )
65
65
 
66
66
  # Check for consecutive special characters
67
- if '..' in name or '--' in name or '__' in name or '.-' in name or '-.' in name:
67
+ if '--' in name or '__' in name or '-_' in name or '_-' in name or '..' in name or '.-' in name or '-.' in name or '._' in name or '_.' in name:
68
68
  raise InvalidNameError(
69
69
  f"Invalid {entity_type} name '{name}'. "
70
70
  f"Names cannot contain consecutive special characters."
@@ -102,13 +102,13 @@ def clean_name(name: str) -> str:
102
102
  cleaned = cleaned.replace(' ', '-')
103
103
 
104
104
  # Remove invalid characters
105
- cleaned = re.sub(r'[^a-z0-9\-._]', '', cleaned)
105
+ cleaned = re.sub(r'[^a-z0-9\-_\.]', '', cleaned)
106
106
 
107
107
  # Remove consecutive special characters
108
- cleaned = re.sub(r'[-._]{2,}', '-', cleaned)
108
+ cleaned = re.sub(r'[-_\.]{2,}', '-', cleaned)
109
109
 
110
110
  # Remove leading/trailing special characters
111
- cleaned = cleaned.strip('-._')
111
+ cleaned = cleaned.strip('-_.')
112
112
 
113
113
  return cleaned
114
114
 
@@ -1,5 +0,0 @@
1
- """CinchDB API package."""
2
-
3
- from cinchdb.api.app import app
4
-
5
- __all__ = ["app"]
@@ -1,76 +0,0 @@
1
- """FastAPI application for CinchDB."""
2
-
3
- from fastapi import FastAPI
4
- from fastapi.middleware.cors import CORSMiddleware
5
- from contextlib import asynccontextmanager
6
-
7
- from cinchdb import __version__
8
- from cinchdb.api.routers import (
9
- auth,
10
- projects,
11
- databases,
12
- branches,
13
- tenants,
14
- tables,
15
- columns,
16
- views,
17
- query,
18
- data,
19
- codegen,
20
- )
21
-
22
-
23
- @asynccontextmanager
24
- async def lifespan(app: FastAPI):
25
- """Application lifespan events."""
26
- # Startup
27
- print(f"Starting CinchDB API v{__version__}")
28
- yield
29
- # Shutdown
30
- print("Shutting down CinchDB API")
31
-
32
-
33
- # Create FastAPI app
34
- app = FastAPI(
35
- title="CinchDB API",
36
- description="Git-like SQLite database management system",
37
- version=__version__,
38
- lifespan=lifespan,
39
- docs_url="/docs",
40
- redoc_url="/redoc",
41
- openapi_url="/openapi.json",
42
- )
43
-
44
- # Configure CORS
45
- app.add_middleware(
46
- CORSMiddleware,
47
- allow_origins=["*"], # Configure appropriately for production
48
- allow_credentials=True,
49
- allow_methods=["*"],
50
- allow_headers=["*"],
51
- )
52
-
53
- # Include routers
54
- app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
55
- app.include_router(projects.router, prefix="/api/v1/projects", tags=["projects"])
56
- app.include_router(databases.router, prefix="/api/v1/databases", tags=["databases"])
57
- app.include_router(branches.router, prefix="/api/v1/branches", tags=["branches"])
58
- app.include_router(tenants.router, prefix="/api/v1/tenants", tags=["tenants"])
59
- app.include_router(tables.router, prefix="/api/v1/tables", tags=["tables"])
60
- app.include_router(columns.router, prefix="/api/v1/columns", tags=["columns"])
61
- app.include_router(views.router, prefix="/api/v1/views", tags=["views"])
62
- app.include_router(query.router, prefix="/api/v1/query", tags=["query"])
63
- app.include_router(data.router, prefix="/api/v1/tables", tags=["data"])
64
- app.include_router(codegen.router, prefix="/api/v1/codegen", tags=["codegen"])
65
-
66
-
67
- @app.get("/")
68
- async def root():
69
- """Root endpoint."""
70
- return {"name": "CinchDB API", "version": __version__, "status": "running"}
71
-
72
-
73
- @app.get("/health")
74
- async def health_check():
75
- """Health check endpoint."""
76
- return {"status": "healthy"}
@@ -1,290 +0,0 @@
1
- """Authentication and API key management for CinchDB API."""
2
-
3
- import uuid
4
- from datetime import datetime, timezone
5
- from typing import Optional, List, Literal
6
- from pathlib import Path
7
-
8
- from fastapi import HTTPException, Security, Depends, Query
9
- from fastapi.security import APIKeyHeader
10
- from pydantic import BaseModel, Field
11
-
12
- from cinchdb.config import Config
13
- from cinchdb.core.path_utils import get_project_root
14
-
15
-
16
- # API Key header
17
- api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
18
-
19
-
20
- class APIKey(BaseModel):
21
- """API key model."""
22
-
23
- key: str = Field(default_factory=lambda: str(uuid.uuid4()))
24
- name: str
25
- created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
26
- permissions: Literal["read", "write"] = "read"
27
- branches: Optional[List[str]] = None # None means all branches
28
- active: bool = True
29
-
30
-
31
- class APIKeyManager:
32
- """Manages API keys in the project configuration."""
33
-
34
- def __init__(self, project_dir: Optional[Path] = None):
35
- """Initialize API key manager.
36
-
37
- Args:
38
- project_dir: Project directory path
39
- """
40
- self.project_dir = project_dir or Path.cwd()
41
- self.config = Config(self.project_dir)
42
-
43
- def create_key(
44
- self,
45
- name: str,
46
- permissions: Literal["read", "write"] = "read",
47
- branches: Optional[List[str]] = None,
48
- ) -> APIKey:
49
- """Create a new API key.
50
-
51
- Args:
52
- name: Name for the API key
53
- permissions: Permission level (read or write)
54
- branches: List of allowed branches (None for all)
55
-
56
- Returns:
57
- Created API key
58
- """
59
- api_key = APIKey(name=name, permissions=permissions, branches=branches)
60
-
61
- # Load config and add key
62
- config_data = self.config.load()
63
- if not config_data.api_keys:
64
- config_data.api_keys = {}
65
-
66
- config_data.api_keys[api_key.key] = {
67
- "name": api_key.name,
68
- "created_at": api_key.created_at.isoformat(),
69
- "permissions": api_key.permissions,
70
- "branches": api_key.branches,
71
- "active": api_key.active,
72
- }
73
-
74
- self.config.save(config_data)
75
- return api_key
76
-
77
- def get_key(self, key: str) -> Optional[APIKey]:
78
- """Get an API key by its value.
79
-
80
- Args:
81
- key: API key string
82
-
83
- Returns:
84
- API key if found and active, None otherwise
85
- """
86
- config_data = self.config.load()
87
- if not config_data.api_keys or key not in config_data.api_keys:
88
- return None
89
-
90
- key_data = config_data.api_keys[key]
91
- if not key_data.get("active", True):
92
- return None
93
-
94
- return APIKey(
95
- key=key,
96
- name=key_data["name"],
97
- created_at=datetime.fromisoformat(key_data["created_at"]),
98
- permissions=key_data.get("permissions", "read"),
99
- branches=key_data.get("branches"),
100
- active=key_data.get("active", True),
101
- )
102
-
103
- def list_keys(self) -> List[APIKey]:
104
- """List all API keys.
105
-
106
- Returns:
107
- List of API keys
108
- """
109
- config_data = self.config.load()
110
- if not config_data.api_keys:
111
- return []
112
-
113
- keys = []
114
- for key, key_data in config_data.api_keys.items():
115
- keys.append(
116
- APIKey(
117
- key=key,
118
- name=key_data["name"],
119
- created_at=datetime.fromisoformat(key_data["created_at"]),
120
- permissions=key_data.get("permissions", "read"),
121
- branches=key_data.get("branches"),
122
- active=key_data.get("active", True),
123
- )
124
- )
125
-
126
- return keys
127
-
128
- def revoke_key(self, key: str) -> bool:
129
- """Revoke an API key.
130
-
131
- Args:
132
- key: API key to revoke
133
-
134
- Returns:
135
- True if revoked, False if not found
136
- """
137
- config_data = self.config.load()
138
- if not config_data.api_keys or key not in config_data.api_keys:
139
- return False
140
-
141
- config_data.api_keys[key]["active"] = False
142
- self.config.save(config_data)
143
- return True
144
-
145
- def delete_key(self, key: str) -> bool:
146
- """Delete an API key.
147
-
148
- Args:
149
- key: API key to delete
150
-
151
- Returns:
152
- True if deleted, False if not found
153
- """
154
- config_data = self.config.load()
155
- if not config_data.api_keys or key not in config_data.api_keys:
156
- return False
157
-
158
- del config_data.api_keys[key]
159
- self.config.save(config_data)
160
- return True
161
-
162
-
163
- class AuthContext(BaseModel):
164
- """Authentication context for requests."""
165
-
166
- api_key: APIKey
167
- project_dir: Path
168
-
169
-
170
- async def get_current_project(project_dir: Optional[str] = None) -> Path:
171
- """Get the current project directory.
172
-
173
- Args:
174
- project_dir: Optional project directory path
175
-
176
- Returns:
177
- Project directory path
178
-
179
- Raises:
180
- HTTPException: If project not found
181
- """
182
- if project_dir:
183
- path = Path(project_dir)
184
- if not path.exists() or not (path / ".cinchdb").exists():
185
- raise HTTPException(status_code=404, detail="Project not found")
186
- return path
187
-
188
- # Try to find project from current directory
189
- project_root = get_project_root(Path.cwd())
190
- if not project_root:
191
- raise HTTPException(
192
- status_code=404,
193
- detail="No CinchDB project found. Specify project_dir parameter.",
194
- )
195
-
196
- return project_root
197
-
198
-
199
- async def verify_api_key(
200
- api_key_header: Optional[str] = Security(api_key_header),
201
- api_key_query: Optional[str] = Query(None, alias="api_key"),
202
- project_dir: Path = Depends(get_current_project),
203
- ) -> AuthContext:
204
- """Verify API key and return auth context.
205
-
206
- Args:
207
- api_key_header: API key from header
208
- api_key_query: API key from query parameter
209
- project_dir: Project directory
210
-
211
- Returns:
212
- Authentication context
213
-
214
- Raises:
215
- HTTPException: If authentication fails
216
- """
217
- # Check header first, then query parameter
218
- api_key = api_key_header or api_key_query
219
-
220
- if not api_key:
221
- raise HTTPException(
222
- status_code=401,
223
- detail="API key required",
224
- headers={"WWW-Authenticate": "ApiKey"},
225
- )
226
-
227
- manager = APIKeyManager(project_dir)
228
- key_obj = manager.get_key(api_key)
229
-
230
- if not key_obj:
231
- raise HTTPException(
232
- status_code=401,
233
- detail="Invalid API key",
234
- headers={"WWW-Authenticate": "ApiKey"},
235
- )
236
-
237
- return AuthContext(api_key=key_obj, project_dir=project_dir)
238
-
239
-
240
- async def require_write_permission(
241
- auth: AuthContext = Depends(verify_api_key), branch: Optional[str] = None
242
- ) -> AuthContext:
243
- """Require write permission for the request.
244
-
245
- Args:
246
- auth: Authentication context
247
- branch: Optional branch name to check
248
-
249
- Returns:
250
- Authentication context
251
-
252
- Raises:
253
- HTTPException: If permission denied
254
- """
255
- if auth.api_key.permissions != "write":
256
- raise HTTPException(status_code=403, detail="Write permission required")
257
-
258
- # Check branch-specific permissions
259
- if branch and auth.api_key.branches and branch not in auth.api_key.branches:
260
- raise HTTPException(
261
- status_code=403, detail=f"Access denied for branch '{branch}'"
262
- )
263
-
264
- return auth
265
-
266
-
267
- async def require_read_permission(
268
- auth: AuthContext = Depends(verify_api_key), branch: Optional[str] = None
269
- ) -> AuthContext:
270
- """Require read permission for the request.
271
-
272
- Args:
273
- auth: Authentication context
274
- branch: Optional branch name to check
275
-
276
- Returns:
277
- Authentication context
278
-
279
- Raises:
280
- HTTPException: If permission denied
281
- """
282
- # All valid API keys have at least read permission
283
-
284
- # Check branch-specific permissions
285
- if branch and auth.api_key.branches and branch not in auth.api_key.branches:
286
- raise HTTPException(
287
- status_code=403, detail=f"Access denied for branch '{branch}'"
288
- )
289
-
290
- return auth