Files
2026-07-13 12:34:57 +08:00

437 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
装饰器模块
提供约定优于配置的装饰器,用于自动注册组件
"""
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: 请求级,基于 contextvarsContextLocalSingleton),
# 同一 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