refactor(download): extract execute_download per-URL loop into _download_one_url / _copy_local_one / _stream_http_one (BE-3273)#531
Conversation
…py_local_one / _stream_http_one (BE-3273) execute_download's per-URL loop body had grown to ~220 lines spanning two download branches (local copy + HTTP stream), four cap/truncation checks, part-file lifecycle, and entry construction — all inline at four levels of nesting. Lift it into three helpers placed with the other _-helpers: - _download_one_url: naming, collision-safe path, symlink-dest refusal, branch dispatch, entry construction. - _copy_local_one: the local-output copy branch. - _stream_http_one: the HTTP stream branch. Strictly behavior-preserving: every guard, guard ORDER, renderer.error code /message/details key, and typer.Exit(code=1) is carried over verbatim. The _local_source_path call stays pinned behind the is_local_job SSRF gate (not hoisted), and _assert_download_url still precedes the Request build. execute_download's loop is now 13 lines of orchestration. All 51 pre-existing tests in test_transfer_download.py pass unmodified; 11 new direct unit tests pin each extracted guard.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 27 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
There was a problem hiding this comment.
🔍 Cursor Review — Consolidated panel
Triggered by @mattmillerai.
Found 1 finding(s).
| Severity | Count |
|---|---|
| 🟢 Low | 1 |
Panel: 4/8 reviewers contributed findings.
Reviewers that did not contribute: claude-opus-4-8-thinking-xhigh:adversarial (parse_error), kimi-k2.5:adversarial (empty), claude-opus-4-8-thinking-xhigh:edge-case (parse_error), kimi-k2.5:edge-case (empty)
CI status: the two red checks are pre-existing
|
|
Correction to my comment above: the tomlkit breakage is already tracked — no new follow-up needed. It is covered by three open PRs: #533 and #536 (re-emit the So |
ELI-5
comfy download's main function had one giantforloop that did everything for each file: figure out the filename, refuse to write through a symlink, then either copy a file off your disk (local jobs) or stream it over HTTP (cloud jobs) — with four separate size/truncation checks and a temp-.part-file dance along the way. All of it was inline, four levels of indentation deep, ~220 lines.This PR moves that loop body into three named functions. Nothing about how
comfy downloadbehaves changes — same files, same filenames, same error messages, same exit codes. It's a pure "cut and paste into a function" refactor, with new tests that pin each safety check so a future edit can't quietly remove one.What changed
execute_download's per-URL loop (was ~220 lines) → 13 lines of orchestration:Three new
_-helpers, placed with the other_-helpers intransfer.py(no new module):_copy_local_onetransfer.py:468_stream_http_onetransfer.py:526_download_one_urltransfer.py:655entry["path"]is alreadystr(local_path.resolve()), identical to the previoussaved_pathsappend.Guard-preservation checklist (BE-3267 findings)
Every guard from the spike, with where it landed. Verified mechanically: I diffed the normalized (whitespace-stripped) old loop body against the new helper bodies — every
code=,message=,hint=,details={...}key andraise typer.Exit(code=1)is identical._local_source_pathpinned behindis_local_job, NOT hoisted676— sole call site in the file697700710/712is_file()check480stat()cap493/502_copy_local_output_capped+(OSError, ValueError)envelope515_assert_download_urlBEFOREurllib.request.Request535→545req546560565582596total != expected)609part_path.replacesuccess rename614HTTPError/(OSError, HTTPException)envelopes617/625finallycleanup645url/path/size+ optionalnode_id/item)715–725Constants
_MAX_DOWNLOAD_BYTES,_DOWNLOAD_TIMEOUT_S,_DOWNLOAD_CHUNK,_DOWNLOAD_OPENERare used unchanged. Guard order is preserved in both helpers.Tests
test_transfer_download.pypass with ZERO modifications — the test-file diff is265 insertions(+), 0 deletions(-), purely additive.TestDownloadHelperExtraction), one per checklist item:_download_one_url,is_local_job=False+ bare-path url →_local_source_pathmonkeypatched to raise proves it is not called; SSRF assert rejects (download_failed, exit 1)._download_one_url,is_local_job=True+ real temp source → copies, entry has correctpath/size, per-item counter advances across two calls sharingitem_counters._copy_local_output_cappedand_DOWNLOAD_OPENER.openboth monkeypatched to raise if reached)._stream_http_one→ declared-over-cap refused before body read (resp.reads == 0); over-declared mid-stream; running cap; truncation; success renames part → final leaving no.partsibling._copy_local_one→ missing source; stat-cap breach;OSError/ValueErrorfrom_copy_local_output_cappedsurfaces as envelope (parametrized).fake_target/_FakeRespfakes — no new harness.Verification run in the worktree:
ruff check+ruff format --check— cleanpytest tests/comfy_cli/command/test_transfer_download.py— 77 passed, 1 skipped (was 65+1; +12 cases from 11 new functions, one parametrized ×2)pytest tests/(full suite) — 2585 passed, 37 skipped, 0 failedJudgment calls
transfer.py@ 858 lines with the loop at 617–837 — that is exactlyorigin/maintoday, and it matched perfectly, so the plan was followed as written. (Anchors confirmed againstorigin/main, not a local branch.)transfer.py. De-indenting the HTTP branch by four nesting levels let the over-declared message's implicit string concat fit on one line, soruff formatcollapsed:ruff_checkCI requires it. This is the only textual difference between the old loop body and the new helper bodies; everything else is verbatim._download_one_urlkeeps a 9-arg signature (per the ticket's spec) rather than introducing a context dataclass — a dataclass would have been a design change, not a mechanical extraction.Notes for the reviewer
_collision_safe_pathdeliberately treats a symlink as "taken" and suffixes past it, so a symlink planted at the base name can never reach theis_symlink()guard. To exercise the guard as a unit, the test pins_collision_safe_pathto return the planted-symlink path. The guard remains defense-in-depth against a TOCTOU race — that behavior is unchanged by this PR.raise typer.Exit(code=1)is a pre-existing guard relocated verbatim, and the full suite proves the existing behavior still holds.transfer.pyiscommand/project.py→_upload_file, which this PR does not touch.