feat: format SQL code via sqlparse in the /format endpoint

This commit is contained in:
2026-07-05 00:07:20 -06:00
parent 8ca04c2e17
commit 7355610f37
5 changed files with 49 additions and 0 deletions

View File

@@ -1,5 +1,7 @@
import subprocess import subprocess
import sqlparse
TIMEOUT_SECONDS = 5 TIMEOUT_SECONDS = 5
CLANG_FORMAT_STYLE = "{BasedOnStyle: LLVM, IndentWidth: 4, BreakBeforeBraces: Attach}" CLANG_FORMAT_STYLE = "{BasedOnStyle: LLVM, IndentWidth: 4, BreakBeforeBraces: Attach}"
@@ -27,6 +29,16 @@ def _run(cmd: list[str], code: str) -> str:
return result.stdout return result.stdout
def _format_with_sql(code: str) -> str:
# sqlparse 对语法错误宽容,不会抛异常,语法问题留给判题阶段反馈
# strip_whitespace 会把多条语句压成一行,先按分号拆分再逐条格式化
statements = sqlparse.split(code)
return "\n\n".join(
sqlparse.format(s, strip_whitespace=True, keyword_case="upper")
for s in statements
)
def format_code(code: str, language: str) -> str: def format_code(code: str, language: str) -> str:
if language in ("python", "turtle"): if language in ("python", "turtle"):
return _run(["ruff", "format", "-", "--stdin-filename", "main.py"], code) return _run(["ruff", "format", "-", "--stdin-filename", "main.py"], code)
@@ -42,4 +54,7 @@ def format_code(code: str, language: str) -> str:
code, code,
) )
if language == "sql":
return _format_with_sql(code)
raise FormatError(f"不支持的语言: {language}") raise FormatError(f"不支持的语言: {language}")

View File

@@ -13,6 +13,7 @@ dependencies = [
"aiosqlite==0.21", "aiosqlite==0.21",
"uvicorn[standard]>=0.38.0", "uvicorn[standard]>=0.38.0",
"ruff>=0.15.17", "ruff>=0.15.17",
"sqlparse>=0.5.5",
] ]
[dependency-groups] [dependency-groups]

View File

@@ -21,6 +21,7 @@ python-dotenv==1.2.1
pytz==2025.2 pytz==2025.2
pyyaml==6.0.3 pyyaml==6.0.3
ruff==0.15.17 ruff==0.15.17
sqlparse==0.5.5
sniffio==1.3.1 sniffio==1.3.1
starlette==0.50.0 starlette==0.50.0
tortoise-orm==0.25.2 tortoise-orm==0.25.2

View File

@@ -31,3 +31,24 @@ def test_format_cpp_uses_allman_braces():
def test_format_unsupported_language_raises(): def test_format_unsupported_language_raises():
with pytest.raises(FormatError): with pytest.raises(FormatError):
format_code("print(1)", "java") format_code("print(1)", "java")
def test_format_sql_uppercases_keywords():
result = format_code("select * from students where score>60", "sql")
assert result == "SELECT * FROM students WHERE score>60"
def test_format_sql_splits_multiple_statements():
result = format_code(
"delete from students where score<60;insert into students (id) values (9);",
"sql",
)
assert result == (
"DELETE FROM students WHERE score<60;\n\n"
"INSERT INTO students (id) VALUES (9);"
)
def test_format_sql_tolerates_syntax_errors():
result = format_code("select from where", "sql")
assert result == "SELECT FROM WHERE"

11
uv.lock generated
View File

@@ -77,6 +77,7 @@ dependencies = [
{ name = "pydantic" }, { name = "pydantic" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "ruff" }, { name = "ruff" },
{ name = "sqlparse" },
{ name = "tortoise-orm" }, { name = "tortoise-orm" },
{ name = "uvicorn", extra = ["standard"] }, { name = "uvicorn", extra = ["standard"] },
] ]
@@ -94,6 +95,7 @@ requires-dist = [
{ name = "pydantic", specifier = ">=2.12.3" }, { name = "pydantic", specifier = ">=2.12.3" },
{ name = "python-dotenv", specifier = ">=1.1.1" }, { name = "python-dotenv", specifier = ">=1.1.1" },
{ name = "ruff", specifier = ">=0.15.17" }, { name = "ruff", specifier = ">=0.15.17" },
{ name = "sqlparse", specifier = ">=0.5.5" },
{ name = "tortoise-orm", specifier = ">=0.25.1" }, { name = "tortoise-orm", specifier = ">=0.25.1" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.38.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.38.0" },
] ]
@@ -550,6 +552,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
] ]
[[package]]
name = "sqlparse"
version = "0.5.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" },
]
[[package]] [[package]]
name = "starlette" name = "starlette"
version = "0.50.0" version = "0.50.0"