Skip to main content

🐚 apiboot

为组件化构建 API 服务而生的 Python 底层工具包

PyPI Python License Stars

零运行时业务依赖 · 全场景懒加载 · Python 3.7+ · 同步 + 异步双 API · 滚动重启守护

从配置加载到服务守护、从 MySQL/Redis 到 LLM/MinerU/ML,一个包搞定所有后端底座。

快速开始 · 模块一览 · CLI 守护进程 · 可选依赖


✨ 为什么选择 apiboot

┌─────────────────────────────────────────────────────────────────┐
│ 你的项目                                                          │
│   ├── main.py        FastAPI 路由                                 │
│   ├── .env           配置 (DB / Redis / LLM / OCR ...)             │
│   ├── models/        ORM 模型 (apiboot 自动 import 建表)            │
│   ├── tasks/         cron 任务 (apiboot 自动 register)             │
│   └── requirements   你自己选的依赖版本                            │
│                                                                 │
│ apiboot  (pip install apiboot, 仅 apscheduler 一个运行时依赖)        │
│   ├── config / logger / middlewares / error / schemas             │
│   ├── db.mysql (DB_MODELS_DIR 自动扫) / db.redis / redis.lock     │
│   ├── cron (TASKS_DIR 自动扫) / ocr / llm / torch / data / retry  │
│   └── cli (abtstart/abtstop/abtrestart/abtstatus/... + 滚动重启)   │
└─────────────────────────────────────────────────────────────────┘
痛点 apiboot 的解法
装一个工具包拖一堆用不到的依赖 默认 dependencies = [apscheduler],业务侧第三方全部按需懒加载,没装就不触发 import
sync / async 数据库 API 风格不统一 统一前缀约定:sync 用裸函数名,async 加 a 前缀,13 对 CRUD 一一对应
项目里写一堆 start.sh / stop.sh abtstart / abtstop / abtstatus / abtlist / abtlog 一个命令搞定
多 worker 进程重启会中断服务 abtrestart 默认走滚动重启(uvicorn workers 逐个 SIGTERM,前端请求 0 中断)
服务被 OOM Killer 杀后无法自动恢复 CLI 内置 supervisor:自动 fork-safe + OOM 自动重启 + 内存阈值预警 + 雪崩保护
业务 / 系统 / HTTP 三类异常混在一起 三个根异常(ApiError / ApiSystemError / HttpError) + 统一错误码常量 + http_status 让业务异常正确映射 HTTP 状态码
Pydantic 字段命名在前后端来回转换 BaseSchema 自动 snake_case ↔ camelCase
models/ / tasks/ 目录漏 import 漏建表 DB_MODELS_DIR / TASKS_DIR 配置驱动自动扫描,业务零感知

📦 安装

pip install apiboot

默认只依赖 apscheduler 一个第三方包,不会污染你的依赖图。其余可选依赖按需安装

按需安装可选依赖(只装你用得到的):

# 最小起步: FastAPI + ORM + Redis
pip install fastapi sqlalchemy pymysql redis httpx pydantic

# AI / OCR 全家桶
pip install langchain langchain-core jieba httpx

# 重试 / 加密 / 雪花 ID
pip install tenacity cryptography snowflake-id

# ML / 数据处理
pip install torch pandas numpy scikit-learn

# 文档解析
pip install pymupdf python-docx python-pptx openpyxl xlrd

# CLI 内存监控 (Linux 用 /proc 不需要, macOS / Windows 必需)
pip install psutil

💡 提示:apiboot 的所有第三方依赖都走懒加载 —— pip install apiboot 之后 import apiboot 永远不会因为缺失依赖而失败。


🚀 快速开始

下面六个例子覆盖 90% 的日常场景,每个都能独立运行。

① 一行启动:配置 + 日志

from apiboot import config, logger

logger.info("服务启动")
print(config.DB_HOST)     # 自动读取 .env
print(config.DEBUG)        # 自动类型转换: 'true' → True

② FastAPI 统一响应

from fastapi import FastAPI
from apiboot.middlewares import JsonResponseMiddleware, ReqResLoggingMiddleware

app = FastAPI()
app.add_middleware(ReqResLoggingMiddleware)   # 日志外层
app.add_middleware(JsonResponseMiddleware)    # 响应包装内层

@app.get("/users/{uid}")
def get_user(uid: str):
    return {"name": "张三", "age": 18}
    # → 自动包装: {"code": 200, "message": "成功", "data": {...}}

③ 统一错误体系

from apiboot.error import BusinessError
from apiboot.error.code import USER_NOT_FOUND

class UserNotFoundError(BusinessError):
    default_code = USER_NOT_FOUND.code      # 30001

raise UserNotFoundError("用户不存在")         # code=30001, HTTP 200

④ MySQL CRUD(sync + async 各 13 对)

from apiboot.db.mysql import init_db, get_db, save, query_page

init_db()                                   # 启动时建表 + 自动 import models/ 目录
with get_db() as session:
    save(User(name="张三"), session=session)
    users, total = query_page(User, page_num=1, page_size=20, session=session)
# 异步版 (FastAPI async def 路由)
from apiboot.db.mysql import ainit_db, aget_db, asave, aquery_page

