文章目录
6.6 连接管理
要打开与数据库的连接,请使用以下Database.connect()方法:
>>> db = SqliteDatabase(':memory:') # In-memory SQLite database.
>>> db.connect()
True
如果我们尝试调用connect()一个已经打开的数据库,我们会得到 OperationalError:
>>> db.connect()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/charles/pypath/peewee.py", line 2390, in connect
raise OperationalError('Connection already opened.')
peewee.OperationalError: Connection already opened.
为了防止引发此异常,我们可以connect()使用附加参数调用reuse_if_open:
>>> db.close() # Close connection.
True
>>> db.connect()
True
>>> db.connect(reuse_if_open=True)
False
请注意,如果数据库连接已经打开,则调用connect()返回。False
要关闭连接,请使用以下Database.close()方法:
>>> db.close()
True
调用close()已经关闭的连接不会导致异常,但会返回False:
>>> db.connect() # Open connection.
True
>>> db.close() # Close connection.
True
>>> db.close() # Connection already closed, returns False.
False
可以使用以下方法测试数据库是否关闭 Database.is_closed():
>>> db.is_closed()
True
6.6.1 使用自动连接
autoconnect=True如果数据库是用(默认)初始化的,则在使用它之前不需要显式连接到数据库。显式管理连接被认为是最佳实践,因此您可以考虑禁用该autoconnect行为。
明确您的连接生命周期非常有帮助。例如,如果连接失败,则在打开连接时将捕获异常,而不是稍后在执行查询时的任意时间。此外,如果使用连接池,则需要调用connect()并close() 确保连接被正确回收。
为了最好的保证正确性,禁用autoconnect:
db = PostgresqlDatabase('my_app', user='postgres', autoconnect=False)
6.6.2 线程安全
Peewee 使用线程本地存储跟踪连接状态,使 PeeweeDatabase对象可以安全地与多个线程一起使用。每个线程都有自己的连接,因此任何给定线程在给定时间只会打开一个连接。
6.6.3 上下文管理器
数据库对象本身可以用作上下文管理器,它在包装的代码块期间打开一个连接。此外,事务在包装块的开头打开并在连接关闭之前提交(除非发生错误,在这种情况下事务被回滚)。
>>> db.is_closed()
True
>>> with db:
... print(db.is_closed()) # db is open inside context manager.
...
False
>>> db.is_closed() # db is closed.
True
如果要单独管理事务,可以使用 Database.connection_context()上下文管理器。
>>> with db.connection_context():
... # db connection is open.
... pass
...
>>> db.is_closed() # db connection is closed.
True
该connection_context()方法也可以用作装饰器:
@db.connection_context()
def prepare_database():
# DB connection will be managed by the decorator, which opens
# a connection, calls function, and closes upon returning.
db.create_tables(MODELS) # Create schema.
load_fixture_data(db)
6.6.4 DB-API 连接对象
要获取对底层 DB-API 2.0 连接的引用,请使用该 Database.connection()方法。此方法将返回当前打开的连接对象,如果存在,则将打开一个新连接。
>>> db.connection()
<sqlite3.Connection object at 0x7f94e9362f10>
6.7 连接池
连接池由pool 模块提供,包含在playhouse扩展库中。池支持:
- 超时后连接将被回收。
- 打开连接数的上限。
from playhouse.pool import PooledPostgresqlExtDatabase
db = PooledPostgresqlExtDatabase(
'my_database',
max_connections=8,
stale_timeout=300,
user='postgres')
class BaseModel(Model):
class Meta:
database = db
可以使用以下池数据库类:
- PooledPostgresqlDatabase
- PooledPostgresqlExtDatabase
- PooledMySQLDatabase
- PooledSqliteDatabase
- PooledSqliteExtDatabase
有关 peewee 连接池的深入讨论,请参阅playhouse文档的连接池 部分。
6.8 测试 Peewee 应用程序
在为使用 Peewee 的应用程序编写测试时,可能需要使用特殊的数据库进行测试。另一种常见的做法是针对干净的数据库运行测试,这意味着在每次测试开始时确保表为空。
要在运行时将模型绑定到数据库,可以使用以下方法:
- Database.bind_ctx(),它返回一个上下文管理器,它将在包装块的持续时间内将给定的模型绑定到数据库实例。
- Model.bind_ctx(),它同样返回一个上下文管理器,在包装块的持续时间内将模型(以及可选的依赖项)绑定到给定的数据库。
- Database.bind(),这是一种一次性操作,将模型(以及可选的依赖项)绑定到给定的数据库。
- Model.bind(),这是一种一次性操作,将模型(以及可选的依赖项)绑定到给定的数据库。
根据您的用例,这些选项之一可能更有意义。对于下面的示例,我将使用Model.bind().
示例测试用例设置:
# tests.py
import unittest
from my_app.models import EventLog, Relationship, Tweet, User
MODELS = [User, Tweet, EventLog, Relationship]
# use an in-memory SQLite for tests.
test_db = SqliteDatabase(':memory:')
class BaseTestCase(unittest.TestCase):
def setUp(self):
# Bind model classes to test db. Since we have a complete list of
# all models, we do not need to recursively bind dependencies.
test_db.bind(MODELS, bind_refs=False, bind_backrefs=False)
test_db.connect()
test_db.create_tables(MODELS)
def tearDown(self):
# Not strictly necessary since SQLite in-memory databases only live
# for the duration of the connection, and in the next step we close
# the connection...but a good practice all the same.
test_db.drop_tables(MODELS)
# Close connection to db.
test_db.close()
# If we wanted, we could re-bind the models to their original
# database here. But for tests this is probably not necessary.
顺便说一句,根据经验,我建议使用与生产中相同的数据库后端来测试您的应用程序,以避免任何潜在的兼容性问题。
如果您想查看更多有关如何使用 Peewee 运行测试的示例,请查看 Peewee 自己的测试套件。
6.9 与 Gevent 异步
建议使用gevent使用 Postgresql 或 MySQL 进行异步 I/O。我喜欢 gevent 的原因:
- 无需对所有内容进行特殊用途的“循环感知”重新实现。使用 asyncio 的第三方库通常必须重新实现层层代码以及重新实现协议本身。
- Gevent 允许您使用普通、干净、惯用的 Python 编写应用程序。无需在每一行都用“async”、“await”和其他噪音乱扔垃圾。没有回调、期货、任务、承诺。没有杂物。
- Gevent 适用于 Python 2和Python 3。
- Gevent 是Pythonic的。Asyncio 是一个非 Python 的可憎之物。
除了monkey-patching套接字,如果您使用MySQL和pymysql 之类的纯 Python 驱动程序,或者 在纯 Python 模式下 使用mysql-connector,则不需要特殊步骤 。用 C 编写的 MySQL 驱动程序需要特殊配置,这超出了本文档的范围。
对于Postgres和psycopg2,这是一个 C 扩展,您可以使用以下代码片段来注册将使您的连接异步的事件挂钩:
from gevent.socket import wait_read, wait_write
from psycopg2 import extensions
# Call this function after monkey-patching socket (etc).
def patch_psycopg2():
extensions.set_wait_callback(_psycopg2_gevent_callback)
def _psycopg2_gevent_callback(conn, timeout=None):
while True:
state = conn.poll()
if state == extensions.POLL_OK:
break
elif state == extensions.POLL_READ:
wait_read(conn.fileno(), timeout=timeout)
elif state == extensions.POLL_WRITE:
wait_write(conn.fileno(), timeout=timeout)
else:
raise ValueError('poll() returned unexpected result')
SQLite,因为它嵌入在 Python 应用程序本身中,所以不会执行任何可能成为非阻塞候选的套接字操作。异步对 SQLite 数据库没有任何影响。