#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
# vim: se ts=4 et syn=python:
# created by: matteo.guadrini
# core -- 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/>.
"""Module that contains the core objects."""
# region imports
from abc import ABC, abstractmethod
from .exception import *
# endregion
# region global variable
API_NAME = 'nosqlapi'
__all__ = ['Connection', 'Selector', 'Session', 'Response', 'Batch']
# endregion
# region classes
[docs]class Connection(ABC):
"""Server connection abstract class
The abstract class :class:`Connection` is used to create connection-type classes that will allow you to work
directly on the layer at the database level.
"""
[docs] def __init__(self,
host=None,
user=None,
password=None,
database=None,
port=None,
bind_address=None,
read_timeout=None,
write_timeout=None,
ssl=None,
ssl_ca=None,
ssl_cert=None,
tls=None,
ssl_key=None,
ssl_verify_cert=None,
max_allowed_packet=None
):
"""Instantiate Connection object
:param host: Name of host that contains database
:param user: Username for connect to the host
:param password: Password for connect to the host
:param database: Name of database
:param port: Tcp port
:param bind_address: Hostname or an IP address for multiple network interfaces
:param read_timeout: Timeout for reading from the connection in seconds
:param write_timeout: Timeout for writing from the connection in seconds
:param ssl: Ssl connection established
:param ssl_ca: Ssl CA file specified
:param ssl_cert: Ssl certificate file specified
:param tls: Tls connection established
:param ssl_key: Ssl private key file specified
:param ssl_verify_cert: Verify certificate file
:param max_allowed_packet: Max size of packet sent to server in bytes
"""
self.host = host
self.user = user
self.password = password
self.database = database
self.port = port
self.bind_address = bind_address
self.read_timeout = read_timeout
self.write_timeout = write_timeout
self.ssl = ssl
self.ssl_ca = ssl_ca
self.ssl_cert = ssl_cert
self.tls = tls
self.ssl_key = ssl_key
self.ssl_verify_cert = ssl_verify_cert
self.max_allowed_packet = max_allowed_packet
self._connected = False
@property
def connected(self):
"""Boolean representing the database connection
:return: bool
"""
return bool(self._connected)
[docs] @abstractmethod
def close(self, *args, **kwargs):
"""Close connection
:return: None
"""
pass
[docs] @abstractmethod
def connect(self, *args, **kwargs):
"""Connect database server
:return: Session object
"""
pass
[docs] @abstractmethod
def create_database(self, *args, **kwargs):
"""Create new database on server
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def has_database(self, *args, **kwargs):
"""Check if database exists
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def delete_database(self, *args, **kwargs):
"""Delete database on server
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def databases(self, *args, **kwargs):
"""Get all databases
:return: Union[tuple, list, Response]
"""
pass
[docs] @abstractmethod
def show_database(self, *args, **kwargs):
"""Show a database information
:return : Union[Any, Response]
"""
pass
[docs] def __repr__(self):
return f"<{API_NAME} {self.__class__.__name__} object>"
[docs] def __str__(self):
return f"host={self.host}, database={self.database}, connected={self.connected}"
def __bool__(self):
return True if self.connected else False
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
[docs]class Selector(ABC):
"""Selector abstract class
The abstract class :class:`Selector` is used to create selector-type classes, which represent a query in the
specific language of the database.
"""
[docs] def __init__(self, selector=None, fields=None, partition=None, condition=None, order=None, limit=None):
"""Instantiate Selector object
:param selector: Selector part of the query string
:param fields: Return fields
:param partition: Partition or collection of data
:param condition: Condition of query
:param order: Order by specific selector
:param limit: Limit result
"""
self.selector = selector
self.fields = fields
self.partition = partition
self.condition = condition
self.order = order
self.limit = limit
@property
def selector(self):
"""Value than you want search"""
return self._selector
@selector.setter
def selector(self, value):
"""Value than you want search"""
self._selector = value
@property
def fields(self):
"""Key that returned from find operations"""
return self._fields
@fields.setter
def fields(self, value):
"""Key that returned from find operations"""
self._fields = value
@property
def partition(self):
"""The name of partition or collection in a database"""
return self._partition
@partition.setter
def partition(self, value):
"""The name of partition or collection in a database"""
self._partition = value
@property
def condition(self):
"""Other condition to apply a selectors"""
return self._condition
@condition.setter
def condition(self, value):
"""Other condition to apply a selectors"""
self._condition = value
@property
def order(self):
"""Order returned from find operations"""
return self._order
@order.setter
def order(self, value):
"""Order returned from find operations"""
self._order = value
@property
def limit(self):
"""Limit number of objects returned from find operations"""
return self._limit
@limit.setter
def limit(self, value):
"""Limit number of objects returned from find operations"""
self._limit = value
[docs] @abstractmethod
def build(self, *args, **kwargs):
"""Build string query selector
:return: string
"""
pass
[docs] def __repr__(self):
return f"<{API_NAME} {self.__class__.__name__} object>"
[docs] def __str__(self):
return self.build()
def __bool__(self):
if self.selector:
return True
[docs]class Session(ABC):
"""Server session abstract class
The abstract class :class:`Session` is used to create session-type classes that will allow you to work
directly on the layer at the data level.
"""
[docs] def __init__(self, connection, database=None):
"""Instantiate Session object
:param connection: Connection object or other object to serve connection
:param database: database name
"""
self._item_count = 0
self._description = ()
self._database = database
self._connection = connection
@property
def database(self):
"""Name of database in current session"""
return self._database
@property
def connection(self):
"""Connection of server in current session"""
return self._connection
@property
@abstractmethod
def item_count(self):
"""Number of item returned from latest CRUD operation"""
return self._item_count
@property
@abstractmethod
def description(self):
"""Contains the session parameters"""
return self._description
@property
@abstractmethod
def acl(self):
"""Access Control List in the current session"""
pass
@property
@abstractmethod
def indexes(self):
"""Name of indexes of the current database"""
pass
[docs] @abstractmethod
def get(self, *args, **kwargs):
"""Get one or more value
:return: Union[tuple, Response]
"""
pass
[docs] @abstractmethod
def insert(self, *args, **kwargs):
"""Insert one value
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def insert_many(self, *args, **kwargs):
"""Insert one or more value
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def update(self, *args, **kwargs):
"""Update one value
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def update_many(self, *args, **kwargs):
"""Update one or more value
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def delete(self, *args, **kwargs):
"""Delete one value
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def close(self, *args, **kwargs):
"""Close session
:return: None
"""
pass
[docs] @abstractmethod
def find(self, *args, **kwargs):
"""Find data
:return: Union[tuple, Response]
"""
pass
[docs] @abstractmethod
def grant(self, *args, **kwargs):
"""Grant users ACLs
:return: Union[Any, Response]
"""
pass
[docs] @abstractmethod
def revoke(self, *args, **kwargs):
"""Revoke users ACLs
:return: Union[Any, Response]
"""
pass
[docs] @abstractmethod
def new_user(self, *args, **kwargs):
"""Create new user
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def set_user(self, *args, **kwargs):
"""Modify exist user
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def delete_user(self, *args, **kwargs):
"""Delete exist user
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def add_index(self, *args, **kwargs):
"""Add index to database
:return: Union[bool, Response]
"""
pass
[docs] @abstractmethod
def delete_index(self, *args, **kwargs):
"""Delete index to database
:return: Union[bool, Response]
"""
pass
[docs] @staticmethod
def call(batch, *args, **kwargs):
"""Call a batch
:return: Union[Any, Response]
"""
if not hasattr(batch, 'execute'):
raise SessionError('batch object must implements an "execute" method.')
return batch.execute(*args, **kwargs)
[docs] def __repr__(self):
return f"<{API_NAME} {self.__class__.__name__} object>"
[docs] def __str__(self):
return f"connection=({self.connection}), description={self.description}"
def __bool__(self):
if self.connection:
return True
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
[docs]class Response(ABC):
"""Server response abstract class
The abstract class :class:`Response` is used to create response-type classes that represent response data to
an operation.
"""
__slots__ = ('_data', '_code', '_header', '_error')
[docs] def __init__(self, data, code=None, header=None, error=None):
"""Instantiate Response object
:param data: Data from operation
:param code: Exit code of operation
:param header: Header of operation
:param error: Error string or Exception class
"""
self._data = data
self._code = code
self._header = header
self._error = error
@property
def data(self):
"""The effective data than returned"""
return self._data
@property
def code(self):
"""Number code of error or success in an operation"""
return self._code
@property
def header(self):
"""Information (header) of an operation"""
return self._header
@property
def error(self):
"""Error of an operation"""
return self._error
@property
def dict(self):
"""dict format for Response object"""
return {'data': self._data,
'code': self._code,
'header': self._header,
'error': self._error}
[docs] def throw(self):
"""Raise or throw exception from error property
:return: Exception
"""
if isinstance(self.error, Exception):
raise self.error
else:
raise Error(self.error)
def __bool__(self):
if self._error:
return False
if self._data:
return True
[docs] def __str__(self):
return str(self.data)
[docs] def __repr__(self):
return f"<{API_NAME} {self.__class__.__name__} object>"
def __len__(self):
return self.data.__len__()
def __contains__(self, item):
return True if item in self.data else False
def __getitem__(self, item):
return self.data[item]
[docs]class Batch(ABC):
"""Batch abstract class
The abstract class :class:`Batch` is used to create batch-type classes that represent a collection of
operations to be performed at the same time.
"""
[docs] def __init__(self, batch, session=None):
"""Instantiate Batch object
:param batch: List of commands
:param session: Session object or other compliant object
"""
self._session = session
self._batch = batch
@property
def session(self):
"""Session object"""
return self._session
@session.setter
def session(self, value):
"""Session object"""
if not hasattr(value, 'call'):
raise SessionError(f'{value} not contains a valid session')
self._session = value
@property
def batch(self):
"""String batch operation"""
return self._batch
@batch.setter
def batch(self, value):
"""String batch operation"""
self._batch = value
[docs] @abstractmethod
def execute(self, *args, **kwargs):
"""Execute some batch statement
:return: Union[tuple, Response]
"""
pass
def __getitem__(self, item):
return self._batch[item]
def __setitem__(self, key, value):
self._batch[key] = value
def __delitem__(self, key):
del self._batch[key]
[docs] def __repr__(self):
return f"<{API_NAME} {self.__class__.__name__} object>"
[docs] def __str__(self):
return str(self.batch)
def __bool__(self):
if self.batch:
return True
# endregion