自动建表依赖 DB_MODELS_DIR(默认 "models"):目录不存在时静默跳过,走老的"用户自己 import model"逻辑。

⑤ Redis 分布式锁(Redisson 风格)

from apiboot.db.redis import init_redis
from apiboot.db.redis.lock import with_lock, LockAcquireError

init_redis()
try:
    with with_lock("order:123", ttl=30, timeout=5) as lock:
        do_critical()                      # Lua CAS 释放 + watchdog 自动续期
except LockAcquireError:
    logger.warning("5 秒内没拿到锁, 跳过")

⚠️ 务必 release 或用上下文管理器:watchdog 后台线程会一直续期,业务异常路径也要 finally 释放,否则锁 key 长时间不被释放。

⑥ LLM 接入

from apiboot.llm import init_model
from apiboot.llm.agent import ChatAgent

llm = init_model()                          # 从 .env 读 LLM_MODEL / LLM_API_KEY / LLM_BASE_URL
agent = ChatAgent(llm)

🛡️ CLI 守护进程管理

装上 apiboot 后,6 个全局命令直接可用,无需写 start.sh / stop.sh:

cd /path/to/your-project           # 有 main.py + .env 的目录

abtstart                            # 启动 (后台 + supervisor 守护)
abtstatus                           # 查看状态
abtstop                             # 停止
abtrestart                          # 重启 (默认走滚动重启)
abtlist                             # 列出所有 abtstart 启动的项目
abtlog                              # tail -f .abt.log

或子命令形式:abt start / stop / restart / status / list / log

常用参数

abtstart --module api.main:app     # 自定义入口
abtstart --port 9000 --host 127.0.0.1
abtstart --workers 4                # 多 worker (uvicorn multiprocessing)
abtstart --reload                   # 开发模式
abtstart --env production           # 加载 .env.production
abtstart --clean                    # 端口被占时强杀

# Supervisor 内存 / 重启阈值
abtstart --mem-warn-ratio 0.80     # RSS 达到 80% 时 WARN
abtstart --mem-hard-ratio 0.95     # RSS 达到 95% 时主动 SIGTERM (防 OOM Killer)
abtstart --max-restarts 5           # 短窗口最大重启次数
abtstart --restart-window 300       # 统计窗口秒数
abtstart --no-supervisor            # 关闭 OOM 守护 (回到旧行为)

# 重启策略 (abtrestart)
abtrestart                          # workers>1 默认滚动重启 (前端请求 0 中断)
abtrestart --force-reload           # 强制 stop+start (改了代码后 reload master)
abtrestart --graceful-timeout 60    # 滚动重启每个 worker 最多等 60s
abtrestart --force                  # 滚动重启跳过 graceful, 直接 SIGKILL worker

关键能力

  • 🔍 自动发现 .env + main.py / app.py / server.py
  • 🌍 多环境 APP_ENV=production 自动加载 .env.production
  • 🚧 端口预检 启动前检测占用,--clean 可强杀
  • 🛡️ OOM 守护(默认开启) 子进程被 OOM Killer 杀,自动重启
  • 📊 内存阈值 RSS 达到 mem-hard-ratio 时主动 SIGTERM,避免被 OOM Killer 突然终止
  • 🚨 雪崩保护 短窗口内重启次数超限,自动放弃
  • 👷 多 worker --workers N 切到 uvicorn multiprocessing
  • 🔁 滚动重启 abtrestartworkers>1 时默认走滚动重启,worker 逐个 SIGTERM → uvicorn master 自动 fork 新 worker 顶上 → 验证就绪 → 处理下一个,整组服务在滚动过程中始终有 N 个 worker 在跑,前端请求 0 中断
  • 🧬 fork-safe 子进程标记 APIBOOT_FORKED=1,业务代码可借此 dispose + 重建 SQLAlchemy engine,避免多 worker 共享父进程 FD 导致 "MySQL server has gone away"
  • 📋 全局 registry abt list 一台机器混多个项目也清晰可查
  • 🧹 残留进程清理 自动杀掉上次没杀干净的 uvicorn 孤儿

⚠️ --workers N 与 OOM 边界:supervisor 的 RSS 防御只对 uvicorn master 可见,看不到后代 worker 的内存。单个 worker 被 OOM Killer 杀由 uvicorn master 自己重启;整套同归于尽才由 supervisor 重启。要管每个 worker 内存,用 systemd MemoryMax= 或 K8s resources.limits.memory,不要指望 supervisor。

⚠️ 滚动重启不 reload 代码:uvicorn worker 是 master 的 fork,继承 master 的代码映像。改了 main.py 等业务文件必须用 --force-reload 走 stop+start 让 master 重启,新 worker 才会 fork 自新 master。滚动重启只 graceful 重启 worker(重置进程内状态、重读 .env 等)。

abtrestart 行为矩阵

场景 路径 中断窗口
workers > 1(默认) 滚动重启 worker 0(整组滚动,服务始终可用)
workers > 1 + 改模块 / env stop + start 几秒 ~ 30s(不可避免)
workers == 1 stop + start 几秒 ~ 30s(不可避免)
任何场景 + --force-reload stop + start 几秒 ~ 30s

滚动重启需要项目装 psutil,Linux 用 /proc 也可不装(走 stdlib 实现)。


🧩 模块一览

apiboot/
├── config/        # .env 加载器 + 类型化访问器 (env.py)
├── log/           # 控制台 + 文件双输出 logger
├── middlewares/   # FastAPI 中间件 (统一响应 + 请求日志 + 大响应体保护)
├── error/         # 三个根异常 + 错误码常量 + http_status 支持
├── schemas/       # BaseSchema (驼峰) + JsonResult + PageReq
├── db/
│   ├── mysql/     # sync + async CRUD (各 13 对) + DB_MODELS_DIR 自动扫
│   ├── redis/     # sync + async KV (含 REDIS_MAX_CONNECTIONS)
│   └── redis/lock # Redisson 风格分布式锁
├── cron/          # 定时任务 (装饰器自动注册 + TASKS_DIR 自动扫)
├── llm/           # LangChain chat model + Agent
│   └── model/     # init_model 实现拆到 base.py
├── ocr/           # MinerU API 异步客户端
├── torch/         # CUDA / MPS / CPU 设备探测
├── data/          # pandas / sklearn 常用工具 (大量扩展)
├── retry/         # tenacity 懒加载透传 (PEP 562 lazy module)
├── utils/         # 字符串 / 时间 / JSON / HTTP / 加密 / ...
└── cli/           # abtstart / abtstop / abtrestart / ... (+ 滚动重启)

顶层入口

符号 作用
apiboot.config 一行拿到 .env 配置(自动类型转换)
apiboot.logger 一行拿到 logger(控制台 + 文件双输出)

FastAPI 中间件(apiboot.middlewares)

组件 功能
JsonResponseMiddleware 路由返回值 + 异常统一包装成 {code, message, data}
ReqResLoggingMiddleware 请求 / 响应日志(每请求两行,含敏感字段脱敏)

特性:

  • 路由返回值 / ApiError / HTTPException / 未预期异常 → 自动包装
  • HTTP 状态码映射:业务异常走异常自身的 http_status 类属性(默认 200), 系统异常默认 500;子类可声明 http_status = 401 把 401 业务异常正确返回
  • SSE / 流式接口自动跳过(识别 StreamingResponse)
  • 非 JSON 响应(文件下载、HTML 页面)原样放行
  • 已包装过的响应(顶层已有 code/message/data)原样放行
  • 大响应体保护:响应超过 MAX_BODY_SIZE (默认 10MB) 直接 pass-through, 避免 OOM + 不必要的 body 解析
  • 双层 try-except:异常路径里中间件自身处理失败会用 INTERNAL_ERROR_CODE 兜底, 永远不会让"框架崩了"代替"业务异常"
  • Datetime 自动格式化为 "%Y-%m-%d %H:%M:%S"(可配置)
  • 响应 body 超过 LOG_MAX_CHARS 自动截断;password/token/secret/api_key 自动脱敏

跳过某些路径:

app.add_middleware(JsonResponseMiddleware, exclude_paths=["/api/chat/stream"])
# 精确匹配 + 前缀匹配: "/api/llm" → 匹配 /api/llm, /api/llm/chat

业务异常映射 HTTP 状态码:

from apiboot.error import BusinessError
from apiboot.error.code import UNAUTHORIZED

class UnauthorizedError(BusinessError):
    default_code = UNAUTHORIZED.code   # 11001
    http_status = 401                  # 同时覆盖 HTTP 状态码(默认 200)

@app.get("/me")
def me():
    raise UnauthorizedError("登录已过期")
    # → HTTP 401 + {"code": 11001, "message": "登录已过期", "data": null}

错误体系(apiboot.error)

Exception
└─── ApiError ─────────── BusinessError (别名)
    ├── HttpError       # 外部 HTTP 调用失败
    └── ApiSystemError  # 系统异常(HTTP 500,业务侧一般不直接抛)

为什么没有 SystemError 别名:Python 内建 SystemError(解释器内部错误用)如果被遮蔽, 用户的 except SystemError:永远捕获不到真正的解释器内部错误。本模块只导出 ApiSystemError,历史用了 from apiboot.error import SystemError 的请改 ApiSystemError

根类 默认 code 默认 HTTP 用途
ApiError / BusinessError 20000 200 业务侧异常
HttpError 20000 200 外部 HTTP 调用失败
ApiSystemError 10099 500 系统侧异常

子类可同时覆盖 default_codehttp_status,让业务异常正确映射到 HTTP 状态码 (如 UnauthorizedError.default_code=11001, http_status=401)。 没声明 http_status 时,业务异常一律 200,系统异常一律 500。

错误码常量(apiboot.error.code):

码段 常量
成功 SUCCESS(200)
系统 10xxx UNKNOWN_ERROR / PARAM_ERROR / INTERNAL_ERROR / SYSTEM_ERROR
认证 11xxx UNAUTHORIZED / TOKEN_INVALID / PERMISSION_DENIED
数据 12xxx NOT_FOUND / DATA_EXISTS / DATA_VALIDATION_FAILED
业务 2xxxx BUSINESS_ERROR / OPERATION_FAILED / STATE_ILLEGAL
用户 30xxx USER_NOT_FOUND / USER_PASSWORD_ERROR

升级注意:本模块移除了 SystemError 别名(避免遮蔽 Python 内建 SystemError)。 历史用了 from apiboot.error import SystemError 的代码,改成:

from apiboot.error import ApiSystemError

类实例化和抛异常的语法完全不变(raise ApiSystemError("...")), isinstance 判断也兼容(因为 ApiSystemError 是类本身,不是别名)。

Schema(apiboot.schemas)

功能 依赖
BaseSchema pydantic Schema 基类,自动 snake_case ↔ camelCase pydantic ≥ 2.0
JsonResult[T] dataclass 实现的统一响应 {code, message, data} stdlib
PageReq 分页请求基类(page_num ≥ 1,page_size 1–500) pydantic
# BaseSchema 自动驼峰
class UserSchema(BaseSchema):
    user_id: int
    user_name: str

u = UserSchema(user_id=1, user_name="alice")
u.model_dump(by_alias=True)    # → {"userId": 1, "userName": "alice"}

# JsonResult 统一响应
from apiboot.schemas.json_result import JsonResult
from apiboot.error.code import NOT_FOUND
return JsonResult.fail(code=NOT_FOUND)
# → JsonResult(code=12001, message="资源不存在")

数据库(apiboot.db)

MySQL CRUD(26 个函数)

类别 函数
save / save_batch
update / update_batch / save_or_update / save_or_update_batch(MySQL ON DUPLICATE KEY)
query / query_primary_key / query_page(rows + total)— 均支持 fields=[...]
delete / delete_batch / soft_delete / soft_delete_batch(UPDATE deleted_at = NOW())

异步版一一对应,加 a 前缀:asave / aquery_page / asoft_delete

自动建表 + models 目录扫描

项目结构
├── models/
│   ├── __init__.py
│   ├── user.py          # class User(Base): ...
│   └── order.py         # class Order(Base): ...
└── .env  →  DB_MODELS_DIR=models   # 默认值, 目录不存在静默跳过
from apiboot.db.mysql import init_db, DeclarativeBase

class Base(DeclarativeBase):
    pass

init_db(Base)      # 启动时自动 import models/ 下所有 .py + create_all
  • 每个 engine 独立标记 lazy 建表状态(WeakKeyDictionary),多 engine 场景互不干扰
  • DB_MODELS_DIR= 显式禁用扫描
  • 单个 model 文件 import 失败仅 WARN,不阻断其他文件

多 engine 支持

# 测试场景:换 engine 后,期望重新跑 lazy 建表 —— 现在做到了
engine_a = create_engine("mysql://a")
engine_b = create_engine("mysql://b")
lazy_ensure_tables_sync(engine_a, Base)   # 跑
lazy_ensure_tables_sync(engine_b, Base)   # 也跑 (之前 process-global bool 会跳过)

Redis KV

init_redis / close_redis / get_redis
set_value / get_value / delete / exists / expire / mget / set_json / get_json / ping
get_value_raw / read_json / scanned_keys / prefixed_pattern
ainit_redis / aclose_redis / aget_redis / aget_redis_session
aset_value / aget_value / adelete / aexists / aexpire / amget / aset_json / aget_json / aping

支持 redis 3.5+(仅 sync),4.2+(sync + async),5.0+ / 6.0+ 推荐。

集群模式:REDIS_CLUSTER_NODES=host1:port,host2:port,host3:port(自动启用)。

连接池大小:REDIS_MAX_CONNECTIONS=N(高并发 FastAPI 建议 100~200,默认 redis-py 自定)。

Redis 分布式锁(apiboot.db.redis.lock)

类型 API
同步 RedisLock / LockAcquireError / acquire_lock / with_lock / locked
异步 AsyncRedisLock / AsyncLockAcquireError / aacquire_lock / awith_lock / alocked
常量 DEFAULT_TTL=30 / DEFAULT_RETRY_INTERVAL=0.1 / DEFAULT_RENEW_RATIO=1/3

Redisson 风格保证:

  • 🛡️ Lua compare-and-delete — 只删自己 token 的锁,防止误删别人的锁
  • 🔄 watchdog 自动续期 — 后台线程(sync)/ asyncio.Task(async),默认开启
  • 阻塞等锁 — spin 循环 + 超时控制

定时任务(apiboot.cron)

from apiboot.cron import scheduled_job, start_scheduler

@scheduled_job("cron", hour=6, minute=0)
def _daily_pipeline_6am():
    print("早上 6 点跑批")

if __name__ == "__main__":
    start_scheduler()    # 无需传扫描路径 —— 装饰器已自动注册

异步版(FastAPI lifespan):

from apiboot.cron import async_scheduled_job, astart_scheduler, astop_scheduler

@async_scheduled_job("cron", hour=6, minute=0)
async def _daily_pipeline_async():
    print("async 跑批")

@asynccontextmanager
async def lifespan(app):
    await astart_scheduler()
    yield
    await astop_scheduler()

约定目录自动扫描(TASKS_DIR)

项目结构
├── tasks/
│   ├── sync_data.py    # def register(scheduler): scheduler.add_job(...)
│   └── report.py
└── .env  →  TASKS_DIR=tasks    # 默认值, 目录不存在静默跳过
# start_scheduler 启动时会自动调 register_jobs_from_dir(tasks_dir)
# 等价于在 main.py 里手动写 register_jobs_from_dir("tasks", scheduler_obj)
API 说明
同步 scheduled_job / start_scheduler / stop_scheduler / get_scheduler / scheduler
异步 async_scheduled_job / ainit_scheduler / astart_scheduler / astop_scheduler / scheduler_async
约定扫描 register_jobs_from_dir / register_jobs_from_files(与传统 def register(scheduler) 风格兼容)
配置 helper build_job_defaults / default_timezone / is_scheduler_enabled

LLM / Agent(apiboot.llm)

模块 功能 依赖
llm.init_model LangChain chat model 一键构造(自动派发 provider) langchain ≥ 1.0
llm.agent.ChatAgent 流式聊天 Agent 封装(适配 FastAPI StreamingResponse) langchain
data.utils.jieba_utils jieba 中文分词懒加载封装 jieba
from apiboot.data.utils.jieba_utils import lcut, load_userdict

load_userdict("./jieba自定义词典.txt")
words = lcut("小明毕业于北京大学计算机系")
# → ['小明', '毕业', '于', '北京大学', '计算机系']

迁移说明: 原 apiboot.llm.utils.jieba_utils 已迁移到 apiboot.data.utils.jieba_utils。 旧路径下不再保留兼容 shim, 项目内无外部引用, 直接迁移。

📦 模块组织:init_model 的实现拆到 apiboot.llm.model.base,apiboot.llm.model.__init__ 仅做导出。业务方 from apiboot.llm import init_model 不受影响。

OCR(apiboot.ocr)

import asyncio
from apiboot.ocr import MinerU

client = MinerU()                                       # 从 .env 读 MINERU_URL
md = asyncio.run(client.parse_file("/path/to/report.pdf"))
类 / 函数 功能
MinerU MinerU API 异步客户端(PDF / 图片 / Office → markdown)
MinerUAPIError MinerU 调用失败的异常(含 status_code / url / method)
mineru_is_supported 判断文件扩展名是否被 MinerU 支持
SUPPORTED_EXTENSIONS / UNSUPPORTED_EXTENSIONS 支持 / 不支持的扩展名常量

支持格式:.pdf / .jpg / .jpeg / .png / .gif / .bmp / .webp / .tiff / .tif / .xlsx / .docx / .pptx / .ofd(.xls 不支持)。

后端可配:MINERU_BACKEND=hybrid-engine(默认,平衡)/ vlm-engine(精度高,速度慢);MINERU_EFFORT=medium / high

数据 / ML 工具

apiboot.torch

函数 功能 依赖
get_torch_device() 返回当前最优 torch.device(cuda > mps > cpu) torch
get_device_info() 返回详细探测 dict torch

apiboot.data.utils.pd_utils

分类 函数
空值处理 drop_empty_rows / fill_empty / fillna_with_strategy / ffill_column / bfill_column / missing_summary
类型转换 safe_to_numeric / safe_to_datetime / safe_to_categorical
去重 drop_duplicate_rows
字符串 normalize_string_column / truncate_string_column / extract_numbers_from_string / split_column / contains_pattern
列操作 select_columns / rename_columns / drop_columns / reorder_columns / cast_columns / move_column
日期处理 extract_date_parts / fill_missing_dates
数值处理 clip_outliers_iqr / bin_numeric_column
断言 assert_columns_exist / assert_no_nulls / assert_unique
报告 / 统计 value_counts_pct / describe_extended
重塑 / 抽样 melt_to_long / pivot_to_wide / stratified_sample / groupby_agg
I/O safe_read_csv
通用 df_to_dict

apiboot.data.utils.sklean_utils

分类 函数
数据缩放 standard_scale / minmax_scale / robust_scale / maxabs_scale
编码 label_encode / onehot_encode
填充缺失 simple_impute
模型评估 classification_metrics / regression_metrics / confusion_matrix_df / cross_validate
特征 polynomial_features
数据切分 train_test_split / train_val_test_split / kfold_split / stratified_kfold_split
模型持久化 save_model / load_model(用 joblib)
重采样 oversample_minority / undersample_majority
from apiboot.torch import get_torch_device, get_device_info
from apiboot.data.utils import train_test_split, standard_scale
from apiboot.data.utils.pd_utils import drop_empty_rows, missing_summary

device = get_torch_device()               # torch.device("mps") / "cuda" / "cpu"
model.to(device)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# sklearn 工具(走 joblib 持久化,不要用 pickle)
from apiboot.data.utils.sklean_utils import save_model, load_model
save_model(model, "model.joblib")
model = load_model("model.joblib")

重试(apiboot.retry)

Tenacity 的懒加载透传,行为完全等价:

分类 符号
核心 retry / Retrying / AsyncRetrying / RetryError / TryAgain / NO_RESULT
停止策略 stop_after_attempt / stop_after_delay / stop_before_delay / stop_any / stop_all / stop_never / stop_when_event_set
等待策略 wait_fixed / wait_random / wait_random_exponential / wait_incrementing / wait_exponential / wait_exponential_jitter / wait_full_jitter / wait_combine / wait_chain / wait_none / wait_exception
重试条件 retry_if_result / retry_if_not_result / retry_if_exception / retry_if_exception_type / retry_if_not_exception_type / retry_if_exception_cause_type / retry_if_exception_message / retry_if_not_exception_message / retry_unless_exception_type / retry_any / retry_all / retry_always / retry_never
回调钩子 before_log / after_log / before_sleep / before_sleep_log / before_nothing / after_nothing / before_sleep_nothing

📦 实现:本模块用 PEP 562 lazy module(__getattr__ + __dir__)实现按需导出,Python 3.7+ 标准 pattern。业务方 from apiboot.retry import ... 无感知。

from apiboot.retry import retry, stop_after_attempt, wait_fixed, retry_if_exception_type

@retry(stop=stop_after_attempt(3), wait=wait_fixed(1), retry=retry_if_exception_type(ConnectionError))
def fetch():
    ...

工具集合(apiboot.utils)

顶层一行导入(纯 stdlib)

分类 函数
路径与文件 get_project_root / get_parent_path / ensure_dir / path_join / normalize_path / get_path_segments / to_relative_path / to_absolute_path / is_relative_to / change_extension / safe_join / find_executable / get_file_type / get_file_name / get_file_stem / get_file_dir / get_file_size / get_modified_time / get_created_time / file_exists / is_file / is_directory / read_text / read_text_safe / write_text / read_bytes / write_bytes / read_lines / write_lines / delete_file / copy_file / move_file / touch / list_files / list_dirs
对象 / 字典 obj_to_dict / dict_to_obj / obj_is_null / obj_is_not_null / obj_get_attr / obj_filter_none / obj_pick / obj_omit / obj_merge / obj_deep_copy / is_dataclass / is_pydantic_model
字符串 str_len / str_is_empty / str_is_not_empty / str_is_blank / str_is_not_blank / str_preview / to_snake_case / to_camel_case / to_pascal_case / to_kebab_case / truncate / truncate_middle / pad_left / pad_right / pad_center / collapse_whitespace / strip_chars / remove_all_whitespace / mask_email / mask_phone / mask_id_card / is_chinese / contains_chinese / is_digits / is_ascii / is_uuid / contains_any / contains_all / default_if_empty / default_if_blank / slugify / reverse_str / repeat_str / remove_prefix / remove_suffix / generate_uuid
日期时间 now / today / yesterday / timestamp / timestamp_ms / timestamp_us / now_utc / timestamp_to_datetime / timestamp_to_str / format_time / parse_time / try_parse_datetime / parse_to_timestamp / start_of_day / end_of_day / start_of_month / end_of_month / add_days / add_hours / add_minutes / add_seconds / add_months / add_years / is_same_day / is_today / is_yesterday / days_between / hours_between / format_iso / parse_iso / to_utc / to_local / humanize_duration / is_leap_year / days_in_month
JSON to_json_str / from_json_str / safe_from_json / to_json_bytes / from_json_bytes / load_json_file / save_json_file / pretty_json / minify_json / validate_json / json_equal / jsonl_iter / jsonl_save
列表 list_len / list_is_empty / list_is_not_empty / list_is_blank / list_union / list_intersection / list_difference / list_symmetric_difference / list_contains / list_contains_any / list_contains_all / list_contains_none / list_index_of / list_distinct / list_reverse / list_sort / list_flatten / list_chunk / list_partition / list_take / list_skip / list_concat / list_push / list_compact / list_remove / list_remove_blank / list_first / list_last / list_get / list_safe / list_join / list_to_tuple / list_count
数学 add / sub / mul / div / round_to / percent / change_percent / random_int / random_float / random_choice / random_sample / random_seed / clamp / lerp / is_close / gcd / lcm / is_prime / sum_safe / mean / median / stdev
依赖探测 get_installed_version

需要第三方包的子模块

子模块 功能 可选依赖
utils.http_utils 同步 + 异步 HTTP 客户端(连接池复用 / 自动重试 / 流式下载) requests / httpx / aiohttp
utils.queue_utils.StreamQueue 异步流式队列(SSE / WebSocket 哨兵结束) stdlib
utils.obj_utils 对象 ↔ dict 互转(pydantic / SQLAlchemy / dataclass 自动识别) pydantic / sqlalchemy / sqlmodel
utils.json_utils pydantic 兼容 JSON(pydantic_to_json / pydantic_from_json 等) pydantic
utils.image_utils PDF / PPTX → 图片(PyMuPDF + LibreOffice), 含单页 / 缩略图 / base64 pymupdf + 系统 LibreOffice
utils.poi_utils PDF / PPTX / DOCX / Excel → 纯文本 pymupdf / python-docx / python-pptx / openpyxl / xlrd / pandas
utils.snowflake_utils 雪花 ID 生成器(线程安全全局单例) snowflake-id
utils.password_utils 用户密码哈希(PBKDF2)+ 字段加密(Fernet)+ HMAC + 随机 token stdlib + cryptography
utils.base_utils 懒加载工具(require_module / _get_optional_module / get_installed_version) stdlib

字符串工具(apiboot.utils.str_utils)

参考 hutool (Java) 的 :class:StringUtil 思路, 按场景分组, 全部 None 安全 (判空 / 默认值 / 搜索), 类型严格 (大小写转换 / 校验遇到非 str 抛 TypeError):

