Commits

sirex committed 21f7085

initial

  • Participants

Comments (0)

Files changed (10)

+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
+A library for creating International Bank Account Numbers (IBAN)
+
+License: Gnu Lesser General Public License

ibanlib/README.txt

+=========================================================
+International Bank Account Number (IBAN) library
+=========================================================
+
+The international bank account number (IBAN) consists basically of the
+following items:
+
+1) two-letter Country ISO-Code (e.g. 'US', 'DE')
+2) two-digit IBAN-Checksum (basically a mod(97))
+3) Bank Code (The domestic bank code)
+4) Branch Code (For national branches of a bank)
+5) Account Number (Bank specific)
+6) Bank specific check sum (often part of the account number)
+
+The international bank code (BIC) is NOT part of the IBAN.
+
+The above items are simply concatenated and form the IBAN. However, every
+country has its own format of the above items, e.g. in Austria account numbers
+are digits only and are between 6 and 11 digits long, which is different in
+other countries.
+
+The iban.py module offers an international account object Int_Account, which
+stores the above items, checks for validity during creation and provides the
+IBAN.
+
+ >>> from ibanlib.iban import IntAccount, valid
+ >>> a = IntAccount()
+ Traceback (most recent call last):
+ AttributeError: Country or IBAN is mandatory
+ 
+The country or IBAN has to be specified during creation, otherwise, the class
+does not know the country for which the IBAN specs should be applied.
+ 
+ >>> a=IntAccount('AT')
+ >>> a.account = '123456789'
+ >>> a.bank = '12345'
+ >>> a.bank
+ '12345'
+ >>> a.bank = '33333'
+ >>> a.bank
+ '33333'
+
+These attributes are automatically checked, wrong values lead to an IBANError
+
+ >>> a.account = '123ABC'                     # Only digits allowed
+ Traceback (most recent call last):
+ IBANError: Invalid value "123ABC" for account
+ >>> a.account = '12234234234234324234'       # Too long
+ Traceback (most recent call last):
+ IBANError: Invalid value "12234234234234324234" for account
+
+These object attributes can also be set at object creation time:
+ >>> a = IntAccount('AT', bank='12345', account='123456789')
+ 
+If all required attributes of a country are set, the iban may be retrieved
+
+ >>> a.iban
+ 'AT141234500123456789'
+
+If attributes are missing, no IBAN can be retrieved
+
+ >>> IntAccount('AT').iban
+ Traceback (most recent call last):
+ IBANError: Attribute bank is missing, no IBAN can be created
+ 
+An international account can also be initialized with the IBAN, whereas the
+other given initialization options (country, account etc.) are ignored:
+
+ >>> b=IntAccount(iban='AT141234500123456789')
+ >>> b.account
+ '123456789'
+ >>> b == a
+ True
+
+If the given IBAN is invalid, an error is raised:
+
+ >>> b=IntAccount(iban='AT141234500123456781')
+ Traceback (most recent call last):
+ IBANError: IBAN has an invalid checksum
+ 
+If the specified country for an IBAN is not known, an error is raised:
+
+ >>> b=IntAccount(iban='XY141234500123456789')
+ Traceback (most recent call last):
+ IBANError: Country XY not implemented
+ >>> IntAccount('ZX')
+ Traceback (most recent call last):
+ IBANError: Country ZX not implemented
+
+Moreover, it can be requested, if the international account, or, more specific,
+the according country, is member of the SEPA contract:
+
+ >>> b.is_sepa
+ True
+
+IBANs can also be easily checked for validity:
+
+ >>> valid('AT141234500123456789')
+ True
+ >>> valid('AT14123450012345678')
+ False
+ >>> valid('AT341234500123456789')
+ False
+ >>> valid('AT14123450012345a78')
+ False
+
+The country specifics are stored in a configuration file called
+"countries.cfg", the syntax of this file is given there. The function
+get_country_specs() can be used to read in the specifications for the needed
+country
+
+ >>> from ibanlib.iban import get_country_specs
+ >>> d=get_country_specs('AT')
+
+All configurations is now read into d. Bank/Account can now be checked for
+validity
+ 
+ >>> d['account'].valid('123')   # too short
+ False
+ >>> d['account'].valid('1231234')  # valid
+ True
+ >>> d['account'].valid('12312312321312321321313') # too long
+ False
+ >>> d['account'].valid('123ABC234')  # only digits allowed
+ False
+
+Short data can be filled to its maximum length
+
+ >>> d['account'].fill('1234567')
+ '00001234567'
+ 
+Of course, IBANs can be generated for various countries, here are some
+examples:
+
+Andorra
+
+ >>> IntAccount(iban='AD1200012030200359100100')
+ IntAccount(country='AD', bank='0001', branche='2030', account='200359100100', check1='None', check2='None', check3='None')
+
+Austria
+ >>> IntAccount('AT',bank='19043',account='234573201').iban
+ 'AT611904300234573201'
+
+Belgium
+ >>> IntAccount (iban='BE68539007547034')
+ IntAccount(country='BE', bank='539', branche='None', account='0075470', check1='None', check2='None', check3='34')
+
+Bosnia and Herzegovina
+ >>> IntAccount (iban='BA391290079401028494')
+ IntAccount(country='BA', bank='129', branche='007', account='94010284', check1='None', check2='None', check3='94')
+
+Bulgaria
+ >>> IntAccount (iban='BG80BNBG96611020345678')
+ IntAccount(country='BG', bank='BNBG', branche='9661', account='1020345678', check1='None', check2='None', check3='None')
+
+Croatia
+ >>> IntAccount (iban='HR1210010051863000160')
+ IntAccount(country='HR', bank='1001005', branche='None', account='1863000160', check1='None', check2='None', check3='None')
+
+Cyprus
+ >>> IntAccount (iban='CY17002001280000001200527600')
+ IntAccount(country='CY', bank='2', branche='128', account='1200527600', check1='None', check2='None', check3='None')
+
+Czech Republik
+ >>> IntAccount (iban='CZ6508000000192000145399')
+ IntAccount(country='CZ', bank='0800', branche='None', account='192000145399', check1='None', check2='None', check3='None')
+
+Denmark
+ >>> IntAccount (iban='DK5000400440116243')
+ IntAccount(country='DK', bank='40', branche='None', account='44011624', check1='None', check2='None', check3='3')
+
+Estonia
+ >>> IntAccount (iban='EE382200221020145685')
+ IntAccount(country='EE', bank='22', branche='None', account='221020145685', check1='None', check2='None', check3='None')
+
+Finland
+ >>> IntAccount (iban='FI2112345600000785')
+ IntAccount(country='FI', bank='123456', branche='None', account='78', check1='None', check2='None', check3='5')
+
+France
+ >>> IntAccount (iban='FR1420041010050500013M02606')
+ IntAccount(country='FR', bank='20041', branche='01005', account='0500013M026', check1='None', check2='None', check3='06')
+
+Germany
+ >>> IntAccount(iban='DE89370400440532013000')
+ IntAccount(country='DE', bank='37040044', branche='None', account='532013000', check1='None', check2='None', check3='None')
+ >>> IntAccount('DE',bank='37040044', account='532013000').iban
+ 'DE89370400440532013000'
+
+Gibraltar
+ >>> IntAccount (iban='GI75NWBK000000007099453')
+ IntAccount(country='GI', bank='NWBK', branche='None', account='000000007099453', check1='None', check2='None', check3='None')
+
+Greece
+ >>> IntAccount (iban='GR1601101250000000012300695')
+ IntAccount(country='GR', bank='11', branche='125', account='12300695', check1='None', check2='None', check3='None')
+
+Hungary
+ >>> IntAccount (iban='HU42117730161111101800000000')
+ IntAccount(country='HU', bank='117', branche='7301', account='111110180000000', check1='None', check2='6', check3='0')
+
+Iceland
+ >>> IntAccount (iban='IS140159260076545510730339')
+ IntAccount(country='IS', bank='0159', branche='None', account='260076545510730339', check1='None', check2='None', check3='None')
+
+Ireland
+ >>> IntAccount (iban='IE29AIBK93115212345678')
+ IntAccount(country='IE', bank='AIBK', branche='931152', account='12345678', check1='None', check2='None', check3='None')
+
+Italy 
+ 
+ >>> IntAccount('IT', bank='05428', branche='11101', 
+ ...            account='123456', check1='X').iban
+ 'IT60X0542811101000000123456'
+ >>> valid(iban='IT21Q054280160000ABCD12ZE34')
+ True
+ >>> valid('IT30C0800001000123VALE456NA')
+ True
+ >>> valid('IT11V0600003200000011556BFE')
+ True
+ >>> valid('IT21J0100516052120050012345')
+ True
+ 
+Latvia
+ >>> IntAccount (iban='LV80BANK0000435195001')
+ IntAccount(country='LV', bank='BANK', branche='None', account='0000435195001', check1='None', check2='None', check3='None')
+
+Liechtenstein
+ >>> IntAccount (iban='LI21088100002324013AA')
+ IntAccount(country='LI', bank='8810', branche='None', account='0002324013AA', check1='None', check2='None', check3='None')
+
+Lithuania
+ >>> IntAccount (iban='LT121000011101001000')
+ IntAccount(country='LT', bank='10000', branche='None', account='11101001000', check1='None', check2='None', check3='None')
+
+Luxemburg
+ >>> IntAccount (iban='LU360029152460050000')
+ IntAccount(country='LU', bank='002', branche='None', account='9152460050000', check1='None', check2='None', check3='None')
+
+Macedonia, former Yugoslav Republic of
+ >>> IntAccount (iban='MK07250120000058984')
+ IntAccount(country='MK', bank='250', branche='None', account='1200000589', check1='None', check2='None', check3='84')
+
+Malta
+ >>> IntAccount (iban='MT84MALT011000012345MTLCAST001S')
+ IntAccount(country='MT', bank='MALT', branche='01100', account='0012345MTLCAST001S', check1='None', check2='None', check3='None')
+ 
+Montenegro
+ >>> IntAccount (iban='ME25505000012345678951')
+ IntAccount(country='ME', bank='505', branche='None', account='0000123456789', check1='None', check2='None', check3='51')
+
+The Netherlands
+ >>> IntAccount (iban='NL91ABNA0417164300')
+ IntAccount(country='NL', bank='ABNA', branche='None', account='417164300', check1='None', check2='None', check3='None')
+
+Norway
+ >>> IntAccount (iban='NO9386011117947')
+ IntAccount(country='NO', bank='8601', branche='None', account='111794', check1='None', check2='None', check3='7')
+
+Poland
+ >>> IntAccount (iban='PL27114020040000300201355387')
+ IntAccount(country='PL', bank='11402004', branche='None', account='0000300201355387', check1='None', check2='None', check3='None')
+
+Portugal
+ >>> IntAccount (iban='PT50000201231234567890154')
+ IntAccount(country='PT', bank='0002', branche='0123', account='12345678901', check1='None', check2='None', check3='54')
+ 
+Romania
+ >>> IntAccount (iban='RO49AAAA1B31007593840000')
+ IntAccount(country='RO', bank='AAAA', branche='None', account='1B31007593840000', check1='None', check2='None', check3='None')
+
+Serbia
+ >>> IntAccount (iban='CS73260005601001611379')
+ IntAccount(country='CS', bank='260', branche='None', account='0056010016113', check1='None', check2='None', check3='79')
+ 
+Slovak Republic
+ >>> IntAccount (iban='SK3112000000198742637541')
+ IntAccount(country='SK', bank='1200', branche='None', account='0000198742637541', check1='None', check2='None', check3='None')
+ 
+Slovenia
+ >>> IntAccount (iban='SI56191000000123438')
+ IntAccount(country='SI', bank='19100', branche='None', account='00001234', check1='None', check2='None', check3='38')
+ 
+Spain
+ >>> IntAccount (iban='ES9121000418450200051332')
+ IntAccount(country='ES', bank='2100', branche='0418', account='0200051332', check1='None', check2='4', check3='5')
+
+Sweden
+ >>> IntAccount (iban='SE3550000000054910000003')
+ IntAccount(country='SE', bank='500', branche='None', account='0000005491000000', check1='None', check2='None', check3='3')
+
+Switzerland
+ >>> IntAccount (iban='CH9300762011623852957')
+ IntAccount(country='CH', bank='00762', branche='None', account='011623852957', check1='None', check2='None', check3='None')
+
+Turkey
+ >>> IntAccount (iban='TR330006100519786457841326')
+ IntAccount(country='TR', bank='00061', branche='None', account='00519786457841326', check1='None', check2='None', check3='None')
+
+United Kingdom
+ >>> IntAccount (iban='GB29NWBK60161331926819')
+ IntAccount(country='GB', bank='NWBK', branche='601613', account='31926819', check1='None', check2='None', check3='None')
+
+Mauritius
+ >>> IntAccount (iban='MU17BOMM0101101030300200000MUR')
+ IntAccount(country='MU', bank='BOMM01', branche='None', account='01101030300200000M', check1='None', check2='None', check3='None')
+ 
+Tunisia
+ >>> IntAccount (iban='TN5914207207100707129648')
+ IntAccount(country='TN', bank='14', branche='207', account='2071007071296', check1='None', check2='None', check3='48')
+ 

