Example #1
0
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
Example #2
0
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)
Example #4
0
 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))
Example #5
0
    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'))
Example #6
0
    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)
Example #7
0
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)
Example #8
0
 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
Example #9
0
 def __init__(self, node: Node) -> None:
     Process.__init__(self, node)
     node.register_name(self, term.Atom('net_kernel'))
Example #10
0
 def __init__(self, node, logger) -> None:
     Process.__init__(self, node)
     node.register_name(self, term.Atom('pythonserver'))
     self.logger = logger
     self.dynamic_functions = {}
Example #11
0
def erlang_error(msg: str):
    return (term.Atom('error'), term.Binary(msg.encode('utf-8')))
Example #12
0
    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))
Example #13
0
    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