From c791a310477712000a39f05728291b34494c1564 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 21 Dec 2025 00:32:25 +0000 Subject: [PATCH] Make square braces only return tables, not views Breaking change for 4.0: db["name"] now only returns Table objects, never View objects. This improves type safety since views lack methods like .insert(). Use db.view("view_name") to access views explicitly. Closes #699 --- sqlite_utils/cli.py | 8 +++++--- sqlite_utils/db.py | 10 +++++----- tests/test_cli.py | 8 +++++--- tests/test_create.py | 4 ++-- tests/test_fts.py | 2 +- tests/test_tracer.py | 1 - 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 9b9ee20..65deb30 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -12,6 +12,7 @@ from sqlite_utils.db import ( BadMultiValues, DescIndex, NoTable, + NoView, quote_identifier, ) from sqlite_utils.plugins import pm, get_plugins @@ -1796,9 +1797,10 @@ def drop_view(path, view, ignore, load_extension): _register_db_for_cleanup(db) _load_extensions(db, load_extension) try: - db[view].drop(ignore=ignore) - except OperationalError: - raise click.ClickException('View "{}" does not exist'.format(view)) + db.view(view).drop(ignore=ignore) + except NoView: + if not ignore: + raise click.ClickException('View "{}" does not exist'.format(view)) @cli.command() diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index aacdc89..b0ee014 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -445,15 +445,15 @@ class Database: finally: self._tracer = prev_tracer - def __getitem__(self, table_name: str) -> Union["Table", "View"]: + def __getitem__(self, table_name: str) -> "Table": """ ``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. + Use ``db.view(view_name)`` to access views. + :param table_name: The name of the table """ - if table_name in self.view_names(): - return self.view(table_name) return self.table(table_name) def __repr__(self) -> str: @@ -1206,9 +1206,9 @@ class Database: return self elif replace: # If SQL is the same, do nothing - if create_sql == self[name].schema: + if create_sql == self.view(name).schema: return self - self[name].drop() + self.view(name).drop() self.execute(create_sql) return self diff --git a/tests/test_cli.py b/tests/test_cli.py index 40c3595..eb7ee30 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1367,7 +1367,8 @@ def test_create_view(): ) assert result.exit_code == 0 assert ( - 'CREATE VIEW "version" AS select sqlite_version()' == db["version"].schema + 'CREATE VIEW "version" AS select sqlite_version()' + == db.view("version").schema ) @@ -1404,7 +1405,7 @@ def test_create_view_ignore(): assert result.exit_code == 0 assert ( 'CREATE VIEW "version" AS select sqlite_version() + 1' - == db["version"].schema + == db.view("version").schema ) @@ -1425,7 +1426,8 @@ def test_create_view_replace(): ) assert result.exit_code == 0 assert ( - 'CREATE VIEW "version" AS select sqlite_version()' == db["version"].schema + 'CREATE VIEW "version" AS select sqlite_version()' + == db.view("version").schema ) diff --git a/tests/test_create.py b/tests/test_create.py index ea09c45..1829216 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -1082,7 +1082,7 @@ def test_drop(fresh_db): def test_drop_view(fresh_db): fresh_db.create_view("foo_view", "select 1") assert ["foo_view"] == fresh_db.view_names() - assert None is fresh_db["foo_view"].drop() + assert None is fresh_db.view("foo_view").drop() assert [] == fresh_db.view_names() @@ -1093,7 +1093,7 @@ def test_drop_ignore(fresh_db): # Testing view is harder, we need to create it in order # to get a View object, then drop it twice fresh_db.create_view("foo_view", "select 1") - view = fresh_db["foo_view"] + view = fresh_db.view("foo_view") assert isinstance(view, View) view.drop() with pytest.raises(sqlite3.OperationalError): diff --git a/tests/test_fts.py b/tests/test_fts.py index 9c635ff..b80af6a 100644 --- a/tests/test_fts.py +++ b/tests/test_fts.py @@ -424,7 +424,7 @@ def test_enable_fts_error_message_on_views(): db = Database(memory=True) db.create_view("hello", "select 1 + 1") with pytest.raises(NotImplementedError) as e: - db["hello"].enable_fts() # type: ignore[call-arg] + db.view("hello").enable_fts() # type: ignore[call-arg] assert e.value.args[0] == "enable_fts() is supported on tables but not on views" diff --git a/tests/test_tracer.py b/tests/test_tracer.py index ac490c5..ab55e8c 100644 --- a/tests/test_tracer.py +++ b/tests/test_tracer.py @@ -15,7 +15,6 @@ def test_tracer(): ("select name from sqlite_master where type = 'view'", None), ("select name from sqlite_master where type = 'table'", None), ("select name from sqlite_master where type = 'view'", None), - ("select name from sqlite_master where type = 'view'", None), ("select name from sqlite_master where type = 'table'", None), ("select name from sqlite_master where type = 'view'", None), ('CREATE TABLE "dogs" (\n "name" TEXT\n);\n ', None),