Skip to content

Commit 3ec8a23

Browse files
committed
Better no connection handling
1 parent fa3f024 commit 3ec8a23

7 files changed

Lines changed: 48 additions & 16 deletions

File tree

.github/workflows/cli-smoke.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,27 @@ jobs:
3838
- name: "rcdb --version"
3939
run: rcdb --version
4040

41+
- name: "No connection fails cleanly (no traceback)"
42+
env:
43+
RCDB_CONNECTION: ""
44+
run: |
45+
# A command that needs the DB must exit non-zero with a clear message,
46+
# not crash with a Python traceback.
47+
set +e
48+
OUT=$(rcdb select "event_count > 0" 2>&1)
49+
CODE=$?
50+
set -e
51+
echo "$OUT"
52+
echo "exit code: $CODE"
53+
test "$CODE" -ne 0
54+
echo "$OUT" | grep -q "No database connection provided"
55+
if echo "$OUT" | grep -q "Traceback"; then
56+
echo "ERROR: command crashed with a traceback instead of a clean error"
57+
exit 1
58+
fi
59+
# `--help` must still work without a connection
60+
rcdb select --help >/dev/null
61+
4162
- name: "Create schema (db init)"
4263
run: rcdb db init --confirm
4364

python/rcdb/app_context.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,26 @@ def __init__(self, home, connection_str):
1414
self.connection_str = connection_str
1515

1616
@property
17-
def db(self):
17+
def db(self) -> RCDBProvider:
1818
if not self._db_instance:
1919
self._db_instance = RCDBProvider(self.connection_str)
2020
return self._db_instance
2121

22+
def require_connected_db(self) -> RCDBProvider:
23+
"""Return the RCDBProvider, or fail with a clear CLI error if no connection was given.
24+
25+
Commands that cannot do anything without a database call this instead of
26+
using ``db`` directly: a missing connection then produces a clean error
27+
message rather than crashing later on a ``None`` session. Commands that can
28+
run without a connection (e.g. ``web`` with ``--add-db``) keep using ``db``.
29+
"""
30+
if not self.connection_str:
31+
raise click.UsageError(
32+
"No connection provided! Provide a DB connection string via the "
33+
"-c/--connection option or the RCDB_CONNECTION environment variable."
34+
)
35+
return self.db
36+
2237
def set_config(self, key, value):
2338
self.config[key] = value
2439
if self.verbose:

python/rcdb/cli/add.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def add_type(context, name, value_type, description):
4545
Example:
4646
rcdb add type beam_current --type=float --description "Beam current in nA"
4747
"""
48-
db = context.db
48+
db = context.require_connected_db()
4949
actual_type = TYPE_MAP[value_type.lower()]
5050

5151
# Create or verify
@@ -69,7 +69,7 @@ def add_condition(context, run_number, condition_name, value, replace):
6969
rcdb add condition 1000 my_value 123.4
7070
rcdb add condition 1000 event_count 10000 --replace
7171
"""
72-
db = context.db
72+
db = context.require_connected_db()
7373

7474
# 1) Ensure run exists
7575
run = db.get_run(run_number)
@@ -115,7 +115,7 @@ def add_file(context, run_number, file_path, importance, overwrite, content):
115115
rcdb add file 1000 /path/to/coda_run.log
116116
rcdb add file 1000 /path/to/config.txt --importance=2 --overwrite
117117
"""
118-
db = context.db
118+
db = context.require_connected_db()
119119
run = db.get_run(run_number)
120120
if not run:
121121
run = db.create_run(run_number)

python/rcdb/cli/app.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,9 @@ def rcdb_cli(ctx, user_config, connection, config, verbose):
4444

4545
# Create a rcdb_app_context object and remember it as the context object. From
4646
# this point onwards other commands can refer to it by using the
47-
# @pass_rcdb_context decorator.
48-
if not connection:
49-
print("(!)WARNING no connection provided! "
50-
"Provide DB connection string via --connection/-c or RCDB_CONNECTION environment variable.")
47+
# @pass_rcdb_context decorator. A connection is not validated here so that
48+
# `--help` and connection-less commands stay clean; commands that need a
49+
# database call context.require_connected_db() to get a clear error instead.
5150
ctx.obj = RcdbApplicationContext(os.path.abspath(user_config), connection)
5251
ctx.obj.verbose = verbose
5352
for key, value in config:

python/rcdb/cli/info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def info_command(context):
1111
"""
1212
Shows various summary information about the RCDB database contents.
1313
"""
14-
db = context.db
14+
db = context.require_connected_db()
1515

1616
# Number of condition types
1717
cnd_type_count = db.session.query(ConditionType).count()

python/rcdb/cli/ls.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import click
22

3-
from rcdb.provider import RCDBProvider
43
from .context import pass_rcdb_context
54

65

@@ -11,8 +10,7 @@
1110
def ls_command(context, search, is_long):
1211
"""List conditions"""
1312

14-
db = context.db
15-
assert isinstance(db, RCDBProvider)
13+
db = context.require_connected_db()
1614
cnd_types = db.get_condition_types_by_name()
1715
names = sorted(cnd_types.keys())
1816
if search:

python/rcdb/cli/select.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import click
33

44
from rcdb.app_context import parse_run_range
5-
from rcdb import RCDBProvider
65
from rcdb.cli.context import pass_rcdb_context
76

87

@@ -37,14 +36,14 @@ def _process_sel_args(args):
3736
@pass_rcdb_context
3837
def select_command(rcdb_context, query, views_or_runs, is_dump_view, is_descending):
3938
"""Select runs and get their values."""
40-
assert isinstance(rcdb_context.db, RCDBProvider)
39+
db = rcdb_context.require_connected_db()
4140
args = []
4241
if query is not None:
4342
args.append(str(query))
4443
args.extend([str(v) for v in views_or_runs])
4544
run_range_str, query, view = _process_sel_args(args)
4645

47-
run_periods = rcdb_context.db.get_run_periods()
46+
run_periods = db.get_run_periods()
4847
run_min, run_max = parse_run_range(run_range_str, run_periods)
4948

5049
if run_min is None:
@@ -60,7 +59,7 @@ def select_command(rcdb_context, query, views_or_runs, is_dump_view, is_descendi
6059

6160
conditions_to_show = view.split()
6261

63-
values = rcdb_context.db.select_values([], query, run_min, run_max)
62+
values = db.select_values([], query, run_min, run_max)
6463

6564
if not is_dump_view:
6665

0 commit comments

Comments
 (0)