Skip to content

refactor: decompose show() into named stages#714

Merged
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h
Jun 14, 2026
Merged

refactor: decompose show() into named stages#714
timtreis merged 7 commits into
mainfrom
claude/open-issues-review-j70n0h

Conversation

@timtreis

@timtreis timtreis commented Jun 13, 2026

Copy link
Copy Markdown
Member

Closes #697.

Decomposes the 644-line show() god-function into named, single-purpose helpers. Behavior-preserving — no public API change.

Extracted helpers

_collect_render_commands · _normalize_title · _resolve_coordinate_systems · _plan_panels · _build_legend_params · _draw_colorbar (promoted from closure) · _layout_pending_colorbars · _render_panel · _finalize_panel · _should_rasterize · _maybe_set_label_colors

Also: _validate_show_parameters now called by keyword; per-render dispatch is a table; has_*/wants_* boolean sprawl replaced by cs_row + a wants dict.

Metrics (lizard, basic.py)

main this PR
show() cyclomatic complexity 109 30
show() NLOC 390 195
avg complexity / function 15.8 8.2

claude added 4 commits June 13, 2026 00:07
…tion

Decompose the show() god-function (#697). First, behavior-preserving steps:
- pass _validate_show_parameters args by keyword so a signature reorder
  can no longer silently misvalidate one parameter as another
- extract _collect_render_commands() and _normalize_title() module helpers

No behavior change.
…bar stages

Continue decomposing show() (#697), behavior-preserving:
- _resolve_coordinate_systems(): CS auto-detection, validation and filtering
- _plan_panels(): panel layout (one-per-CS vs one-per-color-key) + ax-count check
- _build_legend_params(): LegendParams construction with legend_params overrides
- promote the _draw_colorbar closure to a module function taking colorbar_params
  explicitly (was a ~90-line closure capturing it implicitly)
- _layout_pending_colorbars(): the deferred second-pass colorbar layout

No behavior change.
…_finalize_panel

Extract the inner render-command dispatch loop (#697), behavior-preserving:
- _render_panel(): dispatches each queued render command into one panel's axes,
  returning the wanted elements and per-type wants_* flags
- _finalize_panel(): per-panel title / equal-aspect / frame visibility

show() is now a compact orchestrator calling named stages. Full test suite
passes unchanged (719 passed, 1 skipped), image baselines included.

No behavior change.
Post-review cleanups (all behavior-preserving, full suite green 719 passed):
- drop dead _draw_colorbar param base_offsets_axes (never read)
- collapse the two parallel 4-branch location chains in _draw_colorbar into
  data-driven lookups (vertical flag + opposite map + getattr on the axis)
- extract _should_rasterize() to dedup the images/labels rasterize heuristic
- extract _maybe_set_label_colors() for the categorical-color prestep
- replace the 5-branch render dispatch in _render_panel with a renderer table
  keyed by command; graph stays a small special case
- pass cs_row instead of four has_* booleans; return a wants dict instead of a
  four-boolean tuple (removes the parallel-variable sprawl)
@codecov-commenter

codecov-commenter commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.23529% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.13%. Comparing base (8730ff4) to head (3f26158).

Files with missing lines Patch % Lines
src/spatialdata_plot/pl/basic.py 89.07% 8 Missing and 12 partials ⚠️
src/spatialdata_plot/pl/utils.py 50.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #714      +/-   ##
==========================================
+ Coverage   77.00%   77.13%   +0.13%     
==========================================
  Files          14       14              
  Lines        4457     4457              
  Branches     1036     1023      -13     
==========================================
+ Hits         3432     3438       +6     
- Misses        660      661       +1     
+ Partials      365      358       -7     
Files with missing lines Coverage Δ
src/spatialdata_plot/pl/utils.py 69.38% <50.00%> (-0.02%) ⬇️
src/spatialdata_plot/pl/basic.py 82.36% <89.07%> (+1.35%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timtreis timtreis changed the title refactor: decompose show() into named stages (#697) refactor: decompose show() into named stages Jun 14, 2026
timtreis added 2 commits June 14, 2026 18:09
Follow-up cleanup on the show() decomposition:

- Hoist the render-command dispatch dict to a module-level _RENDERERS
  constant instead of rebuilding it on every _render_panel call.
- Derive _VALID_RENDER_CMDS from _RENDER_CMD_TO_CS_FLAG (+ render_graph)
  so the valid-command set isn't a second hand-maintained list.
- Drop the always-true `if "render" in cmd` branch in
  _collect_render_commands (every valid command starts with "render",
  and unknown commands already raise above it).
- Hoist _finalize_panel out of the per-command loop in _render_panel; it
  depends only on per-panel values, so it now runs once per panel.

Behavior-preserving; no public API change. (Pre-existing mypy errors on
this branch are unrelated and unchanged by this commit.)
`_render_panel` iterates render commands whose params are a 5-way
RenderParams union. The command string discriminates the concrete type
but mypy can't infer that, so narrow with explicit casts at each typed
call (_render_graph, _get_wanted_render_elements, _should_rasterize,
_maybe_set_label_colors) and type _RENDERERS as
`dict[str, Callable[..., None]]` so the dynamic dispatch type-checks.

Also fixes an unrelated pre-existing mypy error in utils.py
(`_resolve_measure_table` returning Any) by coercing the table name to
str; ruff additionally normalized the adjacent spatialdata import block.
Both are required for this branch to pass the mypy/ruff pre-commit hooks
(mypy follows imports, so the utils.py error blocked any basic.py commit).

No runtime behavior change: casts are no-ops and table names are strings.
@timtreis timtreis marked this pull request as ready for review June 14, 2026 16:23
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:

- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.

The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.

Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:

- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.

The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.

Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
timtreis added a commit that referenced this pull request Jun 14, 2026
Builds on the per-panel title fix (#695): before the panel loop, validate
that `title`, when a list, has length 1 or exactly num_panels. This:

- surfaces the error before any drawing (not mid-render),
- rejects an over-long title list (previously silently truncated), and
- makes the per-panel IndexError dead code, so the try/except is removed.

The error is now a ValueError (the right type for an invalid argument)
raised once up front, instead of an IndexError raised per panel mid-render.
No test pinned the old exception type. Adds non-visual regression tests
for single- and multi-panel title-count validation.

Pre-existing mypy error in utils.py (_resolve_measure_table returning Any)
is unrelated and unchanged here; it is fixed separately in #714.
Integrate #702 (per-panel title fix) and #703 (as_points + fast extent)
into the show() decomposition.

Conflict resolution in src/spatialdata_plot/pl/basic.py:
- #702 up-front title-count validation: kept (after num_panels). The
  adjacent axes/panel-count check was dropped here because the
  decomposition already relocated it into _plan_panels.
- #702 simplified title selection (dropped the per-panel try/except):
  applied to the extracted _finalize_panel helper.
- #703 had no show()-level render-dispatch changes (as_points is
  param-driven in render.py, handled inside _render_panel already); its
  only show()-level change, get_extent -> _get_extent_fast, auto-merged
  into the extent block and consumes _render_panel's `wants` dict.

Also sweeps up two pre-existing #703 lint/type nits in utils.py surfaced
by the merge: _fast_extent docstring (ruff D205) and _get_extent_fast
Any-return (mypy).

Verified: ruff + mypy clean; 109 non-visual show/shapes/labels tests pass.
@timtreis timtreis merged commit 4ba13a3 into main Jun 14, 2026
7 of 8 checks passed
timtreis added a commit that referenced this pull request Jun 14, 2026
Integrate #714 (show() decomposition) into the utils.py split. Only utils.py
conflicted:
- import block: dropped the now-unused `_locate_value` import (it moved to
  _color.py with the color code that uses it); kept main's `_locate_value`
  out of utils.
- `_fast_extent` docstring: took main's #714 version (D205 fix).

basic.py auto-merged: #714's decomposed show()/helpers now import color and
validation symbols from _color/_validate (the split's repoints), not utils.

Bonus: merging #714 brings its fixes for the pre-existing #703/#705 debt
(_resolve_measure_table str-return, _get_extent_fast Any-return, _fast_extent
D205), so the branch is now fully ruff + mypy clean (no --no-verify).

Verified: no import cycle; ruff + ruff-format + mypy all pass; 410 non-visual
tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor: decompose the 644-line show() god-function into named stages

3 participants