db.schema and 'sqlite-utils schema' command, closes #268

This commit is contained in:
Simon Willison 2021-06-11 13:51:49 -07:00
commit 0d2e4f49f3
6 changed files with 90 additions and 2 deletions

View file

@ -348,6 +348,19 @@ It defaults to showing triggers for all tables. To see triggers for one or more
The command takes the same format options as the ``tables`` and ``views`` commands.
.. _cli_schema:
Showing the schema
==================
The ``sqlite-utils schema`` command shows the full SQL schema for the database::
$ sqlite-utils schema dogs.db
CREATE TABLE "dogs" (
[id] INTEGER PRIMARY KEY,
[name] TEXT
);
.. _cli_analyze_tables:
Analyzing tables

View file

@ -297,6 +297,21 @@ If the record does not exist a ``NotFoundError`` will be raised:
except NotFoundError:
print("Dog not found")
.. _python_api_schema:
Showing the schema
==================
The ``db.schema`` property returns the full SQL schema for the database as a string::
>>> db = sqlite_utils.Database("dogs.db")
>>> print(db.schema)
>>> print(db.schema)
CREATE TABLE "dogs" (
[id] INTEGER PRIMARY KEY,
[name] TEXT
);
.. _python_api_creating_tables:
Creating tables

View file

@ -1348,6 +1348,23 @@ def indexes(
)
@cli.command()
@click.argument(
"path",
type=click.Path(file_okay=True, dir_okay=False, allow_dash=False),
required=True,
)
@load_extension_option
def schema(
path,
load_extension,
):
"Show full schema for this database"
db = sqlite_utils.Database(path)
_load_extensions(db, load_extension)
click.echo(db.schema)
@cli.command()
@click.argument(
"path",

View file

@ -301,6 +301,18 @@ class Database:
"Returns {trigger_name: sql} dictionary"
return {trigger.name: trigger.sql for trigger in self.triggers}
@property
def schema(self):
sqls = []
for row in self.execute(
"select sql from sqlite_master where sql is not null"
).fetchall():
sql = row[0]
if not sql.strip().endswith(";"):
sql += ";"
sqls.append(sql)
return "\n".join(sqls)
@property
def journal_mode(self):
return self.execute("PRAGMA journal_mode;").fetchone()[0]

View file

@ -1916,6 +1916,33 @@ def test_triggers(tmpdir, extra_args, expected):
assert result.output == expected
def test_schema(tmpdir):
db_path = str(tmpdir / "test.db")
db = Database(db_path)
db["dogs"].create({"id": int, "name": str})
db["chickens"].create({"id": int, "name": str, "breed": str})
db["chickens"].create_index(["breed"])
result = CliRunner().invoke(
cli.cli,
["schema", db_path],
catch_exceptions=False,
)
assert result.exit_code == 0
assert result.output == (
"CREATE TABLE [dogs] (\n"
" [id] INTEGER,\n"
" [name] TEXT\n"
");\n"
"CREATE TABLE [chickens] (\n"
" [id] INTEGER,\n"
" [name] TEXT,\n"
" [breed] TEXT\n"
");\n"
"CREATE INDEX [idx_chickens_breed]\n"
" ON [chickens] ([breed]);\n"
)
def test_long_csv_column_value(tmpdir):
db_path = str(tmpdir / "test.db")
csv_path = str(tmpdir / "test.csv")

View file

@ -62,8 +62,12 @@ def test_columns(existing_db):
]
def test_schema(existing_db):
assert "CREATE TABLE foo (text TEXT)" == existing_db["foo"].schema
def test_table_schema(existing_db):
assert existing_db["foo"].schema == "CREATE TABLE foo (text TEXT)"
def test_database_schema(existing_db):
assert existing_db.schema == "CREATE TABLE foo (text TEXT);"
def test_table_repr(fresh_db):