Guardando datos usando formatos de archivo a la medida, en Python 3

En Venezuela una de las pocas formas de saber la paridad entre el dolar «paralelo» y el Bolivar fuerte es utilizando el portal «DolarToday». El sitio web (https://dolartoday.com/historico-dolar/) ofrece datos que van desde el 2010 hasta el presente, en los cuales puede ver la paridad entre las dos monedas.

En un arranque de ociosidad, decidí bajarme el archivo de Excel con las tasas de conversión, lo exporte a CSV y de allí escribí un programa en Python 3 que hace lo siguiente:

  • Extendiende la clase ‘dict’ en Python para mantener un diccionario con claves ordenadas, el cual se pueda guardar y recuperar a si mismo desde el disco duro
  • Usar struct.Struct para guardar y leer data binaria (también muestro como leer un archivo comprimido con gzip).

 

El código a continuación:

#!/usr/bin/env python3
# Simple program to save the 'Dolartoday' extra official dollar rates from CSV to a custom format
# @author Jose Vicente Nunez, josevnz@kodegeek.com
import os, sys, datetime, re, gzip, struct
from optparse import OptionParser, OptionValueError
from datetime import datetime

class DollarToday:
    
    def __init__(self, ddate, value):
        if isinstance(ddate, datetime):
            self.__date = ddate
        else:
            self.__date = datetime.strptime(ddate, "%m-%d-%Y")
        self.__value = float(value)
        assert self.__value > 0.0, "¡{} para la fecha {} es invalida!".format(value, self.__date)
        
    @property
    def date(self):
        return self.__date
    
    @date.setter
    def date(self, date):
        assert isinstance(date, datetime), "Invalid date {}".format(date)
        self.__date = date
    
    @property
    def value(self):
        return self.__value
    
    def __str__(self):
        return "DollarToday[date={}, value={}]".format(self.__date, self.__value)
    
    def __hash__(self):
        return str(id(self))
            
class DollarCollection(dict):
    
    __FILE_MAGIC = b"DLR\x00" # Me invente este numero mágico...
    __FILE_VERSION = b"\x00\x01"
    __dollarStruct = struct.Struct(" self.__FILE_VERSION:
                raise "Unsupported file version: {}, expected {}".format(version, self.__FILE_VERSION)
            self.clear()
            while True:
                data = fh.read(self.__dollarStruct.size)
                if len(data) == 0:
                    break
                numbers = self.__dollarStruct.unpack(data)
                #if verbose:
                #    print("{}".format(",".join([str(x) for x in numbers])))
                dolar = DollarToday(
                                    datetime.fromordinal(numbers[0]),
                                    numbers[1]
                                    )
                self[dolar.date] = dolar
        
        except (Exception) as err:
            raise
        finally:
            if (fh is not None):
                fh.close()
    def readCsv(self, file):
        fh = None
        tempMap = {}
        try:
            fh = open(file, "r", encoding="UTF-8")
            for line in fh.readlines():
                # 6-23-2010       9.92
                (date, value) = line.strip().split("\t")
                if re.match("Fecha", date):
                    continue
                try:
                    dolVsBol = DollarToday(date, value)
                    tempMap[dolVsBol.date] = dolVsBol
                except (Exception) as err:
                    print(err)
            self.clear()
            self.update(tempMap)
        except (Exception) as err:
            raise
        finally:
            if (fh is not None):
                fh.close()

    def __str__(self):
        return "Records={},\n{}".format(len(self), ",\n".join([str(date) for date in self.values()]))

def main(options):

    verbose = options.verbose
    dc = DollarCollection()
    if options.report == None:
        dc.readCsv(options.read)
        dc.save(options.write)
    else:
        dc.readBinary(options.report, verbose)
    if verbose:
        print("{}".format(dc))

if __name__ == "__main__":
    
    usagetext = """
%prog --read csv.file --write binary.file

Or:

%prog --report binary.file

"""

    op = OptionParser(usage=usagetext)
    op.add_option(
                  "-r", "--read",
                  action="store", 
                  dest="read", 
                  help="Ruta completa del archivo CSV fuente, cada linea tiene una clave=valor")
    op.add_option(
                  "-w", "--write", 
                  action="store", 
                  dest="write", 
                  help="Ruta completa para guardar el archivo en nuevo formato binario")
    op.add_option(
                  "-p", "--report", 
                  action="store", 
                  dest="report",
                  help="Lee el archivo binario en memoria. No es compatible con --read y --write")
    op.add_option(
                  "-v", "--verbose", 
                  action="store_true", 
                  default=False, 
                  dest="verbose", 
                  help="Activar impresión de valores por pantalla")
    
    (options, values) = op.parse_args()

    main(options)

Un ejemplo de como correrlo:

DolarToday.py --read /Users/josevnz/Documents/dolartoday.csv --write /Users/josevnz/Documents/dolartoday.jose --verbose
DolarToday.py --report /Users/josevnz/Documents/dolartoday.jose --verbose

Y la salida luce como esto:

Records=1917,
DollarToday[date=2010-06-23 00:00:00, value=9.92],
DollarToday[date=2010-06-25 00:00:00, value=8.05],
DollarToday[date=2010-06-26 00:00:00, value=8.05],
DollarToday[date=2010-06-27 00:00:00, value=7.91],
DollarToday[date=2010-06-28 00:00:00, value=7.91],
DollarToday[date=2010-06-29 00:00:00, value=7.92],
DollarToday[date=2010-06-30 00:00:00, value=7.97],
DollarToday[date=2010-07-01 00:00:00, value=7.97],
DollarToday[date=2010-07-02 00:00:00, value=7.98],

Si tan sólo el gobierno de Nicolás Maduro fuera tan transparente como mis programas 🙂