Skip to content

Stop leaking import machinery, and reject non-string colormaps cleanly#288

Merged
Alek99 merged 1 commit into
alek/fix-static-axis-formatfrom
alek/fix-api-hygiene
Jul 25, 2026
Merged

Stop leaking import machinery, and reject non-string colormaps cleanly#288
Alek99 merged 1 commit into
alek/fix-static-axis-formatfrom
alek/fix-api-hygiene

Conversation

@Alek99

@Alek99 Alek99 commented Jul 25, 2026

Copy link
Copy Markdown
Member

Stacked on #287. Three small API-surface defects; no screenshots because none of
these are visual.

1. Import machinery leaked into the public namespace

xy.annotations resolved to the __future__._Feature object left behind by
from __future__ import annotations — shadowing the annotation components
(xy.hline, xy.callout, …) that the docs describe under exactly that word.
xy.Any, xy.TYPE_CHECKING and xy.import_module were reachable the same way.

>>> import xy; xy.annotations
_Feature((3, 7, 0, 'beta', 1), None, 16777216)      # before
AttributeError: module 'xy' has no attribute 'annotations'   # after

Delete the bindings once the module body no longer needs them, import Any
privately as _Any, and narrow __dir__ to __all__.

2. A non-string colormap= crashed from library internals

is_colormap called name.endswith("_r") on whatever it received, so the
natural first guess for a custom ramp raised an AttributeError one line before
the same path would have raised a ValueError naming the supported colormaps:

xy.scatter(..., colormap=["#fff", "#f00"])
# before: AttributeError: 'list' object has no attribute 'endswith'
# after:  ValueError: colormap must be one of the built-in names, got list.
#         A custom stop list is not supported; pass an (n, 3) or (n, 4) float
#         array as color= for arbitrary per-point color. Known: (...)

The message names the real alternative, which is a working but undocumented
capability.

3. docs/components/facets-and-layers.md shipped an invalid loc

xy.legend(loc="top left"). loc is substring-matched against upper/lower,
so top matches neither and the example rendered a vertically centered
legend — confirmed in-browser (--xy-legend-top: 209px with
translate(0,-50%) on a 420px chart). Every other page uses upper left.

Guard

scripts/check_public_api.py now asserts dir(xy) is exactly __all__ and
that no non-module public name survives in the package globals, so this class of
leak fails CI instead of waiting to be noticed. Submodules are exempt —
importing one binds it on the package, which is import-order dependent (that
exemption is load-bearing: without it the check passes alone and fails inside
the full suite).

Found during a customizability audit.

Three small API-surface defects.

`xy.annotations` resolved to the `__future__._Feature` object left behind by
`from __future__ import annotations`, shadowing the annotation components
(`xy.hline`, `xy.callout`, ...) that the docs describe under that word.
`xy.Any`, `xy.TYPE_CHECKING` and `xy.import_module` were reachable the same way.
Delete the bindings once the module body no longer needs them, import `Any`
privately as `_Any`, and narrow `__dir__` to `__all__`.

`is_colormap` called `name.endswith("_r")` on whatever it received, so
`colormap=["#fff", "#f00"]` — the natural first guess for a custom ramp — raised
`AttributeError: 'list' object has no attribute 'endswith'` from library
internals, one line before the same path would have raised a ValueError naming
the supported colormaps. Type-guard it and answer the non-string case with a
message that names the real alternative: an (n, 3)/(n, 4) array as `color=`.

docs/components/facets-and-layers.md used `xy.legend(loc="top left")`. `loc` is
substring-matched against `upper`/`lower`, so `top` matches neither and the
example rendered a vertically centered legend; every other page uses
`upper left`.

`scripts/check_public_api.py` now asserts `dir(xy)` is exactly `__all__` and
that no non-module public name survives in the package globals, so this class of
leak fails CI rather than waiting to be noticed. Submodules are exempt because
importing one binds it on the package and that is import-order dependent.
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2145ba33-8bd9-48a0-ad8e-c05ac8bf18d1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alek/fix-api-hygiene

Comment @coderabbitai help to get the list of available commands.

@codspeed-hq

codspeed-hq Bot commented Jul 25, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 102 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing alek/fix-api-hygiene (5db919a) with alek/fix-static-axis-format (24410a1)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Alek99
Alek99 merged commit e61546f into alek/fix-static-axis-format Jul 25, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant