cinchdb 0.1.12__tar.gz → 0.1.13__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 (60) hide show
  1. {cinchdb-0.1.12 → cinchdb-0.1.13}/PKG-INFO +22 -1
  2. {cinchdb-0.1.12 → cinchdb-0.1.13}/README.md +19 -0
  3. {cinchdb-0.1.12 → cinchdb-0.1.13}/pyproject.toml +6 -1
  4. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/main.py +6 -1
  5. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/core/connection.py +19 -11
  6. cinchdb-0.1.13/src/cinchdb/security/__init__.py +1 -0
  7. cinchdb-0.1.13/src/cinchdb/security/encryption.py +108 -0
  8. {cinchdb-0.1.12 → cinchdb-0.1.13}/.gitignore +0 -0
  9. {cinchdb-0.1.12 → cinchdb-0.1.13}/LICENSE +0 -0
  10. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/__init__.py +0 -0
  11. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/__main__.py +0 -0
  12. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/__init__.py +0 -0
  13. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/__init__.py +0 -0
  14. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/branch.py +0 -0
  15. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/codegen.py +0 -0
  16. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/column.py +0 -0
  17. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/database.py +0 -0
  18. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/index.py +0 -0
  19. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/query.py +0 -0
  20. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/remote.py +0 -0
  21. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/table.py +0 -0
  22. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/tenant.py +0 -0
  23. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/commands/view.py +0 -0
  24. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/handlers/__init__.py +0 -0
  25. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/handlers/codegen_handler.py +0 -0
  26. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/cli/utils.py +0 -0
  27. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/config.py +0 -0
  28. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/core/__init__.py +0 -0
  29. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/core/database.py +0 -0
  30. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/core/initializer.py +0 -0
  31. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/core/maintenance.py +0 -0
  32. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/core/path_utils.py +0 -0
  33. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/infrastructure/metadata_connection_pool.py +0 -0
  34. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/infrastructure/metadata_db.py +0 -0
  35. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/__init__.py +0 -0
  36. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/branch.py +0 -0
  37. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/change_applier.py +0 -0
  38. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/change_comparator.py +0 -0
  39. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/change_tracker.py +0 -0
  40. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/codegen.py +0 -0
  41. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/column.py +0 -0
  42. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/data.py +0 -0
  43. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/index.py +0 -0
  44. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/merge_manager.py +0 -0
  45. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/query.py +0 -0
  46. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/table.py +0 -0
  47. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/tenant.py +0 -0
  48. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/managers/view.py +0 -0
  49. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/models/__init__.py +0 -0
  50. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/models/base.py +0 -0
  51. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/models/branch.py +0 -0
  52. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/models/change.py +0 -0
  53. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/models/database.py +0 -0
  54. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/models/project.py +0 -0
  55. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/models/table.py +0 -0
  56. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/models/tenant.py +0 -0
  57. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/models/view.py +0 -0
  58. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/utils/__init__.py +0 -0
  59. {cinchdb-0.1.12 → cinchdb-0.1.13}/src/cinchdb/utils/name_validator.py +0 -0
  60. {cinchdb-0.1.12 → cinchdb-0.1.13}/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.12
3
+ Version: 0.1.13
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
@@ -23,6 +23,8 @@ Requires-Dist: requests>=2.28.0
23
23
  Requires-Dist: rich>=13.0.0
24
24
  Requires-Dist: toml>=0.10.0
25
25
  Requires-Dist: typer>=0.9.0
26
+ Provides-Extra: encryption
27
+ Requires-Dist: pysqlcipher3>=1.2.0; extra == 'encryption'
26
28
  Description-Content-Type: text/markdown
27
29
 
28
30
  # CinchDB
@@ -154,6 +156,25 @@ db.update("posts", post_id, {"content": "Updated content"})
154
156
  - **Python SDK**: Core functionality for local development
155
157
  - **CLI**: Full-featured command-line interface
156
158
 
159
+ ## Security & Encryption
160
+
161
+ Optional transparent encryption for tenant databases:
162
+
163
+ ```bash
164
+ # Enable encryption
165
+ export CINCH_ENCRYPT_DATA=true
166
+ export CINCH_ENCRYPTION_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
167
+
168
+ # Install encryption library
169
+ pip install pysqlcipher3
170
+ ```
171
+
172
+ - **Tenant databases**: Encrypted with ChaCha20-Poly1305 (~2-5% overhead)
173
+ - **Metadata**: Unencrypted for operational simplicity
174
+ - **Integration**: Transparent - no code changes needed
175
+
176
+ Works without encryption libraries - gracefully falls back to standard SQLite.
177
+
157
178
  ## Development
