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 import functools | 5 import functools |
6 import collections | 6 import collections |
7 import contextlib | 7 import contextlib |
8 import json | 8 import json |
9 | 9 |
10 from recipe_engine import recipe_api | 10 from recipe_engine import recipe_api |
11 from recipe_engine import util as recipe_util | 11 from recipe_engine import util as recipe_util |
12 from recipe_engine import config_types | 12 from recipe_engine import config_types |
13 | 13 |
14 | 14 |
15 class JsonOutputPlaceholder(recipe_util.OutputPlaceholder): | 15 class JsonOutputPlaceholder(recipe_util.OutputPlaceholder): |
16 """JsonOutputPlaceholder is meant to be a placeholder object which, when added | 16 """JsonOutputPlaceholder is meant to be a placeholder object which, when added |
17 to a step's cmd list, will be replaced by annotated_run with the path to a | 17 to a step's cmd list, will be replaced by annotated_run with the path to a |
18 temporary file (e.g. /tmp/tmp4lp1qM) which will exist only for the duration of | 18 temporary file (e.g. /tmp/tmp4lp1qM) which will exist only for the duration of |
tikuta1
2017/03/14 09:55:31
Better to update comment near here?
iannucci
2017/03/14 10:02:01
Oh, wow, this comment is very out of date! I've fi
| |
19 the step. If the script requires a flag (e.g. --output-json /path/to/file), | 19 the step. If the script requires a flag (e.g. --output-json /path/to/file), |
20 you must supply that flag yourself in the cmd list. | 20 you must supply that flag yourself in the cmd list. |
21 | 21 |
22 This placeholder can be optionally added when you use the Steps.step() | 22 This placeholder can be optionally added when you use the Steps.step() |
23 method in this module. | 23 method in this module. |
24 | 24 |
25 FIXME | 25 FIXME |
26 After the termination of the step, this file is expected to contain a valid | 26 After the termination of the step, this file is expected to contain a valid |
27 JSON document, which will be set as the json.output for that step in the | 27 JSON document, which will be set as the json.output for that step in the |
28 step_history OrderedDict passed to your recipe generator. | 28 step_history OrderedDict passed to your recipe generator. |
29 """ | 29 """ |
30 def __init__(self, api, add_json_log, name=None): | 30 def __init__(self, api, add_json_log, name=None, leak_to=None): |
31 self.raw = api.m.raw_io.output_text('.json') | 31 self.raw = api.m.raw_io.output_text('.json', leak_to=leak_to) |
32 self.add_json_log = add_json_log | 32 self.add_json_log = add_json_log |
33 super(JsonOutputPlaceholder, self).__init__(name=name) | 33 super(JsonOutputPlaceholder, self).__init__(name=name) |
34 | 34 |
35 @property | 35 @property |
36 def backing_file(self): | 36 def backing_file(self): |
37 return self.raw.backing_file | 37 return self.raw.backing_file |
38 | 38 |
39 def render(self, test): | 39 def render(self, test): |
40 return self.raw.render(test) | 40 return self.raw.render(test) |
41 | 41 |
42 def result(self, presentation, test): | 42 def result(self, presentation, test): |
43 raw_data = self.raw.result(presentation, test) | 43 raw_data = self.raw.result(presentation, test) |
44 | 44 |
45 valid = False | 45 valid = False |
46 ret = None | 46 ret = None |
47 try: | 47 try: |
48 ret = JsonApi.loads( | 48 ret = JsonApi.loads( |
49 raw_data, object_pairs_hook=collections.OrderedDict) | 49 raw_data, object_pairs_hook=collections.OrderedDict) |
50 valid = True | 50 valid = True |
51 # TypeError is raised when raw_data is None, which can happen if the json | 51 # TypeError is raised when raw_data is None, which can happen if the json |
52 # file was not created. We then correctly handle this as invalid result. | 52 # file was not created. We then correctly handle this as invalid result. |
53 except (ValueError, TypeError): # pragma: no cover | 53 except (ValueError, TypeError): # pragma: no cover |
54 pass | 54 pass |
55 | 55 |
56 if self.add_json_log: | 56 if self.add_json_log: |
57 key = self.label + ('' if valid else ' (invalid)') | 57 if valid: |
58 with contextlib.closing(recipe_util.StringListIO()) as listio: | 58 with contextlib.closing(recipe_util.StringListIO()) as listio: |
59 json.dump(ret, listio, indent=2, sort_keys=True) | 59 json.dump(ret, listio, indent=2, sort_keys=True) |
60 presentation.logs[key] = listio.lines | 60 presentation.logs[self.label] = listio.lines |
61 else: | |
62 presentation.logs[self.label + ' (invalid)'] = raw_data.splitlines() | |
61 | 63 |
62 return ret | 64 return ret |
63 | 65 |
64 | 66 |
65 class JsonApi(recipe_api.RecipeApi): | 67 class JsonApi(recipe_api.RecipeApi): |
66 def __init__(self, **kwargs): | 68 def __init__(self, **kwargs): |
67 super(JsonApi, self).__init__(**kwargs) | 69 super(JsonApi, self).__init__(**kwargs) |
68 @functools.wraps(json.dumps) | 70 @functools.wraps(json.dumps) |
69 def dumps(*args, **kwargs): | 71 def dumps(*args, **kwargs): |
70 kwargs['sort_keys'] = True | 72 kwargs['sort_keys'] = True |
(...skipping 26 matching lines...) Expand all Loading... | |
97 return True | 99 return True |
98 except Exception: | 100 except Exception: |
99 return False | 101 return False |
100 | 102 |
101 @recipe_util.returns_placeholder | 103 @recipe_util.returns_placeholder |
102 def input(self, data): | 104 def input(self, data): |
103 """A placeholder which will expand to a file path containing <data>.""" | 105 """A placeholder which will expand to a file path containing <data>.""" |
104 return self.m.raw_io.input_text(self.dumps(data), '.json') | 106 return self.m.raw_io.input_text(self.dumps(data), '.json') |
105 | 107 |
106 @recipe_util.returns_placeholder | 108 @recipe_util.returns_placeholder |
107 def output(self, add_json_log=True, name=None): | 109 def output(self, add_json_log=True, name=None, leak_to=None): |
108 """A placeholder which will expand to '/tmp/file'.""" | 110 """A placeholder which will expand to '/tmp/file'. |
109 return JsonOutputPlaceholder(self, add_json_log, name=name) | 111 |
112 If leak_to is provided, it must be a Path object. This path will be used in | |
113 place of a random temporary file, and the file will not be deleted at the | |
114 end of the step. | |
115 """ | |
116 return JsonOutputPlaceholder(self, add_json_log, name=name, leak_to=leak_to) | |
110 | 117 |
111 # TODO(you): This method should be in the `file` recipe_module | 118 # TODO(you): This method should be in the `file` recipe_module |
112 def read(self, name, path, add_json_log=True, output_name=None, **kwargs): | 119 def read(self, name, path, add_json_log=True, output_name=None, **kwargs): |
113 """Returns a step that reads a JSON file.""" | 120 """Returns a step that reads a JSON file.""" |
114 return self.m.python.inline( | 121 return self.m.python.inline( |
115 name, | 122 name, |
116 """ | 123 """ |
117 import shutil | 124 import shutil |
118 import sys | 125 import sys |
119 shutil.copy(sys.argv[1], sys.argv[2]) | 126 shutil.copy(sys.argv[1], sys.argv[2]) |
120 """, | 127 """, |
121 args=[path, | 128 args=[path, |
122 self.output(add_json_log=add_json_log, name=output_name)], | 129 self.output(add_json_log=add_json_log, name=output_name)], |
123 add_python_log=False, | 130 add_python_log=False, |
124 **kwargs | 131 **kwargs |
125 ) | 132 ) |
OLD | NEW |