Source code for api.verification_library

"""
verification_library.py
====================================
Verification Library API
"""

import sys, logging, glob, json, inspect
from typing import Dict, List, Union

sys.path.append("..")
from constrain.library import *

library_schema = {
    "library_item_id": (int, str, float),
    "description_brief": str,
    "description_detailed": str,
    "description_index": list,
    "description_datapoints": dict,
    "description_assertions": list,
    "description_verification_type": str,
    "assertions_type": str,
}


[docs] class VerificationLibrary: def __init__(self, lib_path: str = None): """Instantiate a verification library class object and load specified library items as `self.lib_items`. Args: lib_path (str, optional): path to the verification library file or folder. If the path ends with `*.json`, then library items defined in the json file are loaded. If the path points to a directory, then library items in all jsons in this directory and its subdirectories are loaded. Library item need to have unique name defined in the json files and python files. Defaults to None. """ self.lib_items = {} self.lib_items_json_path = {} self.lib_items_python_path = {} # check argument if lib_path is None: logging.error( "'lib_path' was not provided when instantiating the Verificationlibrary class object!" ) return None if not isinstance(lib_path, str): logging.error( f"lib_path needs to be str of library file or foler path. It cannot be a {type(lib_path)}" ) return None # load lib lib_path = lib_path.strip() lib_files_json_dict = {} if lib_path.split(".")[-1] == "json": lib_items_files = glob.glob(lib_path) else: if lib_path[-1] != "/": lib_path = lib_path + "/" lib_items_files = glob.glob(f"{lib_path}**/*.json", recursive=True) if len(lib_items_files) == 0: logging.error(f"There is no json file in {lib_path}") return None for lib_items_file_path in lib_items_files: with open(lib_items_file_path) as lib_items_file: lib_files_json_dict[lib_items_file_path] = json.load(lib_items_file) # store items for k, v in lib_files_json_dict.items(): shared_keys = list(set(self.lib_items).intersection(v)) if len(shared_keys) > 0: logging.warning( f"{len(shared_keys)} repeated verification item names identified when loading: {shared_keys}" ) self.lib_items.update(v) for lib_name, lib_content in v.items(): self.lib_items_json_path[lib_name] = k if lib_name in globals().keys(): python_path = inspect.getfile(globals()[lib_name]) else: python_path = "Not Found" logging.warning( f"Python class file of loaded library item {lib_name} Not Found in the library" ) self.lib_items_python_path[lib_name] = python_path
[docs] def get_library_item(self, item_name: str) -> Dict: """Get the json definition and meta information of a specific library item. Args: item_name (str): Verification item name to get. Returns: Dict: Library item information with four specific keys: - `library_item_name`: unique str name of the library item. - `library_json`: library item json definition in the library json file. - `library_json_path`: path of the library json file that contains this library item. - `library_python_path`: path of the python file that contains the python implementation of this library item. """ item_name = item_name.strip() if not item_name in self.lib_items.keys(): logging.error(f"{item_name} is not in loaded library items.") return None item_dict = { "library_item_name": item_name, "library_definition": self.lib_items[item_name], "library_json_path": self.lib_items_json_path[item_name], "library_python_path": self.lib_items_python_path[item_name], } return item_dict
[docs] def validate_library(self, items: List[str] = None) -> Dict: """Check the validity of library items definition. This validity check includes checking the completeness of json specification (against library json schema) and Python verification class definition (against library class interface) and the match between the json and python implementation. Args: items: list of str, default []. Library items to validate. `items` must be filled with valid verification item(s). If not, an error occurs. Returns: Dict that contains validity information of library items. """ # check `items` type if not isinstance(items, list): logging.error(f"items needs to be list. It cannot be a {type(items)}.") return None # check if `items` is an empty list if not items: logging.error( f"items is an empty list. Please provide with verification item names." ) return None validity_info = {} for item in items: validity_info[item] = {} # verify the library.json file for lib_key in library_schema.keys(): # check if lib keys exist. "description_detailed" key is optional if lib_key not in ["description_detailed"] and not self.lib_items[ item ].get(lib_key): logging.error( f"{lib_key} doesn't exist. Please make sure to have {lib_key}." ) return None # check if key is in the correct type try: if not isinstance( self.lib_items[item][lib_key], library_schema[lib_key] ): logging.error( f"The type of `{lib_key}` key needs to be {str(library_schema[lib_key])}. It cannot be a {type(self.lib_items[item][lib_key])}." ) return None else: validity_info[item][lib_key] = type( self.lib_items[item][lib_key] ) except KeyError: # if `description_detailed` key doesn't exist, output a warning. validity_info[item][lib_key] = None logging.warning(f"{lib_key} doesn't exist.") # check if datapoints in library file and class are identical if ( list(self.lib_items[item]["description_datapoints"].keys()) != globals()[item].points ): logging.error( f"{item}'s points in the library file and class implementation are not identical." ) return None else: validity_info[item]["datapoints_match"] = True return validity_info
[docs] def get_library_items(self, items: List[str] = []) -> Union[List, None]: """Get the json definition and meta information of a list of specific library items. Args: items: list of str, default []. Library items to get. By default, get all library items loaded at instantiation. Returns: list of `Dict` with four specific keys: - `library_item_name`: unique str name of the library item. - `library_json`: library item json definition in the library json file. - `library_json_path`: path of the library json file that contains this library item. - `library_python_path`: path of the python file that contains the python implementation of this library item. """ # check `items` arg type if not isinstance(items, List): logging.error(f"items' type must be List. It can't be {type(items)}.") return None # cause an error when `items` arg is empty if not items: logging.error( f"items' arg is empty. Please provide with verification item(s)." ) return None # get each lib item's summary info item_list = [] for item in items: item_list.append(self.get_library_item(item)) return item_list
[docs] def get_applicable_library_items_by_datapoints( self, datapoints: List[str] = [] ) -> Dict: """Based on provided datapoints lists, identify potentially applicable library items from all loaded items. Use this function with caution as it 1) requires aligned data points naming across all library items; 2) does not check the topological relationships between datapoints. Args: datapoints: list of str datapoints names. Returns: Dict with keys being the library item names and values being the required datapoints for the corresponding keys. """ # check `datapoints` type if not isinstance(datapoints, List): logging.error( f"datapoints' type must be List. It can't be {type(datapoints)}." ) return None # check if `datapoints` is an empty list if not datapoints: logging.error( f"`datapoints' is an empty list. Please provide with datapoint names." ) return None # check the type of elements in `datapoints` for datapoint in datapoints: if not isinstance(datapoint, str): logging.error( f"element's type in the datapoints argument must be str. It can't be {type(datapoint)}." ) return None # check if applicable lib(s) exists with the datapoints arg applicable_lib_dict = {} for lib_item in self.lib_items.keys(): required_lib_datapoints = list( self.lib_items[lib_item]["description_datapoints"].keys() ) if set(required_lib_datapoints).issubset(set(datapoints)): applicable_lib_dict[lib_item] = required_lib_datapoints return applicable_lib_dict
[docs] def get_required_datapoints_by_library_items( self, datapoints: List[str] = [] ) -> Union[Dict, None]: """Summarize datapoints that need to be used to support specified library items. Use this function with caution as it 1) requires aligned data points naming across all library items; 2) does not check the topological relationships between datapoints. Args: items: list of str, default []. Library items to summarize datapoints from. By default, summarize all library items loaded at instantiation. Returns: Dict with keys being the datapoint name and values being a sub Dict with the following keys: - number_of_items_using_this_datapoint: int, number of library items that use this datapoint. - library_items_list: List, of library item names that use this datapoint. """ if not isinstance(datapoints, List): logging.error( f"The `datapoints` argument type must be List, but {type(datapoints)} type is provided." ) return None # check if `datapoints` is an empty list if not datapoints: logging.error( f"`datapoints' is an empty list. Please provide with datapoint names." ) return None req_datapionts_by_lib_items = {} for datapoint in datapoints: req_datapionts_by_lib_items[datapoint] = { "number_of_items_using_this_datapoint": 0, "library_items_list": [], } for item in self.lib_items.items(): if datapoint in item[1]["description_datapoints"].keys(): req_datapoint_dict = req_datapionts_by_lib_items[datapoint] req_datapoint_dict["number_of_items_using_this_datapoint"] += 1 req_datapoint_dict["library_items_list"].append(item[0]) return req_datapionts_by_lib_items