Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@ jobs:
uses: astral-sh/ruff-action@v3
with:
args: check . --exit-non-zero-on-fix

- name: Prepare database
run: uv run nb orm upgrade
- name: Test
run: uv run pytest ./tests/value_tests/*
- name: Build package
run: uv build # 生成构建产物到dist目录

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/PR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ jobs:
- name: Prepare database
run: uv run nb orm upgrade

- name: Test
run: uv run pytest ./tests/value_tests/*

- name: Run Unit Tests
run: uv run pytest tests/* --cov=nonebot_plugin_value --cov-report=term-missing --cov-report=xml -v
- name: Build package
run: uv build # 生成构建产物到dist目录
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,5 @@ cython_debug/
/data/
/config/
/logs/
/.ruff_cache/
/.ruff_cache/
test-results.xml
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
</p>
</div>

> *为 NoneBot2 设计的娱乐系统基础设施。*

## 核心特性

`nonebot_plugin_value` 是一个基于 NoneBot2 的通用经济系统插件,提供以下核心功能:
Expand Down
64 changes: 23 additions & 41 deletions nonebot_plugin_value/api/api_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ async def set_frozen_all(account_id: str, frozen: bool) -> None:
data_id=get_uni_id(account_id, currency.id),
)
await _set_frozen_all(account_id, frozen, session)
await session.commit()


async def set_frozen(account_id: str, currency_id: str, frozen: bool) -> None:
"""设置账户特定货币冻结状态

Args:
account_id (str): 用户ID
currency_id (str): 货币ID
account_id (str): 账户 ID
currency_id (str): 货币 ID
frozen (bool): 是否冻结
"""
async with get_session() as session:
Expand All @@ -49,6 +50,7 @@ async def set_frozen(account_id: str, currency_id: str, frozen: bool) -> None:
data_id=get_uni_id(account_id, currency_id),
)
await _set_frozen(account_id, frozen, currency_id, session)
await session.commit()


async def list_accounts(
Expand All @@ -65,14 +67,8 @@ async def list_accounts(
if currency_id is None:
currency_id = DEFAULT_CURRENCY_UUID.hex
async with get_session() as session:
result = [
UserAccountData(
id=account.id,
uni_id=account.uni_id,
currency_id=account.currency_id,
balance=account.balance,
last_updated=account.last_updated,
)
result: list[UserAccountData] = [
UserAccountData.model_validate(account, from_attributes=True)
for account in await _list_accounts(session, currency_id)
]
if not no_cache_refresh:
Expand Down Expand Up @@ -137,23 +133,21 @@ async def get_or_create_account(
category=CacheCategoryEnum.ACCOUNT,
data=data,
)
await session.commit()
return data


async def batch_del_balance(
updates: list[tuple[str, float]],
currency_id: str | None = None,
source: str = "batch_update",
) -> list[UserAccountData] | None:
) -> None:
"""批量减少账户余额

Args:
updates (list[tuple[str, float]]): 元组列表,包含用户id和金额
currency_id (str | None, optional): 货币ID. Defaults to None.
source (str, optional): 源说明. Defaults to "batch_update".

Returns:
None: None
updates (list[tuple[str, float]]): 元组列表,包含用户 id 和金额
currency_id (str | None, optional): 货币 ID. Defaults to None.
source (str, optional): 源说明。Defaults to "batch_update".
"""
if currency_id is None:
currency_id = DEFAULT_CURRENCY_UUID.hex
Expand All @@ -168,16 +162,13 @@ async def batch_add_balance(
updates: list[tuple[str, float]],
currency_id: str | None = None,
source: str = "batch_update",
) -> list[UserAccountData] | None:
) -> None:
"""批量添加账户余额

Args:
updates (list[tuple[str, float]]): 元组列表,包含用户id和金额
currency_id (str | None, optional): 货币ID. Defaults to None.
source (str, optional): 源说明. Defaults to "batch_update".

Returns:
None: None
updates (list[tuple[str, float]]): 元组列表,包含用户 id 和金额
currency_id (str | None, optional): 货币 ID. Defaults to None.
source (str, optional): 源说明。Defaults to "batch_update".
"""
if currency_id is None:
currency_id = DEFAULT_CURRENCY_UUID.hex
Expand All @@ -197,18 +188,14 @@ async def add_balance(
"""添加用户余额

Args:
user_id (str): 用户ID
user_id (str): 用户 ID
amount (float): 金额
source (str, optional): 源描述. Defaults to "_transfer".
currency_id (str | None, optional): 货币ID(不填使用默认). Defaults to None.
source (str, optional): 源描述Defaults to "_transfer".
currency_id (str | None, optional): 货币 ID(不填使用默认). Defaults to None.

Raises:
RuntimeError: 如果添加失败则抛出异常

Returns:
None: None
"""

if currency_id is None:
currency_id = DEFAULT_CURRENCY_UUID.hex
data = await _a_balance(user_id, currency_id, amount, source)
Expand All @@ -228,20 +215,18 @@ async def del_balance(
"""减少一个账户的余额

Args:
user_id (str): 用户ID
user_id (str): 用户 ID
amount (float): 金额
source (str, optional): 源说明. Defaults to "_transfer".
currency_id (str | None, optional): 货币ID(不填则使用默认货币). Defaults to Noen.
source (str, optional): 源说明Defaults to "_transfer".
currency_id (str | None, optional): 货币 ID(不填则使用默认货币). Defaults to None.

Raises:
RuntimeError: 如果失败则抛出

Returns:
UserAccountData: 用户数据
"""
if currency_id is None:
currency_id = DEFAULT_CURRENCY_UUID.hex
data = await _d_balance(user_id, currency_id, amount, source)
async with get_session() as session:
data = await _d_balance(user_id, currency_id, amount, session, source)
if not data.success:
raise RuntimeError(data.message)
await CacheManager().expire_cache(
Expand All @@ -267,9 +252,6 @@ async def transfer_funds(

Raises:
RuntimeError: 失败则抛出

Returns:
None: None
"""
if currency_id is None:
currency_id = DEFAULT_CURRENCY_UUID.hex
Expand Down
27 changes: 18 additions & 9 deletions nonebot_plugin_value/api/api_currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ async def update_currency(currency_data: CurrencyData) -> CurrencyData:
async with get_session() as session:
currency = await _update_currency(currency_data, session)
data = CurrencyData.model_validate(currency, from_attributes=True)
await CacheManager().update_cache(
await session.commit()
await CacheManager().expire_cache(
category=CacheCategoryEnum.CURRENCY,
data=data,
data_id=data.id,
)
return data

Expand All @@ -42,7 +43,9 @@ async def remove_currency(currency_id: str) -> None:
await CacheManager().expire_cache(
category=CacheCategoryEnum.CURRENCY, data_id=currency_id
)
await _remove_currency(currency_id)
async with get_session() as session:
await _remove_currency(currency_id)
await session.commit()


async def list_currencies(no_cache_update: bool = False) -> list[CurrencyData]:
Expand All @@ -57,6 +60,7 @@ async def list_currencies(no_cache_update: bool = False) -> list[CurrencyData]:
CurrencyData.model_validate(currency, from_attributes=True)
for currency in currencies
]
await session.commit()
if not no_cache_update:
for currency in result:
await CacheManager().update_cache(
Expand Down Expand Up @@ -84,7 +88,9 @@ async def get_currency(currency_id: str, no_cache: bool = False) -> CurrencyData
currency = await _g_currency(currency_id, session)
if currency is None:
return None
return CurrencyData.model_validate(currency, from_attributes=True)
data = CurrencyData.model_validate(currency, from_attributes=True)
await session.commit()
return data


async def get_currency_by_kwargs(**kwargs: object) -> CurrencyData | None:
Expand All @@ -100,7 +106,9 @@ async def get_currency_by_kwargs(**kwargs: object) -> CurrencyData | None:
currency = await __currency_by_kwargs(**kwargs, session=session)
if currency is None:
return None
return CurrencyData.model_validate(currency, from_attributes=True)
data = CurrencyData.model_validate(currency, from_attributes=True)
await session.commit()
return data


async def get_default_currency() -> CurrencyData:
Expand All @@ -111,24 +119,24 @@ async def get_default_currency() -> CurrencyData:
"""
async with get_session() as session:
currency = await _default_currency(session)
return CurrencyData.model_validate(currency, from_attributes=True)
data = CurrencyData.model_validate(currency, from_attributes=True)
await session.commit()
return data


async def create_currency(currency_data: CurrencyData) -> None:
"""创建新货币

Args:
currency_data (CurrencyData): 货币信息

Returns:
CurrencyData: 货币信息
"""
async with get_session() as session:
await CacheManager().update_cache(
category=CacheCategoryEnum.CURRENCY,
data=currency_data,
)
await _create_currency(currency_data, session)
await session.commit()


async def get_or_create_currency(currency_data: CurrencyData) -> CurrencyData:
Expand All @@ -147,4 +155,5 @@ async def get_or_create_currency(currency_data: CurrencyData) -> CurrencyData:
category=CacheCategoryEnum.CURRENCY,
data=data,
)
await session.commit()
return data
9 changes: 7 additions & 2 deletions nonebot_plugin_value/api/api_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ async def get_transaction_history_by_time_range(
TransactionData.model_validate(transaction, from_attributes=True)
for transaction in data
]
await session.commit()
return result_list


Expand All @@ -57,14 +58,16 @@ async def get_transaction_history(
list[TransactionData]: 包含交易数据的列表
"""
async with get_session() as session:
return [
d = [
TransactionData.model_validate(transaction, from_attributes=True)
for transaction in await _transaction_history(
account_id,
session,
limit,
)
]
await session.commit()
return d


async def remove_transaction(transaction_id: str) -> bool:
Expand All @@ -77,7 +80,9 @@ async def remove_transaction(transaction_id: str) -> bool:
bool: 是否成功删除
"""
async with get_session() as session:
return await _remove_transaction(
d = await _remove_transaction(
transaction_id,
session,
)
await session.commit()
return d
6 changes: 3 additions & 3 deletions nonebot_plugin_value/hook/exception.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any


class BaseException(Exception):
class BasicException(Exception):
"""
Base exception class for this module.
"""
Expand All @@ -11,13 +11,13 @@ def __init__(self, message: str = "", data: Any | None = None):
self.data = data


class CancelAction(BaseException):
class CancelAction(BasicException):
"""
Exception raised when the user cancels an action.
"""


class DataUpdate(Exception):
class DataUpdate(BasicException):
"""
Exception raised when the data updated
"""
Expand Down
21 changes: 13 additions & 8 deletions nonebot_plugin_value/hook/hooks_manager.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# 事件预处理/后处理钩子
import asyncio
from collections.abc import Awaitable, Callable

from nonebot import logger

from .context import TransactionComplete, TransactionContext
from .exception import CancelAction, DataUpdate
from .exception import BasicException
from .hooks_type import HooksType


Expand Down Expand Up @@ -36,7 +37,7 @@ def register(
self, hook_name: str, hook_func: Callable[..., Awaitable[None]]
) -> None:
"""注册一个Hook"""
if hook_name not in HooksType:
if all(hook_name != hook.value for hook in HooksType.__members__.values()):
raise ValueError(f"Invalid hook name: {hook_name}")
self.__hooks.setdefault(hook_name, []).append(hook_func)

Expand All @@ -49,10 +50,14 @@ async def run_hooks(
async def _run_single_hook(hook: Callable[..., Awaitable[None]]) -> None:
try:
await hook(context)
except CancelAction | DataUpdate:
except BasicException:
raise
except Exception:
logger.opt(exception=True).error("钩子执行失败")

for hook in hooks:
await _run_single_hook(hook)
except Exception as e:
logger.opt(exception=e).error("钩子执行失败")

rst: list[BaseException | None] = await asyncio.gather(
*[_run_single_hook(hook) for hook in hooks], return_exceptions=True
) # 即使没用gather的先前实现,遇到exception中断也会抛出异常,此处只是优化性能。
for i in rst:
if i is not None:
raise i
Loading
Loading