def keymap(self): k1 = 'key' v1 = 'value' k2 = 'key2' v2 = 'value2' m = Map({k1: v1, k2: v2}) res = m.keymap(lambda a: len(a)) res.should.have.key(len(k1)).being.equal(v1) res.should.have.key(len(k2)).being.equal(v2)
def flat_map(self): k1 = 'key' v1 = 'value' k2 = 'key2' v2 = 'value2' m = Map({k1: v1, k2: v2}) res = m.flat_map(lambda a, b: Just((a, b)) if a == k1 else Empty()) res.should.have.key(k1).being.equal(v1) res.should_not.have.key(k2)
def add(self): key = 'key' val = 'value' k2 = 'key2' v2 = 'value2' m = Map({key: val}) m2 = m + (k2, v2) m2.get(k2).should.equal(Just(v2)) m.get(k2).should.equal(Empty())
def map(self): k1 = 'key' v1 = 'value' k2 = 'key2' v2 = 'value2' m = Map({k1: v1, k2: v2}) res = m.map(lambda a: (len(a[0]), len(a[1]))) res.should.have.key(len(k1)).being.equal(len(v1)) res.should.have.key(len(k2)).being.equal(len(v2))
def add_multi(self): key = 'key' val = 'value' k2 = 'key2' v2 = 'value2' m = Map({key: val}) m2 = m ** Map({k2: v2}) m2.get(k2).should.equal(Just(v2)) m.get(k2).should.equal(Empty())
def find(self): k1 = 'key' v1 = 'value' k2 = 'key2' v2 = 'value2' m = Map({k1: v1, k2: v2}) m.find(_ == v1).should.equal(Just((k1, v1))) m.find_key(_ == k2).should.equal(Just((k2, v2))) m.find(_ == 'invalid').should.equal(Empty()) m.find_key(_ == 'invalid').should.equal(Empty())
def logger_tree(root): from tryp import __, Map m = Map(logging.Logger.manager.loggerDict) all = m.keyfilter(__.startswith(root)) return sub_loggers(all, 'tryp')
def get(self): key = 'key' val = 'value' m = Map({key: val}) m.get(key).should.equal(Just(val)) m.get(key + key).should.equal(Empty())
def _setup_handlers(self): handlers = inspect.getmembers(self, lambda a: hasattr(a, _machine_attr)) handler_map = List.wrap(handlers).smap(Handler.create).map(lambda a: (a.message, a)) self._message_handlers = Map(handler_map)
class Machine(Logging): _data_type = Data def __init__(self, name: str, parent: "Machine" = None) -> None: self.name = name self.parent = Maybe(parent) self._setup_handlers() def _setup_handlers(self): handlers = inspect.getmembers(self, lambda a: hasattr(a, _machine_attr)) handler_map = List.wrap(handlers).smap(Handler.create).map(lambda a: (a.message, a)) self._message_handlers = Map(handler_map) @property def _default_handler(self): return Handler("unhandled", None, self.unhandled) def process(self, data: Data, msg) -> TransitionResult: self.prepare(msg) handler = self._resolve_handler(msg) result = self._execute_transition(handler, data, msg) return self._dispatch_transition_result(data, result) def prepare(self, msg): pass def _dispatch_transition_result(self, data, result): return result / self._process_result(data) | TransitionResult.empty(data) def loop_process(self, data, msg): sender = lambda z, m: z.accum(self.loop_process(z.data, m)) return self.process(data, msg).fold(sender) def _resolve_handler(self, msg): return self._message_handlers.get(type(msg)).get_or_else(lambda: self._default_handler) def _execute_transition(self, handler, data, msg): try: return handler.run(data, msg) except Exception as e: return self._handle_transition_error(handler, msg, e) def _handle_transition_error(self, handler, msg, e): err = 'transition "{}" failed for {} in {}' self.log.exception(err.format(handler.name, msg, self.name)) if tryp.development: raise TransitionFailed() from e return Empty() @curried def _process_result(self, old_data: Data, result) -> TransitionResult: if isinstance(result, Coroutine): return CoroTransitionResult(data=old_data, coro=result) elif isinstance(result, TransitionResult): return result elif isinstance(result, self._data_type): return TransitionResult.empty(result) elif isinstance(result, Message) or not is_seq(result): result = List(result) datas, rest = List.wrap(result).split_type(self._data_type) strict, rest = rest.split_type(Message) coro, rest = rest.split(iscoroutine) msgs = strict + coro.map(Coroutine).map(_.pub) if rest: tpl = "invalid transition result parts in {}: {}" msg = tpl.format(self.name, rest) if tryp.development: raise MachineError(msg) else: self.log.error(msg) new_data = datas.head | old_data return self._create_result(new_data, msgs) def _create_result(self, data, msgs): pub, resend = msgs.split_type(Publish) pub_msgs = pub.map(_.message) return StrictTransitionResult(data=data, pub=pub_msgs, resend=resend) @may def unhandled(self, data: Data, msg: Message): pass def _command_by_message_name(self, name: str): msg_name = camelcaseify(name) return self._message_handlers.find_key(lambda a: a.__name__ in [name, msg_name]) def command(self, name: str, args: list): return ( self._command_by_message_name(name) .map(lambda a: StateCommand(a[0])) .map(_.call("dispatch", self, args)) .or_else(F(self._invalid_command, name)) ) def _invalid_command(self, name): self.log.error('plugin "{}" has no command "{}"'.format(self.name, name)) return Empty() @may_handle(NvimIOTask) def message_nvim_io(self, data: Data, msg): msg.io.unsafe_perform_io(self.vim) @may_handle(RunTask) def message_run_task(self, data: Data, msg): success = lambda r: r if isinstance(r, Message) else None return msg.task.unsafe_perform_sync().cata(F(Error) >> _.pub, success) @may_handle(DataTask) def message_data_task(self, data: Data, msg): return either_msg(msg.cons(Task.now(data)).unsafe_perform_sync()) def bubble(self, msg): self.parent.cata(_.bubble, lambda: self.send)(msg)
def decode_dict(value): from tryp import Map return Map.wrap(value)\ .keymap(decode)\ .valmap(decode)
def __init__(self, vim: NvimFacade, loop=None) -> None: self.vim = vim self.loop = loop or asyncio.get_event_loop() self.current = Map() # type: Map[Any, Job]
class ProcessExecutor(Logging): ''' Handler for subprocess execution Because python handles signals only on the main thread and subprocess notifcations, like their termination, is noticed by catching SIG_CHLD, the actual execution of the coroutines spawning the subprocess must be done while the main threads's event loop is in the running state, so it can relay the signal to the subprocess's event loop. As this class can be instantiated and used from within another thread's event loop, it is also impossible (afaict) to use either that loop or the main thread loop, for proc.wait() thus blocks indefinitely with a <defunct> process. ''' def __init__(self, vim: NvimFacade, loop=None) -> None: self.vim = vim self.loop = loop or asyncio.get_event_loop() self.current = Map() # type: Map[Any, Job] async def process(self, job: Job): return await asyncio.create_subprocess_exec( job.exe, *job.args, stdin=PIPE, stdout=PIPE, stderr=PIPE, cwd=str(job.cwd), loop=self.loop, ) async def _execute(self, job: Job): try: with self._main_event_loop(): proc = await self.process(job) (out, err) = await proc.communicate(job.stdin) msg = '{} executed successfully ({}, {})'.format(job, out, err) self.log.debug(msg) return proc.returncode, out.decode(), err.decode() except Exception as e: self.log.exception('{} failed with {}'.format(job, repr(e))) return -111, '', 'exception: {}'.format(e) def run(self, job: Job) -> Future[Result]: ''' return values of execute are set as result of the task returned by ensure_future(), obtainable via task.result() ''' if self._can_execute(job): self.log.debug('executing {}'.format(job)) task = asyncio.ensure_future(self._execute(job), loop=self.loop) task.add_done_callback(job.finish) task.add_done_callback(F(self.job_done, job)) self.current[job.client] = job else: self.log.error('invalid execution job: {}'.format(job)) job.cancel('invalid') return job.status def _can_execute(self, job: Job): return job.client not in self.current and job.valid def job_done(self, job, status): self.log.debug('{} is done with status {}'.format(job, status)) if job.client in self.current: self.current.pop(job.client) @property def ready(self): return self.current.is_empty def _main_event_loop(self): return (self.vim.main_event_loop() if trypnv.in_vim else self._dummy_ctx()) @contextmanager def _dummy_ctx(self): yield def _run_jobs(self): self.current.valmap(lambda job: job.run()) @property def futures(self): return self.current.v.map(_.status) def await_threadsafe(self, loop): async def waiter(): while not self.ready: await asyncio.sleep(0.001) loop.run_until_complete(waiter())