如下所示,我正在尝试从具有额外键的python字典初始化sqlalchemy Mapped类。是否可以让Mapped类自动忽略额外的键而不是抛出错误?同样,如果键不存在,Mapped类可以有默认值吗?
from sqlalchemy import Column, Integer, String
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
这是init部分:
my_example_user = {'id'=1, 'name'='john', 'extra_key'= 1234}
User(**my_example_user)
会抛出无效的密钥错误
思想?
答案 0 :(得分:4)
SQLAlchemy Mapper
对象具有attrs
属性,该属性是映射类的字段名称的字典。
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import class_mapper
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String)
user = {
'name': 'Eihli',
'skill': 11
}
user_mapper = class_mapper(User)
mapped_user = User(**user)
# Boom! TypeError: 'skill' is an invalid keyword argument for User
mapped_user = User(**{
k: v for k, v in user.items()
if k in user_mapper.attrs.keys()
})
# Success!
无需弄乱维护排除列表,也无需弄乱 dict 或妨碍超级调用。
如果要使用嵌套数据生成模型,则必须做些不同的事情。否则,您将收到“ Unhashable type'dict'”错误。
这是帮助者检查映射器并获取关系键的示例。
def from_json(model, data):
mapper = class_mapper(model)
keys = mapper.attrs.keys()
relationships = inspect(mapper).relationships
args = {k: v for k, v in data.items()
if k in keys and k not in relationships}
return model(**args)
答案 1 :(得分:2)
简而言之,定义不将参数传递给其超类的构造函数:
class User(Base):
# ...
def __init__(self, **entries):
# NOTE: Do not call superclass
# (which is otherwise a default behaviour).
#super(User, self).__init__(**entries)
self.__dict__.update(entries)
我从peewee过渡到同样的问题,这需要相反的 - 将参数传递给它的超类(因此,构造函数已经定义)。所以,我只是试着评论这条线,事情开始起作用。
<强>更新强>
此外,请确保entries
不包含(并因此覆盖)为SQLAlchemy定义的User
类中的任何元字段,例如,那些ORM关系。这很明显(SQLAlchemy),但是当出现错误时,可能不容易发现问题。
答案 2 :(得分:1)
另外要传递额外的关键字并调用super()
方法,您可以从from sqlalchemy import Column, Integer, String
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
def __init__(self, **kwargs):
extra_kw_list = ['key1', 'key2']
super(User, self).__init__(**{x: y for x, y in kwargs.items()
if x not in extra_kw_list})
#do something you need here
item1, item2 = kwargs['key1'], kwargs['key2']
中排除额外的密钥,然后执行您想要的操作:
os << mValue << "" << iValue << "\n";
答案 3 :(得分:0)
我们是否保证现有超类的__init__
永远不会有比设置__dict__
条目所需的其他效果?完全绕过超类调用我感觉不太舒服,所以我尝试解决这个问题的方法如下,只传递与列名相对应的条目:
class User(Base):
# ...
def __init__(self, **entries):
'''Override to avoid TypeError when passed spurious column names'''
col_names = set([col.name for col in self.__table__.columns])
superentries = {k : entries[k] for k in col_names.intersection(entries.keys())}
super().__init__(**superentries)
答案 4 :(得分:0)
根据R Yakovlev的回答,您可以将元素列表动态化:
from sqlalchemy import Column, Integer, String
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
def __init__(self, **kwargs):
keep_kwargs = {k: v for k, v in kwargs.items() if k in user_columns}
super(User, self).__init__(**keep_kwargs)
user_columns = [_ for _ in User.__dict__.keys() if not _.startswith('_')]
我想尝试找到一种方法将user_columns嵌入到对象中,就像使用@hybrid_property一样,但每次使用时都没有调用它。
我希望这是可能的,但超过了我的时间限制。
答案 5 :(得分:0)
如果您的模型有关系,您可以使用模型的 Mapper
对象,如 @eric-ihli mentioned。这是另一种方式(注意 __init__
方法):
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import backref, relationship
from my_app.db_models import Base
class Employee(Base):
__tablename__ = "employee"
id = Column(Integer, primary_key=True, autoincrement=True)
department_id = Column(Integer, ForeignKey("department.id"), index=True)
email = Column(String, unique=True, index=True, nullable=False)
name = Column(String)
department = relationship(
"Department", backref=backref("employees", cascade="all, delete-orphan")
)
def __init__(self, **kwargs):
allowed_args = self.__mapper__.class_manager # returns a dict
kwargs = {k: v for k, v in kwargs.items() if k in allowed_args}
super().__init__(**kwargs)
通过这种方式,您可以创建这样的员工模型:
from contextlib import closing
from my_app.db_models import Department, Employee, SessionLocal
with closing(SessionLocal()) as db:
dept = db.query(Department).filter(Department.name == 'HR').first()
employee = Employee(name='John Smith', email='john@smith.com', department=dept)
db.add(employee)
db.commit()