mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 05:06:59 +00:00
* fix(skills): support basename data-analysis exports Use the current directory when export output has no parent directory. * test(skills): cover data-analysis export paths Exercise real DuckDB queries, filesystem exports and cached CLI execution across CSV, JSON and Markdown. * docs(skills): record export path regression guidance Document basename and nested output expectations and the regression test command. * docs(skills): explain data-analysis export paths Clarify bare filenames, relative parent directories and absolute output paths. * docs: note supported data-analysis export paths Add a concise README explanation while retaining existing upstream documentation. * docs(skills): clarify export overwrite behavior Document that existing export targets are overwritten without warning, as suggested in PR #7132. Verified real CSV, JSON and Markdown overwrites; 27 export regression tests pass. --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
107 lines
3.6 KiB
Python
107 lines
3.6 KiB
Python
"""Exercise output paths with real DuckDB queries and filesystem exports."""
|
|
|
|
import csv
|
|
import json
|
|
import os
|
|
import runpy
|
|
import subprocess
|
|
import sys
|
|
from contextlib import chdir
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
duckdb = pytest.importorskip("duckdb")
|
|
pytest.importorskip("openpyxl")
|
|
SCRIPT = (
|
|
Path(__file__).resolve().parents[2]
|
|
/ "skills/public/data-analysis/scripts/analyze.py"
|
|
)
|
|
|
|
|
|
def assert_export(path, extension, empty=False):
|
|
if extension == "csv":
|
|
with path.open(encoding="utf-8", newline="") as handle:
|
|
assert list(csv.reader(handle)) == (
|
|
[["answer", "label"]]
|
|
if empty
|
|
else [["answer", "label"], ["42", "分析"]]
|
|
)
|
|
elif extension == "json":
|
|
assert json.loads(path.read_text(encoding="utf-8")) == (
|
|
[] if empty else [{"answer": 42, "label": "分析"}]
|
|
)
|
|
else:
|
|
assert path.read_text(encoding="utf-8") == (
|
|
"| answer | label |\n| --- | --- |\n" + ("" if empty else "| 42 | 分析 |\n")
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("extension", ["csv", "json", "md"])
|
|
@pytest.mark.parametrize("path_kind", ["basename", "dot", "nested", "absolute"])
|
|
@pytest.mark.parametrize("empty", [False, True])
|
|
def test_query_exports_to_requested_path(tmp_path, extension, path_kind, empty):
|
|
analysis = runpy.run_path(str(SCRIPT))
|
|
name = f"report.{extension}"
|
|
output = {
|
|
"basename": name,
|
|
"dot": f"./{name}",
|
|
"nested": f"new directory/reports/{name}",
|
|
"absolute": str(tmp_path / "absolute" / name),
|
|
}[path_kind]
|
|
sql = "SELECT 42 AS answer, '分析' AS label" + (" WHERE false" if empty else "")
|
|
with chdir(tmp_path), duckdb.connect() as con:
|
|
result = analysis["action_query"](con, sql, {}, output)
|
|
assert result == f"Results exported to {output} ({0 if empty else 1} rows)"
|
|
assert_export(Path(output), extension, empty)
|
|
|
|
|
|
@pytest.mark.parametrize("extension", ["csv", "json", "md"])
|
|
def test_cli_exports_basename_from_real_read_only_cache(tmp_path, extension):
|
|
# Build the real CSV cache without requiring the optional spatial extension.
|
|
# The subprocess then exercises main's normal cache-hit and export branches.
|
|
temp_dir = tmp_path / "runtime"
|
|
cache_dir = temp_dir / ".data-analysis-cache"
|
|
cache_dir.mkdir(parents=True)
|
|
source = tmp_path / "source.csv"
|
|
source.write_text("answer,label\n42,分析\n", encoding="utf-8")
|
|
analysis = runpy.run_path(str(SCRIPT))
|
|
key = analysis["compute_files_hash"]([str(source)])
|
|
table_map = {}
|
|
with duckdb.connect(str(cache_dir / f"{key}.duckdb")) as con:
|
|
analysis["_load_csv"](con, str(source), table_map)
|
|
(cache_dir / f"{key}.table_map.json").write_text(
|
|
json.dumps(table_map), encoding="utf-8"
|
|
)
|
|
output = f"report.{extension}"
|
|
completed = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
"-B",
|
|
str(SCRIPT),
|
|
"--files",
|
|
str(source),
|
|
"--action",
|
|
"query",
|
|
"--sql",
|
|
"SELECT * FROM source",
|
|
"--output-file",
|
|
output,
|
|
],
|
|
cwd=tmp_path,
|
|
env={
|
|
**os.environ,
|
|
"TMPDIR": str(temp_dir),
|
|
"TEMP": str(temp_dir),
|
|
"TMP": str(temp_dir),
|
|
},
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
check=False,
|
|
)
|
|
assert completed.returncode == 0, completed.stderr
|
|
assert "Cache hit!" in completed.stderr
|
|
assert f"Results exported to {output} (1 rows)" in completed.stdout
|
|
assert_export(tmp_path / output, extension)
|