sqlalchemy 8 会话 与 异常

先创建一个c8数据库。c8.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///c8.db')    #引擎
Session = sessionmaker(bind=engine)    #会话
session = Session()

from sqlalchemy import Table, Column, Integer, Numeric, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
from sqlalchemy import DateTime
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, backref
Base = declarative_base()

class Cookie(Base):
    __tablename__ = 'cookies'
    cookie_id = Column(Integer(), primary_key=True)
    cookie_name = Column(String(30), index=True)
    cookie_recipe_url = Column(String(255))
    cookie_sku = Column(String(55))
    quantity = Column(Integer())
    unit_cost = Column(Numeric(12,2))
    def __init__(self, name, recipe_url=None, sku=None, quantity=0, unit_cost=0.00):
        self.cookie_name = name
        self.cookie_recipe_url = recipe_url
        self.cookie_sku = sku
        self.quantity = quantity
        self.unit_cost = unit_cost

    def __repr__(self):
        return "Cookie(cookie_name='{self.cookie_name}',"\
                "cookie_recipe_url='{self.cookie_recipe_url}',"\
                "cookie_sku='{self.cookie_sku}',"\
                "quantity={self.quantity},"\
                "unit_cost={self.unit_cost})".format(self=self)

class User(Base):
    __tablename__ = 'users'
    user_id = Column(Integer(), primary_key=True)
    username = Column(String(15), nullable=False, unique=True)
    email_address = Column(String(255), nullable=False)
    phone = Column(String(20), nullable=False)
    password = Column(String(25), nullable=False)
    created_on = Column(DateTime(), default=datetime.now)
    updated_on = Column(DateTime(), default=datetime.now, onupdate=datetime.now)

    def __init__(self, username, email_address, phone, password):
        self.username = username
        self.email_address = email_address
        self.phone = phone
        self.password = password

    def __repr__(self):
        return "User(username='{self.username}', "\
                "email_address='{self.email_address}', "\
                "phone='{self.phone}', "\
                "password='{self.password}')".format(self=self)

class Order(Base):
    __tablename__ = 'orders'
    order_id = Column(Integer(), primary_key=True)
    user_id = Column(Integer(), ForeignKey('users.user_id'))
    shipped = Column(Boolean(), default=False)
    user = relationship('User', backref=backref('orders', order_by=order_id))

    def __repr__(self):
        return "Order(user_id={self.user_id}, " \
                "shipped={self.shipped})".format(self=self)

class LineItems(Base):
    __tablename__ = 'line_items'
    line_items_id = Column(Integer(), primary_key=True)
    order_id = Column(Integer(), ForeignKey('orders.order_id'))
    cookie_id = Column(Integer(), ForeignKey('cookies.cookie_id'))
    quantity = Column(Integer())
    extended_cost = Column(Numeric(12,2))
    order = relationship('Order', backref=backref('line_items', order_by=line_items_id))
    cookie = relationship('Cookie', uselist=False)

    def __repr__(self):
        return "LineItems(order_id={self.order_id}, " \
                "cookie_id={self.cookie_id}, " \
                "quantity={self.quantity}, " \
                "extended_cost={self.extended_cost})".format(self=self)

Base.metadata.create_all(engine)    #表持久化

在查询一个对象时,对象会在 会话的几个状态间切换。

transient 瞬时状态:实例 不在会话中,也不在 数据库中。(不在会话 也不在数据库)

pending 挂起状态:实例 有add加入到 会话中,但未刷新或提交(在会话中 不在数据库中)

persistent 持久化状态:会话中的对象在数据库中有对应的记录。(在会话中 也在数据库中)flush和commit以后,都是这个状态

detached 脱管状态:实例不再与会话相连,但在数据库中有记录(不在会话中 在数据库中)

 查看实例的状态用inspect()。 c8_insert.py

