‘Contexts’ en Python

En Python, un contexto (context) es una clase que implementa los métodos ‘__enter__’ y ‘__exit__’ los cuales son llamados si la clase en llamada con la palabra reservada ‘with’. Por ejemplo, los descriptores de archivo (file handle) en Python se pueden llamar con un contexto, ahorrando llamar ‘finally’ para cerrar archivos, sin importar si hay un error.

Les traigo de vuelta el programa de ‘DolarToday’ el cual escribe datos en un archivo binario, pero ahora utilizando contextos:

#!/usr/bin/env python3
# Simple program to save the 'Dolartoday' extra official dollar rates from CSV to a custom format
# Revisited to use 'contexts' to avoid using finally on exception handling
# @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, "{} for date {} is invalid!".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):
    
    # https://en.wikipedia.org/wiki/List_of_file_signatures
    __FILE_MAGIC = b"DLR\x00"
    __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
                
    def readCsv(self, file):
        tempMap = {}
        try:
            with open(file, "r", encoding="UTF-8") as fh:
                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

    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="Full path to CSV file with date,value pairs per line")
    op.add_option(
                  "-w", "--write", 
                  action="store", 
                  dest="write", 
                  help="Full path to destination file in binary format")
    op.add_option(
                  "-p", "--report", 
                  action="store", 
                  dest="report",
                  help="Read the contents of the binary storage and generate a report. Incompatible with --read and --write")
    op.add_option(
                  "-v", "--verbose", 
                  action="store_true", 
                  default=False, 
                  dest="verbose", 
                  help="Enable verbose mode")
    
    (options, values) = op.parse_args()

    main(options)