Rendere Python un linguaggio di scripting migliore Nicola Musatti nicola.musatti@gmail.com @NMusatti http://wthwdik.wordpress.com Agenda Perché siamo qui? Cos'è un linguaggio di scripting? Una sorta di linguaggio di programmazione Ha meccanisimi di controllo del flusso Fornisce un modo pratico di eseguire comandi Python è un linguaggio di scripting? E' un linguaggio di programmazione Ha molte istruzioni di controllo del flusso Fornisce un modo di eseguire comandi non molto pratico Un comando semplice Qualsiasi interprete di comandi: ls -la Python: from subprocess import call call(("ls", "-la")) Cosa c'è di sbagliato?
Ragionevole per linee di comando fisse e semplici Gestire comandi complessi diventa scomodo La sintassi del linguaggio non fornisce supporto E' difficile rendere il codice riusabile Un comando non molto semplice svnadmin create repo if [ $? == 0 ]; then p="file://$(pwd)/repo/project" svn mkdir $p/branches $p/tags --parents \ -m"create project tree" svn import backup $p/trunk -m"first import" fi mkdir sandbox cd sandbox svn checkout $p/trunk project Prima di nxpy.command import os from subprocess import call p = "file://" + os.getcwd() + "/repo/project" res = call("svnadmin create repo", shell=true) if res == 0: call("svn mkdir " + p + "/branches " + p + "/tags " "--parents -m=\"create project tree\"", shell=true) call("svn import \"backup\" " + p + "/trunk " "-m=\"first import\"", shell=true) os.mkdir("sandbox") os.chdir("sandbox") call("svn checkout " + p + "/trunk \"project\"", shell=true) Vogliamo davvero... import os from nxpy.command.command import Error from magic import run_commands p = "file://" + os.getcwd() + "/repo/project" try: svnadmin create repo svn mkdir $p/branches $p/tags --parents \ -m"create project tree" svn import backup $p/trunk -m"first import" except Error: pass os.mkdir("sandbox") os.chdir("sandbox") svn checkout $p/trunk project Idealmente
from ls import ls print ls(long=true, all=true) Si... può... fare! Lanciare comandi Un comando non molto semplice (ripresa) import os from nxpy.command.command import Error from nxpy.svn.svnadmin import SvnAdmin from svn import Svn svn_ = Svn() p = "file://" + os.getcwd() + "/repo/project" try: SvnAdmin().create("repo") svn_.mkdir(p + "/branches", p + "/tags", parents=true, message="\"create project tree\"") svn_.import_("backup", p + "/trunk", message="\"first import\"") except Error as e: print e os.mkdir("sandbox") os.chdir("sandbox") svn_.checkout(p + "/trunk", "project") Il package nxpy.command svn_.mkdir(p + "/branches", p + "/tags", parents=true, message="\"create project tree\"") I comandi sono espressi con chiamate a metodi Gli argomenti dei comandi sono passati come argomenti semplici Le opzioni sono passate come argomenti keyword Il package nxpy.command (cont.) Le opzioni possono essere specificate in modo dichiarativo Verifica vincoli custom su combinazioni di opzioni e argomenti Costruisce le linee di comando risultanti e le esegue Restituisce output e error del comando Funziona sia con comandi interattivi che non interattivi E' più efficace quando molti sotto-comandi condividono le stesse opzioni ad es. svn, cleartool
Il package nxpy.command (cont.) package nxpy + package command + module option + class Config + class Parser + module command + class Command + module interpreter + class Interpreter La classe nxpy.command.option.config Specifica quali opzioni sono supportate e come devono apparire sulla linea di comando: bool_opts: value_opts: iterable_opts: format_opts: mapped_opts: opposite_options: vanno specificate quando sono True prendono un singolo argomento possono prendere più argomenti la loro sintassi è specificata da una stringa di formato sono mappate su una sintassi specifica devono apparire sulla linea di comando quando non sono presenti La classe nxpy.command.option.config (cont.) _config = Config( bool_opts = ( "ignore_externals", "parents" ), value_opts = ( "message", ), mapped_opts = { "ignore_externals" : "--ignore-externals" } ) Altri argomenti sono: prefix: prefisso delle opzioni. Il default è -- separator: separatore degli argomenti delle opzioni. Il default è spazio La classe nxpy.command.option.parser def import_(self, src, dest, **options): op = Parser(_config, "import", ( src, dest ), options, message="") op.checkmandatoryoptions("message") Controlla che le opzioni passate siano valide Costruisce la linea di comando Gli argomenti keyword indicano le opzioni supportate coi loro default La classe nxpy.command.command.command
class Svn(Command): def init (self): super(svn, self). init ("svn", None) def import_(self, src, dest, **options): op = Parser(_config, "import", ( src, dest ), options, message="") op.checkmandatoryoptions("message") self.run(op) Assembla tutti i pezzi Esegue (o visualizza) la riga di comando risultante Restituisce il contenuto di output ed error del comando Creare un wrapper per il comando ls from nxpy.command.command import Command from nxpy.command.option import Config, Parser _config = Config( bool_opts = ( "all", "long" ), mapped_opts = { "all" : "-a", "long" : "-l" } ) class Ls(Command): def init (self, debug=false): super(ls, self). init ("/bin/ls", debug) def call (self, *args, **options): op = Parser(_config, None, args, options, all=false, long=false) return self.run(op)[0] ls = Ls() Controllare interpreti Gestire programmi interattivi Le opzioni dei sotto-comandi si gestiscono allo stesso modo L' I/O non bloccante è usato per evitare deadlock Bisogna indicare come individuare il completamento del comando corrente Per individuare il completamento è usato un algoritmo di polling Può essere necessario gestire durate eccessive Un esempio: l'interprete di cleartool class FailedCommand(BadCommand): def init (self, cmd, err_code=0, err=""):
message = [] if err: message.append(err) if err_code!= 0: message.append("error: " + err_code) super(failedcommand, self). init (cmd, "\n".join(message)) command_line = "cleartool -status" _result_re = re.compile(r"command \d+ returned status (\d)\r\n") Un esempio: l'interprete di cleartool (cont.) class ClearTool(Interpreter): def _run(self, parser, **kwargs): if isinstance(parser, Parser): cmd = parser.getcommandline() else: cmd = parser raise_on_failure = kwargs.get("raise_on_failure", False) try: kwargs["cond"] = RegexpWaiter(self._result_re, EXP_OUT) out, err = self.run(cmd, **kwargs) if raise_on_failure: err_code = ClearTool._result_re.search(out).group(1) if err_code > 0: raise FailedCommand(cmd, err_code=err_code) return ClearTool._result_re.sub("", out), err, cmd except BadCommand as e: raise FailedCommand(e.command, err=e.stderr) Esprimere condizioni di terminazione Gli interpreti potrebbero emettere lo stesso messaggio ad ogni comando Altrimenti occorre cercare output specifico I Waiter aiutano a individuare la terminazione Waiter disponibili Il modulo interpreter fornisce modi per aspettare: Qualsiasi output / error Un numero specifico di righe Una stringa fissa Un'espressione regolare Un esempio diverso: il client ftp class Ftp(Interpreter): def init (self): super(ftp, self). init ("ftp -v") self.setlog(true) def open(self, host, port, user, password): op = Parser(None, "open", ( host, port ), {}) self.send_cmd(op.getcommandline()) self.expect_regexp("220")
self.send_cmd(user) self.expect_string("password:", EXP_ERR, raise_on_error=false) self.send_cmd(password) self.expect_regexp("230") Personalizzare l'algoritmo di polling A volte non c'è una fine dell'input identificabile I comandi potrebbero bloccarsi Può essere desiderabile interrompere comandi che durano troppo Personalizzare l'algoritmo di polling (cont.) Puoi specificare: Un timeout complessivo Un numero massimo di tentativi Un intervallo fisso tra i tentativi Un limite minimo all'intervallo tra tentativi Un esempio: l'interprete di cleartool (ripresa) def update(self, **options): op = Parser(_config, "update", (), options, print_report=false, force=false, nolog=false, log="") op.checkexclusiveoptions("log", "nolog") self._run(op, raise_on_error=false, timeout=300, interval=0.5, quantum=0.5) Conclusioni Python come linguaggio di scripting - E' importante? L'agilità del linguaggio ed il numero di librerie disponibili rendono spesso più conveniente scrivere veri e propri programmi Python di qualsiasi approccio tipo scripting. Però: I comandi utente possono essere più stabili A volte non esiste API! Tips & tricks Raggruppare i comandi uccide l'auto-documentazione Assicurati di individuare davvero la fine dell'output/error del sotto-comando Prima di usare un approccio EAFP assicurati che il tuo programma perdoni davvero! Punti aperti
Portare a Python 3 Più test! Separare dalla libreria? Indicazioni Il package nxpy.command è parte della libreria Nxpy Home page del progetto: http://nxpy.sourceforge.net E' disponibile su PyPI: pip install Nxpy Colophon sphinx 1.2.2 sphinx-intl 0.9.4 hieroglyph 0.6.5 Nessuno stylesheet ha subito violenze nella realizzazione di questa presentazione! Q & A Q & A [Slide lasciata vuota intenzionalmente]