From 06a3720838591a544932b88cfb6cf5ca0191f7a8 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 3 Jul 2022 13:12:55 -0400 Subject: [PATCH 1/2] added ISIN,CUSIP & SEDOL checks --- tests/test_finance.py | 70 ++++++++++++++++++++ validators/__init__.py | 3 +- validators/finance.py | 147 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 tests/test_finance.py create mode 100644 validators/finance.py diff --git a/tests/test_finance.py b/tests/test_finance.py new file mode 100644 index 00000000..ecffa9f4 --- /dev/null +++ b/tests/test_finance.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +import pytest + +import validators + +#good values +@pytest.mark.parametrize('value', [ + '912796X38', + '912796X20', + '912796x20' +]) +def test_returns_true_on_valid_cusip(value): + assert validators.cusip(value) + +#bad values +@pytest.mark.parametrize('value', [ + '912796T67', + '912796T68', + 'XCVF', + '00^^^1234', + '1234567890' +]) +def test_returns_failed_validation_on_invalid_cusip(value): + result = validators.cusip(value) + assert isinstance(result, validators.ValidationFailure) + + + +#good values +@pytest.mark.parametrize('value', [ + '0263494', + '0540528', + 'B000009' +]) +def test_returns_true_on_valid_sedol(value): + assert validators.sedol(value) + +#bad values +@pytest.mark.parametrize('value', [ + '0540526', + 'XCVF', + '00^^^1234', + 'A000009' +]) +def test_returns_failed_validation_on_invalid_sedol(value): + result = validators.sedol(value) + assert isinstance(result, validators.ValidationFailure) + + + + +#good values +@pytest.mark.parametrize('value', [ + 'US0004026250', + 'JP000K0VF054', + 'US0378331005' +]) +def test_returns_true_on_valid_isin(value): + assert validators.isin(value) + +#bad values +@pytest.mark.parametrize('value', [ + '010378331005' + 'XCVF', + '00^^^1234', + 'A000009' +]) +def test_returns_failed_validation_on_invalid_isin(value): + result = validators.isin(value) + assert isinstance(result, validators.ValidationFailure) diff --git a/validators/__init__.py b/validators/__init__.py index f623e12f..a54c28ca 100644 --- a/validators/__init__.py +++ b/validators/__init__.py @@ -24,12 +24,13 @@ from .url import url from .utils import ValidationFailure, validator from .uuid import uuid +from .finance import ( cusip, sedol, isin ) __all__ = ('between', 'domain', 'email', 'Max', 'Min', 'md5', 'sha1', 'sha224', 'sha256', 'sha512', 'fi_business_id', 'fi_ssn', 'iban', 'ipv4', 'ipv4_cidr', 'ipv6', 'ipv6_cidr', 'length', 'mac_address', 'slug', 'truthy', 'url', 'ValidationFailure', 'validator', 'uuid', 'card_number', 'visa', 'mastercard', 'amex', 'unionpay', 'diners', - 'jcb', 'discover', 'btc_address') + 'jcb', 'discover', 'btc_address', 'cusip', 'sedol', 'isin') __version__ = '0.20.0' diff --git a/validators/finance.py b/validators/finance.py new file mode 100644 index 00000000..8af7888b --- /dev/null +++ b/validators/finance.py @@ -0,0 +1,147 @@ + + +from .utils import validator + +def cusipChecksum( cusip ) : + + check = 0 + val = None # just to be extra safe - should not be needed but ... + + for digitIndex in range(9) : + c = cusip[digitIndex] + if c >= '0' and c <= '9' : + val = ord(c) - ord('0') + elif c >= 'A' and c <= 'Z' : + val = 10 + ord(c) - ord('A') + elif c >= 'a' and c <= 'z' : + val = 10 + ord(c) - ord('a') + elif c == '*' : + val = 36 + elif c == '@' : + val = 37 + elif c == '#' : + val = 38 + else : + return False + + if digitIndex & 1 : + val = val + val + + check = check + (val // 10) + (val % 10) + + # check = ( 10 - ( check % 10 ) ) % 10 + # return chr( ord('0') + check ) == cusip[8] + return (check % 10) == 0 + + +@validator +def cusip(value): + """ + Return whether or not given value is a valid CUSIP. + + If the value is a valid CUSIP this function returns ``True``, otherwise + :class:`~validators.utils.ValidationFailure`. + + Examples:: + + >>> cusip('037833DP2') + True + + >>> cusip('037833DP3') + ValidationFailure(func=cusip, ...) + + .. versionadded:: 0.20 + + :param value: CUSIP string to validate + """ + return len(value)==9 and cusipChecksum(value) + + + +def isinChecksum( value ) : + + check = 0 + val = None # just to be extra safe - should not be needed but ... + + for digitIndex in range(12) : + c = value[digitIndex] + if c >= '0' and c <= '9' and digitIndex>1 : + val = ord(c) - ord('0') + elif c >= 'A' and c <= 'Z' : + val = 10 + ord(c) - ord('A') + elif c >= 'a' and c <= 'z' : + val = 10 + ord(c) - ord('a') + else : + return False + + if digitIndex & 1 : + val = val + val + + return (check %10) == 0 + + +@validator +def isin(value): + """ + Return whether or not given value is a valid ISIN. + + If the value is a valid ISIN this function returns ``True``, otherwise + :class:`~validators.utils.ValidationFailure`. + + Examples:: + + >>> isin('037833DP2') + True + + >>> isin('037833DP3') + ValidationFailure(func=isin, ...) + + .. versionadded:: 0.20 + + :param value: ISIN string to validate + """ + return len(value)==12 and isinChecksum(value) + + + + +@validator +def sedol(value): + """ + Return whether or not given value is a valid SEDOL. + + If the value is a valid SEDOL this function returns ``True``, otherwise + :class:`~validators.utils.ValidationFailure`. + + Examples:: + + >>> sedol('2936921') + True + + >>> sedol('29A6922') + ValidationFailure(func=sedol, ...) + + .. versionadded:: 0.20 + + :param value: SEDOL string to validate + """ + if len(value)!=7 : + return False + + weights = [ 1, 3, 1, 7, 3, 9, 1 ] + check = 0 + for digitIndex in range(7) : + c = value[digitIndex] + if c in 'AEIOU' : + return False + + val = None + if c >= '0' and c <= '9' : + val = ord(c) - ord('0') + elif c >= 'A' and c <= 'Z' : + val = 10 + ord(c) - ord('A') + else : + return False + check += val * weights[digitIndex] + + return (check%10)==0 From 8a7ab660022fae93ad278f19b587f1f1402fcb81 Mon Sep 17 00:00:00 2001 From: Richard Date: Sun, 3 Jul 2022 13:54:34 -0400 Subject: [PATCH 2/2] Added ISIN,CUSIP & SEDOL --- tests/test_finance.py | 1 + validators/__init__.py | 1 + validators/finance.py | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_finance.py b/tests/test_finance.py index ecffa9f4..b150f630 100644 --- a/tests/test_finance.py +++ b/tests/test_finance.py @@ -68,3 +68,4 @@ def test_returns_true_on_valid_isin(value): def test_returns_failed_validation_on_invalid_isin(value): result = validators.isin(value) assert isinstance(result, validators.ValidationFailure) + \ No newline at end of file diff --git a/validators/__init__.py b/validators/__init__.py index a54c28ca..f7c2fff8 100644 --- a/validators/__init__.py +++ b/validators/__init__.py @@ -34,3 +34,4 @@ 'jcb', 'discover', 'btc_address', 'cusip', 'sedol', 'isin') __version__ = '0.20.0' + \ No newline at end of file diff --git a/validators/finance.py b/validators/finance.py index 8af7888b..e0f6bc0e 100644 --- a/validators/finance.py +++ b/validators/finance.py @@ -29,8 +29,6 @@ def cusipChecksum( cusip ) : check = check + (val // 10) + (val % 10) - # check = ( 10 - ( check % 10 ) ) % 10 - # return chr( ord('0') + check ) == cusip[8] return (check % 10) == 0