dzoniandric
dzoniandric

Reputation: 23

How to embed python interpreter into an app written in Python?

Maybe I am completely off track here (and above my paygrade for sure), but what I want to do is to give users of my app (That I am writing in Python since that's the language I know) a python interpreter to control some objects within my app. Something similar like many 3D and VFX softwares have (Maya, Blender, Nuke). This is the code I got so far:

#main.py
import code
import networkx as nx

class Env():
    def __init__(self):
        self.graph = nx.graph.DiGraph()
        # load library with functions that will be availabel for user inside the app.
        import mylib
        functions = {f: getattr(mylib, f) for f in dir(mylib) if not f.startswith('__')}
        self._interpreter = code.InteractiveInterpreter(locals=functions)

    def execute_node(self, node=None):
        # In IRL the main object to be pass1ed to users' interpreter will be the self.graph object
        # but I made it more clear for this question.
        self._interpreter.locals['var'] = 42
        node = "print(var)\nprint(some_function())\nvar = 67" # Let's pretend node object contains this code.        
        self._interpreter.runcode(node)

if __name__ == '__main__':
    e = Env()
    # some code, node creation and so on...
    e.execute_code()
    print(e.locals['var'])
    


#mylib.py
var = None # I have to put this here because if there is no variable function fails at import
def some_function():
        print(var)

Output:

42 # This prints as expected
None # The print within the function prints the value that was there when module was initialized
67 # The last print returns expected value

So, it is clear that python interprets the functions on first import and "bakes" the global variables that it had at the import time. Now the question is can I somehow easily make it use the globals passed from the code.InteractiveInterpreter() or I should look for a completely different solution (and which one) :)? Of course the idea is that the two python programs should communicate, the user should use a special library to operate the software and the backend code should not be exposed to them. Do I make any sense? Thanks :)

Upvotes: 1

Views: 835

Answers (1)

AKX
AKX

Reputation: 168834

This is the one-ish instance where you do want to use the exec() function, but please remember that the user may be able to run any Python code, including stuff that could run forever, mess up your main program, write (or delete) files, etc.

def run_code(code, add_locals={}):
    code_locals = {}
    code_locals.update(add_locals)  # Copy in the additional locals so that dict could be reused
    exec(
      code, 
      {},  # no globals (you may wish to replace this),
      code_locals,
    )
    return code_locals  # return updated locals


class Beeper:  # define a toy object
    def beep(self, times):
        print("Beep! " * times)


beeper = Beeper()  # instantiate the object to play with

# Some user code...
user_code = """
x = 5
beeper.beep(x)
x += 3
"""

new_locals = run_code(user_code, {"beeper": beeper})
print(new_locals)

This outputs

Beep! Beep! Beep! Beep! Beep!
{'beeper': <__main__.Beeper>, 'x': 8}

So you can see we can use the locals the user has modified if need be.

Upvotes: 1

Related Questions