Module jumpscale.data.bcdb.models.base

Expand source code
from jumpscale.loader import j
from jumpscale.data.schema import Property
from jumpscale.data.types import Integer, JSObject
import json


class JSObjBase:
    def __init__(self, model):
        self.model = model

    def get_dict(self):
        return self.model.get_dict(self)

    def save(self):
        return self.model.save_obj(self)

    def __str__(self):
        return json.dumps(self.get_dict(), indent=4)


class ModelBase:
    _schema = ""
    _name = ""

    def __init__(self, bcdb):
        self.schema = self._load_schema()
        self.schema.props["id"] = Property()
        self.schema.props["id"].unique = True
        self.schema.props["id"].index = True
        self.schema.props["id"].type = Integer()
        self.schema.props["id"].name = "id"

        self.bcdb = bcdb
        self.name = self._name

    def _load_schema(self):
        return j.data.schema.parse_schema(self._schema)

    def create_obj(self, data):
        """Create a new object and assign a new id to it with the given data dict.
        Missing props are defaultly initialized.

        Args:
            data (dict): The data dict that is used to create the object.

        Returns:
            JSObjBase: The newly created
        """
        o = JSObjBase(self)
        data["id"] = self._incr_id()
        self.set_from_dict(o, data)
        return o

    def _assert_uniqueness(self, obj):
        """Checks that the object doesn't contain an already existing property that is marked as unique.

        Args:
            obj (JSObjBase): The JS Object.

        Raises:
            RuntimeError: If it contains a duplicate of a unique value.
        """
        for prop in self.schema.props.values():
            if prop.unique:
                dbobj = self.get_by(prop.name, getattr(obj, prop.name))
                if dbobj is not None and dbobj.id != obj.id:
                    raise RuntimeError(f"{prop.name} is unique. One already exists.")

    def save_obj(self, obj):
        """Saves the object to the db. It forwards the call to the bcdb client.

        Args:
            obj (JSObjBase): The object to be saved.
        """
        self._assert_uniqueness(obj)
        self.bcdb.save_obj(self, obj)

    def _incr_id(self):
        """Increment the id counter and returns the newly incremented unique id.

        Returns:
            int: The new id.
        """
        return self.bcdb.model_id_incr(self)

    def get_by(self, key, value):
        """Search for objects whose key equal value.
        1. It searches in the redis index if key is indexed.
        2. Else, It's searched for in the sqlite index if the key is indexed for range search.
        3. Else, All objects in the db belonging to the given model is scanned linearly to determine the matching object.

        Args:
            key (str): The model property that is checked for.
            val (value): The value.

        Raises:
            RuntimeError: If the key is not a part of the schema.

        Returns:
            JSObjBase or None: The matched object (o: o.key == val). None if none matched.
        """
        return self.bcdb.get_entry(self, key, value)

    def get_range(self, key, min, max):
        return self.bcdb.get_item_from_index_set(self, key, min, max)

    def get_pattern(self, key, pattern):
        """Searches for objects whose key matches the given pattern in this model. The key must be registered in the text index.

        Args:
            key (str): The model property that the pattern is searched for in.
            pattern (str): The pattern to be searched for.

        Notes:
            Currently sonic server matches for some patterns and doesn't for others.

        Raises:
            RuntimeError: If the key is not defined in the model.
            RuntimeError: If the key is not indexed for search

        Returns:
            list[JSObjBase]: List of matching objects (o: o.key matches pattern).
        """
        return self.bcdb.get_item_from_index_text(self, key, pattern)

    def get_dict(self, obj):
        """Extracts a dict with all attributes from obj.

        Args:
            obj (JSObjBase): The object that the data is extracted from.

        Returns:
            dict: The data dict.
        """
        d = {}
        for prop in self.schema.props.values():
            prop_name = prop.name
            d[prop_name] = getattr(obj, prop_name)
            if isinstance(prop.type, JSObject):
                d[prop_name] = d[prop_name].get_dict()
        return d

    def set_from_dict(self, o, d):
        """Sets the attributes in the object o to the data from the dict d.
        Default values is used if it's not present in d.

        Args:
            o (JSObjBase): The object that will be set.
            d (dict): The dict containing the object new data.

        Notes:
            Type checking needs modification.
            It was assumed to receive strings convertable to the defined property.

        Raises:
            ValueError: If a value in the dict doesn't conform to the type checking rules imposed by the model's schema.

        Returns:
            JSObjBase: The JSObject after setting its data from the dict d.
        """
        for prop in self.schema.props.values():
            prop_name = prop.name
            if prop_name in d:
                if not prop.type.check(d[prop_name]):
                    raise ValueError("Wrong form")
                if isinstance(prop.type, JSObject):
                    obj_model = self.bcdb.get_model_by_name(prop.defaultvalue)
                    setattr(o, prop_name, obj_model.load_obj_from_dict(d[prop_name]))
                else:
                    setattr(o, prop_name, prop.type.from_str(d[prop_name]))
            else:
                setattr(o, prop_name, prop.type.default)
        return o

    def load_obj_from_dict(self, d):
        """Creates a new object with its data extracted from the d.

        Args:
            d (dict): The dict containing the newly created object's attributes.

        Returns:
            JSObjBase: The newly created object.
        """
        o = JSObjBase(self)
        self.set_from_dict(o, d)
        return o

