Skip to content

Commit

Permalink
Merge pull request #359 from yozachar/workshop
Browse files Browse the repository at this point in the history
feat: adds `finance` validator
  • Loading branch information
yozachar committed Apr 3, 2024
2 parents a69af9d + a13d53d commit 6b970dc
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 0 deletions.
5 changes: 5 additions & 0 deletions docs/api/finance.md
@@ -0,0 +1,5 @@
# finance

::: validators.finance.cusip
::: validators.finance.isin
::: validators.finance.sedol
7 changes: 7 additions & 0 deletions docs/api/finance.rst
@@ -0,0 +1,7 @@
finance
-------

.. module:: validators.finance
.. autofunction:: cusip
.. autofunction:: isin
.. autofunction:: sedol
1 change: 1 addition & 0 deletions mkdocs.yaml
Expand Up @@ -77,6 +77,7 @@ nav:
- api/domain.md
- api/email.md
- api/encoding.md
- api/finance.md
- api/hashes.md
- api/hostname.md
- api/i18n.md
Expand Down
5 changes: 5 additions & 0 deletions src/validators/__init__.py
Expand Up @@ -9,6 +9,7 @@
from .domain import domain
from .email import email
from .encoding import base58, base64
from .finance import cusip, isin, sedol
from .hashes import md5, sha1, sha224, sha256, sha512
from .hostname import hostname
from .i18n import (
Expand Down Expand Up @@ -61,6 +62,10 @@
# encodings
"base58",
"base64",
# finance
"cusip",
"isin",
"sedol",
# hashes
"md5",
"sha1",
Expand Down
139 changes: 139 additions & 0 deletions src/validators/finance.py
@@ -0,0 +1,139 @@
"""Finance."""

from .utils import validator


def _cusip_checksum(cusip: str):
check, val = 0, None

for idx in range(9):
c = cusip[idx]
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 idx & 1:
val += val

check = check + (val // 10) + (val % 10)

return (check % 10) == 0


def _isin_checksum(value: str):
check, val = 0, None

for idx in range(12):
c = value[idx]
if c >= "0" and c <= "9" and idx > 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 idx & 1:
val += val

return (check % 10) == 0


@validator
def cusip(value: str):
"""Return whether or not given value is a valid CUSIP.
Checks if the value is a valid [CUSIP][1].
[1]: https://en.wikipedia.org/wiki/CUSIP
Examples:
>>> cusip('037833DP2')
True
>>> cusip('037833DP3')
ValidationFailure(func=cusip, ...)
Args:
value: CUSIP string to validate.
Returns:
(Literal[True]): If `value` is a valid CUSIP string.
(ValidationError): If `value` is an invalid CUSIP string.
"""
return len(value) == 9 and _cusip_checksum(value)


@validator
def isin(value: str):
"""Return whether or not given value is a valid ISIN.
Checks if the value is a valid [ISIN][1].
[1]: https://en.wikipedia.org/wiki/International_Securities_Identification_Number
Examples:
>>> isin('037833DP2')
True
>>> isin('037833DP3')
ValidationFailure(func=isin, ...)
Args:
value: ISIN string to validate.
Returns:
(Literal[True]): If `value` is a valid ISIN string.
(ValidationError): If `value` is an invalid ISIN string.
"""
return len(value) == 12 and _isin_checksum(value)


@validator
def sedol(value: str):
"""Return whether or not given value is a valid SEDOL.
Checks if the value is a valid [SEDOL][1].
[1]: https://en.wikipedia.org/wiki/SEDOL
Examples:
>>> sedol('2936921')
True
>>> sedol('29A6922')
ValidationFailure(func=sedol, ...)
Args:
value: SEDOL string to validate.
Returns:
(Literal[True]): If `value` is a valid SEDOL string.
(ValidationError): If `value` is an invalid SEDOL string.
"""
if len(value) != 7:
return False

weights = [1, 3, 1, 7, 3, 9, 1]
check = 0
for idx in range(7):
c = value[idx]
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[idx]

return (check % 10) == 0
51 changes: 51 additions & 0 deletions tests/test_finance.py
@@ -0,0 +1,51 @@
"""Test Finance."""

# external
import pytest

# local
from validators import ValidationError, cusip, isin, sedol

# ==> CUSIP <== #


@pytest.mark.parametrize("value", ["912796X38", "912796X20", "912796x20"])
def test_returns_true_on_valid_cusip(value: str):
"""Test returns true on valid cusip."""
assert cusip(value)


@pytest.mark.parametrize("value", ["912796T67", "912796T68", "XCVF", "00^^^1234"])
def test_returns_failed_validation_on_invalid_cusip(value: str):
"""Test returns failed validation on invalid cusip."""
assert isinstance(cusip(value), ValidationError)


# ==> ISIN <== #


@pytest.mark.parametrize("value", ["US0004026250", "JP000K0VF054", "US0378331005"])
def test_returns_true_on_valid_isin(value: str):
"""Test returns true on valid isin."""
assert isin(value)


@pytest.mark.parametrize("value", ["010378331005" "XCVF", "00^^^1234", "A000009"])
def test_returns_failed_validation_on_invalid_isin(value: str):
"""Test returns failed validation on invalid isin."""
assert isinstance(isin(value), ValidationError)


# ==> SEDOL <== #


@pytest.mark.parametrize("value", ["0263494", "0540528", "B000009"])
def test_returns_true_on_valid_sedol(value: str):
"""Test returns true on valid sedol."""
assert sedol(value)


@pytest.mark.parametrize("value", ["0540526", "XCVF", "00^^^1234", "A000009"])
def test_returns_failed_validation_on_invalid_sedol(value: str):
"""Test returns failed validation on invalid sedol."""
assert isinstance(sedol(value), ValidationError)

0 comments on commit 6b970dc

Please sign in to comment.