Como lidar com conexões de banco de dados em um módulo de biblioteca Python

23

Eu criei uma biblioteca em Python que contém funções para acessar um banco de dados. Esta é uma biblioteca de wrapper em torno de um banco de dados de aplicativos de terceiros, gravada devido ao fato de que o aplicativo de terceiros não oferece uma API decente. Agora, originalmente deixei cada função abrir uma conexão com o banco de dados pela duração da chamada de função, até que minha lógica de programa usasse chamadas aninhadas para as funções nas quais eu estaria chamando uma função específica alguns milhares de vezes. Isso não teve muito desempenho. A criação de perfil mostrou que a sobrecarga estava na configuração da conexão com o banco de dados - uma vez por chamada de função. Então, mudei a conexão aberta de dentro da (s) função (s) para o próprio módulo, para que a conexão com o banco de dados fosse aberta quando o módulo da biblioteca fosse importado. Isso me deu um desempenho aceitável.

Agora eu tenho duas perguntas sobre isso. Em primeiro lugar, preciso me preocupar com o fato de não estar mais fechando explicitamente a conexão com o banco de dados e como poderia fazê-lo explicitamente com essa configuração? Em segundo lugar, o que eu fiz cai perto do domínio das boas práticas e como eu poderia abordar isso de outra forma?

leancz
fonte
1
Fornecer uma openConnfunção e fazer o usuário passá-lo para cada função que eles chamam, de que maneira eles podem escopo a conexão em uma withdeclaração ou qualquer outra coisa
Daniel Gratzer
1
Concordo com jozfeg, considere a criação de uma classe que abre a conexão db dentro do construtor, e que fecha a conexão na saída
Nick Burns,

Respostas:

31

Realmente depende da biblioteca que você está usando. Alguns deles podem estar fechando a conexão por conta própria (Nota: verifiquei a biblioteca sqlite3 embutida, e não). O Python chamará um destruidor quando um objeto estiver fora do escopo, e essas bibliotecas podem implementar um destruidor que fecha as conexões normalmente.

No entanto, esse pode não ser o caso! Eu recomendaria, como outros têm nos comentários, envolvê-lo em um objeto.

class MyDB(object):

    def __init__(self):
        self._db_connection = db_module.connect('host', 'user', 'password', 'db')
        self._db_cur = self._db_connection.cursor()

    def query(self, query, params):
        return self._db_cur.execute(query, params)

    def __del__(self):
        self._db_connection.close()

Isso instancia sua conexão com o banco de dados no início e fechá-la quando o local em que seu objeto foi instanciado cai fora de escopo. Nota: Se você instanciar esse objeto no nível do módulo, ele persistirá para todo o aplicativo. A menos que isso seja planejado, sugiro separar as funções do banco de dados das funções que não são do banco de dados.

Felizmente, o python padronizou a API do banco de dados , portanto, isso funcionará com todos os bancos de dados compatíveis para você :)

