_isin_checksum() never accumulated into `check`, so it stayed 0 and
`check % 10 == 0` was always true. Any 12-character string with a
two-letter country code and a trailing digit was accepted regardless of
its check digit (e.g. US0378331004, an invalid variant of Apple's
US0378331005, validated as a valid ISIN).
Implement the real algorithm: expand letters to digits (A=10..Z=35),
keep digits as-is, then run the Luhn checksum over the result. Also
enforce that the country code is alphabetic and the check digit numeric,
rejecting lowercase/malformed input as requested in the issue.
Cross-checked against python-stdnum: this also revealed that the existing
'valid' test fixture JP000K0VF054 was itself an invalid ISIN (correct
check digit is 5) that only passed because the checksum was never
computed; corrected to JP000K0VF055 and added wrong-check-digit cases.
Fixes python-validators#440.
Summary
Fixes #440 (the ISIN portion). validators.isin() accepts any invalid ISIN check digit.
Root cause
_isin_checksum() computes a per-character val and doubles it for odd positions, but never accumulates it into check:
So the checksum is never actually validated — every 12-character string with a two-letter country code and a trailing digit passes. (_cusip_checksum and sedol accumulate correctly and are unaffected.)
Fix
Implement the real ISIN algorithm: expand letters to digits (A=10 … Z=35), keep digits as-is, then run the Luhn checksum over the resulting digit string. Also enforce that the country code (first two chars) is alphabetic and the check digit (last char) numeric, so lowercase/malformed inputs are rejected (as the issue requests).
Verification
Disclosure: prepared with AI assistance; reviewed and cross-checked locally (incl. against python-stdnum).