CX Oracle et gestion des exceptions - bonnes pratiques?

j'essaie d'utiliser cx_Oracle pour me connecter à une instance Oracle et exécuter quelques instructions DDL:

db = None
try:
    db = cx_Oracle.connect('username', 'password', 'hostname:port/SERVICENAME')
#print(db.version)
except cx_Oracle.DatabaseError as e:
    error, = e.args
    if error.code == 1017:
        print('Please check your credentials.')
        # sys.exit()?
    else:
        print('Database connection error: %s'.format(e))
cursor = db.cursor()
try:
    cursor.execute(ddl_statements)
except cx_Oracle.DatabaseError as e:
    error, = e.args
    if error.code == 955:
        print('Table already exists')
    if error.code == 1031:
        print("Insufficient privileges - are you sure you're using the owner account?")
    print(error.code)
    print(error.message)
    print(error.context)
cursor.close()
db.commit()
db.close()

cependant, je ne suis pas tout à fait sûr ce qui est le meilleur design pour la manipulation d'exception ici.

tout d'abord, je crée l'objet db à l'intérieur d'un bloc d'essai, pour détecter toute erreur de connexion.

cependant, s'il ne peut pas se connecter, alors db n'existera pas plus bas - c'est pourquoi j'ai placé db = None ci-dessus. Cependant, est-ce une bonne pratique?

idéalement, je dois attraper les erreurs de connexion, puis les erreurs d'exécution des instructions DDL, et ainsi de suite.

Est l'imbrication des exceptions une bonne idée? Ou y a-t-il une meilleure façon de traiter les exceptions dépendantes/en cascade comme celle-ci?

en outre, il y a certaines parties (par exemple les pannes de connexion) où je voudrais que le script se termine - d'où l'appel sys.exit() commenté. Cependant, j'ai entendu que l'utilisation de la gestion des exceptions pour le contrôle de flux comme c'est une mauvaise pratique. Pensées?

21
demandé sur Ben 2011-09-19 06:19:40

2 réponses

cependant, s'il ne peut pas se connecter, alors db n'existera pas plus bas - c'est pourquoi j'ai mis db = None ci-dessus. Cependant, est-ce une bonne pratique?

Non, paramètre db = None n'est pas le plus pratique. Il y a deux possibilités, soit se connecter à la base de données fonctionnera ou pas.

  • Se connecter à la base de données ne fonctionne pas:

    comme le l'exception soulevée a été prise et n'a pas été soulevée vous continuez jusqu'à ce que vous atteignez cursor = db.Cursor() .

    db == None , donc, une exception qui ressemble à TypeError: 'NoneType' object has no attribute 'Cursor' sera soulevée. Comme l'exception générée lorsque la connexion à la base de données a échoué a déjà été détectée, la raison de l'échec est masquée.

    personnellement, j'ai toujours soulevé une exception de connexion à moins que vous n'essayiez de nouveau sous peu. Comment vous attraper, il est à vous; si l'erreur persiste j'e-mail pour dire "allez vérifier la base de données".

  • Se connecter à la base de données fonctionne:

    la variable db est assignée dans votre bloc try:... except . Si le connect méthode fonctionne alors db est remplacé par l'objet de connexion.

dans les deux cas, la valeur initiale de db n'est jamais utilisée.

cependant, j'ai entendu que l'utilisation de la manipulation d'exception pour le contrôle du débit comme c'est une mauvaise pratique.

contrairement à D'autres langues Python does utiliser la manipulation d'exception pour le contrôle de flux. À la fin de ma réponse, j'ai relié plusieurs questions sur le débordement de la pile et les programmeurs qui posent une question similaire. Dans chaque exemple, vous verrez les mots ", mais en Python".

cela ne veut pas dire que vous devez aller mais Python utilise généralement le mantra EAFP , " il est plus facile de demander pardon que la permission." Les trois voté exemples dans Comment puis-je vérifier si une variable existe? sont de bons exemples de la façon dont vous pouvez à la fois utiliser le contrôle de flux ou non.

Est l'imbrication des exceptions une bonne idée? Ou est-il une meilleure façon de traiter les avec des exceptions dépendantes/en cascade comme celle-ci?

il n'y a rien de mal à faire des exceptions imbriquées, une fois de plus, tant que vous le faites sainement. Considérez votre code. Vous pouvez supprimer toutes les exceptions et envelopper le tout dans un try:... except bloquer. Si une exception est soulevée alors vous savez ce que c'était, mais il est un peu plus difficile de retrouver exactement ce qui a mal tourné.