from c8 import Cookie, session
c = Cookie('chocolate chip','http://some.aweso.me/cookie/recipe.html','cc01',12,0.5)    #新建一个实例
from sqlalchemy import inspect
i = inspect(c)
for state in ['transient','pending','persistent','detached']:
    print('{:>10}:{}'.format(state, getattr(i, state)))    #用getattr遍历
 transient:True
   pending:False
persistent:False
  detached:False

add

#...
session.add(c)
from sqlalchemy import inspect
i = inspect(c)
for state in ['transient','pending','persistent','detached']:
    print('{:>10}:{}'.format(state, getattr(i, state)))
 transient:False
   pending:True
persistent:False
  detached:False

commit 和 flush

#...
session.add(c)
session.commit()
from sqlalchemy import inspect
i = inspect(c)
for state in ['transient','pending','persistent','detached']:
    print('{:>10}:{}'.format(state, getattr(i, state)))
#...
session.add(c)
session.flush()
from sqlalchemy import inspect
i = inspect(c)
for state in ['transient','pending','persistent','detached']:
    print('{:>10}:{}'.format(state, getattr(i, state)))
 transient:False
   pending:False
persistent:True
  detached:False

expunge()。让实例进入 脱管状态。常用于 把数据从一个会话移到另一个会话时,比如把主要数据库中的数据 归档或合并 到数据仓库中。

#。。。
session.add(c)
session.commit()
session.expunge(c)
from sqlalchemy import inspect
i = inspect(c)
for state in ['transient','pending','persistent','detached']:
    print('{:>10}:{}'.format(state, getattr(i, state)))
 transient:False
   pending:False
persistent:False
  detached:True

除了用getattr()遍历,也可以用i.transient,i.pending等直接读出值。

from c8 import Cookie, session
c = Cookie('chocolate chip','http://some.aweso.me/cookie/recipe.html','cc01',12,0.5)
from sqlalchemy import inspect
i = inspect(c)    #从这步开始 inspect实例的状态
session.add(c)    
session.commit()
session.expunge(c)
session.add(c)    #注意书上讲的“加回”到会话中,是接着上面的代码的。已经expunge了要重新add进会话
c.cookie_name = 'change chocolate chip'    #modify
#i = inspect(c)    #从这里开始inspect也行
print(i.modified)    #查看的是commit以后 的modified。如果光是add在会话里,没有commit过,那整个Cookie实例c都是modified的。

for attr, attr_state in i.attrs.items():    #attr是个集合,有key和value,和正常集合一样用
    if attr_state.history.has_changes():
        print('{}: {}'.format(attr, attr_state.value))    #attr_state是个sqlalchemy.orm.state.AttributeState object
        print('history: {}\n'.format(attr_state.history))

输出True,inspect.modified可以看出 在上一次的提交之后  实例有没有发生变化。inspect.attr集合可以查看具体的变化。


回顾一下前面学的两个异常:

AttributeError:当试图访问一个不存在的属性时,就会出现AttributeError。比如 要访问的列在ResultProxy中不存在。、

IntegrityError:当试图做一些 违反 列约束 或 表约束的事情时,就会出现IntegrityError。比如破坏唯一性约束。

 MultipleResultFound:使用one()查询时,如果返回多个结果,就会报MultipleResultFound。

r = session.query(Cookie).one()    #之前已经commit过很多遍了,库里有很多cookie了,直接one就能触发
#报错
sqlalchemy.orm.exc.MultipleResultsFound: Multiple rows were found for one()

NoResultFound:使用one()查询时,没有返回任何结果

异常处理 try/except

from sqlalchemy.orm.exc import MultipleResultsFound
try:
    r = session.query(Cookie).one()
except MultipleResultsFound as error:
    print('we found too many cookies.')

DetachedInstanceError:尝试访问实例的某些属性时,如果这个属性需要从数据库加载,但实例目前有没有连接到数据库,就会报错。范例,先创建一个用户cookiemon及其对应的order和lineitems

cookiemon = User('cookiemon','mon@cookie.com','111-111-1111','password')
session.add(cookiemon)    #add User
o1 = Order()
o1.user = cookiemon
session.add(o1)    #add Order