ibanlib/__init__.py

+version = ('0', '0', '2', None)
+''' IBAN functions and International Accounts '''
+import os.path
+import string
+import ConfigParser
+import ibanlib
+
+cfg_filename = 'iban_countries.cfg'
+cfg_file = os.path.join(
+    os.path.split(ibanlib.__file__)[0],
+    cfg_filename)
+
+cfg_countries = ConfigParser.ConfigParser()
+cfg_countries.read(cfg_file)
+
+
+class IBANError(Exception):
+    pass
+
+
+def containsOnly(seq, aset):
+    """ Check if sequence "seq" contains ONLY letters in set "aset" """
+    for c in seq:
+        if c not in aset: 
+            return False
+    return True
+
+
+class FormatSpec(object):
+    """ Describes format of iban items """
+    min = None
+    max = None
+    type = None
+    fillchar = None
+    fill_left = True
+    
+    def __init__(self, config):
+        """ Initialize object according to config string """
+
+        allchars = string.maketrans('','')
+        #account = 4/11, n, 0
+        config = config.strip().translate(allchars, ' ')
+        # Split the configuration
+        config = config.split(',')
+        # Read min/max
+        l = config[0].split('/')
+        if len(l) == 1:
+            self.min = self.max = int(l[0])
+        elif len(l) == 2:
+            self.min = int(l[0])
+            self.max = int(l[1])
+        else:
+            raise IBANError('Error in configuration file')
+        # Read encoding
+        if config[1] == 'n':
+            self.type = string.digits
+        elif config[1] == 'a':
+            self.type = string.ascii_letters
+        elif config[1] == 'an':
+            self.type = string.digits + string.ascii_letters
+        else:
+            raise IBANError('Error in configuration file')
+        # Read Fill chars, if available
+        if len(config) > 2:
+            self.fillchar = config[2]
+
+    def valid(self, buf):
+        """ Check if given string applies to the format """
+        # Length
+        if len(buf) < self.min or len(buf) > self.max:
+            return False
+        elif not containsOnly(buf, self.type):
+            return False
+        else:
+            return True
+
+    def _fill(self, buf, fillno):
+        """ Fill string with fill characters """
+        if self.fillchar is None:
+            # If no fillchars are defined, there's no filling
+            return buf
+        if self.fill_left:
+            return buf.rjust(fillno, self.fillchar)
+        else:
+            return buf.ljust(fillno, self.fillchar)
+
+    def fill(self, buf):
+        """ Fill string with fill characters up to maximum length """
+        return self._fill(buf, self.max)
+    
+    def minfill(self, buf):
+        """ Fill string with fill characters up to minimum length """
+        return self._fill(buf, self.min)
+
+    def strip(self, buf):
+        """Strip all fill characters from buf"""
+        if not self.fillchar:
+            # If no fillchars are defined, there's no stripping
+            return buf
+        if self.fill_left:
+            buf = buf.lstrip(self.fillchar)
+        else:
+            buf = buf.rstrip(self.fillchar)
+        return buf
+        
+
+def checksum(iban):
+    """
+    Calculate Checksum of a *wellformed* IBAN
+    Basically this is just a 'IBAN mod 97' but this is not so easy
+    as older python versions/systems cannot handle very long integers
+    """
+    lbuf = []
+
+    # first 4 chars to the end
+    buf = iban[4:] + iban[:2] + iban[2:4]
+
+    # Convert letters into digits
+    # 'A' -> 10, 'B' -> 11, ... 'Z' -> 35
+
+    for i in buf:
+        if i in string.digits:
+            lbuf.append(i)
+        else:
+            lbuf.append(str(10 + ord(i) - ord('A')))
+
+    # transform list to string
+    ibannum = long(''.join(lbuf))
+
+    return ibannum % 97
+
+def get_country_specs(country):
+    """ Read country specifics, check and return them as a dictionary """
+    
+    try:
+        country_specs = dict(cfg_countries.items(country))
+    except ConfigParser.NoSectionError:
+        # No configuration for this country
+        raise IBANError('Country %s not implemented' % country)
+    # Check bank, branche, account, check
+    for k in ['bank', 'branche', 'account', 'check1', 'check2', 'check3']:
+        try:
+            f = country_specs[k]
+        except KeyError:
+            raise IBANError(
+                'Missing parameter "%s" in configuration, '
+                'section %s' % (k, country))
+        if f:
+            try:
+                country_specs[k] = FormatSpec(f)
+            except IBANError:
+                raise IBANError(
+                    'Wrong format specifier for %s in configuration, '
+                    'section %s' % (k,country))
+        else:
+            # Empty specifier
+            country_specs[k] = None
+
+    # IBAN length
+    try:
+        f = country_specs['iban_length']
+    except KeyError:
+        raise IBANError('Missing parameter "iban_length" in configuration, '
+                        'section %s' % country)
+    country_specs['iban_length'] = int(f)
+
+    # IBAN order
+    try:
+        f = country_specs['iban_order']
+    except KeyError:
+        raise IBANError('Missing parameter "iban_order" in configuration, '
+                        'section %s' % country)
+    l = []
+    for i in f.split(','):
+        l.append(i.strip())
+    country_specs['iban_order'] = l
+
+    # Is country part of SEPA contract?
+    f = country_specs.get('sepa', False)
+    if isinstance(f, basestring):
+        if f.lower() in ['y','yes']:
+            f = True
+        else:
+            f = False
+    country_specs['sepa'] = f
+
+    return country_specs
+
+class IntAccount(object):
+    """International Bank Account. This class stores all relevant information
+    in specific attributes, e.g. account number, domestic bank number, etc.
+    The IBAN is built on the fly and is not stored in the object."""
+
+    def __init__(self, country=None, bank=None, branche=None,
+                 account=None, check1=None, check2=None, check3=None, 
+                 iban=None):
+        """ Import country specific configuration """
+        # Get country specific formatting
+        if not country and not iban:
+            raise AttributeError('Country or IBAN is mandatory')
+        if iban:
+            # If IBAN is specified, use the country from there
+            # and ignore the country attribute
+            country = iban[:2]
+        
+        self.country_specs = get_country_specs(country)
+
+        if iban:
+            # If there is an IBAN, use that for initializing
+            self.country = country
+            self.iban = iban  # iban is a property!!!
+        else:
+            self.country = country
+            self.bank = bank
+            self.branche = branche
+            self.account = account
+            self.check1 = check1
+            self.check2 = check2
+            self.check3 = check3
+
+    def __cmp__(self, other):
+        """IntAccounts are equal if country/bank/branche/
+        account/checksum are equal"""
+        if self.country != other.country:
+            return -1
+        for i in ['bank', 'branche', 'account', 'check1', 'check2', 'check3']:
+            cs = self.country_specs[i]
+            os = other.country_specs[i]
+            if cs or os:
+                if cs.fill(getattr(self,i)) != os.fill(getattr(other,i)):
+                    return -1
+        # Everything seems to be the same
+        return 0
+
+    def __repr__(self):
+        return ("IntAccount(country='%s', bank='%s', branche='%s', "
+                "account='%s', check1='%s', check2='%s', check3='%s')" % (
+                    self.country,
+                    self.bank,
+                    self.branche,
+                    self.account,
+                    self.check1,
+                    self.check2,
+                    self.check3))
+    @property
+    def is_sepa(self):
+        return self.country_specs['sepa']
+
+    def set_iban(self, iban):
+        """Set all needed attributes from IBAN"""
+        # Check length of IBAN
+        if len(iban) != self.country_specs['iban_length']:
+            raise IBANError('IBAN has an invalid length')
+        # Validate IBAN checksum
+        if checksum(iban) != 1:
+            raise IBANError('IBAN has an invalid checksum')
+        # Now try to write attributes
+        pos = 4 # Initial position from where attributes are read
+        for attrname in self.country_specs['iban_order']:
+            attr_len = self.country_specs[attrname].max
+            # Get attribute from iban
+            attrstr = iban[pos:pos+attr_len]
+            # Strip eventual fill characters
+            attrstr = self.country_specs[attrname].strip(attrstr)
+            setattr(self, attrname, attrstr)
+            pos += attr_len # set new position
+        
+    def get_iban(self):
+        """ Create IBAN out of object attributes """
+        # All attributes are valid, nothing has to be checked,
+        # just assemble the IBAN
+        iban = []
+        iban.append(self.country)
+        iban.append('00') # Empty Checksum
+        # Now append all specified attributes
+        for attrname in self.country_specs['iban_order']:
+            a = getattr(self, attrname)
+            if a == None:
+                # Ooops, attribute is None!
+                raise IBANError('Attribute %s is missing, no IBAN '
+                                'can be created' % attrname)
+            else:
+                # Fill attribute (e.g. precede account number
+                # with zeros) and append it
+                iban.append(self.country_specs[attrname].fill(a))
+        # Calculate checksum
+        c = checksum (''.join(iban))
+        # Add Checksum
+        iban[1]=str(98 - c).zfill(2)
+        return ''.join(iban)
+        
+    iban = property(get_iban, set_iban)
+
+# Modify class, so that it contains property objects
+# that validate certain attributes
+
+def valid_get(attr):
+    """ Factory for getters for constrained attributes """
+    def getx(self):
+        return getattr(self, '_'+attr, None)
+    return getx
+
+def valid_set(attr):
+    """ Factory for setters for constrained attributes which check validity """
+    def setx(self, value):
+        # Check all constraints
+        if value == None:
+            # Allow setting attribute to None
+            setattr(self, '_'+attr, value)
+        else:
+            # Fill up to minimum length
+            value = self.country_specs[attr].minfill(value)
+            if self.country_specs[attr].valid(value):
+                setattr(self, '_'+attr, value)
+            else:
+                raise IBANError('Invalid value "%s" for %s' % (value, attr))
+    return setx
+
+
+for i in ['bank', 'branche', 'account', 'check1', 'check2', 'check3']:
+    setattr(IntAccount, i, property(valid_get(i), valid_set(i)))
+
+def valid(iban):
+    """ Check validity of IBAN """
+    try:
+        i = IntAccount(iban=iban)
+    except IBANError:
+        return False
+    else:
+        return True
+
+def valid_BIC(bic):
+    """Check validity of BIC"""
+    bic = bic.strip()
+    if len(bic) != 8 and len(bic) != 11:
+        # length must be 8 or 11 characters
+        return False
+    if not bic[:6].isalpha():
+        # Characters 0-6 must be letters
+        return False
+    if not bic[6:8].isalnum():
+        # Characters 7,8 must me alphanumeric
+        return False
+    # All seems o.k. so far...
+    return True