alors Qu'advient-il si vous voulez dire e-mail vous-même sur l'échec de cursor.execute ? Vous devriez avoir une exception cursor.execute pour accomplir cette seule tâche. Vous relancez alors l'exception de sorte qu'il est pris dans votre extérieur try:... . Ne pas relancer aurait pour résultat que votre code continue comme si rien ne s'était passé et quelle que soit la logique que vous aviez mise dans votre try:... externe pour traiter d'une exception serait ignorée.

finalement toutes les exceptions sont héritées de BaseException .

aussi, il y a certains pièces (par exemple, pannes de connexion) où je voudrais le script à juste terminer - d'où le sys commenté.exit() de l'appel.

j'ai ajouté une classe simple et comment l'appeler, qui est à peu près comment je ferais ce que vous essayez de faire. Si cela doit être exécuté en arrière-plan, alors l'impression des erreurs n'en vaut pas la peine - les gens ne seront pas assis là à chercher manuellement les erreurs. Ils doivent être connectés quelle que soit votre façon standard et le approprié personnes avisées. J'ai retiré l'impression pour cette raison, et remplacé par un rappel pour vous connecter.

comme j'ai divisé la classe en plusieurs fonctions lorsque la méthode connect échoue et qu'une exception est soulevée, l'appel execute ne sera pas lancé et le script se terminera, après avoir tenté de se déconnecter.

import cx_Oracle

class Oracle(object):

    def connect(self, username, password, hostname, port, servicename):
        """ Connect to the database. """

        try:
            self.db = cx_Oracle.connect(username, password
                                , hostname + ':' + port + '/' + servicename)
        except cx_Oracle.DatabaseError as e:
            # Log error as appropriate
            raise

        # If the database connection succeeded create the cursor
        # we-re going to use.
        self.cursor = self.db.cursor()

    def disconnect(self):
        """
        Disconnect from the database. If this fails, for instance
        if the connection instance doesn't exist, ignore the exception.
        """

        try:
            self.cursor.close()
            self.db.close()
        except cx_Oracle.DatabaseError:
            pass

    def execute(self, sql, bindvars=None, commit=False):
        """
        Execute whatever SQL statements are passed to the method;
        commit if specified. Do not specify fetchall() in here as
        the SQL statement may not be a select.
        bindvars is a dictionary of variables you pass to execute.
        """

        try:
            self.cursor.execute(sql, bindvars)
        except cx_Oracle.DatabaseError as e:
            # Log error as appropriate
            raise

        # Only commit if it-s necessary.
        if commit:
            self.db.commit()

alors appelez-le:

if __name__ == "__main__":

    oracle = Oracle.connect('username', 'password', 'hostname'
                           , 'port', 'servicename')

    try:
        # No commit as you don-t need to commit DDL.
        oracle.execute('ddl_statements')

    # Ensure that we always disconnect from the database to avoid
    # ORA-00018: Maximum number of sessions exceeded. 
    finally:
        oracle.disconnect()

autres lectures:

cx_Oracle documentation

pourquoi ne pas utiliser les exceptions comme flux régulier de contrôle?

python exception est-il plus efficace que PHP et/ou d'autres langues?

Arguments pour ou contre l'utilisation de try catch comme opérateurs logiques

29
répondu Ben 2018-01-12 06:42:13

une solution différente et peut-être élégante est d'utiliser un décorateur à vos fonctions d'appel de base de données. Le décorateur permet la capacité de corriger l'erreur et d'essayer l'appel de base de données à nouveau. Pour les connexions vétustes, la remédiation consiste à reconnecter et réémettre l'appel. Voici le décorateur qui a travaillé pour moi:

####### Decorator named dbReconnect ########
#Retry decorator
#Retries a database function twice when  the 1st fails on a stale connection
def dbReconnect():
    def real_decorator(function):
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except  Exception as inst:
                print ("DB error({0}):".format(inst))
                print ("Reconnecting")
                #...Code for reconnection is to be placed here..
                ......
                #..end of code for reconnection
            return function(*args, **kwargs)
        return wrapper
    return real_decorator

###### Decorate the DB Call like this: #####
    @dbReconnect()
    def DB_FcnCall(...):
    ....

plus de détails sur Github: https://github.com/vvaradarajan/DecoratorForDBReconnect/wiki

Note: Si vous utilisez des piscines de connexion, les techniques de piscine de connexion interne qui vérifient une connexion et la rafraîchissent si elle est périmée, résoudront également le problème.

0
répondu giwyni 2016-11-16 15:10:20