commitdb 2.2.0__tar.gz → 2.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commitdb
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Python driver for CommitDB SQL Server
5
5
  Author: CommitDB Contributors
6
6
  License: Apache-2.0
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
20
20
  Provides-Extra: dev
21
21
  Requires-Dist: pytest>=7.0; extra == "dev"
22
22
  Provides-Extra: ibis
23
- Requires-Dist: ibis-framework>=9.0; extra == "ibis"
23
+ Requires-Dist: ibis-framework>=11.0; extra == "ibis"
24
24
  Requires-Dist: pandas>=2.0; extra == "ibis"
25
25
 
26
26
  # CommitDB Python Driver
@@ -32,6 +32,8 @@ Python client for CommitDB - a Git-backed SQL database engine.
32
32
 
33
33
  **[📚 Full Documentation](https://nickyhof.github.io/CommitDB/python-driver/)**
34
34
 
35
+ > ⚠️ **Experimental Project** - This is a hobby project and should not be used in any production environment.
36
+
35
37
  ## Installation
36
38
 
37
39
  ```bash
@@ -7,6 +7,8 @@ Python client for CommitDB - a Git-backed SQL database engine.
7
7
 
8
8
  **[📚 Full Documentation](https://nickyhof.github.io/CommitDB/python-driver/)**
9
9
 
10
+ > ⚠️ **Experimental Project** - This is a hobby project and should not be used in any production environment.
11
+
10
12
  ## Installation
11
13
 
12
14
  ```bash
@@ -122,7 +122,8 @@ class Backend(SQLBackend):
122
122
  super().__init__()
123
123
  self._client: CommitDBClient | None = None
124
124
  self._current_database: str | None = None
125
- self._in_memory_tables: dict[str, Any] = {}
125
+ # Compile must be an instance, not a class
126
+ self.compiler = CommitDBCompiler()
126
127
 
127
128
  def _get_schema_using_query(self, query: str) -> sch.Schema:
128
129
  """Get schema by executing a query with LIMIT 1.
@@ -150,11 +151,10 @@ class Backend(SQLBackend):
150
151
  def _register_in_memory_table(self, op: Any) -> None:
151
152
  """Register a table for in-memory operations.
152
153
 
153
- CommitDB doesn't support in-memory tables, so we store them locally.
154
+ CommitDB doesn't support in-memory tables, so this is a no-op.
155
+ Required by SQLBackend abstract base class.
154
156
  """
155
- # Store the table data for later insertion
156
- name = op.name
157
- self._in_memory_tables[name] = op
157
+ pass
158
158
 
159
159
  @property
160
160
  def version(self) -> str:
@@ -299,11 +299,43 @@ class Backend(SQLBackend):
299
299
  @contextlib.contextmanager
300
300
  def _safe_raw_sql(self, query: str | sg.Expression, **kwargs):
301
301
  """Execute SQL and yield the result."""
302
- result = self.raw_sql(query, **kwargs)
303
- try:
304
- yield result
305
- finally:
306
- pass # No cursor to close
302
+ yield self.raw_sql(query, **kwargs)
303
+
304
+ def table(
305
+ self,
306
+ name: str,
307
+ database: str | None = None,
308
+ ) -> ir.Table:
309
+ """Get a reference to a table.
310
+
311
+ Handles CommitDB's database.table format by splitting the name.
312
+ """
313
+ # Handle database.table format
314
+ if "." in name and database is None:
315
+ database, name = name.split(".", 1)
316
+
317
+ return super().table(name, database=database)
318
+
319
+ def compile(
320
+ self,
321
+ expr: ir.Expr,
322
+ /,
323
+ *,
324
+ limit: str | int | None = None,
325
+ params: dict | None = None,
326
+ pretty: bool = False,
327
+ ) -> str:
328
+ """Compile an expression to a SQL string.
329
+
330
+ Overridden to remove quotes from identifiers since CommitDB
331
+ doesn't require quoted identifiers for simple names.
332
+ """
333
+ query = self.compiler.to_sqlglot(expr, limit=limit, params=params)
334
+ sql = query.sql(dialect=self.dialect, pretty=pretty, copy=False)
335
+ # Remove quotes from simple identifiers (CommitDB doesn't need them)
336
+ sql = sql.replace('"', '')
337
+ self._log(sql)
338
+ return sql
307
339
 
308
340
  def execute(
309
341
  self,
@@ -322,22 +354,8 @@ class Backend(SQLBackend):
322
354
  client = self._ensure_connected()
323
355
  result = client.query(sql)
324
356
 
325
- # Convert to DataFrame
326
- df = pd.DataFrame(result.data, columns=result.columns)
327
-
328
- # Apply type conversions if needed
329
- schema = expr.schema() if hasattr(expr, "schema") else None
330
- if schema:
331
- for col, dtype in schema.items():
332
- if col in df.columns:
333
- if isinstance(dtype, dt.Int64):
334
- df[col] = pd.to_numeric(df[col], errors="coerce").astype("Int64")
335
- elif isinstance(dtype, dt.Float64):
336
- df[col] = pd.to_numeric(df[col], errors="coerce")
337
- elif isinstance(dtype, dt.Boolean):
338
- df[col] = df[col].map({"true": True, "false": False, "1": True, "0": False})
339
-
340
- return df
357
+ # Convert to DataFrame - pandas infers types from the JSON response
358
+ return pd.DataFrame(result.data, columns=result.columns)
341
359
 
342
360
  def create_table(
343
361
  self,
@@ -432,25 +450,44 @@ class Backend(SQLBackend):
432
450
  def insert(
433
451
  self,
434
452
  table_name: str,
435
- obj: pd.DataFrame | ir.Table,
453
+ obj: pd.DataFrame,
436
454
  *,
437
455
  database: str | None = None,
438
456
  ) -> None:
439
- """Insert data into a table."""
440
- import pandas as pd
441
-
457
+ """Insert data from a pandas DataFrame into a table."""
442
458
  db = database or self._current_database
443
459
  if not db:
444
460
  raise CommitDBError("No database specified.")
445
461
 
446
462
  full_name = f"{db}.{table_name}"
463
+ self._insert_dataframe(full_name, obj)
464
+
465
+ def drop_table(
466
+ self,
467
+ name: str,
468
+ *,
469
+ database: str | None = None,
470
+ force: bool = False,
471
+ ) -> None:
472
+ """Drop a table.
447
473
 
448
- if isinstance(obj, pd.DataFrame):
449
- self._insert_dataframe(full_name, obj)
450
- else:
451
- # Ibis Table - execute and insert
452
- df = obj.execute()
453
- self._insert_dataframe(full_name, df)
474
+ Parameters
475
+ ----------
476
+ name
477
+ Table name to drop
478
+ database
479
+ Database containing the table
480
+ force
481
+ If True, use IF EXISTS to avoid error if table doesn't exist
482
+ """
483
+ client = self._ensure_connected()
484
+ db = database or self._current_database
485
+ if not db:
486
+ raise CommitDBError("No database specified.")
487
+
488
+ full_name = f"{db}.{name}"
489
+ if_exists = "IF EXISTS " if force else ""
490
+ client.execute(f"DROP TABLE {if_exists}{full_name}")
454
491
 
455
492
 
456
493
  def connect(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commitdb
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Python driver for CommitDB SQL Server
5
5
  Author: CommitDB Contributors
6
6
  License: Apache-2.0
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
20
20
  Provides-Extra: dev
21
21
  Requires-Dist: pytest>=7.0; extra == "dev"
22
22
  Provides-Extra: ibis
23
- Requires-Dist: ibis-framework>=9.0; extra == "ibis"
23
+ Requires-Dist: ibis-framework>=11.0; extra == "ibis"
24
24
  Requires-Dist: pandas>=2.0; extra == "ibis"
25
25
 
26
26
  # CommitDB Python Driver
@@ -32,6 +32,8 @@ Python client for CommitDB - a Git-backed SQL database engine.
32
32
 
33
33
  **[📚 Full Documentation](https://nickyhof.github.io/CommitDB/python-driver/)**
34
34
 
35
+ > ⚠️ **Experimental Project** - This is a hobby project and should not be used in any production environment.
36
+
35
37
  ## Installation
36
38
 
37
39
  ```bash
@@ -3,5 +3,5 @@
3
3
  pytest>=7.0
4
4
 
5
5
  [ibis]
6
- ibis-framework>=9.0
6
+ ibis-framework>=11.0
7
7
  pandas>=2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "commitdb"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  description = "Python driver for CommitDB SQL Server"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -36,7 +36,7 @@ commitdb = ["lib/*.so", "lib/*.dylib", "lib/*.dll"]
36
36
 
37
37
  [project.optional-dependencies]
38
38
  dev = ["pytest>=7.0"]
39
- ibis = ["ibis-framework>=9.0", "pandas>=2.0"]
39
+ ibis = ["ibis-framework>=11.0", "pandas>=2.0"]
40
40
 
41
41
  [project.entry-points."ibis.backends"]
42
42
  commitdb = "commitdb.ibis_backend"
@@ -0,0 +1,152 @@
1
+ """Tests for the Ibis backend."""
2
+
3
+ import pytest
4
+
5
+ # Skip all tests if ibis is not installed
6
+ ibis = pytest.importorskip("ibis")
7
+ pd = pytest.importorskip("pandas")
8
+
9
+
10
+ class TestIbisBackendUnit:
11
+ """Unit tests for ibis backend that don't require a server."""
12
+
13
+ def test_import_backend(self):
14
+ """Test that the backend can be imported."""
15
+ from commitdb import ibis_backend
16
+ assert hasattr(ibis_backend, "Backend")
17
+
18
+ def test_backend_registered(self):
19
+ """Test that the backend is registered via entry points."""
20
+ from importlib.metadata import entry_points
21
+
22
+ # Check entry points registration
23
+ eps = entry_points(group='ibis.backends')
24
+ names = [ep.name for ep in eps]
25
+ assert 'commitdb' in names
26
+
27
+ def test_type_mapping(self):
28
+ """Test CommitDB to Ibis type mapping."""
29
+ from commitdb.ibis_backend import COMMITDB_TYPE_MAP
30
+ import ibis.expr.datatypes as dt
31
+
32
+ assert COMMITDB_TYPE_MAP["INT"] == dt.Int64
33
+ assert COMMITDB_TYPE_MAP["STRING"] == dt.String
34
+ assert COMMITDB_TYPE_MAP["FLOAT"] == dt.Float64
35
+ assert COMMITDB_TYPE_MAP["BOOL"] == dt.Boolean
36
+
37
+ def test_backend_instantiation(self):
38
+ """Test that the backend can be instantiated."""
39
+ from commitdb.ibis_backend import Backend
40
+
41
+ backend = Backend()
42
+ assert backend.name == "commitdb"
43
+ assert backend._client is None
44
+
45
+
46
+ @pytest.mark.integration
47
+ class TestIbisBackendIntegration:
48
+ """Integration tests that require a running CommitDB server.
49
+
50
+ Run with: pytest -m integration tests/test_ibis.py
51
+ """
52
+
53
+ @pytest.fixture
54
+ def connection(self):
55
+ """Create a connection to the test server."""
56
+ from commitdb.ibis_backend import Backend
57
+
58
+ backend = Backend()
59
+ try:
60
+ backend.do_connect(host="localhost", port=3306, database="test")
61
+ yield backend
62
+ finally:
63
+ backend.disconnect()
64
+
65
+ def test_connect(self, connection):
66
+ """Test connecting to the server."""
67
+ assert connection._client is not None
68
+
69
+ def test_list_databases(self, connection):
70
+ """Test listing databases."""
71
+ databases = connection.list_databases()
72
+ assert isinstance(databases, list)
73
+
74
+ def test_query_to_dataframe(self, connection):
75
+ """Test that queries return pandas DataFrames."""
76
+ from commitdb.client import CommitDBError
77
+
78
+ # Setup: create database (ignore if exists)
79
+ try:
80
+ connection._client.execute("CREATE DATABASE ibis_test")
81
+ except CommitDBError:
82
+ pass # Already exists
83
+
84
+ connection._client.execute("DROP TABLE IF EXISTS ibis_test.users")
85
+ connection._client.execute(
86
+ "CREATE TABLE ibis_test.users (id INT PRIMARY KEY, name STRING, age INT)"
87
+ )
88
+ connection._client.execute(
89
+ "INSERT INTO ibis_test.users (id, name, age) VALUES (1, 'Alice', 30)"
90
+ )
91
+ connection._client.execute(
92
+ "INSERT INTO ibis_test.users (id, name, age) VALUES (2, 'Bob', 25)"
93
+ )
94
+
95
+ try:
96
+ # Query using Ibis
97
+ table = connection.table("ibis_test.users")
98
+ result = table.execute()
99
+
100
+ assert isinstance(result, pd.DataFrame)
101
+ assert len(result) == 2
102
+ assert "id" in result.columns
103
+ assert "name" in result.columns
104
+ assert "age" in result.columns
105
+ finally:
106
+ # Cleanup
107
+ connection._client.execute("DROP TABLE IF EXISTS ibis_test.users")
108
+ connection._client.execute("DROP DATABASE IF EXISTS ibis_test")
109
+
110
+ def test_drop_table(self, connection):
111
+ """Test dropping a table via the backend."""
112
+ from commitdb.client import CommitDBError
113
+
114
+ # Setup: create database (ignore if exists)
115
+ try:
116
+ connection._client.execute("CREATE DATABASE ibis_test")
117
+ except CommitDBError:
118
+ pass
119
+
120
+ connection._client.execute("DROP TABLE IF EXISTS ibis_test.temp_table")
121
+ connection._client.execute(
122
+ "CREATE TABLE ibis_test.temp_table (id INT PRIMARY KEY)"
123
+ )
124
+
125
+ try:
126
+ # Drop using the backend method
127
+ connection.drop_table("temp_table", database="ibis_test")
128
+
129
+ # Verify table is gone
130
+ tables = connection.list_tables(database="ibis_test")
131
+ assert "temp_table" not in tables
132
+ finally:
133
+ # Cleanup
134
+ connection._client.execute("DROP TABLE IF EXISTS ibis_test.temp_table")
135
+ connection._client.execute("DROP DATABASE IF EXISTS ibis_test")
136
+
137
+ def test_drop_table_force(self, connection):
138
+ """Test dropping a non-existent table with force=True."""
139
+ from commitdb.client import CommitDBError
140
+
141
+ # Setup: create database (ignore if exists)
142
+ try:
143
+ connection._client.execute("CREATE DATABASE ibis_test")
144
+ except CommitDBError:
145
+ pass
146
+
147
+ try:
148
+ # Should not raise an error with force=True
149
+ connection.drop_table("nonexistent_table", database="ibis_test", force=True)
150
+ finally:
151
+ connection._client.execute("DROP DATABASE IF EXISTS ibis_test")
152
+
@@ -1,88 +0,0 @@
1
- """Tests for the Ibis backend."""
2
-
3
- import pytest
4
-
5
- # Skip all tests if ibis is not installed
6
- ibis = pytest.importorskip("ibis")
7
- pd = pytest.importorskip("pandas")
8
-
9
-
10
- class TestIbisBackendUnit:
11
- """Unit tests for ibis backend that don't require a server."""
12
-
13
- def test_import_backend(self):
14
- """Test that the backend can be imported."""
15
- from commitdb import ibis_backend
16
- assert hasattr(ibis_backend, "Backend")
17
-
18
- def test_backend_registered(self):
19
- """Test that the backend is registered via entry points."""
20
- from importlib.metadata import entry_points
21
-
22
- # Check entry points registration
23
- eps = entry_points(group='ibis.backends')
24
- names = [ep.name for ep in eps]
25
- assert 'commitdb' in names
26
-
27
- def test_type_mapping(self):
28
- """Test CommitDB to Ibis type mapping."""
29
- from commitdb.ibis_backend import COMMITDB_TYPE_MAP
30
- import ibis.expr.datatypes as dt
31
-
32
- assert COMMITDB_TYPE_MAP["INT"] == dt.Int64
33
- assert COMMITDB_TYPE_MAP["STRING"] == dt.String
34
- assert COMMITDB_TYPE_MAP["FLOAT"] == dt.Float64
35
- assert COMMITDB_TYPE_MAP["BOOL"] == dt.Boolean
36
-
37
- def test_backend_instantiation(self):
38
- """Test that the backend can be instantiated."""
39
- from commitdb.ibis_backend import Backend
40
-
41
- backend = Backend()
42
- assert backend.name == "commitdb"
43
- assert backend._client is None
44
-
45
-
46
- @pytest.mark.integration
47
- class TestIbisBackendIntegration:
48
- """Integration tests that require a running CommitDB server.
49
-
50
- Run with: pytest -m integration tests/test_ibis.py
51
- """
52
-
53
- @pytest.fixture
54
- def connection(self):
55
- """Create a connection to the test server."""
56
- from commitdb.ibis_backend import Backend
57
-
58
- backend = Backend()
59
- try:
60
- backend.do_connect(host="localhost", port=3306, database="test")
61
- yield backend
62
- finally:
63
- backend.disconnect()
64
-
65
- def test_connect(self, connection):
66
- """Test connecting to the server."""
67
- assert connection._client is not None
68
-
69
- def test_list_databases(self, connection):
70
- """Test listing databases."""
71
- databases = connection.list_databases()
72
- assert isinstance(databases, list)
73
-
74
- def test_query_to_dataframe(self, connection):
75
- """Test that queries return pandas DataFrames."""
76
- # This requires a table to exist
77
- # Skipped if no tables exist
78
- databases = connection.list_databases()
79
- if not databases:
80
- pytest.skip("No databases available")
81
-
82
- tables = connection.list_tables(database=databases[0])
83
- if not tables:
84
- pytest.skip("No tables available")
85
-
86
- table = connection.table(f"{databases[0]}.{tables[0]}")
87
- result = table.limit(5).execute()
88
- assert isinstance(result, pd.DataFrame)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes