Skip to content

Commit 6c3f5e6

Browse files
committed
Added --table and --fmt options for table output using tabulate
1 parent dbf239e commit 6c3f5e6

File tree

4 files changed

+101
-13
lines changed

4 files changed

+101
-13
lines changed

docs/cli.rst

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ If you want to pretty-print the output further, you can pipe it through ``python
6060
Running queries and returning CSV
6161
=================================
6262

63-
You can use the ``--csv`` option to return results as CSV::
63+
You can use the ``--csv`` option (or ``-c`` shortcut) to return results as CSV::
6464

6565
$ sqlite-utils dogs.db "select * from dogs" --csv
6666
id,age,name
@@ -73,6 +73,31 @@ This will default to including the column names as a header row. To exclude the
7373
1,4,Cleo
7474
2,2,Pancakes
7575

76+
.. _cli_query_table:
77+
78+
Running queries and outputting a table
79+
======================================
80+
81+
You can use the ``--table`` option (or ``-t`` shortcut) to output query results as a table::
82+
83+
$ sqlite-utils dogs.db "select * from dogs" --table
84+
id age name
85+
---- ----- --------
86+
1 4 Cleo
87+
2 2 Pancakes
88+
89+
You can use the ``--fmt`` (or ``-f``) option to specify different table formats, for example ``rst`` for reStructuredText::
90+
91+
$ sqlite-utils dogs.db "select * from dogs" --table --fmt rst
92+
==== ===== ========
93+
id age name
94+
==== ===== ========
95+
1 4 Cleo
96+
2 2 Pancakes
97+
==== ===== ========
98+
99+
For a full list of table format options, run ``sqlite-utils query --help``.
100+
76101
.. _cli_rows:
77102

78103
Returning all rows in a table
@@ -84,7 +109,7 @@ You can return every row in a specified table using the ``rows`` subcommand::
84109
[{"id": 1, "age": 4, "name": "Cleo"},
85110
{"id": 2, "age": 2, "name": "Pancakes"}]
86111

87-
This command accepts the same output options as ``query`` - so you can pass ``--nl``, ``--csv`` and ``--no-headers``.
112+
This command accepts the same output options as ``query`` - so you can pass ``--nl``, ``--csv``, ``--no-headers``, ``--table`` and ``--fmt``.
88113

89114
.. _cli_tables:
90115

@@ -124,7 +149,7 @@ Use ``--columns`` to include a list of columns in each table::
124149
{"table": "Gosh2", "count": 0, "columns": ["c1", "c2", "c3"]},
125150
{"table": "dogs", "count": 2, "columns": ["id", "age", "name"]}]
126151

127-
The ``--nl``, ``--csv`` and ``--no-headers`` options are all available.
152+
The ``--nl``, ``--csv`` and ``--table`` options are all available.
128153

129154
.. _cli_inserting_data:
130155

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def get_long_description():
2222
version=VERSION,
2323
license="Apache License, Version 2.0",
2424
packages=find_packages(),
25-
install_requires=["click", "click-default-group"],
25+
install_requires=["click", "click-default-group", "tabulate"],
2626
setup_requires=["pytest-runner"],
2727
extras_require={"test": ["pytest", "black"]},
2828
entry_points="""

sqlite_utils/cli.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import json
66
import sys
77
import csv as csv_std
8+
import tabulate
89
import sqlite3
910

1011

@@ -23,8 +24,17 @@ def output_options(fn):
2324
is_flag=True,
2425
default=False,
2526
),
26-
click.option("--csv", is_flag=True, help="Output CSV"),
27+
click.option("-c", "--csv", is_flag=True, help="Output CSV"),
2728
click.option("--no-headers", is_flag=True, help="Omit CSV headers"),
29+
click.option("-t", "--table", is_flag=True, help="Output as a table"),
30+
click.option(
31+
"-f",
32+
"--fmt",
33+
help="Table format - one of {}".format(
34+
", ".join(tabulate.tabulate_formats)
35+
),
36+
default="simple",
37+
),
2838
)
2939
):
3040
fn = decorator(fn)
@@ -60,7 +70,7 @@ def cli():
6070
is_flag=True,
6171
default=False,
6272
)
63-
def tables(path, fts4, fts5, counts, nl, arrays, csv, no_headers, columns):
73+
def tables(path, fts4, fts5, counts, nl, arrays, csv, no_headers, table, fmt, columns):
6474
"""List the tables in the database"""
6575
db = sqlite_utils.Database(path)
6676
headers = ["table"]
@@ -82,7 +92,9 @@ def _iter():
8292
row.append(cols)
8393
yield row
8494

85-
if csv:
95+
if table:
96+
print(tabulate.tabulate(_iter(), headers=headers, tablefmt=fmt))
97+
elif csv:
8698
writer = csv_std.writer(sys.stdout)
8799
if not no_headers:
88100
writer.writerow(headers)
@@ -175,7 +187,7 @@ def insert_upsert_options(fn):
175187
click.argument("json_file", type=click.File(), required=True),
176188
click.option("--pk", help="Column to use as the primary key, e.g. id"),
177189
click.option("--nl", is_flag=True, help="Expect newline-delimited JSON"),
178-
click.option("--csv", is_flag=True, help="Expect CSV"),
190+
click.option("-c", "--csv", is_flag=True, help="Expect CSV"),
179191
click.option(
180192
"--batch-size", type=int, default=100, help="Commit every X records"
181193
),
@@ -244,12 +256,14 @@ def upsert(path, table, json_file, pk, nl, csv, batch_size):
244256
)
245257
@click.argument("sql")
246258
@output_options
247-
def query(path, sql, nl, arrays, csv, no_headers):
259+
def query(path, sql, nl, arrays, csv, no_headers, table, fmt):
248260
"Execute SQL query and return the results as JSON"
249261
db = sqlite_utils.Database(path)
250262
cursor = iter(db.conn.execute(sql))
251263
headers = [c[0] for c in cursor.description]
252-
if csv:
264+
if table:
265+
print(tabulate.tabulate(list(cursor), headers=headers, tablefmt=fmt))
266+
elif csv:
253267
writer = csv_std.writer(sys.stdout)
254268
if not no_headers:
255269
writer.writerow([c[0] for c in cursor.description])
@@ -266,19 +280,21 @@ def query(path, sql, nl, arrays, csv, no_headers):
266280
type=click.Path(file_okay=True, dir_okay=False, allow_dash=False),
267281
required=True,
268282
)
269-
@click.argument("table")
283+
@click.argument("dbtable")
270284
@output_options
271285
@click.pass_context
272-
def rows(ctx, path, table, nl, arrays, csv, no_headers):
286+
def rows(ctx, path, dbtable, nl, arrays, csv, no_headers, table, fmt):
273287
"Output all rows in the specified table"
274288
ctx.invoke(
275289
query,
276290
path=path,
277-
sql="select * from [{}]".format(table),
291+
sql="select * from [{}]".format(dbtable),
278292
nl=nl,
279293
arrays=arrays,
280294
csv=csv,
281295
no_headers=no_headers,
296+
table=table,
297+
fmt=fmt,
282298
)
283299

284300

tests/test_cli.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,53 @@ def test_tables_counts_and_columns_csv(db_path):
6969
) == result.output.strip()
7070

7171

72+
@pytest.mark.parametrize(
73+
"fmt,expected",
74+
[
75+
(
76+
"simple",
77+
(
78+
"c1 c2 c3\n"
79+
"----- ----- ----------\n"
80+
"verb0 noun0 adjective0\n"
81+
"verb1 noun1 adjective1\n"
82+
"verb2 noun2 adjective2\n"
83+
"verb3 noun3 adjective3"
84+
),
85+
),
86+
(
87+
"rst",
88+
(
89+
"===== ===== ==========\n"
90+
"c1 c2 c3\n"
91+
"===== ===== ==========\n"
92+
"verb0 noun0 adjective0\n"
93+
"verb1 noun1 adjective1\n"
94+
"verb2 noun2 adjective2\n"
95+
"verb3 noun3 adjective3\n"
96+
"===== ===== =========="
97+
),
98+
),
99+
],
100+
)
101+
def test_output_table(db_path, fmt, expected):
102+
db = Database(db_path)
103+
with db.conn:
104+
db["rows"].insert_all(
105+
[
106+
{
107+
"c1": "verb{}".format(i),
108+
"c2": "noun{}".format(i),
109+
"c3": "adjective{}".format(i),
110+
}
111+
for i in range(4)
112+
]
113+
)
114+
result = CliRunner().invoke(cli.cli, ["rows", db_path, "rows", "-t", "-f", fmt])
115+
assert 0 == result.exit_code
116+
assert expected == result.output.strip()
117+
118+
72119
def test_enable_fts(db_path):
73120
assert None == Database(db_path)["Gosh"].detect_fts()
74121
result = CliRunner().invoke(

0 commit comments

Comments
 (0)