web-dev-qa-db-fra.com

Comment gérer les connexions à la base de données dans un module de bibliothèque Python

J'ai créé une bibliothèque en Python qui contient des fonctions pour accéder à une base de données. Il s'agit d'une bibliothèque d'encapsulation autour d'une base de données d'application tierce, écrite du fait que l'application tierce ne propose pas de maintenant. À l'origine, je laissais chaque fonction ouvrir une connexion à la base de données pendant la durée de l'appel de fonction, ce qui était OK, jusqu'à ce que ma logique de programme utilise des appels imbriqués vers les fonctions où j'appellerais alors une fonction particulière plusieurs milliers de fois. n'est pas très performant. Le profilage a montré que la surcharge était dans la configuration de la connexion à la base de données - une fois par appel de fonction. J'ai donc déplacé la connexion ouverte de la ou des fonctions vers le module lui-même, afin que la connexion à la base de données soit ouverte lorsque le module de bibliothèque a été importé, ce qui m'a donné une performance acceptable.

Maintenant, j'ai deux questions à ce sujet. Premièrement, dois-je m'inquiéter de ne plus fermer explicitement la connexion à la base de données et comment pourrais-je le faire explicitement avec cette configuration? Deuxièmement, ce que j'ai fait se rapproche-t-il du domaine des bonnes pratiques et comment pourrais-je autrement aborder cela?

27
leancz

Cela dépend vraiment de la bibliothèque que vous utilisez. Certains d'entre eux pourraient fermer la connexion de leur propre chef (Remarque: j'ai vérifié la bibliothèque sqlite3 intégrée, et ce n'est pas le cas). Python appellera un destructeur lorsqu'un objet sort du cadre, et ces bibliothèques peuvent implémenter un destructeur qui ferme les connexions avec élégance.

Mais ce n'est peut-être pas le cas! Je recommanderais, comme d'autres l'ont fait dans les commentaires, de l'envelopper dans un objet.

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

Cela instanciera votre connexion à la base de données au début et la fermera lorsque l'endroit où votre objet a été instancié devient hors de portée. Remarque: Si vous instanciez cet objet au niveau du module, il persistera pour l'ensemble de votre application. À moins que cela ne soit prévu, je suggérerais de séparer vos fonctions de base de données des fonctions non liées à la base de données.

Heureusement, python a normalisé l'API de base de données , donc cela fonctionnera avec toutes les bases de données conformes pour vous :)

35
Travis

lors de la gestion des connexions à la base de données, il y a deux choses à se soucier:

  1. éviter les instanciations de connexions multiples, laisser chaque fonction ouvrir une connexion à une base de données est considéré comme une mauvaise pratique, étant donné le nombre limité de sessions de base de données, vous manqueriez de sessions; au moins votre solution ne serait pas évolutive, à la place, utilisez un modèle singleton, votre classe ne serait instanciée qu'une seule fois, pour plus d'informations sur ce modèle, voir link

  2. en fermant la connexion à la sortie de l'application, disons que vous ne l'avez pas fait, et que vous avez au moins une douzaine d'instances de l'application en cours d'exécution faisant de même, au début, tout irait bien, mais vous manqueriez de sessions de base de données, et le seul correctif serait de redémarrer le serveur de base de données, ce qui n'est pas une bonne chose pour une application en direct, donc utilisez la même connexion chaque fois que possible.

pour solidifier tous ces concepts, voir l'exemple suivant qui enveloppe 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()
4
ponach

Il serait intéressant d'offrir des capacités de gestionnaire de contexte pour vos objets. Cela signifie que vous pouvez écrire un code comme celui-ci:

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

Cela vous offrira un moyen pratique de fermer automatiquement la connexion à la base de données en appelant la classe à l'aide de l'instruction with:

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