Classes

class JSObjBase (model)
Expand source code
class JSObjBase:
    def __init__(self, model):
        self.model = model

    def get_dict(self):
        return self.model.get_dict(self)

    def save(self):
        return self.model.save_obj(self)

    def __str__(self):
        return json.dumps(self.get_dict(), indent=4)

Methods

def get_dict(self)
Expand source code
def get_dict(self):
    return self.model.get_dict(self)
def save(self)
Expand source code
def save(self):
    return self.model.save_obj(self)
class ModelBase (bcdb)
Expand source code
class ModelBase:
    _schema = ""
    _name = ""

    def __init__(self, bcdb):
        self.schema = self._load_schema()
        self.schema.props["id"] = Property()
        self.schema.props["id"].unique = True
        self.schema.props["id"].index = True
        self.schema.props["id"].type = Integer()
        self.schema.props["id"].name = "id"

        self.bcdb = bcdb
        self.name = self._name

    def _load_schema(self):
        return j.data.schema.parse_schema(self._schema)

    def create_obj(self, data):
        """Create a new object and assign a new id to it with the given data dict.
        Missing props are defaultly initialized.

        Args:
            data (dict): The data dict that is used to create the object.

        Returns:
            JSObjBase: The newly created
        """
        o = JSObjBase(self)
        data["id"] = self._incr_id()
        self.set_from_dict(o, data)
        return o

    def _assert_uniqueness(self, obj):
        """Checks that the object doesn't contain an already existing property that is marked as unique.

        Args:
            obj (JSObjBase): The JS Object.

        Raises:
            RuntimeError: If it contains a duplicate of a unique value.
        """
        for prop in self.schema.props.values():
            if prop.unique:
                dbobj = self.get_by(prop.name, getattr(obj, prop.name))
                if dbobj is not None and dbobj.id != obj.id:
                    raise RuntimeError(f"{prop.name} is unique. One already exists.")

    def save_obj(self, obj):
        """Saves the object to the db. It forwards the call to the bcdb client.

        Args:
            obj (JSObjBase): The object to be saved.
        """
        self._assert_uniqueness(obj)
        self.bcdb.save_obj(self, obj)

    def _incr_id(self):
        """Increment the id counter and returns the newly incremented unique id.

        Returns:
            int: The new id.
        """
        return self.bcdb.model_id_incr(self)

    def get_by(self, key, value):
        """Search for objects whose key equal value.
        1. It searches in the redis index if key is indexed.
        2. Else, It's searched for in the sqlite index if the key is indexed for range search.
        3. Else, All objects in the db belonging to the given model is scanned linearly to determine the matching object.

        Args:
            key (str): The model property that is checked for.
            val (value): The value.

        Raises:
            RuntimeError: If the key is not a part of the schema.

        Returns:
            JSObjBase or None: The matched object (o: o.key == val). None if none matched.
        """
        return self.bcdb.get_entry(self, key, value)

    def get_range(self, key, min, max):
        return self.bcdb.get_item_from_index_set(self, key, min, max)

    def get_pattern(self, key, pattern):
        """Searches for objects whose key matches the given pattern in this model. The key must be registered in the text index.

        Args:
            key (str): The model property that the pattern is searched for in.
            pattern (str): The pattern to be searched for.

        Notes:
            Currently sonic server matches for some patterns and doesn't for others.

        Raises:
            RuntimeError: If the key is not defined in the model.
            RuntimeError: If the key is not indexed for search

        Returns:
            list[JSObjBase]: List of matching objects (o: o.key matches pattern).
        """
        return self.bcdb.get_item_from_index_text(self, key, pattern)

    def get_dict(self, obj):
        """Extracts a dict with all attributes from obj.

        Args:
            obj (JSObjBase): The object that the data is extracted from.

        Returns:
            dict: The data dict.
        """
        d = {}
        for prop in self.schema.props.values():
            prop_name = prop.name
            d[prop_name] = getattr(obj, prop_name)
            if isinstance(prop.type, JSObject):
                d[prop_name] = d[prop_name].get_dict()
        return d

    def set_from_dict(self, o, d):
        """Sets the attributes in the object o to the data from the dict d.
        Default values is used if it's not present in d.

        Args:
            o (JSObjBase): The object that will be set.
            d (dict): The dict containing the object new data.

        Notes:
            Type checking needs modification.
            It was assumed to receive strings convertable to the defined property.

        Raises:
            ValueError: If a value in the dict doesn't conform to the type checking rules imposed by the model's schema.

        Returns:
            JSObjBase: The JSObject after setting its data from the dict d.
        """
        for prop in self.schema.props.values():
            prop_name = prop.name
            if prop_name in d:
                if not prop.type.check(d[prop_name]):
                    raise ValueError("Wrong form")
                if isinstance(prop.type, JSObject):
                    obj_model = self.bcdb.get_model_by_name(prop.defaultvalue)
                    setattr(o, prop_name, obj_model.load_obj_from_dict(d[prop_name]))
                else:
                    setattr(o, prop_name, prop.type.from_str(d[prop_name]))
            else:
                setattr(o, prop_name, prop.type.default)
        return o

    def load_obj_from_dict(self, d):
        """Creates a new object with its data extracted from the d.

        Args:
            d (dict): The dict containing the newly created object's attributes.

        Returns:
            JSObjBase: The newly created object.
        """
        o = JSObjBase(self)
        self.set_from_dict(o, d)
        return o

