Le 7 Février 2019
Pour des raisons de sécurité, vous pouvez être amené à devoir contrôler des morceaux de code Python sans pour autant l'éxecuter. Par exemple, si ce code provient d'un autre organisme ou carrément d'internet, il contient peut être des bugs ou des failles.
Vous souhaitez donc tout contrôler avant de le charger dans votre infrastructure.
Dans ce post, nous allons utiliser des noms farfelus mais il faudra bien entendu les remplacer. Supposons donc que tous les scripts que vous devez contrôler suivent la même structure : Ils doivent fournir une classe appelée 'MyCar' qui hérite d'une classe abstraite ('GlobalCar') que vous avez créée dans le module 'myproject.cars' :
from myproject.cars import GlobalCars class MyCar(GlobalCars): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def drive(self, args): # script custom code here def park(self): # script custom code herePour passer les tests de contrôle, le script :
La première chose à faire est de compiler le script, cela ne l'execute pas et relèvera les erreurs de compilation (syntaxe ou type par exemple) :
try : carcode = compile( open('path/to/script.py','r').read() ,'compiledFile' , 'exec' ) except Exception as e : print('build error : {m}'.format( m = str(e) ) )
Nous pouvons profiter de l'objet compilé pour faire quelques vérifications sur les noms utilisés par le code :
for n in ['GlobalCars', 'myproject.cars','MyCar']: if n not in carcode.co_names : print('error : script must import GlobalCars from myproject.cars')
AST permet de construire une arborescence du code sans l'executer
import ast carast = ast.parse( open('path/to/script.py','r').read())
if not ( len([ x for x in carast.body if type(x) is ast.ImportFrom and x.module == 'myproject.cars' ]) > 0 or True in [ y.name == 'myproject.cars' for x in carast.body if type(x) is ast.Import for y in x.names ] ): print('script must import myproject.cars')
if True not in [ [ y for y in x.bases if y.id == 'GlobalCars' ] != [] for x in carast.body if type(x) is ast.ClassDef and x.name == "MyCar" ] : print('script must provide a class MyCar which inherits from GlobalCar ')
methods = [ z.name for x in carast.body if type(x) is ast.ClassDef and x.name == "MyCar" for y in x.bases if y.id == 'GlobalCars' for z in x.body ] for n in ['run', 'rollback']: if n not in methods: print('MyCar must provide a "{}" method').format(n)
L'utilisation de compile() permet de vérifier que le code ne contient pas d'erreur mais AST est un outil complémentaire très puissant permettant de naviguer dans la structure du script.
La combinaison de ces deux outils permet une étude approfondie d'un code Python