def nb_retrieve(self, value_id): """ Remote call from ``py.erl``: Retrieves a historical value by index. """ if value_id in self.history_: return Atom('ok'), self.history_[value_id] return Atom('error'), Atom('not_found')
def link(self, pid1, pid2, local_only=False): """ Check each of processes pid1 and pid2 if they are local, mutually link them. Assume remote process handles its own linking. :param pid1: First pid :type pid1: term.pid.Pid :param pid2: Second pid :type pid2: term.pid.Pid :param local_only: If set to True, linking to remote pids will send LINK message over distribution protocol """ if pid1.is_local_to(self): if pid1 in self.processes_: self.processes_[pid1].add_link(pid2) else: # not exists self.send_exit_signal(pid2, pid1, Atom("noproc")) elif not local_only: link_m = ('link', pid2, pid1) self._dist_command(receiver_node=pid1.node_name_, message=link_m) if pid2.is_local_to(self): if pid2 in self.processes_: self.processes_[pid2].add_link(pid1) else: # not exists self.send_exit_signal(pid1, pid2, Atom("noproc")) elif not local_only: link_m = ('link', pid1, pid2) self._dist_command(receiver_node=pid2.node_name_, message=link_m)
def _control_term_send(from_pid, dst): """ Creates a control term for dist protocol. :type from_pid: term.pid.Pid :type dst: term.atom.Atom or term.pid.Pid """ if isinstance(dst, Atom): return CONTROL_TERM_REG_SEND, from_pid, Atom(''), dst else: return CONTROL_TERM_SEND, Atom(''), dst
def _trigger_monitors(self, reason): """ On process exit inform all monitor owners that monitor us about the exit reason. """ node = self.get_node() for (monitor_ref, monitor_owner) in self._monitored_by.items(): down_msg = (Atom("DOWN"), monitor_ref, Atom("process"), self.pid_, reason) node.send_nowait(sender=self.pid_, receiver=monitor_owner, message=down_msg)
def _encode_map(self, codec): """ Try a map #{1 => 2, ok => error} """ sample = bytes([ py_impl.ETF_VERSION_TAG, py_impl.TAG_MAP_EXT, 0, 0, 0, 2, py_impl.TAG_SMALL_INT, 1, py_impl.TAG_SMALL_INT, 2, py_impl.TAG_SMALL_ATOM_UTF8_EXT, 2, 111, 107, py_impl.TAG_SMALL_ATOM_UTF8_EXT, 5, 101, 114, 114, 111, 114 ]) val = {1: 2, Atom("ok"): Atom("error")} bin1 = codec.term_to_binary(val, None) self.assertEqual(bin1, sample)
def _decode_map(self, codec): """ Try a map #{1 => 2, ok => error} """ data = self._etf_bytes([ 131, py_impl.TAG_MAP_EXT, 0, 0, 0, 2, py_impl.TAG_SMALL_INT, 1, py_impl.TAG_SMALL_INT, 2, py_impl.TAG_ATOM_EXT, 0, 2, 111, 107, py_impl.TAG_ATOM_EXT, 0, 5, 101, 114, 114, 111, 114 ]) (val, tail) = codec.binary_to_term(data, None) self.assertTrue(isinstance(val, dict)) self.assertEqual(val, {1: 2, Atom("ok"): Atom("error")}) self.assertExpectedTail(tail)
def resolve_arg(key_val): key, pyrlangval_tuple = key_val if isinstance(pyrlangval_tuple, tuple) \ and pyrlangval_tuple[0] == Atom("$pyrlangval"): return key, self._retrieve_value(pyrlangval_tuple) return key, pyrlangval_tuple
def _encode_atom_utf8(self, codec): # Create and encode 'hallå...hallå' 50 times (300 bytes) repeat1 = 50 example1 = bytes([py_impl.ETF_VERSION_TAG, py_impl.TAG_ATOM_UTF8_EXT, 1, (300-256)]) \ + (bytes("hallå", "utf8") * repeat1) b1 = codec.term_to_binary(Atom("hallå" * repeat1), None) self.assertEqual(b1, example1) # Create and encode 'hallå...hallå' 5 times (30 bytes) repeat2 = 5 example2 = bytes([py_impl.ETF_VERSION_TAG, py_impl.TAG_SMALL_ATOM_UTF8_EXT, 30]) \ + (bytes("hallå", "utf8") * repeat2) b2 = codec.term_to_binary(Atom("hallå" * repeat2), None) self.assertEqual(b2, example2)
def __init__(self, node) -> None: """ :param node: pyrlang.node.Node """ GenServer.__init__(self, node_name=node.node_name_, accepted_calls=['is_auth']) node.register_name(self, Atom('net_kernel'))
def _encode_list(self, codec): """ Encode list of something, an improper list and an empty list. """ example1 = bytes([py_impl.ETF_VERSION_TAG, py_impl.TAG_LIST_EXT, 0, 0, 0, 2, # length py_impl.TAG_SMALL_INT, 1, py_impl.TAG_SMALL_ATOM_UTF8_EXT, 2, 111, 107, py_impl.TAG_NIL_EXT]) b1 = codec.term_to_binary([1, Atom("ok")], None) self.assertEqual(b1, example1) example2 = bytes([py_impl.ETF_VERSION_TAG, py_impl.TAG_LIST_EXT, 0, 0, 0, 1, # length py_impl.TAG_SMALL_INT, 1, py_impl.TAG_SMALL_ATOM_UTF8_EXT, 2, 111, 107]) b2 = codec.term_to_binary(ImproperList([1], Atom("ok")), None) self.assertEqual(b2, example2)
def _tuple(self, codec): """ Encode a tuple """ example1 = bytes([py_impl.ETF_VERSION_TAG, py_impl.TAG_SMALL_TUPLE_EXT, 2, # length py_impl.TAG_SMALL_INT, 1, py_impl.TAG_SMALL_ATOM_UTF8_EXT, 2, 111, 107]) b1 = codec.term_to_binary((1, Atom("ok")), None) self.assertEqual(b1, example1)
def __init__(self) -> None: """ :param node: pyrlang.node.Node """ Process.__init__(self) node = self.node_db.get() node.register_name(self, Atom('rex')) self.traceback_depth_ = 5 """ This being non-zero enables formatting exception tracebacks with the
def _on_exit_signal(self, reason): """ Internal function triggered between message handling. """ if reason is None: reason = Atom('normal') self.is_exiting_ = True self._trigger_monitors(reason) self._trigger_links(reason) n = self.node_db.get(self.node_name_) n.on_exit_process(self.pid_, reason)
def _encode_atom(self, codec): """ Try an atom 'hello' encoded as Latin1 atom (16-bit length) or small atom (8bit length) """ # Create and encode 'hello...hello' 52 times (260 bytes) # Expect UTF8 back because encoder only does UTF8 atoms repeat1 = 52 example1 = bytes([py_impl.ETF_VERSION_TAG, py_impl.TAG_ATOM_UTF8_EXT, 1, 4]) \ + (b'hello' * repeat1) b1 = codec.term_to_binary(Atom("hello" * repeat1), None) self.assertEqual(b1, example1) # Create and encode 'hello...hello' 5 times (25 bytes) repeat2 = 5 example2 = bytes([py_impl.ETF_VERSION_TAG, py_impl.TAG_SMALL_ATOM_UTF8_EXT, 25]) \ + (b'hello' * repeat2) b2 = codec.term_to_binary(Atom("hello" * repeat2), None) self.assertEqual(b2, example2)
def _on_exit_signal(self, reason): """ Internal function triggered between message handling. """ if reason is None: reason = Atom('normal') self.is_exiting_ = True self._trigger_monitors(reason) self._trigger_links(reason) from pyrlang import Node n = Node.all_nodes[self.node_name_] n.on_exit_process(self.pid_, reason)
def _decode_improper_list(self, codec): # Test data is [1 | ok] data3 = self._etf_bytes([ 131, py_impl.TAG_LIST_EXT, 0, 0, 0, 1, py_impl.TAG_SMALL_INT, 1, py_impl.TAG_ATOM_EXT, 0, 2, 111, 107 ]) (val3, tail3) = codec.binary_to_term(data3, None) self.assertTrue( isinstance(val3, ImproperList), "Expected list, got: %s (%s)" % (val3.__class__.__name__, val3)) self.assertEqual(val3, ImproperList([1], Atom("ok"))) self.assertExpectedTail(tail3)
def nb_call(self, msg): """ Remote call from ``py.erl``: Calls function defined in ``args``, stores the result in history. :param msg: contains param in msg[1] ``path``: list of strings where first one is to be imported and remaining are used to find the function; ``args``: list of arguments for the callable; ``kwargs``; ``immediate``: will return the value instead of the value ref if this is ``True``, also will not update the history. :returns: Index for stored history value. """ param = msg[1] call_path = param[Atom("path")] call_args = self._resolve_valuerefs_in_args(param[Atom("args")]) call_kwargs = self._resolve_valuerefs_in_kwargs(param[Atom("kwargs")]) call_imm = param[Atom("immediate")] fn = self._resolve_path(call_path) result = fn(*call_args, **call_kwargs) if call_imm: return Atom('value'), result index = self._store_result(result) return Atom('ok'), result.__class__.__name__, index
def _decode_tuple(self, codec): """ Try decode some tuple values """ data1 = self._etf_bytes([ 131, py_impl.TAG_SMALL_TUPLE_EXT, 2, py_impl.TAG_SMALL_INT, 1, py_impl.TAG_ATOM_EXT, 0, 2, 111, 107 ]) (val1, tail1) = codec.binary_to_term(data1, None) self.assertEqual((1, Atom("ok")), val1) self.assertExpectedTail(tail1) data2 = self._etf_bytes([ 131, py_impl.TAG_LARGE_TUPLE_EXT, 0, 0, 0, 2, py_impl.TAG_SMALL_INT, 1, py_impl.TAG_ATOM_EXT, 0, 2, 111, 107 ]) (val2, tail2) = codec.binary_to_term(data2, None) self.assertEqual((1, Atom("ok")), val2) self.assertExpectedTail(tail2) # Empty tuple data3 = self._etf_bytes([131, py_impl.TAG_SMALL_TUPLE_EXT, 0]) (val3, tail3) = codec.binary_to_term(data3, None) self.assertEqual((), val3) self.assertExpectedTail(tail3)
class NetKernel(GenServer): """ A special process which registers itself as ``net_kernel`` and handles one specific ``is_auth`` message, which is used by ``net_adm:ping``. """ def __init__(self) -> None: """ :param node: pyrlang.node.Node """ super().__init__() self.node_db.get().register_name(self, Atom('net_kernel')) @call(lambda msg: type(msg) == tuple and len(msg) == 2 and msg[0] == Atom( 'is_auth')) def is_auth(self, msg): return Atom('yes')
def nb_batch(self, batch: List[tuple], param: Dict[Atom, any]): """ Take a remote call from Erlang to execute batch of Python calls. """ if not batch: return Atom("error"), Atom("batch_empty") call_imm = param[Atom("immediate")] for bitem in batch: call_path = bitem[Atom("path")] call_args = self._resolve_valuerefs_in_args(bitem[Atom("args")]) call_kwargs = self._resolve_valuerefs_in_kwargs( bitem[Atom("kwargs")]) call_ret = bitem[Atom("ret")] fn = self._resolve_path(call_path) result = fn(*call_args, **call_kwargs) last_result_name = self._store_result_as(result, call_ret) if call_imm: return Atom("value"), result return Atom("ok"), result.__class__.__name__, last_result_name
def _trigger_links(self, reason): """ Pass any exit reason other than 'normal' to linked processes. If Reason is 'kill' it will be converted to 'killed'. """ if isinstance(reason, Atom): if reason == 'normal': return elif reason == 'kill': reason = Atom('killed') node = self.get_node() for link in self._links: # For local pids, just forward them the exit signal node.send_link_exit_notification(sender=self.pid_, receiver=link, reason=reason)
def _decode_list(self, codec): """ Try decode some list values """ data1 = self._etf_bytes([131, py_impl.TAG_NIL_EXT]) (val1, tail1) = codec.binary_to_term(data1, None) self.assertEqual([], val1) self.assertExpectedTail(tail1) # Test data is [1, ok] data2 = self._etf_bytes([ 131, py_impl.TAG_LIST_EXT, 0, 0, 0, 2, py_impl.TAG_SMALL_INT, 1, py_impl.TAG_ATOM_EXT, 0, 2, 111, 107, py_impl.TAG_NIL_EXT ]) (val2, tail2) = codec.binary_to_term(data2, None) self.assertTrue( isinstance(val2, list), "Expected list, got: %s (%s)" % (val2.__class__.__name__, val2)) self.assertEqual(val2, [1, Atom("ok")]) self.assertExpectedTail(tail2)
def call(name, msg_len=2): """ specific decorator function Handle the decorator where we expect a tuple of a specific size and the first item being an atom with specific name """ atom = Atom(name) def pattern_match(msg): if type(msg) != tuple: return False if len(msg) != msg_len: return False if msg[0] != atom: return False return True return main_call(pattern_match)
def _resolve_path(self, p: List[str]) -> Callable: """ Imports p[0] and then follows the list p, by applying getattr() repeatedly. """ if isinstance(p, str): p = [p] # First element would be the import, or a stored value reference first_path_element = p[0] if isinstance(first_path_element, tuple) \ and first_path_element[0] == Atom("$pyrlangval"): # First element is {'$pyrlangval', X} - query the value val = self._retrieve_value(first_path_element) else: # First element is a string, import it val = __import__(as_str(first_path_element)) # Follow the elements in path, and getattr deeper for item in p[1:]: val = getattr(val, as_str(item)) return val
def destroy(self): """ Closes incoming and outgoing connections and destroys the local node. This is Python, so some refs from running async handlers may remain. """ self.is_exiting_ = True import copy all_processes = copy.copy(self.processes_) for p in all_processes.values(): p.exit(Atom('killed')) self.processes_.clear() self.reg_names_.clear() for dproto in self.dist_nodes_.values(): dproto.destroy() self.dist_nodes_.clear() self.dist_.destroy() del Node.all_nodes[self.node_name_] self.engine_.destroy()
def is_auth(): return Atom('yes')
def __etf__(self): """ This function will fire if no "encode_hook" was passed in options, and the library doesn't know what to do with this 'CustomClass' """ return Atom('custom-member!')
def encode_hook_fn(obj): """ This function will fire if "encode_hook" is passed in encode options dict. """ if isinstance(obj, Class1): return Atom('custom-hook!')
def catch_all_fn(obj): """ This function will fire if "encode_hook" is passed in encode options dict with the key "catch_all" present. """ if isinstance(obj, Class1): return Atom('custom-hook!')
def resolve_arg(pyrlangval_tuple): if isinstance(pyrlangval_tuple, tuple) \ and pyrlangval_tuple[0] == Atom("$pyrlangval"): return self._retrieve_value(pyrlangval_tuple) return pyrlangval_tuple