sqlalchemy会话用于创建或更新带有*到多个关系的记录

时间:2016-12-12 23:10:10

标签: python database postgresql flask sqlalchemy

我有一个相当简单的烧瓶应用程序连接到postgresql数据库。我主要使用带有flask-admin的烧瓶应用程序,以便我可以将记录添加到数据库中,并可能稍后将其构建到仪表板中。它基本上是一个内部使用目录。

我要做的是编写一个连接到第三方API的脚本来添加/更新数据库中的记录,因此它没有通过烧瓶应用程序。我正在使用SQLAlchemy来执行此操作,因为它与应用程序保持一致,我只需要一些工作就可以不用担心SQL语句。

烧瓶应用程序的数据模型定义如下:

app.py

from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.dialects import postgresql
from flask_admin import Admin

# ... APPLICATION CONFIGURATION ...

# db Models

## Table for many to many
keywords = db.Table('keywords',
    db.Column('keyword_id', db.Integer, db.ForeignKey('keyword.id')),
    db.Column('dataset_id', db.String(24), db.ForeignKey('dataset.dataset_id')),
)

## Model classes
class Dataset(db.Model):
    title = db.Column(db.String(120))
    description = db.Column(db.Text())
    dataset_id = db.Column(db.String(24), primary_key=True, unique=True)

    #relationships
    dataset_documentation = db.relationship('DataDocument', backref='dataset', lazy='dynamic')
    keywords = db.relationship('Keyword', secondary=keywords, backref='dataset', lazy='dynamic')

    def __str__(self):
        return self.title

class Keyword(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    keyword = db.Column(db.String(80))

    def __str__(self):
        return self.keyword

class DataDocument(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    document = db.Column(db.String(120))
    dataset_id = db.Column(db.String(24), db.ForeignKey('dataset.dataset_id'))

    def __str__(self):
        return self.document

# ... APPLICATION VIEWS ...

所以我们datasets有一些基本的元数据,它们与文档的文件路径有一对多的关系,与任意数量的关键字有多对多的关系。

单独的脚本直接连接到数据库,并将现有表映射到可用于创建会话和修改数据库的对象。

script.py

import config #local config only
from sqlalchemy import create_engine, MetaData, Table
from sqlalchemy.orm import mapper, sessionmaker


# Connecting to postgres database and creating a session with database objects, intantiate empty classes to populate
class Dataset(object):
    pass

class DataDocument(object):
    pass

class Keyword(object):
    pass

## How to instantiate the MTM association table?

db_uri = config.SQLALCHEMY_DATABASE_URI
engine = create_engine(db_uri)
meta = MetaData(engine)
dataset_table = Table('dataset', meta, autoload=True) #correct
datadocument_table = Table('dataset', meta, autoload=True) #incorrect?
keyword_table = Table('keyword', meta, autoload=True) #incorrect?
mapper(Dataset, dataset_table) #correct
mapper(DataDocument, datadocument_table, meta, autoload=True) #??
mapper(Keyword, keyword_table, meta, autoload=True) #??

Session = sessionmaker(bind=engine)
session = Session()
# sample update

data_upsert = Dataset()
data_upsert.title = "Some title"
data_upsert.dataset_id = "Uniq_ID-123"
data_upsert.description = "lorem ipsum foo bar foo"

session.merge(data_upsert)

#attempt to add related properties

key1 = Keyword('test1')
key2 = Keyword('test2')
datadoc = DataDocument('path/to/document.txt')

# FAIL.

data_upsert.append(key1)
data_upsert.append(key2)
data_upsert.append(datadoc)

session.flush()

我是sqlalchemy的新手,我几乎无法绕过从数据库引擎创建脚本中的Dataset对象。但我正在考虑加载KeywordDatadocument表,以及它已经基于从数据库加载的内容来理解关系,但这是我的理解正在变薄的地方。

有没有直接的方法来完成这里的图片?我假设在script.py中明确地再次定义我的模型是没有意义的,但在审阅文档和一些教程时,我没有看到将这些关系加载到会话中的缺失部分,以便我可以摄取所有数据进入数据库。

1 个答案:

答案 0 :(得分:0)

更新模型定义以添加构造函数。在这种情况下,它允许您在实例化时将参数传递给对象。

models.py

## Model classes
class Dataset(db.Model):
    title = db.Column(db.String(120))
    description = db.Column(db.Text())
    dataset_id = db.Column(db.String(24), primary_key=True, unique=True)

    #relationships
    dataset_documentation = db.relationship('DataDocument', backref='dataset', lazy='dynamic')
    keywords = db.relationship('Keyword', secondary=keywords, backref='dataset', lazy='dynamic')

    def __init__(self, title=None, desc=None, dataset_id=None):
        self.title = title
        self.description = desc
        self.dataset_id = dataset_id

    def __str__(self):
        return self.title


class Keyword(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    keyword = db.Column(db.String(80))

    def __init__(self, keyword=None):
        self.keyword = keyword

    def __str__(self):
        return self.keyword


class DataDocument(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    document = db.Column(db.String(120))
    dataset_id = db.Column(db.String(24), db.ForeignKey('dataset.dataset_id'))

    def __init__(self, document, dataset_id):
        self.document = document
        self.dataset_id = dataset_id

    def __str__(self):
        return self.document

无需在script.py中再次定义模型类。您只需从models.py中导入要使用的类。然后,您可以通过以下方式将数据对象及其相关对象一起插入到数据库中:

script.py

import config  #local config only
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from models import Dataset, DataDocument, Keyword


def loadSession(engine):
    """"""
    Session = sessionmaker(bind=engine)
    session = Session()
    return session


engine = create_engine(config.SQLALCHEMY_DATABASE_URI, echo=False)
Base = declarative_base(engine)
# load session
session = loadSession(engine)
data_upsert = Dataset(title="Some title", dataset_id="Uniq_ID-125", desc="lorem ipsum foo bar foo")

# add related properties here
key1 = Keyword('test1')
key2 = Keyword('test2')
datadoc = DataDocument('path/to/document.txt', dataset_id="Uniq_ID-125")

# append the properties to the object 
data_upsert.dataset_documentation.append(datadoc)
data_upsert.keywords.append(key1)
data_upsert.keywords.append(key2)

session.add(data_upsert)

session.commit()

我已经在本地测试了代码并希望它适合您。