2013-10-23 12 views
8

Sto progettando una libreria che abbia adattatori che supportano una vasta gamma di librerie. Voglio che la libreria scelga dinamicamente quale adattatore abbia la libreria che usa installata sulla macchina durante l'importazione di classi specifiche.Scegliere l'adattatore dinamicamente in base alle librerie installate

L'obiettivo è essere in grado di modificare la libreria da cui dipende il programma senza dover apportare modifiche al codice. Questa particolare caratteristica è per la gestione delle connessioni RabbitMQ, in quanto abbiamo avuto un sacco di problemi con pika, vogliamo essere in grado di passare a una libreria diversa, ad es. pyAMPQ o rabbitpy senza dover modificare il codice sottostante.

Stavo pensando di implementare qualcosa di simile nel file __init__.py di servicelibrary.simple.

try: 
    #import pika # Is pika installed? 
    from servicelibrary.simple.synchronous import Publisher 
    from servicelibrary.simple.synchronous import Consumer 
except ImportError: 
    #import ampq # Is ampq installed? 
    from servicelibrary.simple.alternative import Publisher 
    from servicelibrary.simple.alternative import Consumer 

Poi, quando l'utente importa la libreria

from servicelibrary.simple import Publisher 

Lo strato sottostante sembra qualcosa di simile

alternative.py

import amqp 

class Publisher(object): 
    ...... 

class Consumer(object): 
    ......  

synchronous.py

import pika 

class Publisher(object): 
    ...... 

class Consumer(object): 
    ...... 

Questo selezionerebbe automaticamente il secondo quando il primo non è installato.

Esiste un modo migliore per implementare qualcosa di simile? Se qualcuno potesse collegare una libreria/adattatore con un'implementazione simile sarebbe utile pure.

[Edit]

Quale sarebbe il modo più pulito di implementare qualcosa di simile? In futuro mi piacerebbe anche essere in grado di modificare la preferenza predefinita. In definitiva potrei accontentarmi di usare la libreria installata, dato che posso controllarla, ma sarebbe una bella funzionalità.

La proposta di Alexanders è interessante, ma mi piacerebbe sapere se c'è un modo più pulito.

[Edit2]

L'esempio originale è stata semplificata. Ogni modulo può contenere più tipi di importazioni, ad es. Consumatore e editore.

+1

Esaminare un'architettura di componenti, ad esempio zope.component, ad esempio. –

risposta

3

Il importlib.import_module potrebbe fare quello che vi serve:

INSTALLED = ['syncronous', 'alternative'] 

for mod_name in INSTALLED: 
    try: 
     module = importlib.import_module('servicelibrary.simple.' + mod_name) 
     Publisher = getattr(module, 'Publisher') 

     if Publisher: 
      break # found, what we needed 

    except ImportError: 
     continue 

immagino, questo non è la tecnica più avanzata, ma l'idea dovrebbe essere chiaro. E puoi anche dare un'occhiata al modulo imp.

+0

Penso che potrei ancora optare per questo, ma voglio vedere se c'è un modo più pulito di implementarlo. Sebbene, con questo, potrei cambiare la libreria preferita regolando l'elenco che è bello. – eandersson

+0

Ho pubblicato la mia versione basandomi su questo. Sono abbastanza soddisfatto del risultato. – eandersson

1

Hai l'idea giusta. Il tuo caso funziona perché ogni suboject ha lo stesso tipo di classi, ad es. entrambe le API hanno una classe chiamata Publisher e puoi semplicemente assicurarti che la versione corretta sia importata.

Se ciò non è vero (se possibile l'implementazione A e B non sono simili) si scrive la propria facciata, che è solo la propria semplice API che quindi chiama la vera API con i metodi/parametri corretti per quella libreria.

Ovviamente il passaggio da una scelta all'altra può richiedere un sovraccarico (non conosco il tuo caso, ma per esempio, supponiamo che tu abbia due librerie per aprire un file aperto e che la libreria gestisca l'apertura del file. basta passare alla seconda libreria nel mezzo del file e aspettarsi che inizi dove la prima libreria si è fermata). Ma è solo una questione di salvarlo:

accessmethods = {} 
try: 
    from modA.modB import classX as apiA_classX 
    from modA.modB import classY as apiA_classY 
    accessmethods['apiA'] = [apiA_classX, apiA_classY] 
    classX = apiA_classX 
    classY = apiA_classY 
except: 
    pass 

try: 
    from modC.modD import classX as apiB_classX 
    from modC.modD import classY as apiB_classY 
    accessmethods['apiB'] = [apiB_classX, apiB_classY] 
    classX = apiB_classX 
    classY = apiB_classY 
except: 
    pass 

def switchMethod(method): 
    global classX 
    global classY 
    try: 
     classX, classY = accessmethods[method] 
    except KeyError as e: 
     raise ValueError, 'Method %s not currently available'%method 

ecc

+0

Grazie per la risposta. Cercherò di implementarlo domani. Fondamentalmente le implementazioni importate sono strati che ho scritto per assicurarmi che indipendentemente dalla libreria utilizzata, si comporti allo stesso modo. Ogni publisher avrà quindi le stesse variabili pubbliche, le stesse funzioni, ecc. – eandersson

