def with_plugs(self, **subplugs: Type[base_plugs.BasePlug]) -> 'PhaseDescriptor': """Substitute plugs for placeholders for this phase. Args: **subplugs: dict of plug name to plug class, plug classes to replace; unknown plug names are ignored. A base_plugs.InvalidPlugError is raised when a test includes a phase that still has a placeholder plug. Raises: base_plugs.InvalidPlugError: if for one of the plug names one of the following is true: - The new plug subclass is not a subclass of the original. - The original plug class is not a placeholder or automatic placeholder. Returns: PhaseDescriptor with updated plugs. """ plugs_by_name = {plug.name: plug for plug in self.plugs} new_plugs = {} for name, sub_class in six.iteritems(subplugs): original_plug = plugs_by_name.get(name) accept_substitute = True if original_plug is None: continue elif isinstance(original_plug.cls, base_plugs.PlugPlaceholder): accept_substitute = issubclass(sub_class, original_plug.cls.base_class) else: # Check __dict__ to see if the attribute is explicitly defined in the # class, rather than being defined in a parent class. accept_substitute = ( 'auto_placeholder' in original_plug.cls.__dict__ and original_plug.cls.auto_placeholder and issubclass(sub_class, original_plug.cls)) if not accept_substitute: raise base_plugs.InvalidPlugError( 'Could not find valid placeholder for substitute plug %s ' 'required for phase %s' % (name, self.name)) new_plugs[name] = data.attr_copy(original_plug, cls=sub_class) if not new_plugs: return self plugs_by_name.update(new_plugs) return data.attr_copy( self, plugs=list(plugs_by_name.values()), options=self.options.format_strings(**subplugs), measurements=[m.with_args(**subplugs) for m in self.measurements])
def with_args(self, **kwargs: Any) -> 'PhaseDescriptor': """Send keyword-arguments to the phase when called. Args: **kwargs: mapping of argument name to value to be passed to the phase function when called. Unknown arguments are ignored. Returns: Updated PhaseDescriptor. """ if six.PY3: argspec = inspect.getfullargspec(self.func) argspec_keywords = argspec.varkw else: argspec = inspect.getargspec(self.func) # pylint: disable=deprecated-method argspec_keywords = argspec.keywords known_arguments = {} for key, arg in six.iteritems(kwargs): if key in argspec.args or argspec_keywords: known_arguments[key] = arg new_info = data.attr_copy(self) new_info.options = new_info.options.format_strings(**kwargs) new_info.extra_kwargs.update(known_arguments) new_info.measurements = [m.with_args(**kwargs) for m in self.measurements] return new_info
def wrap_or_copy(cls, func: PhaseT, **options: Any) -> 'PhaseDescriptor': """Return a new PhaseDescriptor from the given function or instance. We want to return a new copy so that you can reuse a phase with different options, plugs, measurements, etc. Args: func: A phase function or PhaseDescriptor instance. **options: Options to update on the result. Raises: PhaseWrapError: if func is a openhtf.PhaseGroup. Returns: A new PhaseDescriptor object. """ # TODO(arsharma): Remove when type annotations are more enforced. if isinstance(func, openhtf.PhaseGroup): raise PhaseWrapError('Cannot wrap PhaseGroup <%s> as a phase.' % (func.name or 'Unnamed')) # pytype: disable=attribute-error if isinstance(func, cls): # We want to copy so that a phase can be reused with different options # or kwargs. See with_args() below for more details. retval = data.attr_copy(func) else: retval = cls(func) retval.options.update(**options) return retval
def with_args(self, **kwargs: Any) -> 'Measurement': """String substitution for names and docstrings.""" new_validators = [ v.with_args(**kwargs) if hasattr(v, 'with_args') else v for v in self.validators ] new_conditional_validators = [ cv.with_args(**kwargs) for cv in self.conditional_validators ] return data.attr_copy( self, name=util.format_string(self.name, kwargs), docstring=util.format_string(self.docstring, kwargs), validators=new_validators, conditional_validators=new_conditional_validators, cached=None, )
def from_measurement( cls, measurement: measurements.Measurement) -> 'ImmutableMeasurement': """Convert a Measurement into an ImmutableMeasurement.""" measured_value = measurement.measured_value if isinstance(measured_value, measurements.DimensionedMeasuredValue): value = data.attr_copy(measured_value, value_dict=copy.deepcopy( measured_value.value_dict)) else: value = (copy.deepcopy(measured_value.value) if measured_value.is_value_set else None) return cls(name=measurement.name, value=value, units=measurement.units, dimensions=measurement.dimensions, outcome=measurement.outcome)
def copy(self: WithModifierT) -> WithModifierT: """Create a copy of the PhaseNode.""" return data.attr_copy(self)
def with_plugs(self, **subplugs: Any) -> 'Checkpoint': return data.attr_copy(self, name=util.format_string(self.name, subplugs))
def with_args(self, **kwargs: Any) -> 'Checkpoint': return data.attr_copy(self, name=util.format_string(self.name, kwargs))
def load_code_info(self) -> 'PhaseDescriptor': """Load code info for this phase.""" return data.attr_copy( self, code_info=test_record.CodeInfo.for_function(self.func))
def format_strings(self, **kwargs: Any) -> 'PhaseOptions': """String substitution of name.""" return data.attr_copy(self, name=util.format_string(self.name, kwargs))
def _context_wrapper( *phases: phase_descriptor.PhaseCallableOrNodeT ) -> 'PhaseGroup': return cls(setup=data.attr_copy(setup) if setup else None, main=phase_collections.PhaseSequence(phases), teardown=data.attr_copy(teardown) if teardown else None)