Mysql接口开发-aiomysql

概述

aiomysql 是一个用于 Python 的异步 MySQL 客户端库,它允许你使用 asyncio 编写异步代码来与 MySQL 数据库进行交互

这段代码定义了一个名为 `ClientManager` 的类,用于管理数据库连接和执行SQL查询。它使用了 `aiomysql` 库来与MySQL数据库进行异步交互 
1. **导入模块**:
   - `reprlib` 用于生成对象的字符串表示。
   - `asyncio` 用于异步编程。
   - `loguru` 用于日志记录。
   - `cachetools` 用于缓存管理。
   - `pandas` 用于数据分析和数据处理。
   - `aiomysql` 用于异步MySQL数据库交互。
   - `pymysql.constants` 用于定义MySQL常量。
   - `deepfos` 和 `deepfos.api.datatable` 可能是一个自定义的框架或模块。
2. **全局设置**:
   - `aRepr.maxstring = 200`:设置`reprlib`的最大字符串长度。
3. **`Client` 类**:
   - 管理MySQL连接池。
   - 使用`asyncio.Event`来等待连接池初始化完成。
   - 包含连接池、状态、初始化事件、租约管理器和密码。
   - `__init__`方法初始化属性。
   - `init_pool`方法初始化连接池。
   - `ensure_connected`方法确保连接池已建立。
   - `execute`、`select`、`query_dataframe`和`trxn_execute`方法执行不同的SQL操作。
   - `close`方法关闭连接池。
4. **`ClientManager` 类**:
   - 使用`TTLCache`管理`Client`实例的缓存。
   - `new_client`方法获取一个新的或缓存的`Client`实例。
5. **`LeaseManager` 类**:
   - 继承自`AbsLeaseManager`。
   - 管理数据库凭据的租约。
   - `new_api`方法创建一个新的MySQLAPI实例。

这段代码定义了一个名为 `AbsLeaseManager` 的抽象类,用于管理数据库连接的租约
 **`AbsLeaseManager` 类**:
   - `DB` 类变量,用于存储数据库类型。
   - `__init__` 方法初始化租约管理器,包括设置最后续租时间、续租间隔、任务标识、租约信息和续租函数。
   - `new_api` 方法异步地创建一个新的API实例。
   - `_try_renewal` 方法尝试续租,如果失败,会重试一次。
   - `renew` 方法执行续租操作,如果失败,会重试。
   - `loop` 方法启动一个无限循环,不断尝试续租。
   - `schedule` 方法安排续租任务。
   - `cancel` 方法取消续租任务。
这个 `AbsLeaseManager` 类的主要作用是管理数据库连接的租约,通过异步方式进行续租操作,确保数据库连接的有效性。它使用重试机制来处理续租过程中的异常情况。
 

6. **辅助函数**:
   - `select`、`execute`、`query_dataframe`和`trxn_execute`函数使用`ClientManager`获取`Client`实例并执行相应的数据库操作。
7. **日志记录和异常处理**:
   - 使用`logger`进行日志记录。
   - 定义`DBConnecetionError`作为自定义的连接错误。
这个模块提供了对MySQL数据库的异步访问,并且通过连接池和缓存机制来提高性能和效率。它还包含了一个事务执行的方法,可以一次执行多个SQL语句。
 

from reprlib import aRepr
import asyncio

from loguru import logger
from cachetools import TTLCache
import pandas as pd
import aiomysql
from pymysql.constants import FIELD_TYPE

from deepfos import OPTION
from deepfos.api.datatable import MySQLAPI
from deepfos.lib.constant import UNSET
from deepfos.lib.asynchronous import register_on_loop_shutdown
from .utils import (
    decrypt, AbsLeaseManager, ACCOUNT_EXPIRE,
    PENDING, INITIALIZED, INITIALIZING, DBConnecetionError
)

aRepr.maxstring = 200


