Come gestire le connessioni al database in un modulo di libreria Python


23

Ho creato una libreria in Python che contiene funzioni per l'accesso a un database. Questa è una libreria wrapper attorno a un database di applicazioni di terze parti, scritta a causa del fatto che l'applicazione di terze parti non offre un'API decente. Ora originariamente lasciavo che ogni funzione aprisse una connessione al database per la durata della chiamata di funzione che era OK, fino a quando la mia logica del programma usava chiamate nidificate alle funzioni in cui avrei poi chiamato una particolare funzione alcune migliaia di volte. Questo non è stato molto performante. La profilazione ha mostrato che l'overhead era nell'impostazione della connessione al database, una volta per chiamata di funzione. Quindi ho spostato la connessione aperta dall'interno delle funzioni al modulo stesso, in modo che la connessione al database venisse aperta al momento dell'importazione del modulo di libreria. Questo mi ha dato una prestazione accettabile.

Ora ho due domande a riguardo. In primo luogo, devo preoccuparmi di non chiudere più esplicitamente la connessione al database e come potrei farlo esplicitamente con questa configurazione? In secondo luogo, ciò che ho fatto si avvicina al regno delle buone pratiche e come potrei altrimenti affrontarlo?


1
Fornisci una openConnfunzione e fai in modo che l'utente la passi a ciascuna funzione che chiama, in questo modo può esplorare la connessione in una withdichiarazione o altro
Daniel Gratzer,

1
Sono d'accordo con jozfeg, prendere in considerazione la creazione di una classe che apre la connessione db all'interno del costruttore e che chiude la connessione all'uscita
Nick Burns,

Risposte:


31

Dipende molto dalla libreria che stai usando. Alcuni potrebbero chiudere la connessione da soli (Nota: ho controllato la libreria sqlite3 integrata e non lo fa). Python chiamerà un distruttore quando un oggetto esce dall'ambito e queste librerie potrebbero implementare un distruttore che chiude le connessioni con grazia.

Tuttavia, potrebbe non essere così! Consiglierei, come altri hanno fatto nei commenti, di avvolgerlo in un oggetto.

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

Ciò creerà un'istanza della connessione al database all'inizio e la chiuderà quando il luogo in cui l'oggetto è stato istanziato non rientra nell'ambito. Nota: se si crea un'istanza di questo oggetto a livello di modulo, persisterà per l'intera applicazione. A meno che non sia previsto, suggerirei di separare le funzioni del database da quelle non di database.

Fortunatamente, python ha standardizzato l'API del database , quindi funzionerà con tutti i DB conformi per te :)


Come si fa a evitare che selfin def query(self,?
samayo,

2
definire evitare? Il sé è ciò che lo definisce come un metodo di istanza, piuttosto che un metodo di classe. Suppongo che potresti creare il database come proprietà statica sulla classe e quindi utilizzare solo metodi di classe (nessun auto necessario ovunque), ma il database sarebbe globale per la classe, non solo la sua istanza individuale.
Travis,

Sì, perché ho provato a usare il tuo esempio per fare una semplice domanda db.query('SELECT ...', var)e si è lamentato della necessità di un terzo argomento.
samayo,

@samson, devi MyDBprima istanziare l' oggetto:db = MyDB(); db.query('select...', var)
cowbert,

Ciò ha impedito i messaggiResourceWarning: unclosed <socket.socket...
Bob Stein,

3

durante la gestione delle connessioni al database ci sono due cose di cui preoccuparsi:

  1. impedire istanze di connessioni multiple, lasciando che ogni funzione apra una connessione al database è considerata una cattiva pratica, dato il numero limitato di sessioni del database, si esaurirebbero le sessioni; almeno la tua soluzione non si ridimensionerebbe, invece, usa il modello singleton, la tua classe verrebbe istanziata una sola volta, per ulteriori informazioni su questo modello vedi link

  2. chiudendo la connessione all'uscita dall'app, diciamo che non lo hai fatto e che hai almeno una dozzina di istanze dell'app in esecuzione che fanno lo stesso, all'inizio tutto andrebbe bene, ma finiresti le sessioni del database e l'unica soluzione sarebbe riavviare il server di database, il che non è una buona cosa per un'app live quindi utilizzare la stessa connessione ogni volta che è possibile.

per consolidare tutti questi concetti, vedere l'esempio seguente che avvolge 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()

1
Ciao! La ringrazio per la risposta. Ma quando provo ad implementarlo nel mio caso, lo faccio if Database._instance is None: NameError: name 'Database' is not defined. Non riesco a capire cosa Databasesia e come potrei risolverlo.
Ilya Rusin,

1
@IlyaRusin è stata colpa mia, infatti Database è solo una classe genitore in cui ho messo metodi comuni per la gestione RDBMS diversa dal momento che mi collego non solo a Postgres. Tuttavia, scusate l'errore e spero che la versione corretta possa esservi utile, sentitevi liberi di aggiungere, modificare il codice in base alle vostre esigenze se avete qualche domanda correlata, non esitate.
ponach,

Se chiamassi ripetutamente Postgres.query(Postgres(), some_sql_query)in un whileciclo, aprirebbe e chiuderebbe comunque la connessione in ogni iterazione, o lo terrebbe aperto per tutto il tempo del whileciclo fino alla chiusura del programma?

@Michael la classe di connessione è implementata come singleton, quindi sarebbe istanziata solo una volta, ma nel complesso lo consiglierei contro il modo di chiamata suggerito, invece
avviarlo

1
@ponach Grazie, fa esattamente quello che volevo ottenere. Ho adattato un po 'il tuo codice e ho provato a utilizzare un'istruzione UPDATE nella tua query()funzione, ma sembra che ci sia un problema con il mio codice quando eseguo la mia app in "parallelo". Ho fatto una domanda separata al riguardo: softwareengineering.stackexchange.com/questions/399582/…

2

Sarebbe interessante offrire funzionalità di gestione del contesto per i tuoi oggetti. Questo significa che puoi scrivere un codice come questo:

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

Questo ti offrirà un modo pratico per chiudere automaticamente la connessione al database chiamando la classe usando l'istruzione with:

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

-1

Molto tempo per pensarci. Oggi ho trovato la strada. non so che sia il modo migliore. si crea un file con il nome: conn.py e lo si salva nella cartella /usr/local/lib/python3.5/site-packages/conn/. Uso freebsd e questo è il percorso della mia cartella dei pacchetti del sito. nel mio conn.py: conn = "dbname = omnivore user = postgres password = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` e nello script voglio chiamare connessione, scrivo:

import psycopg2 import psycopg2.extras import psycopg2.extensions

da conn import conn provare: conn = psycopg2.connect (conn.conn) tranne: page = "Impossibile accedere al database"

cur = conn.cursor ()

e blah blah ....

lo spero utile

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.