mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-07-23 17:34:32 +02:00
Polish: clearer errors, corrected docstrings, setuptools pin
- insert/upsert into a name that is actually a view now shows a clean error instead of a traceback - db.view() on a name that is a table now says so in its error - Database.__getitem__ docstring documents that views are returned for view names; hash_id docstring corrected (it is a column name, not a boolean) - Registering two migrations with the same name in one set now raises ValueError at registration time instead of executing both and failing with IntegrityError at apply time - build-system requires setuptools>=77, needed for the PEP 639 license expression Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UnLnhsH25Nnv7LHhekUfPd
This commit is contained in:
parent
30cc95c0a6
commit
397cdcc491
7 changed files with 58 additions and 7 deletions
|
|
@ -72,7 +72,8 @@ CI = "https://github.com/simonw/sqlite-utils/actions"
|
|||
sqlite-utils = "sqlite_utils.cli:cli"
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools"]
|
||||
# setuptools 77+ is needed for the PEP 639 license = "Apache-2.0" expression
|
||||
requires = ["setuptools>=77"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.flake8]
|
||||
|
|
|
|||
|
|
@ -1155,6 +1155,8 @@ def insert_upsert_implementation(
|
|||
db.table(table).insert_all(
|
||||
docs, pk=pk, batch_size=batch_size, alter=alter, **extra_kwargs
|
||||
)
|
||||
except NoTable as e:
|
||||
raise click.ClickException(str(e))
|
||||
except Exception as e:
|
||||
if (
|
||||
isinstance(e, OperationalError)
|
||||
|
|
|
|||
|
|
@ -499,10 +499,12 @@ class Database:
|
|||
|
||||
def __getitem__(self, table_name: str) -> Union["Table", "View"]:
|
||||
"""
|
||||
``db[table_name]`` returns a :class:`.Table` object for the table with the specified name.
|
||||
If the table does not exist yet it will be created the first time data is inserted into it.
|
||||
``db[name]`` returns a :class:`.Table` object for the table with the specified name,
|
||||
or a :class:`.View` object if the name matches an existing SQL view.
|
||||
If neither exists yet, a table is assumed - it will be created the first
|
||||
time data is inserted into it.
|
||||
|
||||
:param table_name: The name of the table
|
||||
:param table_name: The name of the table or view
|
||||
"""
|
||||
if table_name in self.view_names():
|
||||
return self.view(table_name)
|
||||
|
|
@ -672,6 +674,12 @@ class Database:
|
|||
:param view_name: Name of the view
|
||||
"""
|
||||
if view_name not in self.view_names():
|
||||
if view_name in self.table_names():
|
||||
raise NoView(
|
||||
"View {name} does not exist - {name} is a table".format(
|
||||
name=view_name
|
||||
)
|
||||
)
|
||||
raise NoView("View {} does not exist".format(view_name))
|
||||
return View(self, view_name)
|
||||
|
||||
|
|
@ -1618,7 +1626,8 @@ class Table(Queryable):
|
|||
:param not_null: List of columns that cannot be null
|
||||
:param defaults: Dictionary of column names and default values
|
||||
:param batch_size: Integer number of rows to insert at a time
|
||||
:param hash_id: If True, use a hash of the row values as the primary key
|
||||
:param hash_id: Name of a column to create and use as a primary key, where the
|
||||
value of that primary key is derived from a hash of the row values
|
||||
:param hash_id_columns: List of columns to use for the hash_id
|
||||
:param alter: If True, automatically alter the table if it doesn't match the schema
|
||||
:param ignore: If True, ignore rows that already exist when inserting
|
||||
|
|
|
|||
|
|
@ -44,8 +44,15 @@ class Migrations:
|
|||
"""
|
||||
|
||||
def inner(func: Callable) -> Callable:
|
||||
migration_name = name or getattr(func, "__name__")
|
||||
if any(m.name == migration_name for m in self._migrations):
|
||||
raise ValueError(
|
||||
"Migration '{}' is already registered in set '{}'".format(
|
||||
migration_name, self.name
|
||||
)
|
||||
)
|
||||
self._migrations.append(
|
||||
self._Migration(name or getattr(func, "__name__"), func, transactional)
|
||||
self._Migration(migration_name, func, transactional)
|
||||
)
|
||||
return func
|
||||
|
||||
|
|
|
|||
|
|
@ -615,3 +615,16 @@ def test_insert_csv_headers_only(tmpdir):
|
|||
# Table should not exist since there were no data rows
|
||||
db = Database(db_path)
|
||||
assert not db["data"].exists()
|
||||
|
||||
|
||||
def test_insert_into_view_errors(tmpdir):
|
||||
db_path = str(tmpdir / "test.db")
|
||||
db = Database(db_path)
|
||||
db["t"].insert({"id": 1})
|
||||
db.create_view("v", "select * from t")
|
||||
db.close()
|
||||
result = CliRunner().invoke(
|
||||
cli.cli, ["insert", db_path, "v", "-"], input='{"id": 2}'
|
||||
)
|
||||
assert result.exit_code == 1
|
||||
assert result.output.strip() == "Error: Table v is actually a view"
|
||||
|
|
|
|||
|
|
@ -1382,7 +1382,10 @@ def test_bad_table_and_view_exceptions(fresh_db):
|
|||
assert ex.value.args[0] == "Table v is actually a view"
|
||||
with pytest.raises(NoView) as ex2:
|
||||
fresh_db.view("t")
|
||||
assert ex2.value.args[0] == "View t does not exist"
|
||||
assert ex2.value.args[0] == "View t does not exist - t is a table"
|
||||
with pytest.raises(NoView) as ex3:
|
||||
fresh_db.view("missing")
|
||||
assert ex3.value.args[0] == "View missing does not exist"
|
||||
|
||||
|
||||
# Tests for issue #655: Table configuration should be stored in _defaults
|
||||
|
|
|
|||
|
|
@ -198,3 +198,19 @@ def test_pending_and_applied_are_read_only(migrations):
|
|||
assert migrations.applied(db) == []
|
||||
# Neither call should have created the tracking table
|
||||
assert db.table_names() == []
|
||||
|
||||
|
||||
def test_duplicate_migration_name_errors():
|
||||
migrations = Migrations("test")
|
||||
|
||||
@migrations()
|
||||
def m001(db):
|
||||
pass
|
||||
|
||||
with pytest.raises(ValueError) as ex:
|
||||
|
||||
@migrations(name="m001")
|
||||
def m001_again(db):
|
||||
pass
|
||||
|
||||
assert "m001" in str(ex.value)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue