Index: recipe_engine/recipe_api.py |
diff --git a/recipe_engine/recipe_api.py b/recipe_engine/recipe_api.py |
index fef517232a4354792d4b93efbf0850b62dee1280..7b6a862f351027a9824788dd7f746d261c9d179c 100644 |
--- a/recipe_engine/recipe_api.py |
+++ b/recipe_engine/recipe_api.py |
@@ -19,57 +19,153 @@ from .util import ModuleInjectionSite |
from . import field_composer |
-_StepConfig = collections.namedtuple('StepConfig', |
+_TriggerSpec = collections.namedtuple('_TriggerSpec', |
+ ('bucket', 'builder_name', 'properties', 'buildbot_changes', 'tags', |
+ 'critical')) |
+ |
+class TriggerSpec(_TriggerSpec): |
+ """ |
+ TriggerSpec is the internal representation of a raw trigger step. You should |
+ use the standard 'step' recipe module, which will construct trigger specs |
+ via API. |
+ """ |
+ |
+ @classmethod |
+ def _make_from_api_dict(cls, trig): |
iannucci
2016/09/12 22:35:16
could we **explode this and add docstring?
|
+ builder_name = trig.pop('builder_name') |
+ if not builder_name: |
+ raise ValueError('Trigger spec: builder_name is not set') |
+ |
+ changes = trig.pop('buildbot_changes', None) |
+ if changes is not None: |
+ assert isinstance(changes, list), 'buildbot_changes must be a list' |
iannucci
2016/09/12 22:35:16
raise valueerror
dnj
2016/09/12 23:30:11
Done.
|
+ |
+ inst = cls( |
+ bucket=trig.pop('bucket', None), |
+ builder_name=builder_name, |
+ properties=trig.pop('properties', None), |
+ buildbot_changes=changes, |
+ tags=trig.pop('tags', None), |
+ critical=trig.pop('critical', True), |
+ ) |
+ |
+ # Confirm that all dictionary keys have been consumed. |
+ if trig: |
+ unknown_keys = sorted(trig.iterkeys()) |
+ raise KeyError('Unknown trigger spec keys: %s' % ( |
iannucci
2016/09/12 22:35:16
I think this is still ValueError. I think KeyError
dnj
2016/09/12 23:30:11
Done.
|
+ ', '.join(unknown_keys))) |
+ |
+ return inst |
+ |
+ def _render_to_dict(self): |
+ d = dict((k, v) for k, v in self._asdict().iteritems() if v) |
+ if d['critical']: |
+ d.pop('critical') |
+ return d |
+ |
+ |
+_StepConfig = collections.namedtuple('_StepConfig', |
('name', 'cmd', 'cwd', 'env', 'allow_subannotations', 'trigger_specs', |
- 'timeout', 'infra_step', 'stdout', 'stderr', 'stdin', 'ok_ret', |
- 'step_test_data', 'nest_level')) |
+ 'timeout', 'infra_step', 'stdout', 'stderr', 'stdin', 'ok_ret', |
+ 'step_test_data', 'nest_level')) |
class StepConfig(_StepConfig): |
""" |
+ StepConfig is the representation of a raw step as the recipe_engine sees it. |
+ You should use the standard 'step' recipe module, which will construct and |
+ pass this data to the engine for you, instead. The only reason why you would |
+ need to worry about this object is if you're modifying the step module itself. |
+ |
+ The optional "env" parameter provides optional overrides for environment |
+ variables. Each value is % formatted with the entire existing os.environ. A |
+ value of `None` will remove that envvar from the environ. e.g. |
+ |
+ { |
+ "envvar": "%(envvar)s;%(envvar2)s;extra", |
+ "delete_this": None, |
+ "static_value": "something", |
+ } |
+ |
StepConfig parameters: |
- name: name of the step, will appear in buildbots waterfall |
- cmd: command to run, list of one or more strings |
- cwd: absolute path to working directory for the command |
- env: dict with overrides for environment variables |
- allow_subannotations: if True, lets the step emit its own annotations |
+ name (str): name of the step, will appear in buildbots waterfall |
+ cmd: command to run. Acceptable types: str, Path, Placeholder, or None. |
+ cwd (str or None): absolute path to working directory for the command |
+ env (dict): overrides for environment variables, described above. |
+ allow_subannotations (bool): if True, lets the step emit its own annotations |
+ NOTE: Enabling this can cause some buggy behavior. Please strongly |
+ consider using step_result.presentation instead. If you have questions, |
+ please contact infra-dev@chromium.org. |
trigger_specs: a list of trigger specifications, see also _trigger_builds. |
timeout: if not None, a datetime.timedelta for the step timeout. |
- infra_step: if True, this is an infrastructure step. |
- stdout: Path to a file to put step stdout into. If used, stdout won't |
- appear in annotator's stdout (and |allow_subannotations| is |
- ignored). |
- stderr: Path to a file to put step stderr into. If used, stderr won't |
- appear in annotator's stderr. |
- stdin: Path to a file to read step stdin from. |
- ok_ret: Allowed return code list. |
- step_test_data: Possible associated step test data. |
- nest_level: the step's nesting level. |
+ infra_step: if True, this is an infrastructure step. Failures will raise |
+ InfraFailure instead of StepFailure. |
+ stdout: Placeholder to put step stdout into. If used, stdout won't appear in |
+ annotator's stdout (and |allow_subannotations| is ignored). |
+ stderr: Placeholder to put step stderr into. If used, stderr won't appear in |
+ annotator's stderr. |
+ stdin: Placeholder to read step stdin from. |
+ ok_ret (iter): set of return codes allowed. If the step process returns |
iannucci
2016/09/12 22:35:16
if you want to say this is an iter, then we should
dnj
2016/09/12 23:30:11
Was converting to a set, but frozenset is even bet
|
+ something not on this list, it will raise a StepFailure (or InfraFailure |
+ if infra_step is True). If omitted, {0} will be used. |
+ step_test_data (func -> recipe_test_api.StepTestData): A factory which |
+ returns a StepTestData object that will be used as the default test data |
+ for this step. The recipe author can override/augment this object in the |
+ GenTests function. |
+ nest_level (int): the step's nesting level. |
""" |
+ _RENDER_WHITELIST=frozenset(( |
+ 'cmd', |
+ )) |
+ |
+ _RENDER_BLACKLIST=frozenset(( |
+ 'nest_level', |
+ 'ok_ret', |
+ 'infra_step', |
+ 'step_test_data', |
+ )) |
+ |
+ @classmethod |
+ def _make_from_api_dict(cls, step): |
+ step_config = StepConfig( |
+ name=step.pop('name'), |
+ cmd=step.pop('cmd', None), |
+ cwd=step.pop('cwd', None), |
+ env=step.pop('env', None), |
+ allow_subannotations=step.pop('allow_subannotations', False), |
+ trigger_specs=[TriggerSpec._make_from_api_dict(trig) |
+ for trig in step.pop('trigger_specs', ())], |
+ timeout=step.pop('timeout', None), |
+ infra_step=step.pop('infra_step', False), |
+ stdout=step.pop('stdout', None), |
+ stderr=step.pop('stderr', None), |
+ stdin=step.pop('stdin', None), |
+ ok_ret=set(step.pop('ok_ret', {0})), |
+ step_test_data=step.pop('step_test_data', None), |
+ nest_level=int(step.pop('step_nest_level', 0)), |
+ ) |
+ |
+ # Confirm that all dictionary keys have been consumed. |
+ if step: |
+ unknown_keys = sorted(step.iterkeys()) |
+ raise KeyError('Unknown step dictionary keys: %s' % ( |
+ ', '.join(unknown_keys))) |
+ |
+ return step_config |
+ |
+ def render_to_dict(self): |
+ self = self._replace( |
+ trigger_specs=[trig._render_to_dict() |
+ for trig in (self.trigger_specs or ())], |
+ ) |
+ return dict((k, v) for k, v in self._asdict().iteritems() |
+ if (v or k in self._RENDER_WHITELIST) |
+ and k not in self._RENDER_BLACKLIST) |
+ |
def _make_step_config(**step): |
- """Galvanize a step dictionary into a formal StepConfig.""" |
- step_config = StepConfig( |
- name=step.pop('name'), |
- cmd=step.pop('cmd', None), |
- cwd=step.pop('cwd', None), |
- env=step.pop('env', None), |
- allow_subannotations=step.pop('allow_subannotations', False), |
- trigger_specs=step.pop('trigger_specs', ()), |
- timeout=step.pop('timeout', None), |
- infra_step=step.pop('infra_step', False), |
- stdout=step.pop('stdout', None), |
- stderr=step.pop('stderr', None), |
- stdin=step.pop('stdin', None), |
- ok_ret=step.pop('ok_ret', {0}), |
- step_test_data=step.pop('step_test_data', None), |
- nest_level=step.pop('step_nest_level', 0), |
- ) |
- if step: |
- unknown_keys = sorted(step.iterkeys()) |
- raise KeyError('Unknown step dictionary keys: %s' % ( |
- ', '.join(unknown_keys))) |
- return step_config |
+ """Convert a step API dictionary into a formal StepConfig.""" |
+ return StepConfig._make_from_api_dict(step) |
class StepFailure(Exception): |