web-dev-qa-db-fra.com

cx_Oracle and Exception Handling - Bonnes pratiques?

J'essaie d'utiliser cx_Oracle pour se connecter à une instance Oracle et exécuter certaines 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 sais pas vraiment quelle est la meilleure conception pour la gestion des exceptions ici.

Tout d'abord, je crée l'objet db à l'intérieur d'un bloc try, pour détecter les erreurs de connexion.

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

Idéalement, j'ai besoin de détecter des erreurs de connexion, puis des erreurs lors de l'exécution des instructions DDL, etc.

Les exceptions d'imbrication sont-elles une bonne idée? Ou existe-t-il une meilleure façon de gérer les exceptions dépendantes/en cascade comme celle-ci?

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

26
victorhooi

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

Non, définir db = None N'est pas la meilleure pratique. Il y a deux possibilités, soit la connexion à la base de données fonctionnera, soit elle ne fonctionnera pas.

  • La connexion à la base de données ne fonctionne pas:

    Comme l'exception levée a été interceptée et non sur-levée, vous continuez jusqu'à ce que vous atteigniez cursor = db.Cursor().

    db == None, Une exception semblable à TypeError: 'NoneType' object has no attribute 'Cursor' Sera donc levée. Étant donné que l'exception générée lorsque la connexion à la base de données a échoué a déjà été interceptée, la raison de l'échec est déguisée.

    Personnellement, je lèverais toujours une exception de connexion, sauf si vous essayez à nouveau sous peu. La façon dont vous l'attrapez dépend de vous; si l'erreur persiste j'envoie un e-mail pour dire "allez vérifier la base de données".

  • La connexion à la base de données fonctionne:

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

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

Cependant, j'ai entendu dire que l'utilisation de la gestion des exceptions pour le contrôle de flux comme ceci est une mauvaise pratique.

Contrairement aux autres langages Python utilise la gestion des exceptions pour le contrôle de flux. À la fin de ma réponse, je me suis lié à plusieurs questions sur Stack Overflow 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 devriez aller trop loin mais Python utilise généralement le mantra EAFP , "Il est plus facile de demander pardon que permission." Les trois premiers exemples votés dans Comment puis-je vérifier si une variable existe? sont de bons exemples de la façon dont vous pouvez les deux utilisent le contrôle de flux ou non.

Les exceptions d'imbrication sont-elles une bonne idée? Ou existe-t-il une meilleure façon de gérer les exceptions dépendantes/en cascade comme celle-ci?

Il n'y a rien de mal avec les exceptions imbriquées, encore une fois tant que vous le faites sainement. Considérez votre code. Vous pouvez supprimer toutes les exceptions et envelopper le tout dans un bloc try:... except. Si une exception est levée, vous savez de quoi il s'agit, mais il est un peu plus difficile de retrouver exactement ce qui n'a pas fonctionné.

Que se passe-t-il alors si vous voulez dire vous-même un e-mail sur l'échec de cursor.execute? Vous devriez avoir une exception autour de cursor.execute Afin d'effectuer cette seule tâche. Vous relancez ensuite l'exception afin qu'elle soit prise dans votre try:... Externe. Ne pas relancer entraînerait la poursuite de votre code comme si rien ne s'était passé et la logique que vous aviez mise dans votre try:... Externe pour traiter une exception serait ignorée.

En fin de compte, toutes les exceptions sont héritées de BaseException .

De plus, il y a certaines parties (par exemple les échecs de connexion) où j'aimerais que le script se termine - d'où l'appel commenté sys.exit ().

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

Comme j'ai divisé la classe en plusieurs fonctions lorsque la méthode connect échoue et qu'une exception est déclenchée, l'appel execute ne sera pas exécuté 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()

Appelez-le ensuite:

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()

Lectures complémentaires:

documentation cx_Oracle

Pourquoi ne pas utiliser les exceptions comme flux de contrôle régulier?
python est-elle plus efficace que PHP et/ou d'autres langages?
Arguments pour ou contre l'utilisation de try catch comme opérateurs logiques

31
Ben

Une solution différente et peut-être élégante consiste à utiliser un décorateur pour vos fonctions d'appel de base de données. Le décorateur permet de corriger l'erreur et de réessayer l'appel à la base de données. Pour les connexions périmées, la correction consiste à se 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

Remarque: Si vous utilisez des pools de connexions, les techniques de pool de connexions internes qui vérifient une connexion et la rafraîchissent si périmée, résoudront également le problème.

1
giwyni