158
179
 
159
180
  ```bash
@@ -127,6 +127,25 @@ db.update("posts", post_id, {"content": "Updated content"})
127
127
  - **Python SDK**: Core functionality for local development
128
128
  - **CLI**: Full-featured command-line interface
129
129
 
130
+ ## Security & Encryption
131
+
132
+ Optional transparent encryption for tenant databases:
133
+
134
+ ```bash
135
+ # Enable encryption
136
+ export CINCH_ENCRYPT_DATA=true
137
+ export CINCH_ENCRYPTION_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(32))')"
138
+
139
+ # Install encryption library
140
+ pip install pysqlcipher3
141
+ ```
142
+
143
+ - **Tenant databases**: Encrypted with ChaCha20-Poly1305 (~2-5% overhead)
144
+ - **Metadata**: Unencrypted for operational simplicity
145
+ - **Integration**: Transparent - no code changes needed
146
+
147
+ Works without encryption libraries - gracefully falls back to standard SQLite.
148
+
130
149
  ## Development
131
150
 
132
151
  ```bash
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cinchdb"
3
- version = "0.1.12"
3
+ version = "0.1.13"
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"
@@ -26,6 +26,11 @@ dependencies = [
26
26
  "requests>=2.28.0",
27
27
  ]
28
28
 
29
+ [project.optional-dependencies]
30
+ encryption = [
31
+ "pysqlcipher3>=1.2.0",
32
+ ]
33
+
29
34
  [project.urls]
30
35
  Homepage = "https://github.com/russellromney/cinchdb"
31
36
  Documentation = "https://russellromney.github.io/cinchdb"
