I have a project that reads spreadsheets as a rectangular list of lists (matrix) transforms the matrix and then writes it to another spreadsheet. I want to be able to read from and write to multiple formats. Consequentially, the program should double as spreadsheet conversion tool.
Here is an example of just copying one sheet to another:
def ConvertSheet(in_sheet, out_sheet) sheet_io = SheetIO() data_matrix = sheet_io.read_sheet(in_sheet) # different functions would operate on data_matrix before writing. sheet_io.write_sheet(out_sheet, modified_data)
Depending on what type of object
out_sheet are determine how
SheetIO writes it but as far as the client cares, that's
The design I have implemented is an odd factory pattern. It's primarily why I am asking for advice.
Abstract base class
This is pretty basic and would essentially be the interface in a proper factory pattern:
class ISheetIO(object): """ Abstract base class... """ def read_sheet(self): raise NotImplementedError("read_sheet not implemented") def write_sheet(self): raise NotImplementedError("write_sheet not implemented")
The only concrete base class I have implemented so far. Note that it reads and writes sub-vectors as rows. I am specifying that all Concrete classes operate that way.
Also note that I am allowing optional arguments, specifying the range to write them to.
class ExcelSheet(ISheetIO): def read_sheet(self, sheet, col_start=0, row_start=0, col_cnt=-1, row_cnt=-1): # TODO: Raise an error instead. Research which error to raise assert not sheet.ragged_rows if row_cnt < 0: row_cnt += sheet.nrows + 1 if col_cnt < 0: col_cnt += sheet.ncols + 1 return [sheet.row_values(r, col_start, row_start) for r in xrange(row_start, row_cnt + 1)] def write_sheet(self, data, sheet, col_start=0, row_start=0): for r, row, in enumerate(data): for c, el in enumerate(row): sheet.write(row_start + r, col_start + c, el) # class CSVSheet(ISheetIO): # class OpenXMLSheet(ISheetIO):
This is where things get hairy. My understanding of the factory pattern is that this should be the factory at it would return a concrete class. Instead the client instructs
SheetIO to read or write and
SheetIO gets and uses the concrete class itself through its own factory. The reason for composing the concrete class instead of returning it is, I am permitting the client to request the data with sub-lists as columns.
SheetIO encapsulates that state with
read_by_row and remembers to transpose the data again when writing.
class SheetIO(ISheetIO): def __init__(self, read_by_row=True): self.by_row = read_by_row def _factory(self, sheet): sheet_type = type(sheet) if sheet_type is xlrd.sheet or sheet_type is xlwt.WorkSheet: return ExcelSheet() # elif CSV # return CSVSheet() # elif OpenXML # return OpenXMLSheet() else: raise TypeError("Unknown Sheet type or not a sheet") def read_sheet(self, sheet, **kwargs): data = self._factory(sheet).read_sheet(sheet, **kwargs) return self.orient_data(data) def write_sheet(self, data, sheet, **kwargs): oriented_data = self._orient_data(data) self._factory(sheet).write_sheet(sheet, oriented_data, **kwargs) def _orient_data(self, data): if self.by_row: return data else: return self._transpose(data) def _transpose(self, matrix): return zip(*matrix)