1

So due metodi, uno è utilizzato sfrenatamente e un altro è la mia congettura. Puoi sceglierne uno per la tua situazione.

Il primo, che è ampiamente utilizzato, come ad esempio from tornado.concurrent import Future.

try: 
    from concurrent import futures 
except ImportError: 
    futures = None 

#define _DummyFuture balabala... 

if futures is None: 
    Future = _DummyFuture 
else: 
    Future = futures.Future 

Quindi è possibile utilizzare from tornado.concurrent import Future in altri file.

Il secondo, che è la mia ipotesi, e scrivo una semplice demo, ma non l'ho utilizzata in ambiente di produzione perché non ne ho bisogno.

import sys 
try: 
    import servicelibrary.simple.synchronous 
except ImportError: 
    import servicelibrary.simple.alternative 
    sys.modules['servicelibrary.simple.synchronous'] = servicelibrary.simple.alternative 

È possibile eseguire lo script prima dell'altro script import servicelibrary.simple.synchronous. Quindi è possibile utilizzare lo script come prima:

from servicelibrary.simple.synchronous import Publisher 
from servicelibrary.simple.synchronous import Consumer 

L'unica cosa che chiedo è che ciò che sono il consequences della mia congetture.

3

Una soluzione flessibile, utilizzando importlib. Questa è una soluzione completa e funzionante che ho testato.

In primo luogo, l'intestazione:

import importlib 
parent = 'servicelib.simple' 
modules = {'.synchronous':['.alternative', '.alternative_2']} 
success = False #an indicator, default is False, 
#changed to True when the import succeeds. 

Importiamo il modulo necessario, impostare il nostro indicatore, e specificare i nostri moduli. modules è un dizionario, con il set di chiavi come modulo predefinito e il valore come un elenco di alternative.

successivo, la parte di importazione-formica:

#Obtain the module 
for default, alternatives in modules.items(): 
    try: #we will try to import the default module first 
     mod = importlib.import_module(parent+default) 
     success = True 
    except ImportError: #the default module fails, try the alternatives 
     for alt in alternatives: 
      try: #try the first alternative, if it still fails, try the next one. 
       mod = importlib.import_module(parent+alt) 
       success = True 
       #Stop searching for alternatives! 
       break 
      except ImportError: 
        continue 

print 'Success: ', success 

E per avere le classi, fare semplicemente:

Publisher = mod.Publisher 
Consumer = mod.Consumer 

Con questa soluzione, è possibile avere più alternative in una sola volta. Ad esempio, puoi usare sia rabbitpy che pyAMPQ come alternative.

Nota: funziona sia con Python 2 e Python 3.

Se avete altre domande, non esitate a commentare e chiedere!

+0

Grazie. Ho finito con una soluzione basata su questo. – eandersson

+1

Sei il benvenuto! – aIKid

1

In base alle risposte, ho trovato la seguente implementazione per Python 2.7.

Gli esempi sono semplificati per stackoverflow..

from importlib import import_module 

PARENT = 'myservicelib.rabbitmq' 
MODULES = ['test_adapter', 
      'test_two_adapter'] 
SUCCESS = False 

for _module in MODULES: 
    try: 
     __module = import_module('{0}.{1}'.format(PARENT, _module)) 
     Consumer = getattr(__module, 'Consumer') 
     Publisher = getattr(__module, 'Publisher') 
     SUCCESS = True 
     break 
    except ImportError: 
     pass 

if not SUCCESS: 
    raise NotImplementedError('no supported rabbitmq library installed.') 

Anche se, come ho avuto anche alcuni dei miei progetti in corso Python 2.6 ho dovuto né modificare il codice, o includere importlib. Il problema con una piattaforma di produzione è che non è sempre facile includere nuove dipendenze.

Questo è il compromesso che ho trovato, basato su __import__ anziché importlib.

Potrebbe valere la pena verificare se sys.modules contiene effettivamente lo spazio dei nomi in modo da non ottenere un aumento di KeyError, ma è improbabile.

import sys 

PARENT = 'myservicelib.rabbitmq' 
MODULES = ['test_adapter', 
      'test_two_adapter'] 
SUCCESS = False 

for _module in MODULES: 
    try: 
     __module_namespace = '{0}.{1}'.format(PARENT, _module) 
     __import__(__module_namespace) 
     __module = sys.modules[__module_namespace] 
     Consumer = getattr(__module, 'Consumer') 
     Publisher = getattr(__module, 'Publisher') 
     SUCCESS = True 
     break 
    except ImportError: 
     pass 

if not SUCCESS: 
    raise NotImplementedError('no supported rabbitmq library installed.') 
+0

Abbiamo appena notato questo, e volevo sottolineare che questo sembra mancare una delle caratteristiche uniche di Python: la clausola else nelle dichiarazioni try e for (bah, nessuna formattazione del codice nei commenti). "else" in try viene eseguito se non viene lanciata alcuna eccezione, "else" in viene eseguito se non è stata eseguita alcuna interruzione; potresti usarlo per eliminare la variabile SUCCESS. –

Problemi correlati