ibanlib/iban_countries.cfg

+# Every country has a section, denoted by square brackets 
+# and the 2-digit country ISO-Code, e.g. [US]
+# 
+# The following parameters have to be specified:
+# - "bank" - the bank number (domestic bank code)
+# - "branche" - the branche code
+# - "account" - the account number
+# - "check1/2/3" - country-specific checksum 
+#    (empty, if included in account number)
+#   If any of the above items is not available in the country (e.g. no 
+#   branche code), it has to empty (e.g. "branche="). Otherwise, the format is 
+#   specified as follows:
+#   "min/max, type, fillchar"
+#   - min is minimium length, max maximum length, if min==max, only one value 
+#     has to be given
+#   - type is 'n' for numeric, 'a' for letters, 'an' for alphanumeric
+#   - fillchar denotes which character is used for filling the account number 
+#     to its maximum length. If fillchar is not specified, no filling is 
+#     performed
+# - "iban_length" - the total length of the IBAN
+# - "iban_order" - the order, in which these items appear in the IBAN, 
+#    e.g. "bank, branche, account"
+
+# ANDORRA
+[AD]
+bank = 4, n
+branche = 4, n
+account = 6/12, an, 0
+check1 =
+check2 =
+check3 =
+iban_length = 24
+iban_order = bank, branche, account
+sepa = N
+
+# AUSTRIA
+[AT]
+bank = 5, n
+branche =
+account = 4/11, n, 0
+check1 = 
+check2 = 
+check3 = 
+iban_length = 20
+iban_order = bank, account
+sepa = Y
+
+# BELGIUM
+[BE]
+bank = 3, n
+branche =
+account = 7, n
+check1 =
+check2 =
+check3 = 2, n
+iban_length = 16
+iban_order = bank, account, check3
+sepa = Y
+
+# BOSNIA AND HERZEGOVINA
+[BA]
+bank = 3, n
+branche = 3, n
+account = 8, n
+check1 =
+check2 =
+check3 = 2, n
+iban_length = 20
+iban_order = bank, branche, account, check3
+sepa = N
+
+# BULGARIA
+[BG]
+bank = 4, a
+branche = 4, n
+# FIXME - Account number consists of a1(2n) + a2(8an)
+account = 10, an
+check1 =
+check2 =
+check3 =
+iban_length = 22
+iban_order = bank, branche, account
+sepa = Y
+
+# CROATIA
+[HR]
+bank = 7, n
+branche =
+account = 10, n
+check1 =
+check2 =
+check3 =
+iban_length = 21
+iban_order = bank, account
+sepa = N
+
+# CYPRUS
+[CY]
+bank = 1/3, n, 0
+branche = 0/5, n, 0
+account = 7/16, an, 0
+check1 =
+check2 =
+check3 =
+iban_length = 28
+iban_order = bank, branche, account
+sepa = Y
+
+# CZECH REPUBLIC
+[CZ]
+bank = 4, n
+branche = 
+# FIXME Account number consists of a1(0/6, n) + a2(2/10, n)
+account = 2/16, n, 0
+check1 =
+check2 =
+check3 =
+iban_length = 24
+iban_order = bank, account
+sepa = Y
+
+# DENMARK
+[DK]
+bank = 1/4, n, 0
+branche = 
+account = 3/9, n, 0
+check1 = 
+check2 = 
+check3 = 1, n
+iban_length = 18
+iban_order = bank, account, check3
+sepa = Y
+
+# ESTONIA
+[EE]
+bank = 2, n
+branche =
+account = 1/14, n, 0
+check1 =
+check2 =
+check3 = 
+iban_length = 20
+iban_order = bank, account
+sepa = Y
+
+# FINLAND
+[FI]
+bank = 6, n
+branche =
+account = 1/7, n, 0
+check1 =
+check2 =
+check3 = 1, n
+iban_length = 18
+iban_order = bank, account, check3
+sepa = Y
+
+# FRANCE
+[FR]
+bank = 5, n
+branche = 5, n
+account = 11, an
+check1 =
+check2 =
+check3 = 2, n
+iban_length = 27
+iban_order = bank, branche, account, check3
+sepa = Y
+
+# GERMANY
+[DE]
+bank = 8, n
+branche = 
+account = 1/10, n, 0
+check1 = 
+check2 = 
+check3 = 
+iban_length = 22
+iban_order = bank, account
+sepa = Y
+
+# GIBRALTAR
+[GI]
+bank = 4, a
+branche =
+account = 15, an, 0
+check1 =
+check2 =
+check3 =
+iban_length = 23
+iban_order = bank, account
+sepa = N
+
+#GREECE
+[GR]
+bank = 0/3, n, 0
+branche = 0/4, n, 0
+account = 8/16, an, 0
+check1 =
+check2 =
+check3 =
+iban_length = 27
+iban_order = bank, branche, account
+sepa = Y
+
+#HUNGARY
+[HU]
+bank = 3, n
+branche = 4, n
+account = 7/15, n, 0
+check1 =
+check2 = 1, n 
+check3 = 1, n
+iban_length = 28
+iban_order = bank, branche, check2, account, check3
+sepa = Y
+
+# ICELAND
+[IS]
+bank = 4, n
+branche =
+account = 18, n, 0
+check1 =
+check2 =
+check3 =
+iban_length = 26
+iban_order = bank, account
+sepa = Y
+
+#IRELAND
+[IE]
+bank = 4, a
+branche = 6, n
+account = 8, n
+check1 =
+check2 =
+check3 =
+iban_length = 22
+iban_order = bank, branche, account
+sepa = Y
+
+# ITALY
+[IT]
+bank = 5, n
+branche = 5, n
+# FIXME - old Banking program has 9-character accounts!
+account = 9/12, an, 0 
+check1 = 1,a
+check2 = 
+check3 =
+iban_length = 27
+iban_order = check1, bank, branche, account
+sepa = Y
+
+# LATVIA
+[LV]
+bank = 4, a
+branche = 
+account = 13, an, 0
+check1 =
+check2 =
+check3 =
+iban_length = 21
+iban_order = bank, account
+sepa = Y
+
+# LIECHTENSTEIN
+[LI]
+bank = 3/5, n, 0
+branche =
+account = 1/12, an
+check1 =
+check2 =
+check3 =
+iban_length = 21
+iban_order = bank, account
+sepa = Y
+
+# LITHUANIA
+[LT]
+bank = 5, n
+branche =
+account = 11, n
+check1 =
+check2 =
+check3 =
+iban_length = 20
+iban_order = bank, account
+sepa = Y
+
+# LUXEMBURG
+[LU]
+bank = 3, n
+branche =
+account = 13, an
+check1 = 
+check2 =
+check3 =
+iban_length = 20
+iban_order = bank, account
+sepa = Y
+
+# MACEDONIA, FORMER YUGOSLAV REPUBLIC OF
+[MK]
+bank = 3, n, 0
+branche =
+account = 10, an, 0
+check1 =
+check2 =
+check3 = 2, n
+iban_length = 19
+iban_order = bank, account, check3
+sepa = N
+
+# MALTA
+[MT]
+bank = 4, a
+branche = 5, n
+account = 18, an
+check1 =
+check2 =
+check3 =
+iban_length = 31
+iban_order = bank, branche, account
+sepa = Y
+
+# MONTENEGRO
+[ME]
+bank = 3, n
+branche = 
+account = 13, n
+check1 =
+check2 =
+check3 = 2, n
+iban_length = 22
+iban_order = bank, account, check3
+sepa = N
+
+# THE NETHERLANDS
+[NL]
+bank = 4, a
+branche = 
+account = 9/10, n, 0
+check1 =
+check2 =
+check3 = 
+iban_length = 18
+iban_order = bank, account
+sepa = Y
+
+# NORWAY
+[NO]
+bank = 4, n
+branche = 
+account = 6, n
+check1 =
+check2 =
+check3 = 1, n
+iban_length = 15
+iban_order = bank, account, check3
+sepa = Y
+
+# POLAND
+[PL]
+bank = 8, n
+branche = 
+account = 16, n
+check1 =
+check2 =
+check3 = 
+iban_length = 28
+iban_order = bank, account
+sepa = Y
+
+# PORTUGAL
+[PT]
+bank = 4, n
+branche = 4, n
+account = 11, n
+check1 =
+check2 =
+check3 = 2, n
+iban_length = 25
+iban_order = bank, branche, account, check3
+sepa = Y
+
+# ROMANIA
+[RO]
+bank = 4, a
+branche = 
+account = 16, an
+check1 =
+check2 =
+check3 = 
+iban_length = 24
+iban_order = bank, account
+sepa = Y
+
+# SERBIA
+[CS]
+bank = 3, n
+branche = 
+account = 13, n
+check1 =
+check2 =
+check3 = 2, n
+iban_length = 22
+iban_order = bank, account, check3
+sepa = N
+
+# SLOVAK REPUBLIC
+[SK]
+bank = 4, n
+branche = 
+account = 16, n
+check1 =
+check2 =
+check3 = 
+iban_length = 24
+iban_order = bank, account
+sepa = Y
+
+# SLOVENIA
+[SI]
+bank = 5, n
+branche = 
+account = 8, n
+check1 =
+check2 =
+check3 = 2, n
+iban_length = 19
+iban_order = bank, account, check3
+sepa = Y
+
+# SPAIN
+[ES]
+bank = 4, n
+branche = 4, n
+account = 10, n, 0
+check1 =
+check2 = 1, n
+check3 = 1, n
+iban_length = 24
+iban_order = bank, branche, check2, check3, account
+sepa = Y
+
+# SWEDEN
+[SE]
+bank = 3, n
+branche = 
+account = 16, n
+check1 =
+check2 = 
+check3 = 1, n
+iban_length = 24
+iban_order = bank, account, check3
+sepa = Y
+
+# SWITZERLAND
+[CH]
+bank = 5, n
+branche = 
+account = 12, an
+check1 =
+check2 = 
+check3 = 
+iban_length = 21
+iban_order = bank, account
+sepa = N
+
+# TURKEY
+[TR]
+bank = 5, n
+branche = 
+account = 17, an
+check1 =
+check2 = 
+check3 = 
+iban_length = 26
+iban_order = bank, account
+sepa = N
+
+# UNITED KINGDOM
+[GB]
+bank = 4, a
+branche = 6, n
+account = 8, n
+check1 =
+check2 = 
+check3 = 
+iban_length = 22
+iban_order = bank, branche, account
+sepa = Y
+
+# MAURITIUS
+[MU]
+bank = 6, an
+branche = 2, n
+account = 18, an
+check1 =
+check2 = 
+check3 = 
+iban_length = 30
+iban_order = bank, account
+sepa = N
+
+# TUNISIA
+[TN]
+bank = 2, n
+branche = 3, n
+account = 13, n
+check1 =
+check2 = 
+check3 = 2, n
+iban_length = 24
+iban_order = bank, branche, account, check3
+sepa = N

