lists.arthurdejong.org
RSS feed

python-stdnum branch master updated. 1.1-38-g3c7a302

[Date Prev][Date Next] [Thread Prev][Thread Next]

python-stdnum branch master updated. 1.1-38-g3c7a302



This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-stdnum".

The branch, master has been updated
       via  3c7a302c5ad9364b69a2ac8df0e605177aa3525c (commit)
       via  c565517701cc44e1bbe68e126ab8048340ddbd09 (commit)
       via  961815fa5868e8e3772f36370cf01d313721f39a (commit)
       via  fb91775be1cb5f047ced3173e894e2c81d7fd311 (commit)
       via  ebb5c07b36b1595b3a5dee22c5c82e15b027dbf3 (commit)
       via  fa8099ebbf06fc45e62d8b1ed6a12b5a2405476c (commit)
       via  111b4fd7c91b5d3c7bce921a1436bc7d21d7cdfa (commit)
       via  9f9d13cc1c1a25157494980cfc7c952411c1ea8c (commit)
       via  522611c2ff966f063f9ee2c2087b984772a97b33 (commit)
      from  320ecea83701cf1a371e360542504414a6403249 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://arthurdejong.org/git/python-stdnum/commit/?id=3c7a302c5ad9364b69a2ac8df0e605177aa3525c

commit 3c7a302c5ad9364b69a2ac8df0e605177aa3525c
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sun Oct 11 11:23:19 2015 +0200

    Convert security ids to ISIN
    
    Allow conversion from national securities identifiers to the
    international ISIN.

diff --git a/stdnum/cusip.py b/stdnum/cusip.py
index 4d54614..acd27d1 100644
--- a/stdnum/cusip.py
+++ b/stdnum/cusip.py
@@ -34,6 +34,8 @@ More information:
 Traceback (most recent call last):
     ...
 InvalidChecksum: ...
+>>> to_isin('91324PAE2')
+'US91324PAE25'
 """
 
 from stdnum.exceptions import *
@@ -78,3 +80,9 @@ def is_valid(number):
         return bool(validate(number))
     except ValidationError:
         return False
+
+
+def to_isin(number):
+    """Convert the number to an ISIN."""
+    from stdnum import isin
+    return isin.from_natid('US', number)
diff --git a/stdnum/de/wkn.py b/stdnum/de/wkn.py
index 9aa908b..8cde237 100644
--- a/stdnum/de/wkn.py
+++ b/stdnum/de/wkn.py
@@ -30,6 +30,8 @@ longer has any structure. It is expected to be replaced by 
the ISIN.
 Traceback (most recent call last):
     ...
 InvalidFormat: ...
+>>> to_isin('SKWM02')
+'DE000SKWM021'
 """
 
 from stdnum.exceptions import *
@@ -64,3 +66,9 @@ def is_valid(number):
         return bool(validate(number))
     except ValidationError:
         return False
+
+
+def to_isin(number):
+    """Convert the number to an ISIN."""
+    from stdnum import isin
+    return isin.from_natid('DE', number)
diff --git a/stdnum/gb/sedol.py b/stdnum/gb/sedol.py
index d53712e..ad768ca 100644
--- a/stdnum/gb/sedol.py
+++ b/stdnum/gb/sedol.py
@@ -29,6 +29,8 @@ in length consisting of six alphanumeric digits, followed by 
a check digit.
 Traceback (most recent call last):
     ...
 InvalidChecksum: ...
+>>> to_isin('B15KXQ8')
+'GB00B15KXQ89'
 """
 
 from stdnum.exceptions import *
@@ -76,3 +78,9 @@ def is_valid(number):
         return bool(validate(number))
     except ValidationError:
         return False
+
+
+def to_isin(number):
+    """Convert the number to an ISIN."""
+    from stdnum import isin
+    return isin.from_natid('GB', number)
diff --git a/stdnum/isin.py b/stdnum/isin.py
index 524640a..c6e93d5 100644
--- a/stdnum/isin.py
+++ b/stdnum/isin.py
@@ -36,6 +36,8 @@ More information:
 Traceback (most recent call last):
     ...
 InvalidChecksum: ...
+>>> from_natid('gb', 'BYXJL75')
+'GB00BYXJL758'
 """
 
 from stdnum.exceptions import *
@@ -114,3 +116,10 @@ def is_valid(number):
         return bool(validate(number))
     except ValidationError:
         return False
+
+
+def from_natid(country_code, number):
+    """Generate an ISIN from a national security identifier."""
+    number = compact(number)
+    number = country_code.upper() + (9 - len(number)) * '0' + number
+    return number + calc_check_digit(number)

http://arthurdejong.org/git/python-stdnum/commit/?id=c565517701cc44e1bbe68e126ab8048340ddbd09

commit c565517701cc44e1bbe68e126ab8048340ddbd09
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sun Oct 11 11:23:02 2015 +0200

    Add German Wertpapierkennnummer
    
    The format itself is pretty simple (no check digit) but this module is
    more for completeness sake.

