Как разобрать методы скомпилированного кода Python - PullRequest
0 голосов
/ 05 февраля 2019

Я пишу Django валидатор для файлов, загруженных с формой.Эти файлы представляют собой скрипты Python, которые могут содержать ошибки и / или быть небезопасными, поэтому я не должен выполнять их до полной проверки.

Одна проверка состоит в том, чтобы убедиться, что присутствуют оба метода "run" и "rollback",снова без выполнения кода .

Все загруженные скрипты Python имеют одинаковую структуру:

class Action(ActionGlobal):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def run(self, args):
        print("custom code here")
        ...

    def rollback(self,args):
        print("custom code here")
        ...

Я нашел следующее решение, чтобы сделать это с AST:

import ast
codetoanalyze=ast.parse(open("/path/to/script_to_analyse.py",'r').read())

if next((x for x in codetoanalyze.body[1].body if x.name == "run"), None) == None :
     raise ValidationError( _('Package error : module must contain a "run" method'), code='compilation_error', )

if next((x for x in codetoanalyze.body[1].body if x.name == "rollback"), None) == None :
     raise ValidationError( _('Package error : module must contain a "rollback" method'), code='compilation_error', )

Работает нормально, но не очень элегантно (body 1 ...), и, поскольку я никогда раньше не использовал модуль AST, я считаю, что может быть умнееспособ достижения этого.

Есть предложения?

Ответы [ 4 ]

0 голосов
/ 08 февраля 2019

Что ж, похоже, AST - отличное решение для моих нужд, поэтому я создал небольшой пост, объясняющий это здесь

Для моего вопроса, вот решение, которое я 'мы реализовали:

codetoanalyze = ast.parse( open('/path/to/script_to_analyse.py','r').read())

# AST checks
if True not in [ [ y for y in x.bases if y.id == 'ActionGlobal' ] != [] for x in codetoanalyze.body if type(x) is ast.ClassDef and x.name == "Action" ] :
    raise ValidationError( _('Package error : module must provide an Action class which inherits from ActionGlobal'), code='compilation_error', )

methods = [ z.name for x in codetoanalyze.body if type(x) is ast.ClassDef and x.name == "Action" for y in x.bases if y.id == 'ActionGlobal' for z in x.body ]
for reqd_name in ['run', 'rollback']:
    if reqd_name not in methods:
        raise ValidationError( _('Package error : module must contain a "{}" method').format(reqd_name), code='compilation_error', )

Я подожду другие сообщения и проголосую, прежде чем выбрать принятый ответ, если кто-то найдет лучший способ добиться этого с помощью другого модуля (может быть, с помощью парсера ?).

Спасибо за помощь

0 голосов
/ 05 февраля 2019

Вы должны использовать dir():

class MyClass:
    def methodA(self):
        print("Method-A")
    def methodB(self):
        print("Method-B")

print(dir(MyClass))

Вывод:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'methodA', 'methodB']

Или, в вашем случае

print(all(elem in dir(Action) for elem in ['run', 'rollback']))

Выход:

True

Работает как в Python 2 и Python 3

0 голосов
/ 05 февраля 2019

Создание набора имен в коде и цикл по всем необходимым именам сделает ваш код немного менее copy-paste-y:

code_names = {x.name for x in codetoanalyze.body[1].body}
for reqd_name in ['run', 'rollback']:
    if reqd_name not in code_names:
        raise ValidationError( _('Package error : module must contain a {!r} method'.format(reqd_name)), code='compilation_error', )
0 голосов
/ 05 февраля 2019

Я бы использовал шаблон, подобный посетителю, чтобы пройти по дереву

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...