Cómo lidiar con conexiones de base de datos en un módulo de biblioteca Python

23

He creado una biblioteca en Python que contiene funciones para acceder a una base de datos. Esta es una biblioteca de contenedor alrededor de una base de datos de aplicaciones de terceros, escrita debido al hecho de que la aplicación de terceros no ofrece una API decente. Ahora originalmente dejé que cada función abriera una conexión de base de datos durante la duración de la llamada a la función, lo cual estuvo bien, hasta que la lógica de mi programa usó llamadas anidadas a las funciones donde luego llamaría a una función en particular unas pocas miles de veces. Esto no fue muy eficiente. El perfil de esto mostró que la sobrecarga estaba en la configuración de la conexión de la base de datos, una vez por llamada a la función. Así que moví la conexión abierta desde dentro de las funciones al módulo en sí, para que la conexión de la base de datos se abriera cuando se importó el módulo de la biblioteca. Esto me dio un rendimiento aceptable.

Ahora tengo dos preguntas con respecto a esto. En primer lugar, ¿debo preocuparme de que ya no cierre explícitamente la conexión de la base de datos y cómo podría hacerlo explícitamente con esta configuración? En segundo lugar, ¿lo que he hecho se acerca al ámbito de las buenas prácticas y cómo podría abordar esto?

leancz
fuente
1
Proporcione una openConnfunción y haga que el usuario la pase a cada función que llame, de esa manera puede ver la conexión en una withdeclaración o lo que sea
Daniel Gratzer
1
Estoy de acuerdo con jozfeg, considere crear una clase que abra la conexión db dentro del constructor, y que cierre la conexión a la salida
Nick Burns

Respuestas:

31

Realmente depende de la biblioteca que estés usando. Algunos de ellos podrían estar cerrando la conexión por su cuenta (Nota: Revisé la biblioteca sqlite3 incorporada, y no lo hace). Python llamará a un destructor cuando un objeto salga del alcance, y estas bibliotecas podrían implementar un destructor que cierre las conexiones con gracia.

Sin embargo, ¡ese podría no ser el caso! Recomendaría, como lo han hecho otros en los comentarios, envolverlo en un 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()

Esto creará una instancia de la conexión de su base de datos al principio y la cerrará cuando el lugar donde se creó la instancia de su objeto quede fuera de alcance. Nota: Si crea una instancia de este objeto a nivel de módulo, persistirá para toda su aplicación. A menos que esto se pretenda, sugeriría separar las funciones de su base de datos de las funciones que no son de la base de datos.

Afortunadamente, python ha estandarizado la API de la base de datos , por lo que funcionará con todas las bases de datos compatibles para usted :)

Travis
fuente
¿Cómo evitas eso selfen def query(self,?
samayo
2
definir evitar? Self es lo que define eso como un método de instancia, en lugar de un método de clase. Supongo que podría crear la base de datos como una propiedad estática en la clase, y luego solo usar métodos de clase (no es necesario en ninguna parte), pero la base de datos sería global para la clase, no solo la instanciación individual de la misma.
Travis
Sí, porque traté de usar su ejemplo para hacer una consulta simple db.query('SELECT ...', var)y se quejó de necesitar un tercer argumento.
Samayo
@samson, primero debe crear una instancia del MyDBobjeto:db = MyDB(); db.query('select...', var)
cowbert
Esto evitó mensajesResourceWarning: unclosed <socket.socket...
Bob Stein
3

Al manejar las conexiones de la base de datos, hay dos cosas que deben preocuparse:

  1. evitar múltiples instancias de conexiones, dejar que cada función abra una conexión de base de datos se considera una mala práctica, dado el número limitado de sesiones de base de datos, se quedaría sin sesiones; al menos su solución no se escalaría, en su lugar, use un patrón singleton, su clase se instanciaría solo una vez, para obtener más información sobre este patrón, consulte el enlace

  2. cerrando la conexión al salir de la aplicación, digamos que no, y que tiene al menos una docena de instancias de la aplicación ejecutándose haciendo lo mismo, al principio todo saldría bien, pero se quedaría sin sesiones de la base de datos, y la única solución sería reiniciar el servidor de la base de datos, lo que no es bueno para una aplicación en vivo, por lo tanto, use la misma conexión siempre que sea posible.

Para solidificar todos estos conceptos, vea el siguiente ejemplo que envuelve 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
fuente
1
¡Hola! Gracias por su respuesta. Pero cuando trato de implementarlo en mi caso, lo tengo if Database._instance is None: NameError: name 'Database' is not defined. No puedo entender qué Databasees y cómo podría solucionarlo.
Ilya Rusin
1
@IlyaRusin fue mi culpa, de hecho, la base de datos es solo una clase principal en la que pongo métodos comunes para diferentes manejos RDBMS ya que me conecto no solo a Postgres. Sin embargo, disculpe el error y espero que la versión corregida pueda serle útil, siéntase libre de agregar, modificar el código según sus necesidades si tiene alguna pregunta relacionada, no lo dude.
ponach
Si llamara repetidamente Postgres.query(Postgres(), some_sql_query)a un whileciclo, ¿se abriría y cerraría la conexión en cada iteración, o lo mantendría abierto durante todo el whileciclo hasta que el programa salga?
@Michael, la clase de conexión se implementa como un singleton, por lo tanto, se instanciará solo una vez, pero en general recomendaría contra la forma de llamada sugerida, en lugar de iniciarla en una variable
ponach
1
@ponach Gracias, hace exactamente lo que quería lograr. Adapte un poco su código e intenté usar una instrucción UPDATE en su query()función, pero parece que hay un problema con mi código cuando ejecuto mi aplicación en "paralelo". Hice una pregunta por separado al respecto: softwareengineering.stackexchange.com/questions/399582/…
2

Sería interesante ofrecer capacidades de administrador de contexto para sus objetos. Esto significa que puede escribir un código como este:

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

Esto le ofrecerá una forma práctica de cerrar la conexión a la base de datos automáticamente llamando a la clase usando la instrucción with:

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

Mucho tiempo para pensar en esto. A día, encontré el camino. No sé, es la mejor manera. crea un archivo con el nombre: conn.py y lo guarda en la carpeta /usr/local/lib/python3.5/site-packages/conn/. Yo uso freebsd y esta es la ruta de mi carpeta de paquetes de sitio. en mi conn.py: conn = "nombrebd = usuario omnívoro = contraseña de postgres = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` y en el script que quiero llamar a la conexión, escribo:

import psycopg2 import psycopg2.extras import psycopg2.extensions

from conn import conn try: conn = psycopg2.connect (conn.conn) excepto: page = "No se puede acceder a la base de datos"

cur = conn.cursor ()

y bla, bla ...

espero que esto sea útil

phong
fuente