Source code for serialbox.savepoint

#!/usr/bin/python3
# -*- coding: utf-8 -*-
##===-----------------------------------------------------------------------------*- Python -*-===##
##
##                                   S E R I A L B O X
##
## This file is distributed under terms of BSD license.
## See LICENSE.txt for more information.
##
##===------------------------------------------------------------------------------------------===##
##
## This file contains the savepoint implementation of the Python Interface.
##
##===------------------------------------------------------------------------------------------===##

from abc import ABCMeta
from ctypes import c_char_p, c_void_p, c_int, Structure, POINTER, c_size_t

from .common import get_library, to_c_string
from .error import invoke, SerialboxError
from .metainfomap import MetainfoMap, MetainfoImpl
from .type import StringTypes
from .util import levenshtein

lib = get_library()


class SavepointImpl(Structure):
    """ Mapping of serialboxSavepoint_t """
    _fields_ = [("impl", c_void_p), ("ownsData", c_int)]


def register_library(library):
    library.serialboxSavepointCreate.argtypes = [c_char_p]
    library.serialboxSavepointCreate.restype = POINTER(SavepointImpl)

    library.serialboxSavepointCreateFromSavepoint.argtypes = [POINTER(SavepointImpl)]
    library.serialboxSavepointCreateFromSavepoint.restype = POINTER(SavepointImpl)

    library.serialboxSavepointDestroy.argtypes = [POINTER(SavepointImpl)]
    library.serialboxSavepointDestroy.restype = None

    library.serialboxSavepointGetName.argtypes = [POINTER(SavepointImpl)]
    library.serialboxSavepointGetName.restype = c_char_p

    library.serialboxSavepointEqual.argtypes = [POINTER(SavepointImpl), POINTER(SavepointImpl)]
    library.serialboxSavepointEqual.restype = c_int

    library.serialboxSavepointToString.argtypes = [POINTER(SavepointImpl)]
    library.serialboxSavepointToString.restype = c_char_p

    library.serialboxSavepointHash.argtypes = [POINTER(SavepointImpl)]
    library.serialboxSavepointHash.restype = c_size_t

    library.serialboxSavepointGetMetainfo.argtypes = [POINTER(SavepointImpl)]
    library.serialboxSavepointGetMetainfo.restype = POINTER(MetainfoImpl)


# ===--------------------------------------------------------------------------------------------===
#   Savepoint
# ==---------------------------------------------------------------------------------------------===