分类 函数
长度 / 判空 str_len / str_is_empty / str_is_not_empty / str_is_blank / str_is_not_blank / str_preview
大小写转换 to_snake_case / to_camel_case / to_pascal_case / to_kebab_case
截断 truncate / truncate_middle (中间省略, 适合长 ID/Hash 显示)
填充 pad_left / pad_right / pad_center
空白归一化 collapse_whitespace / strip_chars / remove_all_whitespace
掩码 (日志脱敏) mask_email / mask_phone / mask_id_card
校验 is_chinese / contains_chinese / is_digits / is_ascii / is_uuid
搜索 contains_any / contains_all
默认值 default_if_empty / default_if_blank
URL slug slugify (NFKC 归一化 + ASCII-only, 中文会被 drop, 需拼音接 pypinyin)
反转 / 重复 reverse_str / repeat_str
前后缀移除 remove_prefix / remove_suffix (兼容 3.7+)
UUID generate_uuid
from apiboot.utils import (
    to_snake_case, mask_phone, slugify,
    contains_any, default_if_blank, repeat_str,
)

to_snake_case("HelloWorld")          # → "hello_world"
mask_phone("13800138000")            # → "138****8000"
slugify("Hello World! 你好")           # → "hello-world"  (中文 drop)
contains_any("error.log", ".log", ".err")  # → True
default_if_blank(None, "N/A")        # → "N/A"
repeat_str("-", 30)                  # → "------------------------------"

列表工具(apiboot.utils.list_utils)

参考 hutool (Java) 的 :class:CollUtil 思路, 按场景分组, 全部 None 安全 / 不可变优先 (排序 / 反转 / 去重 / 移除 都返回新列表, 不修改原列表):

分类 函数
长度 / 判空 list_len / list_is_empty / list_is_not_empty / list_is_blank
集合运算 list_union / list_intersection / list_difference / list_symmetric_difference (元素需可哈希)
包含 / 搜索 list_contains / list_contains_any / list_contains_all / list_contains_none / list_index_of
排序 / 反转 / 去重 / 扁平 list_distinct (保序) / list_reverse / list_sort (不修改原列表) / list_flatten (支持 depth)
分块 / 切片 / 拆分 list_chunk / list_partition / list_take / list_skip
添加 / 删除 / 合并 list_concat / list_push / list_compact / list_remove / list_remove_blank
安全访问 list_first / list_last / list_get / list_safe
转换 / 实用 list_join / list_to_tuple / list_count
from apiboot.utils import (
    list_distinct, list_chunk, list_partition,
    list_remove_blank, list_compact, list_get,
)

list_distinct([1, 2, 2, 3, 1])              # → [1, 2, 3]  (保序)
list_chunk([1, 2, 3, 4, 5], 2)              # → [[1, 2], [3, 4], [5]]
list_partition([1,2,3,4], lambda x: x%2==0) # → ([2, 4], [1, 3])
list_remove_blank([None, "", "x", 0])       # → ["x", 0]   (None 和空白都去, 但保留 0)
list_compact([1, None, 2])                  # → [1, 2]     (只去 None)
list_get([1, 2, 3], 10, default="X")        # → "X"        (越界返回 default)

加密 / 密码工具(apiboot.utils.password_utils)

术语区分 (代码里这 3 个词含义完全不同, 别混用):

中文 参数名 用途 用在
用户密码 password 登录认证, 单向哈希 hash_password / verify_password
加密口令 passphrase 派生 Fernet key 的种子 encrypt / decrypt 系列
共享密钥 key HMAC 签名的对称密钥 hmac_sign / hmac_verify
分类 函数 依赖
用户密码哈希 hash_password / verify_password (PBKDF2-HMAC-SHA256, 200k 轮) stdlib
字段对称加密 encrypt / decrypt (Fernet, AES-128-CBC + HMAC-SHA256) cryptography
NULL 友好版 encrypt_or_none / decrypt_or_none (None/空串直接透传) cryptography
批量加解密 encrypt_dict / decrypt_dict (dict 指定字段批量加解密) cryptography
HMAC 签名 hmac_sign / hmac_verify (HMAC-SHA256, 常数时间比较) stdlib
随机 token generate_token (URL-safe base64) stdlib

典型场景: 数据库字段加密 → API 返回前端前解密

# .env
ENCRYPT_SECRET=<48 字节高熵字符串>

# 业务代码
from apiboot.config import env as _env
from apiboot.utils.password_utils import encrypt_dict, decrypt_dict

PASSPHRASE = _env.get_encrypt_secret()
ENCRYPT_FIELDS = ["phone", "id_card", "address"]

# 写库前 (model → dict 后批量加密)
db.execute(
    "INSERT INTO users ...",
    encrypt_dict(
        {"name": "张三", "phone": "13800138000", "address": "北京市..."},
        fields=ENCRYPT_FIELDS,
        passphrase=PASSPHRASE,
    ),
)

# 读出后 (DB row → dict → 批量解密 → 返回前端)
row = db.fetchone("SELECT * FROM users WHERE id = %s", uid)
return decrypt_dict(dict(row), fields=ENCRYPT_FIELDS, passphrase=PASSPHRASE)