cc = session.query(Cookie).filter(Cookie.cookie_name == 'change chocolate chip').one()
line1 = LineItems(order=o1, cookie=cc, quantity=2, extended_cost=1.00)

session.add(line1)    #add LineItems
session.commit()    #commit
order = session.query(Order).first()
session.expunge(order)
#session.add(order)    #若add回去,就正常。
print(order.line_items)    
#line_items是order的一个relationship属性,relationship在默认情况下,除非特别请求,否则是不会在query时就加载出来的。
#像order的其他Column属性,order.order_id,order.user_id, order.user在query时就已经加载了。即便order被expunge了,也能print出来。
#line_items这个relationship缺少会话 来执行查询 重新加载,触发异常。

 异常处理

from sqlalchemy.orm.exc import DetachedInstanceError
order = session.query(Order).first()
session.expunge(order)
try: 
    print(order.line_items)
except DetachedInstanceError as error:
    session.add(order)    #处理办法就是 手动add回session。
    print(order.line_items)

类似DetachedInstanceError的异常还有:ObjectDeleteError、StaleDataError、ConcurrentModificationError,都是因为与实例会话数据库 之间的信息不一致 有关。


事务transaction,是一组语句。session在刚创建时,并没有连接到数据库。直到session执行第一个操作如query时,才会启动 一个连接 和 一个事务。(即默认情况下,我们不用手动创建 事务transaction)但当 事务的一部分执行成功 一部分执行失败时,触发了异常,我们要知道如何手动控制事务

新事务环境,其实就是加了行CheckConstraint。 c8trans.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///c8trans.db')
Session = sessionmaker(bind=engine)
session = Session()
from sqlalchemy import Table, Column, Integer, Numeric, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime
from sqlalchemy import DateTime
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy import CheckConstraint
Base = declarative_base()

class Cookie(Base):
    __tablename__ = 'cookies'
    __table_args__ = (CheckConstraint('quantity >= 0', name='quantity_positive'),)
    cookie_id = Column(Integer(), primary_key=True)
    cookie_name = Column(String(30), index=True)
    cookie_recipe_url = Column(String(255))
    cookie_sku = Column(String(55))
    quantity = Column(Integer())
    unit_cost = Column(Numeric(12,2))
    def __init__(self, name, recipe_url=None, sku=None, quantity=0, unit_cost=0.00):
        self.cookie_name = name
        self.cookie_recipe_url = recipe_url
        self.cookie_sku = sku
        self.quantity = quantity
        self.unit_cost = unit_cost

    def __repr__(self):
        return "Cookie(cookie_name='{self.cookie_name}',"\
                "cookie_recipe_url='{self.cookie_recipe_url}',"\
                "cookie_sku='{self.cookie_sku}',"\
                "quantity={self.quantity},"\
                "unit_cost={self.unit_cost})".format(self=self)

class User(Base):
    __tablename__ = 'users'
    user_id = Column(Integer(), primary_key=True)
    username = Column(String(15), nullable=False, unique=True)
    email_address = Column(String(255), nullable=False)
    phone = Column(String(20), nullable=False)
    password = Column(String(25), nullable=False)
    created_on = Column(DateTime(), default=datetime.now)
    updated_on = Column(DateTime(), default=datetime.now, onupdate=datetime.now)

    def __init__(self, username, email_address, phone, password):
        self.username = username
        self.email_address = email_address
        self.phone = phone
        self.password = password

    def __repr__(self):
        return "User(username='{self.username}', "\
                "email_address='{self.email_address}', "\
                "phone='{self.phone}', "\
                "password='{self.password}')".format(self=self)

class Order(Base):
    __tablename__ = 'orders'
    order_id = Column(Integer(), primary_key=True)
    user_id = Column(Integer(), ForeignKey('users.user_id'))
    shipped = Column(Boolean(), default=False)
    user = relationship('User', backref=backref('orders', order_by=order_id))

    def __repr__(self):
        return "Order(user_id={self.user_id}, " \
                "shipped={self.shipped})".format(self=self)

