Классы SQLAlchemy в файлах

82

Я пытаюсь понять, как распределить классы 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
@ S.Lott Вышеупомянутое работает, если все классы находятся в одном файле, так что скажите мне :)
joveha
Ваш код не выдает этой ошибки, пожалуйста, опубликуйте код, который имеет фактическую ошибку. Исправьте свой импорт, запустите его, чтобы кто-то действительно мог увидеть вашу ошибку.
knitti
@ S.Lott Моя путаница, очевидно, была связана с тем, как избежать циклического импорта. Я пришел из C, где это не проблема. Приношу свои извинения за то, что отнял время.
joveha
@joveha: Что? Какие у вас проблемы с циклическим импортом? Отправьте код с циклическим импортом, чтобы мы могли объяснить, как их разложить и избежать циклов. В этих комментариях слишком много расплывчатых гипотез. Какая у вас проблема? Пожалуйста, будьте конкретны.
S.Lott

Ответы:

88

Самым простым решением вашей проблемы будет вынуть Baseмодуль, который импортирует A, Bи C; Прервите циклический импорт.

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

Работает на моей машине:

$ python main.py ; echo $?
0
SingleNegationElimination
источник
1
Используйте scoped_session.
пользователь
3
@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
13

Если можно, добавлю немного разума, раз уж у меня была та же проблема. Вам необходимо импортировать классы в файл, в котором вы создаете Base = declarative_base()ПОСЛЕ создания Baseи Tables. Краткий пример настройки моего проекта:

модель / 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
Питер
источник
8

Я использую Python 2.7 + Flask 0.10 + SQLAlchemy 1.0.8 + Postgres 9.4.4.1

Этот шаблон поставляется с настроенными моделями User и UserDetail, хранящимися в одном файле «models.py» в модуле «user». Оба эти класса наследуются от базового класса SQLAlchemy.

Все дополнительные классы, которые я добавил в свой проект, также являются производными от этого базового класса, и по мере увеличения размера файла models.py я решил разделить файл models.py на один файл для каждого класса и столкнулся с описанной проблемой. Вот.

Решение, которое я нашел, в том же духе, что и сообщение @ computermacgyver от 23 октября 2013 г., заключалось в том, чтобы включить все мои классы в файл init .py нового модуля, который я создал для хранения всех вновь созданных файлов классов. Выглядит так:

/project/models/

__init__.py contains

from project.models.a import A 
from project.models.b import B
etc...
RadX3
источник
2
как вы думаете, почему вам нужно использовать Flask?
ночи
0

Для меня добавления import app.tool.tool_entityвнутри app.pyи from app.tool.tool_entity import Toolвнутри tool/__init__.pyбыло достаточно, чтобы создать таблицу. Однако я еще не пробовал добавлять отношения.

Структура папки:

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)
Амбруаз Рабье
источник