SQLAlchemy w różnych plikach

82

Próbuję wymyślić, jak rozłożyć klasy SQLAlchemy na kilka plików i przez całe życie nie wiem, jak to zrobić. Jestem całkiem nowy w SQLAlchemy, więc wybacz mi, jeśli to pytanie jest trywialne.

Rozważ te 3 klasy w każdym osobnym pliku :

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

A potem powiedz, że mamy main.py coś takiego:

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

Powyższe podaje błąd:

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'

Jak udostępnić deklaratywną podstawę w tych plikach?

Jaki jest „właściwy” sposób osiągnięcia tego celu, biorąc pod uwagę, że mógłbym rzucić na to coś takiego jak Pylony lub Turbogears ?

edytuj 10-03-2011

Znalazłem ten opis we frameworku Pyramids, który opisuje problem i, co ważniejsze, weryfikuje, że jest to rzeczywisty problem, a nie (tylko) moja zdezorientowana jaźń to jest problem. Mam nadzieję, że może pomóc innym, którzy odważą się podążać tą niebezpieczną drogą :)

joveha
źródło
7
@ S.Lott Powyższe działa, jeśli wszystkie zajęcia są w jednym pliku, więc mi powiedz :)
joveha
Twój kod nie powoduje tego błędu, prześlij kod zawierający rzeczywisty błąd. Napraw import, uruchom go, aby ktoś mógł zobaczyć Twój błąd.
knitti
@ S.Lott Moje zamieszanie najwyraźniej dotyczyło tego, jak uniknąć cyklicznego importu. Pochodzę z C, gdzie to nie jest problem. Przepraszam za zajęcie czasu.
joveha
@joveha: Co? Jakie masz te cykliczne problemy z importem. Prześlij kod wraz z cyklicznymi importami, abyśmy mogli wyjaśnić, jak je rozłożyć i uniknąć cykli. W tych komentarzach jest zbyt wiele niejasnych hipotez. Jaki masz problem? Proszę, bądź konkretny.
S.Lott,

Odpowiedzi:

88

Najprostszym rozwiązaniem problemu będzie wziąć Basez modułu, który importuje A, Ba C; Przerwij cykliczny 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()

Działa na moim komputerze:

$ python main.py ; echo $?
0
SingleNegationElimination
źródło
1
Użyj scoped_session.
użytkownik
3
@user: obsługa sesji nie ma związku z pytaniem w tym poście, które jest tak naprawdę zwykłym, starym pytaniem w Pythonie (jak mogę importować rzeczy?); ale odkąd mam swoją uwagę, radzę mocno przed użyciem scoped_session, jeśli nie wiesz, dlaczego potrzebujemy cię pamięć lokalna wątku; Problem z używaniem scoped_sessionpolega na tym, że sprawia, że ​​łatwo jest zakończyć z wyciekami transakcji i nieaktualnymi danymi, bez wyraźnego linku do punktu w kodzie, kiedy to mogło się wydarzyć.
SingleNegationElimination
Ten wzorzec projektowy nie wydaje się działać w przypadku Python3. Czy jest jakaś łatwa poprawka, która jest kompatybilna z Python3?
computermacgyver
@computermacgyver: ten wzorzec powinien działać poprawnie w wersjach Pythona. Proszę zadać nowe pytanie, tak, że można to wszystko z Twojego kodu, a błędy widzisz.
SingleNegationElimination
Dzięki @dequestarmappartialsetattr. Zauważyłem, że błąd występuje tylko wtedy, gdy próbowałem umieścić a.py, b.py, c.py i model.py w oddzielnym module. Znalazłem rozwiązanie w tym przypadku, aby zamiast tego dołączyć kod base.py do pliku __init__.py modułu. Tutaj umieściłem kod i więcej wyjaśnień . Dziękuję za odpowiedź.
computermacgyver
13

Jeśli mogę dodać też trochę rozsądku, ponieważ miałem ten sam problem. Musisz zaimportować klasy w pliku, w którym tworzysz Base = declarative_base()PO utworzeniu Basei Tables. Krótki przykład konfiguracji mojego projektu:

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
Piotr
źródło
8

Używam Pythona 2.7 + Flask 0.10 + SQLAlchemy 1.0.8 + Postgres 9.4.4.1

Ten standardowy szablon jest skonfigurowany z modelami User i UserDetail przechowywanymi w tym samym pliku „models.py” w module „user”. Te klasy dziedziczą z klasy bazowej SQLAlchemy.

Wszystkie dodatkowe klasy, które dodałem do mojego projektu, również wywodzą się z tej klasy bazowej, a gdy plik models.py urósł, postanowiłem podzielić plik models.py na jeden plik na klasę i natknąłem się na opisany problem tutaj.

Rozwiązaniem, które znalazłem, podobnie jak w poście @ computermacgyver z 23 października 2013 r., Było dołączenie wszystkich moich klas do pliku init .py nowego modułu, który utworzyłem, aby przechowywać wszystkie nowo utworzone pliki klas. Wygląda tak:

/project/models/

__init__.py contains

from project.models.a import A 
from project.models.b import B
etc...
RadX3
źródło
2
dlaczego myślisz, że musisz używać Flaska?
noce
0

Dla mnie dodanie import app.tool.tool_entitywnętrza app.pyi from app.tool.tool_entity import Toolwnętrza tool/__init__.pywystarczyło, aby powstał stół. Jednak nie próbowałem jeszcze dodawać relacji.

Struktura folderów:

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
źródło