Compare commits

...

7 Commits

11 changed files with 748 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list.d/debi
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
clang-format \
&& rm -rf /var/lib/apt/lists/*
# 复制项目文件

View File

@@ -0,0 +1,474 @@
# 代码格式化整理代码Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:**`codenext` 编辑器增加"整理代码"按钮,后端通过 `POST /format` 调用 ruffPython/海龟绘图)和 clang-formatC/C++)格式化代码。
**Architecture:** `codeapinew` 新增 `formatter.py`(封装 subprocess 调用 ruff/clang-format`main.py` 新增 `/format` 端点;`codenext` 新增 `api.ts``formatCode``composables/code.ts``format()`,并在 `CodeSection.vue` 加按钮。
**Tech Stack:** FastAPI, Pydantic, subprocess, ruff (CLI), clang-format (CLI), Vue 3, axios, naive-ui
---
## Task 1: 后端依赖ruff、pytest、clang-format
**Files:**
- Modify: `codeapinew/pyproject.toml`
- Modify: `codeapinew/requirements.txt`
- Modify: `codeapinew/Dockerfile`
- [ ] **Step 1: 添加 ruff 为运行依赖**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv add ruff
```
Expected: `pyproject.toml``dependencies` 列表新增 `"ruff>=..."``uv.lock` 更新。
- [ ] **Step 2: 添加 pytest 为开发依赖**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv add --dev pytest
```
Expected: `pyproject.toml` 新增 `[dependency-groups]` / `dev = ["pytest>=..."]`
- [ ] **Step 3: 查询已安装的 ruff 版本,同步到 requirements.txt**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run ruff --version
```
记录输出的版本号(如 `ruff 0.8.x`),然后在 `requirements.txt` 中按字母序插入一行(例如插在 `pyyaml``pytz` 之间requirements.txt 当前是字母序排列):
```
ruff==<上一步输出的版本号>
```
- [ ] **Step 4: Dockerfile 中加入 clang-format**
修改 `codeapinew/Dockerfile` 中的 apt 安装步骤:
```dockerfile
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
clang-format \
&& rm -rf /var/lib/apt/lists/*
```
- [ ] **Step 5: Commit**
```bash
cd /home/xuyue/Projects/Code/codeapinew && git add pyproject.toml uv.lock requirements.txt Dockerfile && git commit -m "build: add ruff and pytest dependencies, install clang-format in image"
```
---
## Task 2: `formatter.py` — 格式化核心逻辑TDD
**Files:**
- Create: `codeapinew/formatter.py`
- Test: `codeapinew/test_formatter.py`
- [ ] **Step 1: 写失败的测试**
创建 `codeapinew/test_formatter.py`
```python
import pytest
from formatter import FormatError, format_code
def test_format_python_normalizes_spacing():
result = format_code("x=1\n", "python")
assert result == "x = 1\n"
def test_format_turtle_uses_python_formatter():
result = format_code("t=1\n", "turtle")
assert result == "t = 1\n"
def test_format_python_syntax_error_raises():
with pytest.raises(FormatError):
format_code("def foo(:\n", "python")
def test_format_c_uses_allman_braces():
result = format_code("int main(){int x=1;return x;}", "c")
assert result.rstrip("\n") == "int main()\n{\n int x = 1;\n return x;\n}"
def test_format_cpp_uses_allman_braces():
result = format_code("class Foo{};", "cpp")
assert result.rstrip("\n") == "class Foo\n{\n};"
def test_format_unsupported_language_raises():
with pytest.raises(FormatError):
format_code("print(1)", "java")
```
- [ ] **Step 2: 运行测试,确认失败**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest test_formatter.py -v
```
Expected: FAIL`ModuleNotFoundError: No module named 'formatter'`(或 collection error
- [ ] **Step 3: 实现 `formatter.py`**
创建 `codeapinew/formatter.py`
```python
import subprocess
TIMEOUT_SECONDS = 5
CLANG_FORMAT_STYLE = "{BasedOnStyle: LLVM, IndentWidth: 4, BreakBeforeBraces: Allman}"
class FormatError(Exception):
"""代码格式化失败时抛出message 为可展示给用户的错误信息"""
def _run(cmd: list[str], code: str) -> str:
try:
result = subprocess.run(
cmd,
input=code,
capture_output=True,
text=True,
timeout=TIMEOUT_SECONDS,
)
except subprocess.TimeoutExpired:
raise FormatError("格式化超时")
if result.returncode != 0:
raise FormatError(result.stderr.strip() or "格式化失败")
return result.stdout
def format_code(code: str, language: str) -> str:
if language in ("python", "turtle"):
return _run(["ruff", "format", "-", "--stdin-filename", "main.py"], code)
if language in ("c", "cpp"):
ext = "c" if language == "c" else "cpp"
return _run(
[
"clang-format",
f"-style={CLANG_FORMAT_STYLE}",
f"-assume-filename=main.{ext}",
],
code,
)
raise FormatError(f"不支持的语言: {language}")
```
- [ ] **Step 4: 运行测试,确认通过**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest test_formatter.py -v
```
Expected: 全部 6 个测试 PASS。
- [ ] **Step 5: Commit**
```bash
cd /home/xuyue/Projects/Code/codeapinew && git add formatter.py test_formatter.py && git commit -m "feat: add formatter module wrapping ruff and clang-format"
```
---
## Task 3: `POST /format` 端点TDD
**Files:**
- Modify: `codeapinew/schemas.py`
- Modify: `codeapinew/main.py`
- Test: `codeapinew/test_main.py`
- [ ] **Step 1: 写失败的测试**
创建 `codeapinew/test_main.py`
```python
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_format_endpoint_formats_python_code():
response = client.post("/format", json={"code": "x=1\n", "language": "python"})
assert response.status_code == 200
assert response.json() == {"code": "x = 1\n"}
def test_format_endpoint_returns_400_on_syntax_error():
response = client.post(
"/format", json={"code": "def foo(:\n", "language": "python"}
)
assert response.status_code == 400
```
- [ ] **Step 2: 运行测试,确认失败**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest test_main.py -v
```
Expected: FAIL两个测试均得到 404路由不存在
- [ ] **Step 3: 在 `schemas.py` 新增请求/响应模型**
`codeapinew/schemas.py` 末尾追加:
```python
class FormatRequest(BaseModel):
"""格式化代码的请求模式"""
code: str
language: str
class FormatResponse(BaseModel):
"""格式化代码的响应模式"""
code: str
```
- [ ] **Step 4: 在 `main.py` 新增 `/format` 端点**
修改 `codeapinew/main.py` 第 8 行的导入,加入新模型:
```python
from schemas import (
PresetCodeCreate,
AIAnalysisRequest,
DebugRequest,
FormatRequest,
FormatResponse,
)
```
在文件顶部其他导入旁加入:
```python
from formatter import format_code, FormatError
```
`/debug` 端点(第 127-139 行)之后新增:
```python
@app.post("/format", response_model=FormatResponse)
async def format_code_endpoint(request: FormatRequest) -> FormatResponse:
"""格式化代码"""
try:
formatted = format_code(request.code, request.language)
except FormatError as e:
raise HTTPException(status_code=400, detail=str(e))
return FormatResponse(code=formatted)
```
- [ ] **Step 5: 运行测试,确认通过**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run pytest -v
```
Expected: 全部测试(`test_formatter.py` + `test_main.py`PASS。
- [ ] **Step 6: Commit**
```bash
cd /home/xuyue/Projects/Code/codeapinew && git add schemas.py main.py test_main.py && git commit -m "feat: add POST /format endpoint"
```
---
## Task 4: 前端 API 客户端 `formatCode`
**Files:**
- Modify: `codenext/src/api.ts`
- [ ] **Step 1: 修改导入并新增 `formatCode`**
`codenext/src/api.ts` 第 3 行:
```ts
import { Code, Submission } from "./types"
```
改为:
```ts
import { Code, LANGUAGE, Submission } from "./types"
```
在文件末尾(`debug` 函数之后)新增:
```ts
export async function formatCode(code: string, language: LANGUAGE) {
const res = await api.post("/format", { code, language })
return res.data.code as string
}
```
- [ ] **Step 2: Commit**
```bash
cd /home/xuyue/Projects/Code/codenext && git add src/api.ts && git commit -m "feat: add formatCode API client"
```
---
## Task 5: 前端状态 action `format()`
**Files:**
- Modify: `codenext/src/composables/code.ts`
- [ ] **Step 1: 修改导入并新增 `format` action**
`codenext/src/composables/code.ts` 第 5 行:
```ts
import { getCodeByQuery, submit } from "../api"
```
改为:
```ts
import { formatCode, getCodeByQuery, submit } from "../api"
```
在文件末尾(`share` 函数之后)新增:
```ts
export async function format() {
code.value = await formatCode(code.value, code.language)
}
```
- [ ] **Step 2: Commit**
```bash
cd /home/xuyue/Projects/Code/codenext && git add src/composables/code.ts && git commit -m "feat: add format action to code composable"
```
---
## Task 6: "整理代码"按钮(`CodeSection.vue`+ 手动验证
**Files:**
- Modify: `codenext/src/desktop/CodeSection.vue`
- [ ] **Step 1: 修改导入**
`codenext/src/desktop/CodeSection.vue` 第 7 行:
```ts
import { code, input, reset, size } from "../composables/code"
```
改为:
```ts
import { code, format, input, reset, size } from "../composables/code"
```
- [ ] **Step 2: 新增 `handleFormat` 函数**
`copy` 函数(第 20-23 行)之后新增:
```ts
async function handleFormat() {
try {
await format()
message.success("代码已整理")
} catch (err: any) {
message.error(
`整理失败: ${err?.response?.data?.detail ?? err?.message ?? "未知错误"}`,
)
}
}
```
- [ ] **Step 3: 在 actions 插槽中加入按钮**
将模板中第 68-80 行的 `#actions` 插槽:
```vue
<template #actions>
<n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="reset">清空</n-button>
<n-button
v-if="code.language === 'python'"
quaternary
type="error"
:disabled="!code.value"
@click="handleDebug"
>
调试
</n-button>
</template>
```
改为:
```vue
<template #actions>
<n-button quaternary type="primary" @click="copy">复制</n-button>
<n-button quaternary @click="handleFormat">整理代码</n-button>
<n-button quaternary @click="reset">清空</n-button>
<n-button
v-if="code.language === 'python'"
quaternary
type="error"
:disabled="!code.value"
@click="handleDebug"
>
调试
</n-button>
</template>
```
- [ ] **Step 4: 启动前后端,手动验证**
```bash
cd /home/xuyue/Projects/Code/codeapinew && uv run python main.py
```
```bash
cd /home/xuyue/Projects/Code/codenext && npm run start
```
打开 `http://localhost:3000`,对每种语言验证:
- Python输入 `x=1\ny =2`,点击"整理代码",确认变为 `x = 1\ny = 2`,并提示"代码已整理"
- C选择 C 语言,输入 `int main(){int x=1;return x;}`,点击"整理代码"确认大括号换行Allman 风格)、缩进 4 空格
- C++:同上,验证 `class Foo{};` 格式化结果
- 海龟绘图:输入含 `t=1` 的代码,验证按 Python 格式化
- 错误场景:输入语法错误的 Python`def foo(:`),点击"整理代码",确认弹出错误提示且编辑器内容不变
- [ ] **Step 5: Commit**
```bash
cd /home/xuyue/Projects/Code/codenext && git add src/desktop/CodeSection.vue && git commit -m "feat: add format code button to editor toolbar"
```
---
## Self-Review Notes
- Spec coverage: `/format` 接口Task 2-3、依赖安装Task 1、前端按钮与调用链Task 4-6、错误处理Task 3 的 400 响应 + Task 6 的 message.error、海龟绘图复用 python 格式化Task 2/3 测试覆盖)均已覆盖。移动端按设计明确排除,无需任务。
- 类型一致性:`formatCode(code, language)``api.ts``composables/code.ts``formatter.py``format_code(code, language)` 参数顺序与命名一致;`FormatRequest`/`FormatResponse` 字段名 `code`/`language` 与前端请求体一致。

View File

@@ -0,0 +1,52 @@
# 代码格式化(整理代码)功能设计
## 背景
`codenext` 编辑器支持 Python含海龟绘图、C、C++ 四种语言,目前没有一键格式化代码的功能。需要类似 Prettier 的"整理代码"按钮。
## 方案
后端 `codeapinew` 新增 `POST /format` 接口,按语言调用对应的格式化工具;前端在编辑器工具栏新增"整理代码"按钮调用该接口并替换编辑器内容。
### 后端
**Schema**`schemas.py`
```python
class FormatRequest(BaseModel):
code: str
language: str # python | turtle | c | cpp
class FormatResponse(BaseModel):
code: str
```
**接口**`main.py`
- `POST /format`
- `language``python``turtle` → 调用 `ruff format -`(通过 stdin/stdout 管道)
- `language``c``cpp` → 调用 `clang-format`,使用自定义 style
`-style="{BasedOnStyle: LLVM, IndentWidth: 4, BreakBeforeBraces: Allman}"`
(贴近现有模板的 4 空格缩进 + Allman 大括号风格)
-`subprocess.run`设置超时5 秒)
- 工具返回非零退出码(语法错误等)时,返回 HTTP 400附带 stderr 作为错误信息;原代码不受影响
### 依赖
- `pyproject.toml` 增加 `ruff` 依赖(同步更新 `requirements.txt`
- `Dockerfile``apt-get install` 增加 `clang-format`
### 前端(`codenext`
- `src/api.ts` 新增 `formatCode(code: string, language: LANGUAGE)`,调用 `POST /format`
- `src/composables/code.ts` 新增 `format()`:调用接口成功后替换 `code.value`(自动触发已有的 localStorage 缓存逻辑);失败时抛出错误供调用方提示
- `src/desktop/CodeSection.vue``#actions` 插槽中增加"整理代码"按钮(位于"复制"和"清空"之间),点击调用 `format()`,失败时通过 `useMessage().error(...)` 提示错误信息
## 错误处理
- 网络/接口错误或格式化失败:弹出错误提示,编辑器内容不变
- 超时:视为格式化失败,同样提示并保持原内容
## 范围
仅涉及 `codeapinew`(新增 `/format` 接口及依赖)和 `codenext`(新增按钮与调用逻辑)。移动端 `CodeEditor` 未使用 `label`,不渲染 `#actions` 插槽,与现有"调试"按钮一致,移动端不加入该功能。

45
formatter.py Normal file
View File

@@ -0,0 +1,45 @@
import subprocess
TIMEOUT_SECONDS = 5
CLANG_FORMAT_STYLE = "{BasedOnStyle: LLVM, IndentWidth: 4, BreakBeforeBraces: Attach}"
class FormatError(Exception):
"""代码格式化失败时抛出message 为可展示给用户的错误信息"""
def _run(cmd: list[str], code: str) -> str:
try:
result = subprocess.run(
cmd,
input=code,
capture_output=True,
text=True,
timeout=TIMEOUT_SECONDS,
)
except subprocess.TimeoutExpired:
raise FormatError("格式化超时")
if result.returncode != 0:
raise FormatError(result.stderr.strip() or "格式化失败")
return result.stdout
def format_code(code: str, language: str) -> str:
if language in ("python", "turtle"):
return _run(["ruff", "format", "-", "--stdin-filename", "main.py"], code)
if language in ("c", "cpp"):
ext = "c" if language == "c" else "cpp"
return _run(
[
"clang-format",
f"-style={CLANG_FORMAT_STYLE}",
f"-assume-filename=main.{ext}",
],
code,
)
raise FormatError(f"不支持的语言: {language}")

19
main.py
View File

@@ -5,9 +5,16 @@ from fastapi.responses import StreamingResponse
import os
import json
from openai import OpenAI
from schemas import PresetCodeCreate, AIAnalysisRequest, DebugRequest
from schemas import (
PresetCodeCreate,
AIAnalysisRequest,
DebugRequest,
FormatRequest,
FormatResponse,
)
from database import DatabaseService
from pg_logger import exec_script_str_local
from formatter import format_code, FormatError
from dotenv import load_dotenv
@@ -139,6 +146,16 @@ async def debug(request: DebugRequest):
return {"data": data}
@app.post("/format", response_model=FormatResponse)
async def format_code_endpoint(request: FormatRequest) -> FormatResponse:
"""格式化代码"""
try:
formatted = format_code(request.code, request.language)
except FormatError as e:
raise HTTPException(status_code=400, detail=str(e))
return FormatResponse(code=formatted)
if __name__ == "__main__":
import uvicorn

View File

@@ -12,4 +12,10 @@ dependencies = [
"tortoise-orm>=0.25.1",
"aiosqlite==0.21",
"uvicorn[standard]>=0.38.0",
"ruff>=0.15.17",
]
[dependency-groups]
dev = [
"pytest>=9.1.0",
]

View File

@@ -20,6 +20,7 @@ pypika-tortoise==0.6.3
python-dotenv==1.2.1
pytz==2025.2
pyyaml==6.0.3
ruff==0.15.17
sniffio==1.3.1
starlette==0.50.0
tortoise-orm==0.25.2

View File

@@ -32,3 +32,16 @@ class DebugRequest(BaseModel):
"""调试请求模式,用于调试 Python 代码"""
code: str
inputs: List[str]
class FormatRequest(BaseModel):
"""格式化代码的请求模式"""
code: str
language: str
class FormatResponse(BaseModel):
"""格式化代码的响应模式"""
code: str

33
test_formatter.py Normal file
View File

@@ -0,0 +1,33 @@
import pytest
from formatter import FormatError, format_code
def test_format_python_normalizes_spacing():
result = format_code("x=1\n", "python")
assert result == "x = 1\n"
def test_format_turtle_uses_python_formatter():
result = format_code("t=1\n", "turtle")
assert result == "t = 1\n"
def test_format_python_syntax_error_raises():
with pytest.raises(FormatError):
format_code("def foo(:\n", "python")
def test_format_c_uses_allman_braces():
result = format_code("int main(){int x=1;return x;}", "c")
assert result.rstrip("\n") == "int main()\n{\n int x = 1;\n return x;\n}"
def test_format_cpp_uses_allman_braces():
result = format_code("class Foo{};", "cpp")
assert result.rstrip("\n") == "class Foo\n{\n};"
def test_format_unsupported_language_raises():
with pytest.raises(FormatError):
format_code("print(1)", "java")

18
test_main.py Normal file
View File

@@ -0,0 +1,18 @@
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_format_endpoint_formats_python_code():
response = client.post("/format", json={"code": "x=1\n", "language": "python"})
assert response.status_code == 200
assert response.json() == {"code": "x = 1\n"}
def test_format_endpoint_returns_400_on_syntax_error():
response = client.post(
"/format", json={"code": "def foo(:\n", "language": "python"}
)
assert response.status_code == 400

87
uv.lock generated
View File

@@ -76,10 +76,16 @@ dependencies = [
{ name = "openai" },
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "ruff" },
{ name = "tortoise-orm" },
{ name = "uvicorn", extra = ["standard"] },
]
[package.dev-dependencies]
dev = [
{ name = "pytest" },
]
[package.metadata]
requires-dist = [
{ name = "aiosqlite", specifier = "==0.21" },
@@ -87,10 +93,14 @@ requires-dist = [
{ name = "openai", specifier = ">=2.6.0" },
{ name = "pydantic", specifier = ">=2.12.3" },
{ name = "python-dotenv", specifier = ">=1.1.1" },
{ name = "ruff", specifier = ">=0.15.17" },
{ name = "tortoise-orm", specifier = ">=0.25.1" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.38.0" },
]
[package.metadata.requires-dev]
dev = [{ name = "pytest", specifier = ">=9.1.0" }]
[[package]]
name = "colorama"
version = "0.4.6"
@@ -199,6 +209,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
[[package]]
name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]]
name = "iso8601"
version = "2.1.0"
@@ -295,6 +314,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/27/4b/7c1a00c2c3fbd004253937f7520f692a9650767aa73894d7a34f0d65d3f4/openai-2.14.0-py3-none-any.whl", hash = "sha256:7ea40aca4ffc4c4a776e77679021b47eec1160e341f42ae086ba949c9dcc9183", size = 1067558, upload-time = "2025-12-19T03:28:43.727Z" },
]
[[package]]
name = "packaging"
version = "26.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "pydantic"
version = "2.12.5"
@@ -381,6 +418,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
]
[[package]]
name = "pygments"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
[[package]]
name = "pypika-tortoise"
version = "0.6.3"
@@ -390,6 +436,22 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/6a/da5ba6830dd16cea2804163a2cecc1b2a85b8e06c61f0abb0477069d013d/pypika_tortoise-0.6.3-py3-none-any.whl", hash = "sha256:762e508093f4d73d3654cdde5bce8f92f8f41d999993c44d972d4f1703a663df", size = 46918, upload-time = "2025-11-26T22:07:07.052Z" },
]
[[package]]
name = "pytest"
version = "9.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" },
]
[[package]]
name = "python-dotenv"
version = "1.2.1"
@@ -454,6 +516,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
]
[[package]]
name = "ruff"
version = "0.15.17"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" },
{ url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" },
{ url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" },
{ url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" },
{ url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" },
{ url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" },
{ url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" },
{ url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" },
{ url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" },
{ url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" },
{ url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" },
{ url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" },
{ url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" },
{ url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" },
{ url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" },
{ url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"