Skip to content

Reject hex colors that are not 3 or 6 digits - #5729

Merged
emilykl merged 6 commits into
plotly:mainfrom
dylanpulver:hex-to-rgb-rejects-invalid-length
Sep 15, 2026
Merged

emilykl merged 6 commits into
plotly:mainfrom
dylanpulver:hex-to-rgb-rejects-invalid-length

Conversation

@dylanpulver

Copy link
Copy Markdown
Contributor

Link to issue

Closes #5728

Description of change

hex_to_rgb computed its section width as len(value) // 3 and sliced in steps of that width, so any length other than 3 or 6 produced a tuple of the wrong size instead of an error: hex_to_rgb("#12345") returned (1, 2, 3, 4, 5). An empty string raised ValueError: range() arg 3 must not be zero, which says nothing about colors. This enforces the contract the docstring already states.

Demo

Before:

>>> hex_to_rgb("#12345")
(1, 2, 3, 4, 5)
>>> hex_to_rgb("")
ValueError: range() arg 3 must not be zero

After:

>>> hex_to_rgb("#12345")
ValueError: hex color must be 3 or 6 hex digits, optionally prefixed with '#'; got '12345'

Both valid shapes are unchanged, with or without the leading #: "#ffffff", "#fff", "aabbcc" and "abc" all behave as before.

Testing strategy

Added a parametrized rejection test over "#12345", "#1", "#1234567", "" and "#", plus a case pinning that the leading # stays optional. Reverting the change fails five of them, so they are not passing vacuously.

tests/test_plotly_utils/: 1417 passed, 2 failed. Those 2 are test_fig_deepcopy pyarrow cases that fail identically on master in my environment; nothing fails only on this branch.

Not addressed here, noted in the issue: n_colors(low, high, 1) raises ZeroDivisionError because the increment is diff / (n_colors - 1). The right behaviour for n=1 is your call, so I left it alone rather than guess.

This PR was written with AI assistance (Claude Code).

hex_to_rgb computed the section width as len(value) // 3 and sliced in
steps of that width, so "#12345" came back as the 5-tuple (1, 2, 3, 4, 5)
rather than being rejected. An empty string raised "range() arg 3 must
not be zero", which says nothing about colors.

The docstring already gives the contract: 3 or 6 digits. This enforces
it and keeps the two valid shapes working, with or without the leading
'#'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M5uTyCU4WcNTsPvGrErDo

@emilykl emilykl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @dylanpulver for the fix! This is definitely an improvement in error handling.

I adjusted the function a bit so that 4- and 8-character hex codes (with alpha) are accepted with a warning and the alpha channel is stripped, so that a 3-tuple is always returned no matter the length of the input. We accept those 4- and 8-character hex codes in other areas of Plotly so it's possible they might end up getting passed to this function, so I wanted to err on the side of caution. If it seems nobody is hitting the warning we could change in the future to reject those codes as well.

@emilykl
emilykl merged commit 9b3c4da into plotly:main Sep 15, 2026
26 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.

hex_to_rgb returns a wrong-length tuple for hex strings that are not 3 or 6 digits

2 participants