def __init__( self, name, # type: APartName mri, # type: AMri initial_visibility=False, # type: AInitialVisibility ignore_configure_args=(), # type: UIgnoreConfigureArgs ): # type: (...) -> None super(RunnableChildPart, self).__init__(name, mri, initial_visibility) self.ignore_configure_args = AIgnoreConfigureArgs( ignore_configure_args) # Stored between runs self.run_future = None # type: Future # The configure method model of our child self.configure_model = MethodModel() # The registrar object we get at setup self.registrar = None # type: PartRegistrar # The design we last loaded/saved self.design = None # Hooks self.register_hooked(ValidateHook, self.validate, self.configure_args) self.register_hooked(ConfigureHook, self.configure, self.configure_args) self.register_hooked((RunHook, ResumeHook), self.run) self.register_hooked((PostRunArmedHook, PostRunReadyHook), self.post_run) self.register_hooked(SeekHook, self.seek) self.register_hooked(AbortHook, self.abort)
def test_from_dict(self): m = MethodModel.from_dict(self.serialized) assert m.takes.to_dict() == self.takes.to_dict() assert m.defaults == self.serialized["defaults"] assert m.tags == [] assert m.writeable is False assert m.label == "" assert m.returns.to_dict() == MapMeta().to_dict()
def setUp(self): self.data = BlockModel() self.data.set_endpoint_data("attr", StringMeta().create_attribute_model()) self.data.set_endpoint_data("method", MethodModel()) self.data.set_notifier_path(Mock(), ["block"]) self.controller = Mock() self.context = Mock() self.o = make_view(self.controller, self.context, self.data)
def create_method_models(self): method_name = snake_to_camel(self.field_name) if self.arg_meta: self.arg_name = method_name # Decorate set_field with a Method @method_takes(self.arg_name, self.arg_meta, REQUIRED) def set_field(params): self.set_field(params) self.method = set_field.MethodModel writeable_func = set_field else: self.method = MethodModel() writeable_func = None self.method.set_description(self.description) self.method.set_tags(self.tags) yield method_name, self.method, writeable_func
def init(self, context): self.configure_args_update_queue = Queue() super(RunnableChildPart, self).init(context) # Monitor the child configure Method for changes self.serialized_configure = MethodModel().to_dict() subscription = Subscribe( path=[self.params.mri, "configure"], delta=True, callback=self.update_part_configure_args) # Wait for the first update to come in self.child_controller.handle_request(subscription).wait()
class PandABlocksActionPart(Part): """This will normally be instantiated by the PandABox assembly, not created in yaml""" def __init__(self, client, block_name, field_name, description, tags, arg_meta=None): super(PandABlocksActionPart, self).__init__(field_name) self.client = client self.block_name = block_name self.field_name = field_name self.description = description self.tags = tags self.arg_name = None self.arg_meta = arg_meta self.method = None def create_method_models(self): method_name = snake_to_camel(self.field_name) if self.arg_meta: self.arg_name = method_name # Decorate set_field with a Method @method_takes(self.arg_name, self.arg_meta, REQUIRED) def set_field(params): self.set_field(params) self.method = set_field.MethodModel writeable_func = set_field else: self.method = MethodModel() writeable_func = None self.method.set_description(self.description) self.method.set_tags(self.tags) yield method_name, self.method, writeable_func def set_field(self, params=None): if params is None: value = 0 else: value = params[self.arg_name] self.client.set_field(self.block_name, self.field_name, value)
def update_configure_args(self, part, configure_model): """Tell controller part needs different things passed to Configure""" with self.changes_squashed: # Update the dict self.configure_method_models[part] = configure_model method_models = list(self.configure_method_models.values()) # Update takes with the things we need default_configure = MethodModel.from_dict( RunnableController.configure.MethodModel.to_dict()) default_configure.defaults["axesToMove"] = self.axes_to_move.value method_models.append(default_configure) # Decorate validate and configure with the sum of its parts self._block.validate.recreate_from_others(method_models) self._block.validate.set_returns(self._block.validate.takes) self._block.configure.recreate_from_others(method_models)
def instantiate(self, substitutions): """Keep recursing down from base using dotted name, then call it with self.params and args Args: substitutions (dict): Substitutions to make to self.param_dict Returns: The found object called with (*args, map_from_d) E.g. if ob is malcolm.parts, and name is "ca.CADoublePart", then the object will be malcolm.parts.ca.CADoublePart """ param_dict = self.substitute_params(substitutions) pkg, ident = self.name.rsplit(".", 1) pkg = "malcolm.modules.%s" % pkg try: ob = importlib.import_module(pkg) except ImportError as e: raise_with_traceback( ImportError("\n%s:%d:\n%s" % (self.filename, self.lineno, e))) try: ob = getattr(ob, ident) except AttributeError: raise_with_traceback( ImportError("\n%s:%d:\nPackage %r has no ident %r" % (self.filename, self.lineno, pkg, ident))) try: model = MethodModel.from_callable(ob, returns=False) args = model.validate(param_dict) ret = ob(**args) except Exception as e: sourcefile = inspect.getsourcefile(ob) lineno = inspect.getsourcelines(ob)[1] raise_with_traceback( YamlError("\n%s:%d:\n%s:%d:\n%s" % (self.filename, self.lineno, sourcefile, lineno, e))) else: return ret
def create_method_models(self): # Method instance self.method = MethodModel(self.params.description) # TODO: set widget tag? yield self.params.name, self.method, self.caput
def setUp(self): self.attr = StringMeta().create_attribute_model() self.method = MethodModel() self.o = BlockModel() self.o.set_endpoint_data("attr", self.attr) self.o.set_endpoint_data("method", self.method)
def test_to_dict(self): m = MethodModel(description="test_description") m.set_takes(self.takes) m.set_defaults(self.serialized["defaults"]) assert m.to_dict() == self.serialized
def test_set_label(self): m = MethodModel(description="test_description") m.set_label("new_label") assert "new_label" == m.label
def test_init(self): m = MethodModel(description="test_description") assert "test_description" == m.description assert "malcolm:core/Method:1.0" == m.typeid assert "" == m.label
class RunnableChildPart(ChildPart): """Part controlling a child Block that exposes a configure/run interface""" def __init__( self, name, # type: APartName mri, # type: AMri initial_visibility=False, # type: AInitialVisibility ignore_configure_args=(), # type: UIgnoreConfigureArgs ): # type: (...) -> None super(RunnableChildPart, self).__init__(name, mri, initial_visibility) self.ignore_configure_args = AIgnoreConfigureArgs( ignore_configure_args) # Stored between runs self.run_future = None # type: Future # The configure method model of our child self.configure_model = MethodModel() # The registrar object we get at setup self.registrar = None # type: PartRegistrar # The design we last loaded/saved self.design = None # Hooks self.register_hooked(ValidateHook, self.validate, self.configure_args) self.register_hooked(ConfigureHook, self.configure, self.configure_args) self.register_hooked((RunHook, ResumeHook), self.run) self.register_hooked((PostRunArmedHook, PostRunReadyHook), self.post_run) self.register_hooked(SeekHook, self.seek) self.register_hooked(AbortHook, self.abort) def configure_args(self): args = ["context"] for x in self.configure_model.takes.elements: if x not in self.ignore_configure_args: args.append(x) return args @add_call_types def init(self, context): # type: (AContext) -> None super(RunnableChildPart, self).init(context) # Monitor the child configure Method for changes subscription = Subscribe(path=[self.mri, "configure"], delta=True) subscription.set_callback(self.update_part_configure_args) # Wait for the first update to come in self.child_controller.handle_request(subscription).wait() @add_call_types def halt(self): # type: () -> None super(RunnableChildPart, self).halt() unsubscribe = Unsubscribe() unsubscribe.set_callback(self.update_part_configure_args) self.child_controller.handle_request(unsubscribe) @add_call_types def reset(self, context): # type: (AContext) -> None child = context.block_view(self.mri) if child.abort.writeable: child.abort() super(RunnableChildPart, self).reset(context) @add_call_types def load(self, context, structure, init=False): # type: (AContext, AStructure, AInit) -> None if init: # At init pop out the design so it doesn't get restored here # This stops child devices (like a detector) getting told to # go to multiple conflicting designs at startup design = structure.pop("design", "") super(RunnableChildPart, self).load(context, structure) # Now put it back in the saved structure self.saved_structure["design"] = design # We might not have cleared the changes so report here child = context.block_view(self.mri) self.send_modified_info_if_not_equal("design", child.design.value) else: super(RunnableChildPart, self).load(context, structure) @add_call_types def validate(self, context, **kwargs): # type: (AContext, **Any) -> UParameterTweakInfos child = context.block_view(self.mri) returns = child.validate(**kwargs) ret = [] for k, v in serialize_object(returns).items(): if serialize_object(kwargs[k]) != v: ret.append(ParameterTweakInfo(k, v)) return ret @add_call_types def configure(self, context, **kwargs): # type: (AContext, **Any) -> None child = context.block_view(self.mri) # If we have done a save or load with the child having a particular # design then make sure the child now has that design design = self.saved_structure.get("design", "") if design: child.design.put_value(design) child.configure(**kwargs) @add_call_types def run(self, context): # type: (AContext) -> None context.unsubscribe_all() child = context.block_view(self.mri) child.completedSteps.subscribe_value(self.update_completed_steps) bad_states = [ss.DISABLING, ss.ABORTING, ss.FAULT] match_future = child.when_value_matches_async("state", ss.POSTRUN, bad_states) if child.state.value == ss.ARMED: self.run_future = child.run_async() else: child.resume() try: context.wait_all_futures(match_future) except BadValueError: # If child went into Fault state, raise the friendlier run_future # exception if child.state.value == ss.FAULT: raise self.run_future.exception() else: raise @add_call_types def post_run(self, context): # type: (AContext) -> None context.wait_all_futures(self.run_future) @add_call_types def seek(self, context, completed_steps): # type: (AContext, ACompletedSteps) -> None # Clear out the update_completed_steps and match_future subscriptions context.unsubscribe_all() child = context.block_view(self.mri) child.pause(completedSteps=completed_steps) @add_call_types def abort(self, context): # type: (AContext) -> None child = context.block_view(self.mri) child.abort() def update_completed_steps(self, value): # type: (int) -> None self.registrar.report(RunProgressInfo(value)) def update_part_configure_args(self, response): # Decorate validate and configure with the sum of its parts if isinstance(response, Delta): # Check if the changes contain more than just writeable change writeable_path = [ c[0] and c[0][-1] == "writeable" for c in response.changes ] if all(writeable_path): return else: # Return or Error is the end of our subscription, log and ignore self.log.debug("update_part_configure_args got response %r", response) return for change in response.changes: if change[0]: # Update self.configure_model.apply_change(*change) else: # Replace # TODO: should we make apply_change handle this? self.configure_model = deserialize_object(change[1]) # Extract the bits we need metas = OrderedDict() defaults = OrderedDict() required = [] for k, meta in self.configure_model.takes.elements.items(): if k not in self.ignore_configure_args: # Copy the meta so it belongs to the Controller we report to # TODO: should we pass the serialized version? metas[k] = deserialize_object(meta.to_dict()) if k in self.configure_model.defaults: defaults[k] = self.configure_model.defaults[k] if k in self.configure_model.takes.required: required.append(k) # Notify the controller that we have some new parameters to take self.registrar.report(ConfigureParamsInfo(metas, required, defaults))