From 81e2ad8a4428bc5d5a24e1088973e2621442e62c Mon Sep 17 00:00:00 2001 From: "Chris (ChrisJr404)" <11917633+ChrisJr404@users.noreply.github.com> Date: Tue, 18 Aug 2026 06:22:20 -0400 Subject: [PATCH] tables and views commands accept optional table/view names The tables and views commands now take one or more optional table (or view) names as positional arguments after the database path, restricting the output to just those tables. This is handy with --counts against a database that has a large table you would rather skip. Names are listed in the order they are passed. If any named table or view does not exist the command raises an error and exits with a non-zero status, so the exit code can be used to confirm a set of expected tables is present. Closes #478 --- docs/cli-reference.rst | 12 ++++++++-- docs/cli.rst | 15 +++++++++++++ sqlite_utils/cli.py | 34 +++++++++++++++++++++++++++- tests/test_cli.py | 51 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 3 deletions(-) diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index c53d64222..df8eebb70 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -711,7 +711,7 @@ See :ref:`cli_tables`. :: - Usage: sqlite-utils tables [OPTIONS] PATH + Usage: sqlite-utils tables [OPTIONS] PATH [NAMES]... List the tables in the database @@ -719,6 +719,10 @@ See :ref:`cli_tables`. sqlite-utils tables trees.db + Pass one or more table names to restrict the output to just those tables: + + sqlite-utils tables trees.db plants seeds + Options: --fts4 Just show FTS4 enabled tables --fts5 Just show FTS5 enabled tables @@ -756,7 +760,7 @@ See :ref:`cli_views`. :: - Usage: sqlite-utils views [OPTIONS] PATH + Usage: sqlite-utils views [OPTIONS] PATH [NAMES]... List the views in the database @@ -764,6 +768,10 @@ See :ref:`cli_views`. sqlite-utils views trees.db + Pass one or more view names to restrict the output to just those views: + + sqlite-utils views trees.db recent_plants + Options: --counts Include row counts per view --nl Output newline-delimited JSON diff --git a/docs/cli.rst b/docs/cli.rst index 78c33b8ae..e41e90142 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -747,6 +747,19 @@ You can list the names of tables in a database using the ``tables`` command: {"table": "cats"}, {"table": "chickens"}] +Pass one or more table names to restrict the output to just those tables. This is useful with ``--counts`` against a database that has a large table you want to skip: + +.. code-block:: bash + + sqlite-utils tables mydb.db dogs cats --counts + +.. code-block:: output + + [{"table": "dogs", "count": 12}, + {"table": "cats", "count": 332}] + +The tables are listed in the order you name them. An error is raised, and a non-zero exit code returned, if any of the named tables do not exist. + You can output this list in CSV using the ``--csv`` or ``--tsv`` options: .. code-block:: bash @@ -843,6 +856,8 @@ It takes the same options as the ``tables`` command: * ``--tsv`` * ``--table`` +As with ``tables``, you can pass one or more view names to restrict the output to just those views. + .. note:: In Python: :ref:`db.views or db.view_names() ` CLI reference: :ref:`sqlite-utils views ` diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index c23090283..7ecb2ec38 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -187,6 +187,7 @@ def cli(): type=click.Path(exists=True, file_okay=True, dir_okay=False, allow_dash=False), required=True, ) +@click.argument("names", nargs=-1) @click.option( "--fts4", help="Just show FTS4 enabled tables", default=False, is_flag=True ) @@ -212,6 +213,7 @@ def cli(): @load_extension_option def tables( path, + names, fts4, fts5, counts, @@ -235,6 +237,11 @@ def tables( \b sqlite-utils tables trees.db + + Pass one or more table names to restrict the output to just those tables: + + \b + sqlite-utils tables trees.db plants seeds """ db = sqlite_utils.Database(path) _register_db_for_cleanup(db) @@ -249,8 +256,25 @@ def tables( method = db.view if views else db.table + if names: + existing = set(db.view_names() if views else db.table_names()) + missing = [name for name in names if name not in existing] + if missing: + label = "view" if views else "table" + if len(missing) == 1: + message = "The following {} does not exist: {}".format( + label, missing[0] + ) + else: + message = "The following {}s do not exist: {}".format( + label, ", ".join(missing) + ) + raise click.ClickException(message) + def _iter(): - if views: + if names: + items = list(names) + elif views: items = db.view_names() else: items = db.table_names(fts4=fts4, fts5=fts5) @@ -293,6 +317,7 @@ def _iter(): type=click.Path(exists=True, file_okay=True, dir_okay=False, allow_dash=False), required=True, ) +@click.argument("names", nargs=-1) @click.option( "--counts", help="Include row counts per view", default=False, is_flag=True ) @@ -312,6 +337,7 @@ def _iter(): @load_extension_option def views( path, + names, counts, nl, arrays, @@ -332,10 +358,16 @@ def views( \b sqlite-utils views trees.db + + Pass one or more view names to restrict the output to just those views: + + \b + sqlite-utils views trees.db recent_plants """ assert tables.callback is not None tables.callback( path=path, + names=names, fts4=False, fts5=False, counts=counts, diff --git a/tests/test_cli.py b/tests/test_cli.py index 064026a0b..9c9487255 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -140,6 +140,57 @@ def test_tables_schema(db_path): ) == result.output.strip() +def test_tables_specific_names(db_path): + result = CliRunner().invoke( + cli.cli, ["tables", db_path, "Gosh2"], catch_exceptions=False + ) + assert '[{"table": "Gosh2"}]' == result.output.strip() + + +def test_tables_specific_names_preserve_argument_order(db_path): + result = CliRunner().invoke( + cli.cli, ["tables", db_path, "Gosh2", "Gosh"], catch_exceptions=False + ) + assert '[{"table": "Gosh2"},\n {"table": "Gosh"}]' == result.output.strip() + + +def test_tables_specific_names_with_counts(db_path): + result = CliRunner().invoke( + cli.cli, ["tables", db_path, "Gosh", "--counts"], catch_exceptions=False + ) + assert '[{"table": "Gosh", "count": 0}]' == result.output.strip() + + +def test_tables_missing_name_errors(db_path): + result = CliRunner().invoke(cli.cli, ["tables", db_path, "Gosh", "nope"]) + assert result.exit_code == 1 + assert "The following table does not exist: nope" in result.output + + +def test_tables_multiple_missing_names_errors(db_path): + result = CliRunner().invoke(cli.cli, ["tables", db_path, "nope", "nope2"]) + assert result.exit_code == 1 + assert "The following tables do not exist: nope, nope2" in result.output + + +def test_views_specific_names(db_path): + db = Database(db_path) + db.create_view("v1", "select 1") + db.create_view("v2", "select 2") + result = CliRunner().invoke( + cli.cli, ["views", db_path, "v2"], catch_exceptions=False + ) + assert '[{"view": "v2"}]' == result.output.strip() + + +def test_views_missing_name_errors(db_path): + db = Database(db_path) + db.create_view("v1", "select 1") + result = CliRunner().invoke(cli.cli, ["views", db_path, "nope"]) + assert result.exit_code == 1 + assert "The following view does not exist: nope" in result.output + + @pytest.mark.parametrize( "options,expected", [