[docs]class Savepoint(object): """Savepoints are used within the :class:`Serializer <serialbox.Serializer>` to discriminate fields at different points in time. Savepoints in the :class:`Serializer <serialbox.Serializer>` are unique and primarily identified by their :attr:`name <serialbox.Savepoint.name>` >>> savepoint = Savepoint('savepoint') >>> savepoint.name 'savepoint' >>> and further distinguished by their :attr:`metainfo <serialbox.Savepoint.metainfo>` >>> savepoint = Savepoint('savepoint', {'key': 5}) >>> savepoint.metainfo <MetainfoMap {"key": 5}> >>> """
[docs] def __init__(self, name, metainfo=None, impl=None): """Initialize the Savepoint. This method prepares the Savepoint for usage and gives a name, which is the only required information for the savepoint to be usable. Meta-information can be added after the initialization has been performed. :param str name: Name of the savepoint :param dict metainfo: {Key:value} pair dictionary used for initializing the meta-information of the Savepont :param SavepointImpl impl: Directly set the implementation pointer [internal use] :raises serialbox.SerialboxError: if Savepoint could not be initialized """ if impl: self.__savepoint = impl else: namestr = to_c_string(name)[0] self.__savepoint = invoke(lib.serialboxSavepointCreate, namestr) if metainfo: if isinstance(metainfo, MetainfoMap): metainfo = metainfo.to_dict() metainfomap = self.metainfo for key, value in metainfo.items(): metainfomap.insert(key, value)
@property def name(self): """Name of the Savepoint. >>> s = Savepoint('savepoint') >>> s.name 'savepoint' >>> :return str: Name of the savepoint :rtype: str """ return invoke(lib.serialboxSavepointGetName, self.__savepoint).decode() @property def metainfo(self): """Refrence to the meta-information of the Savepoint. >>> s = Savepoint('savepoint', {'key': 5}) >>> s.metainfo['key'] 5 >>> type(s.metainfo) <class 'serialbox.metainfomap.MetainfoMap'> >>> s.metainfo.insert('key2', 'str') >>> s <MetainfoMap {"key": 5, "key2": str}> >>> :return: Refrence to the meta-information map :rtype: :class:`MetainfoMap <serialbox.MetainfoMap>` """ return MetainfoMap(impl=invoke(lib.serialboxSavepointGetMetainfo, self.__savepoint))
[docs] def clone(self): """Clone the Savepoint by performing a deepcopy. >>> s = Savepoint('savepoint', {'key': 5}) >>> s_clone = s.clone() >>> s.metainfo.clear() >>> s_clone <Savepoint sp {"key": 5}> >>> :return: Clone of the savepoint :rtype: Savepoint """ return Savepoint('', impl=invoke(lib.serialboxSavepointCreateFromSavepoint, self.__savepoint))
[docs] def __eq__(self, other): """Test for equality. Savepoints compare equal if their :attr:`names <serialbox.Savepoint.name>` and :attr:`metainfos <serialbox.Savepoint.metainfo>` compare equal. >>> s1 = Savepoint('savepoint', {'key': 'str'}) >>> s2 = Savepoint('savepoint', {'key': 5}) >>> s1 == s2 False >>> :return: `True` if self == other, `False` otherwise :rtype: bool """ return bool(invoke(lib.serialboxSavepointEqual, self.__savepoint, other.__savepoint))
[docs] def __ne__(self, other): """Test for inequality. Savepoints compare equal if their :attr:`names <serialbox.Savepoint.name>` and :attr:`metainfos <serialbox.Savepoint.metainfo>` compare equal. >>> s1 = Savepoint('savepoint', {'key': 'str'}) >>> s2 = Savepoint('savepoint', {'key': 5}) >>> s1 != s2 True >>> :return: `True` if self != other, `False` otherwise :rtype: bool """ return not self.__eq__(other)
def impl(self): return self.__savepoint def __del__(self): invoke(lib.serialboxSavepointDestroy, self.__savepoint) def __repr__(self): return '<Savepoint {0}>'.format(self.__str__()) def __str__(self): return invoke(lib.serialboxSavepointToString, self.__savepoint).decode() def __hash__(self): return invoke(lib.serialboxSavepointHash, self.__savepoint)
# ===--------------------------------------------------------------------------------------------=== # SavepointCollection # ==---------------------------------------------------------------------------------------------===
[docs]class SavepointCollection(object, metaclass=ABCMeta): """Collection of savepoints. A collection can be obtained by using the :attr:`savepoint <serialbox.Serializer.savepoint>` attribute of the :class:`Serializer <serialbox.Serializer>`. >>> ser = Serializer(OpenModeKind.Write, '.', 'field') >>> ser.register_savepoint(Savepoint('s1')) >>> ser.register_savepoint(Savepoint('s2')) >>> isinstance(ser.savepoint, SavepointCollection) True >>> ser.savpoint.savepoints() [<Savepoint s1 {}>, <Savepoint s2 {}>] >>> ser.savepoint.as_savepoint() Traceback (most recent call last): File "<stdin>", line 1, in ? File "savepoint.py", line 227, in as_savepoint raise SerialboxError(errstr) serialbox.error.SerialboxError: Savepoint is ambiguous. Candidates are: s1 {} s2 {} >>> """
[docs] def savepoints(self): """ Get the list of savepoints in this collection. The savepoints are ordered in the way they were inserted. :return: List of savepoints in the collection. :rtype: :class:`list` [:class:`Savepoint <serialbox.Savepoint>`] """ raise NotImplementedError()
[docs] def as_savepoint(self): """ Return the unique savepoint in the list or raise an :class:`SerialboxError <serialbox.SerialboxError>` if the list has more than 1 element. :return: Unique savepoint in this collection. :rtype: Savepoint :raises serialbox.SerialboxError: if list has more than one Savepoint """ num_savepoints = len(self.savepoints()) if num_savepoints == 1: return self.savepoints()[0] if num_savepoints > 1: errstr = "Savepoint is ambiguous. Candidates are:\n" for sp in self.savepoints(): errstr += " {0}\n".format(str(sp)) raise SerialboxError(errstr) else: raise SerialboxError("SavepointCollection is empty")
def __str__(self): s = "[" for sp in self.savepoints(): s += sp.__str__() + ", " return s[:-2] + "]" def __repr__(self): return '<SavepointCollection {0}>'.format(self.__str__())
def transformed_equal(name, key): """ Return True if ``name`` can be mapped to the transformed ``key`` such that ``key`` is a valid python identifier. The following transformation of ``key`` will be considered: ' ' ==> '_' '-' ==> '_' '.' ==> '_' '[0-9]' ==> _[0-9] """ key_transformed = key.replace(' ', '_').replace('-', '_').replace('.', '_') if key_transformed[0].isdigit(): key_transformed = '_' + key_transformed return key_transformed == name class SavepointTopCollection(SavepointCollection): """ Collection of all savepoints. """ def __init__(self, savepoint_list): self.__savepoint_list = savepoint_list def savepoints(self): return self.__savepoint_list def __make_savepoint_collection(self, name, match_exact=False): savepoint_list = [] for sp in self.__savepoint_list: sp_name = sp.name if name == sp_name: savepoint_list += [sp] elif not match_exact and transformed_equal(name, sp_name): savepoint_list += [sp] if not savepoint_list: errstr = "savepoint with name '%s' does not exist" % name # Make a suggestion if possible dist = [] for sp in self.__savepoint_list: dist += [levenshtein(name, sp.name)] if min(dist) <= 3: errstr += ", did you mean '%s'?" % self.__savepoint_list[dist.index(min(dist))].name raise SerialboxError(errstr) return SavepointNamedCollection(savepoint_list, None) def __getattr__(self, name): """ Access a collection of savepoints identified by `name` :param name: Name of the savepoint :type name: str :return: Collection of savepoints sharing the same `name` :rtype: SavepointNamedCollection """ return self.__make_savepoint_collection(name, False) def __getitem__(self, index): """ Access a collection of savepoints identified by `index` If `index` is an integer (`isinstance(index, int`), the method returns the unique Savepoint at poisition `index` in the savepoint list. Otherwise, :param index: Name or index of the savepoint :type index: str, int :return: Collection of savepoints sharing the same ``name`` or unique savepoint. :rtype: SavepointNamedCollection, Savepoint """ if isinstance(index, int): return self.__savepoint_list[index] return self.__make_savepoint_collection(index, True) class SavepointNamedCollection(SavepointCollection): """ Collection of Savepoints which all share the same `name`. """ def __init__(self, savepoint_list, prev_key): self.__savepoint_list = savepoint_list self.__prev_key = prev_key def savepoints(self): return self.__savepoint_list def __make_named_savepoint_collection(self, key, match_exact=False): savepoint_list = [] for sp in self.__savepoint_list: # Exact match if sp.metainfo.has_key(key): savepoint_list += [sp] if not savepoint_list: # Try a little harder ... we iterate now over all keys of the savepoints in the # collection. keys = [] if not match_exact: for sp in self.__savepoint_list: sp_keys = sp.metainfo.to_dict() for k in sp_keys: if transformed_equal(key, k): keys += [k] savepoint_list += [sp] # At his point we have to give up.. but not before we make a suggestion ;) if not savepoint_list: errstr = "no savepoint named '%s' has meta-info with key '%s'" % ( self.__savepoint_list[0].name, key) raise SerialboxError(errstr) # If we used match_exact=False and matched for example for key 'key_1': 'key 1' and # 'key-1', we just abort as there is no point to handle this case ... if keys.count(keys[0]) != len(keys): errstr = "ambiguous match for key '%s' for savepoint with name '%s'" % ( key, self.__savepoint_list[0].name) errstr += "Found matches:\n" for k in keys: errstr += " %s\n" % k raise SerialboxError(errstr) key = keys[0] return SavepointNamedCollection(savepoint_list, key) def __getattr__(self, key): return self.__make_named_savepoint_collection(key, False) def __getitem__(self, index): # # If `self.__prev_key` is not None, we have a query of the form # `serializer.savepoint.key[1]` meaning we access the meta-info key=value pair with # key=self.__prev_key and value=index. Otherwise, we have a query of the form # `serializer.savepoint['key1']`. # if self.__prev_key: savepoint_list = [] # Check if key=value pair exists for sp in self.__savepoint_list: if sp.metainfo[self.__prev_key] == index: savepoint_list += [sp] # Nothing found.. list the available savepoints and raise if not savepoint_list: errstr = "no savepoint named '%s' has meta-info: {\"%s\": %s}. Candidates are:\n" % ( self.__savepoint_list[0].name, self.__prev_key, index) for sp in self.savepoints(): errstr += " {0}\n".format(str(sp)) raise SerialboxError(errstr) return SavepointNamedCollection(savepoint_list, None) else: if not type(index) in StringTypes: raise SerialboxError("expected string in query for meta-info of Savepoint '%s'" % self.__savepoint_list[0].name) return self.__make_named_savepoint_collection(index, True) register_library(lib)