⚠️ 运维红线:

  • 长度建议 ≥ 32 字符; 生成命令:
    ENCRYPT_SECRET=$(python -c "from apiboot.utils.password_utils import generate_token; print(generate_token(48))")
    
  • ⚠️ 丢失 = 历史加密数据永久无法恢复, 务必多处离线备份 (密码管理工具 / 加密保险柜)
  • ⚠️ 更换密钥需要批量重新加密存量数据, 务必先做灰度
  • 敏感度: ⚠️ 高 (supervisor 子进程环境变量白名单不会透传)

日志(apiboot.log)

from apiboot import logger
logger.info("hello")

特性一览:

  • 📁 默认 ./logs/<name>.log,按天切割,保留 7 天
  • 🖥️ 控制台(stdout) + 文件双输出
  • 🔒 同名 logger 幂等(多次 setup 不会重复挂 handler)
  • 📍 智能锚定日志目录:显式 log_dir > .env LOG_DIR > <项目根>/logs > ./logs
  • 🏷️ 智能解析 logger 名:显式 name > .env LOG_NAME > .env APP_NAME > "app"
  • ⚙️ .envLOG_LEVEL 配置级别(默认 INFO)
  • 🚫 .envLOG_ENABLE=false 关闭文件日志
  • 🛡️ Surrogate 安全 — 自动清洗 LLM 流式输出里偶发的未配对 UTF-16 代理对
  • 🪝 自动接管 apiboot 内部 loggercapture_internal=Trueapiboot.config.config 等子模块的日志也走统一 handler
  • 🐢 目录创建推迟到首次写入(delay=True + _LazyDirTimedRotatingFileHandler)

🔌 可选依赖矩阵

核心原则:默认 dependencies = [apscheduler]。业务侧任何 import 第三方包的代码都走懒加载,用户项目自己装什么版本,apiboot 就用什么版本

业务需求 需要装的可选依赖
HTTP 客户端(同步) requestshttpx
HTTP 客户端(异步) httpxaiohttp
MySQL ORM sqlalchemy + pymysql(同步)/ aiomysqlasyncmy(异步)
SQLModel 兼容(旧项目) sqlmodel
Redis redis(3.5+ 同步 / 4.2+ 异步)
定时任务 已自带 apscheduler(硬依赖)
重试 tenacity
FastAPI 中间件 fastapi + httpx
Pydantic Schema(BaseSchema) pydantic ≥ 2.0
Pydantic 分页(PageReq) pydantic ≥ 1.x
LLM langchain + langchain-core(≥ 1.0)
中文分词 jieba
OCR(MinerU) httpx
文档解析(PDF / Office) pymupdf / python-docx / python-pptx / openpyxl / xlrd / pandas
图片转换(PDF / PPTX → 图片) pymupdf + 系统 LibreOffice
雪花 ID snowflake-id
Fernet 对称加密 cryptography
深度学习(torch 设备探测) torch
数据处理(pandas) pandas + numpy
sklearn 工具 scikit-learn + joblib
CLI 内存检测 psutil(macOS / Windows 必需,Linux 用 /proc 不需要)

新增配置项(.env)

Key 默认 用途
DB_MODELS_DIR models init_db 启动时自动 import 的模型目录(不存在静默跳过)
TASKS_DIR tasks start_scheduler 启动时自动 register 的任务目录(不存在静默跳过)
REDIS_MAX_CONNECTIONS (redis-py 默认) Redis 连接池大小;高并发 FastAPI 建议 100~200
ENCRYPT_SECRET (空) 数据库字段加密口令(Fernet 派生种子);通过 get_encrypt_secret() 读取,建议长度 ≥ 32 字符

🛠️ 开发与测试

# 安装开发依赖
uv add --dev pytest mypy

# 跑测试
uv run pytest tests/ -v

# 发版流程
uv run python scripts/upload_pypi.py --repository testpypi  # 先 Test PyPI
uv run python scripts/upload_pypi.py                          # 正式 PyPI

📄 License

MIT © yanyue

Made with ❤️ for the Python backend community.

⬆ 回到顶部

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

apiboot-0.1.9.tar.gz (317.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

apiboot-0.1.9-py3-none-any.whl (370.6 kB view details)

Uploaded Python 3

File details

Details for the file apiboot-0.1.9.tar.gz.

File metadata

  • Download URL: apiboot-0.1.9.tar.gz
  • Upload date:
  • Size: 317.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for apiboot-0.1.9.tar.gz
Algorithm Hash digest
SHA256 2dac35ecc8686bde64aa1d2ff07afd7f80d086f922080bdd61aad10829c4af21
MD5 bfad2d437748226e2f7e423ec4003ace
BLAKE2b-256 82f9a5e768211d8cf8d00c116940e8c728c213be04b8a5b07bfa87cc878e5d79

See more details on using hashes here.

File details

Details for the file apiboot-0.1.9-py3-none-any.whl.

File metadata

  • Download URL: apiboot-0.1.9-py3-none-any.whl
  • Upload date:
  • Size: 370.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for apiboot-0.1.9-py3-none-any.whl
Algorithm Hash digest
SHA256 a1fc244a897d0e89728c31e88eaf229fb560bb94cd8e23a5e754cac1ff254571
MD5 29caf371055dcc130dfeae755ac1cc36
BLAKE2b-256 f0b176d98ad7566d7efeee414fe54cbb3cc7ef624e83fe035135be432f0d60ff

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.10

2 files

This release

0.1.9 This release

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page