Travis
fonte
Como evitar que selfem def query(self,?
samayo 9/11/2015
2
definir evitar? Self é o que define isso como um método de instância, em vez de um método de classe. Eu acho que você poderia criar o banco de dados como uma propriedade estática na classe e, em seguida, usar apenas métodos de classe (não é necessário em nenhum lugar), mas o banco de dados seria global para a classe, não apenas a instanciação individual.
Travis
Sim, porque tentei usar o seu exemplo para fazer uma consulta simples db.query('SELECT ...', var)e ela se queixou de precisar de um terceiro argumento.
Samayo
@ samson, você precisa instanciar o MyDBobjeto primeiro:db = MyDB(); db.query('select...', var)
cowbert
Isto impediu mensagensResourceWarning: unclosed <socket.socket...
Bob Stein
3

ao lidar com conexões com o banco de dados, há duas coisas com que se preocupar:

  1. impedir instâncias de múltiplas conexões, deixar cada função abrir uma conexão com o banco de dados é considerado uma prática ruim, fornecendo o número limitado de sessões do banco de dados; você ficaria sem sessões; pelo menos, sua solução não seria expandida; em vez disso, use padrão singleton; sua classe seria instanciada apenas uma vez; para obter mais informações sobre esse padrão, consulte o link

  2. fechando a conexão na saída do aplicativo, digamos que você não o fez, e que você tem pelo menos uma dúzia de instâncias do aplicativo executando o mesmo, no início tudo funcionaria bem, mas você ficaria sem sessões de banco de dados e a única correção seria reiniciar o servidor de banco de dados, o que não é uma coisa boa para um aplicativo ao vivo; portanto, use a mesma conexão sempre que possível.

para solidificar todos esses conceitos, veja o exemplo a seguir que envolve psycopg2

import psycopg2


class Postgres(object):
"""docstring for Postgres"""
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            # normally the db_credenials would be fetched from a config file or the enviroment
            # meaning shouldn't be hardcoded as follow
            db_config = {'dbname': 'demo', 'host': 'localhost',
                     'password': 'postgres', 'port': 5432, 'user': 'postgres'}
            try:
                print('connecting to PostgreSQL database...')
                connection = Postgres._instance.connection = psycopg2.connect(**db_config)
                cursor = Postgres._instance.cursor = connection.cursor()
                cursor.execute('SELECT VERSION()')
                db_version = cursor.fetchone()

            except Exception as error:
                print('Error: connection not established {}'.format(error))
                Postgres._instance = None

            else:
                print('connection established\n{}'.format(db_version[0]))

        return cls._instance

    def __init__(self):
        self.connection = self._instance.connection
        self.cursor = self._instance.cursor

    def query(self, query):
        try:
            result = self.cursor.execute(query)
        except Exception as error:
            print('error execting query "{}", error: {}'.format(query, error))
            return None
        else:
            return result

    def __del__(self):
        self.connection.close()
        self.cursor.close()
ponach
fonte
1
Oi! Obrigado pela sua resposta. Mas quando tento implementá-lo no meu caso, tenho if Database._instance is None: NameError: name 'Database' is not defined. Não consigo entender o que Databaseé e como eu poderia consertar isso.
Ilya Rusin 15/10
1
@IlyaRusin que foi minha culpa, na verdade, o banco de dados é apenas uma classe pai na qual eu coloquei métodos comuns para manipulação de RDBMS diferentes, já que eu me conecto não apenas ao Postgres. No entanto, desculpe-me pelo erro e espero que a versão corrigida possa ser útil para você, sinta-se à vontade para adicionar, modificar o código de acordo com suas necessidades, se você tiver alguma dúvida relacionada, não hesite.
Ponach #
Se eu chamar repetidamente Postgres.query(Postgres(), some_sql_query)um whileloop, ele ainda abrirá e fechará a conexão em cada iteração ou a manterá aberta durante todo o tempo do whileloop até que o programa saia?
@ Michael, a classe de conexão é implementada como um singleton, portanto seria instanciada apenas uma vez, mas no geral eu recomendaria contra a maneira sugerida de chamada, em vez de iniciá-la em uma variável
ponach
1
@ponach Obrigado, ele faz exatamente o que eu queria alcançar. Adaptei um pouco o seu código e tentei usar uma instrução UPDATE em sua query()função, mas parece que há um problema com meu código quando executo meu aplicativo em "paralelo". Fiz uma pergunta separada a respeito: softwareengineering.stackexchange.com/questions/399582/…
2

Seria interessante oferecer recursos de gerenciador de contexto para seus objetos. Isso significa que você pode escrever um código como este:

class MyClass:
    def __init__(self):
       # connect to DB
    def __enter__(self):
       return self
    def __exit__(self):
       # close the connection

Isso oferecerá uma maneira prática de fechar a conexão com o banco de dados automaticamente chamando a classe usando a instrução with:

with MyClass() as my_class:
   # do what you need
# at this point, the connection is safely closed.
Billal Begueradj
fonte
-1

Muito tempo para pensar sobre isso. Hoje eu encontrei o caminho. Eu não sei, é o melhor caminho. você cria um arquivo com o nome: conn.py e o salva na pasta /usr/local/lib/python3.5/site-packages/conn/. Eu uso o freebsd e este é o caminho da minha pasta de pacotes de sites. no meu conn.py: conn = "dbname = usuário onívoro = senha do postgres = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` ` e no script que eu quero chamar de conexão, eu escrevo:

importar psycopg2 importar psycopg2.extras importar psycopg2.extensions

de conn import conn try: conn = psycopg2.connect (conn.conn), exceto: page = "Não é possível acessar o banco de dados"

cur = conn.cursor ()

e blá blá ....

espero que seja útil

phong
fonte