19 nov. 2022

[Dynamo =+ Python] Gestion des erreurs (context manager)

 




Une alternative au bloc try except

if this article is not in your language, use the Google Translate widget ⬈ (bottom of page for Mobile version )



Un gestionnaire de contexte en Python est généralement utilisé comme expression dans une instruction, le mot clé with permet de gérer une ressource en garantissant l'invocation d'une méthode d'acquisition et d'une autre de libération de cette ressource.

Un gestionnaire de contexte contient une méthode appelée au début et une méthode appelée à la fin.

La méthode __enter__ est appelée avant le début de l'instruction with.

La méthode __exit__ est appelée après la dernière instruction de la méthode with.

De plus, les gestionnaires de contexte sont très utiles en cas d'exception de notre programme


  • Voici un premier exemple, avec :

    • une gestion d'erreur
    • une écriture d'un log
    • un affichage du message d'erreur via une boite de dialogue 
Ici la boite de dialogue peut être très utile pour informer l'utilisateur si le script est lancé depuis Dynamo Player.


Code Python. (MAJ avec PythonNet3 13/07/2026)

import sys
import clr
import System

clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import MessageBox, MessageBoxButtons, MessageBoxIcon

my_path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments)

import logging

logger = logging.getLogger("MyLogger")
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s :: %(levelname)s :: %(funcName)s :: %(message)s')

file_handler = logging.FileHandler(my_path + '\\MyLogger.log', mode='a')
file_handler.setLevel(logging.ERROR)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.disabled = False


class ValidationError(Exception):
    def __init__(self, typeError="Error", messageError="An error occurred"):
        self._typeError = typeError
        self._messageError = messageError
        Exception.__init__(self, self._typeError)
        # show Message
        self.CMessageBox()
        
    def __str__(self):
        return "{} : {}".format(self._typeError, self._messageError)
        
    def CMessageBox(self):
        MessageBox.Show(self._messageError, self._typeError, MessageBoxButtons.OK, MessageBoxIcon.Error)


# --- CONTEXT MANAGER ---
class ErrorContextManager:
    def __init__(self):
        pass
        
    def __enter__(self):
        print("Context Successfully Started\n")
        return self
        
    def __exit__(self, exc_type, exc_value, exc_tb):
        # Cas 1 : Tout s'est bien passé
        if exc_type is None:
            print("Successfully Exited From Context\n")
            return False
            
        if issubclass(exc_type, SystemExit):
            return False 
            
        line_no = exc_tb.tb_lineno if exc_tb else "unknown"
        error_msg = f"{exc_value} at line {line_no}"
        #
        print(f"Encountered Error: {error_msg}")
        #
        logger.error(f"Error caught by Context Manager: {error_msg}", exc_info=True)
        #
        raise ValidationError(exc_type.__name__, error_msg)


# --- ZONE TEST ---
with ErrorContextManager() as sc:
    test1 = 10 / 2
    test2 = 10 / 2
    test3 = 4 / 1
    
    # error
    test3 = 4 / 0  
    
    # code never used
    test1 = 10 / 2
    test2 = 10 / "2"
  
.

  • Un autre exemple avec Civil 3D CPython3/PythonNet


Avec le mot clé with, lors d'un appel d'une méthode .Net, IronPython appelle la méthode Dispose() (ouClose()) automatiquement lors de la sortie du bloc with


À ce jour, ce n'est pas le cas pour Python.NET   avec le moteur CPython3, ce qui peut poser quelques soucis de débogage lors d'erreurs.

 En attentant une meilleure prise en charge du mot clé with sur les objets .Net IDisposable, voici une amélioration de la gestion d'erreurs avec un context manager.








 Code Python (fonctionne uniquement avec CPython3/PythonNet3)

import sys
import clr
import System
# Add Assemblies for AutoCAD and Civil3D
clr.AddReference('AcMgd')
clr.AddReference('AcCoreMgd')
clr.AddReference('AcDbMgd')
clr.AddReference('AecBaseMgd')
clr.AddReference('AecPropDataMgd')
clr.AddReference('AeccDbMgd')

# Import references from AutoCAD
from Autodesk.AutoCAD.Runtime import *
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.EditorInput import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.AutoCAD.Geometry import *

# Import references from Civil3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *

# The inputs to this node will be stored as a list in the IN variables.
dataEnteringNode = IN

adoc = Application.DocumentManager.MdiActiveDocument
editor = adoc.Editor

class CManager:
    """
    a custom context manager for Disposable Object
    """
    lst_error = []
    def __init__(self, obj):
        self.obj = obj
        
    def __enter__(self):
        return self.obj
        
    def __exit__(self, exc_type, exc_value, exc_tb):
        print(clr.__version__)
        if "3." in clr.__version__: # for pythonnet 3.
            self.obj.Dispose()
        #
        if exc_type :
            error = f"{exc_type.__name__} - {exc_value} at line {exc_tb.tb_lineno}"
            self.__class__.lst_error.append(error)
            # optionnal for pythonet 3
            #if "3." in clr.__version__: 
            #    raise Exception(exc_type.__name__, error)
        return self.obj # or return self
        
        
dynCADObjects = IN[0]
with adoc.LockDocument():
    with CManager(adoc.Database) as db:
        print(db)
        with CManager(db.TransactionManager.StartTransaction()) as t:
            print(t)
            for dynObj in dynCADObjects:
                ent = t.GetObject(dynObj.AcadObjectId, OpenMode.ForWrite)
                ent.Layer = "Layer1"
                a = 10 /0 
            t.Commit()

print(f"{db.IsDisposed=}")
print(f"{t.IsDisposed=}")
if CManager.lst_error:
    OUT = CManager.lst_error
else:
    OUT = "result"

pour aller plus loin, vous pouvez utiliser la libraire standard contextlib

0 commentaires:

Enregistrer un commentaire