twinchain API documentation
Description: <pre> …

# Module `twinchain`

    Block chain layer on top of twincore.

       |   Time Now    |   Time  Now    |  Time Now     |
       |   hash256   | |    hash256   | |   hash256   | |
       |   Header    | |    Header    | |   Header    | |
       |   Payload   | |    Payload   | |   Payload   | |
       |   Backlink  | |    Backlink  | |   Backlink  | |
                     |----->---|      |---->---|     |------

       The sum of fields saved to the next backlink.

       History:

       1.1         Tue 20.Feb.2024     Initial release
       1.2.0       Mon 26.Feb.2024     Moved pip home to pydbase/
       1.4.0       Tue 27.Feb.2024     Addedd pgdebug
       1.4.2       Wed 28.Feb.2024     Fixed multiple instances
       1.4.3       Wed 28.Feb.2024     ChainAdm added
       1.4.4       Fri 01.Mar.2024     Tests for chain functions
       1.4.5       Fri 01.Mar.2024     Misc fixes
       1.4.6       Mon 04.Mar.2024     Vacuum count on vacuumed records
       1.4.7       Tue 05.Mar.2024     In place record update
       1.4.8       Sat 09.Mar.2024     Added new locking mechanism
       1.4.9       Mon 01.Apr.2024     Updated to run on MSYS2, new locking
       1.5.0       Tue 02.Apr.2024     Cleaned, pip upload
       1.5.1       Wed 10.Apr.2024     Dangling lock .. fixed

