浅谈Python peewee 使用经验

本文使用案例是基于 python2.7 实现

以下内容均为个人使用 peewee 的经验和遇到的坑,不会涉及过多的基本操作。所以,没有使用过 peewee,可以先阅读文档

正确性和覆盖面有待提高,如果遇到新的问题欢迎讨论。

一、介绍

Peewee是一个简单、轻巧的 Python ORM。

  1. 简单、轻巧、富有表现力(原词 expressive )的ORM
  2. 支持python版本 2.6+ 和 3.2+
  3. 支持数据库包括:sqlite, mysql and postgresql
  4. 包含一堆实用的扩展在 playhouse 模块中

总而言之,peewee 可以完全可以应付个人或企业的中小型项目的 Model 层,上手容易,功能很强大。

二、基本使用方法

from peewee import *

db = SqliteDatabase('people.db')
class BaseModel(Model):
  class Meta:
    database = db # This model uses the "people.db" database.

class Person(BaseModel):
  name = CharField()
  birthday = DateField()
  is_relative = BooleanField()

基本的使用方法,推荐阅读文档--quickstart

三、推荐使用姿势

下面介绍一些我在使用过程的经验和遇到的坑,希望可以帮助大家更好的使用 peewee。

3.1 连接数据库

连接数据库时,推荐使用 playhouse 中的 db_url 模块。db_url 的 connect 方法可以通过传入的 URL 字符串,生成数据库连接。

3.1.1 connect(url, **connect_params)

通过传入的 url 字符串,创建一个数据库实例

url形如:

  1. mysql://user:passwd@ip:port/my_db 将创建一个 本地 MySQL 的 my_db 数据库的实例(will create a MySQLDatabase instance)
  2. mysql+pool://user:passwd@ip:port/my_db?charset=utf8&max_connections=20&stale_timeout=300 将创建一个本地 MySQL 的 my_db 的连接池,最大连接数为20(In a multi-threaded application, up to max_connections will be opened. Each thread (or, if using gevent, greenlet) will have it's own connection.),超时时间为300秒(will create a PooledMySQLDatabase instance)

注意:charset 默认为utf8。如需要支持 emoji ,charset 设置为utf8mb4,同时保证创建数据库时的字符集设置正确CREATE DATABASE mydatabase CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。

支持的 schemes:

  1. apsw: APSWDatabase
  2. mysql: MySQLDatabase
  3. mysql+pool: PooledMySQLDatabase
  4. postgres: PostgresqlDatabase
  5. postgres+pool: PooledPostgresqlDatabase
  6. postgresext: PostgresqlExtDatabase
  7. postgresext+pool: PooledPostgresqlExtDatabase
  8. sqlite: SqliteDatabase
  9. sqliteext: SqliteExtDatabase
  10. sqlite+pool: PooledSqliteDatabase
  11. sqliteext+pool: PooledSqliteExtDatabase

3.1.2 推荐姿势

from playhouse.db_url import connect

from dock.common import config

# url: mysql+pool://root:root@127.0.0.1:3306/appmanage?max_connections=300&stale_timeout=300
mysql_config_url = config_dict.get('config').get('mysql').get('url')
db = connect(url=mysql_config_url)

查看更多详情请移步官方文档:db-url

3.2 连接池的使用

peewee 的连接池,使用时需要显式的关闭连接。下面先说下为什么,最后会给出推荐的使用方法,避免进坑。

3.2.1 为什么要显式的关闭连接

Connections will not be closed exactly when they exceed their stale_timeout. Instead, stale connections are only closed when a new connection is requested.

这里引用官方文档的提示。大致说:“超时连接不会自动关闭,只会在有新的请求时是才会关闭”。这里的request是指‘web 框架处理的请求',peewee 源码片段:

