def generate(src, adjustments, output_folder): if "fields" in src: src["groups"] = src["fields"] src = Src.FieldSpec().normalise(Meta({}, []).at("src"), src) adjustments = Adjustments.FieldSpec().normalise( Meta({}, []).at("adjustment"), adjustments) Resolver(src, adjustments).resolve() if os.environ.get("NO_OUTPUT") == "1": return by_type = defaultdict(list) for options in adjustments.output: by_type[options.create].append(options) for t in ("enums", "fields"): if len(by_type[t]) != 1: raise errors.InvalidOutput(f"Must specify {t} output only once", found=len(by_type[t])) by_type[t] = by_type[t][0] log.info("Writing enums") write_enums(by_type["enums"], src, adjustments, output_folder) log.info("Writing fields") write_fields(by_type["fields"], src, adjustments, output_folder) log.info("Writing packets") write_packets(by_type["packets"], src, adjustments, output_folder)
def meta(self, config="", extra_prepare=None): with hp.a_temp_file() as fle: fle.write(dedent(config).encode()) fle.close() original = Collector.extra_prepare_after_activation class Prepare: def __enter__(s): if extra_prepare: def extra(*args, **kwargs): extra_prepare(*args, **kwargs) return original(*args, **kwargs) s.patch = mock.patch.object( Collector, "extra_prepare_after_activation", extra ) s.patch.start() def __exit__(s, exc_type, exc, tb): if hasattr(s, "patch"): s.patch.stop() with Prepare(), open(fle.name) as realfile: args_dict = {"photons_app": {"config": realfile}} app = App() logging_handler = mock.Mock(name="logging_handler") collector = app.setup_collector(args_dict, logging_handler, None) return Meta({"collector": collector}, [])
def __init__(self, store, **options): self.store = store everything = MergedOptions.using(options, {"commander": self}, dont_prefix=[dictobj]) self.meta = Meta(everything, [])
def meta(self, collector): def resolve(s): return DeviceFinder.from_url_str(s) collector.configuration["reference_resolver_register"].add("match", resolve) return Meta({"collector": collector}, []).at("test")
def resolve_packet_fields(self): for parent, _ in self.all_parents: fields = [] for field in parent.item_fields: bits = self.adjustments.field_attr(parent.full_name, field.full_name, "bits") if bits: for name in bits: meta = Meta({}, []).at(parent.full_name).at( field.full_name).at(name) f = struct_field_spec().normalise( meta, { "name": name, "type": "bit", "size_bits": 0 }) f.type = ft.SimpleType("bit", 1) fields.append(f) continue if isinstance(field.type, ft.UnionType): fields.append(field) elif getattr(field.type, "expanded", False): expand_structs = isinstance(field.type, ft.PacketType) fields.extend( field.expand_fields(expand_structs=expand_structs)) else: fields.append(field) parent.item_fields = fields
def setup_addon_register(self, photons_app, __main__): """Setup our addon register""" # Create the addon getter and register the crosshair namespace self.addon_getter = AddonGetter() self.addon_getter.add_namespace("lifx.photons", Result.FieldSpec(), Addon.FieldSpec()) # Initiate the addons from our configuration register = Register(self.addon_getter, self) if "addons" in photons_app: addons = photons_app["addons"] if type(addons) in (MergedOptions, dict) or getattr(addons, "is_dict", False): spec = sb.dictof(sb.string_spec(), sb.listof(sb.string_spec())) meta = Meta(photons_app, []).at("addons") for namespace, adns in spec.normalise(meta, addons).items(): register.add_pairs(*[(namespace, adn) for adn in adns]) elif photons_app.get("default_activate"): for comp in photons_app["default_activate"]: register.add_pairs(("lifx.photons", comp)) if __main__ is not None: register.add_pairs(("lifx.photons", "__main__")) # Import our addons register.recursive_import_known() # Resolve our addons register.recursive_resolve_imported() return register
async def apply_theme(collector, target, reference, artifact, **kwargs): """ Apply a theme to specified device ``lan:apply_theme d073d5000001 -- `{"colors": [<color>, <color>, ...], "theme": "SPLOTCH", "overrides": {<hsbk dictionary>}}'`` If you don't specify serials, then the theme will apply to all devices found on the network. Colors may be words like "red", "blue", etc. Or may be [h, s, b, k] arrays where each part is optional. theme must be a valid theme type and defaults to SPLOTCH You may also specify ``duration`` which is how long to take to apply in seconds. And you may also supply ``overrides`` with ``hue``, ``saturation``, ``brightness`` and ``kelvin`` to override the specified colors. """ extra = collector.photons_app.extra_as_json everything = {} if "overrides" in extra: everything["overrides"] = extra["overrides"] if "colors" not in extra: extra["colors"] = default_colors options = Options.FieldSpec().normalise(Meta(everything, []), extra) def errors(e): log.error(e) await target.send(ApplyTheme.script(options), reference, error_catcher=errors)
def make_command(self, meta, val, existing): v = sb.set_options(path=sb.required(sb.string_spec()), allow_ws_only=sb.defaulted(sb.boolean(), False)).normalise( meta, val) path = v["path"] allow_ws_only = v["allow_ws_only"] if path not in self.paths: raise NoSuchPath(path, sorted(self.paths)) val = sb.set_options(body=sb.required( sb.set_options(args=sb.dictionary_spec(), command=sb.required(sb.string_spec())))).normalise( meta, val) args = val["body"]["args"] name = val["body"]["command"] if existing: name = val["body"]["command"] = f"{existing['path']}:{name}" extra_context = {} if existing: extra_context["_parent_command"] = existing["command"] everything = meta.everything if isinstance(meta.everything, MergedOptions): everything = meta.everything.wrapped() everything.update(extra_context) meta = Meta(everything, []).at("body") available_commands = self.paths[path] if name not in available_commands: raise BadSpecValue( "Unknown command", wanted=name, available=self.available(available_commands, allow_ws_only=allow_ws_only), meta=meta.at("command"), ) command = available_commands[name]["spec"].normalise( meta.at("args"), args) if not allow_ws_only and command.__whirlwind_ws_only__: raise BadSpecValue( "Command is for websockets only", wanted=name, available=self.available(available_commands, allow_ws_only=allow_ws_only), meta=meta.at("command"), ) return command, name
def normalise(self, meta, val): if "SERIAL_FILTER" in os.environ and self.see_env: val = os.environ["SERIAL_FILTER"].split(",") if val == ["null"]: return None meta = Meta(meta.everything, []).at("${SERIAL_FILTER}") if val in (None, sb.NotSpecified): return val return sb.listof(serial_spec()).normalise(meta, val)
def make_photons_app(cleanup, **options): photons_app = PhotonsApp.FieldSpec(formatter=MergedOptionStringFormatter).normalise( Meta({}, []).at("photons_app"), options ) photons_app.loop = asyncio.new_event_loop() with mock.patch.object(photons_app, "cleanup", cleanup): yield photons_app
def make(): if cache["info"] is None: make.resolved = True meta = Meta(self.collector.configuration, []).at("targets").at(name).at("options") cache["info"] = ( target.type, self.types[target.type].normalise(meta, target.options), ) return cache["info"]
def make_meta(message_id, **kwargs): return Meta( { "message_id": message_id, "final_future": final_future, "progress_cb": progress_cb, **kwargs, }, [], )
def get_extra_files(self, result, source, configuration, path): extra = hp.nested_dict_retrieve(result, path, []) config_root = {"config_root": result.get("config_root", configuration.get("config_root"))} config = configuration.wrapped() config.update(result, source=source) config.update(config_root) meta = Meta(config, []).at("photons_app").at("extra_files") return extra_files_spec(source).normalise(meta, extra)
def theme_msg(self, gatherer): everything = {} theme_options = dict(self.theme_options) if "overrides" in theme_options: everything["overrides"] = theme_options["overrides"] if "colors" not in theme_options: theme_options["colors"] = default_colors options = ThemeOptions.FieldSpec().normalise(Meta(everything, []), theme_options) return ApplyTheme.script(options, gatherer=gatherer)
def make_run_options(val, animation_options): if isinstance(val, RunOptions): return val if isinstance(val, list): val = {"animations": val} elif not val or val is sb.NotSpecified: val = { "animations": [ [ "swipe", { "line_hues": ["0-10", "100-150"], "fade_amount": 0.2 } ], # ["falling", { "num_seconds": 10 }], # ["balls", { "num_seconds": 10 }], # ["twinkles", { "num_seconds": 5, "skip_next_transition": True }], # ["dots", { "skip_next_transition": True }], # ["dice", { "num_iterations": 1 }], # [ "color_cycle", { "changer": "vertical_morph", "num_seconds": 10 } ], ] } meta = Meta({"animation_options": animation_options}, []) return RunOptions.FieldSpec( formatter=MergedOptionStringFormatter).normalise(meta, val)
def normalise(self, meta, val): if "HARDCODED_DISCOVERY" in os.environ and self.see_env: meta = Meta(meta.everything, []).at("${HARDCODED_DISCOVERY}") try: val = json.loads(os.environ["HARDCODED_DISCOVERY"]) except (TypeError, ValueError) as error: raise BadSpecValue( "Found HARDCODED_DISCOVERY in environment, but it was not valid json", reason=error, meta=meta, ) if val in (sb.NotSpecified, None): return val return self.spec.normalise(meta, val)
def msg(kls, options): if not isinstance(options, Options): options = Options.FieldSpec().normalise(Meta(options, []), options) async def gen(reference, sender, **kwargs): serials = [] canvases = [] combined_canvas = Canvas() plans = sender.make_plans("parts") async for serial, _, info in sender.gatherer.gather(plans, reference, **kwargs): serials.append(serial) for part in info: if part.device.cap.has_chain: combined_canvas.add_parts(part) else: nxt = Canvas() nxt.add_parts(part) canvases.append(nxt) if combined_canvas: canvases.append(combined_canvas) msgs = [] if options.power_on: for serial in serials: msgs.append( LightMessages.SetLightPower( level=65535, duration=options.duration, target=serial, res_required=False, ) ) for canvas in canvases: Applier(canvas, options.colors).apply() for msg in canvas.msgs( options.override_layer, duration=options.duration, acks=True ): msgs.append(msg) yield msgs return FromGenerator(gen)
def meta(self, superman, batman, vegemite, collector): reg = collector.configuration["target_register"] HeroTarget = mock.Mock(name="HeroTarget") herotarget = Target.FieldSpec().empty_normalise(type="hero") reg.register_type("hero", HeroTarget) VillianTarget = mock.Mock(name="VillianTarget") villiantarget = Target.FieldSpec().empty_normalise(type="villian") reg.register_type("villian", VillianTarget) supermancreator = mock.Mock(name="supermancreator", return_value=superman) reg.add_target("superman", herotarget, supermancreator) batmancreator = mock.Mock(name="batmancreator", return_value=batman) reg.add_target("batman", herotarget, batmancreator) vegemitecreator = mock.Mock(name="vegemitecreator", return_value=vegemite) reg.add_target("vegemite", villiantarget, vegemitecreator) return Meta({"collector": collector}, []).at("test")
t = T.create(config, {"one": 20}) assert t.protocol_register is protocol_register assert t.final_future is final_future assert t.one == 20 assert t.item_kls is Item assert t.script_runner_kls is ScriptRunner describe "normalise": async it "gets protocol_register and final_future from the meta": protocol_register = mock.Mock(name="protocol_register") final_future = mock.Mock(name="final_future") config = {"protocol_register": protocol_register, "final_future": final_future} meta = Meta(config, []).at("transport") spec = Target.FieldSpec(formatter=MergedOptionStringFormatter) t = spec.normalise(meta, {}) assert t.protocol_register is protocol_register assert t.final_future is final_future describe "Usage": describe "script": @pytest.fixture() def script(self): return mock.Mock(name="script")
it "can get target_values", collector: target1 = mock.Mock(name="target1") maker1 = mock.Mock(name="maker1", return_value=("type1", target1)) target2 = mock.Mock(name="target2") maker2 = mock.Mock(name="maker2", return_value=("type2", target2)) register = TargetRegister(collector) register.targets["target1"] = maker1 register.targets["target2"] = maker2 assert register.target_values == [target1, target2] it "can get used targets", collector: meta = Meta({}, []).at("targets") targets = PhotonsAppSpec().targets_spec.normalise( meta, { "target1": {"type": "example", "options": {"one": 1}}, "target2": {"type": "example", "options": {"one": 2}}, }, ) class T(dictobj.Spec): one = dictobj.Field(sb.integer_spec) register = TargetRegister(collector) register.register_type("example", T.FieldSpec()) for name, options in targets.items(): register.add_target(name, options)
it "complains if nothing was specified and is mandatory", val, special: spec = reference_spec(mandatory=True, special=special) with assertRaises(BadOption, "This task requires you specify a reference"): spec.normalise(Meta.empty(), val) @pytest.mark.parametrize("val", [None, "", sb.NotSpecified]) it "returns not specified if nothing was specified and isn't mandatory", val: spec = reference_spec(mandatory=False, special=False) assert spec.normalise(Meta.empty(), val) is sb.NotSpecified @pytest.mark.parametrize("val", [None, "", sb.NotSpecified]) it "returns a reference object if nothing but not mandatory", val, collector: spec = reference_spec(mandatory=False, special=True) assert isinstance( spec.normalise(Meta({"collector": collector}, []).at("test"), val), FoundSerials ) describe "with a value": @pytest.fixture() def meta(self, collector): def resolve(s): return DeviceFinder.from_url_str(s) collector.configuration["reference_resolver_register"].add("match", resolve) return Meta({"collector": collector}, []).at("test") @pytest.mark.parametrize( "special,mandatory", [(False, False), (False, True), (True, False), (True, True)]
"/v2": {"three": three}, "/v3": {"hello/five": five}, } describe "command decorator": it "uses the formatter given to the store": store = Store(formatter=MergedOptionStringFormatter) @store.command("thing", path="/v1") class Thing(store.Command): one = dictobj.Field(sb.integer_spec) two = dictobj.Field(sb.string_spec) three = dictobj.Field(sb.overridden("{wat}"), formatted=True) wat = mock.Mock(name="wat") meta = Meta({"wat": wat}, []).at("options") thing = store.paths["/v1"]["thing"]["spec"].normalise(meta, {"one": 2, "two": "yeap"}) assert thing == {"one": 2, "two": "yeap", "three": wat} it "complains if you try to reuse a command": store = Store() @store.command("thing") class Thing(store.Command): pass # Can use a child kls @store.command("another_path") class Other(Thing): pass
it "complains if instantiated with bad options", device: class Op(Operator): class Options(Operator.Options): will_be_wrong = dictobj.Field(sb.integer_spec()) will_be_missing = dictobj.Field(sb.boolean, wrapper=sb.required) try: Op(device, options={"will_be_wrong": "nope"}) except BadSpecValue as error: assert len(error.errors) == 2 assertSameError( error.errors[0], BadSpecValue, "Expected a value but got none", {"meta": Meta({}, []).at("will_be_missing")}, [], ) assertSameError( error.errors[1], BadSpecValue, "Expected an integer", {"meta": Meta({}, []).at("will_be_wrong"), "got": str}, [], ) else: assert False, "Expected an error" it "puts options and device attrs on operator before setup", device: got = []
async def execute(self, path, body, extra_options=None, allow_ws_only=False): """ Responsible for creating a command and calling execute on it. If command is not already a Command instance then we normalise it into one. We have available on the meta object: __init__ options Anything that is provided to the Commander and Executor at __init__ store The store of commands path The path that was passed in executor This executor request_future A future that is cancelled after execute is finished extra options Anything provided as extra_options to this function """ request_future = asyncio.Future() request_future._merged_options_formattable = True try: everything = MergedOptions.using( self.commander.meta.everything, { "path": path, "store": self.commander.store, "executor": self, "progress_cb": self.progress_cb, "request_future": request_future, "request_handler": self.request_handler, }, self.extra_options, extra_options or {}, dont_prefix=[dictobj], ) meta = Meta(everything, self.commander.meta.path).at("<input>") execute = self.commander.store.command_spec.normalise( meta, { "path": path, "body": body, "allow_ws_only": allow_ws_only }) return await execute() finally: request_future.cancel()
describe "Communication": async it "is formattable", V: class Other: pass other = Other() options = {"comms": V.communication, "other": other} class Thing(dictobj.Spec): other = dictobj.Field(sb.overridden("{other}"), formatted=True) comms = dictobj.Field(sb.overridden("{comms}"), formatted=True) thing = Thing.FieldSpec(formatter=MergedOptionStringFormatter).normalise( Meta(options, []).at("thing"), {} ) assert thing.comms is V.communication assert thing.other == str(other) async it "takes in a transport_target", V: assert V.communication.transport_target is V.transport_target assert V.communication.found == Found() assert isinstance(V.communication.receiver, Receiver) async it "has a stop fut", V: assert not V.communication.stop_fut.done() V.communication.stop_fut.cancel() assert not V.final_future.cancelled()
def meta(s): return Meta({"collector": s.collector, "final_future": s.final_future}, []).at( "options" )
def create(kls, configuration, options=None): options = options if options is not None else configuration meta = Meta(configuration, []).at("options") return kls.FieldSpec(formatter=MergedOptionStringFormatter).normalise( meta, options)
def meta(s): options = MergedOptions.using( {"target_register": s.target_register}, dont_prefix=[mock.Mock] ) return Meta(options, [])
def creator(name, typ, target): meta = Meta(self.configuration, []).at("targets").at(name).at("options") t = typ.normalise(meta, target.options) t.instantiated_name = name return t