Skip to content
This repository was archived by the owner on Mar 18, 2019. It is now read-only.

Commit d8ed976

Browse files
Finesse command line error handling
1 parent b2eab88 commit d8ed976

6 files changed

Lines changed: 37 additions & 30 deletions

File tree

coreapi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from coreapi.transport import BaseTransport, HTTPTransport
88

99

10-
__version__ = '1.11.3'
10+
__version__ = '1.11.4'
1111
__all__ = [
1212
'BaseCodec', 'CoreJSONCodec', 'HALCodec', 'HTMLCodec', 'PlainTextCodec', 'PythonCodec',
1313
'negotiate_encoder', 'negotiate_decoder',

coreapi/commandline.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,21 +152,31 @@ def show(path):
152152
if path:
153153
keys = coerce_key_types(doc, path)
154154
for key in keys:
155-
doc = doc[key]
155+
try:
156+
doc = doc[key]
157+
except (KeyError, IndexError):
158+
click.echo('Key %s not found.' % repr(key).strip('u'))
159+
sys.exit(1)
156160
click.echo(display(doc))
157161

158162

159163
def validate_params(ctx, param, value):
160164
if any(['=' not in item for item in value]):
161165
raise click.BadParameter('Parameters need to be in format <field name>=<value>')
166+
167+
# Convert to dict.
162168
params = dict([tuple(item.split('=', 1)) for item in value])
169+
170+
# Gracefully handle non-string values.
171+
# Eg treat "-p complete=true" as {"complete": True}
163172
for key, value in params.items():
164173
try:
165174
value = json.loads(value)
166175
except:
167176
pass
168177
else:
169178
params[key] = value
179+
170180
return params
171181

172182

@@ -201,6 +211,9 @@ def action(path, param, action, inplace):
201211
except coreapi.exceptions.ErrorMessage as exc:
202212
click.echo(display(exc.error))
203213
sys.exit(1)
214+
except coreapi.exceptions.NodeLookupError as exc:
215+
click.echo(exc)
216+
sys.exit(1)
204217
history = history.add(doc)
205218
click.echo(display(doc))
206219
set_document(doc)

coreapi/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ class TransportError(Exception):
3131
pass
3232

3333

34+
class NodeLookupError(Exception):
35+
"""
36+
Raised when `.action` fails to index a link in the document.
37+
"""
38+
pass
39+
40+
3441
class ErrorMessage(Exception):
3542
"""
3643
Raised when the transition returns an error message.

coreapi/validation.py

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from coreapi.compat import string_types
22
from coreapi.document import Document, Link
3+
from coreapi.exceptions import NodeLookupError
34
import collections
45

56

@@ -36,23 +37,6 @@ def validate_parameters(link, parameters):
3637
3738
Raises a `ValueError` if any parameters do not validate.
3839
"""
39-
provided = set(parameters.keys())
40-
required = set([
41-
field.name for field in link.fields if field.required
42-
])
43-
44-
# Determine if any required field names not supplied.
45-
missing = required - provided
46-
missing = ['"' + item + '"' for item in sorted(missing)]
47-
if missing:
48-
fmt = 'Missing required parameter{plural}: {listing}'
49-
plural = 's' if len(missing) > 1 else ''
50-
listing = ', '.join(missing)
51-
raise ValueError(fmt.format(
52-
plural=plural,
53-
listing=listing
54-
))
55-
5640
# Ensure all parameter values are valid types.
5741
for value in parameters.values():
5842
validate_is_primative(value)
@@ -65,7 +49,7 @@ def validate_keys_to_link(document, keys):
6549
Returns a two-tuple of (link, link_ancestors).
6650
"""
6751
if not isinstance(keys, (list, tuple)):
68-
msg = "'keys' must be a dot seperated string or a list of strings."
52+
msg = "'keys' must be a list of strings or intes."
6953
raise TypeError(msg)
7054
if any([
7155
not isinstance(key, string_types) and not isinstance(key, int)
@@ -79,15 +63,22 @@ def validate_keys_to_link(document, keys):
7963
node = document
8064
link_ancestors = [Ancestor(document=document, keys=[])]
8165
for idx, key in enumerate(keys, start=1):
82-
node = node[key]
66+
try:
67+
node = node[key]
68+
except (KeyError, IndexError):
69+
index_string = ''.join('[%s]' % repr(key).strip('u') for key in keys)
70+
msg = 'Index %s did not reference a link. Key %s was not found.'
71+
raise NodeLookupError(msg % (index_string, repr(key).strip('u')))
8372
if isinstance(node, Document):
8473
ancestor = Ancestor(document=node, keys=keys[:idx])
8574
link_ancestors.append(ancestor)
8675

8776
# Ensure that we've correctly indexed into a link.
8877
if not isinstance(node, Link):
89-
raise ValueError(
90-
"Can only call 'action' on a Link. Got type '%s'." % type(node)
78+
index_string = ''.join('[%s]' % repr(key).strip('u') for key in keys)
79+
msg = "Can only call 'action' on a Link. Index %s returned type '%s'."
80+
raise NodeLookupError(
81+
msg % (index_string, type(node).__name__)
9182
)
9283

9384
return (node, link_ancestors)

tests/test_document.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# coding: utf-8
22
from coreapi import action
33
from coreapi import Array, Document, Object, Link, Error, Field
4+
from coreapi.exceptions import NodeLookupError
45
import pytest
56

67

@@ -451,12 +452,12 @@ def test_keys_should_be_a_list_of_strings_or_ints(doc):
451452

452453

453454
def test_keys_should_be_valid_indexes(doc):
454-
with pytest.raises(KeyError):
455+
with pytest.raises(NodeLookupError):
455456
action(doc, 'dummy')
456457

457458

458459
def test_keys_should_access_a_link(doc):
459-
with pytest.raises(ValueError):
460+
with pytest.raises(NodeLookupError):
460461
action(doc, 'dict')
461462

462463

tests/test_validation.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,6 @@ def test_link_with_correct_parameters(link):
2121
validate_parameters(link, {'required': 123, 'optional': 456})
2222

2323

24-
def test_link_missing_required_parameter(link):
25-
with pytest.raises(ValueError):
26-
validate_parameters(link, {'optional': 456})
27-
28-
2924
def test_link_with_additional_parameter(link):
3025
validate_parameters(link, {'required': 123, 'unknown': 123})
3126

0 commit comments

Comments
 (0)