ibanlib/interfaces.py

+from zope.interface import Interface
+from zope.schema import TextLine
+
+class IIntAccount(Interface):
+    """ Schema for International Account IntAccount class """
+    country = TextLine(
+        title=u"Country", 
+        description=u"Two-letter country ISO-code",
+        min_length=2,
+        max_length=2,
+        required=True)
+
+    bank = TextLine(
+        title=u"Bank", 
+        description=u"Bank code")
+
+    branche = TextLine(
+        title=u"Branche",
+        description=u"Branche code")
+
+    account = TextLine(
+        title = u"Account number",
+        description = u"Account number")
+
+    checksum = TextLine(
+        title = u"Checksum",
+        description = u"Checksum of account number")
+
+    iban = TextLine(
+        title = u"IBAN",
+        description = u"International Bank Account Number")
+    
+#!/usr/bin/env python
+
+import unittest
+from doctest import DocFileSuite, REPORT_ONLY_FIRST_FAILURE
+
+def test_suite():
+    suite = DocFileSuite('README.txt',
+                         package='ibanlib',
+                         optionflags = REPORT_ONLY_FIRST_FAILURE)
+    return suite
+    
+if __name__ == '__main__':
+    suite = test_suite()
+    unittest.TextTestRunner().run(suite)
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
+from setuptools import setup, find_packages
+
+setup(
+    name = "ibanlib",
+    version = "0.0.2",
+    description="Library that supports developing applications that integrate the International Bank Account Number (IBAN).",
+    author = "Hermann Himmelbauer",
+    author_email = "dusty@qwer.tk",
+    license = 'LGPL',
+    packages = ['ibanlib'],
+    test_suite = "ibanlib.test.test_suite",
+    package_data = {
+        # If any package contains *.txt or *.rst files, include them:
+        '': ['*.txt'],
+    }
+)
+