Subclasses

Methods

def create_obj(self, data)

Create a new object and assign a new id to it with the given data dict. Missing props are defaultly initialized.

Args

data : dict
The data dict that is used to create the object.

Returns

JSObjBase
The newly created
Expand source code
def create_obj(self, data):
    """Create a new object and assign a new id to it with the given data dict.
    Missing props are defaultly initialized.

    Args:
        data (dict): The data dict that is used to create the object.

    Returns:
        JSObjBase: The newly created
    """
    o = JSObjBase(self)
    data["id"] = self._incr_id()
    self.set_from_dict(o, data)
    return o
def get_by(self, key, value)

Search for objects whose key equal value. 1. It searches in the redis index if key is indexed. 2. Else, It's searched for in the sqlite index if the key is indexed for range search. 3. Else, All objects in the db belonging to the given model is scanned linearly to determine the matching object.

Args

key : str
The model property that is checked for.
val : value
The value.

Raises

RuntimeError
If the key is not a part of the schema.

Returns

JSObjBase or None
The matched object (o: o.key == val). None if none matched.
Expand source code
def get_by(self, key, value):
    """Search for objects whose key equal value.
    1. It searches in the redis index if key is indexed.
    2. Else, It's searched for in the sqlite index if the key is indexed for range search.
    3. Else, All objects in the db belonging to the given model is scanned linearly to determine the matching object.

    Args:
        key (str): The model property that is checked for.
        val (value): The value.

    Raises:
        RuntimeError: If the key is not a part of the schema.

    Returns:
        JSObjBase or None: The matched object (o: o.key == val). None if none matched.
    """
    return self.bcdb.get_entry(self, key, value)
def get_dict(self, obj)

Extracts a dict with all attributes from obj.

Args

obj : JSObjBase
The object that the data is extracted from.

