10

Estoy diseñando una pequeña aplicación GUI para envolver una base de datos sqlite (operaciones CRUD simples). He creado tres modelos SQLAlchemy (m_person, m_card.py, m_loan.py, todo en una carpeta /models) y anteriormente había tenido el siguiente código en la parte superior de cada uno:¿Está bien ejecutar código cuando importa un módulo?

from sqlalchemy import Table, Column, create_engine 
from sqlalchemy import Integer, ForeignKey, String, Unicode 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import backref, relation 

engine = create_engine("sqlite:///devdata.db", echo=True) 
declarative_base = declarative_base(engine) 
metadata = declarative_base.metadata 

esto se sentía un poco mal (SECO) por lo que se sugirió que moviera todas estas cosas al nivel de módulo (en models/__init__.py).

from sqlalchemy import create_engine 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import sessionmaker 
from sqlalchemy import Table, Column, Boolean, Unicode 
from settings import setup 

engine = create_engine('sqlite:///' + setup.get_db_path(), echo=False) 
declarative_base = declarative_base(engine) 
metadata = declarative_base.metadata 

session = sessionmaker() 
session = session() 

..y importar declarative_base así:

from sqlalchemy import Table, Column, Unicode 
from models import declarative_base 


class Person(declarative_base): 
    """ 
    Person model 

    """ 
    __tablename__ = "people" 

    id = Column(Unicode(50), primary_key=True) 
    fname = Column(Unicode(50)) 
    sname = Column(Unicode(50)) 

Sin embargo he tenido un montón de comentarios que la ejecución de código como las importaciones de módulos de este tipo es malo? Estoy buscando una respuesta definitiva en el camino correcto para hacerlo, ya que parece que al tratar de eliminar la repetición de código he introducido algunas otras malas prácticas. Cualquier comentario sería realmente útil.

(A continuación se muestra la get_db_path() método de settings/setup.py está completo como se le llama en el código models/__init__.py anteriormente.)

def get_db_path(): 

    import sys 
    from os import makedirs 
    from os.path import join, dirname, exists 
    from constants import DB_FILE, DB_FOLDER 

    if len(sys.argv) > 1: 
     db_path = sys.argv[1] 
    else: 
     #Check if application is running from exe or .py and adjust db path accordingly 
     if getattr(sys, 'frozen', False): 
      application_path = dirname(sys.executable) 
      db_path = join(application_path, DB_FOLDER, DB_FILE) 
     elif __file__: 
      application_path = dirname(__file__) 
      db_path = join(application_path, '..', DB_FOLDER, DB_FILE) 

    #Check db path exists and create it if not 
    def ensure_directory(db_path): 
     dir = dirname(db_path) 
     if not exists(dir): 
      makedirs(dir) 

    ensure_directory(db_path) 

    return db_path 
+1

Mi 2c, aunque generalmente está bien poner el código de inicio en '__init __. Py' (¡eso es lo que está ahí!), Yendo tan lejos como para establecer la URL de la base de datos, me parece erróneo. Puede hacer las pruebas más difíciles. Me parece que lo que debes hacer es tener una función 'init()' para el módulo. – cha0site

+0

Creo que el problema aquí es que la clase base 'declarative_base' se crea dinámicamente y parece depender del motor de la base de datos. No sé cómo funciona sqlalchemy, pero ¿no es posible definir las clases de modelo antes de hacer la conexión de DB? Apenas puedo creer eso. –

+0

@NiklasB. La 'declarative_base' depende de los" metadatos ", que es básicamente el motor core sqlalchemy. No necesariamente tiene vínculos con la base de datos, aunque puede optar por darle uno por simplicidad. – wberry

Respuesta

5

Algunos marcos populares (Twisted es un ejemplo) llevar a cabo una buena cantidad de inicialización lógica en el tiempo de importación. Los beneficios de poder construir dinámicamente los contenidos del módulo tienen un precio, uno de ellos es que los IDE no siempre pueden decidir qué está "en" el módulo o no.

En su caso específico, es posible que desee refactorizar para que el motor en particular no se suministre en el momento de la importación, sino más tarde. Puede crear los metadatos y su clase declarative_base en el momento de la importación. Luego, durante la hora de inicio, después de definir todas las clases, llame al create_engine y vincule el resultado a su sqlalchemy.orm.sessionmaker. Pero si sus necesidades son simples, tal vez ni siquiera necesite llegar tan lejos.

