peewee 使用经验

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

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

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

一、介绍

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

  • 简单、轻巧、富有表现力(原词 expressive )的ORM
  • 支持python版本 2.6+ 和 3.2+
  • 支持数据库包括:sqlite, mysql and postgresql
  • 包含一堆实用的扩展在 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形如

  • mysql://user:passwd@ip:port/my_db 将创建一个 本地 MySQL 的 my_db 数据库的实例(will create a MySQLDatabase instance)
  • 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

  • apsw: APSWDatabase
  • mysql: MySQLDatabase
  • mysql+pool: PooledMySQLDatabase
  • postgres: PostgresqlDatabase
  • postgres+pool: PooledPostgresqlDatabase
  • postgresext: PostgresqlExtDatabase
  • postgresext+pool: PooledPostgresqlExtDatabase
  • sqlite: SqliteDatabase
  • sqliteext: SqliteExtDatabase
  • sqlite+pool: PooledSqliteDatabase
  • 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'

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

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

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

4.3 打印执行的 SQL

query = News.select().where(News.url == 'test')
sql, param = query.sql()
print sql.replace("?","{}").format(*param)

[Python]peewee使用经验的更多相关文章

  1. [Python]peewee 使用经验

    peewee 使用经验 本文使用案例是基于 python2.7 实现 以下内容均为个人使用 peewee 的经验和遇到的坑,不会涉及过多的基本操作.所以,没有使用过 peewee,可以先阅读文档 正确 ...

  2. python -- peewee处理数据库连接

    目前,实现了的Database子类有三个:SqliteDatabase.MySQLDatabase.PostgresqlDatabase class SqliteDatabase(Database) ...

  3. python peewee.ImproperlyConfigured: MySQLdb or PyMySQL must be installed.

    最近在学习Python,打算先看两个在线教程,再在github上找几个开源的项目练习一下,在学到“被解放的姜戈”时遇到django同步数据库时无法执行的错误,记录一下. 错误现象: 执行python ...

  4. 使用 Python 的 SQLite JSON1 和 FTS5 扩展

    早在九月份,编程界出现一个名为 json1.c 的文件,此前这个文件一直在 SQLite 的库里面.还有,笔者也曾总结通过使用新的 json1 扩展来编译 pysqlite 的技巧.但现在随着 SQL ...

  5. python爬虫scrapy项目详解(关注、持续更新)

    python爬虫scrapy项目(一) 爬取目标:腾讯招聘网站(起始url:https://hr.tencent.com/position.php?keywords=&tid=0&st ...

  6. python 关于GUI的思考(转)

    作者:知乎用户链接:https://www.zhihu.com/question/19721063/answer/21311275来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  7. [转] 以后再有人问你selenium是什么,你就把这篇文章给他

    本文转自:https://blog.csdn.net/TestingGDR/article/details/81950593 写在最前面:目前自动化测试并不属于新鲜的事物,或者说自动化测试的各种方法论 ...

  8. selenium浏览器自动化测试框架文档(修正版)

    写在最前面:目前自动化测试并不属于新鲜的事物,或者说自动化测试的各种方法论已经层出不穷,但是,能够在项目中持之以恒的实践自动化测试的团队,却依旧不是非常多.有的团队知道怎么做,做的还不够好:有的团队还 ...

  9. SAE Python使用经验 好文推荐

    SAE Python使用经验 好文推荐 SAE Python使用经验 好文推荐

随机推荐

  1. Tinychatserver: 一个简易的命令行群聊程序

    这是学习网络编程后写的一个练手的小程序,可以帮助复习socket,I/O复用,非阻塞I/O等知识点. 通过回顾写的过程中遇到的问题的形式记录程序的关键点,最后给出完整程序代码. 0. 功能 编写一个简 ...

  2. Regular Expression(正则表达式)之邮箱验证

    正则表达式(regular expression, 常常缩写为RegExp) 是一种用特殊符号编写的模式,描述一个或多个文本字符串.使用正则表达式匹配文本的模式,这样脚本就可以轻松的识别和操作文本.其 ...

  3. java读取和写入txt文件

    package com.yinghuo.testDES; import java.io.BufferedReader;import java.io.BufferedWriter;import java ...

  4. Asp.Net MVC 之 Autofac 初步使用2 集成mvc 属性注入以及自动注入

    首先看下Demo2的结构     然后下面一步步将Autofac集成到mvc中. 首先,定义Model Product.cs public class Product { public int Id ...

  5. 关于Storm tick

    关于Storm tick 1. tick的功能 Apache Storm中内置了一种定时机制——tick,它能够让任何bolt的所有task每隔一段时间(精确到秒级,用户可以自定义)收到一个来自__s ...

  6. redux:applyMiddleware源码解读

    前言: 笔者之前也有一篇关于applyMiddleware的总结.是applyMiddleware的浅析. 现在阅读了一下redux的源码.下面说说我的理解. 概要源码: step 1:  apply ...

  7. (0)写给Web初学者的教案-----Web基础

    0,Web基础 一.    What is the Web? Can It Eat? 很多同学可能都听说过一个名词叫做“Web”,这个词隐隐约约好像和我们上网相关.但是呢,又很难说的清楚.我们今天每位 ...

  8. Java中static的特点

    前两天面试时被问到静态的特点,当时回答地不是很好,现在来总结一下 在了解某样东西的时候我们通常会从它是什么,为什么,和怎么样在三方面来衡量,对于java中的static,我们也这样讨论下,要明确以下几 ...

  9. [UWP]了解模板化控件(2.1):理解ContentControl

    UWP的UI主要由布局容器和内容控件(ContentControl)组成.布局容器是指Grid.StackPanel等继承自Panel,可以拥有多个子元素的类.与此相对,ContentControl则 ...

  10. flex盒子里面元素linehight对高度的影响

    那天,高高兴兴的测试flex属性.大家都知道,当父盒子display:flex的时候,子盒子即使宽高和大于父盒子,也会由于flex的特性进行收缩:然而,在给父盒子加了flex-direction:co ...