# cdef class Client:
#     cdef:
#         object  pool
#         int status
#         object  inited
#         object  lease
#         bytes secret
class Client:
    def __init__(self):
        self.pool = UNSET
        self.status = PENDING
        self.inited = asyncio.Event()
        # 租约
        self.lease = LeaseManager(ACCOUNT_EXPIRE / 3)
        self.secret = "!ABCD-EFGH-IJKL@".encode()
        # 注册一个回调函数
        # self.close,当事件循环关闭时执行。这个函数可能用于关闭数据库连接池或执行其他清理工作。
        register_on_loop_shutdown(self.close, True)

    async def init_pool(self):
        if self.status == INITIALIZED:
            return self.pool

        if self.status == PENDING:
            # 如果 # self.status # 等于# PENDING,则将其更新为
            # INITIALIZING,这可能是一个状态机,用于跟踪连接池的初始化过程。
            self.status = INITIALIZING

        #     如果 self.status 不等于 INITIALIZED 且不等于 PENDING,则说明连接池正在初始化中。
        else:
            await self.inited.wait()
            if self.status != INITIALIZED:
                raise RuntimeError("Failed to initialze connection pool.")
            return self.pool

        # 使用 self.lease 对象的 renew 方法获取新的数据库配置信息。self.lease 是一个 LeaseManager 实例,用于管理租约。
        conf = await self.lease.renew()
        self.lease.schedule(slow_start=True)

        try:
            # 使用 aiomysql.create_pool 异步创建一个连接池。
            # 参数包括最小连接数 minsize、最大连接数 maxsize、主机 host、端口 port、用户名 user、密码 password(需要解密)和数据库名 db。
            self.pool = await aiomysql.create_pool(
                minsize=5,
                maxsize=10,
                host=conf.host,
                port=conf.port,
                user=conf.name,
                password=decrypt(self.secret, conf.password),
                db=conf.dbName,
            )
        except Exception as e:
            self.status = PENDING
            raise DBConnecetionError(e) from None
        else:
            self.status = INITIALIZED
            self.inited.set()
            return self.pool

    async def ensure_connected(self):
        max_retry = 3
        retries = 0
        interval = 0.5

        while self.pool is UNSET or self.pool._closed:  # noqa
            retries += 1
            try:
                await self.init_pool()
            except DBConnecetionError:
                if retries > max_retry:
                    self.inited.set()
                    raise
                logger.exception(f'Failed to get connection pool, '
                                 f'starting {retries} times retry.')
                await asyncio.sleep(interval)
                interval *= 2
            except Exception:
                self.inited.set()
                self.status = PENDING
                raise

        return self.pool

    async def execute(self, sql):
        pool = await self.ensure_connected()
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                logger.opt(lazy=True).debug("Run sql: {sql}",
                                            sql=lambda: aRepr.repr(sql))
                await cur.execute(sql)
            await conn.commit()
            return conn.affected_rows()

    async def select(self, sql):
        pool = await self.ensure_connected()
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                logger.opt(lazy=True).debug("Run sql: {sql}",
                                            sql=lambda: aRepr.repr(sql))
                await cur.execute(sql)
                return await cur.fetchall()

    async def query_dataframe(self, sql):
        pool = await self.ensure_connected()
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                logger.opt(lazy=True).debug("Run sql: {sql}",
                                            sql=lambda: aRepr.repr(sql))
                await cur.execute(sql)
                columns = [d[0] for d in cur.description]
                decimal_cols = [
                    d[0] for d in cur.description
                    if d[1] == FIELD_TYPE.NEWDECIMAL
                ]
                data = await cur.fetchall()
                df = pd.DataFrame(data=data, columns=columns)
                for col in decimal_cols:
                    df[col] = pd.to_numeric(df[col], downcast='float')
                return df

    async def trxn_execute(self, sqls):
        pool = await self.ensure_connected()
        async with pool.acquire() as conn:
            async with conn.cursor() as cur:
                logger.opt(lazy=True).debug("Run sql: {sql}",
                                            sql=lambda: aRepr.repr(sqls))
                for sql in sqls:
                    await cur.execute(sql)
            await conn.commit()
            return conn.affected_rows()

    async def close(self):
        self.lease.cancel()
        self.status = PENDING

        if self.pool is not UNSET:
            self.pool.close()
            await self.pool.wait_closed()
            self.pool = UNSET


# cdef class ClientManager:
class ClientManager:
    cache = TTLCache(maxsize=10, ttl=1800)

    def new_client(self):
        key = (
            OPTION.api.header.get('space'),
            OPTION.api.header.get('app'),
        )

        if key not in self.cache:
            self.cache[key] = Client()

        return self.cache[key]


class LeaseManager(AbsLeaseManager):
    DB = 'mysql'

    async def new_api(self):
        return await MySQLAPI(version=1.0, sync=False)


async def select(sql):
    return await ClientManager().new_client().select(sql)


async def execute(sql):
    return await ClientManager().new_client().execute(sql)


async def query_dataframe(sql):
    return await ClientManager().new_client().query_dataframe(sql)


async def trxn_execute(sqls):
    return await ClientManager().new_client().trxn_execute(sqls)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值