feat: format SQL code via sqlparse in the /format endpoint
This commit is contained in:
15
formatter.py
15
formatter.py
@@ -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}")
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
11
uv.lock
generated
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user