SQLAlchemy-Klassen über Dateien hinweg

82

Ich versuche herauszufinden, wie SQLAlchemy-Klassen auf mehrere Dateien verteilt werden können, und ich kann für mein Leben nicht herausfinden, wie das geht. Ich bin ziemlich neu in SQLAlchemy, also vergib mir, wenn diese Frage trivial ist.

Betrachten Sie diese 3 Klassen in jeder eigenen Datei :

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"))

Und dann sagen wir, wir haben eine main.py wie diese:

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()

Das obige gibt den Fehler:

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'

Wie teile ich die deklarative Basis für diese Dateien?

Was ist der "richtige" Weg, um dies zu erreichen, wenn man bedenkt, dass ich so etwas wie Pylone oder Turbogears darüber werfen könnte ?

bearbeiten 10-03-2011

Ich fand diese Beschreibung von den Pyramiden Rahmen , die das Problem und was noch wichtiger beschreiben prüft , dass dies ein tatsächliches Problem ist und nicht (nur) nur mein verwirrtes Selbst , das ist das Problem. Hoffe, es kann anderen helfen, die es wagen, diesen gefährlichen Weg zu gehen :)

Joveha
quelle
7
@ S.Lott Das obige funktioniert, wenn alle Klassen in einer Datei sind, also sagst du es mir :)
joveha
Ihr Code gibt diesen Fehler nicht aus. Bitte geben Sie den Code ein, der den tatsächlichen Fehler enthält. Korrigieren Sie Ihre Importe und lassen Sie sie laufen, damit jemand Ihren Fehler tatsächlich sehen kann.
Knitti
@ S.Lott Meine Verwirrung drehte sich anscheinend darum, wie man zyklische Importe vermeidet. Ich komme aus C, wo dies kein Problem ist. Ich entschuldige mich dafür, dass Sie sich Zeit genommen haben.
Joveha
@joveha: Was? Was sind diese zyklischen Importprobleme, die Sie haben? Bitte posten Sie den Code mit den zyklischen Importen, damit wir erklären können, wie man sie zerlegt und die Zyklen vermeidet. Diese Kommentare enthalten zu viele vage Hypothesen. Welches Problem hast du? Bitte erläutern.
S.Lott

Antworten:

87

Die einfachste Lösung für Ihr Problem zu nehmen Basevon dem Modul heraus , daß die Einfuhren A, Bund C; Unterbrechen Sie den zyklischen Import.

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()

Funktioniert auf meiner Maschine:

$ python main.py ; echo $?
0
SingleNegationElimination
quelle
1
Verwenden Sie scoped_session.
Benutzer
3
@user: Die Sitzungsbehandlung hat nichts mit der Frage in diesem Beitrag zu tun. Dies ist wirklich eine einfache alte Python-Frage (wie importiere ich Sachen?). aber da ich Ihre Aufmerksamkeit haben, würde ich raten , stark gegen verwenden scoped_session, es sei denn , Sie wissen , warum Sie lokale Speicher fädeln braucht; Das Problem bei der Verwendung scoped_sessionbesteht darin, dass es zu einfach ist, durchgesickerte Transaktionen und veraltete Daten zu beenden, ohne explizite Verknüpfung zu dem Punkt in Ihrem Code, an dem dies möglicherweise geschehen ist.
SingleNegationElimination
Dieses Entwurfsmuster scheint für Python3 nicht zu funktionieren. Gibt es eine einfache Lösung, die mit Python3 kompatibel ist?
Computermacgyver
@computermacgyver: Dieses Muster sollte in allen Python-Versionen korrekt funktionieren. Bitte stellen Sie eine neue Frage, damit Sie Ihren gesamten Code und die angezeigten Fehler einfügen können.
SingleNegationElimination
Danke @dequestarmappartialsetattr. Ich habe festgestellt, dass der Fehler nur auftritt, wenn ich versucht habe, a.py, b.py, c.py und model.py in ein separates Modul einzufügen. In diesem Fall bestand die Lösung darin, stattdessen den base.py-Code in die __init__.py-Datei des Moduls aufzunehmen. Ich habe den Code und weitere Erklärungen hier eingefügt . Danke für die Antwort.
Computermacgyver
13

Wenn ich auch meinen Sinn hinzufügen darf, da ich das gleiche Problem hatte. Sie müssen die Klassen in die Datei importieren, in der Sie die erstellen, nachdem Sie Base = declarative_base()die Baseund die erstellt haben Tables. Kurzes Beispiel für die Einrichtung meines Projekts:

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'))

model / __ 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
Peter
quelle
8

Ich verwende Python 2.7 + Flask 0.10 + SQLAlchemy 1.0.8 + Postgres 9.4.4.1

Dieses Boilerplate wird mit einem User- und einem UserDetail-Modell konfiguriert, die in derselben Datei "models.py" im Modul "user" gespeichert sind. Diese Klassen erben beide von einer SQLAlchemy-Basisklasse.

Alle zusätzlichen Klassen, die ich meinem Projekt hinzugefügt habe, stammen ebenfalls von dieser Basisklasse. Als die Datei models.py größer wurde, entschied ich mich, die Datei models.py in eine Datei pro Klasse aufzuteilen, und stieß auf das beschriebene Problem Hier.

Die Lösung, die ich in Anlehnung an den Beitrag von @ computermacgyver vom 23. Oktober 2013 gefunden habe, bestand darin, alle meine Klassen in die init .py-Datei des neuen Moduls aufzunehmen, das ich erstellt habe, um alle neu erstellten Klassendateien aufzunehmen. Sieht aus wie das:

/project/models/

__init__.py contains

from project.models.a import A 
from project.models.b import B
etc...
RadX3
quelle
2
Warum denkst du, musst du Flask verwenden?
Nächte
0

Für mich war das Hinzufügen von import app.tool.tool_entityinnen app.pyund from app.tool.tool_entity import Toolinnen tool/__init__.pygenug, um die Tabelle zu erstellen. Ich habe jedoch noch nicht versucht, eine Beziehung hinzuzufügen.

Ordnerstruktur:

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
quelle