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

Side by Side Diff: recipe_engine/recipe_api.py

Issue 2340483002: Revert of Add better documentation, trigger namedtuple. (Closed)
Patch Set: Created 4 years, 3 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_engine/run.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 2016 The LUCI Authors. All rights reserved. 1 # Copyright 2016 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 from __future__ import absolute_import 5 from __future__ import absolute_import
6 import contextlib 6 import contextlib
7 import collections 7 import collections
8 import keyword 8 import keyword
9 import re 9 import re
10 import types 10 import types
11 11
12 from functools import wraps 12 from functools import wraps
13 13
14 from .recipe_test_api import DisabledTestData, ModuleTestData 14 from .recipe_test_api import DisabledTestData, ModuleTestData
15 from .config import Single 15 from .config import Single
16 16
17 from .util import ModuleInjectionSite 17 from .util import ModuleInjectionSite
18 18
19 from . import field_composer 19 from . import field_composer
20 20
21 21
22 _TriggerSpec = collections.namedtuple('_TriggerSpec', 22 _StepConfig = collections.namedtuple('StepConfig',
23 ('bucket', 'builder_name', 'properties', 'buildbot_changes', 'tags',
24 'critical'))
25
26 class TriggerSpec(_TriggerSpec):
27 """
28 TriggerSpec is the internal representation of a raw trigger step. You should
29 use the standard 'step' recipe module, which will construct trigger specs
30 via API.
31 """
32
33 @classmethod
34 def _create(cls, builder_name, bucket=None, properties=None,
35 buildbot_changes=None, tags=None, critical=None):
36 """Creates a new TriggerSpec instance from its step API dictionary
37 keys/values.
38
39 Args:
40 builder_name (str): The name of the builder to trigger.
41 bucket (str or None): The name of the trigger bucket.
42 properties (dict or None): Key/value properties dictionary.
43 buildbot_changes (list or None): Optional list of BuildBot change dicts.
44 tags (list or None): Optional list of tag strings.
45 critical (bool or None): If true and triggering fails asynchronously, fail
46 the entire build. If None, the step defaults to being True.
47 """
48 if not isinstance(buildbot_changes, (types.NoneType, list)):
49 raise ValueError('buildbot_changes must be a list')
50
51 return cls(
52 bucket=bucket,
53 builder_name=builder_name,
54 properties=properties,
55 buildbot_changes=buildbot_changes,
56 tags=tags,
57 critical=bool(critical) if critical is not None else (True),
58 )
59
60 def _render_to_dict(self):
61 d = dict((k, v) for k, v in self._asdict().iteritems() if v)
62 if d['critical']:
63 d.pop('critical')
64 return d
65
66
67 _StepConfig = collections.namedtuple('_StepConfig',
68 ('name', 'cmd', 'cwd', 'env', 'allow_subannotations', 'trigger_specs', 23 ('name', 'cmd', 'cwd', 'env', 'allow_subannotations', 'trigger_specs',
69 'timeout', 'infra_step', 'stdout', 'stderr', 'stdin', 'ok_ret', 24 'timeout', 'infra_step', 'stdout', 'stderr', 'stdin', 'ok_ret',
70 'step_test_data', 'nest_level')) 25 'step_test_data', 'nest_level'))
71 26
72 class StepConfig(_StepConfig): 27 class StepConfig(_StepConfig):
73 """ 28 """
74 StepConfig is the representation of a raw step as the recipe_engine sees it. 29 StepConfig parameters:
75 You should use the standard 'step' recipe module, which will construct and 30 name: name of the step, will appear in buildbots waterfall
76 pass this data to the engine for you, instead. The only reason why you would 31 cmd: command to run, list of one or more strings
77 need to worry about this object is if you're modifying the step module itself. 32 cwd: absolute path to working directory for the command
78 33 env: dict with overrides for environment variables
79 The optional "env" parameter provides optional overrides for environment 34 allow_subannotations: if True, lets the step emit its own annotations
80 variables. Each value is % formatted with the entire existing os.environ. A 35 trigger_specs: a list of trigger specifications, see also _trigger_builds.
81 value of `None` will remove that envvar from the environ. e.g. 36 timeout: if not None, a datetime.timedelta for the step timeout.
82 37 infra_step: if True, this is an infrastructure step.
83 { 38 stdout: Path to a file to put step stdout into. If used, stdout won't
84 "envvar": "%(envvar)s;%(envvar2)s;extra", 39 appear in annotator's stdout (and |allow_subannotations| is
85 "delete_this": None, 40 ignored).
86 "static_value": "something", 41 stderr: Path to a file to put step stderr into. If used, stderr won't
87 } 42 appear in annotator's stderr.
43 stdin: Path to a file to read step stdin from.
44 ok_ret: Allowed return code list.
45 step_test_data: Possible associated step test data.
46 nest_level: the step's nesting level.
88 """ 47 """
89 48
90 _RENDER_WHITELIST=frozenset((
91 'cmd',
92 ))
93 49
94 _RENDER_BLACKLIST=frozenset(( 50 def _make_step_config(**step):
95 'nest_level', 51 """Galvanize a step dictionary into a formal StepConfig."""
96 'ok_ret', 52 step_config = StepConfig(
97 'infra_step', 53 name=step.pop('name'),
98 'step_test_data', 54 cmd=step.pop('cmd', None),
99 )) 55 cwd=step.pop('cwd', None),
100 56 env=step.pop('env', None),
101 @classmethod 57 allow_subannotations=step.pop('allow_subannotations', False),
102 def create(cls, name, cmd=None, cwd=None, env=None, 58 trigger_specs=step.pop('trigger_specs', ()),
103 allow_subannotations=None, trigger_specs=None, timeout=None, 59 timeout=step.pop('timeout', None),
104 infra_step=None, stdout=None, stderr=None, stdin=None, 60 infra_step=step.pop('infra_step', False),
105 ok_ret=None, step_test_data=None, step_nest_level=None): 61 stdout=step.pop('stdout', None),
106 """ 62 stderr=step.pop('stderr', None),
107 Initializes a new StepConfig step API dictionary. 63 stdin=step.pop('stdin', None),
108 64 ok_ret=step.pop('ok_ret', {0}),
109 Args: 65 step_test_data=step.pop('step_test_data', None),
110 name (str): name of the step, will appear in buildbots waterfall 66 nest_level=step.pop('step_nest_level', 0),
111 cmd: command to run. Acceptable types: str, Path, Placeholder, or None. 67 )
112 cwd (str or None): absolute path to working directory for the command 68 if step:
113 env (dict): overrides for environment variables, described above. 69 unknown_keys = sorted(step.iterkeys())
114 allow_subannotations (bool): if True, lets the step emit its own 70 raise KeyError('Unknown step dictionary keys: %s' % (
115 annotations. NOTE: Enabling this can cause some buggy behavior. Please 71 ', '.join(unknown_keys)))
116 strongly consider using step_result.presentation instead. If you have 72 return step_config
117 questions, please contact infra-dev@chromium.org.
118 trigger_specs: a list of trigger specifications, see also _trigger_builds.
119 timeout: if not None, a datetime.timedelta for the step timeout.
120 infra_step: if True, this is an infrastructure step. Failures will raise
121 InfraFailure instead of StepFailure.
122 stdout: Placeholder to put step stdout into. If used, stdout won't appear
123 in annotator's stdout (and |allow_subannotations| is ignored).
124 stderr: Placeholder to put step stderr into. If used, stderr won't appear
125 in annotator's stderr.
126 stdin: Placeholder to read step stdin from.
127 ok_ret (iter): set of return codes allowed. If the step process returns
128 something not on this list, it will raise a StepFailure (or
129 InfraFailure if infra_step is True). If omitted, {0} will be used.
130 step_test_data (func -> recipe_test_api.StepTestData): A factory which
131 returns a StepTestData object that will be used as the default test
132 data for this step. The recipe author can override/augment this object
133 in the GenTests function.
134 step_nest_level (int): the step's nesting level.
135 """
136 return cls(
137 name=name,
138 cmd=cmd,
139 cwd=cwd,
140 env=env,
141 allow_subannotations=bool(allow_subannotations),
142 trigger_specs=[TriggerSpec._create(**trig)
143 for trig in (trigger_specs or ())],
144 timeout=timeout,
145 infra_step=bool(infra_step),
146 stdout=stdout,
147 stderr=stderr,
148 stdin=stdin,
149 ok_ret=frozenset(ok_ret or (0,)),
150 step_test_data=step_test_data,
151 nest_level=int(step_nest_level or 0),
152 )
153
154 def render_to_dict(self):
155 self = self._replace(
156 trigger_specs=[trig._render_to_dict()
157 for trig in (self.trigger_specs or ())],
158 )
159 return dict((k, v) for k, v in self._asdict().iteritems()
160 if (v or k in self._RENDER_WHITELIST)
161 and k not in self._RENDER_BLACKLIST)
162 73
163 74
164 class StepFailure(Exception): 75 class StepFailure(Exception):
165 """ 76 """
166 This is the base class for all step failures. 77 This is the base class for all step failures.
167 78
168 Raising a StepFailure counts as 'running a step' for the purpose of 79 Raising a StepFailure counts as 'running a step' for the purpose of
169 infer_composite_step's logic. 80 infer_composite_step's logic.
170 """ 81 """
171 def __init__(self, name_or_reason, result=None): 82 def __init__(self, name_or_reason, result=None):
(...skipping 416 matching lines...) Expand 10 before | Expand all | Expand 10 after
588 return itm(base), params 499 return itm(base), params
589 except KeyError: 500 except KeyError:
590 if optional: 501 if optional:
591 return None, generic_params 502 return None, generic_params
592 else: # pragma: no cover 503 else: # pragma: no cover
593 raise # TODO(iannucci): raise a better exception. 504 raise # TODO(iannucci): raise a better exception.
594 505
595 def set_config(self, config_name=None, optional=False, **CONFIG_VARS): 506 def set_config(self, config_name=None, optional=False, **CONFIG_VARS):
596 """Sets the modules and its dependencies to the named configuration.""" 507 """Sets the modules and its dependencies to the named configuration."""
597 assert self._module 508 assert self._module
598 config, _ = self.make_config_params(config_name, optional, **CONFIG_VARS) 509 config, params = self.make_config_params(config_name, optional,
510 **CONFIG_VARS)
599 if config: 511 if config:
600 self.c = config 512 self.c = config
601 513
602 def apply_config(self, config_name, config_object=None, optional=False): 514 def apply_config(self, config_name, config_object=None, optional=False):
603 """Apply a named configuration to the provided config object or self.""" 515 """Apply a named configuration to the provided config object or self."""
604 assert config_name in self._module.CONFIG_CTX.CONFIG_ITEMS, ( 516 assert config_name in self._module.CONFIG_CTX.CONFIG_ITEMS, (
605 config_name, self._module.CONFIG_CTX.CONFIG_ITEMS) 517 config_name, self._module.CONFIG_CTX.CONFIG_ITEMS)
606 self._module.CONFIG_CTX.CONFIG_ITEMS[config_name]( 518 self._module.CONFIG_CTX.CONFIG_ITEMS[config_name](
607 config_object or self.c, optional=optional) 519 config_object or self.c, optional=optional)
608 520
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
686 598
687 if name in ('self',): 599 if name in ('self',):
688 return False 600 return False
689 601
690 if keyword.iskeyword(name): 602 if keyword.iskeyword(name):
691 return False 603 return False
692 604
693 regex = r'^[a-zA-Z][a-zA-Z0-9_]*$' if is_param_name else r'^[a-zA-Z][.\w]*$' 605 regex = r'^[a-zA-Z][a-zA-Z0-9_]*$' if is_param_name else r'^[a-zA-Z][.\w]*$'
694 return bool(re.match(regex, name)) 606 return bool(re.match(regex, name))
695 607
696 def __init__(self, default, helptext, kind, name, property_type, module, 608 def __init__(self, default, help, kind, name, property_type, module,
697 param_name=None): 609 param_name=None):
698 """ 610 """
699 Constructor for BoundProperty. 611 Constructor for BoundProperty.
700 612
701 Args: 613 Args:
702 default: The default value for this Property. Note: A default 614 default: The default value for this Property. Note: A default
703 value of None is allowed. To have no default value, omit 615 value of None is allowed. To have no default value, omit
704 this argument. 616 this argument.
705 helptext: The help text for this Property. 617 help: The help text for this Property.
706 kind: The type of this Property. You can either pass in a raw python 618 kind: The type of this Property. You can either pass in a raw python
707 type, or a Config Type, using the recipe engine config system. 619 type, or a Config Type, using the recipe engine config system.
708 name: The name of this Property. 620 name: The name of this Property.
709 param_name: The name of the python function parameter this property 621 param_name: The name of the python function parameter this property
710 should be stored in. Can be used to allow for dotted property 622 should be stored in. Can be used to allow for dotted property
711 names, e.g. 623 names, e.g.
712 PROPERTIES = { 624 PROPERTIES = {
713 'foo.bar.bam': Property(param_name="bizbaz") 625 'foo.bar.bam': Property(param_name="bizbaz")
714 } 626 }
715 module: The module this Property is a part of. 627 module: The module this Property is a part of.
716 """ 628 """
717 if not BoundProperty.legal_name(name): 629 if not BoundProperty.legal_name(name):
718 raise ValueError("Illegal name '{}'.".format(param_name)) 630 raise ValueError("Illegal name '{}'.".format(param_name))
719 631
720 param_name = param_name or name 632 param_name = param_name or name
721 if not BoundProperty.legal_name(param_name, is_param_name=True): 633 if not BoundProperty.legal_name(param_name, is_param_name=True):
722 raise ValueError("Illegal param_name '{}'.".format(param_name)) 634 raise ValueError("Illegal param_name '{}'.".format(param_name))
723 635
724 self.__default = default 636 self.__default = default
725 self.__help = helptext 637 self.__help = help
726 self.__kind = kind 638 self.__kind = kind
727 self.__name = name 639 self.__name = name
728 self.__property_type = property_type 640 self.__property_type = property_type
729 self.__param_name = param_name 641 self.__param_name = param_name
730 self.__module = module 642 self.__module = module
731 643
732 @property 644 @property
733 def name(self): 645 def name(self):
734 return self.__name 646 return self.__name
735 647
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
772 return value 684 return value
773 685
774 if self.default is not PROPERTY_SENTINEL: 686 if self.default is not PROPERTY_SENTINEL:
775 return self.default 687 return self.default
776 688
777 raise ValueError( 689 raise ValueError(
778 "No default specified and no value provided for '{}' from {} '{}'".format( 690 "No default specified and no value provided for '{}' from {} '{}'".format(
779 self.name, self.__property_type, self.module)) 691 self.name, self.__property_type, self.module))
780 692
781 class Property(object): 693 class Property(object):
782 def __init__(self, default=PROPERTY_SENTINEL, helptext="", kind=None, 694 def __init__(self, default=PROPERTY_SENTINEL, help="", kind=None,
783 param_name=None): 695 param_name=None):
784 """ 696 """
785 Constructor for Property. 697 Constructor for Property.
786 698
787 Args: 699 Args:
788 default: The default value for this Property. Note: A default 700 default: The default value for this Property. Note: A default
789 value of None is allowed. To have no default value, omit 701 value of None is allowed. To have no default value, omit
790 this argument. 702 this argument.
791 helptext: The help text for this Property. 703 help: The help text for this Property.
792 kind: The type of this Property. You can either pass in a raw python 704 kind: The type of this Property. You can either pass in a raw python
793 type, or a Config Type, using the recipe engine config system. 705 type, or a Config Type, using the recipe engine config system.
794 """ 706 """
795 self._default = default 707 self._default = default
796 self.helptext = helptext 708 self.help = help
797 self.param_name = param_name 709 self.param_name = param_name
798 710
799 if isinstance(kind, type): 711 if isinstance(kind, type):
800 if kind in (str, unicode): 712 if kind in (str, unicode):
801 kind = basestring 713 kind = basestring
802 kind = Single(kind) 714 kind = Single(kind)
803 self.kind = kind 715 self.kind = kind
804 716
805 def bind(self, name, property_type, module): 717 def bind(self, name, property_type, module):
806 """ 718 """
807 Gets the BoundProperty version of this Property. Requires a name. 719 Gets the BoundProperty version of this Property. Requires a name.
808 """ 720 """
809 return BoundProperty( 721 return BoundProperty(
810 self._default, self.helptext, self.kind, name, property_type, module, 722 self._default, self.help, self.kind, name, property_type, module,
811 self.param_name) 723 self.param_name)
812 724
813 class UndefinedPropertyException(TypeError): 725 class UndefinedPropertyException(TypeError):
814 pass 726 pass
815 727
OLDNEW
« no previous file with comments | « no previous file | recipe_engine/run.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698