Подтвердить что ты не робот

Cx_Oracle и обработка исключений - хорошая практика?

Я пытаюсь использовать cx_Oracle для подключения к экземпляру Oracle и выполнять некоторые инструкции 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()

Однако я не совсем уверен, какой лучший дизайн для обработки исключений здесь.

Во-первых, я создаю объект db внутри блока try, чтобы уловить любые ошибки соединения.

Однако, если он не может подключиться, то db не будет существовать дальше - вот почему я установил db = None выше. Однако это хорошая практика?

В идеале мне нужно ловить ошибки с подключением, затем ошибки с запуском DDL-операторов и т.д.

Является ли исключение гнездования хорошей идеей? Или есть лучший способ справиться с зависимыми/каскадными исключениями, подобными этому?

Кроме того, есть некоторые части (например, сбои подключения), где я хотел бы, чтобы script просто закончил - следовательно, прокомментировал вызов sys.exit(). Тем не менее, я слышал, что использование обработки исключений для управления потоком, как это, является плохой практикой. Мысли?

4b9b3361

Ответ 1

Однако, если он не может подключиться, то db не будет существовать дальше - поэтому я установил db = None выше. Однако это хорошая практика?

Нет, настройка db = None не самая лучшая практика. Есть две возможности: либо подключение к базе данных будет работать, либо не будет.

  • Подключение к базе данных не работает:

    По мере того, как поднятое исключение было поймано и не было повторно поднято, вы продолжаете, пока не достигнете cursor = db.Cursor().

    db == None, поэтому будет создано исключение, напоминающее TypeError: 'NoneType' object has no attribute 'Cursor'. Поскольку исключение, сгенерированное при сбое соединения с базой данных, уже было обнаружено, причина отказа маскируется.

    Лично я всегда вызываю исключение соединения, если вы не попробуете снова в ближайшее время. Как вы это поймете, зависит от вас; если ошибка сохраняется, я пишу по электронной почте, чтобы сказать "идите и проверьте базу данных".

  • Подключение к базе данных работает:

    Переменная db назначается в вашем блоке try:... except. Если метод connect работает, то db заменяется объектом соединения.

В любом случае начальное значение db никогда не используется.

Однако я слышал, что использование обработки исключений для управления потоком это плохая практика.

В отличие от других языков, Python использует обработку исключений для управления потоком. В конце моего ответа я связался с несколькими вопросами о переполнении стека и программистами, которые задают аналогичный вопрос. В каждом примере вы увидите слова "но на Python".

Это не означает, что вы должны переходить за борт, но Python обычно использует мантру EAFP: "Проще просить прощения, чем разрешения". В первую тройку проголосовали примеры в Как проверить, существует ли переменная? являются хорошими примерами того, как вы можете использовать управление потоком или нет.

Является ли исключение гнездования хорошей идеей? Или есть лучший способ с зависимыми/каскадными исключениями вроде этого?

Нет ничего плохого в вложенных исключениях, еще раз, пока вы делаете это здорово. Рассмотрите свой код. Вы можете удалить все исключения и обернуть всю вещь в блоке try:... except. Если возникает исключение, вы знаете, что это было, но немного сложнее определить, что пошло не так.

Что произойдет, если вы захотите сказать по электронной почте о провале cursor.execute? У вас должно быть исключение вокруг cursor.execute, чтобы выполнить эту задачу. Затем вы повторно поднимаете исключение, чтобы оно попало в ваш внешний try:.... Не повторное поднятие приведет к тому, что ваш код будет продолжаться, как будто ничего не произошло, и любая логика, которую вы внесли в ваш внешний try:... для обработки исключения, будет проигнорирована.

В конечном счете все исключения наследуются от BaseException.

Кроме того, есть некоторые части (например, сбои подключения), где мне бы хотелось script, чтобы просто закончить - отсюда вызывается вызов sys.exit().

Я добавил простой класс и как его назвать, что примерно так, как я буду делать то, что вы пытаетесь сделать. Если это будет запущено в фоновом режиме, то печать ошибок не стоит - люди не будут сидеть там, глядя вручную на ошибки. Они должны регистрироваться независимо от вашего стандартного способа и уведомлять соответствующих людей. По этой причине я удалил печать и заменил ее напоминанием о регистрации.

Поскольку я разбил класс на несколько функций, когда метод connect завершился с ошибкой, и возникло исключение, вызов execute не будет запущен, а script завершит работу после попытки отсоединения.

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

Затем назовите его:

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

Дальнейшее чтение:

cx_Oracle документация

Почему бы не использовать исключения в качестве регулярного потока управления?
Является ли обработка исключений python более эффективной, чем PHP и/или другие языки?
Аргументы за или против использования try catch в качестве логических операторов

Ответ 2

Другое и, возможно, элегантное решение - использовать декоратор для ваших функций вызова базы данных. Декоратор позволяет устранить ошибку и повторить вызов базы данных. Для устаревших соединений исправление заключается в повторном подключении и повторном вызове вызова. Вот декоратор, который работал у меня:

####### 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(...):
    ....

Подробнее о Github: https://github.com/vvaradarajan/DecoratorForDBReconnect/wiki

Примечание. Если вы используете пулы соединений, то также будут устранены проблемы с использованием методов пула соединений, которые проверяют соединение и обновляют его, если они устарели.