Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(259)

Side by Side Diff: recipe_modules/json/api.py

Issue 2750793002: [json] improve output to get leak_to and better logging. (Closed)
Patch Set: fixit Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | recipe_modules/json/example.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 the recipe engine 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
19 the step. If the script requires a flag (e.g. --output-json /path/to/file), 19 the step. Create a JsonOutputPlaceholder by calling the 'output()' method of
20 you must supply that flag yourself in the cmd list. 20 the JsonApi.
21 21
22 This placeholder can be optionally added when you use the Steps.step() 22 The step is expected to write JSON data to this file, and when the step is
23 method in this module. 23 finished, the file will be read and the JSON parsed back into the recipe, and
24 will be available as part of the step result.
24 25
25 FIXME 26 Example:
26 After the termination of the step, this file is expected to contain a valid 27 result = api.step('step name',
27 JSON document, which will be set as the json.output for that step in the 28 ['write_json_to_file.sh', api.json.output()])
28 step_history OrderedDict passed to your recipe generator. 29 # `result.json.output` is the parsed JSON value.
30
31 See the example recipe (./example.py) for some more uses.
29 """ 32 """
30 def __init__(self, api, add_json_log, name=None): 33 def __init__(self, api, add_json_log, name=None, leak_to=None):
31 self.raw = api.m.raw_io.output_text('.json') 34 self.raw = api.m.raw_io.output_text('.json', leak_to=leak_to)
32 self.add_json_log = add_json_log 35 self.add_json_log = add_json_log
33 super(JsonOutputPlaceholder, self).__init__(name=name) 36 super(JsonOutputPlaceholder, self).__init__(name=name)
34 37
35 @property 38 @property
36 def backing_file(self): 39 def backing_file(self):
37 return self.raw.backing_file 40 return self.raw.backing_file
38 41
39 def render(self, test): 42 def render(self, test):
40 return self.raw.render(test) 43 return self.raw.render(test)
41 44
42 def result(self, presentation, test): 45 def result(self, presentation, test):
43 raw_data = self.raw.result(presentation, test) 46 raw_data = self.raw.result(presentation, test)
44 47
45 valid = False 48 valid = False
49 invalid_error = ''
46 ret = None 50 ret = None
47 try: 51 try:
48 ret = JsonApi.loads( 52 ret = JsonApi.loads(
49 raw_data, object_pairs_hook=collections.OrderedDict) 53 raw_data, object_pairs_hook=collections.OrderedDict)
50 valid = True 54 valid = True
51 # TypeError is raised when raw_data is None, which can happen if the json 55 # 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. 56 # file was not created. We then correctly handle this as invalid result.
53 except (ValueError, TypeError): # pragma: no cover 57 except (ValueError, TypeError) as ex: # pragma: no cover
54 pass 58 invalid_error = str(ex)
55 59
56 if self.add_json_log: 60 if self.add_json_log:
57 key = self.label + ('' if valid else ' (invalid)') 61 if valid:
58 with contextlib.closing(recipe_util.StringListIO()) as listio: 62 with contextlib.closing(recipe_util.StringListIO()) as listio:
59 json.dump(ret, listio, indent=2, sort_keys=True) 63 json.dump(ret, listio, indent=2, sort_keys=True)
60 presentation.logs[key] = listio.lines 64 presentation.logs[self.label] = listio.lines
65 else:
66 presentation.logs[self.label + ' (invalid)'] = raw_data.splitlines()
67 presentation.logs[self.label + ' (exception)'] = (
68 invalid_error.splitlines())
61 69
62 return ret 70 return ret
63 71
64 72
65 class JsonApi(recipe_api.RecipeApi): 73 class JsonApi(recipe_api.RecipeApi):
66 def __init__(self, **kwargs): 74 def __init__(self, **kwargs):
67 super(JsonApi, self).__init__(**kwargs) 75 super(JsonApi, self).__init__(**kwargs)
68 @functools.wraps(json.dumps) 76 @functools.wraps(json.dumps)
69 def dumps(*args, **kwargs): 77 def dumps(*args, **kwargs):
70 kwargs['sort_keys'] = True 78 kwargs['sort_keys'] = True
(...skipping 26 matching lines...) Expand all
97 return True 105 return True
98 except Exception: 106 except Exception:
99 return False 107 return False
100 108
101 @recipe_util.returns_placeholder 109 @recipe_util.returns_placeholder
102 def input(self, data): 110 def input(self, data):
103 """A placeholder which will expand to a file path containing <data>.""" 111 """A placeholder which will expand to a file path containing <data>."""
104 return self.m.raw_io.input_text(self.dumps(data), '.json') 112 return self.m.raw_io.input_text(self.dumps(data), '.json')
105 113
106 @recipe_util.returns_placeholder 114 @recipe_util.returns_placeholder
107 def output(self, add_json_log=True, name=None): 115 def output(self, add_json_log=True, name=None, leak_to=None):
108 """A placeholder which will expand to '/tmp/file'.""" 116 """A placeholder which will expand to '/tmp/file'.
109 return JsonOutputPlaceholder(self, add_json_log, name=name) 117
118 If leak_to is provided, it must be a Path object. This path will be used in
119 place of a random temporary file, and the file will not be deleted at the
120 end of the step.
121 """
122 return JsonOutputPlaceholder(self, add_json_log, name=name, leak_to=leak_to)
110 123
111 # TODO(you): This method should be in the `file` recipe_module 124 # 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): 125 def read(self, name, path, add_json_log=True, output_name=None, **kwargs):
113 """Returns a step that reads a JSON file.""" 126 """Returns a step that reads a JSON file."""
114 return self.m.python.inline( 127 return self.m.python.inline(
115 name, 128 name,
116 """ 129 """
117 import shutil 130 import shutil
118 import sys 131 import sys
119 shutil.copy(sys.argv[1], sys.argv[2]) 132 shutil.copy(sys.argv[1], sys.argv[2])
120 """, 133 """,
121 args=[path, 134 args=[path,
122 self.output(add_json_log=add_json_log, name=output_name)], 135 self.output(add_json_log=add_json_log, name=output_name)],
123 add_python_log=False, 136 add_python_log=False,
124 **kwargs 137 **kwargs
125 ) 138 )
OLDNEW
« no previous file with comments | « no previous file | recipe_modules/json/example.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698