def _column_value_getter(self, obj): """Create callable that returns column values for given object types.""" if is_namedtuple(obj): # Get values using properties def get(obj, column): return getattr(obj, column, None) elif is_dict_like(obj): # Get values using dictionary keys def get(obj, column): return obj.get(column) elif is_list_like(obj): # Get values using list indexes def get(obj, column): col = self.column_location(column) try: return obj[col] except IndexError: return None else: # Fallback to single column def get(obj, _): return obj return get
def __init__(self, data=None, columns=None, index=None): self._data = [] self._columns = [] self._index = [] # Use public setters to validate data if columns is not None: self.columns = list(columns) if index is not None: self.index = list(index) if not data: self._init_empty() elif isinstance(data, Table): self._init_table(data) elif is_dict_like(data): self._init_dict(data) elif is_list_like(data): self._init_list(data) else: raise TypeError("Not a valid input format") if columns: self._sort_columns(columns) self._validate_self()
def to_options( opts: Options, default: Optional[str] = None ) -> Tuple[List[str], Optional[str]]: """Convert keyword argument for multiple options into a list of strings. Also handles default option validation. """ if isinstance(opts, str): opts = [opt.strip() for opt in opts.split(",")] elif is_list_like(opts): opts = [str(opt) for opt in opts] else: raise ValueError(f"Unsupported options type: {opts}") if not opts: return [], None if default is None: default = opts[0] if default not in opts: raise ValueError(f"Default '{default}' is not in available options") return opts, default
def _column_name_getter(self, obj): """Create callable that returns column names for given obj types.""" if is_namedtuple(obj): # Use namedtuple fields as columns def get(obj): return list(obj._fields) elif is_dict_like(obj): # Use dictionary keys as columns def get(obj): return list(obj.keys()) elif is_list_like(obj): # Use either predefined columns, or # generate range-based column values predefined = list(self._columns) def get(obj): count = len(obj) if predefined: if count > len(predefined): raise ValueError( f"Data had more than defined {count} columns") return predefined[:count] else: return list(range(count)) else: # Fallback to single column def get(_): return self._columns[:1] if self._columns else [0] return get
def _driver_path(factory: Any, download: bool) -> Optional[Path]: if platform.system() != "Windows": manager = factory(link_path="/usr/bin") else: manager = factory() driver_names = manager.get_driver_filename() if driver_names is None: return None if not is_list_like(driver_names): driver_names = [driver_names] primary_path = Path(DRIVER_DIR) / driver_names[0] if download or primary_path.exists(): return primary_path for name in driver_names: temp_path = Path(DRIVER_DIR) / name if temp_path.exists(): return temp_path link_path = Path(manager.link_path) / name if link_path.exists(): return link_path return Path(manager.link_path) / driver_names[0]
def _validate_index(self, names): """Validate that given index names can be used.""" if not is_list_like(names): raise ValueError("Index should be list-like") if len(set(names)) != len(names): raise ValueError("Duplicate index names") if self._data and len(names) != len(self._data): raise ValueError("Invalid index length")
def _validate_columns(self, names): """Validate that given column names can be used.""" if not is_list_like(names): raise ValueError("Columns should be list-like") if len(set(names)) != len(names): raise ValueError("Duplicate column names") if self._data and len(names) != len(self._data[0]): raise ValueError("Invalid columns length")
def _link_paths(manager: Any): names = manager.get_driver_filename() if names is None: return [] if not is_list_like(names): names = [names] return [Path(manager.download_root) / name for name in names]
def get(self, indexes=None, columns=None, as_list=False): """Get values from table. Return type depends on input dimensions. If `indexes` and `columns` are scalar, i.e. not lists: Returns single cell value If either `indexes` or `columns` is a list: Returns matching row or column If both `indexes` and `columns` are lists: Returns a new Table instance with matching cell values :param indexes: list of indexes, or all if not given :param columns: list of columns, or all if not given """ indexes = if_none(indexes, self._index) columns = if_none(columns, self._columns) if is_list_like(indexes) and is_list_like(columns): return self.get_table(indexes, columns, as_list) elif not is_list_like(indexes) and is_list_like(columns): return self.get_row(indexes, columns, as_list) elif is_list_like(indexes) and not is_list_like(columns): return self.get_column(columns, indexes, as_list) else: return self.get_cell(indexes, columns)
def _sort_by(self, values, reverse=False): """Sort index and data by using `values` as sorting criteria.""" assert is_list_like(values) assert len(values) == len(self._data) def sorter(row): """Sort table by given values, while allowing for disparate types. Order priority: - Values by typename - Numeric types - None values """ criteria = [] for value in row[1]: # Ignore enumeration criteria.append( ( value is not None, "" if isinstance(value, Number) else type(value).__name__, value, ) ) return criteria # Store original index order using enumerate() before sort, and # use it to sort index/data later values = sorted(enumerate(values), key=sorter, reverse=reverse) idxs = [value[0] for value in values] # Re-order index, and update range-based values indexes = [] for idx_new, idx_old in enumerate(idxs): index = self._index[idx_old] if isinstance(index, int): indexes.append(idx_new) else: indexes.append(index) # Re-order data self._data = [self._data[idx] for idx in idxs]
def to_list(obj, size=1): """Convert (possibly scalar) value to list of `size`.""" if not is_list_like(obj): return [obj] * int(size) else: return obj