def _serialize_object(obj, cd: set = None): """ Given an arbitraty Python object creates a tuple (ClassName, {Fields}). A fair effort is made to avoid infinite recursion on cyclic objects. :param obj: Arbitrary object to encode :param cd: A set with ids of object, for cycle detection :return: A 2-tuple ((ClassName, {Fields}) or None, CycleDetect value) """ if cd is None: cd = set() # if cyclic encoding detected, ignore this value if id(obj) in cd: return None, cd cd.add(id(obj)) object_name = type(obj).__name__ fields = {} for key in dir(obj): val = getattr(obj, key) if not callable(val) and not key.startswith("_"): if _is_a_simple_object(val): fields[term.Atom(key)] = val else: (ser, cd) = _serialize_object(val, cd=cd) fields[term.Atom(key)] = ser return (term.Atom(object_name), fields), cd
def convert_to_erlang_type(x): if isinstance(x, str): return term.Binary(x.encode('utf-8')) elif isinstance(x, bytes): return term.Binary(x) elif isinstance(x, list) or isinstance(x, set): new_value = term.List() for e in x: new_value.append(convert_to_erlang_type(e)) return new_value elif isinstance(x, tuple): new_value = () for e in x: new_value = new_value + (convert_to_erlang_type(e), ) return new_value elif isinstance(x, dict): new_value = {} for k, v in x: new_value[convert_to_erlang_type(k)] = convert_to_erlang_type(v) return new_value elif isinstance(x, bool): if x: return term.Atom('true') else: return term.Atom('false') else: return x
def test_encode_atom(self): """ Try an atom 'hello' """ data1 = etf.term_to_binary(term.Atom('hello', encoding="latin-1")) expected1 = bytes([131, ord('d'), 0, 5, 104, 101, 108, 108, 111]) self.assertEqual(data1, expected1) data2 = etf.term_to_binary(term.Atom('hello', encoding="utf8")) expected2 = bytes([131, ord('v'), 0, 5, 104, 101, 108, 108, 111]) self.assertEqual(data2, expected2)
def load(self, namebin, codebin, configbin): try: dynamic_function_name = namebin.bytes_.decode('utf-8') self.logger.debug('loading {0}'.format(dynamic_function_name)) code = codebin.bytes_.decode('utf-8') config = configbin.bytes_.decode('utf-8') c = compile(code, '<string>', 'exec') if config: json_config = json.loads(config) else: json_config = {} code_locals = {} # workaround for numpy (PATH not in environ) # https://github.com/numpy/numpy/issues/10338 # https://stackoverflow.com/questions/48168482/keyerror-path-on-numpy-import exec( compile("import os; os.environ.setdefault('PATH', '')", '<string>', 'exec'), code_locals, code_locals) exec(c, code_locals, code_locals) #sys.stderr.write('code_locals = ' + str(code_locals)) code_locals['_get_config'] = lambda: json_config # lets find out whether the function (with given name) exists # in the code and also its arity (that is its number of parameters) if ('main' in code_locals) and callable(code_locals['main']): sig = signature(code_locals['main']) function_arity = len(sig.parameters) # copy locals to globals because otherwise # eval('..', globals, locals) will fail since # the depending functions will not be available in globals full_function_name = dynamic_function_name + '/' + str( function_arity) self.dynamic_functions[full_function_name] = ( code_locals, hash(codebin.bytes_), hash(configbin.bytes_)) self.logger.debug(' saved = ' + str(self.dynamic_functions)) return (term.Atom('ok'), function_arity) else: # the code compiled but function do not exist return (term.Atom('error'), term.Atom('not_found')) except Exception as e: print(traceback.format_exception(None, e, e.__traceback__), file=sys.stderr, flush=True) return erlang_error(str(e))
def handle_one_inbox_message(self, msg) -> None: gencall = gen.parse_gen_message(msg) if isinstance(gencall, str): self.logger.debug("MyProcess: {0}".format(gencall)) return # Handle the message in 'gencall' using its sender_, ref_ and # message_ fields if True: # gencall is of type gen.GenIncomingMessage if type(gencall.message_) != tuple or len(gencall.message_) != 3: error_message = "Only {M :: binary(), F :: binary(), Args :: tuple()} messages allowed" gencall.reply(local_pid=self.pid_, result=erlang_error(error_message)) else: (module, function, arguments) = gencall.message_ module_name = module.bytes_.decode('utf-8') function_name = function.bytes_.decode('utf-8') self.logger.debug('{0}.{1}({2})'.format( module_name, function_name, arguments)) try: if module_name == 'MyProcess' and function_name == 'load': result = self.load(*arguments) elif module_name == 'MyProcess' and function_name == 'eval': result = self.evaluate(*arguments) elif module_name == 'MyProcess' and function_name == 'invoke': result = self.invoke_main(*arguments) elif module_name == 'MyProcess' and function_name == 'invoke_simple_http': result = self.invoke_simple_http(*arguments) else: # DONT allow generic calls directly, instead # let user define dynamic functions and then within # that call those functions if required #fun_ref = eval(module_name + '.' + function_name, # self.code_globals, # self.code_locals) #result = fun_ref(*arguments) result = erlang_error('only dynamic calls allowed') except Exception as e: print(traceback.format_exception(None, e, e.__traceback__), file=sys.stderr, flush=True) result = erlang_error(str(e)) # flush stdout and err so that peer can read latest values # if listening on the same sys.stdout.flush() sys.stderr.flush() # Send a reply gencall.reply(local_pid=self.pid_, result=result) else: # Send an error exception which will crash Erlang caller gencall.reply_exit(local_pid=self.pid_, reason=term.Atom('error'))
def handle_one_inbox_message(self, msg): gencall = gen.parse_gen_message(msg) if not isinstance(gencall, gen.GenIncomingMessage): print("NetKernel:", gencall) return # Incoming gen_call packet to net_kernel, might be that net_adm:ping msg = gencall.message_ if isinstance(msg[0], term.Atom) and msg[0].text_ == 'is_auth': gencall.reply(local_pid=self.pid_, result=term.Atom('yes')) else: print("NetKernel: unknown message", msg)
def _bytes_to_atom(name: bytes, encoding: str, options: dict): """ Recognize familiar atom values. """ if name == b'true': return True if name == b'false': return False if name == b'undefined': return None if options.get("atoms_as_strings", False): return name.decode(encoding) return term.Atom(text=name.decode(encoding), encoding=encoding)
def _control_term_send(from_pid, dst): if isinstance(dst, term.Atom): return CONTROL_TERM_REG_SEND, from_pid, term.Atom(''), dst else: return CONTROL_TERM_SEND, term.Atom(''), dst
def __init__(self, node: Node) -> None: Process.__init__(self, node) node.register_name(self, term.Atom('net_kernel'))
def __init__(self, node, logger) -> None: Process.__init__(self, node) node.register_name(self, term.Atom('pythonserver')) self.logger = logger self.dynamic_functions = {}
def erlang_error(msg: str): return (term.Atom('error'), term.Binary(msg.encode('utf-8')))
def invoke(self, entry_fname, namebin, codebin, configbin, arguments): """entry_fname is either 'main' or 'handle_event'""" try: name = namebin.bytes_.decode('utf-8') if not isinstance(arguments, tuple): raise Exception('Arguments must be a tuple but is ' + str(type(arguments))) self.logger.debug(' retrieved = ' + str(self.dynamic_functions)) name_with_arity = name + '/' + str(len(arguments)) if name_with_arity not in self.dynamic_functions: # lazy load the function and compile it load_result = self.load(namebin, codebin, configbin) if load_result[0] != term.Atom('ok'): return load_result entry = self.dynamic_functions[name_with_arity] else: entry = self.dynamic_functions[name_with_arity] stored_code_hash = entry[1] if hash(codebin.bytes_) != stored_code_hash: # compile and save the function because # the hashes do not match load_result = self.load(namebin, codebin, configbin) if load_result[0] != term.Atom('ok'): return load_result # re-assign entry to updated one entry = self.dynamic_functions[name_with_arity] stored_config_hash = entry[2] if hash(configbin.bytes_) != stored_config_hash: config = configbin.bytes_.decode('utf-8') if config: json_config = json.loads(config) else: json_config = {} entry[0]['_get_config'] = lambda: json_config self.dynamic_functions[name_with_arity] = ( entry[0], entry[1], hash(configbin.bytes_)) entry = self.dynamic_functions[name_with_arity] code_locals = entry[0] if (entry_fname in code_locals) and (callable( code_locals[entry_fname])): main_fun = code_locals[entry_fname] self.logger.info('main_fun = {0}'.format(main_fun)) if inspect.isfunction(main_fun): sig = signature(main_fun) main_fun_arity = len(sig.parameters) if main_fun_arity == len(arguments): # without promoting locals to temporary globals # the import statements do not work call_arguments = [] for i in range(0, len(arguments)): argname = 'arg' + str(i) + '__' # save arguments to code_locals code_locals[argname] = arguments[i] call_arguments.append(argname) call_command = \ entry_fname + '(' \ + ','.join(call_arguments) \ + ')' self.logger.info( 'call_command, = {0}'.format(call_command)) eval_result = eval(call_command, code_locals, code_locals) self.logger.debug('response = {0}'.format(eval_result)) result = convert_to_erlang_type(eval_result) # cleanup code_locals to save memory for i in range(0, len(arguments)): argname = 'arg' + str(i) + '__' del code_locals[argname] # send back the result if is_valid_erlang_type(result): return result else: return erlang_error( "Invalid result type = {}".format( type(result))) else: return erlang_error( '{0}.{1} takes {2} arguments only'.format( name, entry_fname, main_fun_arity)) return erlang_error('Missing function {0}.{1}().'.format( name, entry_fname)) except Exception as e: print(traceback.format_exception(None, e, e.__traceback__), file=sys.stderr, flush=True) return erlang_error(str(e))
def __init__(self, node: Node) -> None: Process.__init__(self, node) node.register_name(self, term.Atom('rex')) self.traceback_depth_ = 5 """ This being non-zero enables formatting exception tracebacks with the