diff --git a/stdnum/de/wkn.py b/stdnum/de/wkn.py
new file mode 100644
index 0000000..9aa908b
--- /dev/null
+++ b/stdnum/de/wkn.py
@@ -0,0 +1,66 @@
+# wkn.py - functions for handling Wertpapierkennnummer
+# coding: utf-8
+#
+# Copyright (C) 2015 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""Wertpapierkennnummer (German securities identification code).
+
+The WKN, WPKN, WPK (Wertpapierkennnummer) is a German code to identify
+securities. It is a 6-digit alphanumeric number without a check digit that no
+longer has any structure. It is expected to be replaced by the ISIN.
+
+>>> validate('A0MNRK')
+'A0MNRK'
+>>> validate('AOMNRK')  # no capital o allowed
+Traceback (most recent call last):
+    ...
+InvalidFormat: ...
+"""
+
+from stdnum.exceptions import *
+from stdnum.util import clean
+
+
+def compact(number):
+    """Convert the number to the minimal representation. This strips the
+    number of any valid separators and removes surrounding whitespace."""
+    return clean(number, ' ').strip().upper()
+
+
+# O and I are not valid but are accounted for in the check digit calculation
+_alphabet = '0123456789ABCDEFGH JKLMN PQRSTUVWXYZ'
+
+
+def validate(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    number = compact(number)
+    if not all(x in _alphabet for x in number):
+        raise InvalidFormat()
+    if len(number) != 6:
+        raise InvalidLength()
+    return number
+
+
+def is_valid(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    try:
+        return bool(validate(number))
+    except ValidationError:
+        return False
diff --git a/tests/test_de_wkn.doctest b/tests/test_de_wkn.doctest
new file mode 100644
index 0000000..db47c49
--- /dev/null
+++ b/tests/test_de_wkn.doctest
@@ -0,0 +1,233 @@
+test_de_wkn.doctest - more detailed doctests for the stdnum.de.wkn module
+
+Copyright (C) 2015 Arthur de Jong
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA
+
+
+This file contains more detailed doctests for the stdnum.de.wkn module. It
+tries to validate a number of numbers that have been found online.
+
+>>> from stdnum.de import wkn
+>>> from stdnum.exceptions import *
+
+
+These have been found online and should all be valid numbers.
+
+>>> numbers = '''
+...
+... 934056
+... A0Q2A9
+... 978273
+... 989756
+... A0D85Q
+... A0MUD3
+... A0F67A
+... 622925
+... A0ESSP
+... A0M82M
+... 200435
+... 895705
+... A0MMMK
+... 989226
+... 977858
+... A0DNG5
+... A0LGM2
+... 701932
+... A0H0UT
+... 989127
+... A0JM7H
+... 516810
+... 977023
+... 971043
+... 865742
+... A0MYFQ
+... A1EWXW
+... 591961
+... 980276
+... A0RNQ5
+... A0BMA4
+... 531713
+... A0F6EB
+... A0MUHB
+... 975694
+... A1J0ZD
+... A0M11P
+... A0MNX5
+... A0F5T6
+... A0MR7J
+... 974839
+... 972001
+... A0M16T
+... 592895
+... 977974
+... A0HGWD
+... A0LHCL
+... 691298
+... DBX0HY
+... 986732
+... 974956
+... 973275
+... ETFL29
+... 857534
+... A0MVL0
+... A0CBA2
+... A0NGX0
+... A0M92M
+... A0MSAG
+... A0NFHK
+... A0Q35X
+... 513010
+... 921418
+... 531435
+... A0BL1D
+... A0MKA9
+... 976169
+... 779374
+... 847519
+... A0HG5F
+... 986868
+... 847347
+... 971929
+... A0DJ6U
+... 847910
+... A0JMLN
+... 701276
+... 580515
+... 986881
+... A0M2BY
+... A0M93X
+... 989427
+... A0LE9R
+... 848390
+... 980457
+... DWS0UR
+... 977187
+... 850667
+... 930920
+... 987906
+... A0MY8N
+... 970047
+... A0M1NX
+... 933368
+... DBX0F1
+... 848186
+... A0J3GM
+... DBX0HC
+... A0Q09X
+... 988890
+... 589684
+... A0KE7P
+... A0EAD3
+... DBX1F1
+... A1JJTG
+... A0EQ91
+... A0KD2Q
+... 971117
+... 973210
+... A0F6WG
+... A0MLJK
+... A0M46B
+... A0HNPN
+... 848066
+... A0LGQN
+... DBX0G7
+... LYX0AB
+... A0LGWU
+... A0RDGC
+... 848639
+... 589006
+... A0LEEE
+... 552538
+... 977025
+... 531416
+... A0DJ49
+... A0REJL
+... A0YE2R
+... 251119
+... 693287
+... A0MJP7
+... 577345
+... 973228
+... 866953
+... A0MWAL
+... 113596
+... 847426
+... 722538
+... DK0EBQ
+... 603005
+... A1H6H7
+... 593396
+... 987972
+... 541779
+... 750437
+... 588798
+... A0Q60E
+... A0NC6Z
+... A0LGCU
+... 532018
+... 257575
+... 629236
+... A1J0BH
+... 515231
+... 851143
+... LYX0MG
+... 801536
+... 888323
+... 976956
+... 853836
+... 723890
+... A0BL78
+... 971915
+... A0YCYF
+... A0J3WX
+... 847661
+... A0X970
+... 976375
+... A0J3W3
+... 989844
+... A0B5LC
+... 620440
+... LYX0CB
+... 795322
+... 848534
+... 677496
+... 727516
+... 973806
+... 971872
+... 859123
+... 970578
+... 675179
+... 976999
+... A0HF4G
+... 871970
+... 978945
+... 691660
+... A0DPBF
+... 930424
+... 978047
+... 847665
+... 971534
+... A0BMAJ
+... A0NC87
+... A1JJP8
+... A0M2EB
+... A0EQYQ
+... 263233
+...
+... '''
+>>> [x for x in numbers.splitlines() if x and not wkn.is_valid(x)]
+[]

http://arthurdejong.org/git/python-stdnum/commit/?id=961815fa5868e8e3772f36370cf01d313721f39a

commit 961815fa5868e8e3772f36370cf01d313721f39a
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Oct 10 22:20:39 2015 +0200

    Add SEDOL number

diff --git a/stdnum/gb/sedol.py b/stdnum/gb/sedol.py
new file mode 100644
index 0000000..d53712e
--- /dev/null
+++ b/stdnum/gb/sedol.py
@@ -0,0 +1,78 @@
+# sedol.py - functions for handling SEDOL numbers
+#
+# Copyright (C) 2015 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""SEDOL number (Stock Exchange Daily Official List number).
+
+The SEDOL number is a security identifier used in the United Kingdom and
+Ireland assigned by the London Stock Exchange. A SEDOL is seven characters
+in length consisting of six alphanumeric digits, followed by a check digit.
+
+>>> validate('B15KXQ8')
+'B15KXQ8'
+>>> validate('B15KXQ7')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+"""
+
+from stdnum.exceptions import *
+from stdnum.util import clean
+
+
+# the letters allowed in an SEDOL (vowels are never used)
+_alphabet = '0123456789 BCD FGH JKLMN PQRST VWXYZ'
+
+
+def compact(number):
+    """Convert the number to the minimal representation. This strips the
+    number of any valid separators and removes surrounding whitespace."""
+    return clean(number, ' ').strip().upper()
+
+
+def calc_check_digit(number):
+    """Calculate the check digits for the number."""
+    weights = (1, 3, 1, 7, 3, 9)
+    s = sum(w * _alphabet.index(n) for w, n in zip(weights, number))
+    return str((10 - s) % 10)
+
+
+def validate(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    number = compact(number)
+    if not all(x in _alphabet for x in number):
+        raise InvalidFormat()
+    if len(number) != 7:
+        raise InvalidLength()
+    if number[0].isdigit() and not number.isdigit():
+        # new style SEDOLs are supposed to start with a letter, old-style
+        # numbers should be fully numeric
+        raise InvalidFormat()
+    if calc_check_digit(number[:-1]) != number[-1]:
+        raise InvalidChecksum()
+    return number
+
+
+def is_valid(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    try:
+        return bool(validate(number))
+    except ValidationError:
+        return False
diff --git a/tests/test_gb_sedol.doctest b/tests/test_gb_sedol.doctest
new file mode 100644
index 0000000..407d295
--- /dev/null
+++ b/tests/test_gb_sedol.doctest
@@ -0,0 +1,255 @@
+test_sedol.doctest - more detailed doctests for the stdnum.gb.sedol module
+
+Copyright (C) 2015 Arthur de Jong
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA
+
+
+This file contains more detailed doctests for the stdnum.gb.sedol module. It
+tries to validate a number of numbers that have been found online.
+
+>>> from stdnum.gb import sedol
+>>> from stdnum.exceptions import *
+
+
+Old-style number are fully numeric, new-style numbers start with a letter.
+
+>>> sedol.validate('0017505')
+'0017505'
+>>> sedol.validate('B07MXC1')
+'B07MXC1'
+>>> sedol.validate('107MXC1')
+Traceback (most recent call last):
+    ...
+InvalidFormat: ...
+
+
+No vowels are allowed:
+
+>>> sedol.validate('BO7MXC9')
+Traceback (most recent call last):
+    ...
+InvalidFormat: ...
+
+
+These have been found online and should all be valid numbers.
+
+>>> numbers = '''
+...
+... 0016308
+... 0059585
+... 0286941
+... 0371847
+... 0885865
+... 0937636
+... 2181334
+... 2342034
+... 2458113
+... 3111084
+... 3143838
+... 3395349
+... 4178419
+... 4913223
+... 5533976
+... 7142091
+... B012BV2
+... B059874
+... B05CW04
+... B05D467
+... B05D6X8
+... B05D724
+... B05D746
+... B05D7C4
+... B05D7G8
+... B05D9N9
+... B05DCY1
+... B05DKZ8
+... B05DS21
+... B05DSL0
+... B05F6M3
+... B05F7R5
+... B05F7Z3
+... B05FC47
+... B05FFN7
+... B05FFX7
+... B05FHZ3
+... B05FJ02
+... B05FKV0
+... B05FKX2
+... B05FPZ9
+... B05FRQ4
+... B05FXV1
+... B05FZQ0
+... B05FZS2
+... B05G198
+... B05G637
+... B05G7J0
+... B068074
+... B0M6373
+... B0R46X9
+... B0SDR09
+... B0VMH70
+... B0VMJM9
+... B0VMPT8
+... B0XWNB4
+... B0YVBC7
+... B10LNL1
+... B11S1X9
+... B1893V7
+... B18S7B2
+... B1CD9S4
+... B1CDK14
+... B1CQNK3
+... B1CQRH8
+... B1CQRN4
+... B1CQV98
+... B1CQWZ1
+... B1CQY00
+... B1CR0L6
+... B1CR0S3
+... B1KTQX8
+... B1KYVC7
+... B1KYVZ0
+... B1RMWL0
+... B1VCNQ8
+... B1XG8T6
+... B243G00
+... B29LZ80
+... B2N6X76
+... B2PRS50
+... B2PRWF8
+... B2PRWJ2
+... B2PS0H9
+... B2PV5Y0
+... B2PVGZ8
+... B2PVHM2
+... B2PVMB6
+... B2PVP84
+... B2PVRV1
+... B2PY1R4
+... B2PY390
+... B2PY3H8
+... B2PY572
+... B2PY5J4
+... B2Q1N90
+... B2QXZK1
+... B2RKQW0
+... B39DW15
+... B3CFXY8
+... B3CFYB2
+... B3CG1T2
+... B3F8162
+... B3KF8V2
+... B3LFLQ7
+... B3LT1Q9
+... B3LXSJ3
+... B3M3MB2
+... B3M5D48
+... B3M7ZH1
+... B3MPTK6
+... B3MVRM2
+... B3NSQZ8
+... B3NVM93
+... B3P2YG5
+... B3P9Y44
+... B3PHCS8
+... B3PL150
+... B3PQ1W2
+... B3Q3L88
+... B3SC0P3
+... B3VM3R3
+... B3VVG60
+... B3XK5J1
+... B42TM62
+... B45BZT9
+... B4JT339
+... B4MJF52
+... B4PRH35
+... B50HQ74
+... B52LK94
+... B545JR5
+... B5497R3
+... B54V1Z5
+... B599TV6
+... B59TPT6
+... B5BKK36
+... B5BKMR4
+... B5P8YX4
+... B5T42N4
+... B5V3ZY1
+... B5VR9Q3
+... B61BDZ9
+... B626RZ1
+... B6496D9
+... B64JSM2
+... B652H90
+... B657SR0
+... B66G553
+... B6734R8
+... B676F30
+... B67NKZ8
+... B6QDDF0
+... B76V7N7
+... B78DL95
+... B7K2811
+... B7RRJJ4
+... B7RRKB3
+... B7WNMF4
+... B7XCP73
+... B80QGD8
+... B83MH18
+... B8C0D37
+... B8KQFS6
+... B8N44W4
+... B8N45L0
+... B8N46J5
+... B8V9FZ1
+... B9BRCL7
+... B9DQ900
+... BB97138
+... BBGBF31
+... BC7GZX2
+... BCRY644
+... BCW3NW3
+... BCZSZF2
+... BGJZZG8
+... BK1PTB7
+... BKXH0G3
+... BRWQVY5
+... BSBNC63
+... BTF8JJ3
+... BTL1K93
+... BVC3VM2
+... BVXCDJ9
+... BVXLP67
+... BW38RQ9
+... BWWD0R7
+... BWXBQ27
+... BWXTNQ4
+... BX7RKZ9
+... BX7RPQ5
+... BYN8P69
+... BYT3LB5
+... BYTLC94
+... BYW6P64
+... BYXJKZ6
+... BYXX1Y4
+... BZ0S6X3
+... BZ21T08
+...
+... '''
+>>> [x for x in numbers.splitlines() if x and not sedol.is_valid(x)]
+[]

http://arthurdejong.org/git/python-stdnum/commit/?id=fb91775be1cb5f047ced3173e894e2c81d7fd311

commit fb91775be1cb5f047ced3173e894e2c81d7fd311
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Oct 10 17:06:19 2015 +0200

    Add information to Russian package

diff --git a/stdnum/ru/__init__.py b/stdnum/ru/__init__.py
index e69de29..fb58656 100644
--- a/stdnum/ru/__init__.py
+++ b/stdnum/ru/__init__.py
@@ -0,0 +1,21 @@
+# __init__.py - collection of Russian numbers
+# coding: utf-8
+#
+# Copyright (C) 2015 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""Collection of Russian numbers."""

http://arthurdejong.org/git/python-stdnum/commit/?id=ebb5c07b36b1595b3a5dee22c5c82e15b027dbf3

commit ebb5c07b36b1595b3a5dee22c5c82e15b027dbf3
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Oct 10 17:03:17 2015 +0200

    Move numdb test file
    
    This places the test database file in the tests directory.

diff --git a/MANIFEST.in b/MANIFEST.in
index e544383..320edf1 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,3 @@
-include README NEWS ChangeLog COPYING numdb-test.dat *.py
+include README NEWS ChangeLog COPYING *.py
 recursive-include tests *.doctest
 recursive-include docs *.rst *.py
diff --git a/stdnum/numdb.py b/stdnum/numdb.py
index a21e7e7..8a635fd 100644
--- a/stdnum/numdb.py
+++ b/stdnum/numdb.py
@@ -25,7 +25,7 @@ numbers, etc).
 
 To read a database from a file:
 
->>> dbfile = read(open('numdb-test.dat', 'r'))
+>>> dbfile = read(open('tests/numdb-test.dat', 'r'))
 
 To split a number:
 
diff --git a/numdb-test.dat b/tests/numdb-test.dat
similarity index 100%
rename from numdb-test.dat
rename to tests/numdb-test.dat

http://arthurdejong.org/git/python-stdnum/commit/?id=fa8099ebbf06fc45e62d8b1ed6a12b5a2405476c

commit fa8099ebbf06fc45e62d8b1ed6a12b5a2405476c
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Oct 10 16:54:45 2015 +0200

    Add int. maritime org. number (IMO)
    
    This adds checks for the International Maritime Organization number used
    to identify ships. However, there seem to be a lot of ships with an IMO
    number that does not follow these rules (different check digits or even
    length).

diff --git a/stdnum/imo.py b/stdnum/imo.py
new file mode 100644
index 0000000..a423bf3
--- /dev/null
+++ b/stdnum/imo.py
@@ -0,0 +1,85 @@
+# imo.py - functions for handling IMO numbers
+# coding: utf-8
+#
+# Copyright (C) 2015 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""IMO number (International Maritime Organization number).
+
+A number used to uniquely identify ships (the hull) for purposes of
+registering owners and management companies. The ship identification number
+consists of a six-digit sequentially assigned number and a check digit. The
+number is usually prefixed with "IMO".
+
+Note that there seem to be a large number of ships with an IMO that does not
+have a valid check digit or even have a different length.
+
+>>> validate('IMO 9319466')
+'9319466'
+>>> validate('IMO 8814275')
+'8814275'
+>>> validate('8814274')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+>>> format('8814275')
+'IMO 8814275'
+"""
+
+from stdnum.exceptions import *
+from stdnum.util import clean
+
+
+def compact(number):
+    """Convert the number to the minimal representation. This strips the
+    number of any valid separators and removes surrounding whitespace."""
+    number = clean(number, ' ').upper().strip()
+    if number.startswith('IMO'):
+        number = number[3:]
+    return number
+
+
+def calc_check_digit(number):
+    """Calculate the check digits for the number."""
+    return str(sum(int(n) * (7 - i) for i, n in enumerate(number[:6])) % 10)
+
+
+def validate(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    number = compact(number)
+    if not number.isdigit():
+        raise InvalidFormat()
+    if len(number) != 7:
+        raise InvalidLength()
+    if calc_check_digit(number[:-1]) != number[-1]:
+        raise InvalidChecksum()
+    return number
+
+
+def is_valid(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    try:
+        return bool(validate(number))
+    except ValidationError:
+        return False
+
+
+def format(number):
+    """Reformat the passed number to the standard format."""
+    return 'IMO ' + compact(number)

http://arthurdejong.org/git/python-stdnum/commit/?id=111b4fd7c91b5d3c7bce921a1436bc7d21d7cdfa

commit 111b4fd7c91b5d3c7bce921a1436bc7d21d7cdfa
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Oct 10 15:25:48 2015 +0200

    Fix handling of strip_check_digits in ISAN
    
    This fixes the compact() function to honor the strip_check_digits
    argument and does not validate the check digits if they are passed to
    validate together with strip_check_digits.

diff --git a/stdnum/isan.py b/stdnum/isan.py
index 24438f5..8edb091 100644
--- a/stdnum/isan.py
+++ b/stdnum/isan.py
@@ -70,7 +70,8 @@ def compact(number, strip_check_digits=True):
     of any valid separators and removes surrounding whitespace. The check
     digits are removed by default."""
     number = list(split(number))
-    number[2] = number[4] = ''
+    if strip_check_digits:
+        number[2] = number[4] = ''
     return ''.join(number)
 
 
@@ -89,14 +90,15 @@ def validate(number, strip_check_digits=False, 
add_check_digits=False):
     if len(root) != 12 or len(episode) != 4 or len(check1) not in (0, 1) or \
        len(version) not in (0, 8) or len(check1) not in (0, 1):
         raise InvalidLength()
+    # allow removing check digits
+    if strip_check_digits:
+        check1 = check2 = ''
     # check check digits
     if check1:
         mod_37_36.validate(root + episode + check1)
     if check2:
         mod_37_36.validate(root + episode + version + check2)
-    # remove or add check digits
-    if strip_check_digits:
-        check1 = check2 = ''
+    # add check digits
     if add_check_digits and not check1:
         check1 = mod_37_36.calc_check_digit(root + episode)
     if add_check_digits and not check2 and version:
diff --git a/tests/test_isan.doctest b/tests/test_isan.doctest
index 7984e48..32f0f1d 100644
--- a/tests/test_isan.doctest
+++ b/tests/test_isan.doctest
@@ -45,10 +45,14 @@ Compacting should also work:
 '000000018947000000000000'
 >>> isan.compact('0000-0000-D07A-0090-Q-0000-0000-X')
 '00000000D07A009000000000'
+>>> isan.compact('0000-0000-D07A-0090-Q-0000-0000-X', strip_check_digits=False)
+'00000000D07A0090Q00000000X'
 >>> isan.compact('000000018947000000000000')
 '000000018947000000000000'
 >>> isan.compact('1881-66C7-3420-6541-Y')
 '188166C734206541'
+>>> isan.compact('1881-66C7-3420-6541-Y', strip_check_digits=False)
+'188166C734206541Y'
 >>> isan.compact('1881-66C7-3420-6541')
 '188166C734206541'
 
@@ -89,12 +93,20 @@ or absence of a version number.
 '0000-0000-D07A-0090-Q'
 >>> isan.format('1881-66C734206541-9F3A-0245')
 '1881-66C7-3420-6541-Y-9F3A-0245-O'
+>>> isan.format('1881-66C7-3420-6541-?-9F3A-0245-?', strip_check_digits=True, 
add_check_digits=False)
+'1881-66C7-3420-6541-9F3A-0245'
 >>> isan.format('1881-66C7-3420-6541-?-9F3A-0245-?', strip_check_digits=True, 
 >>> add_check_digits=True)
 '1881-66C7-3420-6541-Y-9F3A-0245-O'
+
+
+The validate() function also allows stripping and (re)adding check digits.
+
 >>> isan.validate('1881-66C7-3420-6541-Y-9F3A-0245-O', strip_check_digits=True)
 '188166C7342065419F3A0245'
 >>> isan.validate('188166C7342065419F3A0245', add_check_digits=True)
 '188166C734206541Y9F3A0245O'
+>>> isan.validate('1881-66C7-3420-6541-X-9F3A-0245-A', 
strip_check_digits=True, add_check_digits=True)
+'188166C734206541Y9F3A0245O'
 
 
 A simple test for the to_binary() function.

http://arthurdejong.org/git/python-stdnum/commit/?id=9f9d13cc1c1a25157494980cfc7c952411c1ea8c

commit 9f9d13cc1c1a25157494980cfc7c952411c1ea8c
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Oct 10 15:09:04 2015 +0200

    Add international securities id (ISIN)
    
    This adds support for handling ISINs (International Securities
    Identification Number). The can contain a CUSIP but performing this
    additional validation is currently not performed.

diff --git a/stdnum/isin.py b/stdnum/isin.py
new file mode 100644
index 0000000..524640a
--- /dev/null
+++ b/stdnum/isin.py
@@ -0,0 +1,116 @@
+# isin.py - functions for handling ISIN numbers
+#
+# Copyright (C) 2015 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""ISIN (International Securities Identification Number).
+
+The ISIN is a 12-character alpha-numerical code specified in ISO 6166 used to
+identify exchange listed securities such as bonds, commercial paper, stocks
+and warrants. The number is formed of a two-letter country code, a nine
+character national security identifier and a single check digit.
+
+This module does not currently separately validate the embedded national
+security identifier part (e.g. when it is a CUSIP).
+
+More information:
+  https://en.wikipedia.org/wiki/International_Securities_Identification_Number
+
+>>> validate('US0378331005')
+'US0378331005'
+>>> validate('US0378331003')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+"""
+
+from stdnum.exceptions import *
+from stdnum.util import clean
+
+
+# all valid ISO 3166-1 alpha-2 country codes
+_iso_3116_1_country_codes = [
+    'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT',
+    'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI',
+    'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY',
+    'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN',
+    'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM',
+    'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK',
+    'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL',
+    'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM',
+    'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR',
+    'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN',
+    'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS',
+    'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK',
+    'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW',
+    'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP',
+    'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM',
+    'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW',
+    'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM',
+    'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TC', 'TD', 'TF',
+    'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW',
+    'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI',
+    'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW']
+
+# the special XS country code is for international securities
+# substitute agencies can allocate an ISIN starting with XA (CUSIP Global
+# Services), XB (NSD Russia), XC (WM Datenservice Germany) or XD (SIX
+# Telekurs).
+_country_codes = set(_iso_3116_1_country_codes + [
+    'XS', 'EU', 'XA', 'XB', 'XC', 'XD'])
+
+# the letters allowed in an ISIN
+_alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+
+
+def compact(number):
+    """Convert the number to the minimal representation. This strips the
+    number of any valid separators and removes surrounding whitespace."""
+    return clean(number, ' ').strip().upper()
+
+
+def calc_check_digit(number):
+    """Calculate the check digits for the number."""
+    # convert to numeric first, then double some, then sum individual digits
+    number = ''.join(str(_alphabet.index(n)) for n in number)
+    number = ''.join(
+        str(int(n) * (2 - i % 2)) for i, n in enumerate(reversed(number)))
+    return str((10 - sum(int(n) for n in number)) % 10)
+
+
+def validate(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    number = compact(number)
+    if not all(x in _alphabet for x in number):
+        raise InvalidFormat()
+    if len(number) != 12:
+        raise InvalidLength()
+    if number[:2] not in _country_codes:
+        raise InvalidComponent()
+    if calc_check_digit(number[:-1]) != number[-1]:
+        raise InvalidChecksum()
+    return number
+
+
+def is_valid(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    try:
+        return bool(validate(number))
+    except ValidationError:
+        return False
diff --git a/tests/test_isin.doctest b/tests/test_isin.doctest
new file mode 100644
index 0000000..84cb9fa
--- /dev/null
+++ b/tests/test_isin.doctest
@@ -0,0 +1,343 @@
+test_isin.doctest - more detailed doctests for the stdnum.isin module
+
+Copyright (C) 2015 Arthur de Jong
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA
+
+
+This file contains more detailed doctests for the stdnum.isin module. It
+tries to validate a number of numbers that have been found online.
+
+>>> from stdnum import isin
+>>> from stdnum.exceptions import *
+
+
+The number should start with a valid ISO 3166-1 alpha-2 country code:
+
+>>> isin.validate('ZZ0378331005')
+Traceback (most recent call last):
+    ...
+InvalidComponent: ...
+
+
+These have been found online and should all be valid numbers.
+
+>>> numbers = '''
+...
+... AU000000AAI6
+... AU000000AAT3
+... AU000000CDD7
+... AU000000HYR2
+... AU000000IIN2
+... AU000000PMI9
+... AU000000QMN7
+... AU000000TAG0
+... AU000000TAN6
+... AU000000YRR1
+... AU00000GPHO9
+... AU00000HAVO0
+... AU00000IMFG2
+... AU00000LSRO2
+... AU00000MEUO4
+... AU00000MFFO2
+... AU00000NMGG0
+... AU00000TGGR6
+... AU00000TISN6
+... AU00000TYXN3
+... AU00000WXHG6
+... AU0000ARUAM8
+... AU0000AVIAL7
+... AU0000AYCAA7
+... AU0000BRKDC2
+... AU0000BWPAK0
+... AU0000CIMAK4
+... AU0000COOAQ5
+... AU0000CXMAK3
+... AU0000DM1AE9
+... AU0000DM1XX1
+... AU0000DNAAK7
+... AU0000ENRAK9
+... AU0000FXJAB1
+... AU0000INDAK0
+... AU0000JALAO0
+... AU0000JKLAN1
+... AU0000LCYAM9
+... AU0000MSCAK3
+... AU0000NANAI0
+... AU0000PGIAO0
+... AU0000RGSAO5
+... AU0000SHKAO8
+... AU0000SIRAW4
+... AU0000SWLAI7
+... AU0000TSMAQ4
+... AU0000UPGDA8
+... AU0000VICAK5
+... AU0000VMXDC4
+... AU0000WBCAB7
+... AU0000WBCPF6
+... AU0000WBCZZ3
+... AU0000YNBAE4
+... AU000AMPJOF0
+... AU000ANZSRX4
+... AU000ARGJOM4
+... AU000CBALOX0
+... AU000CBALOZ5
+... AU000DJXKOB1
+... AU000ETFBSK6
+... AU000ETPCOP1
+... AU000ETPNRG0
+... AU000FMGWOP0
+... AU000GEMJOG9
+... AU000HFRJOT6
+... AU000MMSKOA8
+... AU000ORIKOC0
+... AU000PRYKOU6
+... AU000QANSWH6
+... AU000SFRKOJ2
+... AU000SGPKOF2
+... AU000SHLSSA4
+... AU000SYDIOO7
+... AU000SYISRX9
+... AU000TLSSSN2
+... AU000WBCLOQ8
+... AU000WESSWC6
+... AU000WFDDSN2
+... AU000WORSSR3
+... AU000WPLIOZ6
+... AU000XINABL9
+... AU000XINAHO0
+... AU000XINAKD7
+... AU000XINANQ3
+... AU000XINAV76
+... AU300PUMC025
+... AU60WHT00394
+... BMG4209T1009
+... BMG9834K1505
+... CA4656761042
+... CH0004171952
+... CH0010543905
+... CH0011105639
+... CH0011443832
+... CH0019112892
+... CH0028465273
+... CH0031152017
+... CH0032973528
+... CH0032979764
+... CH0032979871
+... CH0033337277
+... CH0037485627
+... CH0037485734
+... CH0042345089
+... CH0049815712
+... CH0100461042
+... CH0237135386
+... CH0276194575
+... CH0276213797
+... CH0278112146
+... CH0284369565
+... CH0288210914
+... CH0292642904
+... CH0293194293
+... CNE100000114
+... CNE100001M79
+... CWN8141V6954
+... CWN8144F3826
+... DE0005994388
+... DE0005997316
+... DE0007200412
+... DE0007681454
+... DE0007681975
+... DE0007932220
+... DE000A0DMV42
+... DE000A0DMV83
+... DE000A0G9FM3
+... DE000A0Z2946
+... DE000A0Z29G3
+... DE000A0Z3WV9
+... DE000A0Z3Z46
+... DE000A1EW8B3
+... DK0002006113
+... DK0002021690
+... DK0004903424
+... DK0004903770
+... DK0004909033
+... DK0005602439
+... DK0006321633
+... DK0006707070
+... DK0006900741
+... DK0007300339
+... DK0007702054
+... DK0007703458
+... DK0008100290
+... DK0008703150
+... DK0009255002
+... DK0009281511
+... DK0009351900
+... DK0009360141
+... DK0009362519
+... DK0009363087
+... DK0009705063
+... DK0009770471
+... DK0009771529
+... DK0030074216
+... DK0030269220
+... FR0010655753
+... GB0002405495
+... GB0003375820
+... GB0005058408
+... GB0031192486
+... GB0032211095
+... GB00B0599712
+... GB00B0599P95
+... GB00B05CVL88
+... GB00B05DCP29
+... GB00B05DGB62
+... GB00B05DNW68
+... GB00B05DSX21
+... GB00B05F5F96
+... GB00B05F5Q02
+... GB00B05FQ671
+... GB00B05FVH32
+... GB00B05G0029
+... GB00B05G4005
+... GB00B05G5K71
+... GB00B05G9160
+... GB00B0SD8170
+... GB00B0SDJ810
+... GB00B0SDL857
+... GB00B0VMNC71
+... GB00B11DNW78
+... GB00B1893G22
+... GB00B1CQNL45
+... GB00B1CQTJ43
+... GB00B1CQVM10
+... GB00B1KW2C82
+... GB00B2NL0X68
+... GB00B2PVJ269
+... GB00B2PVKX41
+... GB00B2PY1T61
+... GB00B2PY4209
+... GB00B2RBNV18
+... GB00B3BR9475
+... GB00B3CFYP64
+... GB00B3CFZV99
+... GB00B3CG0P10
+... GB00B3CG1L47
+... GB00B3LBXL47
+... GB00B3LFND86
+... GB00B3NVXT01
+... GB00B3Q2HT84
+... GB00B4V4XS73
+... GB00B4ZQ7X29
+... GB00B51RP987
+... GB00B5VJC047
+... GB00B6RRK619
+... GB00B6VQMK23
+... GB00B6VW1G93
+... GB00B6ZXH376
+... GB00B7LDLC53
+... GB00B7MT2J68
+... GB00B80KHR13
+... GB00B8113P38
+... GB00B8B4R053
+... GB00B8J3Q414
+... GB00BJ36MF67
+... GB00BK1PTB77
+... GB00BRKXHJ51
+... GB00BV0MBK93
+... GB00BVVT7780
+... GB00BYZ3J264
+... HK0000096617
+... HK0000179686
+... HK0000227162
+... HK0000230331
+... HK0000244647
+... HK0000245925
+... HK0000246709
+... HK0000248267
+... HK0000248515
+... HK0000249794
+... HK0000250578
+... HK0000253002
+... HK0000258720
+... HK0000262441
+... HK0000263449
+... HK0000265345
+... HK0000267077
+... IE0004906560
+... IE00B4WPHX27
+... IE00BLSNMW37
+... IE00BRHZ0398
+... INE019A07282
+... INE115A07ED1
+... INE115A07GL9
+... INE321N07046
+... INE476M07081
+... INE476M07123
+... INE523E07BF9
+... INE523E07BT0
+... INE549F08434
+... INE651J07036
+... INE691I07AO9
+... INE823G07193
+... INE827N07109
+... INF209K01MJ3
+... JE00B1RJLF86
+... JE00B24DML30
+... JE00B2NFV571
+... KYG198891072
+... KYG4643G1029
+... KYG4708D1016
+... KYG607441022
+... KYG811511131
+... KYG886121097
+... LU0455009182
+... LU0460389678
+... LU0683010093
+... LU0688203917
+... LU0859479155
+... LU1048317298
+... NL0010524690
+... NL0010865176
+... NL0010882288
+... NL0010948949
+... NL0011221981
+... NL0011278445
+... NL0011327101
+... NL0011337290
+... US101137AA59
+... US12591DAC56
+... US17322R1059
+... US26067A1108
+... US26070W1099
+... US36962GXS82
+... US78387GAK94
+... US867363AF06
+... XC0006883695
+... XS0110106365
+... XS0137672381
+... XS0162732951
+... XS0324245116
+... XS0409318309
+... XS0458685913
+... XS0691593114
+... XS0758793342
+... XS0841191991
+... XS0861774635
+...
+... '''
+>>> [x for x in numbers.splitlines() if x and not isin.is_valid(x)]
+[]

http://arthurdejong.org/git/python-stdnum/commit/?id=522611c2ff966f063f9ee2c2087b984772a97b33

commit 522611c2ff966f063f9ee2c2087b984772a97b33
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Fri Oct 9 21:48:00 2015 +0200

    Add CUSIP number

diff --git a/stdnum/cusip.py b/stdnum/cusip.py
new file mode 100644
index 0000000..4d54614
--- /dev/null
+++ b/stdnum/cusip.py
@@ -0,0 +1,80 @@
+# cusip.py - functions for handling CUSIP numbers
+#
+# Copyright (C) 2015 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""CUSIP number (financial security identification number).
+
+CUSIP (Committee on Uniform Securities Identification Procedures) numbers are
+used to identify financial securities. CUSIP numbers are a nine-character
+alphanumeric code where the first six characters identify the issuer,
+followed by two digits that identify and a check digit.
+
+More information:
+  https://en.wikipedia.org/wiki/CUSIP
+  https://www.cusip.com/
+
+>>> validate('DUS0421C5')
+'DUS0421C5'
+>>> validate('DUS0421CN')
+Traceback (most recent call last):
+    ...
+InvalidChecksum: ...
+"""
+
+from stdnum.exceptions import *
+from stdnum.util import clean
+
+
+def compact(number):
+    """Convert the number to the minimal representation. This strips the
+    number of any valid separators and removes surrounding whitespace."""
+    return clean(number, ' ').strip().upper()
+
+
+# O and I are not valid but are accounted for in the check digit calculation
+_alphabet = '0123456789ABCDEFGH JKLMN PQRSTUVWXYZ*@#'
+
+
+def calc_check_digit(number):
+    """Calculate the check digits for the number."""
+    # convert to numeric first, then sum individual digits
+    number = ''.join(
+        str((i % 2 + 1) * _alphabet.index(n)) for i, n in enumerate(number))
+    return str((10 - sum(int(n) for n in number)) % 10)
+
+
+def validate(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    number = compact(number)
+    if not all(x in _alphabet for x in number):
+        raise InvalidFormat()
+    if len(number) != 9:
+        raise InvalidLength()
+    if calc_check_digit(number[:-1]) != number[-1]:
+        raise InvalidChecksum()
+    return number
+
+
+def is_valid(number):
+    """Checks to see if the number provided is valid. This checks the length
+    and check digit."""
+    try:
+        return bool(validate(number))
+    except ValidationError:
+        return False
diff --git a/tests/test_cusip.doctest b/tests/test_cusip.doctest
new file mode 100644
index 0000000..60e9c18
--- /dev/null
+++ b/tests/test_cusip.doctest
@@ -0,0 +1,249 @@
+test_cusip.doctest - more detailed doctests for the stdnum.cusip module
+
+Copyright (C) 2015 Arthur de Jong
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA
+
+
+This file contains more detailed doctests for the stdnum.cusip module. It
+tries to validate a number of numbers that have been found online.
+
+>>> from stdnum import cusip
+>>> from stdnum.exceptions import *
+
+
+Number should not use O (captial o) or I (capital 1) to avoid confusion with
+0 and 1:
+
+>>> cusip.validate('0O141T575')
+Traceback (most recent call last):
+    ...
+InvalidFormat: ...
+>>> cusip.validate('257I32103')
+Traceback (most recent call last):
+    ...
+InvalidFormat: ...
+
+
+These have been found online and should all be valid numbers.
+
+>>> numbers = '''
+...
+... 00078H125
+... 00080Y348
+... 00141H409
+... 00141M572
+... 00141T577
+... 00141V267
+... 00142F832
+... 00142K500
+... 00170J862
+... 00170K109
+... 00170M873
+... 00758M261
+... 024524746
+... 024932808
+... 024934408
+... 025081704
+... 025081860
+... 02631C817
+... 068278704
+... 068278878
+... 06828M405
+... 101156602
+... 119804102
+... 12628J600
+... 140543828
+... 192476109
+... 19765J830
+... 19765N401
+... 19765Y852
+... 207267105
+... 23336W809
+... 23337G134
+... 23337R502
+... 23338F713
+... 245908660
+... 245917505
+... 24610B859
+... 25155T528
+... 25156A668
+... 25157M778
+... 25159K309
+... 25159L745
+... 25264S403
+... 254939424
+... 257132100
+... 258618701
+... 261967103
+... 261967822
+... 261986566
+... 265458513
+... 265458570
+... 269858817
+... 277902565
+... 277905436
+... 29372R208
+... 313923302
+... 314172743
+... 315792598
+... 315805325
+... 315807651
+... 315911875
+... 315920579
+... 316069103
+... 31607A208
+... 316146257
+... 316175850
+... 31638R204
+... 316390277
+... 316390335
+... 316390640
+... 316390681
+... 320600109
+... 320604606
+... 320917107
+... 353496854
+... 353535107
+... 354128704
+... 354723769
+... 36158T506
+... 409902624
+... 416649507
+... 416649606
+... 425888104
+... 42588P825
+... 42588P882
+... 44929K630
+... 461418691
+... 465898682
+... 469785109
+... 471023531
+... 47803M663
+... 4812A4427
+... 4812C0548
+... 52106N335
+... 52106N442
+... 52106N632
+... 52106N657
+... 543912604
+... 543913305
+... 552984601
+... 552986309
+... 552986853
+... 557492428
+... 56063J849
+... 56063U851
+... 56166Y438
+... 561709692
+... 561717661
+... 57056B ZW1
+... 575719109
+... 592905756
+... 61744J499
+... 640917209
+... 640917407
+... 64122M506
+... 643642200
+... 647108414
+... 648018828
+... 650914203
+... 66537Y165
+... 67065R408
+... 67065R812
+... 670678762
+... 670690767
+... 670700608
+... 670725738
+... 670729599
+... 670729730
+... 680029667
+... 68583W507
+... 704329101
+... 70472Q302
+... 70472Q880
+... 72200Q232
+... 72201F383
+... 72201F458
+... 72201M800
+... 72201T664
+... 72201U430
+... 741481105
+... 741486104
+... 74149P390
+... 74149P648
+... 74149P689
+... 74149P820
+... 742935521
+... 742935547
+... 74316P207
+... 743185373
+... 743185464
+... 74318Q864
+... 74683L508
+... 749255121
+... 74972H200
+... 74972H283
+... 74972H390
+... 74972H598
+... 74972K666
+... 76628T496
+... 77956H302
+... 783554470
+... 783554728
+... 784924458
+... 803431105
+... 803431410
+... 829334101
+... 82980D400
+... 884116872
+... 890085327
+... 890085871
+... 89354D874
+... 904504560
+... 904504586
+... 912810EQ7
+... 912828C24
+... 912828EG1
+... 912828HA1
+... 912828KD1
+... 912828UA6
+... 920461209
+... 92646A252
+... 92913K645
+... 92913K884
+... 92913L775
+... 92913R822
+... 92914A661
+... 93208V106
+... 936793306
+... 936793504
+... 94975P686
+... 94984B108
+... 94984B538
+... 949915177
+... 949915557
+... 957904584
+... 969251719
+... 969251834
+... 984281204
+... Y0488F100
+... Y27257149
+... Y44425117
+...
+... '''
+>>> [x for x in numbers.splitlines() if x and not cusip.is_valid(x)]
+[]

-----------------------------------------------------------------------

Summary of changes:
 MANIFEST.in                            |    2 +-
 stdnum/{do/rnc.py => cusip.py}         |   61 +++---
 stdnum/{al/nipt.py => de/wkn.py}       |   62 +++---
 stdnum/{do/rnc.py => gb/sedol.py}      |   59 +++---
 stdnum/{do/rnc.py => imo.py}           |   54 ++---
 stdnum/isan.py                         |   10 +-
 stdnum/isin.py                         |  125 ++++++++++++
 stdnum/numdb.py                        |    2 +-
 stdnum/ru/__init__.py                  |   21 ++
 numdb-test.dat => tests/numdb-test.dat |    0
 tests/test_cusip.doctest               |  249 +++++++++++++++++++++++
 tests/test_de_wkn.doctest              |  233 ++++++++++++++++++++++
 tests/test_gb_sedol.doctest            |  255 ++++++++++++++++++++++++
 tests/test_isan.doctest                |   12 ++
 tests/test_isin.doctest                |  343 ++++++++++++++++++++++++++++++++
 15 files changed, 1372 insertions(+), 116 deletions(-)
 copy stdnum/{do/rnc.py => cusip.py} (51%)
 copy stdnum/{al/nipt.py => de/wkn.py} (55%)
 copy stdnum/{do/rnc.py => gb/sedol.py} (51%)
 copy stdnum/{do/rnc.py => imo.py} (56%)
 create mode 100644 stdnum/isin.py
 rename numdb-test.dat => tests/numdb-test.dat (100%)
 create mode 100644 tests/test_cusip.doctest
 create mode 100644 tests/test_de_wkn.doctest
 create mode 100644 tests/test_gb_sedol.doctest
 create mode 100644 tests/test_isin.doctest


hooks/post-receive
-- 
python-stdnum
-- 
To unsubscribe send an email to
python-stdnum-commits-unsubscribe@lists.arthurdejong.org or see
http://lists.arthurdejong.org/python-stdnum-commits/