437 lines
14 KiB
Python
437 lines
14 KiB
Python
"""
|
||
装饰器模块
|
||
|
||
提供约定优于配置的装饰器,用于自动注册组件
|
||
"""
|
||
|
||
import re
|
||
from functools import wraps
|
||
from typing import Any, Dict, List, Optional, Union, Callable
|
||
|
||
|
||
from ..utils.naming import camel_to_snake as _camel_to_snake
|
||
|
||
|
||
def route(path: str, methods: List[str] = None, **kwargs):
|
||
"""
|
||
路由装饰器
|
||
|
||
Args:
|
||
path: 路由路径
|
||
methods: HTTP 方法列表
|
||
**kwargs: 其他路由参数
|
||
"""
|
||
if methods is None:
|
||
methods = ['GET']
|
||
|
||
def decorator(func):
|
||
func.__myboot_route__ = {
|
||
'path': path,
|
||
'methods': methods,
|
||
'kwargs': kwargs
|
||
}
|
||
return func
|
||
return decorator
|
||
|
||
|
||
def get(path: str, **kwargs):
|
||
"""GET 路由装饰器"""
|
||
return route(path, methods=['GET'], **kwargs)
|
||
|
||
|
||
def post(path: str, **kwargs):
|
||
"""POST 路由装饰器"""
|
||
return route(path, methods=['POST'], **kwargs)
|
||
|
||
|
||
def put(path: str, **kwargs):
|
||
"""PUT 路由装饰器"""
|
||
return route(path, methods=['PUT'], **kwargs)
|
||
|
||
|
||
def delete(path: str, **kwargs):
|
||
"""DELETE 路由装饰器"""
|
||
return route(path, methods=['DELETE'], **kwargs)
|
||
|
||
|
||
def patch(path: str, **kwargs):
|
||
"""PATCH 路由装饰器"""
|
||
return route(path, methods=['PATCH'], **kwargs)
|
||
|
||
|
||
def cron(cron_expression: str, enabled: Optional[bool] = None, all_workers: bool = False, **kwargs):
|
||
"""
|
||
Cron 任务装饰器
|
||
|
||
支持在函数和类方法中使用:
|
||
- 函数:@cron("0 0 * * *") 或 @cron("0 0 * * * *")
|
||
- 类方法:在类的方法上使用 @cron("0 0 * * *")
|
||
|
||
Args:
|
||
cron_expression: Cron 表达式
|
||
- 标准5位格式:分 时 日 月 周(如 "0 0 * * *" 表示每小时)
|
||
- 6位格式:秒 分 时 日 月 周(如 "0 0 * * * *" 表示每小时,兼容旧格式)
|
||
enabled: 是否启用任务,如果为 None 则默认启用
|
||
可以通过手动获取配置来传递,例如:
|
||
from myboot.core.config import get_config
|
||
enabled = get_config('jobs.heartbeat.enabled', True)
|
||
all_workers: 多 worker 模式下是否在每个 worker 进程都注册执行
|
||
默认 False(仅 primary worker 执行)
|
||
**kwargs: 其他任务参数
|
||
"""
|
||
def decorator(func):
|
||
func.__myboot_job__ = {
|
||
'type': 'cron',
|
||
'cron': cron_expression,
|
||
'enabled': enabled,
|
||
'all_workers': all_workers,
|
||
'kwargs': kwargs
|
||
}
|
||
return func
|
||
return decorator
|
||
|
||
|
||
def interval(seconds: int = None, minutes: int = None, hours: int = None, enabled: Optional[bool] = None, all_workers: bool = False, **kwargs):
|
||
"""
|
||
间隔任务装饰器
|
||
|
||
支持在函数和类方法中使用:
|
||
- 函数:@interval(seconds=60)
|
||
- 类方法:在类的方法上使用 @interval(seconds=60)
|
||
|
||
Args:
|
||
seconds: 秒数
|
||
minutes: 分钟数
|
||
hours: 小时数
|
||
enabled: 是否启用任务,如果为 None 则默认启用
|
||
可以通过手动获取配置来传递,例如:
|
||
from myboot.core.config import get_config
|
||
enabled = get_config('jobs.heartbeat.enabled', True)
|
||
all_workers: 多 worker 模式下是否在每个 worker 进程都注册执行
|
||
默认 False(仅 primary worker 执行)
|
||
**kwargs: 其他任务参数
|
||
"""
|
||
def decorator(func):
|
||
interval_value = seconds or (minutes * 60) or (hours * 3600)
|
||
func.__myboot_job__ = {
|
||
'type': 'interval',
|
||
'interval': interval_value,
|
||
'enabled': enabled,
|
||
'all_workers': all_workers,
|
||
'kwargs': kwargs
|
||
}
|
||
return func
|
||
return decorator
|
||
|
||
|
||
def once(run_date: str = None, enabled: Optional[bool] = None, all_workers: bool = False, **kwargs):
|
||
"""
|
||
一次性任务装饰器
|
||
|
||
支持在函数和类方法中使用:
|
||
- 函数:@once('2025-12-31 23:59:59')
|
||
- 类方法:在类的方法上使用 @once('2025-12-31 23:59:59')
|
||
|
||
Args:
|
||
run_date: 运行日期
|
||
enabled: 是否启用任务,如果为 None 则默认启用
|
||
可以通过手动获取配置来传递,例如:
|
||
from myboot.core.config import get_config
|
||
enabled = get_config('jobs.heartbeat.enabled', True)
|
||
all_workers: 多 worker 模式下是否在每个 worker 进程都注册执行
|
||
默认 False(仅 primary worker 执行)
|
||
**kwargs: 其他任务参数
|
||
"""
|
||
def decorator(func):
|
||
func.__myboot_job__ = {
|
||
'type': 'once',
|
||
'run_date': run_date,
|
||
'enabled': enabled,
|
||
'all_workers': all_workers,
|
||
'kwargs': kwargs
|
||
}
|
||
return func
|
||
return decorator
|
||
|
||
|
||
# @service/@client 支持的生命周期范围
|
||
# - singleton: 单例(默认),每个 worker 进程内一个实例
|
||
# - request: 请求级,基于 contextvars(ContextLocalSingleton),
|
||
# 同一 HTTP 请求(asyncio 任务)内同一实例,跨请求不同实例
|
||
# - factory: 每次解析都创建新实例
|
||
_VALID_SCOPES = {'singleton', 'request', 'factory'}
|
||
|
||
|
||
def _validate_scope(scope: str, decorator_name: str) -> None:
|
||
"""校验 scope 参数,装饰期即报错,避免拼写错误静默回退为单例"""
|
||
if scope not in _VALID_SCOPES:
|
||
raise ValueError(
|
||
f"@{decorator_name} 不支持的 scope: {scope!r},"
|
||
f"可选值: {sorted(_VALID_SCOPES)}"
|
||
)
|
||
|
||
|
||
def service(name: str = None, scope: str = 'singleton', **kwargs):
|
||
"""
|
||
服务装饰器
|
||
|
||
Args:
|
||
name: 服务名称
|
||
scope: 生命周期范围(singleton/request/factory,默认 singleton)
|
||
- 'singleton': 每个 worker 进程内单例
|
||
- 'request': 每个请求(asyncio 任务上下文)内单例
|
||
- 'factory': 每次解析创建新实例
|
||
**kwargs: 其他服务参数
|
||
"""
|
||
_validate_scope(scope, 'service')
|
||
|
||
def decorator(cls):
|
||
cls.__myboot_service__ = {
|
||
'name': name or _camel_to_snake(cls.__name__),
|
||
'scope': scope,
|
||
'kwargs': kwargs
|
||
}
|
||
return cls
|
||
return decorator
|
||
|
||
|
||
def model(name: str = None, **kwargs):
|
||
"""
|
||
模型装饰器
|
||
|
||
Args:
|
||
name: 模型名称
|
||
**kwargs: 其他模型参数
|
||
"""
|
||
def decorator(cls):
|
||
cls.__myboot_model__ = {
|
||
'name': name or _camel_to_snake(cls.__name__),
|
||
'kwargs': kwargs
|
||
}
|
||
return cls
|
||
return decorator
|
||
|
||
|
||
def client(name: str = None, scope: str = 'singleton', **kwargs):
|
||
"""
|
||
客户端装饰器
|
||
|
||
Args:
|
||
name: 客户端名称
|
||
scope: 生命周期范围(singleton/request/factory,默认 singleton)
|
||
- 'singleton': 每个 worker 进程内单例,注册到 app.clients
|
||
- 'request': 每个请求(asyncio 任务上下文)内单例,
|
||
通过 DI 容器按需解析,不出现在 app.clients
|
||
- 'factory': 每次解析创建新实例,不出现在 app.clients
|
||
**kwargs: 其他客户端参数
|
||
"""
|
||
_validate_scope(scope, 'client')
|
||
|
||
def decorator(cls):
|
||
cls.__myboot_client__ = {
|
||
'name': name or _camel_to_snake(cls.__name__),
|
||
'scope': scope,
|
||
'kwargs': kwargs
|
||
}
|
||
return cls
|
||
return decorator
|
||
|
||
|
||
def component(
|
||
name: str = None,
|
||
scope: str = 'singleton',
|
||
primary: bool = False,
|
||
lazy: bool = False,
|
||
**kwargs
|
||
):
|
||
"""
|
||
组件装饰器
|
||
|
||
用于将类注册为通用组件,支持依赖注入。
|
||
可用于任意需要托管的类(Job 实例、工具类、配置类等)。
|
||
|
||
Args:
|
||
name: 组件名称,默认使用类名的 snake_case 形式
|
||
scope: 生命周期范围
|
||
- 'singleton': 单例模式(默认),整个应用生命周期内只创建一个实例
|
||
- 'prototype': 原型模式,每次获取时创建新实例
|
||
primary: 当按类型获取有多个匹配时,是否为首选
|
||
lazy: 是否懒加载,True 时在首次使用时才创建实例
|
||
**kwargs: 其他组件参数
|
||
|
||
Examples:
|
||
# 基本用法
|
||
@component()
|
||
class EmailHelper:
|
||
def send(self, to: str, content: str):
|
||
pass
|
||
|
||
# 带依赖注入
|
||
@component(name='redis_cache')
|
||
class RedisCache:
|
||
def __init__(self, redis_client: RedisClient):
|
||
self.redis = redis_client
|
||
|
||
# 包含定时任务的组件
|
||
@component()
|
||
class DataSyncJobs:
|
||
def __init__(self, data_service: DataService):
|
||
self.data_service = data_service
|
||
|
||
@cron("0 2 * * *")
|
||
def sync_daily(self):
|
||
self.data_service.sync()
|
||
"""
|
||
def decorator(cls):
|
||
cls.__myboot_component__ = {
|
||
'name': name or _camel_to_snake(cls.__name__),
|
||
'scope': scope,
|
||
'primary': primary,
|
||
'lazy': lazy,
|
||
'kwargs': kwargs
|
||
}
|
||
return cls
|
||
return decorator
|
||
|
||
|
||
def middleware(
|
||
name: str = None,
|
||
order: int = 0,
|
||
path_filter: Union[str, List[str]] = None,
|
||
methods: List[str] = None,
|
||
condition: Callable = None,
|
||
**kwargs
|
||
):
|
||
"""
|
||
中间件装饰器
|
||
|
||
Args:
|
||
name: 中间件名称
|
||
order: 执行顺序,数字越小越先执行(默认 0)
|
||
path_filter: 路径过滤,支持字符串、字符串列表或正则表达式模式
|
||
例如: '/api/*', ['/api/*', '/admin/*']
|
||
methods: HTTP 方法过滤,如 ['GET', 'POST'](默认 None,处理所有方法)
|
||
condition: 条件函数,接收 request 对象,返回 bool 决定是否执行中间件
|
||
**kwargs: 其他中间件参数
|
||
|
||
Examples:
|
||
@middleware(order=1, path_filter='/api/*')
|
||
def api_middleware(request, next_handler):
|
||
return next_handler(request)
|
||
|
||
@middleware(order=2, methods=['POST', 'PUT'])
|
||
def post_middleware(request, next_handler):
|
||
return next_handler(request)
|
||
"""
|
||
def decorator(func):
|
||
func.__myboot_middleware__ = {
|
||
'name': name or func.__name__,
|
||
'order': order,
|
||
'path_filter': path_filter,
|
||
'methods': methods,
|
||
'condition': condition,
|
||
'kwargs': kwargs
|
||
}
|
||
return func
|
||
return decorator
|
||
|
||
|
||
def _worker_hook(event: str, order_or_func):
|
||
"""on_worker_start/on_worker_stop 的公共实现
|
||
|
||
同时支持两种用法:
|
||
@on_worker_start (裸装饰器)
|
||
@on_worker_start(order=1) (带参数)
|
||
"""
|
||
if callable(order_or_func):
|
||
# 裸装饰器用法:@on_worker_start
|
||
func = order_or_func
|
||
func.__myboot_worker_hook__ = {'event': event, 'order': 0}
|
||
return func
|
||
|
||
order = order_or_func
|
||
|
||
def decorator(func):
|
||
func.__myboot_worker_hook__ = {'event': event, 'order': order}
|
||
return func
|
||
return decorator
|
||
|
||
|
||
def on_worker_start(order: Union[int, Callable] = 0):
|
||
"""
|
||
Worker 启动钩子装饰器
|
||
|
||
被装饰的模块级函数在每个 worker 进程的 lifespan 启动阶段触发一次
|
||
(startup_hooks 之后、调度器启动之前)。单 worker 模式下也触发一次,
|
||
语义一致。支持同步和异步函数。
|
||
|
||
钩子函数不接收参数,可通过 myboot.core.application.app() 读取
|
||
worker_id / is_primary_worker 等信息。
|
||
|
||
Args:
|
||
order: 执行顺序,数字越小越先执行(默认 0)
|
||
|
||
Examples:
|
||
@on_worker_start
|
||
def init_pool():
|
||
...
|
||
|
||
@on_worker_start(order=1)
|
||
async def warm_cache():
|
||
...
|
||
"""
|
||
return _worker_hook('start', order)
|
||
|
||
|
||
def on_worker_stop(order: Union[int, Callable] = 0):
|
||
"""
|
||
Worker 停止钩子装饰器
|
||
|
||
被装饰的模块级函数在每个 worker 进程的 lifespan 关闭阶段触发一次
|
||
(调度器停止之后、shutdown_hooks 之前)。支持同步和异步函数。
|
||
|
||
注意:Windows 多 worker 模式下父进程通过 terminate()(硬终止)清理
|
||
worker 进程,lifespan 关闭阶段可能不会执行,因此 stop 钩子在
|
||
Windows 上不保证触发。
|
||
|
||
Args:
|
||
order: 执行顺序,数字越小越先执行(默认 0)
|
||
"""
|
||
return _worker_hook('stop', order)
|
||
|
||
|
||
def rest_controller(base_path: str, **kwargs):
|
||
"""
|
||
REST 控制器装饰器
|
||
|
||
用于标记 REST 控制器类,为类中的方法提供基础路径。
|
||
类中的方法需要显式使用 @get、@post、@put、@delete、@patch 等装饰器才会生成路由。
|
||
|
||
路径合并规则:
|
||
- 方法路径以 // 开头:作为绝对路径使用(去掉一个 /)
|
||
- 方法路径以 / 开头:去掉开头的 / 后追加到基础路径
|
||
- 方法路径不以 / 开头:直接追加到基础路径
|
||
|
||
示例:
|
||
@rest_controller('/api/reports')
|
||
class ReportController:
|
||
@post('/generate') # 最终路径: POST /api/reports/generate
|
||
def create_report(self, report_type: str):
|
||
return {"message": "报告生成任务已创建"}
|
||
|
||
@get('/status/{job_id}') # 最终路径: GET /api/reports/status/{job_id}
|
||
def get_status(self, job_id: str):
|
||
return {"status": "completed"}
|
||
|
||
Args:
|
||
base_path: 基础路径
|
||
**kwargs: 其他路由参数
|
||
"""
|
||
def decorator(cls):
|
||
cls.__myboot_rest_controller__ = {
|
||
'base_path': base_path.rstrip('/'),
|
||
'kwargs': kwargs
|
||
}
|
||
return cls
|
||
return decorator
|