【Python百日进阶-Web开发-Peewee】Day247 - 数据库 连接管理

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 数据库没有任何影响。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是使用 FastAPI 中 Tortoise-ORM、SQLAlchemy 和 peewee 进行 ORM 查询的示例。 ## Tortoise-ORM ```python from fastapi import FastAPI from tortoise import fields from tortoise.contrib.fastapi import register_tortoise, HTTPNotFoundError from tortoise.models import Model from tortoise import Tortoise class User(Model): id = fields.IntField(pk=True) name = fields.CharField(50) email = fields.CharField(50) class Meta: table = "users" app = FastAPI() @app.on_event("startup") async def startup(): await Tortoise.init( db_url="sqlite://db.sqlite3", modules={"models": ["main"]} ) await Tortoise.generate_schemas() @app.on_event("shutdown") async def shutdown(): await Tortoise.close_connections() @app.get("/users") async def get_users(): users = await User.all() return users @app.get("/users/{user_id}") async def get_user(user_id: int): user = await User.get_or_none(id=user_id) if user is None: raise HTTPNotFoundError return user register_tortoise( app, db_url="sqlite://db.sqlite3", modules={"models": ["main"]}, generate_schemas=True, add_exception_handlers=True ) ``` ## SQLAlchemy ```python from fastapi import FastAPI from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) name = Column(String(50)) email = Column(String(50)) engine = create_engine("sqlite:///db.sqlite3") SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) app = FastAPI() @app.get("/users") async def get_users(): db = SessionLocal() users = db.query(User).all() db.close() return users @app.get("/users/{user_id}") async def get_user(user_id: int): db = SessionLocal() user = db.query(User).filter(User.id == user_id).first() db.close() if user is None: raise HTTPNotFoundError return user ``` ## peewee ```python from fastapi import FastAPI from peewee import SqliteDatabase, Model, CharField, IntegerField from playhouse.shortcuts import model_to_dict db = SqliteDatabase("db.sqlite3") class User(Model): id = IntegerField(primary_key=True) name = CharField() email = CharField() class Meta: database = db table_name = "users" app = FastAPI() @app.on_event("startup") def startup(): db.connect() db.create_tables([User]) @app.on_event("shutdown") def shutdown(): db.close() @app.get("/users") async def get_users(): users = [model_to_dict(user) for user in User.select()] return users @app.get("/users/{user_id}") async def get_user(user_id: int): user = User.get_or_none(User.id == user_id) if user is None: raise HTTPNotFoundError return model_to_dict(user) ``` 注意:以上示例中的代码仅用于演示 ORM 查询的基本用法,并且没有进行错误处理。在实际应用中,你应该根据需要添加适当的错误处理和安全性检查。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岳涛@心馨电脑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值