class LineItems(Base):
    __tablename__ = 'line_items'
    line_items_id = Column(Integer(), primary_key=True)
    order_id = Column(Integer(), ForeignKey('orders.order_id'))
    cookie_id = Column(Integer(), ForeignKey('cookies.cookie_id'))
    quantity = Column(Integer())
    extended_cost = Column(Numeric(12,2))
    order = relationship('Order', backref=backref('line_items', order_by=line_items_id))
    cookie = relationship('Cookie', uselist=False)

    def __repr__(self):
        return "LineItems(order_id={self.order_id}, " \
                "cookie_id={self.cookie_id}, " \
                "quantity={self.quantity}, " \
                "extended_cost={self.extended_cost})".format(self=self)

Base.metadata.create_all(engine)

插入新数据。c8trans_insert.py

from c8trans import session, User, Cookie, Order, LineItems
#两种cookie
cookiemon = User('cookiemon','mon@cookie.com','111-111-1111','password')
cc = Cookie('chocolate chip', 'http://some.aweso.me/cookie/recipe.html', 'cc01', 12, 0.50)
dcc = Cookie('dark chocolate chip', 'http://some.aweso.me/cookie/recipe_dark.html', 'cc02', 1, 0.75)
session.add(cc)
session.add(dcc)
session.commit()
#order1
o1 = Order()
o1.user = cookiemon
session.add(o1)
line1 = LineItems(order=o1, cookie=cc, quantity=9, extended_cost=1.50)
session.add(line1)
#order2
o2 = Order()
o2.user = cookiemon
session.add(o2)
line1 = LineItems(order=o2, cookie=cc, quantity=2, extended_cost=1.50)
line2 = LineItems(order=o2, cookie=dcc, quantity=9, extended_cost=6.75)
session.add(line1)
session.add(line2)

session.commit()

ship_it函数

def ship_it(order_id):
    order = session.query(Order).get(order_id)
    for li in order.line_items:
        li.cookie.quantity = li.cookie.quantity - li.quantity
        session.add(li.cookie)    #只有发生变化的对象才要add
    order.shipped = True
    session.add(order)    #只有发生变化的对象才要add
    session.commit()
    print('shipped order id:{}'.format(order_id))

ship_it(1)    #正常
ship_it(2)    #报错,IntegrityError,quantity小于0违反约束了

异常会中断当前的会话,如果尝试通过 这个会话发送更多语句(如查询)

try:
    ship_it(2)
except:
    print(session.query(Cookie.cookie_name, Cookie.quantity).all())
#报错
sqlalchemy.exc.InvalidRequestError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: (sqlite3.IntegrityError) CHECK constraint failed: quantity_positive
[SQL: UPDATE cookies SET quantity=? WHERE cookies.cookie_id = ?]
[parameters: (-8, 4)]

继续使用中断的会话 发送语句会触发InvalidRequestError。(当上一个事务没有正常结束时,会话就会卡住,影响后面的事务。)

处理异常:This Session's transaction has been rolled back due to a previous exception during flush.这句话表达不清楚,它的本意是需要我们手动回滚,而不是已经回滚了。To begin a new transaction with this Session, first issue Session.rollback()

注意回滚rollback的是事务transaction,而不是回滚会话。用rollback把这个问题事务清除,会话恢复正常。

try/except

from sqlalchemy.exc import IntegrityError
def ship_it(order_id):
    order = session.query(Order).get(order_id)
    for li in order.line_items:
        li.cookie.quantity = li.cookie.quantity - li.quantity
        session.add(li.cookie)
    order.shipped = True
    session.add(order)
    try:
        session.commit()
        print('shipped order id:{}'.format(order_id))
    except IntegrityError as error:
        session.rollback()
        print('cookie is not enough')

ship_it(2)
print(session.query(Cookie.cookie_name, Cookie.quantity).all())

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值