feat(ci): Finalize Docker-based CI and OCR benchmark harness - #80
feat(ci): Finalize Docker-based CI and OCR benchmark harness#80seonghobae wants to merge 1 commit into
Conversation
- Implements the OCR benchmark harness and its unit test. - Introduces a Dockerfile to create a stable test environment. - Overhauls the CI workflow to build and use this Docker image for tests, resolving persistent dependency and environment issues. This includes fixing the image tag to be lowercase.
📝 WalkthroughWalkthrough워크플로우가 Docker 기반 테스트 환경 이미지를 별도의 작업에서 구성하여 ghcr.io로 푸시하고, 테스트를 해당 컨테이너 내에서 실행하도록 변경됩니다. 새로운 OCR 벤치마크 도구와 해당 테스트가 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant CLI as benchmark_ocr CLI
participant Runner as run_benchmark()
participant Engine as OCR_ENGINES[engine_name]
participant Mineru as run_mineru_engine()
participant Output as output.json
User->>CLI: Execute with engines & output path
CLI->>Runner: Call main(argv)
Runner->>Runner: Validate requested engines
loop For each PDF fixture
loop For each selected engine
Runner->>Engine: Execute engine handler
alt Success
Engine->>Mineru: Parse PDF bytes
Mineru-->>Engine: Return metrics
Engine-->>Runner: {status: "success", ...}
else RuntimeError/CalledProcessError
Engine-->>Runner: {status: "failed", error: "..."}
else TimeoutExpired
Engine-->>Runner: {status: "timed_out"}
end
Runner->>Runner: Aggregate results
end
end
Runner->>Output: Write JSON (per-engine + per-file summaries)
Output-->>User: Benchmark report
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
tests/test_benchmark_ocr.py (1)
30-41: 목(mock) 엔진이 실제 서비스 예외 계층과 달라 하네스의 예외 처리 허점을 감추고 있습니다.테스트는
RuntimeError와subprocess.TimeoutExpired만 발생시키는데, 실제mineru엔진 경로(parse_pdf_bytes→run_mineru)는fastapi.HTTPException을 던집니다. 이 때문에tools/benchmark_ocr.py의 실제 프로덕션 실패 경로에 대한 회귀 테스트가 전혀 없습니다(관련 이슈는tools/benchmark_ocr.py의 예외 처리 코멘트 참조). 하네스 수정 후HTTPException을 던지는 목 케이스도 하나 추가해 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_benchmark_ocr.py` around lines 30 - 41, The test's mock engines only raise subprocess.TimeoutExpired and RuntimeError, but production code (parse_pdf_bytes → run_mineru) can raise fastapi.HTTPException; update the mock sequence for mock_engine2 in tests/test_benchmark_ocr.py to include a fastapi.HTTPException instance as one of the side_effect entries so the benchmark_ocr failure path in tools/benchmark_ocr.py is exercised; ensure you import fastapi.HTTPException in the test and add the HTTPException case before the successful response in mock_engine2.side_effect so the code paths handling HTTP errors are covered.Dockerfile.test (1)
19-28:uv sync를 소스 코드 없이 실행하면 setuptools 프로젝트 빌드가 실패할 수 있습니다.
pyproject.toml이 setuptools를 빌드 백엔드로 사용하며[project]섹션으로 선언되어 있는데, 소스 코드 복사 전에uv sync --frozen --all-extras를 실행하면 패키지 빌드 단계에서 필요한 소스 파일이 없어 실패하거나 불완전한 상태가 될 수 있습니다. Docker 레이어 캐시를 최적화하려면 의존성만 먼저 설치한 후 소스 코드를 복사하고 프로젝트까지 설치하는 패턴을 권장합니다.♻️ 제안 수정안
COPY pyproject.toml uv.lock ./ RUN pip install uv==0.1.41 -RUN uv sync --frozen --all-extras +# 의존성만 먼저 설치 (프로젝트 자체 제외)하여 레이어 캐시 최대화 +RUN uv sync --frozen --all-extras --no-install-project COPY . . +# 프로젝트 자체까지 설치 +RUN uv sync --frozen --all-extras🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Dockerfile.test` around lines 19 - 28, The Dockerfile runs "RUN uv sync --frozen --all-extras" before copying the project sources, which breaks setuptools-based builds declared in pyproject.toml; fix by installing uv with "RUN pip install uv==0.1.41", then copying the full source (the "COPY . ." step) before running "uv sync --frozen --all-extras" so the build backend and package sources are available during dependency sync; keep "COPY pyproject.toml uv.lock ./" before to leverage layer caching for unchanged dependency metadata.tools/benchmark_ocr.py (1)
29-30:page_count/article_count는 PydanticParseResponse에서 파생되며, 스키마 변경 시 AttributeError 위험이 있습니다.
ParseResponse.pages(line 68)와PageNode.articles(line 51)는src/newsdom_api/schemas.py에 현재 정의되어 있으나, 위의 예외 핸들러(line 72-89)는RuntimeError,subprocess.CalledProcessError,subprocess.TimeoutExpired만 처리하며AttributeError는 포함되지 않습니다. 향후 스키마에서 이 속성들이 제거되면 벤치마크는 포착되지 않은AttributeError로 실패하게 됩니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tools/benchmark_ocr.py` around lines 29 - 30, The code computes page_count and article_count from ParseResponse.pages and PageNode.articles directly, which can raise AttributeError if the schema changes; update the benchmark to compute these defensively (e.g., use getattr(response, "pages", []) and sum(len(getattr(p, "articles", [])) for p in getattr(response, "pages", []))) or wrap the computation in a try/except that also catches AttributeError, and if caught handle it the same way as the existing RuntimeError/subprocess.CalledProcessError/subprocess.TimeoutExpired handling so the benchmark fails gracefully.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/tests.yml:
- Line 20: Replace every workflow action reference that's using a tag (e.g.,
actions/checkout@v4, docker/setup-buildx-action@v3, docker/login-action@v3,
docker/build-push-action@v5) with the corresponding official release commit SHA
and keep the tag as an inline comment for readability (for example replace
"actions/checkout@v4" with "actions/checkout@<full-sha> # v4.x.y"); update all
occurrences noted (the first actions/checkout@v4 and the other instances at the
indicated lines) so every "uses:" entry is pinned by SHA rather than by tag.
- Around line 46-59: The tests workflow (.github/workflows/tests.yml) removed
the astral-sh/setup-uv step causing the quality-gate test
test_tests_workflow_pins_uv_version to fail because it expects an action SHA
pin; either restore the setup step with a pinned reference (add
astral-sh/setup-uv@<sha> back into the pytest job) or modify the quality-gate
check to validate the uv version is pinned inside the container build (e.g.,
check Dockerfile.test or the test image build step for a pip install uv==<ver>);
pick the container-first approach if you prefer, then update the quality-gate
test to look for a concrete uv version in Dockerfile.test (or wherever uv is
installed during build) instead of requiring astral-sh/setup-uv@<sha>.
- Around line 32-34: The image name step (id: image_name) currently uses the
Actions expression `${{ github.repository_owner,, }}` which is invalid; change
the run script to capture `${{ github.repository_owner }}` into a shell
variable, lowercase it using Bash parameter expansion (e.g.,
owner_low="${owner,,}"), then echo the image name using that lowercased variable
to $GITHUB_OUTPUT so GHCR receives a valid lowercase owner; update the run body
in the image_name step accordingly.
- Around line 58-59: The CI step running "uv run pytest" is executed from
/github/workspace so it cannot reuse the Docker image's /app/.venv; update the
workflow to run pytest from the correct project root by either setting the job
step's working-directory to /app, changing the run command to call the venv
directly with "run: /app/.venv/bin/pytest", or setting the environment variable
UV_PROJECT_ENVIRONMENT=/app/.venv so that "uv run pytest" picks up the existing
virtualenv; modify the step that currently contains "run: uv run pytest"
accordingly.
In `@Dockerfile.test`:
- Line 22: The Dockerfile currently pins uv to an old release via the RUN pip
install uv==0.1.41 instruction; update the pin to the current stable 0.11.x
series (e.g., 0.11.7) to pick up bug fixes and behavioral changes in uv sync, or
switch to the recommended installer (use the independent install script or
GitHub Action like astral-sh/setup-uv) for container builds; change the RUN pip
install line (RUN pip install uv==0.1.41) to either install uv==0.11.7 or invoke
the recommended installer step so CI uses the modern stable version.
In `@tools/benchmark_ocr.py`:
- Around line 80-108: The benchmark loop currently only catches RuntimeError,
subprocess.CalledProcessError and subprocess.TimeoutExpired but mineru_runner
wraps errors in fastapi.HTTPException (504 for timeout, 500 for failures) and
can raise FileNotFoundError, so update the except handling in the try block that
calls engine_runner(pdf_path) (the run_benchmark / loop that contains
engine_runner) to also catch fastapi.HTTPException and FileNotFoundError (and
optionally a final broad Exception fallback); when catching
fastapi.HTTPException inspect e.status_code and treat 504 as "timed_out" and
other codes as "failed" (increment engine_results["timed_out"] or ["failed"],
set engine_results["results"][pdf_path.name] with status, duration and error),
treat FileNotFoundError as "failed" with the error message, and ensure a final
except Exception records unexpected errors as "failed" so the benchmark
continues and partial results are saved.
---
Nitpick comments:
In `@Dockerfile.test`:
- Around line 19-28: The Dockerfile runs "RUN uv sync --frozen --all-extras"
before copying the project sources, which breaks setuptools-based builds
declared in pyproject.toml; fix by installing uv with "RUN pip install
uv==0.1.41", then copying the full source (the "COPY . ." step) before running
"uv sync --frozen --all-extras" so the build backend and package sources are
available during dependency sync; keep "COPY pyproject.toml uv.lock ./" before
to leverage layer caching for unchanged dependency metadata.
In `@tests/test_benchmark_ocr.py`:
- Around line 30-41: The test's mock engines only raise
subprocess.TimeoutExpired and RuntimeError, but production code (parse_pdf_bytes
→ run_mineru) can raise fastapi.HTTPException; update the mock sequence for
mock_engine2 in tests/test_benchmark_ocr.py to include a fastapi.HTTPException
instance as one of the side_effect entries so the benchmark_ocr failure path in
tools/benchmark_ocr.py is exercised; ensure you import fastapi.HTTPException in
the test and add the HTTPException case before the successful response in
mock_engine2.side_effect so the code paths handling HTTP errors are covered.
In `@tools/benchmark_ocr.py`:
- Around line 29-30: The code computes page_count and article_count from
ParseResponse.pages and PageNode.articles directly, which can raise
AttributeError if the schema changes; update the benchmark to compute these
defensively (e.g., use getattr(response, "pages", []) and sum(len(getattr(p,
"articles", [])) for p in getattr(response, "pages", []))) or wrap the
computation in a try/except that also catches AttributeError, and if caught
handle it the same way as the existing
RuntimeError/subprocess.CalledProcessError/subprocess.TimeoutExpired handling so
the benchmark fails gracefully.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 414374f3-02a5-4467-82be-5d533f6916a1
📒 Files selected for processing (4)
.github/workflows/tests.ymlDockerfile.testtests/test_benchmark_ocr.pytools/benchmark_ocr.py
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
액션들이 SHA로 핀되어 있지 않아 quality-gate 파이프라인이 실패합니다.
리포지토리 정책상 워크플로우의 모든 액션은 SHA로 핀되어야 합니다(test_workflow_actions_are_pinned_by_sha 실패). 현재 actions/checkout@v4, docker/setup-buildx-action@v3, docker/login-action@v3, docker/build-push-action@v5, 그리고 하단의 actions/checkout@v4 모두 태그로만 참조되고 있어 이 중 첫 번째가 보고되어 있고 나머지도 순차적으로 실패할 것입니다. 각 액션을 공식 릴리스의 커밋 SHA로 교체하고, 가독성을 위해 태그를 주석으로 남겨 주세요.
🔒 예시 (버전 주석 유지)
- uses: actions/checkout@<full-sha> # v4.x.y
- uses: docker/setup-buildx-action@<full-sha> # v3.x.y
- uses: docker/login-action@<full-sha> # v3.x.y
- uses: docker/build-push-action@<full-sha> # v5.x.yAlso applies to: 23-23, 26-26, 37-37, 56-56
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/tests.yml at line 20, Replace every workflow action
reference that's using a tag (e.g., actions/checkout@v4,
docker/setup-buildx-action@v3, docker/login-action@v3,
docker/build-push-action@v5) with the corresponding official release commit SHA
and keep the tag as an inline comment for readability (for example replace
"actions/checkout@v4" with "actions/checkout@<full-sha> # v4.x.y"); update all
occurrences noted (the first actions/checkout@v4 and the other instances at the
indicated lines) so every "uses:" entry is pinned by SHA rather than by tag.
| - name: Define image name and tag | ||
| id: image_name | ||
| run: echo "name=ghcr.io/${{ github.repository_owner,, }}/newsdom-api/test:${{ github.sha }}" | tee -a $GITHUB_OUTPUT |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does the GitHub Actions expression language support the ,,Bash-style lowercase parameter expansion (e.g.,${{ github.repository_owner,, }})?
💡 Result:
No. GitHub Actions expressions do not support Bash-style lowercase parameter expansion syntax like ${{ github.repository_owner,, }}. GitHub’s expression language supports a defined set of literals, operators, and built-in functions (documented in the Expressions reference). It does not document shell-style parameter expansion operators ,, as part of the expression syntax, and the docs describe expressions as evaluating context references plus supported functions/operators rather than performing Bash parameter expansions. If you need lowercase, do it in a runner step using the shell (for example ${VAR,,} in Bash) or use an action designed for string case conversion (e.g., a JS-based action calling toLowerCase), then pass the result via outputs/env.
Citations:
- 1: https://docs.github.com/actions/reference/workflows-and-actions/expressions
- 2: https://docs.github.com/en/actions/reference/workflows-and-actions/expressions
- 3: https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions
- 4: https://stackoverflow.com/questions/62627931/github-actions-expression-functions-string-manipulation
- 5: https://github.com/ASzc/change-string-case-action
${{ github.repository_owner,, }}는 유효한 GitHub Actions 표현식이 아니므로 이미지 이름이 잘못 생성되어 푸시가 실패합니다.
,,는 Bash 파라미터 확장 문법이며, GitHub Actions 표현식 언어는 이를 지원하지 않습니다. github.repository_owner가 대문자를 포함하면 GHCR은 소문자 이름만 허용하므로 푸시가 실패합니다. Bash 스텝에서 소문자로 변환하고 결과를 출력으로 전달해야 합니다.
수정안
- - name: Define image name and tag
- id: image_name
- run: echo "name=ghcr.io/${{ github.repository_owner,, }}/newsdom-api/test:${{ github.sha }}" | tee -a $GITHUB_OUTPUT
+ - name: Define image name and tag
+ id: image_name
+ env:
+ OWNER: ${{ github.repository_owner }}
+ SHA: ${{ github.sha }}
+ run: |
+ owner_lc="${OWNER,,}"
+ echo "name=ghcr.io/${owner_lc}/newsdom-api/test:${SHA}" | tee -a "$GITHUB_OUTPUT"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Define image name and tag | |
| id: image_name | |
| run: echo "name=ghcr.io/${{ github.repository_owner,, }}/newsdom-api/test:${{ github.sha }}" | tee -a $GITHUB_OUTPUT | |
| - name: Define image name and tag | |
| id: image_name | |
| env: | |
| OWNER: ${{ github.repository_owner }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| owner_lc="${OWNER,,}" | |
| echo "name=ghcr.io/${owner_lc}/newsdom-api/test:${SHA}" | tee -a "$GITHUB_OUTPUT" |
🧰 Tools
🪛 actionlint (1.7.12)
[error] 34-34: parser did not reach end of input after parsing the expression. 2 remaining token(s) in the input: ",", ","
(expression)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/tests.yml around lines 32 - 34, The image name step (id:
image_name) currently uses the Actions expression `${{ github.repository_owner,,
}}` which is invalid; change the run script to capture `${{
github.repository_owner }}` into a shell variable, lowercase it using Bash
parameter expansion (e.g., owner_low="${owner,,}"), then echo the image name
using that lowercased variable to $GITHUB_OUTPUT so GHCR receives a valid
lowercase owner; update the run body in the image_name step accordingly.
| pytest: | ||
| name: Run Pytest in Container | ||
| runs-on: ubuntu-latest | ||
| needs: build-test-image | ||
| container: | ||
| image: ${{ needs.build-test-image.outputs.image_name }} | ||
| env: | ||
| PYTHONWARNINGS: error | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Run tests with warnings as errors | ||
| env: | ||
| PYTHONWARNINGS: error | ||
| - name: Run tests | ||
| run: uv run pytest |
There was a problem hiding this comment.
astral-sh/setup-uv 핀 부재로 quality-gate의 test_tests_workflow_pins_uv_version이 실패합니다.
이 PR은 uv 설치를 컨테이너 이미지 빌드 시점으로 옮겼기 때문에 워크플로우에서 astral-sh/setup-uv 스텝이 사라졌지만, 품질 게이트 테스트는 여전히 해당 액션의 SHA 핀 존재를 요구합니다. 둘 중 하나를 선택해야 합니다:
- 품질 게이트 테스트를 컨테이너 내
uv핀(예:Dockerfile.test의pip install uv==<ver>)을 검증하도록 업데이트, 또는 pytest잡에 다시astral-sh/setup-uv@<sha>를 추가(컨테이너 내부 uv를 선호한다면 중복이 됩니다).
컨테이너 기반 전략을 유지하는 방향이라면 첫 번째 옵션이 자연스럽습니다.
품질 게이트 테스트가 어느 파일에 있는지 알려주시면 컨테이너 내부 uv 핀을 검사하도록 수정하는 패치를 제안해 드릴 수 있습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/tests.yml around lines 46 - 59, The tests workflow
(.github/workflows/tests.yml) removed the astral-sh/setup-uv step causing the
quality-gate test test_tests_workflow_pins_uv_version to fail because it expects
an action SHA pin; either restore the setup step with a pinned reference (add
astral-sh/setup-uv@<sha> back into the pytest job) or modify the quality-gate
check to validate the uv version is pinned inside the container build (e.g.,
check Dockerfile.test or the test image build step for a pip install uv==<ver>);
pick the container-first approach if you prefer, then update the quality-gate
test to look for a concrete uv version in Dockerfile.test (or wherever uv is
installed during build) instead of requiring astral-sh/setup-uv@<sha>.
| - name: Run tests | ||
| run: uv run pytest |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
How does uv runbehave whenUV_PROJECT_ENVIRONMENTis unset and the current working directory differs from the directory whereuv sync was originally run? Does it create a new .venv?
💡 Result:
When UV_PROJECT_ENVIRONMENT is unset, uv run discovers the project root by searching upwards from the current working directory (CWD) for the nearest ancestor directory containing a pyproject.toml file (with a [project] table). It then uses the .venv in that discovered project root directory. If the CWD is within the project directory where uv sync was run (i.e., the project root or a subdirectory), uv run will find the same project root and use the existing .venv there (creating it if missing). If the CWD differs from that directory—specifically, if the CWD is outside the project tree (no pyproject.toml found upwards from CWD)—then uv run will not discover any project. In that case, it falls back to non-project behavior: it does not use or create a project .venv, but may create/use other environments depending on context (e.g., no persistent .venv creation tied to a project). uv run does not create a new .venv in the CWD or elsewhere unless using flags like --isolated or specific script modes. It only manages the project .venv when a project is discovered.
Citations:
- 1: Behaviour of
uv runanduv syncis inconsistent based on current directory in a workspace astral-sh/uv#13749 - 2: https://docs.astral.sh/uv/concepts/projects/run/
- 3: https://docs.astral.sh/uv/guides/projects/
- 4: https://docs.astral.sh/uv/concepts/projects/layout/
- 5: http://docs.astral.sh/uv/concepts/projects/layout/
- 6: Command to print project root directory astral-sh/uv#13636
- 7: How to find root uv config? astral-sh/uv#9354
- 8: Improve the behavior of
uv runwhen running from outside the project folder astral-sh/uv#14585 - 9: allow "uv run" to work without a project astral-sh/uv#8666
- 10:
uv runwith a different root directory astral-sh/uv#6733 - 11: How to use the parent directory
venvwithin subfolders? astral-sh/uv#10543
🏁 Script executed:
# Find and examine the Dockerfile.test to see the actual setup
find . -name "Dockerfile.test" -o -name "Dockerfile" -o -name "*.Dockerfile" | head -20Repository: Seongho-Bae/newsdom-api
Length of output: 127
🏁 Script executed:
# Look at the complete GitHub Actions workflow configuration
cat -n .github/workflows/tests.yml | head -80Repository: Seongho-Bae/newsdom-api
Length of output: 1932
🏁 Script executed:
# Check if pyproject.toml exists and where it's located
find . -name "pyproject.toml" -type fRepository: Seongho-Bae/newsdom-api
Length of output: 83
🏁 Script executed:
cat -n ./Dockerfile.testRepository: Seongho-Bae/newsdom-api
Length of output: 1382
🏁 Script executed:
cat -n ./pyproject.toml | head -30Repository: Seongho-Bae/newsdom-api
Length of output: 959
GHA 컨테이너 작업 단계에서 Dockerfile에 포함된 .venv 재사용이 불가능합니다.
actions/checkout@v4는 컨테이너 내에서 저장소를 기본적으로 /github/workspace에 체크아웃합니다. uv run pytest를 /github/workspace에서 실행하면, uv는 현재 디렉터리에서 pyproject.toml을 찾아 프로젝트 루트를 /github/workspace로 해석합니다. Dockerfile에서 /app에 구축한 .venv는 이 프로젝트 트리 내에 존재하지 않으므로 발견되지 않습니다. 이미지 빌드 단계의 노력이 활용되지 않으며, 대신 새 가상 환경이 필요하게 됩니다.
다음 중 하나로 해결할 수 있습니다:
- GHA 단계에서
working-directory: /app를 설정하여 올바른 프로젝트 루트에서 실행 run: /app/.venv/bin/pytest로 직접 호출env: UV_PROJECT_ENVIRONMENT=/app/.venv를 설정하여 명시적으로 가상 환경 경로 지정
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/tests.yml around lines 58 - 59, The CI step running "uv
run pytest" is executed from /github/workspace so it cannot reuse the Docker
image's /app/.venv; update the workflow to run pytest from the correct project
root by either setting the job step's working-directory to /app, changing the
run command to call the venv directly with "run: /app/.venv/bin/pytest", or
setting the environment variable UV_PROJECT_ENVIRONMENT=/app/.venv so that "uv
run pytest" picks up the existing virtualenv; modify the step that currently
contains "run: uv run pytest" accordingly.
| COPY pyproject.toml uv.lock ./ | ||
|
|
||
| # Install uv via pip | ||
| RUN pip install uv==0.1.41 |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🌐 Web query:
What is the current recommended stable version of the Python uv package manager in 2026, and are there any known issues with uv 0.1.41?
💡 Result:
The current recommended stable version of the Python uv package manager is 0.11.7, released on 2026-04-15. uv is described as stable and ready for production use. uv 0.1.41 was an early release from 2024-05-08 with a single documented bug fix: "Remove unconstrained version error from requirements (#3443)". No major or ongoing known issues are reported for it in recent sources; it appears to be a minor historical version superseded by many stable updates.
Citations:
- 1: https://github.com/astral-sh/uv
- 2: https://pypi.org/project/uv/
- 3: https://docs.astral.sh/uv/getting-started/installation/
- 4: https://docs.astral.sh/uv/
- 5: https://github.com/astral-sh/uv/blob/main/changelogs/0.1.x.md
uv==0.1.41 버전 핀이 매우 오래된 버전입니다
uv 0.1.41은 2024년 5월 릴리스된 버전으로, 현재 권장 안정 버전인 0.11.7(2026년 4월 릴리스)와 비교했을 때 상당한 기능 및 동작 차이가 있습니다. 특히 uv sync의 프로젝트 설치 동작이 변경되었을 가능성이 높습니다. CI 재현성을 유지하면서도 버그 픽스와 성능 개선을 반영할 수 있도록 최신 안정 버전(0.11.x)으로 핀을 업데이트하는 것을 권장합니다.
추가로, 공식 문서에서 권장하는 설치 방식은 독립 설치 스크립트 또는 GitHub Actions에서는 astral-sh/setup-uv를 사용하는 것입니다. 컨테이너 환경에서는 pip install uv==<version> 유지는 가능하지만, 버전을 최신 안정 버전으로 업그레이드하는 것이 좋습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@Dockerfile.test` at line 22, The Dockerfile currently pins uv to an old
release via the RUN pip install uv==0.1.41 instruction; update the pin to the
current stable 0.11.x series (e.g., 0.11.7) to pick up bug fixes and behavioral
changes in uv sync, or switch to the recommended installer (use the independent
install script or GitHub Action like astral-sh/setup-uv) for container builds;
change the RUN pip install line (RUN pip install uv==0.1.41) to either install
uv==0.11.7 or invoke the recommended installer step so CI uses the modern stable
version.
| try: | ||
| result = engine_runner(pdf_path) | ||
| duration = time.monotonic() - start_time | ||
| engine_results["success"] += 1 | ||
| engine_results["results"][pdf_path.name] = { | ||
| "status": "success", | ||
| "duration": round(duration, 2), | ||
| **result, | ||
| } | ||
| print(f" [SUCCESS] {pdf_path.name} in {duration:.2f}s") | ||
|
|
||
| except (RuntimeError, subprocess.CalledProcessError) as e: | ||
| duration = time.monotonic() - start_time | ||
| engine_results["failed"] += 1 | ||
| engine_results["results"][pdf_path.name] = { | ||
| "status": "failed", | ||
| "duration": round(duration, 2), | ||
| "error": str(e), | ||
| } | ||
| print(f" [FAILED] {pdf_path.name} in {duration:.2f}s") | ||
|
|
||
| except subprocess.TimeoutExpired: | ||
| duration = time.monotonic() - start_time | ||
| engine_results["timed_out"] += 1 | ||
| engine_results["results"][pdf_path.name] = { | ||
| "status": "timed_out", | ||
| "duration": round(duration, 2), | ||
| } | ||
| print(f" [TIMED OUT] {pdf_path.name} after {duration:.2f}s") |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# run_mineru 내부에서 어떤 예외 타입이 실제로 발생하는지 재확인
rg -nP --type=py -C2 'raise (HTTPException|FileNotFoundError|RuntimeError|subprocess\.)' src/newsdom_api/mineru_runner.pyRepository: Seongho-Bae/newsdom-api
Length of output: 1457
🏁 Script executed:
cat -n tools/benchmark_ocr.py | head -150Repository: Seongho-Bae/newsdom-api
Length of output: 5983
mineru 엔진의 실제 예외가 여기서 포착되지 않아 벤치마크가 중단될 수 있습니다.
parse_pdf_bytes → run_mineru의 구현(src/newsdom_api/mineru_runner.py)은 subprocess.TimeoutExpired와 subprocess.CalledProcessError를 fastapi.HTTPException으로 래핑해서 다시 던집니다(상태 코드 504, 500). 또한 mineru 실행 파일을 찾지 못하거나 필수 JSON 파일이 없으면 FileNotFoundError를 발생시킵니다.
현재 핸들러 (RuntimeError, subprocess.CalledProcessError)(91행)와 subprocess.TimeoutExpired(101행)는 이들 예외 중 어느 것도 매칭되지 않습니다. 따라서 mineru이 타임아웃하거나 실패할 때마다 예외가 루프 밖으로 전파되어 벤치마크 실행 자체가 중단되며, "failed"/"timed_out" 카운터가 증가하지 않고 부분 결과도 JSON으로 남지 않습니다.
가장 견고한 해법은 run_mineru_engine이 내부에서 HTTPException/FileNotFoundError를 의미 있는 예외로 변환하거나, 또는 run_benchmark의 except 절에 포괄적인 Exception 폴백을 추가해 예기치 않은 예외도 "failed"로 기록하는 것입니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tools/benchmark_ocr.py` around lines 80 - 108, The benchmark loop currently
only catches RuntimeError, subprocess.CalledProcessError and
subprocess.TimeoutExpired but mineru_runner wraps errors in
fastapi.HTTPException (504 for timeout, 500 for failures) and can raise
FileNotFoundError, so update the except handling in the try block that calls
engine_runner(pdf_path) (the run_benchmark / loop that contains engine_runner)
to also catch fastapi.HTTPException and FileNotFoundError (and optionally a
final broad Exception fallback); when catching fastapi.HTTPException inspect
e.status_code and treat 504 as "timed_out" and other codes as "failed"
(increment engine_results["timed_out"] or ["failed"], set
engine_results["results"][pdf_path.name] with status, duration and error), treat
FileNotFoundError as "failed" with the error message, and ensure a final except
Exception records unexpected errors as "failed" so the benchmark continues and
partial results are saved.
This PR fundamentally resolves the persistent CI failures (#77) by introducing a robust, container-based testing strategy. It also completes the implementation of the extensible OCR benchmark harness, providing the tooling to address #60.
Key Changes:
Docker-Based CI:
Dockerfile.testis introduced to create a self-contained environment with all system and Python dependencies (includingmineruandtesseract) pre-installed..github/workflows/tests.ymlworkflow is completely refactored into a two-stage pipeline:pytestinside the container from the build stage, ensuring a consistent and reliable environment.OCR Benchmark Harness:
tools/benchmark_ocr.pyscript, which can run multiple OCR engines against a corpus of PDFs and generate a JSON report of the results (success, failure, timeout).tests/test_benchmark_ocr.pyto unit-test the new harness.This PR unblocks the OCR benchmarking effort and significantly improves the stability and reliability of the entire CI process.
Closes #77
Addresses #60
Summary by CodeRabbit
릴리스 노트
새로운 기능
테스트
Chores