Source code for beat.core.library

#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

###################################################################################
#                                                                                 #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/               #
# Contact: beat.support@idiap.ch                                                  #
#                                                                                 #
# Redistribution and use in source and binary forms, with or without              #
# modification, are permitted provided that the following conditions are met:     #
#                                                                                 #
# 1. Redistributions of source code must retain the above copyright notice, this  #
# list of conditions and the following disclaimer.                                #
#                                                                                 #
# 2. Redistributions in binary form must reproduce the above copyright notice,    #
# this list of conditions and the following disclaimer in the documentation       #
# and/or other materials provided with the distribution.                          #
#                                                                                 #
# 3. Neither the name of the copyright holder nor the names of its contributors   #
# may be used to endorse or promote products derived from this software without   #
# specific prior written permission.                                              #
#                                                                                 #
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   #
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          #
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE    #
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL      #
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR      #
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER      #
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   #
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   #
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            #
#                                                                                 #
###################################################################################


"""
=======
library
=======

Validation for libraries

Forward imported from :py:mod:`beat.backend.python.library`:
:py:class:`beat.backend.python.library.Storage`
"""
import six

from beat.backend.python.library import Library as BackendLibrary
from beat.backend.python.library import Storage

from . import prototypes
from . import schema


[docs]class Library(BackendLibrary): """Librarys represent independent algorithm components within the platform. This class can only parse the meta-parameters of the library. The actual library is not directly treated by this class - only by the associated algorithms. Parameters: prefix (str): Establishes the prefix of your installation. data (:py:class:`object`, Optional): The piece of data representing the library. It must validate against the schema defined for libraries. If a string is passed, it is supposed to be a valid path to an library in the designated prefix area. If a tuple is passed (or a list), then we consider that the first element represents the library declaration, while the second, the code for the library (either in its source format or as a binary blob). If ``None`` is passed, loads our default prototype for libraries (source code will be in Python). library_cache (:py:class:`dict`, Optional): A dictionary mapping library names to loaded libraries. This parameter is optional and, if passed, may greatly speed-up library loading times as libraries that are already loaded may be re-used. Attributes: name (str): The library name description (str): The short description string, loaded from the JSON file if one was set. documentation (str): The full-length docstring for this object. storage (object): A simple object that provides information about file paths for this library libraries (dict): A mapping object defining other libraries this library needs to load so it can work properly. uses (dict): A mapping object defining the required library import name (keys) and the full-names (values). errors (list): A list containing errors found while loading this library. data (dict): The original data for this library, as loaded by our JSON decoder. code (str): The code that is associated with this library, loaded as a text (or binary) file. """ def __init__(self, prefix, data, library_cache=None): super(Library, self).__init__(prefix, data, library_cache) def _load(self, data, library_cache): """Loads the library""" self.errors = [] self.data = None self.code = None self._name = None self.storage = None self.libraries = {} # preloaded libraries code = None if data is None: # loads prototype and validates it data = None code = None elif isinstance(data, (tuple, list)): # user has passed individual info data, code = data # break down into two components if isinstance(data, six.string_types): # user has passed a file pointer # make sure to log this into the cache (avoids recursion) library_cache[data] = None self._name = data self.storage = Storage(self.prefix, self._name) if not self.storage.json.exists(): self.errors.append("Library declaration file not found: %s" % data) return data = self.storage.json.path # loads data from JSON declaration # At this point, `data' can be a dictionary or ``None`` if data is None: # loads the default declaration for an library self.data, self.errors = prototypes.load("library") assert not self.errors, "\n * %s" % "\n *".join(self.errors) # nosec else: # just assign it # this runs basic validation, including JSON loading if required self.data, self.errors = schema.validate("library", data) if self.errors: return # don't proceed with the rest of validation if self.storage is not None: # loading from the disk, check code if not self.storage.code.exists(): self.errors.append( "Library code not found: %s" % self.storage.code.path ) return else: code = self.storage.code.load() # At this point, `code' can be a string (or a binary blob) or ``None`` if code is None: # loads the default code for an library self.code = prototypes.binary_load("library.py") self.data["language"] = "python" else: # just assign it - notice that in this case, no language is set self.code = code if self.errors: return # don't proceed with the rest of validation # if no errors so far, make sense out of the library data self.data.setdefault("uses", {}) # now we check for consistence self._validate_required_libraries(library_cache) if self.errors: return # don't proceed with the rest of validation self._check_language_consistence() def _validate_required_libraries(self, library_cache): # all used libraries must be loadable; cannot use self as a library if self.uses is not None: for name, value in self.uses.items(): if value in library_cache: if library_cache[value] is None: self.errors.append( "recursion for library `%s' detected" % value ) continue self.libraries[value] = library_cache[value] else: self.libraries[value] = Library(self.prefix, value, library_cache) if not self.libraries[value].valid: self.errors.append( "referred library `%s' (%s) is not valid" % (self.libraries[value].name, name) ) def _check_language_consistence(self): # all used libraries must be programmed with the same language if self.language == "unknown": return # bail out on unknown language if self.uses is not None: for name, library in self.uses.items(): if library not in self.libraries: continue # invalid if self.libraries[library].language != self.language: self.errors.append( "language for used library `%s' (`%s') " "differs from current language for this library (`%s')" % (library, self.libraries[library].language, self.language) )