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

23

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'applications tierces, écrite du fait que l'application tierce n'offre pas une API décente. Maintenant, je laissais à l'origine 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. Ce n'était 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 lors de l'importation du module de bibliothèque. Cela 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, est-ce que ce que j'ai fait se rapproche du domaine des bonnes pratiques et comment pourrais-je autrement aborder cela?

leancz
la source
1
Fournissez une openConnfonction et demandez à l'utilisateur de la transmettre à chaque fonction qu'il appelle, de cette façon, il peut étendre la connexion dans une withinstruction ou autre
Daniel Gratzer
1
Je suis d'accord avec jozfeg, envisagez de créer une classe qui ouvre la connexion db au sein du constructeur et qui ferme la connexion à la sortie
Nick Burns

Réponses:

31

Cela dépend vraiment de la bibliothèque que vous utilisez. Certains d'entre eux pourraient fermer la connexion par eux-mêmes (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 de la portée, 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, le python a standardisé l'API de base de données , donc cela fonctionnera avec toutes les bases de données conformes pour vous :)

Travis
la source
Comment éviter cela selfen def query(self,?
samayo
2
définir éviter? Le self est ce qui définit cela comme une méthode d'instance, plutôt que comme une méthode de classe. Je suppose que vous pouvez créer la base de données en tant que propriété statique sur la classe, puis utiliser uniquement des méthodes de classe (aucun auto-requis n'importe où), mais la base de données serait globale pour la classe, pas seulement son instanciation individuelle.
Travis
Oui, parce que j'ai essayé d'utiliser votre exemple pour faire une simple requête au fur db.query('SELECT ...', var)et à mesure qu'il se plaignait d'avoir besoin d'un troisième argument.
samayo
@samson, vous devez d'abord instancier l' MyDBobjet:db = MyDB(); db.query('select...', var)
cowbert
Cela a empêché les messagesResourceWarning: unclosed <socket.socket...
Bob Stein
3

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, utilisez plutôt un modèle singleton, votre classe ne serait instanciée qu'une seule fois, pour plus d'informations sur ce modèle, voir le lien

  2. la fermeture de 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()
ponach
la source
1
Salut! Merci pour votre réponse. Mais quand j'essaie de l'implémenter dans mon cas, je l'ai if Database._instance is None: NameError: name 'Database' is not defined. Je ne peux pas comprendre ce que Databasec'est et comment je pourrais y remédier.
Ilya Rusin du
1
@IlyaRusin qui était ma faute, en fait, Database n'est qu'une classe parente dans laquelle j'ai mis des méthodes courantes pour la gestion de différents SGBDR puisque je me connecte non seulement à Postgres. Cependant désolé pour l'erreur et j'espère que la version corrigée pourra vous être utile, n'hésitez pas à l'ajouter, à modifier le code selon vos besoins si vous avez une question connexe n'hésitez pas.
ponach
Si j'appelais à plusieurs reprises Postgres.query(Postgres(), some_sql_query)dans une whileboucle, ouvrirait-elle et fermerait-elle toujours la connexion à chaque itération, ou la maintiendrait-elle ouverte pendant toute la durée de la whileboucle jusqu'à la fin du programme?
@Michael, la classe de connexion est implémentée en tant que singleton, donc elle ne serait instanciée qu'une seule fois, mais dans l'ensemble, je recommanderais contre le mode d'appel suggéré, plutôt l'initier dans une variable
ponach
1
@ponach Merci, il fait exactement ce que je voulais réaliser. J'ai un peu adapté votre code et essayé d'utiliser une instruction UPDATE dans votre query()fonction, mais il semble qu'il y ait un problème avec mon code lorsque j'exécute mon application en "parallèle". J'ai fait une question distincte à ce sujet: softwareengineering.stackexchange.com/questions/399582/…
2

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.
Billal Begueradj
la source
-1

Beaucoup de temps pour y penser. Aujourd'hui, j'ai trouvé le chemin. je ne sais pas c'est la meilleure façon. vous créez un fichier avec le nom: conn.py et l'enregistrez dans le dossier /usr/local/lib/python3.5/site-packages/conn/. J'utilise freebsd et c'est le chemin de mon dossier site-packages. dans mon conn.py: conn = "dbname = omnivore user = postgres password = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` ` et dans le script je veux appeler connection, j'écris:

importation psycopg2 importation psycopg2.extras importation psycopg2.extensions

depuis conn import conn conn: conn = psycopg2.connect (conn.conn) sauf: page = "Impossible d'accéder à la base de données"

cur = conn.cursor ()

et bla bla ...

j'espère que c'est utile

phong
la source