Skip to content

Commit 6b970dc

Browse files
authored
Merge pull request #359 from yozachar/workshop
feat: adds `finance` validator
2 parents a69af9d + a13d53d commit 6b970dc

File tree

6 files changed

+208
-0
lines changed

6 files changed

+208
-0
lines changed

docs/api/finance.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# finance
2+
3+
::: validators.finance.cusip
4+
::: validators.finance.isin
5+
::: validators.finance.sedol

docs/api/finance.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
finance
2+
-------
3+
4+
.. module:: validators.finance
5+
.. autofunction:: cusip
6+
.. autofunction:: isin
7+
.. autofunction:: sedol

mkdocs.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ nav:
7777
- api/domain.md
7878
- api/email.md
7979
- api/encoding.md
80+
- api/finance.md
8081
- api/hashes.md
8182
- api/hostname.md
8283
- api/i18n.md

src/validators/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .domain import domain
1010
from .email import email
1111
from .encoding import base58, base64
12+
from .finance import cusip, isin, sedol
1213
from .hashes import md5, sha1, sha224, sha256, sha512
1314
from .hostname import hostname
1415
from .i18n import (
@@ -61,6 +62,10 @@
6162
# encodings
6263
"base58",
6364
"base64",
65+
# finance
66+
"cusip",
67+
"isin",
68+
"sedol",
6469
# hashes
6570
"md5",
6671
"sha1",

src/validators/finance.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
"""Finance."""
2+
3+
from .utils import validator
4+
5+
6+
def _cusip_checksum(cusip: str):
7+
check, val = 0, None
8+
9+
for idx in range(9):
10+
c = cusip[idx]
11+
if c >= "0" and c <= "9":
12+
val = ord(c) - ord("0")
13+
elif c >= "A" and c <= "Z":
14+
val = 10 + ord(c) - ord("A")
15+
elif c >= "a" and c <= "z":
16+
val = 10 + ord(c) - ord("a")
17+
elif c == "*":
18+
val = 36
19+
elif c == "@":
20+
val = 37
21+
elif c == "#":
22+
val = 38
23+
else:
24+
return False
25+
26+
if idx & 1:
27+
val += val
28+
29+
check = check + (val // 10) + (val % 10)
30+
31+
return (check % 10) == 0
32+
33+
34+
def _isin_checksum(value: str):
35+
check, val = 0, None
36+
37+
for idx in range(12):
38+
c = value[idx]
39+
if c >= "0" and c <= "9" and idx > 1:
40+
val = ord(c) - ord("0")
41+
elif c >= "A" and c <= "Z":
42+
val = 10 + ord(c) - ord("A")
43+
elif c >= "a" and c <= "z":
44+
val = 10 + ord(c) - ord("a")
45+
else:
46+
return False
47+
48+
if idx & 1:
49+
val += val
50+
51+
return (check % 10) == 0
52+
53+
54+
@validator
55+
def cusip(value: str):
56+
"""Return whether or not given value is a valid CUSIP.
57+
58+
Checks if the value is a valid [CUSIP][1].
59+
[1]: https://en.wikipedia.org/wiki/CUSIP
60+
61+
Examples:
62+
>>> cusip('037833DP2')
63+
True
64+
>>> cusip('037833DP3')
65+
ValidationFailure(func=cusip, ...)
66+
67+
Args:
68+
value: CUSIP string to validate.
69+
70+
Returns:
71+
(Literal[True]): If `value` is a valid CUSIP string.
72+
(ValidationError): If `value` is an invalid CUSIP string.
73+
"""
74+
return len(value) == 9 and _cusip_checksum(value)
75+
76+
77+
@validator
78+
def isin(value: str):
79+
"""Return whether or not given value is a valid ISIN.
80+
81+
Checks if the value is a valid [ISIN][1].
82+
[1]: https://en.wikipedia.org/wiki/International_Securities_Identification_Number
83+
84+
Examples:
85+
>>> isin('037833DP2')
86+
True
87+
>>> isin('037833DP3')
88+
ValidationFailure(func=isin, ...)
89+
90+
Args:
91+
value: ISIN string to validate.
92+
93+
Returns:
94+
(Literal[True]): If `value` is a valid ISIN string.
95+
(ValidationError): If `value` is an invalid ISIN string.
96+
"""
97+
return len(value) == 12 and _isin_checksum(value)
98+
99+
100+
@validator
101+
def sedol(value: str):
102+
"""Return whether or not given value is a valid SEDOL.
103+
104+
Checks if the value is a valid [SEDOL][1].
105+
[1]: https://en.wikipedia.org/wiki/SEDOL
106+
107+
Examples:
108+
>>> sedol('2936921')
109+
True
110+
>>> sedol('29A6922')
111+
ValidationFailure(func=sedol, ...)
112+
113+
Args:
114+
value: SEDOL string to validate.
115+
116+
Returns:
117+
(Literal[True]): If `value` is a valid SEDOL string.
118+
(ValidationError): If `value` is an invalid SEDOL string.
119+
"""
120+
if len(value) != 7:
121+
return False
122+
123+
weights = [1, 3, 1, 7, 3, 9, 1]
124+
check = 0
125+
for idx in range(7):
126+
c = value[idx]
127+
if c in "AEIOU":
128+
return False
129+
130+
val = None
131+
if c >= "0" and c <= "9":
132+
val = ord(c) - ord("0")
133+
elif c >= "A" and c <= "Z":
134+
val = 10 + ord(c) - ord("A")
135+
else:
136+
return False
137+
check += val * weights[idx]
138+
139+
return (check % 10) == 0

tests/test_finance.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""Test Finance."""
2+
3+
# external
4+
import pytest
5+
6+
# local
7+
from validators import ValidationError, cusip, isin, sedol
8+
9+
# ==> CUSIP <== #
10+
11+
12+
@pytest.mark.parametrize("value", ["912796X38", "912796X20", "912796x20"])
13+
def test_returns_true_on_valid_cusip(value: str):
14+
"""Test returns true on valid cusip."""
15+
assert cusip(value)
16+
17+
18+
@pytest.mark.parametrize("value", ["912796T67", "912796T68", "XCVF", "00^^^1234"])
19+
def test_returns_failed_validation_on_invalid_cusip(value: str):
20+
"""Test returns failed validation on invalid cusip."""
21+
assert isinstance(cusip(value), ValidationError)
22+
23+
24+
# ==> ISIN <== #
25+
26+
27+
@pytest.mark.parametrize("value", ["US0004026250", "JP000K0VF054", "US0378331005"])
28+
def test_returns_true_on_valid_isin(value: str):
29+
"""Test returns true on valid isin."""
30+
assert isin(value)
31+
32+
33+
@pytest.mark.parametrize("value", ["010378331005" "XCVF", "00^^^1234", "A000009"])
34+
def test_returns_failed_validation_on_invalid_isin(value: str):
35+
"""Test returns failed validation on invalid isin."""
36+
assert isinstance(isin(value), ValidationError)
37+
38+
39+
# ==> SEDOL <== #
40+
41+
42+
@pytest.mark.parametrize("value", ["0263494", "0540528", "B000009"])
43+
def test_returns_true_on_valid_sedol(value: str):
44+
"""Test returns true on valid sedol."""
45+
assert sedol(value)
46+
47+
48+
@pytest.mark.parametrize("value", ["0540526", "XCVF", "00^^^1234", "A000009"])
49+
def test_returns_failed_validation_on_invalid_sedol(value: str):
50+
"""Test returns failed validation on invalid sedol."""
51+
assert isinstance(sedol(value), ValidationError)

0 commit comments

Comments
 (0)