Expand source code

    #!/usr/bin/env python3

    '''
        <pre>

     Block chain layer on top of twincore.

        |   Time Now    |   Time  Now    |  Time Now     |
        |   hash256   | |    hash256   | |   hash256   | |
        |   Header    | |    Header    | |   Header    | |
        |   Payload   | |    Payload   | |   Payload   | |
        |   Backlink  | |    Backlink  | |   Backlink  | |
                      |----->---|      |---->---|     |------

        The sum of fields saved to the next backlink.

        History:

        1.1         Tue 20.Feb.2024     Initial release
        1.2.0       Mon 26.Feb.2024     Moved pip home to pydbase/
        1.4.0       Tue 27.Feb.2024     Addedd pgdebug
        1.4.2       Wed 28.Feb.2024     Fixed multiple instances
        1.4.3       Wed 28.Feb.2024     ChainAdm added
        1.4.4       Fri 01.Mar.2024     Tests for chain functions
        1.4.5       Fri 01.Mar.2024     Misc fixes
        1.4.6       Mon 04.Mar.2024     Vacuum count on vacuumed records
        1.4.7       Tue 05.Mar.2024     In place record update
        1.4.8       Sat 09.Mar.2024     Added new locking mechanism
        1.4.9       Mon 01.Apr.2024     Updated to run on MSYS2, new locking
        1.5.0       Tue 02.Apr.2024     Cleaned, pip upload
        1.5.1       Wed 10.Apr.2024     Dangling lock .. fixed

        </pre>
    '''

    import  os, sys, getopt, signal, select, socket, time, struct
    import  random, stat, os.path, datetime, threading, uuid
    import  struct, io, hashlib

    import pyvpacker

    base = os.path.dirname(os.path.realpath(__file__))
    sys.path.append(os.path.join(base, '..', 'pydbase'))

    from dbutils import *
    from twincore import *

    version         = "1.4.3"
    ProtocolVersion = "1.0"

    # Exceptions for bad records

    class DBCheckError(Exception):

        def __init__(self, message):
             self.message = message

    class DBLinkError(Exception):

        def __init__(self, message):
             self.message = message

    # ------------------------------------------------------------------------

    class TwinChain(TwinCore):

        '''
            Derive from database to accomodate block chain.
        '''

        def __init__(self, fname = "pydbchain.pydb", pgdebug = 0, verbose = 0):

            self.chain_verbose = verbose

            # Upper lock name
            ulockname = os.path.splitext(fname)[0] + ".ulock"
            self.ulock = FileLock(ulockname)
            self.ulock.waitlock()    #(self.ulockname)

            super(TwinChain, self).__init__(fname, pgdebug)

            self.packer = pyvpacker.packbin()

            sss = self.getdbsize()
            '''if sss == 0:
                # Produce initial data structure
                # This is the wron place to produce it, as the chain has no
                # knowledge of the actual structure ... we provide minimum here
                header = str(uuid.uuid1())
                dt = datetime.datetime.utcnow()
                fdt = dt.strftime('%a, %d %b %Y %H:%M:%S`')

                payload = {"Initial": "Initial record, do not use.",
                                "Default" : 'None', "header": header}

                #payload2 = {"header": header, "payload" : payload}

                # Here we fake the initial backlink for the anchor record
                old_dicx = {}
                old_dicx["now"] =  fdt
                old_dicx["hash256"]  =  self._hash_any(payload).hexdigest()
                old_dicx["header"]   =  header
                old_dicx["payload"]  =  payload
                old_dicx["backlink"] =  ""

                #print("old_dicx:", self.old_dicx)

                aaa = self._fill_record(header, payload, old_dicx)
                #print("aaa:", aaa)
                encoded = self.packer.encode_data("", [aaa])
                #print("encoded:", encoded)

                self.save_data(header, encoded)
            '''

            self.ulock.unlock() #self.ulockname)

        def _hashtohex(self, varx):

            if type(varx) == type(""):
                varx = varx.encode()

            hh = hashlib.new("sha256");
            hh.update(varx)
            return hh.hexdigest()

        def _hash_any(self, any):

            hh = hashlib.new("sha256");
            if type(any) == type(""):
                hh.update(any)
            if type(any) == type({}):
                sss = self._expand_dict(any)
                hh.update(sss.encode())
            return hh

        def _expand_dict(self, dicx):

            sstr = ""
            if type(dicx) == type({}):
                for aa in sorted(dicx.keys()):
                    sstr += aa + ":" + dicx[aa]
            if type(dicx) == type([]):
                for aa in dicx:
                    sstr += aa
            if type(dicx) == type(""):
                sstr = dicx
            if type(dicx) == type(b""):
                sstr = dicx

            #print("sstr", sstr)
            return sstr

        def _build_sumstr(self, aaa):

            sumstr  = aaa.get('now', "")
            sumstr += aaa.get('header', "")
            sumstr += self._expand_dict(aaa.get('payload', ""))
            #print("raw sumstr", sumstr)
            return sumstr

        def _build_backlink(self, dicold):

            backlink  =  dicold.get("now", "")
            backlink +=  dicold.get("hash256", "")
            backlink += dicold.get("header", "")
            backlink += self._expand_dict(dicold.get("payload", ""))
            backlink += dicold.get("backlink", "")
            #print("raw backlink", backlink)
            return backlink

        # --------------------------------------------------------------------

        def _fill_record(self, header, payload, old_dict):

            aaa = {}
            aaa["header"] = header
            aaa["payload"] = payload
            aaa["protocol"] = ProtocolVersion

            dt = datetime.datetime.now()
            fdt = dt.strftime('%a, %d %b %Y %H:%M:%S')
            aaa["now"] = fdt
            #self._key_n_data(aaa, "hash32", str(self.hash32(payload)))

            sumstr = self._build_sumstr(aaa)
            aaa["hash256"] = self._hashtohex(sumstr)

            backlink = self._build_backlink(old_dict)
            aaa["backlink"] = self._hashtohex(backlink)

            return aaa

        def get_payload(self, recnum):

            ''' Return the payload on record number '''

            arr = self.get_rec(recnum)
            if not arr:
                return []
            try:
                decoded = self.packer.decode_data(arr[1])
                #print("decoded", decoded)
            except:
                print("Cannot decode record at", recnum, recnum, sys.exc_info())
                raise
            dic = self._get_fields(decoded[0])

            if self.chain_verbose > 2:
                print(dic)
            if self.chain_verbose > 0:
                print(dic['header'] + " " + dic['now'], dic['payload'])

            return arr[0].decode(), dic['payload']

        def get_payoffs_bykey(self, keyval, maxrec = 1):
            " get payload offset by key"
            arr = []
            rrr = self.getdbsize()
            for aa in range(rrr -1, -1, -1):
                head = self.get_header(aa)
                if head == keyval:
                    arr.append(aa)
                    if len(arr) >= maxrec:
                        break
            return arr

        def get_data_bykey(self, keyval, maxrec = 1, check = True):

            ''' Get data by key value. Searches the database from the back so lastly
                entered data is presented.

                Input:

                    keyval    :    value to search for
                    maxrec    :    maximum number of records
                    check     :    check as found. Raises exception

                Returns:

                    data array matching criteria.

            '''
            arr = []
            rrr = self.getdbsize()
            for aa in range(rrr -1, -1, -1):
                head = self.get_header(aa)
                if head == keyval:
                    ch = self.checkdata(aa)
                    li = self.linkintegrity(aa)

                    if self.chain_verbose:
                        print("li", li, "ch", ch)

                    if not ch:
                        raise  DBCheckError("Check failed at rec %a" % aa);
                    if not li:
                        raise  DBLinkError("Link checl failed at rec %a" % aa);

                    pay = self.get_payload(aa)
                    arr.append((head, pay))
                    if len(arr) >= maxrec:
                        break
            return arr

        def get_header(self, recnum):

            ''' Get the header of record. '''

            if self.pgdebug > 5:
                print("get_header()", recnum)
            arr = self.get_rec(recnum)
            if not arr:
                if self.pgdebug > 5:
                    print("get_header(): empty/deleted record", recnum)

                if self.chain_verbose > 1:
                    print("get_header(): empty/deleted record", recnum)
                return []

            if self.chain_verbose > 1:
                print("arr", arr)
                uuu = uuid.UUID(arr[0].decode())
                ddd = str(uuid2date(uuu))
                print("header", arr[0])
            return arr[0].decode()

        def linkintegrity(self, recnum):

            ''' Scan one record an its integrity based on the previous one '''

            if recnum < 1:
                if self.chain_verbose:
                    print("Cannot check initial record.")
                return True

            if self.chain_verbose > 4:
                print("Checking link ...", recnum)

            arr = self.get_rec(recnum-1)
            try:
                decoded = self.packer.decode_data(arr[1])
            except:
                print("Cannot decode prev:", recnum, sys.exc_info())
                return
            dicold = self._get_fields(decoded[0])
            arr2 = self.get_rec(recnum)
            try:
                decoded2 = self.packer.decode_data(arr2[1])
            except:
                if self.chain_verbose > 2:
                    print("Cannot decode curr:", recnum, sys.exc_info())
                return
            dic = self._get_fields(decoded2[0])
            backlink = self._build_backlink(dicold)
            hhh = self._hashtohex(backlink)
            if self.chain_verbose > 2:
                print("calc      ", hhh)
                print("backlink  ", dic['backlink'])
            return hhh == dic['backlink']

        def checkdata(self, recnum):

            ''' Integrity check of record. '''

            arr = self.get_rec(recnum)
            try:
                decoded = self.packer.decode_data(arr[1])
            except:
                if self.chain_verbose > 2:
                    print("Cannot decode:", recnum, sys.exc_info())
                return

            aaa = self._get_fields(decoded[0])
            sumstr = self._build_sumstr(aaa)
            hhh = self._hashtohex(sumstr)
            if self.chain_verbose > 1:
                print("data", hhh)
            if self.chain_verbose > 2:
                print("hash", aaa['hash256'])
            return hhh == aaa['hash256']

        def appendwith(self, header, datax):

            ''' Append data and header to the end of database '''

            #if type(header) != type(b""):
            #    header = header.encode() #errors='strict')

            #if type(datax) != type(b""):
            #    datax = datax.encode() #errors='strict')

            if self.chain_verbose > 0:
                print("Appendwith", header, datax)

            self.ulock.waitlock()    #self.ulockname)

            try:
                uuu = uuid.UUID(header)
            except:
                if self.chain_verbose:
                    print("Header override must be a valid UUID string.")

                self.ulock.unlock() #self.ulockname)
                raise ValueError("Header override must be a valid UUID string.")

            old_dicx = {}
            # Get last data from db
            sss = self.getdbsize()
            #print("sss", sss)

            # We fake an empty set ... checking against an empty set is a valid OP
            if not sss:
                #raise ValueError("Invalid database, must have at least one record.")
                ddd = self.packer.encode_data("", {"blank": 0, })
                ooo = [0, ddd,]
            else:
                ooo = self.get_rec(sss-1)

            if self.pgdebug > 5:
                print("decoding", ooo)

            decoded = self.packer.decode_data(ooo[1])
            old_dicx = self._get_fields(decoded[0])

            #print("old_fff", old_dicx["hash256"])
            #print("old_time", old_dicx["now"])

            aaa = self._fill_record(header, datax, old_dicx)
            encoded = self.packer.encode_data("", aaa)

            #print("save", header, "-", encoded)
            #print("bbb", self.getdbsize())
            self.save_data(header, encoded)
            #print("eee", self.getdbsize())

            if self.chain_verbose > 1:
                bbb = self.packer.decode_data(encoded)
                print("Rec", bbb[0])

            self.ulock.unlock() #self.ulockname)
            return True

        def append(self, datax):

            ''' Append data to the end of database '''

            if self.chain_verbose > 0:
                print("Append", datax)

            # Get last data from db
            #sss = self.getdbsize()
            #if not sss:
            #    #raise ValueError("Invalid database, must have at least one record.")
            #    pass

            # Produce header  structure
            uuu = uuid.uuid1()
            #print("uuid date",uuid2date(uuu))
            header = str(uuu)
            uuuu = uuid.UUID(header)
            #print("uuid date2", header, uuid2date(uuuu))
            ret = self.appendwith(header, datax)
            return ret

        def dump_rec(self, bbb):

            ''' Dump one record to console '''

            for aa in range(len(bbb)//2):
                print(pad(bbb[2*aa]), "=", bbb[2*aa+1])

        def _get_fields(self, bbb):
            if type(bbb) == type({}):
                dicx = bbb
            else:
                dicx = {}
                for aa in range(len(bbb)//2):
                    dicx[bbb[2*aa]] = bbb[2*aa+1]
            return dicx

    # EOF

## Classes

`class DBCheckError (message)`

  Common base class for all non-exit exceptions.

  Expand source code

      class DBCheckError(Exception):

          def __init__(self, message):
               self.message = message

  ### Ancestors

  - builtins.Exception
  - builtins.BaseException
`class DBLinkError (message)`

  Common base class for all non-exit exceptions.

  Expand source code

      class DBLinkError(Exception):

          def __init__(self, message):
               self.message = message

  ### Ancestors

  - builtins.Exception
  - builtins.BaseException
`class TwinChain (fname='pydbchain.pydb', pgdebug=0, verbose=0)`

  Derive from database to accomodate block chain.

  Expand source code

      class TwinChain(TwinCore):

          '''
              Derive from database to accomodate block chain.
          '''

          def __init__(self, fname = "pydbchain.pydb", pgdebug = 0, verbose = 0):

              self.chain_verbose = verbose

              # Upper lock name
              ulockname = os.path.splitext(fname)[0] + ".ulock"
              self.ulock = FileLock(ulockname)
              self.ulock.waitlock()    #(self.ulockname)

              super(TwinChain, self).__init__(fname, pgdebug)

              self.packer = pyvpacker.packbin()

              sss = self.getdbsize()
              '''if sss == 0:
                  # Produce initial data structure
                  # This is the wron place to produce it, as the chain has no
                  # knowledge of the actual structure ... we provide minimum here
                  header = str(uuid.uuid1())
                  dt = datetime.datetime.utcnow()
                  fdt = dt.strftime('%a, %d %b %Y %H:%M:%S`')

                  payload = {"Initial": "Initial record, do not use.",
                                  "Default" : 'None', "header": header}

                  #payload2 = {"header": header, "payload" : payload}

                  # Here we fake the initial backlink for the anchor record
                  old_dicx = {}
                  old_dicx["now"] =  fdt
                  old_dicx["hash256"]  =  self._hash_any(payload).hexdigest()
                  old_dicx["header"]   =  header
                  old_dicx["payload"]  =  payload
                  old_dicx["backlink"] =  ""

                  #print("old_dicx:", self.old_dicx)

                  aaa = self._fill_record(header, payload, old_dicx)
                  #print("aaa:", aaa)
                  encoded = self.packer.encode_data("", [aaa])
                  #print("encoded:", encoded)

                  self.save_data(header, encoded)
              '''

              self.ulock.unlock() #self.ulockname)

          def _hashtohex(self, varx):

              if type(varx) == type(""):
                  varx = varx.encode()

              hh = hashlib.new("sha256");
              hh.update(varx)
              return hh.hexdigest()

          def _hash_any(self, any):

              hh = hashlib.new("sha256");
              if type(any) == type(""):
                  hh.update(any)
              if type(any) == type({}):
                  sss = self._expand_dict(any)
                  hh.update(sss.encode())
              return hh

          def _expand_dict(self, dicx):

              sstr = ""
              if type(dicx) == type({}):
                  for aa in sorted(dicx.keys()):
                      sstr += aa + ":" + dicx[aa]
              if type(dicx) == type([]):
                  for aa in dicx:
                      sstr += aa
              if type(dicx) == type(""):
                  sstr = dicx
              if type(dicx) == type(b""):
                  sstr = dicx

              #print("sstr", sstr)
              return sstr

          def _build_sumstr(self, aaa):

              sumstr  = aaa.get('now', "")
              sumstr += aaa.get('header', "")
              sumstr += self._expand_dict(aaa.get('payload', ""))
              #print("raw sumstr", sumstr)
              return sumstr

          def _build_backlink(self, dicold):

              backlink  =  dicold.get("now", "")
              backlink +=  dicold.get("hash256", "")
              backlink += dicold.get("header", "")
              backlink += self._expand_dict(dicold.get("payload", ""))
              backlink += dicold.get("backlink", "")
              #print("raw backlink", backlink)
              return backlink

          # --------------------------------------------------------------------

          def _fill_record(self, header, payload, old_dict):

              aaa = {}
              aaa["header"] = header
              aaa["payload"] = payload
              aaa["protocol"] = ProtocolVersion

              dt = datetime.datetime.now()
              fdt = dt.strftime('%a, %d %b %Y %H:%M:%S')
              aaa["now"] = fdt
              #self._key_n_data(aaa, "hash32", str(self.hash32(payload)))

              sumstr = self._build_sumstr(aaa)
              aaa["hash256"] = self._hashtohex(sumstr)

              backlink = self._build_backlink(old_dict)
              aaa["backlink"] = self._hashtohex(backlink)

              return aaa

          def get_payload(self, recnum):

              ''' Return the payload on record number '''

              arr = self.get_rec(recnum)
              if not arr:
                  return []
              try:
                  decoded = self.packer.decode_data(arr[1])
                  #print("decoded", decoded)
              except:
                  print("Cannot decode record at", recnum, recnum, sys.exc_info())
                  raise
              dic = self._get_fields(decoded[0])

              if self.chain_verbose > 2:
                  print(dic)
              if self.chain_verbose > 0:
                  print(dic['header'] + " " + dic['now'], dic['payload'])

              return arr[0].decode(), dic['payload']

          def get_payoffs_bykey(self, keyval, maxrec = 1):
              " get payload offset by key"
              arr = []
              rrr = self.getdbsize()
              for aa in range(rrr -1, -1, -1):
                  head = self.get_header(aa)
                  if head == keyval:
                      arr.append(aa)
                      if len(arr) >= maxrec:
                          break
              return arr

          def get_data_bykey(self, keyval, maxrec = 1, check = True):

              ''' Get data by key value. Searches the database from the back so lastly
                  entered data is presented.

                  Input:

                      keyval    :    value to search for
                      maxrec    :    maximum number of records
                      check     :    check as found. Raises exception

                  Returns:

                      data array matching criteria.

              '''
              arr = []
              rrr = self.getdbsize()
              for aa in range(rrr -1, -1, -1):
                  head = self.get_header(aa)
                  if head == keyval:
                      ch = self.checkdata(aa)
                      li = self.linkintegrity(aa)

                      if self.chain_verbose:
                          print("li", li, "ch", ch)

                      if not ch:
                          raise  DBCheckError("Check failed at rec %a" % aa);
                      if not li:
                          raise  DBLinkError("Link checl failed at rec %a" % aa);

                      pay = self.get_payload(aa)
                      arr.append((head, pay))
                      if len(arr) >= maxrec:
                          break
              return arr

          def get_header(self, recnum):

              ''' Get the header of record. '''

              if self.pgdebug > 5:
                  print("get_header()", recnum)
              arr = self.get_rec(recnum)
              if not arr:
                  if self.pgdebug > 5:
                      print("get_header(): empty/deleted record", recnum)

                  if self.chain_verbose > 1:
                      print("get_header(): empty/deleted record", recnum)
                  return []

              if self.chain_verbose > 1:
                  print("arr", arr)
                  uuu = uuid.UUID(arr[0].decode())
                  ddd = str(uuid2date(uuu))
                  print("header", arr[0])
              return arr[0].decode()

          def linkintegrity(self, recnum):

              ''' Scan one record an its integrity based on the previous one '''

              if recnum < 1:
                  if self.chain_verbose:
                      print("Cannot check initial record.")
                  return True

              if self.chain_verbose > 4:
                  print("Checking link ...", recnum)

              arr = self.get_rec(recnum-1)
              try:
                  decoded = self.packer.decode_data(arr[1])
              except:
                  print("Cannot decode prev:", recnum, sys.exc_info())
                  return
              dicold = self._get_fields(decoded[0])
              arr2 = self.get_rec(recnum)
              try:
                  decoded2 = self.packer.decode_data(arr2[1])
              except:
                  if self.chain_verbose > 2:
                      print("Cannot decode curr:", recnum, sys.exc_info())
                  return
              dic = self._get_fields(decoded2[0])
              backlink = self._build_backlink(dicold)
              hhh = self._hashtohex(backlink)
              if self.chain_verbose > 2:
                  print("calc      ", hhh)
                  print("backlink  ", dic['backlink'])
              return hhh == dic['backlink']

          def checkdata(self, recnum):

              ''' Integrity check of record. '''

              arr = self.get_rec(recnum)
              try:
                  decoded = self.packer.decode_data(arr[1])
              except:
                  if self.chain_verbose > 2:
                      print("Cannot decode:", recnum, sys.exc_info())
                  return

              aaa = self._get_fields(decoded[0])
              sumstr = self._build_sumstr(aaa)
              hhh = self._hashtohex(sumstr)
              if self.chain_verbose > 1:
                  print("data", hhh)
              if self.chain_verbose > 2:
                  print("hash", aaa['hash256'])
              return hhh == aaa['hash256']

          def appendwith(self, header, datax):

              ''' Append data and header to the end of database '''

              #if type(header) != type(b""):
              #    header = header.encode() #errors='strict')

              #if type(datax) != type(b""):
              #    datax = datax.encode() #errors='strict')

              if self.chain_verbose > 0:
                  print("Appendwith", header, datax)

              self.ulock.waitlock()    #self.ulockname)

              try:
                  uuu = uuid.UUID(header)
              except:
                  if self.chain_verbose:
                      print("Header override must be a valid UUID string.")

                  self.ulock.unlock() #self.ulockname)
                  raise ValueError("Header override must be a valid UUID string.")

              old_dicx = {}
              # Get last data from db
              sss = self.getdbsize()
              #print("sss", sss)

              # We fake an empty set ... checking against an empty set is a valid OP
              if not sss:
                  #raise ValueError("Invalid database, must have at least one record.")
                  ddd = self.packer.encode_data("", {"blank": 0, })
                  ooo = [0, ddd,]
              else:
                  ooo = self.get_rec(sss-1)

              if self.pgdebug > 5:
                  print("decoding", ooo)

              decoded = self.packer.decode_data(ooo[1])
              old_dicx = self._get_fields(decoded[0])

              #print("old_fff", old_dicx["hash256"])
              #print("old_time", old_dicx["now"])

              aaa = self._fill_record(header, datax, old_dicx)
              encoded = self.packer.encode_data("", aaa)

              #print("save", header, "-", encoded)
              #print("bbb", self.getdbsize())
              self.save_data(header, encoded)
              #print("eee", self.getdbsize())

              if self.chain_verbose > 1:
                  bbb = self.packer.decode_data(encoded)
                  print("Rec", bbb[0])

              self.ulock.unlock() #self.ulockname)
              return True

          def append(self, datax):

              ''' Append data to the end of database '''

              if self.chain_verbose > 0:
                  print("Append", datax)

              # Get last data from db
              #sss = self.getdbsize()
              #if not sss:
              #    #raise ValueError("Invalid database, must have at least one record.")
              #    pass

              # Produce header  structure
              uuu = uuid.uuid1()
              #print("uuid date",uuid2date(uuu))
              header = str(uuu)
              uuuu = uuid.UUID(header)
              #print("uuid date2", header, uuid2date(uuuu))
              ret = self.appendwith(header, datax)
              return ret

          def dump_rec(self, bbb):

              ''' Dump one record to console '''

              for aa in range(len(bbb)//2):
                  print(pad(bbb[2*aa]), "=", bbb[2*aa+1])

          def _get_fields(self, bbb):
              if type(bbb) == type({}):
                  dicx = bbb
              else:
                  dicx = {}
                  for aa in range(len(bbb)//2):
                      dicx[bbb[2*aa]] = bbb[2*aa+1]
              return dicx

  ### Ancestors

  - twincore.TwinCore
  - twinbase.TwinCoreBase

  ### Methods

  `def append(self, datax)`

    Append data to the end of database

    Expand source code

        def append(self, datax):

            ''' Append data to the end of database '''

            if self.chain_verbose > 0:
                print("Append", datax)

            # Get last data from db
            #sss = self.getdbsize()
            #if not sss:
            #    #raise ValueError("Invalid database, must have at least one record.")
            #    pass

            # Produce header  structure
            uuu = uuid.uuid1()
            #print("uuid date",uuid2date(uuu))
            header = str(uuu)
            uuuu = uuid.UUID(header)
            #print("uuid date2", header, uuid2date(uuuu))
            ret = self.appendwith(header, datax)
            return ret

  `def appendwith(self, header, datax)`

    Append data and header to the end of database

    Expand source code

        def appendwith(self, header, datax):

            ''' Append data and header to the end of database '''

            #if type(header) != type(b""):
            #    header = header.encode() #errors='strict')

            #if type(datax) != type(b""):
            #    datax = datax.encode() #errors='strict')

            if self.chain_verbose > 0:
                print("Appendwith", header, datax)

            self.ulock.waitlock()    #self.ulockname)

            try:
                uuu = uuid.UUID(header)
            except:
                if self.chain_verbose:
                    print("Header override must be a valid UUID string.")

                self.ulock.unlock() #self.ulockname)
                raise ValueError("Header override must be a valid UUID string.")

            old_dicx = {}
            # Get last data from db
            sss = self.getdbsize()
            #print("sss", sss)

            # We fake an empty set ... checking against an empty set is a valid OP
            if not sss:
                #raise ValueError("Invalid database, must have at least one record.")
                ddd = self.packer.encode_data("", {"blank": 0, })
                ooo = [0, ddd,]
            else:
                ooo = self.get_rec(sss-1)

            if self.pgdebug > 5:
                print("decoding", ooo)

            decoded = self.packer.decode_data(ooo[1])
            old_dicx = self._get_fields(decoded[0])

            #print("old_fff", old_dicx["hash256"])
            #print("old_time", old_dicx["now"])

            aaa = self._fill_record(header, datax, old_dicx)
            encoded = self.packer.encode_data("", aaa)

            #print("save", header, "-", encoded)
            #print("bbb", self.getdbsize())
            self.save_data(header, encoded)
            #print("eee", self.getdbsize())

            if self.chain_verbose > 1:
                bbb = self.packer.decode_data(encoded)
                print("Rec", bbb[0])

            self.ulock.unlock() #self.ulockname)
            return True

  `def checkdata(self, recnum)`

    Integrity check of record.

    Expand source code

        def checkdata(self, recnum):

            ''' Integrity check of record. '''

            arr = self.get_rec(recnum)
            try:
                decoded = self.packer.decode_data(arr[1])
            except:
                if self.chain_verbose > 2:
                    print("Cannot decode:", recnum, sys.exc_info())
                return

            aaa = self._get_fields(decoded[0])
            sumstr = self._build_sumstr(aaa)
            hhh = self._hashtohex(sumstr)
            if self.chain_verbose > 1:
                print("data", hhh)
            if self.chain_verbose > 2:
                print("hash", aaa['hash256'])
            return hhh == aaa['hash256']

  `def dump_rec(self, bbb)`

    Dump one record to console

    Expand source code

        def dump_rec(self, bbb):

            ''' Dump one record to console '''

            for aa in range(len(bbb)//2):
                print(pad(bbb[2*aa]), "=", bbb[2*aa+1])

  `def get_data_bykey(self, keyval, maxrec=1, check=True)`

    Get data by key value. Searches the database from the back so lastly entered data is presented.

    ## Input

    keyval : value to search for maxrec : maximum number of records check : check as found. Raises exception

    ## Returns

    data array matching criteria.

    Expand source code

        def get_data_bykey(self, keyval, maxrec = 1, check = True):

            ''' Get data by key value. Searches the database from the back so lastly
                entered data is presented.

                Input:

                    keyval    :    value to search for
                    maxrec    :    maximum number of records
                    check     :    check as found. Raises exception

                Returns:

                    data array matching criteria.

            '''
            arr = []
            rrr = self.getdbsize()
            for aa in range(rrr -1, -1, -1):
                head = self.get_header(aa)
                if head == keyval:
                    ch = self.checkdata(aa)
                    li = self.linkintegrity(aa)

                    if self.chain_verbose:
                        print("li", li, "ch", ch)

                    if not ch:
                        raise  DBCheckError("Check failed at rec %a" % aa);
                    if not li:
                        raise  DBLinkError("Link checl failed at rec %a" % aa);

                    pay = self.get_payload(aa)
                    arr.append((head, pay))
                    if len(arr) >= maxrec:
                        break
            return arr

  `def get_header(self, recnum)`

    Get the header of record.

    Expand source code

        def get_header(self, recnum):

            ''' Get the header of record. '''

            if self.pgdebug > 5:
                print("get_header()", recnum)
            arr = self.get_rec(recnum)
            if not arr:
                if self.pgdebug > 5:
                    print("get_header(): empty/deleted record", recnum)

                if self.chain_verbose > 1:
                    print("get_header(): empty/deleted record", recnum)
                return []

            if self.chain_verbose > 1:
                print("arr", arr)
                uuu = uuid.UUID(arr[0].decode())
                ddd = str(uuid2date(uuu))
                print("header", arr[0])
            return arr[0].decode()

  `def get_payload(self, recnum)`

    Return the payload on record number

    Expand source code

        def get_payload(self, recnum):

            ''' Return the payload on record number '''

            arr = self.get_rec(recnum)
            if not arr:
                return []
            try:
                decoded = self.packer.decode_data(arr[1])
                #print("decoded", decoded)
            except:
                print("Cannot decode record at", recnum, recnum, sys.exc_info())
                raise
            dic = self._get_fields(decoded[0])

            if self.chain_verbose > 2:
                print(dic)
            if self.chain_verbose > 0:
                print(dic['header'] + " " + dic['now'], dic['payload'])

            return arr[0].decode(), dic['payload']

  `def get_payoffs_bykey(self, keyval, maxrec=1)`

    get payload offset by key

    Expand source code

        def get_payoffs_bykey(self, keyval, maxrec = 1):
            " get payload offset by key"
            arr = []
            rrr = self.getdbsize()
            for aa in range(rrr -1, -1, -1):
                head = self.get_header(aa)
                if head == keyval:
                    arr.append(aa)
                    if len(arr) >= maxrec:
                        break
            return arr

  `def linkintegrity(self, recnum)`

    Scan one record an its integrity based on the previous one

    Expand source code

        def linkintegrity(self, recnum):

            ''' Scan one record an its integrity based on the previous one '''

            if recnum < 1:
                if self.chain_verbose:
                    print("Cannot check initial record.")
                return True

            if self.chain_verbose > 4:
                print("Checking link ...", recnum)

            arr = self.get_rec(recnum-1)
            try:
                decoded = self.packer.decode_data(arr[1])
            except:
                print("Cannot decode prev:", recnum, sys.exc_info())
                return
            dicold = self._get_fields(decoded[0])
            arr2 = self.get_rec(recnum)
            try:
                decoded2 = self.packer.decode_data(arr2[1])
            except:
                if self.chain_verbose > 2:
                    print("Cannot decode curr:", recnum, sys.exc_info())
                return
            dic = self._get_fields(decoded2[0])
            backlink = self._build_backlink(dicold)
            hhh = self._hashtohex(backlink)
            if self.chain_verbose > 2:
                print("calc      ", hhh)
                print("backlink  ", dic['backlink'])
            return hhh == dic['backlink']