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?
Cependant, s'il ne peut pas se connecter, alors
db
n'existera pas plus bas - c'est pourquoi j'ai définidb = 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:
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
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(...):
....
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.