dgorissen / pycel

A library for compiling excel spreadsheets to python code & visualizing them as a graph
GNU General Public License v3.0
570 stars 149 forks source link

Pycel raises NotImplementedError on rectangular ranges #99

Closed rmorel closed 3 years ago

rmorel commented 4 years ago

What actually happened

Pycel raises a exception on rectangular intervals if they are written as e.g., Sheet1!A1:Sheet1!A9 instead of Sheet1!A1:A9. Both are exactly the same interval.

What was expected to happen

Both intervals should nor raise exceptions,since they are the same.

Problem description

Pycel is interpreting intervals defined as e.g. Sheet1!A1:Sheet1!A9 as non rectangular. They are rectangular if the sheet reference is the same on both ends of the range.

Code Sample

I attached a test file Test Interval.xlsx to repeat the problem.

The code to reproduce the problem is

from pycel import ExcelCompiler

filename = 'Test Interval.xlsx'
model = ExcelCompiler(filename)

# This is a rectangular formula
model.evaluate('Sheet2!A1')

My traceback was

---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-1-35ef567d212e> in <module>
      5 
      6 # This is a rectangular formula
----> 7 model.evaluate('Sheet2!A1')

c:\users\rodrigo\documents\github\pycel\src\pycel\excelcompiler.py in _evaluate_non_iterative(self, address)
    804 
    805             if address.address not in self.cell_map:
--> 806                 self._gen_graph(address)
    807 
    808         result = self._evaluate(str(address))

c:\users\rodrigo\documents\github\pycel\src\pycel\excelcompiler.py in _gen_graph(self, seed, recursed)
    873         if not recursed:
    874             # if not entered to process one cell / cellrange process other work
--> 875             self._process_gen_graph()
    876 
    877     def _process_gen_graph(self):

c:\users\rodrigo\documents\github\pycel\src\pycel\excelcompiler.py in _process_gen_graph(self)
    883             self.log.debug("Handling {}".format(dependant.address))
    884 
--> 885             for precedent_address in dependant.needed_addresses:
    886                 if precedent_address.address not in self.cell_map:
    887                     self._gen_graph(precedent_address, recursed=True)

c:\users\rodrigo\documents\github\pycel\src\pycel\excelcompiler.py in needed_addresses(self)
   1069     @property
   1070     def needed_addresses(self):
-> 1071         return self.formula and self.formula.needed_addresses or ()
   1072 
   1073 

c:\users\rodrigo\documents\github\pycel\src\pycel\excelformula.py in needed_addresses(self)
    612         if self._needed_addresses is None:
    613             # get all the cells/ranges this formula refers to, and remove dupes
--> 614             if self.python_code:
    615                 code = iter((self.python_code.encode(),))
    616                 tokens = tuple(tk.tokenize(lambda: next(code)))

c:\users\rodrigo\documents\github\pycel\src\pycel\excelformula.py in python_code(self)
    634                 self._python_code = ''
    635             else:
--> 636                 self._python_code = self.ast.emit
    637         return self._python_code
    638 

c:\users\rodrigo\documents\github\pycel\src\pycel\excelformula.py in emit(self)
    445             # map to the correct name
    446             return "{}({})".format(
--> 447                 self.func_map.get(func, func), self.comma_join_emit())
    448 
    449     @staticmethod

c:\users\rodrigo\documents\github\pycel\src\pycel\excelformula.py in comma_join_emit(self, fmt_str, to_emit)
    422             to_emit = self.children
    423         if fmt_str is None:
--> 424             return ", ".join(n.emit for n in to_emit)
    425         else:
    426             return ", ".join(

c:\users\rodrigo\documents\github\pycel\src\pycel\excelformula.py in <genexpr>(.0)
    422             to_emit = self.children
    423         if fmt_str is None:
--> 424             return ", ".join(n.emit for n in to_emit)
    425         else:
    426             return ", ".join(

c:\users\rodrigo\documents\github\pycel\src\pycel\excelformula.py in emit(self)
    344     @property
    345     def emit(self):
--> 346         return self._emit()
    347 
    348     def _emit(self, value=None):

c:\users\rodrigo\documents\github\pycel\src\pycel\excelformula.py in _emit(self, value)
    354         try:
    355             addr_str = value.replace('$', '')
--> 356             address = AddressRange.create(addr_str, sheet=sheet, cell=self.cell)
    357         except ValueError:
    358             # check for table relative address

c:\users\rodrigo\documents\github\pycel\src\pycel\excelutil.py in create(cls, address, sheet, cell)
    344             return address
    345 
--> 346         sheetname, addr = split_sheetname(address, sheet=sheet)
    347         addr_tuple, sheetname = range_boundaries(
    348             addr, sheet=sheetname, cell=cell)

c:\users\rodrigo\documents\github\pycel\src\pycel\excelutil.py in split_sheetname(address, sheet)
    547         sh, address_part = address.split('!', maxsplit=1)
    548         if '!' in address_part:
--> 549             raise NotImplementedError(
    550                 "Non-rectangular formulas: {}".format(address))
    551         sh = unquote_sheetname(sh)

NotImplementedError: Non-rectangular formulas: Sheet1!A1:'Sheet1'!A9

Environment