class NoDiscoveryOptions(DiscoveryOptions): """ A DiscoveryOptions object that will never have hardcoded_discovery or serial_filter """ serial_filter = dictobj.Field(sb.overridden(None)) hardcoded_discovery = dictobj.Field(sb.overridden(None))
class Assets(dictobj.Spec): src = dictobj.Field( sb.overridden("{photons_interactor/static:resource}"), formatted=True, help="Folder where we can find the source of the assets", ) @property def assets_folder(self): return os.path.join(self.src, "dist", "static") def ensure_npm(self): if not shutil.which("npm"): raise PhotonsAppError( "Couldn't find npm, I suggest you use nvm...") @property def needs_install(self): return (not os.path.exists(os.path.join(self.src, "node_modules")) or os.environ.get("REBUILD") == 1) def run(self, *args, extra_env=None): env = dict(os.environ) if extra_env: env.update(extra_env) subprocess.check_call(["npm", *args], cwd=self.src, env=env)
class Assets(dictobj.Spec): src = dictobj.Field( sb.overridden("{arranger/static:resource}"), formatted=True, help="Folder where we can find the source of the assets", ) @property def dist(self): if os.environ.get("NODE_ENV", "production") == "development": return os.path.join(self.src, "dist", "dev") else: return os.path.join(self.src, "dist", "prod") def ensure_npm(self): if not shutil.which("npm"): raise PhotonsAppError("Couldn't find npm, I suggest you use nvm") @property def needs_install(self): return ( not os.path.exists(os.path.join(self.src, "node_modules")) or os.environ.get("REBUILD") == 1 ) def run(self, *args, no_node_env=False): env = None if no_node_env: env = dict(os.environ) if "NODE_ENV" in env: del env["NODE_ENV"] subprocess.check_call(["npm", *args], cwd=self.src, **({} if env is None else {"env": env}))
def extra_configuration_collection(self, configuration): """ Hook to do any extra configuration collection or converter registration Here we register our base configuration converters: photons_app .. autoattribute:: photons_app.option_spec.photons_app_spec.PhotonsAppSpec.photons_app_spec targets .. autoattribute:: photons_app.option_spec.photons_app_spec.PhotonsAppSpec.targets_spec target_register .. autoattribute:: photons_app.option_spec.photons_app_spec.PhotonsAppSpec.target_register_spec protocol_register The protocol_register object from photons_messages """ photons_app_spec = PhotonsAppSpec() self.register_converters( { "targets": photons_app_spec.targets_spec, "photons_app": photons_app_spec.photons_app_spec, "target_register": photons_app_spec.target_register_spec, "protocol_register": sb.overridden(protocol_register), "reference_resolver_register": photons_app_spec.reference_resolver_register_spec, }, configuration=configuration, )
class Database(dictobj.Spec): uri = dictobj.Field(format_into=database_uri_spec(), help="Uri to our database") db_migrations = dictobj.Field( sb.overridden( os.path.join("{interactor:resource}", "database", "migrations")), format_into=sb.directory_spec, )
def target_register_spec(self): """ Make a TargetRegister object """ return sb.create_spec( TargetRegister, collector=sb.formatted( sb.overridden("{collector}"), formatter=MergedOptionStringFormatter ), )
class Database(dictobj.Spec): uri = dictobj.Field(sb.string_spec, wrapper=sb.required, formatted=True, help="Uri to our database") db_migrations = dictobj.Field( sb.overridden( os.path.join("{photons_interactor:resource}", "database", "migrations")), format_into=sb.directory_spec, )
class Stuff(store.Command): three = dictobj.Field(sb.overridden("{wat}"), formatted=True) async def execute(self): return self
class Thing(store.Command): one = dictobj.Field(sb.integer_spec) two = dictobj.Field(sb.string_spec) three = dictobj.Field(sb.overridden("{wat}"), formatted=True)
class TileTransitionOptions(dictobj.Spec): background = dictobj.Field( sb.overridden( BackgroundOption.FieldSpec().empty_normalise(type="current")))
class Thing(dictobj.Spec): other = dictobj.Field(sb.overridden("{other}"), formatted=True) comms = dictobj.Field(sb.overridden("{comms}"), formatted=True)
class Target(dictobj.Spec): protocol_register = dictobj.Field(sb.overridden("{protocol_register}"), formatted=True) final_future = dictobj.Field(sb.overridden("{final_future}"), formatted=True) description = dictobj.Field(sb.string_spec, default="Base transport functionality") item_kls = Item script_runner_kls = ScriptRunner def session_kls(self, *args, **kwargs): raise NotImplementedError() @classmethod 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 send(self, msg, reference=None, **kwargs): return Sender(self, msg, reference, **kwargs) def script(self, raw): """Return us a ScriptRunner for the given `raw` against this `target`""" items = list(self.simplify(raw)) if not items: items = None elif len(items) > 1: original = items async def gen(*args, **kwargs): for item in original: yield item items = list( self.simplify(FromGenerator(gen, reference_override=True)))[0] else: items = items[0] return self.script_runner_kls(items, target=self) def session(self): info = {} class Session: async def __aenter__(s): session = info["session"] = await self.make_sender() return session async def __aexit__(s, exc_type, exc, tb): if "session" in info: await self.close_sender(info["session"]) return Session() async def make_sender(self): """Create an instance of the sender. This is designed to be shared.""" return self.session_kls(self) # backwards compatibility args_for_run = make_sender async def close_sender(self, sender): """Close a sender""" await sender.finish() # backwards compatibility close_args_for_run = close_sender def simplify(self, script_part): """ Used by ``self.script`` to convert ``raw`` into TransportItems For each item that is found: * Use as is if it already has a run method on it * Use item.simplified(self.simplify) if it has a simplified method * Otherwise, provide to self.item_kls For each leaf child that is found, we gather messages into groups of messages without a ``run`` method and yield ``self.item_kls(group)``. For example, let's say we have ``[p1, p2, m1, p3]`` where ``m1`` has a ``run`` method on it and the others don't, we'll yield: * ``self.item_kls([p1, p2])`` * ``m1`` * ``self.item_kls([p3])`` """ if type(script_part) is not list: script_part = [script_part] final = [] for p in script_part: if hasattr(p, "run"): final.append(p) elif hasattr(p, "simplified"): final.append(p.simplified(self.simplify)) continue else: final.append(p) buf = [] for p in final: if not hasattr(p, "run"): buf.append(p) else: if buf: yield self.item_kls(buf) buf = [] yield p if buf: yield self.item_kls(buf)
class PhotonsApp(dictobj.Spec): """ The main photons_app object. .. dictobj_params:: """ config = dictobj.Field( sb.file_spec, wrapper=sb.optional_spec, help="The root configuration file" ) extra = dictobj.Field( sb.string_spec, default="", help="The arguments after the ``--`` in the commandline" ) debug = dictobj.Field(sb.boolean, default=False, help="Whether we are in debug mode or not") artifact = dictobj.Field( default="", format_into=sb.string_spec, help="The artifact string from the commandline" ) reference = dictobj.Field( default="", format_into=sb.string_spec, help="The device(s) to send commands to" ) cleaners = dictobj.Field( lambda: sb.overridden([]), help="A list of functions to call when cleaning up at the end of the program", ) default_activate = dictobj.NullableField( sb.listof(sb.string_spec()), help="A list of photons modules to load by default" ) task_specifier = dictobj.Field( sb.delayed(task_specifier_spec()), help="Used to determine chosen task and target" ) @hp.memoized_property def final_future(self): return self.loop.create_future() @hp.memoized_property def graceful_final_future(self): fut = self.loop.create_future() fut.setup = False return fut @hp.memoized_property def loop(self): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) if self.debug: loop.set_debug(True) return loop @hp.memoized_property def extra_as_json(self): options = "{}" if self.extra in (None, "", sb.NotSpecified) else self.extra location = None if options.startswith("file://"): parsed = urlparse(options) location = os.path.abspath(f"{parsed.netloc}{unquote(parsed.path)}") if not os.path.exists(location): raise BadOption(f"The path {location} does not exist") with open(location, "r") as fle: options = fle.read() try: return json.loads(options) except (TypeError, ValueError) as error: kwargs = {"error": error} if location: kwargs["read_from"] = location raise BadOption("The options after -- wasn't valid json", **kwargs) def separate_final_future(self, sleep=0): other_future = asyncio.Future() def stop(): other_future.cancel() self.loop.remove_signal_handler(signal.SIGTERM) self.loop.add_signal_handler(signal.SIGTERM, stop) class CM: async def __aenter__(s): return other_future async def __aexit__(s, exc_typ, exc, tb): if sleep > 0: await asyncio.sleep(sleep) self.final_future.cancel() return CM() @contextmanager def using_graceful_future(self): """ This context manager is used so that a server may shut down before the real final_future is stopped. This is useful because many photons resources will stop themselves when the real final_future is stopped. But we may want to stop (say a server) before we run cleanup activities and mark final_future as done. Usage is like:: with photons_app.graceful_final_future() as final_future: try: await final_future except ApplicationStopped: await asyncio.sleep(7) """ final_future = self.final_future graceful_future = self.graceful_final_future graceful_future.setup = True reinstate_handler = False if platform.system() != "Windows": def stop(): if not graceful_future.done(): graceful_future.set_exception(ApplicationStopped) reinstate_handler = self.loop.remove_signal_handler(signal.SIGTERM) self.loop.add_signal_handler(signal.SIGTERM, stop) yield graceful_future # graceful future is no longer in use graceful_future.setup = False if reinstate_handler: def stop(): if not final_future.done(): final_future.set_exception(ApplicationStopped) self.loop.remove_signal_handler(signal.SIGTERM) self.loop.add_signal_handler(signal.SIGTERM, stop) async def cleanup(self, targets): for cleaner in self.cleaners: try: await cleaner() except asyncio.CancelledError: break except KeyboardInterrupt: break except (RuntimeWarning, Exception): exc_info = sys.exc_info() log.error(exc_info[1], exc_info=exc_info) for target in targets: try: if hasattr(target, "finish"): await target.finish() except asyncio.CancelledError: break except KeyboardInterrupt: break except (RuntimeWarning, Exception): exc_info = sys.exc_info() log.error(exc_info[1], exc_info=exc_info)
class Options(dictobj.Spec): port = dictobj.NullableField(sb.integer_spec()) service = dictobj.Field(sb.overridden(Services.UDP)) state_service = dictobj.Field(sb.overridden(Services.UDP))
class Options(dictobj.Spec): port = dictobj.Field(sb.overridden(56700)) service = dictobj.Field(sb.overridden(MemoryService)) state_service = dictobj.Field(sb.overridden(Services.UDP))