def _connect(self, *args, **kwargs):
  while True:
    try:
      # Remove the oldest connection from the heap.
      ts, conn = heapq.heappop(self._connections) # _connections是连接实例的list(pool)
      key = self.conn_key(conn)
    except IndexError:
      ts = conn = None
      logger.debug('No connection available in pool.')
      break
    else:
      if self._is_closed(key, conn):
        # This connecton was closed, but since it was not stale
        # it got added back to the queue of available conns. We
        # then closed it and marked it as explicitly closed, so
        # it's safe to throw it away now.
        # (Because Database.close() calls Database._close()).
        logger.debug('Connection %s was closed.', key)
        ts = conn = None
        self._closed.discard(key)
      elif self.stale_timeout and self._is_stale(ts):
        # If we are attempting to check out a stale connection,
        # then close it. We don't need to mark it in the "closed"
        # set, because it is not in the list of available conns
        # anymore.
        logger.debug('Connection %s was stale, closing.', key)
        self._close(conn, True)
        self._closed.discard(key)
        ts = conn = None
      else:
        break
  if conn is None:
    if self.max_connections and (
        len(self._in_use) >= self.max_connections):
      raise ValueError('Exceeded maximum connections.')
    conn = super(PooledDatabase, self)._connect(*args, **kwargs)
    ts = time.time()
    key = self.conn_key(conn)
    logger.debug('Created new connection %s.', key)

  self._in_use[key] = ts # 使用中的数据库连接实例dict
  return conn

根据 pool 库中的 _connect 方法的代码可知:每次在建立数据库连接时,会检查连接实例是否超时。但是需要注意一点:使用中的数据库连接实例(_in_use dict中的数据库连接实例),是不会在创建数据库连接时,检查是否超时的。

因为这段代码中,每次创建连接实例,都是在 _connections(pool) 取实例,如果有的话就判断是否超时;如果没有的话就新建。

然而,使用中的数据库连接并不在 _connections 中,所以每次创建数据库连接实例时,并没有检测使用中的数据库连接实例是否超时。

只有调用连接池实例的 _close 方法。执行这个方法后,才会把使用后的连接实例放回到 _connections (pool)。

def _close(self, conn, close_conn=False):
  key = self.conn_key(conn)
  if close_conn:
    self._closed.add(key)
    super(PooledDatabase, self)._close(conn) # 关闭数据库连接的方法
  elif key in self._in_use:
    ts = self._in_use[key]
    del self._in_use[key]
    if self.stale_timeout and self._is_stale(ts):  # 到这里才会判断_in_use中的连接实例是否超时
      logger.debug('Closing stale connection %s.', key)
      super(PooledDatabase, self)._close(conn)  # 超时的话,关闭数据库连接
    else:
      logger.debug('Returning %s to pool.', key)
      heapq.heappush(self._connections, (ts, conn)) # 没有超时的话,放回到pool中

3.2.2 如果不显式的关闭连接,会出现的问题

如果不调用_close方法的话,使用后 的数据库连接就一直不会关闭(两个含义:回到pool中和关闭数据库连接),这样会造成两个问题:

1.每次都是新建数据库连接,因为 pool 中没有数据库连接实例。会导致稍微有一点并发量就会返回Exceeded maximum connections.错误

2.MySQL也是有 timeout 的,如果一个连接长时间没有请求的话,MySQL Server 就会关闭这个连接,但是,peewee的已建立(后面会解释为什么特指已建立的)的连接实例,并不知道 MySQL Server 已经关闭了,再去通过这个连接请求数据的话,就会返回 Error 2006: “MySQL server has gone away”错误,根据官方文档

3.2.3 推荐姿势

所以,每次操作完数据库就关闭连接实例。

用法1:使用with

def send_rule():
  with db.execution_context():
  # A new connection will be opened or, if using a connection pool,
  # pulled from the pool of available connections. Additionally, a
  # transaction will be started.
    for user in get_all_user():
      user_id = user['id']
      rule = Rule(user_id)
      rule_dict = rule.slack_rule(index)
      .....do something.....

用法2:使用Flask hook

@app.before_request
def _db_connect():
  database.connect()
