Commit 60b2387f authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Updated database API

- definition of context API
- implementation of inmemory and Redis backend engines
- implementation of unit tests for both engines
parent c8fa042e
Loading
Loading
Loading
Loading
+13 −10
Original line number Diff line number Diff line
import logging, os
from enum import Enum
from .inmemory.InMemoryDatabase import InMemoryDatabase
from .redis.RedisDatabase import RedisDatabase
from .api.Database import Database
from .engines.inmemory.InMemoryDatabaseEngine import InMemoryDatabaseEngine
from .engines.redis.RedisDatabaseEngine import RedisDatabaseEngine

LOGGER = logging.getLogger(__name__)

@@ -13,8 +14,8 @@ class DatabaseEngineEnum(Enum):
    #ETCD = 'etcd'

ENGINES = {
    DatabaseEngineEnum.INMEMORY.value: InMemoryDatabase,
    DatabaseEngineEnum.REDIS.value: RedisDatabase,
    DatabaseEngineEnum.INMEMORY.value: InMemoryDatabaseEngine,
    DatabaseEngineEnum.REDIS.value: RedisDatabaseEngine,
    #DatabaseEngineEnum.MONGO.value: MongoDatabase,
    #DatabaseEngineEnum.RETHINK.value: RethinkDatabase,
    #DatabaseEngineEnum.ETCD.value: EtcdDatabase,
@@ -22,10 +23,12 @@ ENGINES = {

DEFAULT_DB_ENGINE = DatabaseEngineEnum.INMEMORY

def get_database(engine=None, **parameters):
    if engine is None: engine = os.environ.get('DB_ENGINE', DEFAULT_DB_ENGINE)
    if(isinstance(engine, DatabaseEngineEnum)): engine = engine.value
    LOGGER.info('Selected Database Engine: {}'.format(engine))
def get_database(**settings) -> Database:
    engine = os.environ.get('DB_ENGINE', DEFAULT_DB_ENGINE)
    engine = settings.pop('DB_ENGINE', engine)
    if engine is None: raise Exception('Database Engine not specified')
    if isinstance(engine, DatabaseEngineEnum): engine = engine.value
    engine_class = ENGINES.get(engine)
    if engine_class is None: raise Exception('Unsupported Engine({})'.format(engine))
    return engine_class(**parameters)
    if engine_class is None: raise Exception('Unsupported DatabaseEngine({})'.format(engine))
    LOGGER.info('Selected Database Engine: {}'.format(engine))
    return Database(engine_class(**settings))
+38 −0
Original line number Diff line number Diff line
import logging
from typing import List
from ..engines._DatabaseEngine import _DatabaseEngine
from .context.Context import Context

LOGGER = logging.getLogger(__name__)

class Database:
    def __init__(self, database_engine : _DatabaseEngine):
        if not isinstance(database_engine, _DatabaseEngine):
            raise Exception('database_engine must inherit from _DatabaseEngine')
        self._database_engine = database_engine
        self._acquired = False
        self._owner_key = None

    def __enter__(self) -> '_DatabaseEngine':
        self._acquired, self._owner_key = self._database_engine.lock()
        if not self._acquired: raise Exception('Unable to acquire database lock')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._database_engine.unlock(self._owner_key)

    def clear_all(self, keep_keys=set()):
        LOGGER.info('Cleaning up...')
        keys = self._database_engine.keys()
        LOGGER.info('  keys before = {}'.format(str(keys)))
        for key in keys:
            if(key in keep_keys): continue
            self._database_engine.delete(key)
        LOGGER.info('  keys after  = {}'.format(str(self._database_engine.keys())))

    def dump(self) -> List[str]:
        entries = self._database_engine.dump()
        entries.sort()
        return ['[{:>4s}] {:100s} :: {}'.format(k_type, k_name, k_value) for k_name,k_type,k_value in entries]

    def context(self, context_uuid : str) -> Context: return Context(context_uuid, self._database_engine)
+45 −0
Original line number Diff line number Diff line
from typing import Dict
from ...engines._DatabaseEngine import _DatabaseEngine
from ..entity._Entity import _Entity
from ..entity.EntityAttributes import EntityAttributes
from ..entity.EntityCollection import EntityCollection
from .Keys import KEY_CONTEXT, KEY_TOPOLOGIES
from .Topology import Topology

VALIDATORS = {}

class Context(_Entity):
    def __init__(self, context_uuid : str, database_engine : _DatabaseEngine):
        self._database_engine = database_engine
        super().__init__(parent=self)
        self._context_uuid = context_uuid
        self._attributes = EntityAttributes(self, KEY_CONTEXT, validators=VALIDATORS)
        self._topologies = EntityCollection(self, KEY_TOPOLOGIES)

    @property
    def database_engine(self) -> _DatabaseEngine: return self._database_engine

    @property
    def parent(self) -> 'Context': return self

    @property
    def context(self) -> 'Context': return self

    @property
    def context_uuid(self) -> str: return self._context_uuid

    @property
    def attributes(self) -> EntityAttributes: return self._attributes

    @property
    def topologies(self) -> EntityCollection: return self._topologies

    def topology(self, topology_uuid : str) -> Topology: return Topology(topology_uuid, self)

    def create(self) -> 'Context': return self

    def delete(self):
        for topology_uuid in self.topologies.get(): self.topology(topology_uuid).delete()

    def dump(self) -> Dict:
        return {topology_uuid : self.topology(topology_uuid).dump() for topology_uuid in self.topologies.get()}
+42 −16
Original line number Diff line number Diff line
from typing import Dict
from .tools._Entity import _Entity
from .tools.EntityAttributes import EntityAttributes
from .tools.EntityCollection import EntityCollection
from .Keys import KEY_DEVICE, KEY_DEVICE_ENDPOINTS
from __future__ import annotations
from typing import TYPE_CHECKING, Dict
from ..entity._Entity import _Entity
from ..entity.EntityAttributes import EntityAttributes
from ..entity.EntityCollection import EntityCollection
from .Endpoint import Endpoint
from .Keys import KEY_DEVICE, KEY_DEVICE_ENDPOINTS
from .OperationalStatus import OperationalStatus, to_operationalstatus_enum

if TYPE_CHECKING:
    from .Context import Context
    from .Topology import Topology

VALIDATORS = {
    'device_type': lambda v: v is None or isinstance(v, str),
    'device_config': lambda v: v is None or isinstance(v, str),
@@ -20,16 +25,36 @@ TRANSCODERS = {
}

class Device(_Entity):
    def __init__(self, device_uuid : str, parent : 'Topology'): # type: ignore
        super().__init__(device_uuid, parent=parent)
        self.device_uuid = self.get_uuid()
        self.topology_uuid = self._parent.get_uuid()
        self.context_uuid = self._parent._parent.get_uuid()
        self.attributes = EntityAttributes(self, KEY_DEVICE, VALIDATORS, transcoders=TRANSCODERS)
        self.endpoints = EntityCollection(self, KEY_DEVICE_ENDPOINTS)
    def __init__(self, device_uuid : str, parent : 'Topology'):
        super().__init__(parent=parent)
        self._device_uuid = device_uuid
        self._topology_uuid = self._parent.topology_uuid
        self._context_uuid = self._parent.context_uuid
        self._attributes = EntityAttributes(self, KEY_DEVICE, VALIDATORS, transcoders=TRANSCODERS)
        self._endpoints = EntityCollection(self, KEY_DEVICE_ENDPOINTS)

    @property
    def parent(self) -> 'Topology': return self._parent

    @property
    def context(self) -> 'Context': return self._parent.context

    @property
    def context_uuid(self) -> str: return self.context.context_uuid

    @property
    def topology_uuid(self) -> str: return self.parent.topology_uuid

    @property
    def device_uuid(self) -> str: return self._device_uuid

    @property
    def attributes(self) -> EntityAttributes: return self._attributes

    @property
    def endpoints(self) -> EntityCollection: return self._endpoints

    def endpoint(self, endpoint_uuid : str) -> Endpoint:
        return Endpoint(endpoint_uuid, self)
    def endpoint(self, endpoint_uuid : str) -> Endpoint: return Endpoint(endpoint_uuid, self)

    def create(self, type : str, config : str, operational_status : OperationalStatus) -> 'Device':
        self.update(update_attributes={
@@ -37,7 +62,7 @@ class Device(_Entity):
            'device_config': config,
            'device_operational_status': operational_status,
        })
        self._parent.devices.add(self.get_uuid())
        self.parent.devices.add(self.device_uuid)
        return self

    def update(self, update_attributes={}, remove_attributes=[]) -> 'Device':
@@ -45,9 +70,10 @@ class Device(_Entity):
        return self

    def delete(self) -> None:
        for endpoint_uuid in self.endpoints.get(): self.endpoint(endpoint_uuid).delete()
        remove_attributes = ['device_type', 'device_config', 'device_operational_status']
        self.update(remove_attributes=remove_attributes)
        self._parent.devices.delete(self.get_uuid())
        self.parent.devices.delete(self.device_uuid)

    def dump(self) -> Dict:
        attributes = self.attributes.get()
Loading