Returns

dict
The data dict.
Expand source code
def get_dict(self, obj):
    """Extracts a dict with all attributes from obj.

    Args:
        obj (JSObjBase): The object that the data is extracted from.

    Returns:
        dict: The data dict.
    """
    d = {}
    for prop in self.schema.props.values():
        prop_name = prop.name
        d[prop_name] = getattr(obj, prop_name)
        if isinstance(prop.type, JSObject):
            d[prop_name] = d[prop_name].get_dict()
    return d
def get_pattern(self, key, pattern)

Searches for objects whose key matches the given pattern in this model. The key must be registered in the text index.

Args

key : str
The model property that the pattern is searched for in.
pattern : str
The pattern to be searched for.

Notes

Currently sonic server matches for some patterns and doesn't for others.

Raises

RuntimeError
If the key is not defined in the model.
RuntimeError
If the key is not indexed for search

Returns

list[JSObjBase]
List of matching objects (o: o.key matches pattern).
Expand source code
def get_pattern(self, key, pattern):
    """Searches for objects whose key matches the given pattern in this model. The key must be registered in the text index.

    Args:
        key (str): The model property that the pattern is searched for in.
        pattern (str): The pattern to be searched for.

    Notes:
        Currently sonic server matches for some patterns and doesn't for others.

    Raises:
        RuntimeError: If the key is not defined in the model.
        RuntimeError: If the key is not indexed for search

    Returns:
        list[JSObjBase]: List of matching objects (o: o.key matches pattern).
    """
    return self.bcdb.get_item_from_index_text(self, key, pattern)
def get_range(self, key, min, max)
Expand source code
def get_range(self, key, min, max):
    return self.bcdb.get_item_from_index_set(self, key, min, max)
def load_obj_from_dict(self, d)

Creates a new object with its data extracted from the d.

Args

d : dict
The dict containing the newly created object's attributes.

Returns

JSObjBase
The newly created object.
Expand source code
def load_obj_from_dict(self, d):
    """Creates a new object with its data extracted from the d.

    Args:
        d (dict): The dict containing the newly created object's attributes.

    Returns:
        JSObjBase: The newly created object.
    """
    o = JSObjBase(self)
    self.set_from_dict(o, d)
    return o
def save_obj(self, obj)

Saves the object to the db. It forwards the call to the bcdb client.

Args

obj : JSObjBase
The object to be saved.
Expand source code
def save_obj(self, obj):
    """Saves the object to the db. It forwards the call to the bcdb client.

    Args:
        obj (JSObjBase): The object to be saved.
    """
    self._assert_uniqueness(obj)
    self.bcdb.save_obj(self, obj)
def set_from_dict(self, o, d)

Sets the attributes in the object o to the data from the dict d. Default values is used if it's not present in d.

Args

o : JSObjBase
The object that will be set.
d : dict
The dict containing the object new data.

Notes

Type checking needs modification. It was assumed to receive strings convertable to the defined property.

Raises

ValueError
If a value in the dict doesn't conform to the type checking rules imposed by the model's schema.

Returns

JSObjBase
The JSObject after setting its data from the dict d.
Expand source code
def set_from_dict(self, o, d):
    """Sets the attributes in the object o to the data from the dict d.
    Default values is used if it's not present in d.

    Args:
        o (JSObjBase): The object that will be set.
        d (dict): The dict containing the object new data.

    Notes:
        Type checking needs modification.
        It was assumed to receive strings convertable to the defined property.

    Raises:
        ValueError: If a value in the dict doesn't conform to the type checking rules imposed by the model's schema.

    Returns:
        JSObjBase: The JSObject after setting its data from the dict d.
    """
    for prop in self.schema.props.values():
        prop_name = prop.name
        if prop_name in d:
            if not prop.type.check(d[prop_name]):
                raise ValueError("Wrong form")
            if isinstance(prop.type, JSObject):
                obj_model = self.bcdb.get_model_by_name(prop.defaultvalue)
                setattr(o, prop_name, obj_model.load_obj_from_dict(d[prop_name]))
            else:
                setattr(o, prop_name, prop.type.from_str(d[prop_name]))
        else:
            setattr(o, prop_name, prop.type.default)
    return o