跨文件使用SQLAlchemy类

112
我正在尝试解决如何在多个文件中分散使用SQLAlchemy类的问题,但我无论如何都无法解决。我对SQLAlchemy还比较陌生,如果这个问题很基础,请原谅我。考虑在每个文件中有以下3个类:

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

然后我们有一个类似这样的main.py

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

以上代码会出现错误:
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'

如何在这些文件之间共享声明性基础?
考虑到我可能会在此基础上添加像Pylons或Turbogears这样的东西,那么“正确”的完成方式是什么?
编辑于10-03-2011:
我在Pyramids框架中找到了这个描述,它描述了问题,更重要的是验证了这是一个实际的问题,而不仅仅是我困惑的问题。希望它能帮助其他敢于走这条危险路线的人 :)

7
如果所有类都在一个文件中,上述方法就有效,那么你告诉我吧 :) - joveha
你的代码没有出现这个错误,请发布真正出错的代码。修复你的导入,让它运行,这样别人才能真正“看到”你的错误。 - knitti
1
@S.Lott 我的困惑显然集中在如何避免循环导入上。我来自 C 语言,这不是问题。对于浪费您的时间,我深表歉意。 - joveha
@joveha:什么?你遇到了哪些循环导入问题。请发布带有循环导入的代码,以便我们可以解释如何分解它们并避免循环。这些评论中有太多模糊的假设。你遇到了什么问题?请具体说明。 - S.Lott
5个回答

114

最简单的解决方法是将Base从导入ABC的模块中移除,打破循环导入。

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

主程序.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()

我的电脑上可以运行:

$ python main.py ; echo $?
0

1
使用 scoped_session - user
7
@user: 这篇文章中的问题与会话处理无关,实际上是一个普通的Python问题(如何导入东西?);但既然我引起了你的注意,我要强烈建议不要使用scoped_session,除非你知道自己需要线程本地存储;使用scoped_session的问题在于很容易出现泄漏的事务和过时的数据,而且没有明确的链接到可能发生这种情况的代码点。 - SingleNegationElimination
这个设计模式似乎不适用于Python3。有没有任何简单的修复方法,可以与Python3兼容? - computermacgyver
@computermacgyver:这个模式应该在Python的各个版本中都能正常工作。请提出一个新问题,以便您可以包含所有您的代码和您看到的错误。 - SingleNegationElimination
感谢 @dequestarmappartialsetattr。我发现只有当我尝试将a.py、b.py、c.py和model.py放入单独的模块时才会出现错误。在这种情况下,我找到了解决方案,即将base.py代码包含在模块的__init__.py文件中。我已经在这里放置了代码和更多解释。感谢您的回复。 - computermacgyver
2
来自遥远的未来,汽车由激光制成,而这个答案仍然是相关/有用的。非常感谢您提供的优秀代码示例——这正是我想要做的,但我在将其转化为SEO友好的搜索术语方面遇到了困难。 - Mass Dot Net

24

如果我也可以发表一下我的意见,因为我曾经遇到过同样的问题。你需要在创建Base = declarative_base()Tables之后,在创建文件中导入类。下面是我的项目设置的简短示例:

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

模型/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'))

模型/__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

2
只有在Python中才需要这样做,但我认为这是一个非常优雅的方法来解决一些问题。唯一的区别是我需要执行from . import Base而不是from model import Base - kevlarr

8
我正在使用Python 2.7 + Flask 0.10 + SQLAlchemy 1.0.8 + Postgres 9.4.4.1。
这个样板文件已经配置了User和UserDetail模型,存储在“user”模块的同一文件“models.py”中。这些类都继承自SQLAlchemy基类。
我添加到项目中的所有其他类也都派生自此基类,随着models.py文件变得越来越大,我决定将其拆分为每个类一个文件,并遇到了这里描述的问题。
我找到的解决方案与@computermacgyver的2013年10月23日的帖子类似,即将我所有的类包含到新模块的__init__.py文件中,以容纳所有新创建的类文件。看起来像这样:
/project/models/

__init__.py contains

from project.models.a import A 
from project.models.b import B
etc...

2
你为什么认为需要使用Flask? - nights

0
对我来说,在app.py中添加import app.tool.tool_entity,并在tool/__init__.py中添加from app.tool.tool_entity import Tool就足以创建表格。虽然我还没有尝试添加关系。

文件夹结构:

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)

0
在我的情况下,使用SQLAlchemy在FASTAPI中的Python3,我有一堆模型,所以将所有模型都放在models.py中是一场噩梦。我也喜欢将我的模式与模型保持紧密,所以我创建了“模型包”。上述答案是正确的,问题在于(db)类的导入顺序。因为我的目录结构是每个“领域/实体”都是一个包,所以我只是以编程方式导入包。

示例目录结构

| - domains 
| - __init__.py
| - database.py # <- Note here my DeclarativeBase is define here and 
     |          #  each modelpkg*/model.py imports it 
     |
     |- modelpkgA
        | - __init__.py
        | - model.py
        | - schema.py
     |
     | - modelPkgB
         | - __init__.py
         | - model.py
         | - [...] 

我刚刚在domains/__init__.py中添加了以下几行代码,以导入15个以上的“model”包。
import os 

import importlib

for pkg in os.listdir("concept"):
    if not pkg.endswith(".py") and not pkg.endswith("__"):
        importlib.import_module(f".{pkg}.model", "concept")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接