#
# This hook ensures that the connection is closed when we've finished
# processing the request.
@app.teardown_request
def _db_close(exc):
  if not database.is_closed():
    database.close()
#
#
# 更优雅的用法:
from playhouse.flask_utils import FlaskDB
from dock_fastgear.model.base import db
#
app = Flask(__name__)
FlaskDB(app, db) # 这样就自动做了上面的事情(具体实现可查看http://docs.peewee-orm.com/en/latest/peewee/playhouse.html?highlight=Flask%20DB#flask-utils)

查看更多详情请移步官方文档:pool-apis

3.3 处理查询结果

这里没有什么大坑,就是有两点需要注意:

首先,查询的结果都是该 Model 的 object,注意不是 dict。如果想让结果为 dict,需要 playhouse 模块的工具方法进行转化:from playhouse.shortcuts import model_to_dict

其次,get方法只会返回一条记录

3.3.1 推荐姿势

from playhouse.shortcuts import model_to_dict
from model import HelloGitHub

def read_from_db(input_vol):
  content_list = []
  category_object_list = HelloGitHub.select(HelloGitHub.category).where(HelloGitHub.vol == input_vol)\
    .group_by(HelloGitHub.category).order_by(HelloGitHub.category)

  for fi_category_object in category_object_list:
    hellogithub = HelloGitHub.select()\
      .where((HelloGitHub.vol == input_vol)
          & (HelloGitHub.category == fi_category_object.category))\
      .order_by(HelloGitHub.create_time)
    for fi_hellogithub in hellogithub:
      content_list.append(model_to_dict(fi_hellogithub))
  return content_list

四、常见错误及解决办法

4.1 'buffer' object has no attribute 'translate'

  1. 错误信息: "'buffer' object has no attribute 'translate'"
  2. 场景:BlobField 字段存储zlib compress压缩的数据
  3. 解决办法:需要指定pymysql的版本小于0.6.7 否则会报错
  4. 参考

