diff --git a/tangostationcontrol/tangostationcontrol/toolkit/archiver.py b/tangostationcontrol/tangostationcontrol/toolkit/archiver.py index 6c0cd3baa8e2c708d39858191bc56e73e84a9eff..0de191499b9700c725885b645faa1a339c4d3bc6 100644 --- a/tangostationcontrol/tangostationcontrol/toolkit/archiver.py +++ b/tangostationcontrol/tangostationcontrol/toolkit/archiver.py @@ -1,6 +1,5 @@ #! /usr/bin/env python3 -#from logging import raiseExceptions import logging from tango import DeviceProxy, AttributeProxy @@ -11,7 +10,8 @@ import json, os from sqlalchemy import create_engine, and_ from sqlalchemy.orm import sessionmaker from sqlalchemy.orm.exc import NoResultFound -from .archiver_base import * +import importlib +import numpy as np logger = logging.getLogger() @@ -52,7 +52,7 @@ class Archiver(): dev_polling_time = None dev_archive_time = None - def __init__(self, selector_filename:str = None, cm_name: str = 'archiving/hdbpp/confmanager01', context: str = 'RUN'): + def __init__(self, selector_filename:str = None, cm_name: str = 'archiving/hdbppts/confmanager01', context: str = 'RUN'): self.cm_name = cm_name self.cm = DeviceProxy(cm_name) try: @@ -468,16 +468,19 @@ class Retriever(): """ The Retriever class implements retrieve operations on a given DBMS """ - def __init__(self, cm_name: str = 'archiving/hdbpp/confmanager01'): + def __init__(self, cm_name: str = 'archiving/hdbppts/confmanager01'): self.cm_name = cm_name - self.session = self.connect_to_archiving_db() + self.session, self.dbms = self.connect_to_archiving_db() + self.ab = self.set_archiver_base() + def get_db_credentials(self): """ Retrieves the DB credentials from the Tango properties of Configuration Manager """ cm = DeviceProxy(self.cm_name) - config_list = cm.get_property('LibConfiguration')['LibConfiguration'] # dictionary {'LibConfiguration': list of strings} + config_list = list(cm.get_property('LibConfiguration')['LibConfiguration']) # dictionary {'LibConfiguration': list of strings} + if 'connect_string=' in config_list[0]: config_list.pop(0) # possibly remove connect string because it causes errors host = str([s for s in config_list if "host" in s][0].split('=')[1]) dbname = str([s for s in config_list if "dbname" in s][0].split('=')[1]) port = str([s for s in config_list if "port" in s][0].split('=')[1]) @@ -490,15 +493,34 @@ class Retriever(): Returns a session to a MySQL DBMS using default credentials. """ host,dbname,port,user,pw = self.get_db_credentials() - engine = create_engine('mysql+pymysql://'+user+':'+pw+'@'+host+':'+port+'/'+dbname) + # Set sqlalchemy library connection + if host=='archiver-maria-db': + libname = 'mysql+pymysql' + dbms = 'mysql' + elif host=='archiver-timescale': + libname = 'postgresql+psycopg2' + dbms = 'postgres' + else: + raise Exception(f"Invalid hostname! {host}") + engine = create_engine(libname+'://'+user+':'+pw+'@'+host+':'+port+'/'+dbname) Session = sessionmaker(bind=engine) - return Session() + return Session(),dbms + + def set_archiver_base(self): + """ + Sets the right mapper class following the DBMS connection + """ + if self.dbms == 'postgres': + ab = importlib.import_module('.archiver_base_ts', package=__package__) + elif self.dbms == 'mysql': + ab = importlib.import_module('.archiver_base_mysql', package=__package__) + return ab def get_all_archived_attributes(self): """ Returns a list of the archived attributes in the DB. """ - attrs = self.session.query(Attribute).order_by(Attribute.att_conf_id).all() + attrs = self.session.query(self.ab.Attribute).order_by(self.ab.Attribute.att_conf_id).all() # Returns the representation as set in __repr__ method of the mapper class return attrs @@ -510,8 +532,8 @@ class Retriever(): [domain, family, member] = device_fqname.split('/') except: raise AttributeFormatException(f"Could not parse device name {device_fqname}. Please provide FQDN, e.g. STAT/Device/1") - attrs = self.session.query(Attribute).filter(and_(Attribute.domain == domain, Attribute.family == family, \ - Attribute.member == member)).all() + attrs = self.session.query(self.ab.Attribute).filter(and_(self.ab.Attribute.domain == domain, self.ab.Attribute.family == family, \ + self.ab.Attribute.member == member)).all() # Returns the representation as set in __repr__ method of the mapper class return attrs @@ -524,8 +546,8 @@ class Retriever(): except: raise AttributeFormatException(f"Could not parse attribute name {attribute_fqname}. Please provide FQDN, e.g. STAT/Device/1/Attribute") try: - result = self.session.query(Attribute.att_conf_id).filter(and_(Attribute.domain == domain, Attribute.family == family, \ - Attribute.member == member, Attribute.name == name)).one() + result = self.session.query(self.ab.Attribute.att_conf_id).filter(and_(self.ab.Attribute.domain == domain, self.ab.Attribute.family == family, \ + self.ab.Attribute.member == member, self.ab.Attribute.name == name)).one() return result[0] except TypeError as e: raise Exception("Attribute not found!") from e @@ -543,14 +565,51 @@ class Retriever(): except: raise AttributeFormatException(f"Could not parse attribute name {attribute_fqname}. Please provide FQDN, e.g. STAT/Device/1/Attribute") try: - result = self.session.query(DataType.data_type).join(Attribute,Attribute.att_conf_data_type_id==DataType.att_conf_data_type_id).\ - filter(and_(Attribute.domain == domain, Attribute.family == family, Attribute.member == member, Attribute.name == name)).one() + if self.dbms=='mysql': + result = self.session.query(self.ab.DataType.data_type).join(self.ab.Attribute,self.ab.Attribute.att_conf_data_type_id==self.ab.DataType.att_conf_data_type_id).\ + filter(and_(self.ab.Attribute.domain == domain, self.ab.Attribute.family == family, self.ab.Attribute.member == member, self.ab.Attribute.name == name)).one() + elif self.dbms=='postgres': + result = self.session.query(self.ab.DataType.type).join(self.ab.Attribute,self.ab.Attribute.att_conf_type_id==self.ab.DataType.att_conf_type_id).\ + filter(and_(self.ab.Attribute.domain == domain, self.ab.Attribute.family == family, self.ab.Attribute.member == member, self.ab.Attribute.name == name)).one() return result[0] except TypeError as e: raise Exception("Attribute not found!") from e except NoResultFound as e: raise Exception(f"No records of attribute {attribute_fqname} found in DB") from e + def get_attribute_format(self,attribute_fqname: str): + """ + Takes as input the fully-qualified name of an attribute and returns its format. + Formats are basically three: Scalar, Spectrum and Image. + * Works only for POSTGRESQL * + """ + try: + [domain, family, member, name] = attribute_fqname.split('/') + except: + raise AttributeFormatException(f"Could not parse attribute name {attribute_fqname}. Please provide FQDN, e.g. STAT/Device/1/Attribute") + try: + result = self.session.query(self.ab.Format.format).join(self.ab.Attribute,self.ab.Attribute.att_conf_format_id==self.ab.Format.att_conf_format_id).\ + filter(and_(self.ab.Attribute.domain == domain, self.ab.Attribute.family == family, self.ab.Attribute.member == member, self.ab.Attribute.name == name)).one() + return result[0] + except TypeError as e: + raise Exception("Attribute not found!") from e + except NoResultFound as e: + raise Exception(f"No records of attribute {attribute_fqname} found in DB") from e + + def get_attribute_tablename(self,attribute_fqname: str): + try: + [domain, family, member, name] = attribute_fqname.split('/') + except: + raise AttributeFormatException(f"Could not parse attribute name {attribute_fqname}. Please provide FQDN, e.g. STAT/Device/1/Attribute") + try: + result = self.session.query(self.ab.Attribute.table_name).filter(and_(self.ab.Attribute.domain == domain, self.ab.Attribute.family == family, \ + self.ab.Attribute.member == member, self.ab.Attribute.name == name)).one() + return result[0] + except TypeError as e: + raise Exception("Attribute not found!") from e + except NoResultFound as e: + raise Exception(f"No records of attribute {attribute_fqname} found in DB") from e + def get_attribute_value_by_hours(self,attribute_fqname: str, hours: float = 1.0): """ Takes as input the attribute fully-qualified name and the number of past hours since the actual time @@ -559,9 +618,12 @@ class Retriever(): """ attr_id = self.get_attribute_id(attribute_fqname) attr_datatype = self.get_attribute_datatype(attribute_fqname) - attr_table_name = 'att_'+str(attr_datatype) # Retrieves the class that maps the DB table given the tablename - base_class = get_class_by_tablename(attr_table_name) + if self.dbms=='mysql': + tablename = 'att_'+str(attr_datatype) + elif self.dbms=='postgres': + tablename = self.get_attribute_tablename(attribute_fqname) + base_class = self.ab.get_class_by_tablename(tablename) # Retrieves the timestamp time_now = datetime.now() time_delta = time_now - timedelta(hours=hours) @@ -570,8 +632,8 @@ class Retriever(): time_delta_db = str(time_delta.strftime("%Y-%m-%d %X")) try: result = self.session.query(base_class).\ - join(Attribute,Attribute.att_conf_id==base_class.att_conf_id).\ - filter(and_(Attribute.att_conf_id == attr_id,base_class.data_time >= time_delta_db, \ + join(self.ab.Attribute,self.ab.Attribute.att_conf_id==base_class.att_conf_id).\ + filter(and_(self.ab.Attribute.att_conf_id == attr_id,base_class.data_time >= time_delta_db, \ base_class.data_time <= time_now_db)).order_by(base_class.data_time).all() except AttributeError as e: raise Exception(f"Empty result! Attribute {attribute_fqname} not found") from e @@ -587,18 +649,22 @@ class Retriever(): attr_datatype = self.get_attribute_datatype(attribute_fqname) attr_table_name = 'att_'+str(attr_datatype) # Retrieves the class that maps the DB table given the tablename - base_class = get_class_by_tablename(attr_table_name) + if self.dbms=='mysql': + tablename = 'att_'+str(attr_datatype) + elif self.dbms=='postgres': + tablename = self.get_attribute_tablename(attribute_fqname) + base_class = self.ab.get_class_by_tablename(tablename) try: result = self.session.query(base_class).\ - join(Attribute,Attribute.att_conf_id==base_class.att_conf_id).\ - filter(and_(Attribute.att_conf_id == attr_id,base_class.data_time >= str(start_time), \ + join(self.ab.Attribute,self.ab.Attribute.att_conf_id==base_class.att_conf_id).\ + filter(and_(self.ab.Attribute.att_conf_id == attr_id,base_class.data_time >= str(start_time), \ base_class.data_time <= str(stop_time))).order_by(base_class.data_time).all() except AttributeError as e: raise Exception(f"Empty result! Attribute {attribute_fqname} not found") from e return result - def get_masked_fpga_temp(self,start_time: datetime, stop_time: datetime,temp_attr_name:str='LTS/SDP/1/fpga_temp_r', - mask_attr_name:str='LTS/SDP/1/tr_fpga_mask_r'): + def get_masked_fpga_temp(self,start_time: datetime, stop_time: datetime,temp_attr_name:str='stat/sdp/1/fpga_temp_r', + mask_attr_name:str='stat/sdp/1/tr_fpga_mask_r'): """ Returns a list of SDP/fpga_temp_r values, but only if SDP/tr_fpga_mask_r values are TRUE """ @@ -617,11 +683,11 @@ class Retriever(): else: raise Exception # Convert DB Array records into Python lists - mask_data = build_array_from_record(mask_values,mask_values[0].dim_x_r) - temp_data = build_array_from_record(temp_values,temp_values[0].dim_x_r) + mask_data = self.ab.build_array_from_record(mask_values,mask_values[0].dim_x_r) + temp_data = self.ab.build_array_from_record(temp_values,temp_values[0].dim_x_r) # Extract only the value from the array - mask_array_values = get_values_from_record(mask_data) - temp_array_values = get_values_from_record(temp_data) + mask_array_values = self.ab.get_values_from_record(mask_data) + temp_array_values = self.ab.get_values_from_record(temp_data) # Multiply the matrix #masked_values = np.multiply(temp_array_values,mask_array_values) masked_values = np.ma.masked_array(temp_array_values,mask=np.invert(mask_array_values.astype(bool))) diff --git a/tangostationcontrol/tangostationcontrol/toolkit/archiver_base.py b/tangostationcontrol/tangostationcontrol/toolkit/archiver_base_mysql.py similarity index 99% rename from tangostationcontrol/tangostationcontrol/toolkit/archiver_base.py rename to tangostationcontrol/tangostationcontrol/toolkit/archiver_base_mysql.py index 4440957bb8546a9e42638a0a6b441e43119fa601..0e85ad5ec377536d2e50f4323e60ca9ddb9d76e1 100644 --- a/tangostationcontrol/tangostationcontrol/toolkit/archiver_base.py +++ b/tangostationcontrol/tangostationcontrol/toolkit/archiver_base_mysql.py @@ -28,10 +28,9 @@ class Attribute(Base): family = Column(String) member = Column(String) name = Column(String) - - + def __repr__(self): - return f"<Attribute(fullname='{self.att_name}',data_type ='{self.att_conf_data_type_id}',ttl='{self.att_ttl}',facility ='{elf.facility}',domain ='{self.domain}',family ='{self.family}',member ='{self.member}',name ='{self.name}')>" + return f"<Attribute(fullname='{self.att_name}',data_type ='{self.att_conf_data_type_id}',ttl='{self.att_ttl}',facility ='{self.facility}',domain ='{self.domain}',family ='{self.family}',member ='{self.member}',name ='{self.name}')>" class DataType(Base): """ diff --git a/tangostationcontrol/tangostationcontrol/toolkit/archiver_base_ts.py b/tangostationcontrol/tangostationcontrol/toolkit/archiver_base_ts.py new file mode 100644 index 0000000000000000000000000000000000000000..e86473df231e1e39604c154ea4883392a3866a08 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/toolkit/archiver_base_ts.py @@ -0,0 +1,473 @@ +#! /usr/bin/env python3 + +from sqlalchemy.dialects.postgresql import ARRAY,TIMESTAMP,FLOAT, JSON +from sqlalchemy.dialects.postgresql.base import BYTEA +from sqlalchemy.dialects.postgresql.ranges import INT4RANGE, INT8RANGE +from sqlalchemy.sql.sqltypes import INTEGER, TEXT, Boolean +from sqlalchemy.orm import declarative_base +from sqlalchemy import Column, Integer, String +from sqlalchemy.sql.expression import table +from typing import List +from itertools import groupby +import numpy as np + +#Declarative system used to define classes mapped to relational DB tables +Base = declarative_base() + +class Attribute(Base): + """ + Class that represents a Tango Attribute mapped to table 'att_conf' + """ + __tablename__ = 'att_conf' + __table_args__ = {'extend_existing': True} + + att_conf_id = Column(Integer, primary_key=True) + att_name = Column(String) + att_conf_type_id = Column(Integer) + att_conf_format_id = Column(Integer) + table_name = Column(String) + cs_name = Column(String) + domain = Column(String) + family = Column(String) + member = Column(String) + name = Column(String) + ttl = Column(Integer) + + def __repr__(self): + return f"<Attribute(fullname='{self.att_name}',data_type ='{self.att_conf_type_id}',format='{self.att_conf_format_id}',table_name='{self.table_name}',cs_name ='{self.cs_name}',domain ='{self.domain}',family ='{self.family}',member ='{self.member}',name ='{self.name}'),ttl='{self.ttl}'>" + +class DataType(Base): + """ + Class that represents a Tango Data Type mapped to table 'att_conf_data_type' + """ + __tablename__ = 'att_conf_type' + __table_args__ = {'extend_existing': True} + + att_conf_type_id = Column(Integer, primary_key=True) + type = Column(String) + + def __repr__(self): + return f"<DataType(type='{self.type}')>" + +class Format(Base): + """ + Class that represents a Tango Format mapped to table 'att_conf_format' + """ + __tablename__ = 'att_conf_format' + __table_args__ = {'extend_existing': True} + + att_conf_format_id = Column(Integer, primary_key=True) + format = Column(String) + format_num = Column(Integer) + + def __repr__(self): + return f"<Format(format='{self.format}', format_num='{self.format_num}')>" + +class Scalar(Base): + """ + Abstract class that represents Super-class of Scalar mapper classes + """ + # In the concrete inheritance use case, it is common that the base class is not represented + # within the database, only the subclasses. In other words, the base class is abstract. + __abstract__ = True + + # Primary key is not defined for tables which store values, but SQLAlchemy requires a mandatory + # primary key definition. Anyway, this definition is on Python-side and does not compromise + # DBMS architecture + att_conf_id = Column(Integer, primary_key=True) + data_time = Column(TIMESTAMP, primary_key=True) + quality = Column(Integer) + att_error_desc_id = Column(Integer) + details = Column(JSON) + +class Scalar_Boolean(Scalar): + """ + Class that represents a Tango Boolean mapped to table 'att_scalar_devboolean' + """ + __tablename__ = 'att_scalar_devboolean' + __table_args__ = {'extend_existing': True} + value_r = Column(Boolean) + value_w = Column(Boolean) + + def __repr__(self): + return f"<Scalar_Boolean(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_Double(Scalar): + """ + Class that represents a Tango Double mapped to table 'att_scalar_devdouble' + """ + __tablename__ = 'att_scalar_devdouble' + __table_args__ = {'extend_existing': True} + value_r = Column(FLOAT) + value_w = Column(FLOAT) + + def __repr__(self): + return f"<Scalar_Double(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_Encoded(Scalar): + """ + Class that represents a Tango Encoded mapped to table 'att_scalar_devencoded' + """ + __tablename__ = 'att_scalar_devencoded' + __table_args__ = {'extend_existing': True} + value_r = Column(BYTEA) + value_w = Column(BYTEA) + + def __repr__(self): + return f"<Scalar_Encoded(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_Enum(Scalar): + """ + Class that represents a Tango Enum mapped to table 'att_scalar_devenum' + """ + __tablename__ = 'att_scalar_devenum' + __table_args__ = {'extend_existing': True} + value_r_label = Column(TEXT) + value_r = Column(INTEGER) + value_w_label = Column(TEXT) + value_w = Column(INTEGER) + + def __repr__(self): + return f"<Scalar_Enum(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r_label='{self.value_r_label}',value_r='{self.value_r}',value_w_label='{self.value_w_label},value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_Float(Scalar): + """ + Class that represents a Tango Float mapped to table 'att_scalar_devfloat' + """ + __tablename__ = 'att_scalar_devfloat' + __table_args__ = {'extend_existing': True} + value_r = Column(FLOAT) + value_w = Column(FLOAT) + + def __repr__(self): + return f"<Scalar_Float(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_Long(Scalar): + """ + Class that represents a Tango Long mapped to table 'att_scalar_devlong' + """ + __tablename__ = 'att_scalar_devlong' + __table_args__ = {'extend_existing': True} + value_r = Column(INT4RANGE) + value_w = Column(INT4RANGE) + + def __repr__(self): + return f"<Scalar_Long(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_Long64(Scalar): + """ + Class that represents a Tango Long64 mapped to table 'att_scalar_devlong64' + """ + __tablename__ = 'att_scalar_devlong64' + __table_args__ = {'extend_existing': True} + value_r = Column(INT8RANGE) + value_w = Column(INT8RANGE) + + def __repr__(self): + return f"<Scalar_Long64(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_Short(Scalar): + """ + Class that represents a Tango Short mapped to table 'att_scalar_devshort' + """ + __tablename__ = 'att_scalar_devshort' + __table_args__ = {'extend_existing': True} + value_r = Column(INTEGER) + value_w = Column(INTEGER) + + def __repr__(self): + return f"<Scalar_Short(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_State(Scalar): + """ + Class that represents a Tango State mapped to table 'att_scalar_devstate' + """ + __tablename__ = 'att_scalar_devstate' + __table_args__ = {'extend_existing': True} + value_r = Column(INTEGER) + value_w = Column(INTEGER) + + def __repr__(self): + return f"<Scalar_State(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_String(Scalar): + """ + Class that represents a Tango String mapped to table 'att_scalar_devstring' + """ + __tablename__ = 'att_scalar_devstring' + __table_args__ = {'extend_existing': True} + value_r = Column(TEXT) + value_w = Column(TEXT) + + def __repr__(self): + return f"<Scalar_String(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_UChar(Scalar): + """ + Class that represents a Tango UChar mapped to table 'att_scalar_devuchar' + """ + __tablename__ = 'att_scalar_devuchar' + __table_args__ = {'extend_existing': True} + value_r = Column(INTEGER) + value_w = Column(INTEGER) + + def __repr__(self): + return f"<Scalar_UChar(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_ULong(Scalar): + """ + Class that represents a Tango ULong mapped to table 'att_scalar_devulong' + """ + __tablename__ = 'att_scalar_devulong' + __table_args__ = {'extend_existing': True} + value_r = Column(INTEGER) + value_w = Column(INTEGER) + + def __repr__(self): + return f"<Scalar_ULong(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_ULong64(Scalar): + """ + Class that represents a Tango ULong64 mapped to table 'att_scalar_devulong64' + """ + __tablename__ = 'att_scalar_devulong64' + __table_args__ = {'extend_existing': True} + value_r = Column(INTEGER) + value_w = Column(INTEGER) + + def __repr__(self): + return f"<Scalar_ULong64(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Scalar_UShort(Scalar): + """ + Class that represents a Tango UShort mapped to table 'att_scalar_devushort' + """ + __tablename__ = 'att_scalar_devushort' + __table_args__ = {'extend_existing': True} + value_r = Column(INTEGER) + value_w = Column(INTEGER) + + def __repr__(self): + return f"<Scalar_UShort(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array(Base): + """ + Abstract class that represents Super-class of Array mapper classes + """ + __abstract__ = True + # Primary key is not defined for tables which store values, but SQLAlchemy requires a mandatory + # primary key definition. Anyway, this definition is on Python-side and does not compromise + # DBMS architecture + att_conf_id = Column(Integer, primary_key=True) + data_time = Column(TIMESTAMP, primary_key=True) + quality = Column(Integer) + att_error_desc_id = Column(Integer) + details = Column(JSON) + +class Array_Boolean(Array): + """ + Class that represents a Tango Boolean Array mapped to table 'att_array_devboolean' + """ + __tablename__ = 'att_array_devboolean' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(Boolean)) + value_w = Column(ARRAY(Boolean)) + + def __repr__(self): + return f"<Array_Boolean(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_Double(Array): + """ + Class that represents a Tango Double Array mapped to table 'att_array_devdouble' + """ + __tablename__ = 'att_array_devdouble' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(FLOAT)) + value_w = Column(ARRAY(FLOAT)) + + def __repr__(self): + return f"<Array_Double(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_Encoded(Array): + """ + Class that represents a Tango Encoded Array mapped to table 'att_array_devencoded' + """ + __tablename__ = 'att_array_devencoded' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(BYTEA)) + value_w = Column(ARRAY(BYTEA)) + + def __repr__(self): + return f"<Array_Encoded(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_Enum(Array): + """ + Class that represents a Tango Enum Array mapped to table 'att_array_devenum' + """ + __tablename__ = 'att_array_devenum' + __table_args__ = {'extend_existing': True} + value_r_label = Column(ARRAY(TEXT)) + value_r = Column(ARRAY(INTEGER)) + value_w_label = Column(ARRAY(TEXT)) + value_w = Column(ARRAY(INTEGER)) + + def __repr__(self): + return f"<Array_Enum(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r_label='{self.value_r_label}',value_r='{self.value_r}',value_w_label='{self.value_w_label},value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_Float(Array): + """ + Class that represents a Tango Float Array mapped to table 'att_array_devfloat' + """ + __tablename__ = 'att_array_devfloat' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(FLOAT)) + value_w = Column(ARRAY(FLOAT)) + + def __repr__(self): + return f"<Array_Float(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_Long(Array): + """ + Class that represents a Tango Long Array mapped to table 'att_array_devlong' + """ + __tablename__ = 'att_array_devlong' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(INT4RANGE)) + value_w = Column(ARRAY(INT4RANGE)) + + def __repr__(self): + return f"<Array_Long(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_Long64(Array): + """ + Class that represents a Tango Long64 Array mapped to table 'att_array_devlong64' + """ + __tablename__ = 'att_array_devlong64' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(INT8RANGE)) + value_w = Column(ARRAY(INT8RANGE)) + + def __repr__(self): + return f"<Array_Long64(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_Short(Array): + """ + Class that represents a Tango Short Array mapped to table 'att_array_devshort' + """ + __tablename__ = 'att_array_devshort' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(INTEGER)) + value_w = Column(ARRAY(INTEGER)) + + def __repr__(self): + return f"<Array_Short(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_State(Array): + """ + Class that represents a Tango State Array mapped to table 'att_array_devstate' + """ + __tablename__ = 'att_array_devstate' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(INT4RANGE)) + value_w = Column(ARRAY(INT4RANGE)) + + def __repr__(self): + return f"<Array_State(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_String(Array): + """ + Class that represents a Tango String Array mapped to table 'att_array_devstring' + """ + __tablename__ = 'att_array_devstring' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(TEXT)) + value_w = Column(ARRAY(TEXT)) + + def __repr__(self): + return f"<Array_String(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_UChar(Array): + """ + Class that represents a Tango UChar Array mapped to table 'att_array_devuchar' + """ + __tablename__ = 'att_array_devuchar' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(INTEGER)) + value_w = Column(ARRAY(INTEGER)) + + def __repr__(self): + return f"<Array_UChar(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_ULong(Array): + """ + Class that represents a Tango ULong Array mapped to table 'att_array_devulong' + """ + __tablename__ = 'att_array_devulong' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(INTEGER)) + value_w = Column(ARRAY(INTEGER)) + + def __repr__(self): + return f"<Array_ULong(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_ULong64(Array): + """ + Class that represents a Tango ULong64 Array mapped to table 'att_array_devulong64' + """ + __tablename__ = 'att_array_devulong64' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(INTEGER)) + value_w = Column(ARRAY(INTEGER)) + + def __repr__(self): + return f"<Array_ULong64(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +class Array_UShort(Array): + """ + Class that represents a Tango UShort Array mapped to table 'att_array_devushort' + """ + __tablename__ = 'att_array_devushort' + __table_args__ = {'extend_existing': True} + value_r = Column(ARRAY(INTEGER)) + value_w = Column(ARRAY(INTEGER)) + + def __repr__(self): + return f"<Array_UShort(att_conf_id='{self.att_conf_id}',data_time='{self.data_time}',value_r='{self.value_r}',value_w='{self.value_w},quality='{self.quality}',att_error_desc_id='{self.att_error_desc_id}',details='{self.details}')>" + +def get_class_by_tablename(tablename: str): + """ + Returns class reference mapped to a table. + """ + for mapper in Base.registry.mappers: + c = mapper.class_ + classname = c.__name__ + if not classname.startswith('_'): + if hasattr(c, '__tablename__') and c.__tablename__ == tablename: + return c + return None + +def build_array_from_record(rows: List[Array], dim_x: int): + """ + Converts Array database items in Python lists + """ + matrix = np.array([]) + for i in range(0,dim_x): + x = np.array([item for item in rows if item.idx==i]) #group records by array index + if i==0: + matrix = np.append(matrix,x) #append first row + else: + matrix = np.vstack([matrix,x]) #stack vertically + result = np.transpose(matrix) #transpose -> each row is a distinct array of value + list_result = result.tolist() + return list_result + +def get_values_from_record(data_matrix: List[Array]): + """ + Returns a matrix of values from a matrix of Array records + """ + array_matrix = np.matrix(data_matrix) + value_matrix = np.empty(array_matrix.shape) + for index in range(array_matrix.size): # for each object element + value_matrix.itemset(index,array_matrix.item(index).value_r) # extract the value from object and put in the matrix + return value_matrix +