| OLD | NEW |
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Methods for producing and consuming JSON.""" | 5 """Methods for producing and consuming JSON.""" |
| 6 | 6 |
| 7 import functools | 7 import functools |
| 8 import collections | 8 import collections |
| 9 import contextlib | 9 import contextlib |
| 10 import json | 10 import json |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 will be available as part of the step result. | 31 will be available as part of the step result. |
| 32 | 32 |
| 33 Example: | 33 Example: |
| 34 result = api.step('step name', | 34 result = api.step('step name', |
| 35 ['write_json_to_file.sh', api.json.output()]) | 35 ['write_json_to_file.sh', api.json.output()]) |
| 36 # `result.json.output` is the parsed JSON value. | 36 # `result.json.output` is the parsed JSON value. |
| 37 | 37 |
| 38 See the example recipe (./examples/full.py) for some more uses. | 38 See the example recipe (./examples/full.py) for some more uses. |
| 39 """ | 39 """ |
| 40 def __init__(self, api, add_json_log, name=None, leak_to=None): | 40 def __init__(self, api, add_json_log, name=None, leak_to=None): |
| 41 assert add_json_log in (True, False, 'on_failure'), ( |
| 42 'add_json_log=%r' % add_json_log) |
| 41 self.raw = api.m.raw_io.output_text('.json', leak_to=leak_to) | 43 self.raw = api.m.raw_io.output_text('.json', leak_to=leak_to) |
| 42 self.add_json_log = add_json_log | 44 self.add_json_log = add_json_log |
| 43 super(JsonOutputPlaceholder, self).__init__(name=name) | 45 super(JsonOutputPlaceholder, self).__init__(name=name) |
| 44 | 46 |
| 45 @property | 47 @property |
| 46 def backing_file(self): | 48 def backing_file(self): |
| 47 return self.raw.backing_file | 49 return self.raw.backing_file |
| 48 | 50 |
| 49 def render(self, test): | 51 def render(self, test): |
| 50 return self.raw.render(test) | 52 return self.raw.render(test) |
| 51 | 53 |
| 52 def result(self, presentation, test): | 54 def result(self, presentation, test): |
| 53 raw_data = self.raw.result(presentation, test) | 55 raw_data = self.raw.result(presentation, test) |
| 54 | 56 |
| 55 valid = False | 57 valid = False |
| 56 invalid_error = '' | 58 invalid_error = '' |
| 57 ret = None | 59 ret = None |
| 58 try: | 60 try: |
| 59 ret = JsonApi.loads( | 61 ret = JsonApi.loads( |
| 60 raw_data, object_pairs_hook=collections.OrderedDict) | 62 raw_data, object_pairs_hook=collections.OrderedDict) |
| 61 valid = True | 63 valid = True |
| 62 # TypeError is raised when raw_data is None, which can happen if the json | 64 # TypeError is raised when raw_data is None, which can happen if the json |
| 63 # file was not created. We then correctly handle this as invalid result. | 65 # file was not created. We then correctly handle this as invalid result. |
| 64 except (ValueError, TypeError) as ex: # pragma: no cover | 66 except (ValueError, TypeError) as ex: # pragma: no cover |
| 65 invalid_error = str(ex) | 67 invalid_error = str(ex) |
| 66 | 68 |
| 67 if self.add_json_log: | 69 if self.add_json_log is True or ( |
| 70 self.add_json_log == 'on_failure' and presentation.status != 'SUCCESS'): |
| 68 if valid: | 71 if valid: |
| 69 with contextlib.closing(recipe_util.StringListIO()) as listio: | 72 with contextlib.closing(recipe_util.StringListIO()) as listio: |
| 70 json.dump(ret, listio, indent=2, sort_keys=True) | 73 json.dump(ret, listio, indent=2, sort_keys=True) |
| 71 presentation.logs[self.label] = listio.lines | 74 presentation.logs[self.label] = listio.lines |
| 72 else: | 75 else: |
| 73 presentation.logs[self.label + ' (invalid)'] = raw_data.splitlines() | 76 presentation.logs[self.label + ' (invalid)'] = raw_data.splitlines() |
| 74 presentation.logs[self.label + ' (exception)'] = ( | 77 presentation.logs[self.label + ' (exception)'] = ( |
| 75 invalid_error.splitlines()) | 78 invalid_error.splitlines()) |
| 76 | 79 |
| 77 return ret | 80 return ret |
| (...skipping 23 matching lines...) Expand all Loading... |
| 101 """A placeholder which will expand to a file path containing <data>.""" | 104 """A placeholder which will expand to a file path containing <data>.""" |
| 102 return self.m.raw_io.input_text(self.dumps(data), '.json') | 105 return self.m.raw_io.input_text(self.dumps(data), '.json') |
| 103 | 106 |
| 104 @recipe_util.returns_placeholder | 107 @recipe_util.returns_placeholder |
| 105 def output(self, add_json_log=True, name=None, leak_to=None): | 108 def output(self, add_json_log=True, name=None, leak_to=None): |
| 106 """A placeholder which will expand to '/tmp/file'. | 109 """A placeholder which will expand to '/tmp/file'. |
| 107 | 110 |
| 108 If leak_to is provided, it must be a Path object. This path will be used in | 111 If leak_to is provided, it must be a Path object. This path will be used in |
| 109 place of a random temporary file, and the file will not be deleted at the | 112 place of a random temporary file, and the file will not be deleted at the |
| 110 end of the step. | 113 end of the step. |
| 114 |
| 115 Args: |
| 116 * add_json_log (True|False|'on_failure') - Log a copy of the output json |
| 117 to a step link named `name`. If this is 'on_failure', only create this |
| 118 log when the step has a non-SUCCESS status. |
| 111 """ | 119 """ |
| 112 return JsonOutputPlaceholder(self, add_json_log, name=name, leak_to=leak_to) | 120 return JsonOutputPlaceholder(self, add_json_log, name=name, leak_to=leak_to) |
| 113 | 121 |
| 114 # TODO(you): This method should be in the `file` recipe_module | 122 # TODO(you): This method should be in the `file` recipe_module |
| 115 def read(self, name, path, add_json_log=True, output_name=None, **kwargs): | 123 def read(self, name, path, add_json_log=True, output_name=None, **kwargs): |
| 116 """Returns a step that reads a JSON file.""" | 124 """Returns a step that reads a JSON file.""" |
| 117 return self.m.python.inline( | 125 return self.m.python.inline( |
| 118 name, | 126 name, |
| 119 """ | 127 """ |
| 120 import shutil | 128 import shutil |
| 121 import sys | 129 import sys |
| 122 shutil.copy(sys.argv[1], sys.argv[2]) | 130 shutil.copy(sys.argv[1], sys.argv[2]) |
| 123 """, | 131 """, |
| 124 args=[path, | 132 args=[path, |
| 125 self.output(add_json_log=add_json_log, name=output_name)], | 133 self.output(add_json_log=add_json_log, name=output_name)], |
| 126 add_python_log=False, | 134 add_python_log=False, |
| 127 **kwargs | 135 **kwargs |
| 128 ) | 136 ) |
| OLD | NEW |