cinchdb 0.1.0__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/__init__.py +7 -0
- cinchdb/__main__.py +6 -0
- cinchdb/api/__init__.py +5 -0
- cinchdb/api/app.py +76 -0
- cinchdb/api/auth.py +290 -0
- cinchdb/api/main.py +137 -0
- cinchdb/api/routers/__init__.py +25 -0
- cinchdb/api/routers/auth.py +135 -0
- cinchdb/api/routers/branches.py +368 -0
- cinchdb/api/routers/codegen.py +164 -0
- cinchdb/api/routers/columns.py +290 -0
- cinchdb/api/routers/data.py +479 -0
- cinchdb/api/routers/databases.py +177 -0
- cinchdb/api/routers/projects.py +133 -0
- cinchdb/api/routers/query.py +156 -0
- cinchdb/api/routers/tables.py +349 -0
- cinchdb/api/routers/tenants.py +216 -0
- cinchdb/api/routers/views.py +219 -0
- cinchdb/cli/__init__.py +0 -0
- cinchdb/cli/commands/__init__.py +1 -0
- cinchdb/cli/commands/branch.py +479 -0
- cinchdb/cli/commands/codegen.py +176 -0
- cinchdb/cli/commands/column.py +308 -0
- cinchdb/cli/commands/database.py +212 -0
- cinchdb/cli/commands/query.py +136 -0
- cinchdb/cli/commands/remote.py +144 -0
- cinchdb/cli/commands/table.py +289 -0
- cinchdb/cli/commands/tenant.py +173 -0
- cinchdb/cli/commands/view.py +189 -0
- cinchdb/cli/handlers/__init__.py +5 -0
- cinchdb/cli/handlers/codegen_handler.py +189 -0
- cinchdb/cli/main.py +137 -0
- cinchdb/cli/utils.py +182 -0
- cinchdb/config.py +177 -0
- cinchdb/core/__init__.py +5 -0
- cinchdb/core/connection.py +175 -0
- cinchdb/core/database.py +537 -0
- cinchdb/core/maintenance.py +73 -0
- cinchdb/core/path_utils.py +153 -0
- cinchdb/managers/__init__.py +26 -0
- cinchdb/managers/branch.py +167 -0
- cinchdb/managers/change_applier.py +414 -0
- cinchdb/managers/change_comparator.py +194 -0
- cinchdb/managers/change_tracker.py +182 -0
- cinchdb/managers/codegen.py +523 -0
- cinchdb/managers/column.py +579 -0
- cinchdb/managers/data.py +455 -0
- cinchdb/managers/merge_manager.py +429 -0
- cinchdb/managers/query.py +214 -0
- cinchdb/managers/table.py +383 -0
- cinchdb/managers/tenant.py +258 -0
- cinchdb/managers/view.py +252 -0
- cinchdb/models/__init__.py +27 -0
- cinchdb/models/base.py +44 -0
- cinchdb/models/branch.py +26 -0
- cinchdb/models/change.py +47 -0
- cinchdb/models/database.py +20 -0
- cinchdb/models/project.py +20 -0
- cinchdb/models/table.py +86 -0
- cinchdb/models/tenant.py +19 -0
- cinchdb/models/view.py +15 -0
- cinchdb/utils/__init__.py +15 -0
- cinchdb/utils/sql_validator.py +137 -0
- cinchdb-0.1.0.dist-info/METADATA +195 -0
- cinchdb-0.1.0.dist-info/RECORD +68 -0
- cinchdb-0.1.0.dist-info/WHEEL +4 -0
- cinchdb-0.1.0.dist-info/entry_points.txt +3 -0
- cinchdb-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,153 @@
|
|
1
|
+
"""Path utilities for CinchDB."""
|
2
|
+
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import List
|
5
|
+
|
6
|
+
|
7
|
+
def get_project_root(start_path: Path) -> Path:
|
8
|
+
"""Find the project root by looking for .cinchdb directory.
|
9
|
+
|
10
|
+
Args:
|
11
|
+
start_path: Path to start searching from
|
12
|
+
|
13
|
+
Returns:
|
14
|
+
Path to project root
|
15
|
+
|
16
|
+
Raises:
|
17
|
+
FileNotFoundError: If no project root found
|
18
|
+
"""
|
19
|
+
current = Path(start_path).resolve()
|
20
|
+
|
21
|
+
while current != current.parent:
|
22
|
+
if (current / ".cinchdb").exists():
|
23
|
+
return current
|
24
|
+
current = current.parent
|
25
|
+
|
26
|
+
raise FileNotFoundError(f"No CinchDB project found from {start_path}")
|
27
|
+
|
28
|
+
|
29
|
+
def get_database_path(project_root: Path, database: str) -> Path:
|
30
|
+
"""Get path to a database directory.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
project_root: Project root directory
|
34
|
+
database: Database name
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
Path to database directory
|
38
|
+
"""
|
39
|
+
return project_root / ".cinchdb" / "databases" / database
|
40
|
+
|
41
|
+
|
42
|
+
def get_branch_path(project_root: Path, database: str, branch: str) -> Path:
|
43
|
+
"""Get path to a branch directory.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
project_root: Project root directory
|
47
|
+
database: Database name
|
48
|
+
branch: Branch name
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Path to branch directory
|
52
|
+
"""
|
53
|
+
return get_database_path(project_root, database) / "branches" / branch
|
54
|
+
|
55
|
+
|
56
|
+
def get_tenant_path(
|
57
|
+
project_root: Path, database: str, branch: str, tenant: str
|
58
|
+
) -> Path:
|
59
|
+
"""Get path to tenant directory.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
project_root: Project root directory
|
63
|
+
database: Database name
|
64
|
+
branch: Branch name
|
65
|
+
tenant: Tenant name
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
Path to tenant directory
|
69
|
+
"""
|
70
|
+
return get_branch_path(project_root, database, branch) / "tenants"
|
71
|
+
|
72
|
+
|
73
|
+
def get_tenant_db_path(
|
74
|
+
project_root: Path, database: str, branch: str, tenant: str
|
75
|
+
) -> Path:
|
76
|
+
"""Get path to tenant database file.
|
77
|
+
|
78
|
+
Args:
|
79
|
+
project_root: Project root directory
|
80
|
+
database: Database name
|
81
|
+
branch: Branch name
|
82
|
+
tenant: Tenant name
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
Path to tenant database file
|
86
|
+
"""
|
87
|
+
return get_tenant_path(project_root, database, branch, tenant) / f"{tenant}.db"
|
88
|
+
|
89
|
+
|
90
|
+
def ensure_directory(path: Path) -> None:
|
91
|
+
"""Ensure a directory exists, creating it if necessary.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
path: Directory path to ensure exists
|
95
|
+
"""
|
96
|
+
path.mkdir(parents=True, exist_ok=True)
|
97
|
+
|
98
|
+
|
99
|
+
def list_databases(project_root: Path) -> List[str]:
|
100
|
+
"""List all databases in a project.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
project_root: Project root directory
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
List of database names
|
107
|
+
"""
|
108
|
+
db_dir = project_root / ".cinchdb" / "databases"
|
109
|
+
if not db_dir.exists():
|
110
|
+
return []
|
111
|
+
|
112
|
+
return sorted([d.name for d in db_dir.iterdir() if d.is_dir()])
|
113
|
+
|
114
|
+
|
115
|
+
def list_branches(project_root: Path, database: str) -> List[str]:
|
116
|
+
"""List all branches in a database.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
project_root: Project root directory
|
120
|
+
database: Database name
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
List of branch names
|
124
|
+
"""
|
125
|
+
branches_dir = get_database_path(project_root, database) / "branches"
|
126
|
+
if not branches_dir.exists():
|
127
|
+
return []
|
128
|
+
|
129
|
+
return sorted([b.name for b in branches_dir.iterdir() if b.is_dir()])
|
130
|
+
|
131
|
+
|
132
|
+
def list_tenants(project_root: Path, database: str, branch: str) -> List[str]:
|
133
|
+
"""List all tenants in a branch.
|
134
|
+
|
135
|
+
Args:
|
136
|
+
project_root: Project root directory
|
137
|
+
database: Database name
|
138
|
+
branch: Branch name
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
List of tenant names
|
142
|
+
"""
|
143
|
+
tenants_dir = get_branch_path(project_root, database, branch) / "tenants"
|
144
|
+
if not tenants_dir.exists():
|
145
|
+
return []
|
146
|
+
|
147
|
+
# Only list .db files, not WAL or SHM files
|
148
|
+
tenants = []
|
149
|
+
for f in tenants_dir.iterdir():
|
150
|
+
if f.is_file() and f.suffix == ".db":
|
151
|
+
tenants.append(f.stem)
|
152
|
+
|
153
|
+
return sorted(tenants)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"""CinchDB managers."""
|
2
|
+
|
3
|
+
from cinchdb.managers.branch import BranchManager
|
4
|
+
from cinchdb.managers.tenant import TenantManager
|
5
|
+
from cinchdb.managers.change_tracker import ChangeTracker
|
6
|
+
from cinchdb.managers.change_applier import ChangeApplier
|
7
|
+
from cinchdb.managers.change_comparator import ChangeComparator
|
8
|
+
from cinchdb.managers.merge_manager import MergeManager, MergeError
|
9
|
+
from cinchdb.managers.table import TableManager
|
10
|
+
from cinchdb.managers.column import ColumnManager
|
11
|
+
from cinchdb.managers.view import ViewModel
|
12
|
+
from cinchdb.managers.codegen import CodegenManager
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
"BranchManager",
|
16
|
+
"TenantManager",
|
17
|
+
"ChangeTracker",
|
18
|
+
"ChangeApplier",
|
19
|
+
"ChangeComparator",
|
20
|
+
"MergeManager",
|
21
|
+
"MergeError",
|
22
|
+
"TableManager",
|
23
|
+
"ColumnManager",
|
24
|
+
"ViewModel",
|
25
|
+
"CodegenManager",
|
26
|
+
]
|
@@ -0,0 +1,167 @@
|
|
1
|
+
"""Branch management for CinchDB."""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import shutil
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import List, Dict, Any
|
7
|
+
from datetime import datetime, timezone
|
8
|
+
|
9
|
+
from cinchdb.config import Config
|
10
|
+
from cinchdb.models import Branch
|
11
|
+
from cinchdb.core.path_utils import (
|
12
|
+
get_database_path,
|
13
|
+
get_branch_path,
|
14
|
+
list_branches,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
class BranchManager:
|
19
|
+
"""Manages branches within a database."""
|
20
|
+
|
21
|
+
def __init__(self, project_root: Path, database: str):
|
22
|
+
"""Initialize branch manager.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
project_root: Path to project root
|
26
|
+
database: Database name
|
27
|
+
"""
|
28
|
+
self.project_root = Path(project_root)
|
29
|
+
self.database = database
|
30
|
+
self.db_path = get_database_path(self.project_root, database)
|
31
|
+
|
32
|
+
def list_branches(self) -> List[Branch]:
|
33
|
+
"""List all branches in the database.
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
List of Branch objects
|
37
|
+
"""
|
38
|
+
branch_names = list_branches(self.project_root, self.database)
|
39
|
+
branches = []
|
40
|
+
|
41
|
+
for name in branch_names:
|
42
|
+
metadata = self.get_branch_metadata(name)
|
43
|
+
branch = Branch(
|
44
|
+
name=name,
|
45
|
+
database=self.database,
|
46
|
+
parent_branch=metadata.get("parent_branch"),
|
47
|
+
is_main=(name == "main"),
|
48
|
+
metadata=metadata,
|
49
|
+
)
|
50
|
+
branches.append(branch)
|
51
|
+
|
52
|
+
return branches
|
53
|
+
|
54
|
+
def create_branch(self, source_branch: str, new_branch_name: str) -> Branch:
|
55
|
+
"""Create a new branch from an existing branch.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
source_branch: Name of branch to copy from
|
59
|
+
new_branch_name: Name for the new branch
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
Created Branch object
|
63
|
+
|
64
|
+
Raises:
|
65
|
+
ValueError: If source doesn't exist or new branch already exists
|
66
|
+
"""
|
67
|
+
# Validate source branch exists
|
68
|
+
if source_branch not in list_branches(self.project_root, self.database):
|
69
|
+
raise ValueError(f"Source branch '{source_branch}' does not exist")
|
70
|
+
|
71
|
+
# Validate new branch doesn't exist
|
72
|
+
if new_branch_name in list_branches(self.project_root, self.database):
|
73
|
+
raise ValueError(f"Branch '{new_branch_name}' already exists")
|
74
|
+
|
75
|
+
# Get paths
|
76
|
+
source_path = get_branch_path(self.project_root, self.database, source_branch)
|
77
|
+
new_path = get_branch_path(self.project_root, self.database, new_branch_name)
|
78
|
+
|
79
|
+
# Copy entire branch directory
|
80
|
+
shutil.copytree(source_path, new_path)
|
81
|
+
|
82
|
+
# Update metadata for new branch
|
83
|
+
metadata = self.get_branch_metadata(new_branch_name)
|
84
|
+
metadata["parent_branch"] = source_branch
|
85
|
+
metadata["created_at"] = datetime.now(timezone.utc).isoformat()
|
86
|
+
self.update_branch_metadata(new_branch_name, metadata)
|
87
|
+
|
88
|
+
# New branch inherits all changes from source branch
|
89
|
+
# (changes.json is already copied by copytree, so nothing to do here)
|
90
|
+
|
91
|
+
return Branch(
|
92
|
+
name=new_branch_name,
|
93
|
+
database=self.database,
|
94
|
+
parent_branch=source_branch,
|
95
|
+
is_main=False,
|
96
|
+
metadata=metadata,
|
97
|
+
)
|
98
|
+
|
99
|
+
def delete_branch(self, branch_name: str) -> None:
|
100
|
+
"""Delete a branch.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
branch_name: Name of branch to delete
|
104
|
+
|
105
|
+
Raises:
|
106
|
+
ValueError: If branch doesn't exist or is main branch
|
107
|
+
"""
|
108
|
+
# Can't delete main branch
|
109
|
+
if branch_name == "main":
|
110
|
+
raise ValueError("Cannot delete the main branch")
|
111
|
+
|
112
|
+
# Validate branch exists
|
113
|
+
if branch_name not in list_branches(self.project_root, self.database):
|
114
|
+
raise ValueError(f"Branch '{branch_name}' does not exist")
|
115
|
+
|
116
|
+
# Delete branch directory
|
117
|
+
branch_path = get_branch_path(self.project_root, self.database, branch_name)
|
118
|
+
shutil.rmtree(branch_path)
|
119
|
+
|
120
|
+
|
121
|
+
def get_branch_metadata(self, branch_name: str) -> Dict[str, Any]:
|
122
|
+
"""Get metadata for a branch.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
branch_name: Branch name
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
Metadata dictionary
|
129
|
+
"""
|
130
|
+
metadata_path = (
|
131
|
+
get_branch_path(self.project_root, self.database, branch_name)
|
132
|
+
/ "metadata.json"
|
133
|
+
)
|
134
|
+
|
135
|
+
if metadata_path.exists():
|
136
|
+
with open(metadata_path, "r") as f:
|
137
|
+
return json.load(f)
|
138
|
+
|
139
|
+
return {}
|
140
|
+
|
141
|
+
def update_branch_metadata(
|
142
|
+
self, branch_name: str, metadata: Dict[str, Any]
|
143
|
+
) -> None:
|
144
|
+
"""Update metadata for a branch.
|
145
|
+
|
146
|
+
Args:
|
147
|
+
branch_name: Branch name
|
148
|
+
metadata: Metadata dictionary to save
|
149
|
+
"""
|
150
|
+
metadata_path = (
|
151
|
+
get_branch_path(self.project_root, self.database, branch_name)
|
152
|
+
/ "metadata.json"
|
153
|
+
)
|
154
|
+
|
155
|
+
with open(metadata_path, "w") as f:
|
156
|
+
json.dump(metadata, f, indent=2)
|
157
|
+
|
158
|
+
def branch_exists(self, branch_name: str) -> bool:
|
159
|
+
"""Check if a branch exists.
|
160
|
+
|
161
|
+
Args:
|
162
|
+
branch_name: Branch name to check
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
True if branch exists, False otherwise
|
166
|
+
"""
|
167
|
+
return branch_name in list_branches(self.project_root, self.database)
|