动态数据集和 SQLAlchemy
Dynamic Datasets and SQLAlchemy
我正在将 Python 中的一些旧 SQLite3 SQL 语句重构到 SQLAlchemy 中。在我们的框架中,我们有以下 SQL 语句,这些语句接受带有某些已知键和可能任意数量的意外键和值的字典(取决于提供的信息)。
import sqlite3
import sys
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def Create_DB(db):
# Delete the database
from os import remove
remove(db)
# Recreate it and format it as needed
with sqlite3.connect(db) as conn:
conn.row_factory = dict_factory
conn.text_factory = str
cursor = conn.cursor()
cursor.execute("CREATE TABLE [Listings] ([ID] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, [timestamp] REAL NOT NULL DEFAULT(( datetime ( 'now' , 'localtime' ) )), [make] VARCHAR, [model] VARCHAR, [year] INTEGER);")
def Add_Record(db, data):
with sqlite3.connect(db) as conn:
conn.row_factory = dict_factory
conn.text_factory = str
cursor = conn.cursor()
#get column names already in table
cursor.execute("SELECT * FROM 'Listings'")
col_names = list(map(lambda x: x[0], cursor.description))
#check if column doesn't exist in table, then add it
for i in data.keys():
if i not in col_names:
cursor.execute("ALTER TABLE 'Listings' ADD COLUMN '{col}' {type}".format(col=i, type='INT' if type(data[i]) is int else 'VARCHAR'))
#Insert record into table
cursor.execute("INSERT INTO Listings({cols}) VALUES({vals});".format(cols = str(data.keys()).strip('[]'),
vals=str([data[i] for i in data]).strip('[]')
))
#Database filename
db = 'test.db'
Create_DB(db)
data = {'make': 'Chevy',
'model' : 'Corvette',
'year' : 1964,
'price' : 50000,
'color' : 'blue',
'doors' : 2}
Add_Record(db, data)
data = {'make': 'Chevy',
'model' : 'Camaro',
'year' : 1967,
'price' : 62500,
'condition' : 'excellent'}
Add_Record(db, data)
这种程度的动态是必要的,因为我们无法知道将提供哪些额外信息,但无论如何,我们存储提供给我们的所有信息是很重要的。这从来都不是问题,因为在我们的框架中,我们从未预料到我们的表中会有大量的列。
虽然上面的代码有效,但很明显它不是一个干净的实现,因此我试图将它重构为 SQLAlchemy 更干净、更健壮的 ORM 范例。我开始浏览 SQLAlchemy 的官方教程和各种示例,并得出以下代码:
from sqlalchemy import Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Listing(Base):
__tablename__ = 'Listings'
id = Column(Integer, primary_key=True)
make = Column(String)
model = Column(String)
year = Column(Integer)
engine = create_engine('sqlite:///')
session = sessionmaker()
session.configure(bind=engine)
Base.metadata.create_all(engine)
data = {'make':'Chevy',
'model' : 'Corvette',
'year' : 1964}
record = Listing(**data)
s = session()
s.add(record)
s.commit()
s.close()
它与该数据字典配合得很好。现在,当我添加一个新关键字时,例如
data = {'make':'Chevy',
'model' : 'Corvette',
'year' : 1964,
'price' : 50000}
我收到 TypeError: 'price' is an invalid keyword argument for Listing
错误。为了尝试解决这个问题,我也将 class 修改为动态的:
class Listing(Base):
__tablename__ = 'Listings'
id = Column(Integer, primary_key=True)
make = Column(String)
model = Column(String)
year = Column(Integer)
def __checker__(self, data):
for i in data.keys():
if i not in [a for a in dir(self) if not a.startswith('__')]:
if type(i) is int:
setattr(self, i, Column(Integer))
else:
setattr(self, i, Column(String))
else:
self[i] = data[i]
但我很快意识到这根本行不通,原因有几个,例如class 已经初始化,如果不重新初始化数据字典就无法将其输入 class,这比什么都难,等等)。我想得越多,在我看来使用 SQLAlchemy 的解决方案就越不明显。所以,我的主要问题是,如何使用 SQLAlchemy 实现这种级别的动态?
我研究了一下,看看是否有人有类似的问题。我发现最接近的是 Dynamic Class Creation in SQLAlchemy but it only talks about the constant attributes ("tablename" et al.). I believe the unanswered 可能会问同样的问题。虽然 Python 不是我的强项,但我认为自己在上下文 scientific/engineering 应用程序方面是一个非常熟练的程序员(C++ 和 JavaScript 是我最擅长的语言),所以我可能不会打正确的 Python-我搜索中的特定关键字。
我欢迎任何帮助。
class Listing(Base):
__tablename__ = 'Listings'
id = Column(Integer, primary_key=True)
make = Column(String)
model = Column(String)
year = Column(Integer)
def __init__(self,**kwargs):
for k,v in kwargs.items():
if hasattr(self,k):
setattr(self,k,v)
else:
engine.execute("ALTER TABLE %s AD COLUMN %s"%(self.__tablename__,k)
setattr(self.__class__,Column(k, String))
setattr(self,k,v)
可能有用……也许……我不完全确定我没有测试它
更好的解决方案是使用关系 table
class Attribs(Base):
listing_id = Column(Integer,ForeignKey("Listing"))
name = Column(String)
val = Column(String)
class Listing(Base):
id = Column(Integer,primary_key = True)
attributes = relationship("Attribs",backref="listing")
def __init__(self,**kwargs):
for k,v in kwargs.items():
Attribs(listing_id=self.id,name=k,value=v)
def __str__(self):
return "\n".join(["A LISTING",] + ["%s:%s"%(a.name,a.val) for a in self.attribs])
另一个解决方案是存储 json
class Listing(Base):
__tablename__ = 'Listings'
id = Column(Integer, primary_key=True)
data = Column(String)
def __init__(self,**kwargs):
self.data = json.dumps(kwargs)
self.data_dict = kwargs
最好的解决方案是使用无-sql 键值存储(甚至可能只是一个简单的 json 文件?或者搁置?甚至我猜 pickle)
我正在将 Python 中的一些旧 SQLite3 SQL 语句重构到 SQLAlchemy 中。在我们的框架中,我们有以下 SQL 语句,这些语句接受带有某些已知键和可能任意数量的意外键和值的字典(取决于提供的信息)。
import sqlite3
import sys
def dict_factory(cursor, row):
d = {}
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d
def Create_DB(db):
# Delete the database
from os import remove
remove(db)
# Recreate it and format it as needed
with sqlite3.connect(db) as conn:
conn.row_factory = dict_factory
conn.text_factory = str
cursor = conn.cursor()
cursor.execute("CREATE TABLE [Listings] ([ID] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, [timestamp] REAL NOT NULL DEFAULT(( datetime ( 'now' , 'localtime' ) )), [make] VARCHAR, [model] VARCHAR, [year] INTEGER);")
def Add_Record(db, data):
with sqlite3.connect(db) as conn:
conn.row_factory = dict_factory
conn.text_factory = str
cursor = conn.cursor()
#get column names already in table
cursor.execute("SELECT * FROM 'Listings'")
col_names = list(map(lambda x: x[0], cursor.description))
#check if column doesn't exist in table, then add it
for i in data.keys():
if i not in col_names:
cursor.execute("ALTER TABLE 'Listings' ADD COLUMN '{col}' {type}".format(col=i, type='INT' if type(data[i]) is int else 'VARCHAR'))
#Insert record into table
cursor.execute("INSERT INTO Listings({cols}) VALUES({vals});".format(cols = str(data.keys()).strip('[]'),
vals=str([data[i] for i in data]).strip('[]')
))
#Database filename
db = 'test.db'
Create_DB(db)
data = {'make': 'Chevy',
'model' : 'Corvette',
'year' : 1964,
'price' : 50000,
'color' : 'blue',
'doors' : 2}
Add_Record(db, data)
data = {'make': 'Chevy',
'model' : 'Camaro',
'year' : 1967,
'price' : 62500,
'condition' : 'excellent'}
Add_Record(db, data)
这种程度的动态是必要的,因为我们无法知道将提供哪些额外信息,但无论如何,我们存储提供给我们的所有信息是很重要的。这从来都不是问题,因为在我们的框架中,我们从未预料到我们的表中会有大量的列。
虽然上面的代码有效,但很明显它不是一个干净的实现,因此我试图将它重构为 SQLAlchemy 更干净、更健壮的 ORM 范例。我开始浏览 SQLAlchemy 的官方教程和各种示例,并得出以下代码:
from sqlalchemy import Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class Listing(Base):
__tablename__ = 'Listings'
id = Column(Integer, primary_key=True)
make = Column(String)
model = Column(String)
year = Column(Integer)
engine = create_engine('sqlite:///')
session = sessionmaker()
session.configure(bind=engine)
Base.metadata.create_all(engine)
data = {'make':'Chevy',
'model' : 'Corvette',
'year' : 1964}
record = Listing(**data)
s = session()
s.add(record)
s.commit()
s.close()
它与该数据字典配合得很好。现在,当我添加一个新关键字时,例如
data = {'make':'Chevy',
'model' : 'Corvette',
'year' : 1964,
'price' : 50000}
我收到 TypeError: 'price' is an invalid keyword argument for Listing
错误。为了尝试解决这个问题,我也将 class 修改为动态的:
class Listing(Base):
__tablename__ = 'Listings'
id = Column(Integer, primary_key=True)
make = Column(String)
model = Column(String)
year = Column(Integer)
def __checker__(self, data):
for i in data.keys():
if i not in [a for a in dir(self) if not a.startswith('__')]:
if type(i) is int:
setattr(self, i, Column(Integer))
else:
setattr(self, i, Column(String))
else:
self[i] = data[i]
但我很快意识到这根本行不通,原因有几个,例如class 已经初始化,如果不重新初始化数据字典就无法将其输入 class,这比什么都难,等等)。我想得越多,在我看来使用 SQLAlchemy 的解决方案就越不明显。所以,我的主要问题是,如何使用 SQLAlchemy 实现这种级别的动态?
我研究了一下,看看是否有人有类似的问题。我发现最接近的是 Dynamic Class Creation in SQLAlchemy but it only talks about the constant attributes ("tablename" et al.). I believe the unanswered 可能会问同样的问题。虽然 Python 不是我的强项,但我认为自己在上下文 scientific/engineering 应用程序方面是一个非常熟练的程序员(C++ 和 JavaScript 是我最擅长的语言),所以我可能不会打正确的 Python-我搜索中的特定关键字。
我欢迎任何帮助。
class Listing(Base):
__tablename__ = 'Listings'
id = Column(Integer, primary_key=True)
make = Column(String)
model = Column(String)
year = Column(Integer)
def __init__(self,**kwargs):
for k,v in kwargs.items():
if hasattr(self,k):
setattr(self,k,v)
else:
engine.execute("ALTER TABLE %s AD COLUMN %s"%(self.__tablename__,k)
setattr(self.__class__,Column(k, String))
setattr(self,k,v)
可能有用……也许……我不完全确定我没有测试它
更好的解决方案是使用关系 table
class Attribs(Base):
listing_id = Column(Integer,ForeignKey("Listing"))
name = Column(String)
val = Column(String)
class Listing(Base):
id = Column(Integer,primary_key = True)
attributes = relationship("Attribs",backref="listing")
def __init__(self,**kwargs):
for k,v in kwargs.items():
Attribs(listing_id=self.id,name=k,value=v)
def __str__(self):
return "\n".join(["A LISTING",] + ["%s:%s"%(a.name,a.val) for a in self.attribs])
另一个解决方案是存储 json
class Listing(Base):
__tablename__ = 'Listings'
id = Column(Integer, primary_key=True)
data = Column(String)
def __init__(self,**kwargs):
self.data = json.dumps(kwargs)
self.data_dict = kwargs
最好的解决方案是使用无-sql 键值存储(甚至可能只是一个简单的 json 文件?或者搁置?甚至我猜 pickle)