Source code for nosqlapi.columndb.odm

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
# vim: se ts=4 et syn=python:

# created by: matteo.guadrini
# odm -- nosqlapi
#
#     Copyright (C) 2022 Matteo Guadrini <matteo.guadrini@hotmail.it>
#
#     This program is free software: you can redistribute it and/or modify
#     it under the terms of the GNU General Public License as published by
#     the Free Software Foundation, either version 3 of the License, or
#     (at your option) any later version.
#
#     This program is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#     GNU General Public License for more details.
#
#     You should have received a copy of the GNU General Public License
#     along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""ODM module for column NOSQL database."""

# region Imports
from collections import namedtuple
from functools import wraps

from nosqlapi.common import Counter
from nosqlapi.kvdb.odm import Keyspace as Ks

# endregion

# region global variable
__all__ = ['Keyspace', 'Table', 'Column', 'Index', 'column']


# endregion

# region Classes
[docs]class Keyspace(Ks): """Represents keyspace like database""" pass
[docs]class Table: """Represents table as container of columns"""
[docs] def __init__(self, name, *columns, **options): """Table object :param name: Name of table :param columns: Columns :param options: Options """ self._name = name self._columns = [col for col in columns] self._options = options self._index = []
@property def name(self): """Name of table""" return self._name @name.setter def name(self, value): """Name of table""" self._name = value @property def columns(self): """List of columns""" return self._columns @property def options(self): """Options""" return self._options @property def index(self): """List of indexes""" return self._index @property def header(self): return tuple([col.name for col in self.columns]) @property def primary_key(self): pk = [col.name for col in self.columns if col.primary_key] return pk[0] @primary_key.setter def primary_key(self, value: str): for col in self.columns: if col.name == value: col.primary_key = True
[docs] def add_column(self, *columns): """Adding one or more column object to table :param columns: Column objects :return: None """ self._columns.extend(columns)
[docs] def delete_column(self, index=-1): """Deleting one column to table :param index: Number of index :return: None """ self._columns.pop(index)
[docs] def set_option(self, option): """Update options :param option: Dict options :return: None """ self._options.update(option)
[docs] def add_row(self, *rows): """Add one or more row into columns :param rows: Tuple of objects :return: None """ for row in rows: # Check length of columns and row if len(row) != len(self.columns): raise ValueError(f"length of row {row} is different of length of columns {len(self.columns)}") for element, col in zip(row, self.columns): col.append(element)
[docs] def delete_row(self, row=-1): """Delete one row into columns :param row: Index of row :return: None """ for col in self.columns: col.pop(row)
[docs] def get_rows(self): """Getting all rows :return: List[tuple] """ return [dataset for dataset in self]
[docs] def add_index(self, index): """Adding index to index property :param index: Name or Index object :return: None """ self._index.append(index)
[docs] def delete_index(self, index=-1): """Deleting index to index property :param index: Name or Index object :return: None """ self._index.pop(index)
def __getitem__(self, item): return self._columns[item] def __setitem__(self, key, value): self._columns[key] = value def __delitem__(self, key=-1): self._columns.pop(key) def __iter__(self): return (tuple([col[index] for col in self.columns]) for index in range(len(self.columns[0])))
[docs] def __repr__(self): return f'<{self.__class__.__name__} object, name={self.name}>'
[docs] def __str__(self): return f'{self.columns}'
[docs]class Column: """Represents column as container of values"""
[docs] def __init__(self, name, data=None, of_type=None, max_len=None, auto_increment=False, primary_key=False, default=None): """Column object :param name: Name of column :param data. Data list or tuple :param of_type: Type of column :param max_len: Max length of column :param auto_increment: Boolean value (default False) :param primary_key: Set this column like a primary key :param default: Default function for generate data """ self.name = name self._of_type = of_type if of_type is not None else object self.max_len = max_len self._data = [] if not data else list(data) self._default = default self._primary_key = primary_key self._auto_increment = auto_increment
@property def of_type(self): """Type of column""" return self._of_type @property def data(self): """List of values""" return self._data @property def auto_increment(self): """Auto-increment value""" return self._auto_increment @auto_increment.setter def auto_increment(self, value: bool): """Auto-increment value""" if value is not bool: raise TypeError('auto_increment must be a bool value') self._auto_increment = value @property def default(self): return self._default @property def primary_key(self): return bool(self._primary_key) @primary_key.setter def primary_key(self, value): self._primary_key = bool(value)
[docs] def append(self, data=None): """Appending data to column. If auto_increment is True, the value is incremented automatically. :param data: Any type of data :return: None """ if self.max_len and len(self._data) >= self.max_len: raise IndexError(f'maximum number of satisfied data: {self.max_len}') if not isinstance(data, self.of_type) and self.of_type is not None: raise TypeError(f'the data must be of the type {self.of_type} or NoneType') if self.auto_increment: if isinstance(self.of_type, (int, float, Counter)): try: last = self.data[-1] self._data.append(last + 1) except IndexError: self._data.append(1) elif self.default: if not callable(self.default): raise ValueError('default value must be callable without args') self._data.append(self.default()) else: self._data.append(data)
[docs] def pop(self, index=-1): """Deleting value :param index: Number of index :return: None """ self._data.pop(index)
def __getitem__(self, item): return self.data[item] def __setitem__(self, key, value): self._data[key] = value def __delitem__(self, key=-1): self.pop(key) def __iter__(self): return (item for item in self.data) def __len__(self): return len(self.data)
[docs] def __repr__(self): return f'<{self.__class__.__name__} object, name={self.name} type={self.of_type.__class__.__name__}>'
[docs] def __str__(self): return f'{self.data}'
# endregion # region Other objects Index = namedtuple('Index', ['name', 'table', 'column']) # endregion # region Functions
[docs]def column(func): """Decorator function to transform list or tuple object to Column object :param func: function to decorate :return: Column object """ @wraps(func) def inner(*args, **kwargs): data = func(*args, **kwargs) if not isinstance(data, (list, tuple)): raise ValueError(f"function {func.__name__} doesn't return a list or a tuple") return Column(name=func.__name__, data=data) return inner
# endregion