Name | zWebApi JSON |
Version |
0.11.1
JSON |
| download |
home_page | None |
Summary | 一个功能丰富、开箱即用的 Python Web 框架,基于 FastAPI 构建。 |
upload_time | 2025-07-31 09:29:25 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.8 |
license | MIT |
keywords |
zwebapi
fastapi
framework
web
api
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# zWebApi
一个功能丰富、开箱即用的 Python Web 框架,基于 FastAPI 构建。它旨在通过约定优于配置的原则,简化 API 开发流程,提供自动路由、统一异常处理、日志记录和可扩展工具集。


## 目录
+ [特性](#特性)
+ [安装](#安装)
+ [快速开始](#快速开始)
- [1. 项目结构](#1-项目结构)
- [2. 创建应用 (](#2-创建应用-mainpy)`main.py`[)](#2-创建应用-mainpy)
- [3. 定义路由 (](#3-定义路由-action)`action/`[)](#3-定义路由-action)
- [4. 运行应用](#4-运行应用)
+ [核心概念](#核心概念)
- [应用创建与配置](#应用创建与配置)
- [路由自动注册](#路由自动注册)
- [路由函数签名规范](#路由函数签名规范)
- [统一响应与异常处理](#统一响应与异常处理)
* [全局异常处理](#全局异常处理)
* [自定义 ](#自定义-panic-异常)`Panic`[ 异常](#自定义-panic-异常)
- [日志记录](#日志记录)
- [工具模块](#工具模块)
+ [高级用法](#高级用法)
- [CORS 配置](#cors-配置)
- [使用框架日志](#使用框架日志)
+ [API 文档](#api-文档)
+ [贡献](#贡献)
+ [许可证](#许可证)
## 特性
+ **🚀**** 快速启动**: 基于 FastAPI 和 Uvicorn,提供异步高性能。
+ **🧭**** 自动路由注册**: 只需按约定在 `action/` 目录下组织代码,路由自动生效。
+ **🔒**** 路由签名强制**: 确保所有路由函数遵循统一的 `query`/`body` 参数规范。
+ **🛡️**** 统一异常处理**: 全局捕获异常,返回格式统一的 JSON 错误响应。
+ **🚨**** 自定义 **`Panic`** 异常**: 简单易用的自定义异常类,用于主动返回错误。
+ **📋**** 全面日志记录**: 自动记录应用启动、路由、异常等信息,支持文件和控制台输出,并提供日志查看接口。
+ **🛠️**** 可扩展工具模块**: 通过 `zWebApi.tools.*` 轻松访问和扩展框架功能。
+ **🌐**** CORS 支持**: 内置 CORS 中间件,轻松配置跨域资源共享。
+ **📦**** 易于打包和分发**: 标准 Python 包,可通过 `pip` 安装。
## 安装
```bash
pip install zWebApi
pip install zWebApi -i https://pypi.tuna.tsinghua.edu.cn/simple/
```
## 打包
```bash
python -m build
twine upload dist/*
```
## 快速开始
### 1. 项目结构
创建一个符合框架约定的项目目录结构:
```plain
my_project/
├── main.py # 应用入口点
├── action/ # 路由定义目录 (必须)
│ └── user/ # 模块目录 (例如 'user')
│ └── user.py # 路由文件 (必须与模块目录同名)
├── domain/ # 业务逻辑层 (可选,但推荐)
├── dao/ # 数据访问层 (可选,但推荐)
├── utils/ # 项目通用工具 (可选)
└── zwebApi.log # (运行后自动生成) 日志文件
```
### 2. 创建应用 (`main.py`)
这是你应用的入口文件。
```python
# main.py
from zWebApi import create_app
# 创建应用实例,并设置 API 标题和基础路径前缀
app = create_app(title="我的酷炫API")
# --- 可选:添加自定义中间件或配置 ---
# from fastapi.middleware import Middleware
# app.add_middleware(SomeMiddleware)
if __name__ == "__main__":
# 使用框架封装的 run 方法启动服务器
# 等效于 uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
app.run(host="0.0.0.0", port=8000, reload=True) # reload=True 适用于开发环境
```
### 3. 定义路由 (`action/`)
在 `action/` 目录下创建模块和路由文件。
**创建 **`action/user/user.py`
```python
# action/user/user.py
# 导入自定义异常
from zWebApi import zRouter, Panic
# 导入校验参数
from schema.user import (
UserQueryParams,
UserCreate,
UserResponse
)
# 必须创建一个 APIRouter 实例,变量名必须为 'router'
router = zRouter(tags=["用户管理"]) # tags 用于 API 文档分组
# --- 定义路由处理函数 ---
# 注意:函数签名必须遵循规范,只使用 'query' 和 'body' 作为参数名,
# 且它们必须是 Pydantic BaseModel 或 None。
@router.get("/info")
async def get_user_info(query: UserQueryParams = None):
"""获取用户信息"""
if query and query.user_id:
# 模拟业务逻辑
if query.user_id == 999:
# 使用 Panic 异常返回自定义错误
raise Panic(code=404, msg="用户未找到", error="请求的用户ID不存在。")
return {
"user_id": query.user_id,
"name": f"User_{query.user_id}",
"filter_used": query.name_filter
}
return {"message": "请提供 user_id 查询参数"}
@router.post("/create")
async def create_user(body: UserCreate = None):
"""创建新用户"""
if body is None:
# 使用 Panic 异常返回错误
raise Panic(code=400, msg="请求体缺失", error="创建用户必须提供请求体。")
# 模拟创建用户
new_user = UserResponse(id=123, name=body.name, age=body.age)
return {"message": "用户创建成功", "user": new_user}
```
在`schema/`目录下创建校验参数
**创建 **`schema/user.py`
```python
# --- 定义 Pydantic 模型用于参数校验 ---
from pydantic import BaseModel
from typing import Optional
# 查询参数模型
class UserQueryParams(BaseModel):
user_id: Optional[int] = None
name_filter: Optional[str] = None
# 请求体模型
class UserCreate(BaseModel):
name: str
age: int
# 响应体模型 (可选但推荐)
class UserResponse(BaseModel):
id: int
name: str
age: int
```
### 4. 运行应用
在你的项目根目录 (`your_project/`) 下打开终端,运行:
```bash
python main.py
```
应用将在 `http://0.0.0.0:8000` 启动。
+ **API 根路径**: `http://localhost:8000/`
+ **用户模块路径**: `http://localhost:8000/我的酷炫api/user/...`
+ **API 文档**: `http://localhost:8000/docs`
+ **日志查看**: `http://localhost:8000/我的酷炫api/api/error/logs`
## 核心概念
### 应用创建与配置
使用 `zWebApi.create_app` 工厂函数创建 FastAPI 应用实例。
```python
app = create_app(
title="My App", # API 标题,也用作基础路径前缀
enable_cors=True, # 是否启用 CORS
cors_origins=["*"], # CORS 允许的源
cors_allow_credentials=True,
cors_allow_methods=["*"],
cors_allow_headers=["*"]
)
```
### 路由自动注册
框架启动时会自动扫描 `action/` 目录。
+ 遍历 `action/` 下的每个子目录(如 `user/`)。
+ 在每个子目录中查找与目录同名的 Python 文件(如 `user.py`)。
+ 导入该文件,并查找名为 `router` 的 `APIRouter` 实例。
+ 将该 `router` 挂载到以目录名(如 `/user`)为前缀的路径下。
+ 最终的基础路径是 `/title` (title 转为小写并用下划线替换空格)。
### 路由函数签名规范
为了保持一致性并利用框架的校验功能,路由处理函数 **必须** 遵循以下签名:
+ **只接受** `query` 和 `body` 两个命名参数。
+ **参数类型** 必须是 Pydantic `BaseModel` 的子类或 `None`。
+ **默认值** 应为 `None` 以使其成为可选参数。
+ **参数名** 必须严格是 `query` 和/或 `body`。
**正确示例:**
```python
# 只有 query
async def get_items(query: ItemQueryParams = None): ...
# 只有 body
async def create_item(body: CreateItemRequest = None): ...
# 两者都有
async def update_item(query: UpdateQuery = None, body: UpdateItemRequest = None): ...
```
**错误示例 (会导致应用启动失败):**
```python
# 错误:使用了不允许的参数名 'item_id'
async def get_item(item_id: int): ...
# 错误:没有使用 BaseModel
async def search(name: str = ""): ...
```
### 统一响应与异常处理
#### 全局异常处理
框架自动注册了多个全局异常处理器,确保所有错误都返回统一的 JSON 格式:
```json
{
"code": 400,
"msg": "错误的请求",
"error": "具体错误信息...",
"data": null
}
```
+ `HTTPException`: 处理 FastAPI/Starlette 抛出的 HTTP 异常 (如 404, 403)。
+ `RequestValidationError`: 处理 Pydantic 模型校验失败 (422)。
+ `Exception`: 捕获所有未处理的服务器内部错误 (500)。
+ `Panic`: 处理用户自定义的 `Panic` 异常。
#### 自定义 `Panic` 异常
用户可以在任何地方(路由、domain、dao)主动抛出 `Panic` 来返回自定义错误。
```python
from zWebApi import Panic
# 在路由、服务或数据访问层
def some_business_logic(user_id):
if user_id <= 0:
# 主动抛出 Panic 异常
raise Panic(
code=400, # HTTP 状态码和业务码
msg="无效的用户ID", # 用户友好信息
error="用户ID必须是正整数。", # 技术错误详情
data={"provided_id": user_id} # 可选的附加数据
)
```
### 日志记录
框架使用 Python `logging` 模块提供全面的日志功能。
+ **格式**: `[级别][年月日时分秒][文件名][行号]: 消息`
- 例如: `[INFO][20240521180000][app.py][150]: 应用创建完成。`
- 例如: `[ERROR][20240521180001][user.py][30]: 无效的用户ID`
+ **输出**: 同时记录到控制台(开发)和项目根目录下的 `weblog.log` 文件。
+ **轮转**: 使用 `TimedRotatingFileHandler`,默认每10天轮转一次日志文件。
+ **查看**: 提供内置 API 接口 `GET /<title>/api/error/logs` 查看日志内容。
- 可通过 `?lines=N` 参数指定返回最后 N 行。
### 工具模块
框架提供了一个可扩展的 `tools` 包,用于存放通用功能模块。
**导入方式:**
```python
# 从框架内置工具导入
from zWebApi.tools.db.mysql import testsql, MySQLHelper
# 未来可扩展
# from zWebApi.tools.cache.redis_client import RedisManager
```
**创建自定义工具:**
在框架源码的 `src/zWebApi/tools/` 下创建新的子目录和 `.py` 文件即可。用户安装更新后的包即可使用。
## 高级用法
### CORS 配置
在 `create_app` 时配置 CORS:
```python
app = create_app(
title="API",
enable_cors=True,
cors_origins=["http://localhost:3000", "https://myfrontend.com "],
cors_allow_credentials=True,
cors_allow_methods=["GET", "POST", "PUT", "DELETE"],
cors_allow_headers=["*"],
)
```
### 使用框架日志
在你的项目代码中,可以使用框架配置好的日志记录器:
```python
# 在你的 action, domain, dao 等模块中
from zWebApi import get_logger
logger = get_logger()
@router.get("/some-path")
async def my_endpoint():
logger.info("处理 /some-path 请求")
try:
# ... 业务逻辑 ...
logger.debug("业务逻辑执行成功")
return {"result": "ok"}
except Exception as e:
logger.error(f"处理请求时出错: {e}", exc_info=True)
raise # 让全局异常处理器捕获
```
## API 文档
框架完全兼容 FastAPI 的自动生成文档功能。
+ **Swagger UI**: `http://<your-host>:<port>/docs`
+ **ReDoc**: `http://<your-host>:<port>/redoc`
## 贡献
欢迎提交 Issue 和 Pull Request!
1. Fork 本仓库
2. 创建你的特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交你的更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启一个 Pull Request
## 许可证
本项目采用 MIT 许可证。详情请见 [LICENSE](LICENSE) 文件。
Raw data
{
"_id": null,
"home_page": null,
"name": "zWebApi",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "zWebApi, fastapi, framework, web, api",
"author": null,
"author_email": "Fatosy <fatosy@163.com>",
"download_url": "https://files.pythonhosted.org/packages/30/c8/266e04334cc24eb3926076b0d4d37afbf1ae4f1eccb8676d6b7b793702da/zwebapi-0.11.1.tar.gz",
"platform": null,
"description": "# zWebApi\n\u4e00\u4e2a\u529f\u80fd\u4e30\u5bcc\u3001\u5f00\u7bb1\u5373\u7528\u7684 Python Web \u6846\u67b6\uff0c\u57fa\u4e8e FastAPI \u6784\u5efa\u3002\u5b83\u65e8\u5728\u901a\u8fc7\u7ea6\u5b9a\u4f18\u4e8e\u914d\u7f6e\u7684\u539f\u5219\uff0c\u7b80\u5316 API \u5f00\u53d1\u6d41\u7a0b\uff0c\u63d0\u4f9b\u81ea\u52a8\u8def\u7531\u3001\u7edf\u4e00\u5f02\u5e38\u5904\u7406\u3001\u65e5\u5fd7\u8bb0\u5f55\u548c\u53ef\u6269\u5c55\u5de5\u5177\u96c6\u3002\n\n \n\n\n## \u76ee\u5f55\n+ [\u7279\u6027](#\u7279\u6027)\n+ [\u5b89\u88c5](#\u5b89\u88c5)\n+ [\u5feb\u901f\u5f00\u59cb](#\u5feb\u901f\u5f00\u59cb)\n - [1. \u9879\u76ee\u7ed3\u6784](#1-\u9879\u76ee\u7ed3\u6784)\n - [2. \u521b\u5efa\u5e94\u7528 (](#2-\u521b\u5efa\u5e94\u7528-mainpy)`main.py`[)](#2-\u521b\u5efa\u5e94\u7528-mainpy)\n - [3. \u5b9a\u4e49\u8def\u7531 (](#3-\u5b9a\u4e49\u8def\u7531-action)`action/`[)](#3-\u5b9a\u4e49\u8def\u7531-action)\n - [4. \u8fd0\u884c\u5e94\u7528](#4-\u8fd0\u884c\u5e94\u7528)\n+ [\u6838\u5fc3\u6982\u5ff5](#\u6838\u5fc3\u6982\u5ff5)\n - [\u5e94\u7528\u521b\u5efa\u4e0e\u914d\u7f6e](#\u5e94\u7528\u521b\u5efa\u4e0e\u914d\u7f6e)\n - [\u8def\u7531\u81ea\u52a8\u6ce8\u518c](#\u8def\u7531\u81ea\u52a8\u6ce8\u518c)\n - [\u8def\u7531\u51fd\u6570\u7b7e\u540d\u89c4\u8303](#\u8def\u7531\u51fd\u6570\u7b7e\u540d\u89c4\u8303)\n - [\u7edf\u4e00\u54cd\u5e94\u4e0e\u5f02\u5e38\u5904\u7406](#\u7edf\u4e00\u54cd\u5e94\u4e0e\u5f02\u5e38\u5904\u7406)\n * [\u5168\u5c40\u5f02\u5e38\u5904\u7406](#\u5168\u5c40\u5f02\u5e38\u5904\u7406)\n * [\u81ea\u5b9a\u4e49 ](#\u81ea\u5b9a\u4e49-panic-\u5f02\u5e38)`Panic`[ \u5f02\u5e38](#\u81ea\u5b9a\u4e49-panic-\u5f02\u5e38)\n - [\u65e5\u5fd7\u8bb0\u5f55](#\u65e5\u5fd7\u8bb0\u5f55)\n - [\u5de5\u5177\u6a21\u5757](#\u5de5\u5177\u6a21\u5757)\n+ [\u9ad8\u7ea7\u7528\u6cd5](#\u9ad8\u7ea7\u7528\u6cd5)\n - [CORS \u914d\u7f6e](#cors-\u914d\u7f6e)\n - [\u4f7f\u7528\u6846\u67b6\u65e5\u5fd7](#\u4f7f\u7528\u6846\u67b6\u65e5\u5fd7)\n+ [API \u6587\u6863](#api-\u6587\u6863)\n+ [\u8d21\u732e](#\u8d21\u732e)\n+ [\u8bb8\u53ef\u8bc1](#\u8bb8\u53ef\u8bc1)\n\n## \u7279\u6027\n+ **\ud83d\ude80**** \u5feb\u901f\u542f\u52a8**: \u57fa\u4e8e FastAPI \u548c Uvicorn\uff0c\u63d0\u4f9b\u5f02\u6b65\u9ad8\u6027\u80fd\u3002\n+ **\ud83e\udded**** \u81ea\u52a8\u8def\u7531\u6ce8\u518c**: \u53ea\u9700\u6309\u7ea6\u5b9a\u5728 `action/` \u76ee\u5f55\u4e0b\u7ec4\u7ec7\u4ee3\u7801\uff0c\u8def\u7531\u81ea\u52a8\u751f\u6548\u3002\n+ **\ud83d\udd12**** \u8def\u7531\u7b7e\u540d\u5f3a\u5236**: \u786e\u4fdd\u6240\u6709\u8def\u7531\u51fd\u6570\u9075\u5faa\u7edf\u4e00\u7684 `query`/`body` \u53c2\u6570\u89c4\u8303\u3002\n+ **\ud83d\udee1\ufe0f**** \u7edf\u4e00\u5f02\u5e38\u5904\u7406**: \u5168\u5c40\u6355\u83b7\u5f02\u5e38\uff0c\u8fd4\u56de\u683c\u5f0f\u7edf\u4e00\u7684 JSON \u9519\u8bef\u54cd\u5e94\u3002\n+ **\ud83d\udea8**** \u81ea\u5b9a\u4e49 **`Panic`** \u5f02\u5e38**: \u7b80\u5355\u6613\u7528\u7684\u81ea\u5b9a\u4e49\u5f02\u5e38\u7c7b\uff0c\u7528\u4e8e\u4e3b\u52a8\u8fd4\u56de\u9519\u8bef\u3002\n+ **\ud83d\udccb**** \u5168\u9762\u65e5\u5fd7\u8bb0\u5f55**: \u81ea\u52a8\u8bb0\u5f55\u5e94\u7528\u542f\u52a8\u3001\u8def\u7531\u3001\u5f02\u5e38\u7b49\u4fe1\u606f\uff0c\u652f\u6301\u6587\u4ef6\u548c\u63a7\u5236\u53f0\u8f93\u51fa\uff0c\u5e76\u63d0\u4f9b\u65e5\u5fd7\u67e5\u770b\u63a5\u53e3\u3002\n+ **\ud83d\udee0\ufe0f**** \u53ef\u6269\u5c55\u5de5\u5177\u6a21\u5757**: \u901a\u8fc7 `zWebApi.tools.*` \u8f7b\u677e\u8bbf\u95ee\u548c\u6269\u5c55\u6846\u67b6\u529f\u80fd\u3002\n+ **\ud83c\udf10**** CORS \u652f\u6301**: \u5185\u7f6e CORS \u4e2d\u95f4\u4ef6\uff0c\u8f7b\u677e\u914d\u7f6e\u8de8\u57df\u8d44\u6e90\u5171\u4eab\u3002\n+ **\ud83d\udce6**** \u6613\u4e8e\u6253\u5305\u548c\u5206\u53d1**: \u6807\u51c6 Python \u5305\uff0c\u53ef\u901a\u8fc7 `pip` \u5b89\u88c5\u3002\n\n## \u5b89\u88c5\n```bash\npip install zWebApi\npip install zWebApi -i https://pypi.tuna.tsinghua.edu.cn/simple/\n```\n\n## \u6253\u5305\n```bash\npython -m build\ntwine upload dist/*\n```\n\n## \u5feb\u901f\u5f00\u59cb\n### 1. \u9879\u76ee\u7ed3\u6784\n\u521b\u5efa\u4e00\u4e2a\u7b26\u5408\u6846\u67b6\u7ea6\u5b9a\u7684\u9879\u76ee\u76ee\u5f55\u7ed3\u6784\uff1a\n\n```plain\nmy_project/\n\u251c\u2500\u2500 main.py # \u5e94\u7528\u5165\u53e3\u70b9\n\u251c\u2500\u2500 action/ # \u8def\u7531\u5b9a\u4e49\u76ee\u5f55 (\u5fc5\u987b)\n\u2502 \u2514\u2500\u2500 user/ # \u6a21\u5757\u76ee\u5f55 (\u4f8b\u5982 'user')\n\u2502 \u2514\u2500\u2500 user.py # \u8def\u7531\u6587\u4ef6 (\u5fc5\u987b\u4e0e\u6a21\u5757\u76ee\u5f55\u540c\u540d)\n\u251c\u2500\u2500 domain/ # \u4e1a\u52a1\u903b\u8f91\u5c42 (\u53ef\u9009\uff0c\u4f46\u63a8\u8350)\n\u251c\u2500\u2500 dao/ # \u6570\u636e\u8bbf\u95ee\u5c42 (\u53ef\u9009\uff0c\u4f46\u63a8\u8350)\n\u251c\u2500\u2500 utils/ # \u9879\u76ee\u901a\u7528\u5de5\u5177 (\u53ef\u9009)\n\u2514\u2500\u2500 zwebApi.log # (\u8fd0\u884c\u540e\u81ea\u52a8\u751f\u6210) \u65e5\u5fd7\u6587\u4ef6\n```\n\n### 2. \u521b\u5efa\u5e94\u7528 (`main.py`)\n\u8fd9\u662f\u4f60\u5e94\u7528\u7684\u5165\u53e3\u6587\u4ef6\u3002\n\n```python\n# main.py\nfrom zWebApi import create_app\n\n# \u521b\u5efa\u5e94\u7528\u5b9e\u4f8b\uff0c\u5e76\u8bbe\u7f6e API \u6807\u9898\u548c\u57fa\u7840\u8def\u5f84\u524d\u7f00\napp = create_app(title=\"\u6211\u7684\u9177\u70abAPI\")\n\n# --- \u53ef\u9009\uff1a\u6dfb\u52a0\u81ea\u5b9a\u4e49\u4e2d\u95f4\u4ef6\u6216\u914d\u7f6e ---\n# from fastapi.middleware import Middleware\n# app.add_middleware(SomeMiddleware)\n\nif __name__ == \"__main__\":\n # \u4f7f\u7528\u6846\u67b6\u5c01\u88c5\u7684 run \u65b9\u6cd5\u542f\u52a8\u670d\u52a1\u5668\n # \u7b49\u6548\u4e8e uvicorn.run(\"main:app\", host=\"0.0.0.0\", port=8000, reload=True)\n app.run(host=\"0.0.0.0\", port=8000, reload=True) # reload=True \u9002\u7528\u4e8e\u5f00\u53d1\u73af\u5883\n```\n\n### 3. \u5b9a\u4e49\u8def\u7531 (`action/`)\n\u5728 `action/` \u76ee\u5f55\u4e0b\u521b\u5efa\u6a21\u5757\u548c\u8def\u7531\u6587\u4ef6\u3002\n\n**\u521b\u5efa **`action/user/user.py`\n\n```python\n# action/user/user.py\n# \u5bfc\u5165\u81ea\u5b9a\u4e49\u5f02\u5e38\nfrom zWebApi import zRouter, Panic\n# \u5bfc\u5165\u6821\u9a8c\u53c2\u6570\nfrom schema.user import (\n UserQueryParams,\n UserCreate,\n UserResponse\n)\n\n# \u5fc5\u987b\u521b\u5efa\u4e00\u4e2a APIRouter \u5b9e\u4f8b\uff0c\u53d8\u91cf\u540d\u5fc5\u987b\u4e3a 'router'\nrouter = zRouter(tags=[\"\u7528\u6237\u7ba1\u7406\"]) # tags \u7528\u4e8e API \u6587\u6863\u5206\u7ec4\n\n# --- \u5b9a\u4e49\u8def\u7531\u5904\u7406\u51fd\u6570 ---\n# \u6ce8\u610f\uff1a\u51fd\u6570\u7b7e\u540d\u5fc5\u987b\u9075\u5faa\u89c4\u8303\uff0c\u53ea\u4f7f\u7528 'query' \u548c 'body' \u4f5c\u4e3a\u53c2\u6570\u540d\uff0c\n# \u4e14\u5b83\u4eec\u5fc5\u987b\u662f Pydantic BaseModel \u6216 None\u3002\n\n@router.get(\"/info\")\nasync def get_user_info(query: UserQueryParams = None):\n \"\"\"\u83b7\u53d6\u7528\u6237\u4fe1\u606f\"\"\"\n if query and query.user_id:\n # \u6a21\u62df\u4e1a\u52a1\u903b\u8f91\n if query.user_id == 999:\n # \u4f7f\u7528 Panic \u5f02\u5e38\u8fd4\u56de\u81ea\u5b9a\u4e49\u9519\u8bef\n raise Panic(code=404, msg=\"\u7528\u6237\u672a\u627e\u5230\", error=\"\u8bf7\u6c42\u7684\u7528\u6237ID\u4e0d\u5b58\u5728\u3002\")\n return {\n \"user_id\": query.user_id,\n \"name\": f\"User_{query.user_id}\",\n \"filter_used\": query.name_filter\n }\n return {\"message\": \"\u8bf7\u63d0\u4f9b user_id \u67e5\u8be2\u53c2\u6570\"}\n\n@router.post(\"/create\")\nasync def create_user(body: UserCreate = None):\n \"\"\"\u521b\u5efa\u65b0\u7528\u6237\"\"\"\n if body is None:\n # \u4f7f\u7528 Panic \u5f02\u5e38\u8fd4\u56de\u9519\u8bef\n raise Panic(code=400, msg=\"\u8bf7\u6c42\u4f53\u7f3a\u5931\", error=\"\u521b\u5efa\u7528\u6237\u5fc5\u987b\u63d0\u4f9b\u8bf7\u6c42\u4f53\u3002\")\n \n # \u6a21\u62df\u521b\u5efa\u7528\u6237\n new_user = UserResponse(id=123, name=body.name, age=body.age)\n return {\"message\": \"\u7528\u6237\u521b\u5efa\u6210\u529f\", \"user\": new_user}\n\n```\n\n\u5728`schema/`\u76ee\u5f55\u4e0b\u521b\u5efa\u6821\u9a8c\u53c2\u6570\n\n**\u521b\u5efa **`schema/user.py`\n\n```python\n# --- \u5b9a\u4e49 Pydantic \u6a21\u578b\u7528\u4e8e\u53c2\u6570\u6821\u9a8c ---\nfrom pydantic import BaseModel\nfrom typing import Optional\n\n# \u67e5\u8be2\u53c2\u6570\u6a21\u578b\nclass UserQueryParams(BaseModel):\n user_id: Optional[int] = None\n name_filter: Optional[str] = None\n\n# \u8bf7\u6c42\u4f53\u6a21\u578b\nclass UserCreate(BaseModel):\n name: str\n age: int\n\n# \u54cd\u5e94\u4f53\u6a21\u578b (\u53ef\u9009\u4f46\u63a8\u8350)\nclass UserResponse(BaseModel):\n id: int\n name: str\n age: int\n```\n\n### 4. \u8fd0\u884c\u5e94\u7528\n\u5728\u4f60\u7684\u9879\u76ee\u6839\u76ee\u5f55 (`your_project/`) \u4e0b\u6253\u5f00\u7ec8\u7aef\uff0c\u8fd0\u884c\uff1a\n\n```bash\npython main.py\n```\n\n\u5e94\u7528\u5c06\u5728 `http://0.0.0.0:8000` \u542f\u52a8\u3002\n\n+ **API \u6839\u8def\u5f84**: `http://localhost:8000/`\n+ **\u7528\u6237\u6a21\u5757\u8def\u5f84**: `http://localhost:8000/\u6211\u7684\u9177\u70abapi/user/...`\n+ **API \u6587\u6863**: `http://localhost:8000/docs`\n+ **\u65e5\u5fd7\u67e5\u770b**: `http://localhost:8000/\u6211\u7684\u9177\u70abapi/api/error/logs`\n\n## \u6838\u5fc3\u6982\u5ff5\n### \u5e94\u7528\u521b\u5efa\u4e0e\u914d\u7f6e\n\u4f7f\u7528 `zWebApi.create_app` \u5de5\u5382\u51fd\u6570\u521b\u5efa FastAPI \u5e94\u7528\u5b9e\u4f8b\u3002\n\n```python\napp = create_app(\n title=\"My App\", # API \u6807\u9898\uff0c\u4e5f\u7528\u4f5c\u57fa\u7840\u8def\u5f84\u524d\u7f00\n enable_cors=True, # \u662f\u5426\u542f\u7528 CORS\n cors_origins=[\"*\"], # CORS \u5141\u8bb8\u7684\u6e90\n cors_allow_credentials=True,\n cors_allow_methods=[\"*\"],\n cors_allow_headers=[\"*\"]\n)\n```\n\n### \u8def\u7531\u81ea\u52a8\u6ce8\u518c\n\u6846\u67b6\u542f\u52a8\u65f6\u4f1a\u81ea\u52a8\u626b\u63cf `action/` \u76ee\u5f55\u3002\n\n+ \u904d\u5386 `action/` \u4e0b\u7684\u6bcf\u4e2a\u5b50\u76ee\u5f55\uff08\u5982 `user/`\uff09\u3002\n+ \u5728\u6bcf\u4e2a\u5b50\u76ee\u5f55\u4e2d\u67e5\u627e\u4e0e\u76ee\u5f55\u540c\u540d\u7684 Python \u6587\u4ef6\uff08\u5982 `user.py`\uff09\u3002\n+ \u5bfc\u5165\u8be5\u6587\u4ef6\uff0c\u5e76\u67e5\u627e\u540d\u4e3a `router` \u7684 `APIRouter` \u5b9e\u4f8b\u3002\n+ \u5c06\u8be5 `router` \u6302\u8f7d\u5230\u4ee5\u76ee\u5f55\u540d\uff08\u5982 `/user`\uff09\u4e3a\u524d\u7f00\u7684\u8def\u5f84\u4e0b\u3002\n+ \u6700\u7ec8\u7684\u57fa\u7840\u8def\u5f84\u662f `/title` (title \u8f6c\u4e3a\u5c0f\u5199\u5e76\u7528\u4e0b\u5212\u7ebf\u66ff\u6362\u7a7a\u683c)\u3002\n\n### \u8def\u7531\u51fd\u6570\u7b7e\u540d\u89c4\u8303\n\u4e3a\u4e86\u4fdd\u6301\u4e00\u81f4\u6027\u5e76\u5229\u7528\u6846\u67b6\u7684\u6821\u9a8c\u529f\u80fd\uff0c\u8def\u7531\u5904\u7406\u51fd\u6570 **\u5fc5\u987b** \u9075\u5faa\u4ee5\u4e0b\u7b7e\u540d\uff1a\n\n+ **\u53ea\u63a5\u53d7** `query` \u548c `body` \u4e24\u4e2a\u547d\u540d\u53c2\u6570\u3002\n+ **\u53c2\u6570\u7c7b\u578b** \u5fc5\u987b\u662f Pydantic `BaseModel` \u7684\u5b50\u7c7b\u6216 `None`\u3002\n+ **\u9ed8\u8ba4\u503c** \u5e94\u4e3a `None` \u4ee5\u4f7f\u5176\u6210\u4e3a\u53ef\u9009\u53c2\u6570\u3002\n+ **\u53c2\u6570\u540d** \u5fc5\u987b\u4e25\u683c\u662f `query` \u548c/\u6216 `body`\u3002\n\n**\u6b63\u786e\u793a\u4f8b:**\n\n```python\n# \u53ea\u6709 query\nasync def get_items(query: ItemQueryParams = None): ...\n\n# \u53ea\u6709 body\nasync def create_item(body: CreateItemRequest = None): ...\n\n# \u4e24\u8005\u90fd\u6709\nasync def update_item(query: UpdateQuery = None, body: UpdateItemRequest = None): ...\n```\n\n**\u9519\u8bef\u793a\u4f8b (\u4f1a\u5bfc\u81f4\u5e94\u7528\u542f\u52a8\u5931\u8d25):**\n\n```python\n# \u9519\u8bef\uff1a\u4f7f\u7528\u4e86\u4e0d\u5141\u8bb8\u7684\u53c2\u6570\u540d 'item_id'\nasync def get_item(item_id: int): ...\n\n# \u9519\u8bef\uff1a\u6ca1\u6709\u4f7f\u7528 BaseModel\nasync def search(name: str = \"\"): ...\n```\n\n### \u7edf\u4e00\u54cd\u5e94\u4e0e\u5f02\u5e38\u5904\u7406\n#### \u5168\u5c40\u5f02\u5e38\u5904\u7406\n\u6846\u67b6\u81ea\u52a8\u6ce8\u518c\u4e86\u591a\u4e2a\u5168\u5c40\u5f02\u5e38\u5904\u7406\u5668\uff0c\u786e\u4fdd\u6240\u6709\u9519\u8bef\u90fd\u8fd4\u56de\u7edf\u4e00\u7684 JSON \u683c\u5f0f\uff1a\n\n```json\n{\n \"code\": 400,\n \"msg\": \"\u9519\u8bef\u7684\u8bf7\u6c42\",\n \"error\": \"\u5177\u4f53\u9519\u8bef\u4fe1\u606f...\",\n \"data\": null\n}\n```\n\n+ `HTTPException`: \u5904\u7406 FastAPI/Starlette \u629b\u51fa\u7684 HTTP \u5f02\u5e38 (\u5982 404, 403)\u3002\n+ `RequestValidationError`: \u5904\u7406 Pydantic \u6a21\u578b\u6821\u9a8c\u5931\u8d25 (422)\u3002\n+ `Exception`: \u6355\u83b7\u6240\u6709\u672a\u5904\u7406\u7684\u670d\u52a1\u5668\u5185\u90e8\u9519\u8bef (500)\u3002\n+ `Panic`: \u5904\u7406\u7528\u6237\u81ea\u5b9a\u4e49\u7684 `Panic` \u5f02\u5e38\u3002\n\n#### \u81ea\u5b9a\u4e49 `Panic` \u5f02\u5e38\n\u7528\u6237\u53ef\u4ee5\u5728\u4efb\u4f55\u5730\u65b9\uff08\u8def\u7531\u3001domain\u3001dao\uff09\u4e3b\u52a8\u629b\u51fa `Panic` \u6765\u8fd4\u56de\u81ea\u5b9a\u4e49\u9519\u8bef\u3002\n\n```python\nfrom zWebApi import Panic\n\n# \u5728\u8def\u7531\u3001\u670d\u52a1\u6216\u6570\u636e\u8bbf\u95ee\u5c42\ndef some_business_logic(user_id):\n if user_id <= 0:\n # \u4e3b\u52a8\u629b\u51fa Panic \u5f02\u5e38\n raise Panic(\n code=400, # HTTP \u72b6\u6001\u7801\u548c\u4e1a\u52a1\u7801\n msg=\"\u65e0\u6548\u7684\u7528\u6237ID\", # \u7528\u6237\u53cb\u597d\u4fe1\u606f\n error=\"\u7528\u6237ID\u5fc5\u987b\u662f\u6b63\u6574\u6570\u3002\", # \u6280\u672f\u9519\u8bef\u8be6\u60c5\n data={\"provided_id\": user_id} # \u53ef\u9009\u7684\u9644\u52a0\u6570\u636e\n )\n```\n\n### \u65e5\u5fd7\u8bb0\u5f55\n\u6846\u67b6\u4f7f\u7528 Python `logging` \u6a21\u5757\u63d0\u4f9b\u5168\u9762\u7684\u65e5\u5fd7\u529f\u80fd\u3002\n\n+ **\u683c\u5f0f**: `[\u7ea7\u522b][\u5e74\u6708\u65e5\u65f6\u5206\u79d2][\u6587\u4ef6\u540d][\u884c\u53f7]: \u6d88\u606f`\n - \u4f8b\u5982: `[INFO][20240521180000][app.py][150]: \u5e94\u7528\u521b\u5efa\u5b8c\u6210\u3002`\n - \u4f8b\u5982: `[ERROR][20240521180001][user.py][30]: \u65e0\u6548\u7684\u7528\u6237ID`\n+ **\u8f93\u51fa**: \u540c\u65f6\u8bb0\u5f55\u5230\u63a7\u5236\u53f0\uff08\u5f00\u53d1\uff09\u548c\u9879\u76ee\u6839\u76ee\u5f55\u4e0b\u7684 `weblog.log` \u6587\u4ef6\u3002\n+ **\u8f6e\u8f6c**: \u4f7f\u7528 `TimedRotatingFileHandler`\uff0c\u9ed8\u8ba4\u6bcf10\u5929\u8f6e\u8f6c\u4e00\u6b21\u65e5\u5fd7\u6587\u4ef6\u3002\n+ **\u67e5\u770b**: \u63d0\u4f9b\u5185\u7f6e API \u63a5\u53e3 `GET /<title>/api/error/logs` \u67e5\u770b\u65e5\u5fd7\u5185\u5bb9\u3002\n - \u53ef\u901a\u8fc7 `?lines=N` \u53c2\u6570\u6307\u5b9a\u8fd4\u56de\u6700\u540e N \u884c\u3002\n\n### \u5de5\u5177\u6a21\u5757\n\u6846\u67b6\u63d0\u4f9b\u4e86\u4e00\u4e2a\u53ef\u6269\u5c55\u7684 `tools` \u5305\uff0c\u7528\u4e8e\u5b58\u653e\u901a\u7528\u529f\u80fd\u6a21\u5757\u3002\n\n**\u5bfc\u5165\u65b9\u5f0f:**\n\n```python\n# \u4ece\u6846\u67b6\u5185\u7f6e\u5de5\u5177\u5bfc\u5165\nfrom zWebApi.tools.db.mysql import testsql, MySQLHelper\n\n# \u672a\u6765\u53ef\u6269\u5c55\n# from zWebApi.tools.cache.redis_client import RedisManager\n```\n\n**\u521b\u5efa\u81ea\u5b9a\u4e49\u5de5\u5177:**\n\n\u5728\u6846\u67b6\u6e90\u7801\u7684 `src/zWebApi/tools/` \u4e0b\u521b\u5efa\u65b0\u7684\u5b50\u76ee\u5f55\u548c `.py` \u6587\u4ef6\u5373\u53ef\u3002\u7528\u6237\u5b89\u88c5\u66f4\u65b0\u540e\u7684\u5305\u5373\u53ef\u4f7f\u7528\u3002\n\n## \u9ad8\u7ea7\u7528\u6cd5\n### CORS \u914d\u7f6e\n\u5728 `create_app` \u65f6\u914d\u7f6e CORS\uff1a\n\n```python\napp = create_app(\n title=\"API\",\n enable_cors=True,\n cors_origins=[\"http://localhost:3000\", \"https://myfrontend.com \"],\n cors_allow_credentials=True,\n cors_allow_methods=[\"GET\", \"POST\", \"PUT\", \"DELETE\"],\n cors_allow_headers=[\"*\"],\n)\n```\n\n### \u4f7f\u7528\u6846\u67b6\u65e5\u5fd7\n\u5728\u4f60\u7684\u9879\u76ee\u4ee3\u7801\u4e2d\uff0c\u53ef\u4ee5\u4f7f\u7528\u6846\u67b6\u914d\u7f6e\u597d\u7684\u65e5\u5fd7\u8bb0\u5f55\u5668\uff1a\n\n```python\n# \u5728\u4f60\u7684 action, domain, dao \u7b49\u6a21\u5757\u4e2d\nfrom zWebApi import get_logger\n\nlogger = get_logger()\n\n@router.get(\"/some-path\")\nasync def my_endpoint():\n logger.info(\"\u5904\u7406 /some-path \u8bf7\u6c42\")\n try:\n # ... \u4e1a\u52a1\u903b\u8f91 ...\n logger.debug(\"\u4e1a\u52a1\u903b\u8f91\u6267\u884c\u6210\u529f\")\n return {\"result\": \"ok\"}\n except Exception as e:\n logger.error(f\"\u5904\u7406\u8bf7\u6c42\u65f6\u51fa\u9519: {e}\", exc_info=True)\n raise # \u8ba9\u5168\u5c40\u5f02\u5e38\u5904\u7406\u5668\u6355\u83b7\n```\n\n## API \u6587\u6863\n\u6846\u67b6\u5b8c\u5168\u517c\u5bb9 FastAPI \u7684\u81ea\u52a8\u751f\u6210\u6587\u6863\u529f\u80fd\u3002\n\n+ **Swagger UI**: `http://<your-host>:<port>/docs`\n+ **ReDoc**: `http://<your-host>:<port>/redoc`\n\n## \u8d21\u732e\n\u6b22\u8fce\u63d0\u4ea4 Issue \u548c Pull Request\uff01\n\n1. Fork \u672c\u4ed3\u5e93\n2. \u521b\u5efa\u4f60\u7684\u7279\u6027\u5206\u652f (`git checkout -b feature/AmazingFeature`)\n3. \u63d0\u4ea4\u4f60\u7684\u66f4\u6539 (`git commit -m 'Add some AmazingFeature'`)\n4. \u63a8\u9001\u5230\u5206\u652f (`git push origin feature/AmazingFeature`)\n5. \u5f00\u542f\u4e00\u4e2a Pull Request\n\n## \u8bb8\u53ef\u8bc1\n\u672c\u9879\u76ee\u91c7\u7528 MIT \u8bb8\u53ef\u8bc1\u3002\u8be6\u60c5\u8bf7\u89c1 [LICENSE](LICENSE) \u6587\u4ef6\u3002\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "\u4e00\u4e2a\u529f\u80fd\u4e30\u5bcc\u3001\u5f00\u7bb1\u5373\u7528\u7684 Python Web \u6846\u67b6\uff0c\u57fa\u4e8e FastAPI \u6784\u5efa\u3002",
"version": "0.11.1",
"project_urls": {
"Homepage": "https://github.com/Fatosy/zWebApi",
"Repository": "https://github.com/Fatosy/zWebApi.git"
},
"split_keywords": [
"zwebapi",
" fastapi",
" framework",
" web",
" api"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "470529c708214fb3235bddfb251ff58b27f0c2e2198be5c0a3104b86743507aa",
"md5": "fb5bdf51d3e61286459e5ed28abca607",
"sha256": "a47ca34f656a75dbde9f0d6992904c5a11a74347ae1552471be02f1aba12ecad"
},
"downloads": -1,
"filename": "zwebapi-0.11.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "fb5bdf51d3e61286459e5ed28abca607",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 24021,
"upload_time": "2025-07-31T09:29:24",
"upload_time_iso_8601": "2025-07-31T09:29:24.592872Z",
"url": "https://files.pythonhosted.org/packages/47/05/29c708214fb3235bddfb251ff58b27f0c2e2198be5c0a3104b86743507aa/zwebapi-0.11.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "30c8266e04334cc24eb3926076b0d4d37afbf1ae4f1eccb8676d6b7b793702da",
"md5": "ca33279190e1fff81b38416d159dc8c5",
"sha256": "bc1f809463a67736c99d98b49aaf08608380a6b2ece29121d84407ddc938019a"
},
"downloads": -1,
"filename": "zwebapi-0.11.1.tar.gz",
"has_sig": false,
"md5_digest": "ca33279190e1fff81b38416d159dc8c5",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 25759,
"upload_time": "2025-07-31T09:29:25",
"upload_time_iso_8601": "2025-07-31T09:29:25.860929Z",
"url": "https://files.pythonhosted.org/packages/30/c8/266e04334cc24eb3926076b0d4d37afbf1ae4f1eccb8676d6b7b793702da/zwebapi-0.11.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-31 09:29:25",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Fatosy",
"github_project": "zWebApi",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "zwebapi"
}