@@ -18,7 +18,7 @@ from cinchdb.cli.commands import (
18
18
 
19
19
  app = typer.Typer(
20
20
  name="cinch",
21
- help="CinchDB - A Git-like SQLite database management system",
21
+ help="CinchDB - A Git-like SQLite database management system\n\nENCRYPTION: Set CINCH_ENCRYPT_DATA=true to enable tenant database encryption.",
22
22
  add_completion=False,
23
23
  invoke_without_command=True,
24
24
  )
@@ -28,6 +28,11 @@ app = typer.Typer(
28
28
  def main(ctx: typer.Context):
29
29
  """
30
30
  CinchDB - A Git-like SQLite database management system
31
+
32
+ ENCRYPTION:
33
+ Set CINCH_ENCRYPT_DATA=true to enable tenant database encryption.
34
+ Set CINCH_ENCRYPTION_KEY=your-key to provide encryption key.
35
+ Requires SQLite3MultipleCiphers for encryption support.
31
36
  """
32
37
  if ctx.invoked_subcommand is None:
33
38
  # No subcommand was invoked, show help
@@ -6,6 +6,8 @@ from typing import Optional, Dict, List
6
6
  from contextlib import contextmanager
7
7
  from datetime import datetime
8
8
 
9
+ from cinchdb.security.encryption import encryption
10
+
9
11
 
10
12
  # Custom datetime adapter and converter for SQLite
11
13
  def adapt_datetime(dt):
@@ -42,18 +44,24 @@ class DatabaseConnection:
42
44
  # Ensure directory exists
43
45
  self.path.parent.mkdir(parents=True, exist_ok=True)
44
46
 
45
- # Connect with row factory for dict-like access
46
- # detect_types=PARSE_DECLTYPES tells SQLite to use our registered converters
47
- self._conn = sqlite3.connect(
48
- str(self.path),
49
- detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES,
50
- )
47
+ # Use encryption if enabled, otherwise standard connection
48
+ if encryption.enabled:
49
+ self._conn = encryption.get_connection(self.path)
50
+ else:
51
+ # Connect with row factory for dict-like access
52
+ # detect_types=PARSE_DECLTYPES tells SQLite to use our registered converters
53
+ self._conn = sqlite3.connect(
54
+ str(self.path),
55
+ detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES,
56
+ )
57
+
58
+ # Configure WAL mode and settings (encryption module handles this if enabled)
59
+ self._conn.execute("PRAGMA journal_mode = WAL")
60
+ self._conn.execute("PRAGMA synchronous = NORMAL")
61
+ self._conn.execute("PRAGMA wal_autocheckpoint = 0")
62
+
63
+ # Always set row factory and foreign keys
51
64
  self._conn.row_factory = sqlite3.Row
52
-
53
- # Configure WAL mode and settings
54
- self._conn.execute("PRAGMA journal_mode = WAL")
55
- self._conn.execute("PRAGMA synchronous = NORMAL")
56
- self._conn.execute("PRAGMA wal_autocheckpoint = 0")
57
65
  self._conn.execute("PRAGMA foreign_keys = ON")
58
66
  self._conn.commit()
59
67
 
@@ -0,0 +1 @@
1
+ """Security module for CinchDB encryption."""
@@ -0,0 +1,108 @@
1
+ """Simple SQLite encryption using environment variables with KMS upgrade path."""
2
+
3
+ import os
4
+ import sqlite3
5
+ import logging
6
+ from pathlib import Path
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class SQLiteEncryption:
12
+ """Simple SQLite encryption with static keys and KMS upgrade path."""
13
+
14
+ def __init__(self):
15
+ # Encryption is optional for open source users
16
+ self.enabled = os.getenv("CINCH_ENCRYPT_DATA", "false").lower() == "true"
17
+ self._encryption_key = None
18
+
19
+ if self.enabled:
20
+ self._encryption_key = self._get_encryption_key()
21
+
22
+ def _get_encryption_key(self) -> str:
23
+ """Get encryption key with future KMS support."""
24
+
25
+ # Future KMS support - check for KMS configuration first
26
+ kms_provider = os.getenv("CINCH_KMS_PROVIDER")
27
+ if kms_provider:
28
+ # TODO: Implement KMS key retrieval
29
+ # return self._get_key_from_kms(kms_provider)
30
+ logger.warning("KMS provider configured but not implemented yet")
31
+
32
+ # Require explicit key
33
+ static_key = os.getenv("CINCH_ENCRYPTION_KEY")
34
+ if static_key:
35
+ return static_key
36
+
37
+ # Fail fast if no key provided
38
+ raise ValueError(
39
+ "CINCH_ENCRYPTION_KEY environment variable is required when CINCH_ENCRYPT_DATA=true. "
40
+ "Generate a key with: python -c \"import secrets; print(secrets.token_urlsafe(32))\""
41
+ )
42
+
43
+ def get_connection(self, db_path: Path) -> sqlite3.Connection:
44
+ """Get SQLite connection with optional encryption."""
45
+ # Connect with datetime parsing support
46
+ conn = sqlite3.connect(
47
+ str(db_path),
48
+ detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES,
49
+ )
50
+
51
+ if self.enabled and self._encryption_key:
52
+ try:
53
+ # Apply encryption key
54
+ conn.execute(f"PRAGMA key = '{self._encryption_key}'")
55
+
56
+ # Configure recommended cipher (ChaCha20-Poly1305) if supported
57
+ try:
58
+ conn.execute("PRAGMA cipher = 'chacha20'")
59
+ except sqlite3.OperationalError:
60
+ # Fallback to default cipher if ChaCha20 not available
61
+ logger.debug("ChaCha20 cipher not available, using default")
62
+
63
+ # Security hardening
64
+ conn.execute("PRAGMA temp_store = MEMORY") # Encrypt temp data
65
+ conn.execute("PRAGMA secure_delete = ON") # Overwrite deleted data
66
+
67
+ except sqlite3.OperationalError as e:
68
+ logger.error(f"Failed to apply encryption to {db_path}: {e}")
69
+ # Close connection and re-raise with helpful message
70
+ conn.close()
71
+ raise sqlite3.OperationalError(
72
+ f"Failed to apply encryption. Make sure SQLite3MultipleCiphers is installed. Error: {e}"
73
+ ) from e
74
+
75
+ # Standard SQLite optimizations
76
+ conn.execute("PRAGMA journal_mode = WAL")
77
+ conn.execute("PRAGMA synchronous = NORMAL")
78
+ conn.execute("PRAGMA cache_size = -2000")
79
+
80
+ return conn
81
+
82
+ def is_encrypted(self, db_path: Path) -> bool:
83
+ """Check if database file is encrypted."""
84
+ if not db_path.exists():
85
+ return False
86
+
87
+ try:
88
+ # Try to open without key
89
+ test_conn = sqlite3.connect(str(db_path))
90
+ test_conn.execute("SELECT name FROM sqlite_master LIMIT 1")
91
+ test_conn.close()
92
+ return False # Successfully opened = not encrypted
93
+ except sqlite3.DatabaseError:
94
+ return True # Failed to open = likely encrypted
95
+
96
+ def test_encryption_support(self) -> bool:
97
+ """Test if SQLite encryption is available."""
98
+ try:
99
+ conn = sqlite3.connect(':memory:')
100
+ conn.execute("PRAGMA key = 'test'")
101
+ conn.close()
102
+ return True
103
+ except sqlite3.OperationalError:
104
+ return False
105
+
106
+
107
+ # Global instance for easy access
108
+ encryption = SQLiteEncryption()
File without changes
File without changes
File without changes