def fetch(self, dataset): """ Reads the dataset sub-objects from database Retrieves the list of Timeseries :param dataset: Dataset object :type dataset: Dataset :returns: the list of Timeseries composing the Dataset :rtype: list of Timeseries :raises TypeError: if name is not a Dataset :raises IkatsNotFoundError: if dataset not found in database """ check_type(value=dataset, allowed_types=Dataset, var_name="dataset", raise_exception=True) result = self.dm_client.dataset_read(dataset.name) ts = [ Timeseries(tsuid=x['tsuid'], fid=x['funcId'], api=self.api) for x in result.get('ts_list', []) ] return ts
def __init__(self, api, json_data=None): """ Constructor :param api: see IkatsObject :param json_data: information provided by catalog to fill this input/parameter/output :type api: IkatsAPI :type json_data: dict """ self.__desc = None self.__domain = None self.__label = None self.__name = None self.__order_index = None self.__dtype = None self.__default_value = None self.__rid = None self.__api = None self.api = api if json_data is not None: check_type(value=json_data, allowed_types=dict, var_name="json_data", raise_exception=True) self.desc = json_data.get("description", None) self.domain = json_data.get("domain", None) self.label = json_data.get("label", None) self.name = json_data.get("name", None) self.order_index = json_data.get("order_index", None) self.dtype = json_data.get("type", None) self.default_value = json_data.get("default_values", None)
def delete(self, name, raise_exception=True): """ Delete a table Returns a boolean status of the action (True means "OK", False means "errors occurred") :param name: the name of the table to delete :param raise_exception: Indicates if exceptions shall be raised (True, default) or not (False) :type name: str :type raise_exception: bool :returns: the status of deletion (True=deleted, False otherwise) :rtype: bool :raises IkatsNotFoundError: if table not found in database """ check_type(value=name, allowed_types=str, var_name="name", raise_exception=True) check_type(value=raise_exception, allowed_types=bool, var_name="raise_exception", raise_exception=True) try: self.dm_client.table_delete(name=name) except IkatsException: if raise_exception: raise return False return True
def get_type(self, name): """ Get the metadata type if present in local cache. If cache is empty, fetch the data from database :param name: name of the metadata to get :type name: str :returns: the type of the value :rtype: DTYPE :raises IkatsNotFoundError: if metadata doesn't exist """ # Input check check_type(value=name, allowed_types=str, var_name="name", raise_exception=True) # Update metadata if empty if self.__data is None: self.fetch() # A metadata marked as 'deleted' shall not be returned if name not in self.__data or self.__data[name]["deleted"]: raise IkatsNotFoundError("Metadata '%s' not defined" % name) return self.__data[name]["dtype"]
def get(self, name): """ Reads the dataset information from database Retrieves description and list of Timeseries :param name: Dataset name :type name: str :returns: the retrieved Dataset object with Timeseries list filled :rtype: Dataset :raises TypeError: if *name* is not a Dataset :raises IkatsNotFoundError: if dataset not found in database """ check_type(value=name, allowed_types=str, var_name="name", raise_exception=True) result = self.dm_client.dataset_read(name) ts = [ Timeseries(tsuid=x['tsuid'], fid=x['funcId'], api=self.api) for x in result.get('ts_list', []) ] description = result.get("description", "") return Dataset(api=self.api, name=name, desc=description, ts=ts)
def is_json_valid(data, raise_exception=True): """ Check if the provided JSON (as a dict) contains all necessary information to be considered valid :param data: the JSON as dict :param raise_exception: Indicates if exceptions shall be raised (True, default) or not (False) :type data: dict :type raise_exception: bool :return: the check status :rtype: bool :raises SchemaError: if JSON is invalid """ check_type(value=data, allowed_types=dict, var_name="data", raise_exception=True) try: TABLE_SCHEMA.validate(data=data) return True except SchemaError: if raise_exception: raise return False
def delete(self, name): """ Mark a metadata as 'deleted' The deletion will be trigger on remote side upon `Metadata.save()` action The marked metadata won't be accessible locally anymore However, the metadata can still be recreated again (using Metadata.set()) :param name: Name of the metadata to delete :type name: str """ # Input check check_type(value=name, allowed_types=str, var_name="name", raise_exception=True) # Empty local database if self.__data is None: self.__data = dict() # Metadata is absent if name not in self.__data: self.__data[name] = dict() self.__data[name]["deleted"] = True
def fetch(self, metadata): """ Fetch and return metadata information about the Metadata object provided The returned dict has the following format: { 'md1':{'value':'value1', 'dtype': 'dtype', 'deleted': False}, 'md2':{'value':'value2', 'dtype': 'dtype', 'deleted': False} } :param metadata: Metadata object containing a valid tsuid :type metadata: Metadata :returns: the object containing information about each metadata matching the TSUID. :rtype: dict :raises IkatsNotFoundError: if metadata doesn't exist """ check_type(value=metadata, allowed_types=Metadata, raise_exception=True) result = self.dm_client.metadata_get_typed( ts_list=[metadata.tsuid])[metadata.tsuid] for md in result: # Converts MDType result[md]["dtype"] = result[md]["dtype"] # Flag metadata as "not deleted" result[md]["deleted"] = False return result
def new(self, name=None, data=None): """ Creates an empty table locally :param name: (optional) name of the Table :param data: (optional) data of the table (as a JSON) :type name: str or None :type data: dict or None :return: the Table object :rtype: Table :raises IkatsConflictError: if table already exist """ check_type(value=name, allowed_types=[str, None], var_name="name", raise_exception=True) check_type(value=data, allowed_types=[dict, None], var_name="data", raise_exception=True) try: self.dm_client.table_read(name=name) except IkatsNotFoundError: return Table(api=self.api, name=name, data=data) raise IkatsConflictError( "Table already exist. Try using `get()` method")
def list(self, name=None, strict=True): """ List all tables If name is specified, filter by name name can contains "*", this character is considered as "any chars" (equivalent to regexp /.*/) :param name: name to find :param strict: consider name without any wildcards :type name: str or None :type strict: bool :returns: the list of tables matching the requirements :rtype: list """ check_type(value=name, allowed_types=[str, None], var_name="name", raise_exception=True) check_type(value=strict, allowed_types=bool, var_name="strict", raise_exception=True) return self.dm_client.table_list(name=name, strict=strict)
def get_func_id_from_tsuid(self, tsuid): """ Retrieve the functional identifier resource associated to the tsuid param. The resource returned aggregates original tsuid and retrieved fundId. :param tsuid: one tsuid value :type tsuid: str :returns: retrieved functional identifier resource :rtype: dict having following keys defined: - 'tsuid' - and 'funcId' :raises exception: - TypeError: if tsuid is not a str OR status_code 400 (bad request) OR unexpected http status_code - ValueError: mismatched result: http status_code 404: not found - ServerError: http status_code for server errors: 500 <= status_code < 600 """ check_type(value=tsuid, allowed_types=str, var_name="tsuid", raise_exception=True) response = self.send( root_url=self.session.dm_url + self.root_url, verb=GenericClient.VERB.GET, template=TEMPLATES['get_one_functional_identifier'], uri_params={'tsuid': tsuid}, data=None, files=None) check_http_code(response) return response.json['funcId']
def get_fid(self, tsuid): """ Get a functional ID from its TSUID :param tsuid: TSUID identifying the TS :type tsuid: str :raises TypeError: if *tsuid* not a str :raises ValueError: if *tsuid* is empty :raises ValueError: if *tsuid* doesn't have a FID """ # Checks inputs check_type(value=tsuid, allowed_types=str, var_name="tsuid", raise_exception=True) if tsuid == "": self.session.log.error("tsuid must not be empty") raise ValueError("tsuid must not be empty") response = self.send(root_url=self.session.dm_url + self.root_url, verb=GenericClient.VERB.GET, template=TEMPLATES['get_fid'], uri_params={'tsuid': tsuid}) # in case of success, web app returns 2XX if response.status_code == 200: if response.json != '{}': fid = response.json['funcId'] return fid raise IndexError("No FID for TSUID [%s]" % tsuid) raise ValueError("No FID for TSUID [%s]" % tsuid)
def desc(self, value): check_type(value=value, allowed_types=[str, None], var_name="description", raise_exception=True) self.__desc = value if value is None: self.__desc = ""
def session(self, value): check_type(value=value, allowed_types=IkatsSession, var_name="session", raise_exception=True) if value.rs is None: raise ValueError( "Requests Session not set in provided IKATS session") self.__session = value
def name(self, value): check_type(value=value, allowed_types=[str, None], var_name="name", raise_exception=True) if value is not None: check_is_valid_ds_name(value=value, raise_exception=True) if self.__name != value: self.__flag_ts_loaded = False self.__name = value
def port(self, value): check_type(value=value, allowed_types=[int, str, float, None], var_name="port", raise_exception=True) if int(value) <= 0 or int(value) >= 65535: raise ValueError("Port must be within ]0;65535] (got %s)" % value) self.__port = int(value)
def ts_delete(self, tsuid, raise_exception=True): """ Remove timeseries from database return bool status except if raise_exception is set to True. In this case, return True or raise the corresponding exception Corresponding web app resource operation: **removeTimeSeries** which deletes also all information related to other objects like Metadata, Dataset :param tsuid: tsuid of the timeseries to remove :param raise_exception: set to True to raise exceptions :type tsuid: str :type raise_exception: bool :returns: the action status (True if deleted, False otherwise) :rtype: bool :raises TypeError: if *tsuid* is not a str :raises IkatsNotFoundError: if *tsuid* is not found on server :raises IkatsConflictError: if *tsuid* belongs to -at least- one dataset :raises SystemError: if any other unhandled error occurred """ # Checks inputs check_type(value=tsuid, allowed_types=str, var_name="tsuid", raise_exception=True) response = self.send(root_url=self.session.dm_url + self.root_url, verb=GenericClient.VERB.DELETE, template=TEMPLATES['remove_ts'], uri_params={'tsuid': tsuid}) if response.status_code == 204: # Timeseries has been successfully deleted result = True elif response.status_code == 404: # Timeseries not found in database if raise_exception: raise IkatsNotFoundError( "Timeseries %s not found in database" % tsuid) result = False elif response.status_code == 409: # Timeseries linked to existing dataset if raise_exception: raise IkatsConflictError( "%s belongs to -at least- one dataset" % tsuid) result = False else: if raise_exception: raise SystemError("An unhandled error occurred") result = False return result
def name(self, value): check_type(value=value, allowed_types=[str, None], var_name="name", raise_exception=True) if value is not None: self.__name = value if self.__data is not None: try: self.__data['table_desc']['name'] = value except KeyError: pass
def tsuid(self, value): """ Setter for tsuid This setter shouldn't be used manually """ check_type(value=value, allowed_types=[str, None], var_name="tsuid", raise_exception=True) if self.__tsuid != value: self.__tsuid = value # Update Metadata link self.metadata.tsuid = value
def data(self, value): check_type(value=value, allowed_types=[dict, None], var_name="name", raise_exception=True) if value is not None: self.is_json_valid(data=value) try: self.name = value['table_desc']['name'] except KeyError: # The data doesn't contain the name, just skip this part pass self.__data = value
def import_fid(self, tsuid, fid): """ Import a functional ID into database :param tsuid: TSUID identifying the TS :param fid: Functional identifier :type tsuid: str :type fid: str :raises TypeError: if *tsuid* not a str :raises TypeError: if *fid* not a str :raises ValueError: if *tsuid* is empty :raises ValueError: if *fid* is empty :raises IkatsConflictError: if *fid* exists :raises SystemError: if another issue occurs """ # Checks inputs check_is_fid_valid(fid=fid, raise_exception=True) check_type(value=tsuid, allowed_types=str, var_name="tsuid", raise_exception=True) if tsuid == "": raise ValueError("tsuid must not be empty") response = self.send(root_url=self.session.dm_url + self.root_url, verb=GenericClient.VERB.POST, template=TEMPLATES['import_fid'], uri_params={ 'tsuid': tsuid, 'fid': fid }) # In case of success, web app returns 2XX if response.status_code == 200: pass elif response.status_code == 409: raise IkatsConflictError( "TSUID:%s - FID already exists (not updated) %s" % (tsuid, fid)) else: self.session.log.warning("TSUID:%s - FID %s not created (got %s)", tsuid, fid, response.status_code) raise SystemError("TSUID:%s - FID %s not created (got %s)" % (tsuid, fid, response.status_code))
def ts(self, value): check_type(value=value, allowed_types=[list, None], var_name="ts", raise_exception=True) if value is not None: for ts in value: check_type(value=ts, allowed_types=[str, Timeseries], var_name="ts", raise_exception=True) self.__ts = [ x if isinstance(x, Timeseries) else Timeseries(tsuid=x, api=self.api) for x in value ]
def dataset_delete(self, name, deep=False): """ Remove data_set from base Corresponding web app resource operation: **removeDataSet** :param name: name of the dataset to delete :type name: str :param deep: true to deeply remove dataset (TSUID and metadata erased) :type deep: boolean :returns: the response body :rtype: str .. note:: Removing an unknown dataset results in a successful operation (server constraint) The only possible errors may come from server (HTTP status_code code 5xx) :raises TypeError: if *name* is not a str :raises TypeError: if *deep* is not a bool """ # Checks inputs check_type(value=name, allowed_types=str, var_name="name", raise_exception=True) check_is_valid_ds_name(value=name, raise_exception=True) check_type(value=deep, allowed_types=bool, var_name="deep", raise_exception=True) template = 'dataset_remove' if deep: template = 'dataset_deep_remove' response = self.send(root_url=self.session.dm_url + self.root_url, verb=GenericClient.VERB.DELETE, template=TEMPLATES[template], uri_params={'name': name}) if response.status_code == 404: raise IkatsNotFoundError("Dataset %s not found in database" % name) return response.text
def delete(self, ts, raise_exception=True): """ Delete the data corresponding to a *ts* object and all associated metadata Note that if timeseries belongs to a dataset it will not be removed Returns a boolean status of the action (True means "OK", False means "errors occurred") :param ts: tsuid of the timeseries or Timeseries Object to remove :param raise_exception: (optional) Indicates if IKATS exceptions shall be raised (True, default) or not (False) :type ts: str or Timeseries :type raise_exception: bool :returns: the status of the action :rtype: bool :raises TypeError: if *ts* is not a str nor a Timeseries :raises IkatsNotFoundError: if timeseries is not found on server :raises IkatsConflictError: if timeseries belongs to -at least- one dataset """ check_type(value=ts, allowed_types=[str, Timeseries], var_name="ts", raise_exception=True) tsuid = ts if isinstance(ts, Timeseries): if ts.tsuid is not None: tsuid = ts.tsuid elif ts.fid is not None: try: tsuid = self.dm_client.get_tsuid_from_fid(fid=ts.fid) except IkatsException: if raise_exception: raise return False else: raise ValueError( "Timeseries object shall have set at least tsuid or fid") return self.dm_client.ts_delete(tsuid=tsuid, raise_exception=raise_exception)
def dataset_read(self, name): """ Retrieve the details of a Dataset provided in arguments Corresponding web app resource operation: **getDataSet** :param name: name of the dataset to request TS list from :type name: str :return: information about ts_list and description * *ts_list* is the list of TS matching the data_set * *description* is the description sentence of the dataset :rtype: dict :raises TypeError: if name is not a str :raises IkatsNotFoundError: if dataset doesn't exist in database """ # Checks inputs check_type(value=name, allowed_types=str, var_name="name", raise_exception=True) ret = {'ts_list': [], 'description': None} response = self.send(root_url=self.session.dm_url + self.root_url, verb=GenericClient.VERB.GET, template=TEMPLATES['dataset_read'], uri_params={'name': name}) is_404(response=response, msg="Dataset %s not found in database" % name) if response.status_code == 200: if 'fids' in response.json: ret['ts_list'] = response.json['fids'] if 'description' in response.json: ret['description'] = response.json['description'] return ret raise SystemError("Something wrong happened")
def search_functional_identifiers(self, criterion_type, criteria_list): """ Retrieve the list of functional identifier records. Each resource record aggregates one tsuid and associated fundId. Note: partial match will not raise error, contrary to empty match. :param criterion_type: defines criterion applicable to this search :type criterion_type: str value accepted by server. ex: 'tsuids' or 'funcIds' :param criteria_list: non empty list of possible values for the criterion type :type criteria_list: list of str :returns: matching list of functional identifier resources: dict having following keys defined: - 'tsuid', - and 'funcId' :rtype: list of dict :raises exception: - TypeError: if unexpected arguments OR status_code 400 (bad request) OR unexpected http status_code - ValueError: mismatched result: http status_code 404: not found - ServerError: http status_code for server errors: 500 <= status_code < 600 """ check_type(value=criterion_type, allowed_types=str, var_name="criterion_type", raise_exception=True) check_type(value=criteria_list, allowed_types=list, var_name="criteria_list", raise_exception=True) my_filter = dict() my_filter[criterion_type] = criteria_list response = self.send( root_url=self.session.dm_url + self.root_url, verb=GenericClient.VERB.POST, template=TEMPLATES['search_functional_identifier_list'], data=my_filter, files=None) check_http_code(response) return response.json
def delete(self, raise_exception=True): """ Remove the table from database but keep the local object Returns a boolean status of the action (True means "OK", False means "errors occurred") :param raise_exception: Indicates if exceptions shall be raised (True, default) or not (False) :type raise_exception: bool :returns: the status of the action :rtype: bool :raises IkatsNotFoundError: if table not found in database """ check_type(value=raise_exception, allowed_types=bool, var_name="raise_exception", raise_exception=True) return self.api.table.delete(name=self.name, raise_exception=raise_exception)
def delete(self, name, deep=False, raise_exception=True): """ Remove dataset from database Returns a boolean status of the action (True means "OK", False means "errors occurred") :param name: Dataset name to delete :param deep: true to deeply remove dataset (tsuid and metadata erased) :param raise_exception: Indicates if exceptions shall be raised (True, default) or not (False) :type name: str or Dataset :type deep: bool :type raise_exception: bool :returns: the status of the action :rtype: bool :raises TypeError: if *name* is not a str :raises TypeError: if *deep* is not a bool :raises ValueError: if *name* is a valid name :raises IkatsNotFoundError: if dataset not found in database """ check_type(value=deep, allowed_types=[bool, None], var_name="deep", raise_exception=True) check_type(value=name, allowed_types=[str, Dataset], var_name="name", raise_exception=True) if isinstance(name, Dataset): name = name.name check_is_valid_ds_name(value=name, raise_exception=True) try: self.dm_client.dataset_delete(name=name, deep=deep) except IkatsException: if raise_exception: raise return False return True
def get(self, name): """ Reads the data blob content: for the unique table identified by id. :param name: the id key of the raw table to get data from :type name: str :returns: the content data stored. :rtype: bytes or str or object :raises IkatsNotFoundError: no resource identified by ID :raises IkatsException: any other error """ check_type(value=name, allowed_types=str, var_name="name", raise_exception=True) data = self.dm_client.table_read(name=name) return Table(api=self.api, name=name, data=data)
def save(self, raise_exception=True): """ Save the table to database (creation only, no update available) Returns a boolean status of the action (True means "OK", False means "errors occurred") :param raise_exception: Indicates if exceptions shall be raised (True, default) or not (False) :type raise_exception: bool :returns: the status of the action :rtype: bool :raises IkatsConflictError: if Table name already exists in database """ check_type(value=raise_exception, allowed_types=bool, var_name="raise_exception", raise_exception=True) return self.api.table.save(data=self.data, name=self.name, raise_exception=raise_exception)