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
async def transform(collector, target, reference, **kwargs): """ Do a http-api like transformation over whatever target you specify ``target:transform d073d5000000 -- '{"color": "red", "effect": "pulse"}'`` It takes in ``color``, ``effect``, ``power`` and valid options for a ``SetWaveformOptional``. You may also specify ``transform_options`` that change how the transform works. keep_brightness Ignore brightness options in the request transition_color If the light is off and we power on, setting this to True will mean the color of the light is not set to the new color before we make it appear to be on. This defaults to False, which means it will appear to turn on with the new color """ extra = collector.photons_app.extra_as_json extra = sb.dictionary_spec().normalise(Meta.empty(), extra) transform_options = sb.set_options( transform_options=sb.dictionary_spec()).normalise( Meta.empty(), extra)["transform_options"] msg = Transformer.using(extra, **transform_options) if not msg: raise PhotonsAppError( 'Please specify valid options after --. For example ``transform -- \'{"power": "on", "color": "red"}\'``' ) await target.send(msg, reference)
async def execute_task(self, **kwargs): options = sb.set_options( indication=sb.required(sb.boolean()), duration_s=sb.required(sb.integer_spec()), ).normalise(Meta.empty(), self.photons_app.extra_as_json) await self.target.send(SetCleanConfig(**options), self.reference, **kwargs)
async def make_crontab(collector, target, reference, artifact, **kwargs): """ Make a crontab file executing our day dusk options. Usage is:: ./generate-crontab.py """ collector.register_converters( {"daydusk": DayDusk.FieldSpec(formatter=MergedOptionStringFormatter)}) daydusk = collector.configuration["daydusk"] spec = sb.set_options(path=sb.defaulted(sb.string_spec(), '/config/daydusk.crontab'), lifx_script=sb.defaulted(sb.string_spec(), '/usr/local/bin/lifx')) extra = collector.configuration["photons_app"].extra_as_json kwargs = { k: v for k, v in spec.normalise(Meta.empty(), extra).items() if v is not sb.NotSpecified } cronfile = kwargs['path'] lifx_script = kwargs['lifx_script'] if not daydusk.schedules: raise NoSchedules() cron = CronTab() extra_script_args = ["--silent"] for name, options in daydusk.schedules.items(): script_args = {**options.hsbk, **options.extra} command = [ lifx_script, options.task, options.reference, *extra_script_args, "--", json.dumps(script_args), ] command = str(" ".join([shlex.quote(part) for part in command])) job = cron.new(command=command) job.dow.on(*options.dow) job.minute.on(options.minute) job.hour.on(options.hour) if os.path.exists(cronfile): os.remove(cronfile) cron.write(cronfile) print(f"Generated crontab at {cronfile}")
def normalise_filled(self, meta, val): options_spec = sb.set_options( filename=sb.required(sb.string_spec()), optional=sb.defaulted(sb.boolean(), False) ) lst_spec = sb.listof(sb.or_spec(sb.string_spec(), options_spec)) if isinstance(val, list): val = {"after": lst_spec.normalise(meta, val)} val = sb.set_options(after=lst_spec, before=lst_spec).normalise(meta, val) formatted = sb.formatted(sb.string_spec(), formatter=MergedOptionStringFormatter) for key in ("after", "before"): result = [] for i, thing in enumerate(val[key]): filename = thing optional = False if isinstance(thing, dict): filename = thing["filename"] optional = thing["optional"] filename = formatted.normalise(meta.at(key).indexed_at(i), filename) if optional and not os.path.exists(filename): log.warning(hp.lc("Ignoring optional configuration", filename=filename)) continue if not os.path.exists(filename): raise BadConfiguration( "Specified extra file doesn't exist", source=self.source, filename=filename, meta=meta, ) result.append(filename) val[key] = result return self.extras_spec.normalise(meta, val)
async def execute_task(self, **kwargs): broadcast = True if self.artifact is not sb.NotSpecified: broadcast = self.artifact options = sb.set_options( cli_output=sb.defaulted(sb.boolean(), None), env_output=sb.defaulted(sb.boolean(), False), settings_output=sb.defaulted(sb.boolean(), False), ).normalise(Meta.empty(), self.collector.photons_app.extra_as_json) if options["env_output"] is False and options[ "settings_output"] is False: if options["cli_output"] is None: options["cli_output"] = True env_output = options["env_output"] cli_output = options["cli_output"] settings_output = options["settings_output"] ips = {} async with self.target.session() as sender: found, serials = await self.reference.find(sender, timeout=20, broadcast=broadcast) for serial in serials: services = found[binascii.unhexlify(serial)] if Services.UDP in services: ip = services[Services.UDP].host ips[serial] = ip sorted_ips = sorted(ips.items(), key=lambda item: ipaddress.ip_address(item[1])) if cli_output: for serial, ip in sorted_ips: print(f"{serial}: {ip}") if cli_output and (env_output or settings_output): print() if env_output: print(f"export HARDCODED_DISCOVERY='{json.dumps(sorted_ips)}'") if settings_output: print() if settings_output: print("discovery_options:") print(" hardcoded_discovery:") for serial, ip in sorted_ips: print(f' {serial}: "{ip}"')
def normalise_empty(self, meta): env = os.environ.get("NOISY_NETWORK_LIMIT") if env: if env == "true": env = 2 elif env == "null": env = 0 return sb.integer_spec().normalise(meta, env) animation_options = sb.set_options( noisy_network_limit=sb.defaulted(sb.integer_spec(), 0)).normalise( meta, meta.everything.get("animation_options") or {}) if animation_options["noisy_network_limit"]: return animation_options["noisy_network_limit"] return 0
spec = scene_spec.hsbk() with assertRaises( BadSpecValue, "Number must be between min and max", minimum=1500, maximum=9000 ): spec.normalise(meta, [1, 0, 0, 1000]) with assertRaises( BadSpecValue, "Number must be between min and max", minimum=1500, maximum=9000 ): spec.normalise(meta, [360, 1, 1, 9001]) describe "json_string_spec": describe "storing": it "loads if the val is a string and returns as dumps", meta: spec = sb.set_options(one=sb.integer_spec()) spec = scene_spec.json_string_spec(spec, True) got = spec.normalise(meta, '{"one": 2, "two": 3}') assert got == '{"one": 2}' it "doesn't loads if not a string and returns as dumps", meta: spec = sb.set_options(one=sb.integer_spec()) spec = scene_spec.json_string_spec(spec, True) got = spec.normalise(meta, {"one": 2, "two": 3}) assert got == '{"one": 2}' it "complains if string is not valid json", meta: spec = sb.set_options(one=sb.integer_spec()) spec = scene_spec.json_string_spec(spec, True) with assertRaises(BadSpecValue, "Value was not valid json"): spec.normalise(meta, "{")
def setup(self): self.spec = sb.dictof( service_type_spec(), sb.set_options(host=sb.required(sb.string_spec()), port=sb.required(sb.integer_spec())), )