async def stream(self, animation_state): self.started = time.time() del self.ticker async def tick(): async with self.ticker as ticks: async for result in ticks: yield result def errors(e): if not isinstance(e, asyncio.CancelledError): log.error(hp.lc(error=e, error_type=type(e))) with hp.ChildOfFuture( self.final_future, name="Animation::streamer[stop_fut]") as stop_fut: async with hp.ResultStreamer( stop_fut, error_catcher=errors, exceptions_only_to_error_catcher=True, name=f"Animation({self.__class__.__name__})", ) as streamer: await streamer.add_generator(tick(), context=AnimationEvent.Types.TICK) await streamer.add_generator( self.make_user_events(animation_state), context=AnimationEvent.Types.USER_EVENT) streamer.no_more_work() async for result in streamer: if result.value is hp.ResultStreamer.GeneratorComplete: continue yield result
def spawn(self, address, backoff=0.05, timeout=10): """Spawn a socket for this address""" if self.stop_fut.cancelled(): raise PhotonsAppError("The target has been cancelled") # Don't care about port, only care about host if type(address) is tuple: address = address[0] if address in self.sockets: fut = self.sockets[address] if not fut.done(): return fut if not fut.cancelled() and not fut.exception(): transport = fut.result() if self.is_transport_active(transport): return fut del self.sockets[address] # Initialize a spot for this address if address not in self.sockets: self.sockets[address] = hp.ChildOfFuture(self.stop_fut) # Ok, let's do this! self._spawn(address, self.sockets[address], backoff, timeout) # And return our future return self.sockets[address]
def __init__( self, sender, *, limit=30, finder=None, forget_after=30, final_future=None, search_interval=20, time_between_queries=None, ): self.sender = sender self.search_interval = search_interval self.time_between_queries = time_between_queries final_future = final_future or sender.stop_fut self.final_future = hp.ChildOfFuture( final_future, name="DeviceFinderDaemon::__init__[final_future]" ) self.own_finder = not bool(finder) self.finder = finder or Finder( self.sender, self.final_future, forget_after=forget_after, limit=limit ) self.ts = hp.TaskHolder(self.final_future, name="DeviceFinderDaemon::__init__[ts]") self.hp_tick = hp.tick
def __init__(self, target): self.transport_target = target self.found = Found() self.stop_fut = hp.ChildOfFuture(self.transport_target.final_future) self.receiver = Receiver() self.received_data_tasks = [] self.make_plans = __import__( "photons_control.planner").planner.make_plans self.setup()
def __init__(self, stop_fut, transport_target, protocol_register, default_broadcast="255.255.255.255"): self.transport_target = transport_target self.found = {} self.stop_fut = hp.ChildOfFuture(stop_fut) self.device_source = self.generate_source() self.broadcast_source = self.generate_source() self.protocol_register = protocol_register self.default_broadcast = default_broadcast
def __init__(self, item, stop_fut, reference, sender, kwargs): self.item = item self.kwargs = kwargs self.reference = reference self.sender = sender self.stop_fut = hp.ChildOfFuture( stop_fut, name="FromGenerator>Runner::__init__[stop_fut]") self.streamer = hp.ResultStreamer( self.stop_fut, name="FromGenerator>Runner::__init__[streamer]", error_catcher=squash, )
def __init__(self, sender, final_future=None, *, forget_after=30, limit=30): self.sender = sender self.forget_after = forget_after self.limit = limit if isinstance(self.limit, int): self.limit = asyncio.Semaphore(self.limit) self.devices = {} self.last_seen = {} self.searched = hp.ResettableFuture(name="Finder::__init__[searched]") self.collections = Collections() self.final_future = hp.ChildOfFuture( final_future or self.sender.stop_fut, name="Finder::__init__[final_future]" )
def __init__(self, target): self.transport_target = target self.found = Found() self.stop_fut = hp.ChildOfFuture( self.transport_target.final_future, name=f"{type(self).__name__}.__init__|stop_fut|") self.receiver = Receiver() self.received_data_tasks = hp.TaskHolder( self.stop_fut, name=f"{type(self).__name__}.__init__|received_data_tasks|") self.make_plans = __import__( "photons_control.planner").planner.make_plans self.setup()
def __init__(self, stop_fut, writer, retry_options): self.writer = writer self.results = [] self.retry_options = retry_options self.final_future = hp.ChildOfFuture(stop_fut) def ignore(f): # Make sure we don't leak tasks if hasattr(self, "_writings"): self._writings.cancel() del self._writings del self._writings_cb self.results = [] # I don't care about the exception from final_future if not f.cancelled(): f.exception() self.add_done_callback(ignore)
def __init__(self, sender, final_future=None, *, forget_after=30, limit=30): self.sender = sender self.forget_after = forget_after self.limit = limit if isinstance(self.limit, int): self.limit = asyncio.Semaphore(self.limit) self.devices = {} self.last_seen = {} self.searcher = Searcher(sender) self.collections = Collections() self.final_future = hp.ChildOfFuture(final_future or self.sender.stop_fut)
async def start( self, identity, reference, *, run_options=sb.NotSpecified, animations=sb.NotSpecified, ): pauser = asyncio.Semaphore() final_future = hp.ChildOfFuture( self.final_future, name=f"Animations::start({identity})[final_future]") if run_options is sb.NotSpecified: run_options = {} if animations is not sb.NotSpecified: run_options = MergedOptions.using(run_options, { "animations": animations }).as_dict() runner = AnimationRunner( self.sender, reference, run_options, final_future=final_future, error_catcher=errors, animation_options=self.animation_options, ) runner.run_options.pauser = pauser def remove(res): if identity in self.animations: del self.animations[identity] self.animations[identity] = Animation(final_future, identity, runner, pauser).start( self.tasks, remove) return self.info(started=identity)
def __init__(self, sender, reference, run_options, *, final_future, animation_options=None, **kwargs): self.sender = sender self.kwargs = kwargs self.reference = reference self.run_options = make_run_options(run_options, animation_options) self.final_future = hp.ChildOfFuture( final_future, name="AnimationRunner::__init__[final_future]") self.original_canvas = Canvas() self.started = None self.collected = {} self.animations_ran = 0 self.current_animation = None self.seen_serials = set() self.used_serials = set()
def __init__( self, sender, *, limit=30, finder=None, forget_after=30, final_future=None, search_interval=20, time_between_queries=None, ): self.sender = sender self.search_interval = search_interval self.time_between_queries = time_between_queries final_future = final_future or sender.stop_fut self.final_future = hp.ChildOfFuture(final_future) self.own_finder = not bool(finder) self.finder = finder or Finder(self.sender, self.final_future, forget_after=forget_after, limit=limit)
async def animate(self, ts, cannon, state, animations): ans = iter(animations()) animation = None while True: self.animations_ran += 1 try: make_animation, background = ans.send(animation) except StopIteration: break with hp.ChildOfFuture( self.final_future, name="AnimationRunner::animate[animation_fut]" ) as animation_fut: animation = make_animation(animation_fut, self.run_options.pauser) self.current_animation = animation try: await state.set_animation(animation, background) async for messages in state.messages(): by_serial = defaultdict(list) for msg in messages: by_serial[msg.serial].append(msg) for serial, msgs in by_serial.items(): ts.add(cannon.fire(ts, serial, msgs)) except asyncio.CancelledError: raise except Finish: pass except Exception: log.exception("Unexpected error running animation")
async def start_session(self, final_future, parent_ts): self.last_final_future = final_future self.parent_ts = parent_ts self.final_future = hp.ChildOfFuture( final_future, name= f"{self.__class__.__name__}({self.device.serial}::start_session[final_future]", ) self.ts = hp.TaskHolder( self.final_future, name= f"{self.__class__.__name__}({self.device.serial}::start_session[ts]", ) self.incoming = hp.Queue( self.final_future, name= f"{self.__class__.__name__}({self.device.serial}::start_session[incoming]", ) await self.ts.start() self.ts.add(self.incoming_loop())
import asyncio import pytest @pytest.fixture() def final_future(): fut = hp.create_future(name="final") try: yield fut finally: fut.cancel() describe "ChildOfFuture": async it "takes in an original future", final_future: with hp.ChildOfFuture(final_future) as fut: assert fut.original_fut is final_future assert fut.name is None assert isinstance(fut.fut, asyncio.Future) assert fut.fut.name == "ChildOfFuture(None)::__init__[fut]" assert repr(fut) == "<ChildOfFuture#None((pending))<Future#final(pending)>>" with hp.ChildOfFuture(final_future, name="FUTZ") as fut: assert fut.original_fut is final_future assert fut.name == "FUTZ" assert isinstance(fut.fut, asyncio.Future) assert fut.fut.name == "ChildOfFuture(FUTZ)::__init__[fut]" assert repr(fut) == "<ChildOfFuture#FUTZ((pending))<Future#final(pending)>>"
task.cancel() with assertRaises(asyncio.CancelledError): await task finish.assert_called_once_with(asyncio.CancelledError, mock.ANY, mock.ANY) describe "finishing by final_future": async it "stops retrieving if there is results left to yield": called = [] gen_fut = hp.create_future() func_fut = hp.create_future() func3_fut = hp.create_future() final_future = hp.ChildOfFuture(hp.create_future()) async def gen(): called.append(("started", "gen")) try: yield 1 yield 2 await gen_fut yield 3 except asyncio.CancelledError: called.append(("cancelled", "gen")) raise async def func(): called.append(("started", "func")) await func_fut
def start(self, animation, target, serials, reference, afr, options, stop_conflicting=False): repeat = False shuffle = False if animation in self.presets: animations = list(self.presets[animation]) repeat = options.get("repeat", True) shuffle = options.get("shuffle", True) elif animation in self.animators: animations = [ PresetAnimation(animation=animation, options=options) ] else: raise NoSuchAnimation(wanted=animation, available=list(self.animators)) conflicting = set() for aid, info in self.animations.items(): if not info["task_future"].done(): for s in info["serials"]: if s in serials: if stop_conflicting: self.stop(aid) else: conflicting.add(s) remaining = set(serials) - conflicting if not remaining: raise AllSerialsAlreadyAnimating(want=serials, conflicting=list(conflicting)) serials = list(remaining) animation_id = str(uuid.uuid4()) pauser = asyncio.Condition() final_future = hp.ChildOfFuture(afr.stop_fut) info = { "pauser": pauser, "paused": False, "final_future": final_future, "started": time.time(), "serials": serials, "name": animations[0].animation, } async def coro(): while True: if shuffle: random.shuffle(animations) for i, a in enumerate(animations): if final_future.done(): return if info["name"] != a.animation: info["name"] = a.animation self.activate_listeners() opts = dict(a.options) if "combine_tiles" not in a.options and options.get( "combine_tiles"): opts["combine_tiles"] = True try: await self.animators[a.animation].animate( target, afr, final_future, reference, opts, pauser=info["pauser"], global_options=self.global_options, ) except Finish: pass except asyncio.CancelledError: raise except Exception as error: log.exception(error) await asyncio.sleep(1) if final_future.done(): return if len(animations) > 1 and (i < (len(animations) - 1) or repeat): info["name"] = "transition" self.activate_listeners() try: await transition_animation.animate( target, afr, final_future, reference, options, self.global_options) except Finish: pass except asyncio.CancelledError: raise except Exception as error: log.exception(error) await asyncio.sleep(1) if not repeat: return task_future = asyncio.ensure_future(hp.async_as_background(coro())) info["task_future"] = task_future async def remove_after_a_minute(): await asyncio.sleep(60) self.remove(animation_id) def activate(*args): self.activate_listeners() if "stopped" not in info: info["stopped"] = time.time() self.removers.append( hp.async_as_background(remove_after_a_minute())) self.removers = [t for t in self.removers if not t.done()] info["task_future"].add_done_callback(activate) self.animations[animation_id] = info self.activate_listeners() return animation_id
def cof(s): return hp.ChildOfFuture(s.orig_fut)
V.received( LightMessages.GetColor(), LightMessages.GetColor(), LightMessages.GetColor(), DeviceMessages.GetHostFirmware(), keep_duplicates=True, ) ff.cancel() checker_task = None time_between_queries = {"FIRMWARE": 100} await FoundSerials().find(V.sender, timeout=1) with hp.ChildOfFuture(V.final_future) as ff: async with hp.TaskHolder(ff, name="TEST") as ts: checker_task = ts.add(checker(ff)) ts.add( V.device.refresh_information_loop( V.sender, time_between_queries, V.finder.collections ) ) await checker_task async it "can start an information loop for a switch", fake_time, sender, finder, final_future: V = VBase(fake_time, sender, finder, final_future) await V.choose_device("switch") fake_time.set(1)
def __init__(self, stop_fut, data_receiver): self.stop_fut = hp.ChildOfFuture(stop_fut) self.data_receiver = data_receiver self.sockets = {}
async def send_single(self, original, packet, *, timeout, no_retry=False, broadcast=False, connect_timeout=10): transport, is_broadcast = await self._transport_for_send( None, packet, original, broadcast, connect_timeout) retry_gaps = self.retry_gaps(original, transport) writer = Writer( self, transport, self.receiver, original, packet, retry_gaps, did_broadcast=is_broadcast, connect_timeout=connect_timeout, ) results = [] unlimited = False if hasattr(original, "Meta"): unlimited = original.Meta.multi == -1 async def wait_for_remainders(tick_fut, streamer_fut): try: await hp.wait_for_all_futures(tick_fut) finally: if unlimited and any(result.wait_for_result() for result in results): await hp.wait_for_first_future(*results) streamer_fut.cancel() tick_fut = hp.ChildOfFuture( self.stop_fut, name= f"SendPacket({original.pkt_type, packet.serial})::send_single[tick_fut]", ) streamer_fut = hp.ChildOfFuture( self.stop_fut, name= f"SendPacket({original.pkt_type, packet.serial})::send_single[streamer_fut]", ) retry_ticker = retry_gaps.retry_ticker( name= f"{type(self).__name__}({type(transport).__name__})::retry_ticker") with tick_fut, streamer_fut: async with hp.ResultStreamer( streamer_fut, name= f"SendPacket({original.pkt_type, packet.serial}).send_single" ) as streamer: await streamer.add_generator( retry_ticker.tick(tick_fut, timeout), context="tick", on_done=lambda res: tick_fut.cancel(), ) await streamer.add_coroutine( wait_for_remainders(tick_fut, streamer_fut)) streamer.no_more_work() async for result in streamer: if not result.successful: try: raise result.value finally: del result if result.context == "tick": if not no_retry or not results: result = await writer() results.append(result) await streamer.add_task(result, context="write") elif result.context == "write": return result.value raise TimedOut( "Waiting for reply to a packet", serial=packet.serial, sent_pkt_type=packet.pkt_type, source=packet.source, sequence=packet.sequence, )
describe "ChildOfFuture": @pytest.fixture() def V(self): class V: orig_fut = asyncio.Future() @hp.memoized_property def cof(s): return hp.ChildOfFuture(s.orig_fut) return V() async it "ensure_future returns the ChildOfFuture as is": fut = asyncio.Future() fut = hp.ChildOfFuture(fut) assert asyncio.ensure_future(fut) is fut describe "set_result": async it "complains if the original fut is already cancelled", V: V.orig_fut.cancel() with assertRaises(hp.InvalidStateError, "CANCELLED: .+"): V.cof.set_result(True) async it "Otherwise sets a result on the fut", V: assert not V.cof.done() V.cof.set_result(True) assert V.cof.done() assert not V.orig_fut.done() assert await V.cof is True