Skip to content

Commit

Permalink
add 'exists=' flag to the 'create_table'
Browse files Browse the repository at this point in the history
Signed-off-by: Grant Ramsay <seapagan@gmail.com>
  • Loading branch information
seapagan committed Sep 27, 2024
1 parent cdd369e commit 4b8d9ae
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

**New Features**

- Add `reset=` to SqliterDB(), to drop all existing tables ([#33](https://github.com/seapagan/sqliter-py/pull/33)) by [seapagan](https://github.com/seapagan)
- Order by primary key if no field specified to `order()` ([#32](https://github.com/seapagan/sqliter-py/pull/32)) by [seapagan](https://github.com/seapagan)
- Add `drop_table` method ([#31](https://github.com/seapagan/sqliter-py/pull/31)) by [seapagan](https://github.com/seapagan)
- Add debug logging option ([#29](https://github.com/seapagan/sqliter-py/pull/29)) by [seapagan](https://github.com/seapagan)
- Create relevant database fields depending on the Model types ([#27](https://github.com/seapagan/sqliter-py/pull/27)) by [seapagan](https://github.com/seapagan)
Expand Down
10 changes: 10 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ db.create_table(User)
>
> The Table is created **regardless** of the `auto_commit` setting.
By default, if the table already exists, it will not be created again and no
error will be raised. If you want to raise an exception if the table already
exists, you can set `exists_ok=False`:

```python
db.create_table(User, exists_ok=False)
```

This will raise a `TableCreationError` if the table already exists.

### Dropping Tables

```python
Expand Down
13 changes: 11 additions & 2 deletions sqliter/sqliter.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,15 @@ def commit(self) -> None:
if self.conn:
self.conn.commit()

def create_table(self, model_class: type[BaseDBModel]) -> None:
def create_table(
self, model_class: type[BaseDBModel], *, exists_ok: bool = True
) -> None:
"""Create a table in the database based on the given model class.
Args:
model_class: The Pydantic model class representing the table.
exists_ok: If True, do not raise an error if the table already
exists. Default is True which is the original behavior.
Raises:
TableCreationError: If there's an error creating the table.
Expand Down Expand Up @@ -237,8 +241,13 @@ def create_table(self, model_class: type[BaseDBModel]) -> None:
sqlite_type = infer_sqlite_type(field_info.annotation)
fields.append(f"{field_name} {sqlite_type}")

if exists_ok:
create_str = "CREATE TABLE IF NOT EXISTS"
else:
create_str = "CREATE TABLE"

create_table_sql = f"""
CREATE TABLE IF NOT EXISTS {table_name} (
{create_str} {table_name} (
{", ".join(fields)}
)
"""
Expand Down
78 changes: 78 additions & 0 deletions tests/test_sqliter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,24 @@
from sqliter.exceptions import (
RecordFetchError,
RecordNotFoundError,
TableCreationError,
)
from sqliter.model import BaseDBModel
from tests.conftest import ComplexModel, DetailedPersonModel, ExampleModel


class ExistOkModel(BaseDBModel):
"""Just used to test table creation with an existing table."""

name: str
age: int

class Meta:
"""Meta class for the model."""

table_name = "exist_ok_table"


class TestSqliterDB:
"""Test class to test the SqliterDB class."""

Expand Down Expand Up @@ -793,3 +806,68 @@ class Meta:
db_reset.select(TestModel1).fetch_all()
with pytest.raises(RecordFetchError):
db_reset.select(TestModel2).fetch_all()

def test_create_table_exists_ok_true(self, db_mock) -> None:
"""Test creating a table with exists_ok=True (default behavior)."""
# First creation should succeed
db_mock.create_table(ExistOkModel)

# Second creation should not raise an error
try:
db_mock.create_table(ExistOkModel)
except TableCreationError as e:
pytest.fail(f"create_table raised {type(e).__name__} unexpectedly!")

def test_create_table_exists_ok_false(self, db_mock) -> None:
"""Test creating a table with exists_ok=False."""
# First creation should succeed
db_mock.create_table(ExistOkModel)

# Second creation should raise an error
with pytest.raises(TableCreationError):
db_mock.create_table(ExistOkModel, exists_ok=False)

def test_create_table_exists_ok_false_new_table(self) -> None:
"""Test creating a new table with exists_ok=False."""
# Create a new database connection
new_db = SqliterDB(":memory:")

# Define a new model class specifically for this test
class UniqueTestModel(BaseDBModel):
name: str
age: int

class Meta:
table_name = "unique_test_table"

# Creation of a new table should succeed with exists_ok=False
try:
new_db.create_table(UniqueTestModel, exists_ok=False)
except TableCreationError as e:
pytest.fail(f"create_table raised {type(e).__name__} unexpectedly!")

# Clean up
new_db.close()

def test_create_table_sql_generation(self, db_mock, mocker) -> None:
"""Test SQL generation for table creation based on exists_ok value."""
mock_cursor = mocker.MagicMock()
mocker.patch.object(
db_mock, "connect"
).return_value.__enter__.return_value.cursor.return_value = mock_cursor

# Test with exists_ok=True
db_mock.create_table(ExistOkModel, exists_ok=True)
mock_cursor.execute.assert_called()
sql = mock_cursor.execute.call_args[0][0]
assert "CREATE TABLE IF NOT EXISTS" in sql

# Reset the mock
mock_cursor.reset_mock()

# Test with exists_ok=False
db_mock.create_table(ExistOkModel, exists_ok=False)
mock_cursor.execute.assert_called()
sql = mock_cursor.execute.call_args[0][0]
assert "CREATE TABLE" in sql
assert "IF NOT EXISTS" not in sql

0 comments on commit 4b8d9ae

Please sign in to comment.