2016-03-16 21 views
6

Le applicazioni spesso devono connettersi ad altri servizi (un database, una cache, un'API, ecc.). Per sanità mentale e DRY, vorremmo mantenere tutte queste connessioni in un modulo in modo che il resto della nostra base di codice possa condividere connessioni.Gestione della creazione di connessioni in Python?

Per ridurre boilerplate, l'utilizzo a valle dovrebbe essere semplice:

# app/do_stuff.py 
from .connections import AwesomeDB 

db = AwesomeDB() 

def get_stuff(): 
    return db.get('stuff') 

E impostare la connessione dovrebbe essere semplice:

# app/cli.py or some other main entry point 
from .connections import AwesomeDB 

db = AwesomeDB() 
db.init(username='stuff admin') # Or os.environ['DB_USER'] 

framework web come Django e Flask fare qualcosa del genere, ma ci si sente un po 'goffo:

Connect to a Database in Flask, Which Approach is better? http://flask.pocoo.org/docs/0.10/tutorial/dbcon/

Un grosso problema con questo è che vogliamo un riferimento all'attuale oggetto di connessione invece di un proxy, perché vogliamo mantenere il completamento delle tabulazioni in iPython e in altri ambienti di sviluppo.

Quindi qual è il modo giusto (tm) per farlo? Dopo un paio di iterazioni, ecco la mia idea:

#app/connections.py 
from awesome_database import AwesomeDB as RealAwesomeDB 
from horrible_database import HorribleDB as RealHorribleDB 


class ConnectionMixin(object): 
    __connection = None 

    def __new__(cls): 
     cls.__connection = cls.__connection or object.__new__(cls) 
     return cls.__connection 

    def __init__(self, real=False, **kwargs): 
     if real: 
      super().__init__(**kwargs) 

    def init(self, **kwargs): 
     kwargs['real'] = True 
     self.__init__(**kwargs) 


class AwesomeDB(ConnectionMixin, RealAwesomeDB): 
    pass 


class HorribleDB(ConnectionMixin, RealHorribleDB): 
    pass 

Camera di miglioramento: Impostare __connection iniziale a un ConnectionProxy generica invece di None, che cattura tutti gli accessi di attributo e genera un'eccezione.

Ho fatto un po 'di ricerche qui su SO e in vari progetti OSS e non ho visto nulla di simile. Sembra piuttosto solido, anche se ciò significa che una serie di moduli renderà istanziati gli oggetti di connessione come un effetto collaterale al momento dell'importazione. Mi farà esplodere in faccia? Ci sono altre conseguenze negative per questo approccio?

risposta

0

In primo luogo, il design-saggio, potrei mancare qualcosa, ma non vedo il motivo per cui è necessario il pesante mixin + macchinari Singleton anziché definire soltanto un aiuto in questo modo:

_awesome_db = None 
def awesome_db(**overrides): 
    global _awesome_db 
    if _awesome_db is None: 
     # Read config/set defaults. 
     # overrides.setdefault(...) 
     _awesome_db = RealAwesomeDB(**overrides) 
    return _awesome_db 

Inoltre, ci è un bug che potrebbe non sembrare un caso d'uso sostenuto, ma in ogni caso: se si fanno le seguenti 2 chiamate di fila, si potrebbe erroneamente ottenere lo stesso oggetto di connessione due volte anche se hai superato diversi parametri:

db = AwesomeDB() 
db.init(username='stuff admin') 

db = AwesomeDB() 
db.init(username='not-admin') # You'll get admin connection here. 

Una soluzione semplice sarebbe quella di utilizzare un ditt di connessioni digitate sul parametri di input.

Ora, sull'essenza della domanda.

Penso che la risposta dipenda da come le vostre "connessioni" sono effettivamente implementate.

potenziali aspetti negativi con il vostro approccio che vedo sono:

  • In un ambiente multithreading si potrebbe ottenere problemi con l'accesso simultaneo unsychronized all'oggetto di connessione globale da più thread, a meno che non sia già thread-safe. Se ti interessa, puoi cambiare il tuo codice e interfacciarne un po 'e usare una variabile locale del thread.

  • Cosa succede se un processo si blocca dopo aver creato la connessione? I server di applicazioni Web tendono a farlo e potrebbero non essere sicuri, sempre in base alla connessione sottostante.

  • L'oggetto di connessione ha lo stato? Cosa succede se l'oggetto connessione diventa non valido (a causa di errore/timeout della connessione)? Potrebbe essere necessario sostituire la connessione interrotta con una nuova per tornare la prossima volta che viene richiesta una connessione.

gestione delle connessioni è spesso già implementata in modo efficiente e sicuro attraverso un connection pool in librerie client.

Ad esempio, il client Redis Redis-py utilizza la seguente implementazione:

https://github.com/andymccurdy/redis-py/blob/1c2071762ad9b9288e786665990083e61c1cf355/redis/connection.py#L974

Il cliente Redis utilizza poi il pool di connessioni in questo modo:

Quindi, poiché il client Redis gestisce tutto ciò che è sotto il cofano, puoi tranquillamente fare ciò che vuoi direttamente. Le connessioni verranno create pigramente finché il pool di connessioni non raggiungerà la piena capacità.

# app/connections.py 
def redis_client(**kwargs): 
    # Maybe read configuration/set default arguments 
    # kwargs.setdefault() 
    return redis.Redis(**kwargs) 

Analogamente, SQLAlchemy può utilizzare connection pooling as well.

In sintesi, la mia comprensione è che:

  • Se la libreria client supporta il pool di connessioni, non c'è bisogno di fare nulla di speciale per condividere le connessioni tra i moduli e anche discussioni. Si potrebbe semplicemente definire un helper simile a redis_client() che legge la configurazione o specifica i parametri predefiniti.

  • Se la libreria client fornisce solo oggetti di connessione di basso livello, è necessario assicurarsi che l'accesso ad essi sia sicuro per i thread e sicuro da fork. Inoltre, è necessario assicurarsi che ogni volta che si restituisce una connessione valida (o sollevare un'eccezione se non è possibile stabilire o riutilizzare uno esistente).

+0

Grazie per la risposta approfondita! Non avevo davvero preso in considerazione la sicurezza di thread e fork, ci penserò sicuramente. Re: bug dei parametri di connessione, buona cattura e buona correzione. Ri: i pool client, anche un buon punto, ma anche con un pool di connessioni, è necessario centralizzare l'inizializzazione e garantire che si stia utilizzando lo stesso pool in tutta l'applicazione. – knite

+0

Re: semplice funzione di supporto, ho considerato questo approccio in origine. Diventa rapidamente fastidioso a causa di ogni modulo che ha bisogno di importare awesome_db * e * chiamarlo. – knite