En general, diría que esto no es Java o C. No hay ninguna razón para temer hacer cosas en el nivel del módulo que no sean definir funciones, clases y constantes. Sus clases se crean cuando la aplicación comienza de todos modos, una después de la otra. Un pequeño parche de mono de clases (¡en el mismo módulo!), O la creación de una o dos tablas de búsqueda globales, está bien en mi opinión si simplifica su implementación.

Lo que definitivamente evitaría es cualquier código en su módulo que haga que el orden de las importaciones sea importante para sus usuarios (que no sea la manera normal de simplemente proporcionar lógica para usar), o que modifique el comportamiento del código fuera de su módulo. Entonces su módulo se convierte en magia negra, que es aceptado en el mundo de Perl (ala use strict;), pero me parece que no es "pitónico".

Por ejemplo, si su módulo modifica las propiedades de sys.stdout cuando se importa, yo diría que el comportamiento debería moverse en una función que el usuario puede llamar o no.

2

Me encontré con esto también y creé un archivo database.py con una clase de administrador de base de datos, después de lo cual creé un solo objeto global. De esa forma, la clase podría leer configuraciones de mi archivo settings.py para configurar la base de datos, y la primera clase que necesitaba usar un objeto base (o sesión/motor) inicializaría el objeto global, después de lo cual todos simplemente lo volverían a usar. Me siento mucho más cómodo teniendo "de mi proyecto".base de datos de importación DM" en la parte superior de cada clase utilizando el SQLAlchemy ORM, donde DM es mi objeto de base de datos global, y luego DM.getBase() para obtener el objeto base

Aquí es mi clase database.py:.

Session = scoped_session(sessionmaker(autoflush=True)) 

class DatabaseManager(): 
    """ 
    The top level database manager used by all the SQLAlchemy classes to fetch their session/declarative base. 
    """ 
    engine = None 
    base = None 

    def ready(self): 
     """Determines if the SQLAlchemy engine and base have been set, and if not, initializes them.""" 
     host='<database connection details>' 
     if self.engine and self.base: 
      return True 
     else: 
      try: 
       self.engine = create_engine(host, pool_recycle=3600) 
       self.base = declarative_base(bind=self.engine) 
       return True 
      except: 
       return False 

    def getSession(self): 
     """Returns the active SQLAlchemy session.""" 
     if self.ready(): 
      session = Session() 
      session.configure(bind=self.engine) 
      return session 
     else: 
      return None 

    def getBase(self): 
     """Returns the active SQLAlchemy base.""" 
     if self.ready(): 
      return self.base 
     else: 
      return None 

    def getEngine(self): 
     """Returns the active SQLAlchemy engine.""" 
     if self.ready(): 
      return self.engine 
     else: 
      return None 

DM = DatabaseManager() 
3

en principio no hay nada malo con la ejecución de código Python cuando se importa un módulo, de hecho cada módulo de Python solo funciona de esa manera. la definición de miembros de módulo está ejecutando el código, después de todo.

Sin embargo, en su particular, caso de uso, le recomiendo encarecidamente que no cree un objeto de sesión de base de datos singleton en su base de código. o hacer muchas cosas, por ejemplo, probar su modelo con un SQLite en memoria u otro tipo de motor de base de datos.

Eche un vistazo a documentation for declarative_base y observe cómo los ejemplos pueden crear el modelo con una clase suministrada declarative_base que aún no está vinculada a un motor de base de datos. Lo ideal es que quieras hacer eso y luego tener algún tipo de función o clase de conexión que administre creando una sesión y luego vincularla a la base.

+0

Si entiendo correctamente, ¿está aconsejando no crear un objeto de sesión y luego simplemente pasarlo, sino crear una nueva sesión cada vez que necesito usarlo? – johnharris85

+0

No, puede crear uno y pasarlo, simplemente no lo cree a nivel de módulo. Puede hacer que devuelva una clase por 'sessionmaker' o una de sus envolturas en el nivel de módulo, pero no tiene ninguna intancia de nivel de módulo de esa clase. –

2

No hay nada de malo con la ejecución del código en el momento de la importación.

La guía se está ejecutando lo suficiente como para que su módulo sea utilizable, pero no tanto que la importación sea innecesariamente lenta, y no tanto que limite innecesariamente lo que se puede hacer con ella.

Por lo general, esto significa definir clases, funciones y nombres globales (en realidad el nivel del módulo - poco del nombre incorrecto allí), así como importar cualquier cosa que sus clases, funciones, etc. necesiten operar.

Esto generalmente no implica hacer conexiones a bases de datos, sitios web u otros recursos dinámicos externos, sino que proporciona una función para establecer esas conexiones cuando el usuario del módulo está listo para hacerlo.

Cuestiones relacionadas