4.2 Can't connect to MySQL server Lost connection to MySQL server during query

  1. 错误信息:Can't connect to MySQL server Lost connection to MySQL server during query
  2. 场景:向 RDS 中插入数据
  3. 解决办法:因为请求的连接数过多,达到了 RDS 设置的连接数,所以需要调高 RDS 连接数
  4. 参考

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Python轻量级ORM框架Peewee访问sqlite数据库的方法详解

    本文实例讲述了Python轻量级ORM框架Peewee访问sqlite数据库的方法.分享给大家供大家参考,具体如下: ORM框架就是 object relation model,对象关系模型,用来实现把数据库中的表 映射到 面向对象编程语言中的类,不需要写sql,通过操作对象就能实现 增删改查. ORM的基本技术有3种: (1)映射技术 数据类型映射:就是把数据库中的数据类型,映射到编程语言中的数据类型.比如,把数据库的int类型映射到Python中的integer 类型. 类映射:把数据库中的

  • 浅谈Python peewee 使用经验

    本文使用案例是基于 python2.7 实现 以下内容均为个人使用 peewee 的经验和遇到的坑,不会涉及过多的基本操作.所以,没有使用过 peewee,可以先阅读文档 正确性和覆盖面有待提高,如果遇到新的问题欢迎讨论. 一.介绍 Peewee是一个简单.轻巧的 Python ORM. 简单.轻巧.富有表现力(原词 expressive )的ORM 支持python版本 2.6+ 和 3.2+ 支持数据库包括:sqlite, mysql and postgresql 包含一堆实用的扩展在 pl

  • 浅谈python中copy和deepcopy中的区别

    在下是个编程爱好者,最近将魔爪伸向了Python编程.....遇到copy和deepcopy感到很困惑,现在针对这两个方法进行区分,一种是浅复制(copy),一种是深度复制(deepcopy). 首先说一下deepcopy,所谓的深度复制,在这里我理解的是完全复制然后变成一个新的对象,复制的对象和被复制的对象没有任何关系,彼此之间无论怎么改变都相互不影响. 然后说一下copy,在这里我分为两类来说,一种是字典数据类型的copy函数,一种是copy包的copy函数. 一.字典数据类型的copy函数

  • 浅谈python中列表、字符串、字典的常用操作

    列表操作如此下: a = ["haha","xixi","baba"] 增:a.append[gg] a.insert[1,gg] 在下标为1的地方,新增 gg 删:a.remove(haha) 删除列表中从左往右,第一个匹配到的 haha del a.[0] 删除下标为0 对应的值 a.pop(0) 括号里不写内容,默认删除最后一个,写了,就删除对应下标的内容 改:a.[0] = "gg" 查:a[0] a.index(&q

  • 浅谈Python数据类型之间的转换

    Python数据类型之间的转换 函数 描述 int(x [,base]) 将x转换为一个整数 long(x [,base] ) 将x转换为一个长整数 float(x) 将x转换到一个浮点数 complex(real [,imag]) 创建一个复数 str(x) 将对象 x 转换为字符串 repr(x) 将对象 x 转换为表达式字符串 eval(str) 用来计算在字符串中的有效Python表达式,并返回一个对象 tuple(s) 将序列 s 转换为一个元组 list(s) 将序列 s 转换为一个

  • 浅谈Python 字符串格式化输出(format/printf)

    Python 字符串格式化使用 "字符 %格式1 %格式2 字符"%(变量1,变量2),%格式表示接受变量的类型.简单的使用例子如下: # 例:字符串格式化 Name = '17jo'   print 'www.%s.com'%Name   >> www.17jo.com Name = '17jo' Zone = 'com' print 'www.%s.%s'%(Name,Zone) >> www.17jo.com 字符串格式化时百分号后面有不同的格式符号,代表

  • 浅谈Python基础之I/O模型

    一.I/O模型 IO在计算机中指Input/Output,也就是输入和输出.由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘.网络等,就需要IO接口. 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别? 这个问题其实不同的人给出的答案都可能不同,比如wiki,就认为asynchronous IO和non-blockin

  • 浅谈python for循环的巧妙运用(迭代、列表生成式)

    介绍 我们可以通过for循环来迭代list.tuple.dict.set.字符串,dict比较特殊dict的存储不是连续的,所以迭代(遍历)出来的值的顺序也会发生变化. 迭代(遍历) #!/usr/bin/env python3 #-*- coding:utf-8 -*- vlist=['a','b','c'] vtuple=('a','b','c') vdict={'a': 1, 'b': 2, 'c': 3} vset={'a','b','c'} vstr='abc' for x in vl

  • 浅谈Python中函数的参数传递

    1.普通的参数传递 >>> def add(a,b): return a+b >>> print add(1,2) 3 >>> print add('abc','123') abc123 2.参数个数可选,参数有默认值的传递 >>> def myjoin(string,sep='_'): return sep.join(string) >>> myjoin('Test') 'T_e_s_t' >>>

  • 浅谈Python中用datetime包进行对时间的一些操作

    1. 计算给出两个时间之间的时间差 import datetime as dt # current time cur_time = dt.datetime.today() # one day pre_time = dt.date(2016, 5, 20) # eg: 2016.5.20 delta = cur_time - pre_time # if you want to get discrepancy in days print delta.days # if you want to get

  • 浅谈python 四种数值类型(int,long,float,complex)

    Python支持四种不同的数值类型,包括int(整数)long(长整数)float(浮点实际值)complex (复数),本文章向码农介绍python 四种数值类型,需要的朋友可以参考一下. 数字数据类型存储数值.他们是不可改变的数据类型,这意味着改变数字数据类型的结果,在一个新分配的对象的值. Number对象被创建,当你给他们指派一个值.例如: var1 = 1 var2 = 10 您也可以删除数字对象的参考,使用del语句. del语句的语法是: del var1[,var2[,var3[

随机推荐