web-dev-qa-db-fra.com

Comment intercepter la sortie d'exception de Python subprocess.check_output ()?

J'essaie de faire un paiement Bitcoin à partir de Python. En bash, je ferais normalement ceci:

bitcoin sendtoaddress <bitcoin address> <amount>

ainsi par exemple:

bitcoin sendtoaddress 1HoCUcbK9RbVnuaGQwiyaJGGAG6xrTPC9y 1.4214

si cela réussit, je reçois un identifiant de transaction en sortie mais si j'essaie de transférer un montant supérieur à mon solde en bitcoins, j'obtiens le résultat suivant:

error: {"code":-4,"message":"Insufficient funds"}

Dans mon Python, je tente maintenant d'effectuer le paiement comme suit:

import subprocess

try:
    output = subprocess.check_output(['bitcoin', 'sendtoaddress', address, str(amount)])
except:
    print "Unexpected error:", sys.exc_info()

S'il y a suffisamment d'équilibre, cela fonctionne bien, mais s'il n'y en a pas assez sys.exc_info() affiche ceci:

(<class 'subprocess.CalledProcessError'>, CalledProcessError(), <traceback object at 0x7f339599ac68>)

Cela n'inclut pas l'erreur que je reçois sur la ligne de commande cependant. Donc ma question est: Comment puis-je obtenir l'erreur générée ({"code":-4,"message":"Insufficient funds"}) depuis Python?

Tous les conseils sont les bienvenus!

46
kramer65

Selon subprocess.check_output() docs , l'exception déclenchée en cas d'erreur comporte un attribut output que vous pouvez utiliser pour accéder aux détails de l'erreur:

try:
    subprocess.check_output(...)
except subprocess.CalledProcessError as e:
    print e.output

Vous devriez alors pouvoir analyser cette chaîne et analyser les détails de l'erreur avec le module json:

if e.output.startswith('error: {'):
    error = json.loads(e.output[7:]) # Skip "error: "
    print error['code']
    print error['message']
78
Ferdinand Beyer

Je ne pense pas que la solution acceptée gère le cas où le texte d'erreur est signalé sur stderr. D'après mes tests, l'attribut de sortie de l'exception ne contenait pas les résultats de stderr et la documentation met en garde contre l'utilisation de stderr = PIPE dans check_output (). Au lieu de cela, je suggérerais une petite amélioration à la solution de J.F Sebastian en ajoutant le support stderr. Après tout, nous essayons de gérer les erreurs et c’est souvent là où elles sont signalées.

from subprocess import Popen, PIPE

p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE, stderr=PIPE)
output, error = p.communicate()
if p.returncode != 0: 
   print("bitcoin failed %d %s %s" % (p.returncode, output, error))
24
Phil R

Essayer de "transférer un montant supérieur à mon solde en bitcoins" n'est pas une erreur inattendue. Vous pouvez utiliser Popen.communicate() directement au lieu de check_output() pour éviter de déclencher une exception inutilement:

from subprocess import Popen, PIPE

p = Popen(['bitcoin', 'sendtoaddress', ..], stdout=PIPE)
output = p.communicate()[0]
if p.returncode != 0: 
   print("bitcoin failed %d %s" % (p.returncode, output))
7
jfs

Il y a de bonnes réponses ici, mais dans ces réponses, il n'y a pas eu de réponse avec le texte de la sortie de trace de pile, qui est le comportement par défaut d'une exception.

Si vous souhaitez utiliser ces informations de trace mises en forme, vous souhaiterez peut-être:

import traceback

try:
    check_call( args )
except CalledProcessError:
    tb = traceback.format_exc()
    tb = tb.replace(passwd, "******")
    print(tb)
    exit(1)

Comme vous le savez peut-être, ce qui précède est utile si vous avez un mot de passe dans le check_call (args) que vous souhaitez empêcher l’affichage.

2
macetw