In the context of a complex application, I need to import user-supplied \'scripts\'. Ideally, a script would have
def init():
blah
def execute():
mo
Not sure if you'll consider this elegant, but it is somewhat intelligent in the sense that it recognizes when def init are tokens and not just part of a tricky multi-line string:
'''
def init does not define init...
'''
It will not recognize when init is defined in tricky alternate ways such as
init = lambda ...
or
codestr='def i'+'nit ...'
exec(codestr)
The only way to handle all such cases is to run the code (e.g. in a sandbox or by importing) and inspect the result.
import tokenize
import token
import io
import collections
userscript = '''\
def init():
blah
"""
def execute():
more blah
"""
yadda
'''
class Token(object):
def __init__(self, tok):
toknum, tokval, (srow, scol), (erow, ecol), line = tok
self.toknum = toknum
self.tokname = token.tok_name[toknum]
self.tokval = tokval
self.srow = srow
self.scol = scol
self.erow = erow
self.ecol = ecol
self.line = line
class Validator(object):
def __init__(self, codestr):
self.codestr = codestr
self.toks = collections.deque(maxlen = 2)
self.names = set()
def validate(self):
tokens = tokenize.generate_tokens(io.StringIO(self.codestr).readline)
self.toks.append(Token(next(tokens)))
for tok in tokens:
self.toks.append(Token(tok))
if (self.toks[0].tokname == 'NAME' # First token is a name
and self.toks[0].scol == 0 # First token starts at col 0
and self.toks[0].tokval == 'def' # First token is 'def'
and self.toks[1].tokname == 'NAME' # Next token is a name
):
self.names.add(self.toks[1].tokval)
delta = set(['init', 'cleanup', 'execute']) - self.names
if delta:
raise ValueError('{n} not defined'.format(n = ' and '.join(delta)))
v = Validator(userscript)
v.validate()
yields
ValueError: execute and cleanup not defined