summaryrefslogtreecommitdiff
path: root/logilab/common/table.py
diff options
context:
space:
mode:
Diffstat (limited to 'logilab/common/table.py')
-rw-r--r--logilab/common/table.py246
1 files changed, 146 insertions, 100 deletions
diff --git a/logilab/common/table.py b/logilab/common/table.py
index 1f1101c..e7b9195 100644
--- a/logilab/common/table.py
+++ b/logilab/common/table.py
@@ -18,6 +18,10 @@
"""Table management module."""
from __future__ import print_function
+from types import CodeType
+from typing import Any, List, Optional, Tuple, Union, Dict, Iterator
+from _io import StringIO
+from mypy_extensions import NoReturn
__docformat__ = "restructuredtext en"
@@ -30,51 +34,64 @@ class Table(object):
forall(self.data, lambda x: len(x) <= len(self.col_names))
"""
- def __init__(self, default_value=0, col_names=None, row_names=None):
- self.col_names = []
- self.row_names = []
- self.data = []
- self.default_value = default_value
+ def __init__(self, default_value: int = 0, col_names: Optional[List[str]] = None, row_names: Optional[Any] = None) -> None:
+ self.col_names: List = []
+ self.row_names: List = []
+ self.data: List = []
+ self.default_value: int = default_value
if col_names:
self.create_columns(col_names)
if row_names:
self.create_rows(row_names)
- def _next_row_name(self):
+ def _next_row_name(self) -> str:
return 'row%s' % (len(self.row_names)+1)
- def __iter__(self):
+ def __iter__(self) -> Iterator:
return iter(self.data)
- def __eq__(self, other):
+ # def __eq__(self, other: Union[List[List[int]], List[Tuple[str, str, str, float]]]) -> bool:
+ def __eq__(self, other: object) -> bool:
+ def is_iterable(variable: Any) -> bool:
+ try:
+ iter(variable)
+ except TypeError:
+ return False
+ else:
+ return True
+
if other is None:
return False
+ elif is_iterable(other):
+ # mypy: No overload variant of "list" matches argument type "object"
+ # checked before
+ return list(self) == list(other) # type: ignore
else:
- return list(self) == list(other)
+ return False
__hash__ = object.__hash__
def __ne__(self, other):
return not self == other
- def __len__(self):
+ def __len__(self) -> int:
return len(self.row_names)
## Rows / Columns creation #################################################
- def create_rows(self, row_names):
+ def create_rows(self, row_names: List[str]) -> None:
"""Appends row_names to the list of existing rows
"""
self.row_names.extend(row_names)
for row_name in row_names:
self.data.append([self.default_value]*len(self.col_names))
- def create_columns(self, col_names):
+ def create_columns(self, col_names: List[str]) -> None:
"""Appends col_names to the list of existing columns
"""
for col_name in col_names:
self.create_column(col_name)
- def create_row(self, row_name=None):
+ def create_row(self, row_name: str = None) -> None:
"""Creates a rowname to the row_names list
"""
row_name = row_name or self._next_row_name()
@@ -82,7 +99,7 @@ class Table(object):
self.data.append([self.default_value]*len(self.col_names))
- def create_column(self, col_name):
+ def create_column(self, col_name: str) -> None:
"""Creates a colname to the col_names list
"""
self.col_names.append(col_name)
@@ -90,7 +107,7 @@ class Table(object):
row.append(self.default_value)
## Sort by column ##########################################################
- def sort_by_column_id(self, col_id, method = 'asc'):
+ def sort_by_column_id(self, col_id: str, method: str = 'asc') -> None:
"""Sorts the table (in-place) according to data stored in col_id
"""
try:
@@ -100,7 +117,7 @@ class Table(object):
raise KeyError("Col (%s) not found in table" % (col_id))
- def sort_by_column_index(self, col_index, method = 'asc'):
+ def sort_by_column_index(self, col_index: int, method: str = 'asc') -> None:
"""Sorts the table 'in-place' according to data stored in col_index
method should be in ('asc', 'desc')
@@ -119,29 +136,33 @@ class Table(object):
self.data.append(row)
self.row_names.append(row_name)
- def groupby(self, colname, *others):
+ def groupby(self, colname: str, *others: str) -> Union[Dict[str, Dict[str, 'Table']],
+ Dict[str, 'Table']]:
"""builds indexes of data
:returns: nested dictionaries pointing to actual rows
"""
- groups = {}
+ groups: Dict = {}
colnames = (colname,) + others
col_indexes = [self.col_names.index(col_id) for col_id in colnames]
for row in self.data:
ptr = groups
for col_index in col_indexes[:-1]:
ptr = ptr.setdefault(row[col_index], {})
- ptr = ptr.setdefault(row[col_indexes[-1]],
- Table(default_value=self.default_value,
- col_names=self.col_names))
- ptr.append_row(tuple(row))
+ table = ptr.setdefault(row[col_indexes[-1]],
+ Table(default_value=self.default_value,
+ col_names=self.col_names))
+ table.append_row(tuple(row))
return groups
- def select(self, colname, value):
+ def select(self, colname: str, value: str) -> 'Table':
grouped = self.groupby(colname)
try:
- return grouped[value]
+ # mypy: Incompatible return value type (got "Union[Dict[str, Table], Table]",
+ # mypy: expected "Table")
+ # I guess we are sure we'll get a Table here?
+ return grouped[value] # type: ignore
except KeyError:
- return []
+ return Table()
def remove(self, colname, value):
col_index = self.col_names.index(colname)
@@ -151,13 +172,13 @@ class Table(object):
## The 'setter' part #######################################################
- def set_cell(self, row_index, col_index, data):
+ def set_cell(self, row_index: int, col_index: int, data: int) -> None:
"""sets value of cell 'row_indew', 'col_index' to data
"""
self.data[row_index][col_index] = data
- def set_cell_by_ids(self, row_id, col_id, data):
+ def set_cell_by_ids(self, row_id: str, col_id: str, data: Union[int, str]) -> None:
"""sets value of cell mapped by row_id and col_id to data
Raises a KeyError if row_id or col_id are not found in the table
"""
@@ -173,7 +194,7 @@ class Table(object):
raise KeyError("Column (%s) not found in table" % (col_id))
- def set_row(self, row_index, row_data):
+ def set_row(self, row_index: int, row_data: Union[List[float], List[int], List[str]]) -> None:
"""sets the 'row_index' row
pre::
@@ -183,7 +204,7 @@ class Table(object):
self.data[row_index] = row_data
- def set_row_by_id(self, row_id, row_data):
+ def set_row_by_id(self, row_id: str, row_data: List[str]) -> None:
"""sets the 'row_id' column
pre::
@@ -199,7 +220,7 @@ class Table(object):
raise KeyError('Row (%s) not found in table' % (row_id))
- def append_row(self, row_data, row_name=None):
+ def append_row(self, row_data: Union[List[Union[float, str]], List[int]], row_name: Optional[str] = None) -> int:
"""Appends a row to the table
pre::
@@ -211,7 +232,7 @@ class Table(object):
self.data.append(row_data)
return len(self.data) - 1
- def insert_row(self, index, row_data, row_name=None):
+ def insert_row(self, index: int, row_data: List[str], row_name: str = None) -> None:
"""Appends row_data before 'index' in the table. To make 'insert'
behave like 'list.insert', inserting in an out of range index will
insert row_data to the end of the list
@@ -225,7 +246,7 @@ class Table(object):
self.data.insert(index, row_data)
- def delete_row(self, index):
+ def delete_row(self, index: int) -> List[str]:
"""Deletes the 'index' row in the table, and returns it.
Raises an IndexError if index is out of range
"""
@@ -233,7 +254,7 @@ class Table(object):
return self.data.pop(index)
- def delete_row_by_id(self, row_id):
+ def delete_row_by_id(self, row_id: str) -> None:
"""Deletes the 'row_id' row in the table.
Raises a KeyError if row_id was not found.
"""
@@ -244,7 +265,7 @@ class Table(object):
raise KeyError('Row (%s) not found in table' % (row_id))
- def set_column(self, col_index, col_data):
+ def set_column(self, col_index: int, col_data: Union[List[int], range]) -> None:
"""sets the 'col_index' column
pre::
@@ -256,7 +277,7 @@ class Table(object):
self.data[row_index][col_index] = cell_data
- def set_column_by_id(self, col_id, col_data):
+ def set_column_by_id(self, col_id: str, col_data: Union[List[int], range]) -> None:
"""sets the 'col_id' column
pre::
@@ -272,7 +293,7 @@ class Table(object):
raise KeyError('Column (%s) not found in table' % (col_id))
- def append_column(self, col_data, col_name):
+ def append_column(self, col_data: range, col_name: str) -> None:
"""Appends the 'col_index' column
pre::
@@ -284,7 +305,7 @@ class Table(object):
self.data[row_index].append(cell_data)
- def insert_column(self, index, col_data, col_name):
+ def insert_column(self, index: int, col_data: range, col_name: str) -> None:
"""Appends col_data before 'index' in the table. To make 'insert'
behave like 'list.insert', inserting in an out of range index will
insert col_data to the end of the list
@@ -298,7 +319,7 @@ class Table(object):
self.data[row_index].insert(index, cell_data)
- def delete_column(self, index):
+ def delete_column(self, index: int) -> List[int]:
"""Deletes the 'index' column in the table, and returns it.
Raises an IndexError if index is out of range
"""
@@ -306,7 +327,7 @@ class Table(object):
return [row.pop(index) for row in self.data]
- def delete_column_by_id(self, col_id):
+ def delete_column_by_id(self, col_id: str) -> None:
"""Deletes the 'col_id' col in the table.
Raises a KeyError if col_id was not found.
"""
@@ -319,53 +340,68 @@ class Table(object):
## The 'getter' part #######################################################
- def get_shape(self):
+ def get_shape(self) -> Tuple[int, int]:
"""Returns a tuple which represents the table's shape
"""
return len(self.row_names), len(self.col_names)
shape = property(get_shape)
- def __getitem__(self, indices):
+ def __getitem__(self, indices: Union[Tuple[Union[int, slice, str], Union[int, str]], int, slice]) -> Any:
"""provided for convenience"""
- rows, multirows = None, False
- cols, multicols = None, False
+ multirows: bool = False
+ multicols: bool = False
+
+ rows: slice
+ cols: slice
+
+ rows_indice: Union[int, slice, str]
+ cols_indice: Union[int, str, None] = None
+
if isinstance(indices, tuple):
- rows = indices[0]
+ rows_indice = indices[0]
if len(indices) > 1:
- cols = indices[1]
+ cols_indice = indices[1]
else:
- rows = indices
+ rows_indice = indices
+
# define row slice
- if isinstance(rows, str):
+ if isinstance(rows_indice, str):
try:
- rows = self.row_names.index(rows)
+ rows_indice = self.row_names.index(rows_indice)
except ValueError:
- raise KeyError("Row (%s) not found in table" % (rows))
- if isinstance(rows, int):
- rows = slice(rows, rows+1)
+ raise KeyError("Row (%s) not found in table" % (rows_indice))
+
+ if isinstance(rows_indice, int):
+ rows = slice(rows_indice, rows_indice + 1)
multirows = False
else:
rows = slice(None)
multirows = True
+
# define col slice
- if isinstance(cols, str):
+ if isinstance(cols_indice, str):
try:
- cols = self.col_names.index(cols)
+ cols_indice = self.col_names.index(cols_indice)
except ValueError:
- raise KeyError("Column (%s) not found in table" % (cols))
- if isinstance(cols, int):
- cols = slice(cols, cols+1)
+ raise KeyError("Column (%s) not found in table" % (cols_indice))
+
+ if isinstance(cols_indice, int):
+ cols = slice(cols_indice, cols_indice + 1)
multicols = False
else:
cols = slice(None)
multicols = True
+
# get sub-table
tab = Table()
tab.default_value = self.default_value
+
tab.create_rows(self.row_names[rows])
tab.create_columns(self.col_names[cols])
+
for idx, row in enumerate(self.data[rows]):
tab.set_row(idx, row[cols])
+
if multirows :
if multicols:
return tab
@@ -409,7 +445,7 @@ class Table(object):
raise KeyError("Column (%s) not found in table" % (col_id))
return self.get_column(col_index, distinct)
- def get_columns(self):
+ def get_columns(self) -> List[List[int]]:
"""Returns all the columns in the table
"""
return [self[:, index] for index in range(len(self.col_names))]
@@ -421,14 +457,14 @@ class Table(object):
col = list(set(col))
return col
- def apply_stylesheet(self, stylesheet):
+ def apply_stylesheet(self, stylesheet: 'TableStyleSheet') -> None:
"""Applies the stylesheet to this table
"""
for instruction in stylesheet.instructions:
eval(instruction)
- def transpose(self):
+ def transpose(self) -> 'Table':
"""Keeps the self object intact, and returns the transposed (rotated)
table.
"""
@@ -440,7 +476,7 @@ class Table(object):
return transposed
- def pprint(self):
+ def pprint(self) -> str:
"""returns a string representing the table in a pretty
printed 'text' format.
"""
@@ -482,7 +518,7 @@ class Table(object):
return '\n'.join(lines)
- def __repr__(self):
+ def __repr__(self) -> str:
return repr(self.data)
def as_text(self):
@@ -499,7 +535,7 @@ class TableStyle:
"""Defines a table's style
"""
- def __init__(self, table):
+ def __init__(self, table: Table) -> None:
self._table = table
self.size = dict([(col_name, '1*') for col_name in table.col_names])
@@ -516,12 +552,12 @@ class TableStyle:
self.units['__row_column__'] = ''
# XXX FIXME : params order should be reversed for all set() methods
- def set_size(self, value, col_id):
+ def set_size(self, value: str, col_id: str) -> None:
"""sets the size of the specified col_id to value
"""
self.size[col_id] = value
- def set_size_by_index(self, value, col_index):
+ def set_size_by_index(self, value: str, col_index: int) -> None:
"""Allows to set the size according to the column index rather than
using the column's id.
BE CAREFUL : the '0' column is the '__row_column__' one !
@@ -534,13 +570,13 @@ class TableStyle:
self.size[col_id] = value
- def set_alignment(self, value, col_id):
+ def set_alignment(self, value: str, col_id: str) -> None:
"""sets the alignment of the specified col_id to value
"""
self.alignment[col_id] = value
- def set_alignment_by_index(self, value, col_index):
+ def set_alignment_by_index(self, value: str, col_index: int) -> None:
"""Allows to set the alignment according to the column index rather than
using the column's id.
BE CAREFUL : the '0' column is the '__row_column__' one !
@@ -553,13 +589,13 @@ class TableStyle:
self.alignment[col_id] = value
- def set_unit(self, value, col_id):
+ def set_unit(self, value: str, col_id: str) -> None:
"""sets the unit of the specified col_id to value
"""
self.units[col_id] = value
- def set_unit_by_index(self, value, col_index):
+ def set_unit_by_index(self, value: str, col_index: int) -> None:
"""Allows to set the unit according to the column index rather than
using the column's id.
BE CAREFUL : the '0' column is the '__row_column__' one !
@@ -574,13 +610,13 @@ class TableStyle:
self.units[col_id] = value
- def get_size(self, col_id):
+ def get_size(self, col_id: str) -> str:
"""Returns the size of the specified col_id
"""
return self.size[col_id]
- def get_size_by_index(self, col_index):
+ def get_size_by_index(self, col_index: int) -> str:
"""Allows to get the size according to the column index rather than
using the column's id.
BE CAREFUL : the '0' column is the '__row_column__' one !
@@ -593,13 +629,13 @@ class TableStyle:
return self.size[col_id]
- def get_alignment(self, col_id):
+ def get_alignment(self, col_id: str) -> str:
"""Returns the alignment of the specified col_id
"""
return self.alignment[col_id]
- def get_alignment_by_index(self, col_index):
+ def get_alignment_by_index(self, col_index: int) -> str:
"""Allors to get the alignment according to the column index rather than
using the column's id.
BE CAREFUL : the '0' column is the '__row_column__' one !
@@ -612,13 +648,13 @@ class TableStyle:
return self.alignment[col_id]
- def get_unit(self, col_id):
+ def get_unit(self, col_id: str) -> str:
"""Returns the unit of the specified col_id
"""
return self.units[col_id]
- def get_unit_by_index(self, col_index):
+ def get_unit_by_index(self, col_index: int) -> str:
"""Allors to get the unit according to the column index rather than
using the column's id.
BE CAREFUL : the '0' column is the '__row_column__' one !
@@ -649,28 +685,30 @@ class TableStyleSheet:
2_5 = sqrt(2_3**2 + 2_4**2)
"""
- def __init__(self, rules = None):
+ def __init__(self, rules: Optional[List[str]] = None) -> None:
rules = rules or []
- self.rules = []
- self.instructions = []
+
+ self.rules: List[str] = []
+ self.instructions: List[CodeType] = []
+
for rule in rules:
self.add_rule(rule)
- def add_rule(self, rule):
+ def add_rule(self, rule: str) -> None:
"""Adds a rule to the stylesheet rules
"""
try:
source_code = ['from math import *']
source_code.append(CELL_PROG.sub(r'self.data[\1][\2]', rule))
self.instructions.append(compile('\n'.join(source_code),
- 'table.py', 'exec'))
+ 'table.py', 'exec'))
self.rules.append(rule)
except SyntaxError:
print("Bad Stylesheet Rule : %s [skipped]" % rule)
- def add_rowsum_rule(self, dest_cell, row_index, start_col, end_col):
+ def add_rowsum_rule(self, dest_cell: Tuple[int, int], row_index: int, start_col: int, end_col: int) -> None:
"""Creates and adds a rule to sum over the row at row_index from
start_col to end_col.
dest_cell is a tuple of two elements (x,y) of the destination cell
@@ -686,7 +724,7 @@ class TableStyleSheet:
self.add_rule(rule)
- def add_rowavg_rule(self, dest_cell, row_index, start_col, end_col):
+ def add_rowavg_rule(self, dest_cell: Tuple[int, int], row_index: int, start_col: int, end_col: int) -> None:
"""Creates and adds a rule to make the row average (from start_col
to end_col)
dest_cell is a tuple of two elements (x,y) of the destination cell
@@ -703,7 +741,7 @@ class TableStyleSheet:
self.add_rule(rule)
- def add_colsum_rule(self, dest_cell, col_index, start_row, end_row):
+ def add_colsum_rule(self, dest_cell: Tuple[int, int], col_index: int, start_row: int, end_row: int) -> None:
"""Creates and adds a rule to sum over the col at col_index from
start_row to end_row.
dest_cell is a tuple of two elements (x,y) of the destination cell
@@ -719,7 +757,7 @@ class TableStyleSheet:
self.add_rule(rule)
- def add_colavg_rule(self, dest_cell, col_index, start_row, end_row):
+ def add_colavg_rule(self, dest_cell: Tuple[int, int], col_index: int, start_row: int, end_row: int) -> None:
"""Creates and adds a rule to make the col average (from start_row
to end_row)
dest_cell is a tuple of two elements (x,y) of the destination cell
@@ -741,7 +779,7 @@ class TableCellRenderer:
"""Defines a simple text renderer
"""
- def __init__(self, **properties):
+ def __init__(self, **properties: Any) -> None:
"""keywords should be properties with an associated boolean as value.
For example :
renderer = TableCellRenderer(units = True, alignment = False)
@@ -752,7 +790,7 @@ class TableCellRenderer:
self.properties = properties
- def render_cell(self, cell_coord, table, table_style):
+ def render_cell(self, cell_coord: Tuple[int, int], table: Table, table_style: TableStyle) -> Union[str, int]:
"""Renders the cell at 'cell_coord' in the table, using table_style
"""
row_index, col_index = cell_coord
@@ -763,14 +801,14 @@ class TableCellRenderer:
table_style, col_index + 1)
- def render_row_cell(self, row_name, table, table_style):
+ def render_row_cell(self, row_name: str, table: Table, table_style: TableStyle) -> Union[str, int]:
"""Renders the cell for 'row_id' row
"""
cell_value = row_name
return self._render_cell_content(cell_value, table_style, 0)
- def render_col_cell(self, col_name, table, table_style):
+ def render_col_cell(self, col_name: str, table: Table, table_style: TableStyle) -> Union[str, int]:
"""Renders the cell for 'col_id' row
"""
cell_value = col_name
@@ -779,7 +817,7 @@ class TableCellRenderer:
- def _render_cell_content(self, content, table_style, col_index):
+ def _render_cell_content(self, content: Union[str, int], table_style: TableStyle, col_index: int) -> Union[str, int]:
"""Makes the appropriate rendering for this cell content.
Rendering properties will be searched using the
*table_style.get_xxx_by_index(col_index)' methods
@@ -789,11 +827,12 @@ class TableCellRenderer:
return content
- def _make_cell_content(self, cell_content, table_style, col_index):
+ def _make_cell_content(self, cell_content: int, table_style: TableStyle, col_index: int) -> Union[int, str]:
"""Makes the cell content (adds decoration data, like units for
example)
"""
- final_content = cell_content
+ final_content: Union[int, str] = cell_content
+
if 'skip_zero' in self.properties:
replacement_char = self.properties['skip_zero']
else:
@@ -812,7 +851,7 @@ class TableCellRenderer:
return final_content
- def _add_unit(self, cell_content, table_style, col_index):
+ def _add_unit(self, cell_content: int, table_style: TableStyle, col_index: int) -> str:
"""Adds unit to the cell_content if needed
"""
unit = table_style.get_unit_by_index(col_index)
@@ -824,7 +863,7 @@ class DocbookRenderer(TableCellRenderer):
"""Defines how to render a cell for a docboook table
"""
- def define_col_header(self, col_index, table_style):
+ def define_col_header(self, col_index: int, table_style: TableStyle) -> str:
"""Computes the colspec element according to the style
"""
size = table_style.get_size_by_index(col_index)
@@ -832,7 +871,7 @@ class DocbookRenderer(TableCellRenderer):
(col_index, size)
- def _render_cell_content(self, cell_content, table_style, col_index):
+ def _render_cell_content(self, cell_content: Union[int, str], table_style: TableStyle, col_index: int) -> str:
"""Makes the appropriate rendering for this cell content.
Rendering properties will be searched using the
table_style.get_xxx_by_index(col_index)' methods.
@@ -847,17 +886,20 @@ class DocbookRenderer(TableCellRenderer):
# KeyError <=> Default alignment
return "<entry>%s</entry>\n" % cell_content
+ # XXX really?
+ return ""
+
class TableWriter:
"""A class to write tables
"""
- def __init__(self, stream, table, style, **properties):
+ def __init__(self, stream: StringIO, table: Table, style: Optional[Any], **properties: Any) -> None:
self._stream = stream
self.style = style or TableStyle(table)
self._table = table
self.properties = properties
- self.renderer = None
+ self.renderer: Optional[DocbookRenderer] = None
def set_style(self, style):
@@ -866,7 +908,7 @@ class TableWriter:
self.style = style
- def set_renderer(self, renderer):
+ def set_renderer(self, renderer: DocbookRenderer) -> None:
"""sets the way to render cell
"""
self.renderer = renderer
@@ -878,7 +920,7 @@ class TableWriter:
self.properties.update(properties)
- def write_table(self, title = ""):
+ def write_table(self, title: str = "") -> None:
"""Writes the table
"""
raise NotImplementedError("write_table must be implemented !")
@@ -889,9 +931,11 @@ class DocbookTableWriter(TableWriter):
"""Defines an implementation of TableWriter to write a table in Docbook
"""
- def _write_headers(self):
+ def _write_headers(self) -> None:
"""Writes col headers
"""
+ assert self.renderer is not None
+
# Define col_headers (colstpec elements)
for col_index in range(len(self._table.col_names)+1):
self._stream.write(self.renderer.define_col_header(col_index,
@@ -908,9 +952,11 @@ class DocbookTableWriter(TableWriter):
self._stream.write("</row>\n</thead>\n")
- def _write_body(self):
+ def _write_body(self) -> None:
"""Writes the table body
"""
+ assert self.renderer is not None
+
self._stream.write('<tbody>\n')
for row_index, row in enumerate(self._table.data):
@@ -931,7 +977,7 @@ class DocbookTableWriter(TableWriter):
self._stream.write('</tbody>\n')
- def write_table(self, title = ""):
+ def write_table(self, title: str = "") -> None:
"""Writes the table
"""
self._stream.write('<table>\n<title>%s></title>\n'%(title))