diff --git a/tests/test_finance.py b/tests/test_finance.py new file mode 100644 index 00000000..b150f630 --- /dev/null +++ b/tests/test_finance.py @@ -0,0 +1,71 @@ +# -*- 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) + \ No newline at end of file diff --git a/validators/__init__.py b/validators/__init__.py index f623e12f..f7c2fff8 100644 --- a/validators/__init__.py +++ b/validators/__init__.py @@ -24,12 +24,14 @@ 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' + \ No newline at end of file diff --git a/validators/finance.py b/validators/finance.py new file mode 100644 index 00000000..e0f6bc0e --- /dev/null +++ b/validators/finance.py @@ -0,0 +1,145 @@ + + +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) + + 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