Clases de SQLAlchemy en archivos

82

Estoy tratando de averiguar cómo hacer que las clases de SQLAlchemy se distribuyan en varios archivos y, por mi vida, no puedo averiguar cómo hacerlo. Soy bastante nuevo en SQLAlchemy, así que perdóname si esta pregunta es trivial.

Considere estas 3 clases en cada uno su propio archivo :

A.py:

from sqlalchemy import *
from main import Base

class A(Base):
    __tablename__ = "A"
    id  = Column(Integer, primary_key=True)
    Bs  = relationship("B", backref="A.id")
    Cs  = relationship("C", backref="A.id")

B.py:

from sqlalchemy import *
from main import Base

class B(Base):
    __tablename__ = "B"
    id    = Column(Integer, primary_key=True)
    A_id  = Column(Integer, ForeignKey("A.id"))

C.py:

from sqlalchemy import *
from main import Base

class C(Base):
    __tablename__ = "C"    
    id    = Column(Integer, primary_key=True)
    A_id  = Column(Integer, ForeignKey("A.id"))

Y luego digamos que tenemos un main.py algo como esto:

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref, sessionmaker

Base = declarative_base()

import A
import B
import C

engine = create_engine("sqlite:///test.db")
Base.metadata.create_all(engine, checkfirst=True)
Session = sessionmaker(bind=engine)
session = Session()

a  = A.A()
b1 = B.B()
b2 = B.B()
c1 = C.C()
c2 = C.C()

a.Bs.append(b1)
a.Bs.append(b2)    
a.Cs.append(c1)
a.Cs.append(c2)    
session.add(a)
session.commit()

Lo anterior da el error:

sqlalchemy.exc.NoReferencedTableError: Foreign key assocated with column 'C.A_id' could not find table 'A' with which to generate a foreign key to target column 'id'

¿Cómo comparto la base declarativa entre estos archivos?

¿Cuál es la forma "correcta" de lograr esto, considerando que podría arrojar algo como Pylons o Turbogears encima de esto?

editar 10-03-2011

Encontré esta descripción del marco de Pyramids que describe el problema y, lo que es más importante, verifica que este es un problema real y no (solo) mi yo confundido, ese es el problema. Espero que pueda ayudar a otros que se atreven por este camino peligroso :)

joveha
fuente
7
@ S.Lott Lo anterior funciona si todas las clases están en un archivo, así que dime :)
joveha
Su código no da este error, publique el código que tiene el error real. Corrija sus importaciones, hágalo funcionar para que alguien pueda ver su error.
Knitti
@ S.Lott Mi confusión aparentemente se centró en cómo evitar las importaciones cíclicas. Vengo de C donde esto no es un problema. Mis disculpas por tomarse su tiempo.
joveha
@joveha: ¿Qué? ¿Cuáles son estos problemas de importación cíclica que tiene? Publique el código con las importaciones cíclicas para que podamos explicar cómo descomponerlas y evitar los ciclos. Hay demasiadas hipótesis vagas en estos comentarios. ¿Qué problema tienes? Por favor sea especifico.
S.Lott

Respuestas:

87

La solución más sencilla a su problema será llevar Basea cabo del módulo que las importaciones A, By C; Rompe la importación cíclica.

base.py

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

a.py

from sqlalchemy import *
from base import Base
from sqlalchemy.orm import relationship

class A(Base):
    __tablename__ = "A"
    id  = Column(Integer, primary_key=True)
    Bs  = relationship("B", backref="A.id")
    Cs  = relationship("C", backref="A.id")

b.py

from sqlalchemy import *
from base import Base

class B(Base):
    __tablename__ = "B"
    id    = Column(Integer, primary_key=True)
    A_id  = Column(Integer, ForeignKey("A.id"))

c.py

from sqlalchemy import *
from base import Base

class C(Base):
    __tablename__ = "C"    
    id    = Column(Integer, primary_key=True)
    A_id  = Column(Integer, ForeignKey("A.id"))

main.py

from sqlalchemy import create_engine
from sqlalchemy.orm import relationship, backref, sessionmaker

import base


import a
import b
import c

engine = create_engine("sqlite:///:memory:")
base.Base.metadata.create_all(engine, checkfirst=True)
Session = sessionmaker(bind=engine)
session = Session()

a1 = a.A()
b1 = b.B()
b2 = b.B()
c1 = c.C()
c2 = c.C()

a1.Bs.append(b1)
a1.Bs.append(b2)    
a1.Cs.append(c1)
a1.Cs.append(c2)    
session.add(a1)
session.commit()

Funciona en mi máquina:

$ python main.py ; echo $?
0
SingleNegationElimination
fuente
1
Utilice scoped_session.
usuario
3
@usuario: el manejo de sesiones no está relacionado con la pregunta de esta publicación, que en realidad es una pregunta simple de Python (¿cómo importo cosas?); pero como tengo su atención, le recomiendo encarecidamente no usarlo scoped_session, a menos que sepa por qué necesita almacenamiento local de subprocesos; El problema con el uso scoped_sessiones que hace que todo sea demasiado fácil para terminar con transacciones filtradas y datos obsoletos, sin un vínculo explícito al punto en su código cuando eso podría haber sucedido.
SingleNegationElimination
Este patrón de diseño no parece funcionar para python3. ¿Existe alguna solución fácil que sea compatible con python3?
computermacgyver
@computermacgyver: este patrón debería funcionar correctamente en todas las versiones de Python. Haga una nueva pregunta para que pueda incluir todo su código y los errores que está viendo.
SingleNegationElimination
Gracias @dequestarmappartialsetattr. Encontré que el error solo ocurre cuando intenté poner a.py, b.py, c.py y model.py en un módulo separado. Encontré que la solución en ese caso era incluir el código base.py en el archivo __init__.py del módulo. He puesto el código y más explicaciones aquí . Gracias por la respuesta.
computermacgyver
13

Si puedo agregar mi sentido común también, ya que tuve el mismo problema. Necesita importar las clases en el archivo donde crea el Base = declarative_base()DESPUÉS de que creó elBase y el Tables. Breve ejemplo de cómo se configura mi proyecto:

model / user.py

from sqlalchemy import *
from sqlalchemy.orm import relationship

from model import Base

class User(Base):
     __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    budgets = relationship('Budget')

model / budget.py

from sqlalchemy import *

from model import Base

class Budget(Base):
    __tablename__ = 'budget'

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('user.id'))

modelo / __ init__.py

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

_DB_URI = 'sqlite:///:memory:'
engine = create_engine(_DB_URI)

Base = declarative_base()
Base.metadata.create_all(engine)
DBSession = sessionmaker(bind=engine)
session = DBSession()

from .user import User
from .budget import Budget
Pedro
fuente
8

Estoy usando Python 2.7 + Flask 0.10 + SQLAlchemy 1.0.8 + Postgres 9.4.4.1

Esta plantilla viene configurada con un usuario y modelos UserDetail almacenados en el mismo archivo "modelos.py" en el módulo "usuario". Ambas clases heredan de una clase base SQLAlchemy.

Todas las clases adicionales que agregué a mi proyecto también se derivaron de esta clase base, y a medida que el archivo models.py creció, decidí dividir el archivo models.py en un archivo por clase y encontré el problema descrito aquí.

La solución que encontré, en la misma línea que la publicación de @ computermacgyver del 23 de octubre de 2013, fue incluir todas mis clases en el archivo init .py del nuevo módulo que creé para contener todos los archivos de clases recién creados. Se ve como esto:

/project/models/

__init__.py contains

from project.models.a import A 
from project.models.b import B
etc...
RadX3
fuente
2
¿Por qué crees que necesitas usar Flask?
noches del
0

Para mi, agregando por import app.tool.tool_entitydentro app.pyy por from app.tool.tool_entity import Tooldentrotool/__init__.py fue suficiente para crear la tabla. Sin embargo, todavía no he intentado agregar una relación.

Estructura de carpetas:

app/
  app.py
  tool/
    __init__.py
    tool_entity.py
    tool_routes.py
# app/tool/tool_entity.py

from app.base import Base
from sqlalchemy import Column, Integer, String


class Tool(Base):
    __tablename__ = 'tool'

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)
    fullname = Column(String)
    fullname2 = Column(String)
    nickname = Column(String)

    def __repr__(self):
        return "<User(name='%s', fullname='%s', nickname='%s')>" % (
            self.name, self.fullname, self.nickname)
# app/tool/__init__.py
from app.tool.tool_entity import Tool
# app/app.py

from flask import Flask
from sqlalchemy import create_engine
from app.tool.tool_routes import tool_blueprint
from app.base import Base


db_dialect = 'postgresql'
db_user = 'postgres'
db_pwd = 'postgrespwd'
db_host = 'db'
db_name = 'db_name'
engine = create_engine(f'{db_dialect}://{db_user}:{db_pwd}@{db_host}/{db_name}', echo=True)
Base.metadata.create_all(engine)


app = Flask(__name__)
@app.route('/')
def hello_world():
    return 'hello world'


app.register_blueprint(tool_blueprint, url_prefix='/tool')

if __name__ == '__main__':
    # you can add this import here, or anywhere else in the file, as debug (watch mode) is on, 
    # the table should be created as soon as you save this file.
    import app.tool.tool_entity
    app.run(host='0.0.0.0', port=5000, debug=True)
Ambroise Rabier
fuente