# **************************************************************************
# *
# * Authors: Yunior C. Fonseca Reyna (cfonseca@cnb.csic.es)
# *
# *
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
# *
# * This program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation; either version 2 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# * 02111-1307 USA
# *
# * All comments concerning this program package may be sent to the
# * e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************
import logging
import os
import sys
from subprocess import Popen
import numpy
logger = logging.getLogger(__name__)
from datetime import datetime
import sqlite3
import tempfile
import time
import pyworkflow as pw
from metadataviewer.dao.model import IDAO
from metadataviewer.model import Table, Column, BoolRenderer, ImageRenderer, StrRenderer, FloatRenderer
from metadataviewer.model.renderers import Action
from pwem.convert.transformations import euler_from_matrix
ALLOWED_COLUMNS_TYPES = ['String', 'Float', 'Integer', 'Boolean', 'Matrix',
'CsvList']
ADITIONAL_INFO_DISPLAY_COLUMN_LIST = ['_size', 'id']
EXCLUDED_COLUMNS = ['label', 'comment', 'creation', '_streamState']
PERMANENT_COLUMNS = ['id', 'enabled']
CLASS_OBJECT = 1
REPRESENTATIVE_OBJECT = 2
CLASS_ELEMENTS = 3
EXTENDED_COLUMN_NAME = 'stack'
ENABLED_COLUMN = 'enabled'
PROPERTIES_TABLE = 'Properties'
OBJECT_TABLE = 'objects'
SCIPION_OBJECT_ID = "SCIPION_OBJECT_ID"
SCIPION_PORT = "SCIPION_PORT"
# --------- Helper functions ------------------------
def _guessType(strValue):
if strValue is None:
return str('None')
try:
int(strValue)
return int
except ValueError:
try:
float(strValue)
return float
except ValueError:
return str
[docs]class ScipionTable(Table):
def __init__(self, name, definitionTable):
super().__init__(name)
self.definitionTable = definitionTable
[docs] def getDefinitionTable(self):
return self.definitionTable
[docs]class ScipionColumn(Column):
def __init__(self, name, renderer=None, callback=None):
super().__init__(name, renderer=renderer)
self.callback = callback
[docs] def setCallback(self, callback):
""" Callback to compute the value for this column. The callback will receives the row"""
self.callback = callback
[docs] def calculate(self, row, values):
self.callback(row, values)
[docs]class ScipionSetsDAO(IDAO):
""" Class to serve data from scipion sets files (sqlite). """
def __init__(self, sqliteFile):
self._names = []
self._file = sqliteFile
self._con = self.__loadDB(sqliteFile)
self._con.row_factory = self._dictFactory
self._tableCount = {}
self._tables = {} # Dictionary to hold the table as key, and the table with the definition as value {"Class001_Objects": "Class001_Classess"}
self._labels = {}
self._labelsTypes = {}
self._aliases = {}
self._columnsMap = {}
self._tableWithAdditionalInfo = None
self._objectsType = {}
def __loadDB(self, sqliteFile):
"""Load a sqlite file"""
try:
return sqlite3.connect(f"file:{sqliteFile}?mode=ro", uri=True)
except Exception as e:
logger.error("The file could not be opened. Make sure the path is "
"correct: \n %s" % e)
return None
[docs] def composeDataTables(self, tablesNames):
"""This method is used to generate a dictionary with the principal
tables mapping the dependencies with other tables"""
tablesNames = sorted(tablesNames)
for tableName in tablesNames:
alias = self.composeTableAlias(tableName)
self._aliases[tableName] = alias
divTable = tableName.split('_')
if len(divTable) > 1:
if divTable[-1].startswith('Class') and tableName not in self._tables:
objectTable = tableName.replace(divTable[-1], '') + 'Objects'
self._tables[objectTable] = tableName
self._names.append(objectTable)
self._tables[PROPERTIES_TABLE] = PROPERTIES_TABLE
self._names.append(PROPERTIES_TABLE)
[docs] def composeObjectType(self):
"""Define the different objects types"""
# General type defined into Properties table
firstRow = self.getTableRow(PROPERTIES_TABLE, 0)
objectType = firstRow['value']
self._objectsType[self._aliases[OBJECT_TABLE]] = objectType
for alias in self._aliases.values():
objectTypeAux = alias.split('_')
if len(objectTypeAux) == 2:
sufix = 's' if objectTypeAux[0][-1] in "aeiouAEIOU" or objectTypeAux[0][-1] != 's' else ''
objectType = 'SetOf%s%s' % (objectTypeAux[1], sufix)
if objectTypeAux[1] not in self._objectsType:
self._objectsType[objectTypeAux[1]] = objectType
[docs] def composeTableAlias(self, tableName):
"""Create an alias for the given table"""
if tableName != PROPERTIES_TABLE:
firstRow = self.getTableRow(tableName, 0)
className = firstRow['class_name']
if tableName.__contains__('_'):
tableSplit = tableName.split('_')
lenTableSplit = len(tableSplit)
if lenTableSplit > 1:
alias = tableName.replace(tableSplit[-1], '') + className
else:
alias = className
else:
alias = PROPERTIES_TABLE
return alias
[docs] def getTables(self):
""" Return all the table names found in the database. """
initTime = time.time()
if not self._names:
# Add main items table
self._tables = {OBJECT_TABLE: ScipionTable(OBJECT_TABLE, definitionTable='classes')}
alias = self.composeTableAlias('Classes')
self._tables[OBJECT_TABLE].setAlias(alias)
self._aliases[OBJECT_TABLE] = alias
self._names = [OBJECT_TABLE]
# Add additional tables: Case for classes or titl series elements
res = self._con.execute("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%_Objects%'")
for row in res.fetchall():
name = row['name']
definitionTable = name.replace('_Objects', "_Classes")
alias = self.composeTableAlias(definitionTable)
self._tables[name] = ScipionTable(name, definitionTable=definitionTable)
self._tables[name].setAlias(alias)
self._aliases[name] = alias
self._names.append(name)
# Add properties table
self._tables[PROPERTIES_TABLE] = ScipionTable(PROPERTIES_TABLE, definitionTable=PROPERTIES_TABLE)
self._tables[PROPERTIES_TABLE].setAlias(PROPERTIES_TABLE)
self._aliases[PROPERTIES_TABLE] = PROPERTIES_TABLE
self._names.append(PROPERTIES_TABLE)
self.composeObjectType()
endTime = time.time()
logging.debug("Getting Tables: %f" % (endTime - initTime))
return self._tables
def _fillMappingColumns(self, table: ScipionTable):
tableName = table.getName()
# Getting the first row to identify the labels and theirs type
firstRow = self.getTableRow(tableName, 0, classes=table.getDefinitionTable())
self._labels[tableName] = [key for key in firstRow.keys() if key not in EXCLUDED_COLUMNS]
labelsTypes = []
for key, value in firstRow.items():
if key not in EXCLUDED_COLUMNS:
labelsTypes.append(_guessType(value))
self._labelsTypes[tableName] = labelsTypes
[docs] def generateTableActions(self, table, objectManager):
"""Generate actions for a given table in order to create subsets"""
if self.getScipionPort() and table.getName() != PROPERTIES_TABLE:
alias = table.getAlias()
labels = list(self._objectsType.keys())
objectTypes = list(self._objectsType.values())
aliasSplit = alias.split('_')
if alias.startswith('Class') and len(aliasSplit) == 1:
table.addAction(labels[0], lambda: self.createSubsetCallback(table, objectTypes[0], objectManager))
table.addAction(labels[1], lambda: self.createSubsetCallback(table, objectTypes[1], objectManager))
if alias == 'Class2D':
table.addAction('Averages',
lambda: self.createSubsetCallback(table, 'SetOfAverages', objectManager))
else:
table.addAction('Volumes', lambda: self.createSubsetCallback(table, 'SetOfVolumes', objectManager))
elif alias.startswith('Class'):
table.addAction(aliasSplit[1],
lambda: self.createSubsetCallback(table, self._objectsType[aliasSplit[1]],
objectManager))
elif alias in self._objectsType and self._objectsType[alias].startswith('SetOf'):
table.addAction(alias,
lambda: self.createSubsetCallback(table, self._objectsType[alias], objectManager))
[docs] def composeImageFilename(self, row, values):
indexStr = "%s@" % values[-2] if values[-2] != 0 else ""
values.append("%s%s" % (indexStr, values[-1]))
[docs] def fillTable(self, table: ScipionTable, objectManager):
"""Create the table structure (columns) and set the table alias"""
initTime = time.time()
tableName = table.getName()
self._fillMappingColumns(table)
colNames = self._labels[tableName]
computedColsCount = 0
row = self.getTableRow(tableName, 0, classes=table.getDefinitionTable())
if 'id' not in row:
table.setHasColumnId(False)
values = [value for key, value in row.items() if key not in EXCLUDED_COLUMNS]
imgRenderer = None # Renderer for the main image column (EXTENDED_COLUMN_NAME)
for index, colName in enumerate(colNames):
isFileNameCol = imgRenderer is None and colName.endswith("_filename")
if colName == ENABLED_COLUMN:
renderer = BoolRenderer()
elif isFileNameCol:
renderer = StrRenderer()
else:
renderer = table.guessRenderer(str(values[index]))
if isinstance(renderer, ImageRenderer):
imageExt = str(values[index]).split('.')[-1]
self.addExternalProgram(renderer, imageExt)
newCol = ScipionColumn(colName, renderer)
newCol.setIsSorteable(True)
if tableName == OBJECT_TABLE:
newCol.setIsVisible(objectManager.isLabelVisible(colName))
else:
newCol.setIsVisible(True)
table.addColumn(newCol)
if isFileNameCol:
previousCol = colNames[index - 1]
if previousCol.endswith("_index"):
logger.debug("Creating an extended column: %s" % EXTENDED_COLUMN_NAME)
imageExt = str(values[index]).split('.')[-1]
if values[index] is not None and ImageRenderer().getImageReader(values[index]) is not None:
renderer = ImageRenderer()
imgRenderer = renderer
self.addExternalProgram(renderer, imageExt)
extraCol = ScipionColumn(EXTENDED_COLUMN_NAME, renderer)
extraCol.setCallback(self.composeImageFilename)
extraCol.setIsVisible(newCol.isVisible())
extraCol.setIsSorteable(False)
table.addColumn(extraCol)
newCol.setIsVisible(False)
computedColsCount += 1
elif colName.endswith("_matrix"):
def addAlignmentColumn(name, offset, position):
extraCol = ScipionColumn(name, renderer=FloatRenderer())
extraCol.setIsSorteable(False)
extraCol.setIsVisible(newCol.isVisible())
extraCol.setCallback(lambda row, values: self.exctractAngularValue(values, offset, position))
table.addColumn(extraCol)
def addShiftColumn(name, offset, position):
extraCol = ScipionColumn(name, renderer=FloatRenderer())
extraCol.setIsVisible(newCol.isVisible())
extraCol.setIsSorteable(False)
extraCol.setCallback(lambda row, values: self.exctractShift(values, offset, position))
table.addColumn(extraCol)
colNamePrefix = colName.split("_matrix")[0]
addAlignmentColumn(colNamePrefix + "_rot", -1, 0)
computedColsCount += 1
if imgRenderer:
imgRenderer.setRotationColumnIndex(index + computedColsCount)
addAlignmentColumn(colNamePrefix + "_tilt", -2, 1)
computedColsCount += 1
addAlignmentColumn(colNamePrefix + "_psi", -3, 2)
computedColsCount += 1
addShiftColumn(colNamePrefix + "_shiftX", -4, 0)
computedColsCount += 1
addShiftColumn(colNamePrefix + "_shiftY", -5, 1)
computedColsCount += 1
# table.setAlias(self._aliases[tableName])
self.generateTableActions(table, objectManager)
table.setSortingColumn(table.getColumns()[0].getName())
endTime = time.time()
logger.debug("Table structure created: %f" % (endTime - initTime))
[docs] def exctractAngularValue(self, values, offset, position):
""" Extract the euler angle form the matrix"""
matrix = values[offset]
if isinstance(matrix, str):
matrix = numpy.array(eval(matrix))
values[offset] = matrix
matrixI = numpy.linalg.inv(matrix)
euler_data = euler_from_matrix(matrix=matrixI, axes='szyz')
values.append(numpy.rad2deg(euler_data[position]))
[docs] def exctractShift(self, values, offset, position):
""" Extract offset value """
matrix = values[offset]
shape = matrix.shape[0]
values.append(matrix[position, shape - 1])
[docs] def addExternalProgram(self, renderer: ImageRenderer, imageExt: str):
self.addChimera(renderer, imageExt)
self.addImageJ(renderer)
self.addImageViewer(renderer, imageExt)
[docs] def addChimera(self, renderer: ImageRenderer, imageExt: str):
chimeraPath = os.environ.get('CHIMERA_HOME', None)
if chimeraPath is not None and imageExt not in ['st', 'stk', 'mrcs']:
icon = pw.findResource('chimera.png')
def openChimeraCallback(imagePath):
path = imagePath.split('@')[-1]
program = os.path.join(chimeraPath, 'bin', 'ChimeraX')
cmd = program + ' "%s"' % path
Popen(cmd, shell=True, cwd=os.getcwd())
renderer.addAction(Action('ChX', icon, 'Open with ChimeraX',
openChimeraCallback))
[docs] def addImageJ(self, renderer: ImageRenderer):
imageJPath = os.environ.get('IMAGEJ_BINARY_PATH', None)
if imageJPath is not None:
icon = pw.findResource('Imagej.png')
def openImageJCallback(imagePath):
imageSplit = imagePath.split('@')
if len(imageSplit) > 1:
selectedSlice = int(imageSplit[0])
else:
selectedSlice = 0
path = imageSplit[-1]
program = os.path.join(imageJPath)
macro = r"""
path = "%s";
open(path);
slice = %d;
Stack.setSlice(slice);
""" % (os.path.abspath(path), selectedSlice)
with tempfile.NamedTemporaryFile(delete=False) as tempFile:
tempFile.write(macro.encode('utf-8'))
macroPath = tempFile.name
cmd = program + ' -macro %s' % macroPath
Popen(cmd, shell=True, cwd=os.getcwd())
renderer.addAction(Action('IJ', icon, 'Open with ImageJ',
openImageJCallback))
[docs] def addImageViewer(self, renderer: ImageRenderer, imageExt: str):
icon = os.path.join(pw.getResourcesPath(), 'file_vol.png')
def openImageViewerCallback(imagePath):
pythonPath = sys.executable
program = '%s -m pwem.viewers.mdviewer.volumeViewer' % pythonPath
cmd = program + ' "%s"' % imagePath
Popen(cmd, shell=True, cwd=os.getcwd())
if imageExt in ['mrc', 'stk']:
renderer.addAction(Action('IV', icon, 'Open with ImageViewer',
openImageViewerCallback))
[docs] def fillPage(self, page, actualColumn: str, orderAsc=True):
"""
Read the given table from the sqlite and fill the page(add rows)
"""
initTime = time.time()
table = page.getTable()
tableName = table.getName()
# moving to the first row of the page
pageNumber = page.getPageNumber()
pageSize = page.getPageSize()
firstRow = pageNumber * pageSize - pageSize
limit = pageSize
columnLabel = actualColumn if tableName != PROPERTIES_TABLE else table.getColumns()[0].getName()
mode = 'ASC' if orderAsc else 'DESC'
hasId = None
for rowcount, row in enumerate(self.iterTable(tableName, start=firstRow, limit=limit,
classes=table.getDefinitionTable(),
orderBy=columnLabel, mode=mode)):
if row:
values = []
for column in page.getTable().getColumns():
if column.isSorteable():
values.append(row[column.getName()])
else:
column.calculate(row, values)
# Resolve the id value
if hasId is None:
hasId = 'id' in row.keys()
if hasId:
idValue = row['id']
else:
idValue = rowcount
page.addRow((int(idValue), values))
endTime = time.time()
logger.debug("Page filled in %f seconds." % (endTime - initTime))
[docs] def getRowsCount(self, tableName):
""" Return the number of elements in the given table. """
logger.debug("Reading the table %s" % tableName)
if tableName == OBJECT_TABLE:
size = int(self._con.execute(f"SELECT * FROM {PROPERTIES_TABLE} WHERE key='_size'").fetchall()[0]['value'])
return size
return self._con.execute(f"SELECT COUNT(*) FROM {tableName}").fetchone()['COUNT(*)']
[docs] def getTableRowCount(self, tableName):
if tableName not in self._tableCount:
initTime = time.time()
self._tableCount[tableName] = self.getRowsCount(tableName)
endTime = time.time()
logger.debug("Table count: %f" % (endTime - initTime))
return self._tableCount[tableName]
[docs] def getSelectedRangeRowsIds(self, tableName, startRow, numberOfRows, column, reverse=True):
"""Return a range of rows starting at 'startRow' an amount
of 'numberOfRows' """
logger.debug(
"Reading the table %s and selected a range of rows %d - %d" % (tableName, startRow, numberOfRows + 1))
mode = 'ASC' if reverse else 'DESC'
col = self._getColumnMap(tableName, column)
if col == None:
col = column
query = "SELECT id FROM %s ORDER BY %s %s LIMIT %d , %d" % (
tableName, col, mode, startRow - 1, numberOfRows + 1)
rowsList = self._con.execute(query).fetchall()
rowsIds = [row['id'] for row in rowsList]
return rowsIds
[docs] def getColumnsValues(self, tableName, columns, xAxis, selection, limit,
useSelection, reverse=True):
"""Get the values of the selected columns in order to plot them"""
logger.debug("Reading the table %s and selected some columns values..." % tableName)
cols = [colName for colName in columns]
if xAxis and xAxis not in cols:
cols.append(xAxis)
columnNames = []
for column in cols:
col = self._getColumnMap(tableName, column) or column
columnNames.append(col)
if 'id' not in cols:
columnNames.append('id') # Always retrieve the id values to create subsets
cols.append('id')
columnNames = ", ".join(columnNames)
col = self._getColumnMap(tableName, xAxis)
if col is not None:
xAxis = col
mode = 'ASC' if reverse else 'DESC'
orderBy = ' ORDER BY %s %s' % (xAxis, mode) if xAxis else ''
limit = ' LIMIT %d' % limit if limit is not None else ''
where = f" WHERE id in ({', '.join(map(str, selection.getSelection().keys()))})" if selection.getCount() > 1 and useSelection else ''
query = "SELECT %s FROM %s %s %s %s" % (columnNames, tableName, where,
orderBy, limit)
selectedColumns = self._con.execute(query).fetchall()
columnsValues = {}
firstValue = selectedColumns[0]
for colName in cols:
col = self._getColumnMap(tableName, colName) or colName
columnsValues[colName] = [firstValue[col]]
for pos, value in enumerate(selectedColumns):
if pos > 0:
for colName in cols:
col = self._getColumnMap(tableName, colName) or colName
columnsValues[colName].append((value[col]))
return columnsValues
[docs] def iterTable(self, tableName, **kwargs):
"""
Method to iterate over the table's rows
:param tableName: the name of the table
:param kwargs:
limit: integer value to limit the number of elements
start: start from a given element
classes: read column names from a 'classes' table
orderBy: clause to sort given a column name
mode: sort direction ASC or DESC
"""
query = f"SELECT * FROM {tableName}"
if 'mode' in kwargs:
orderBy = kwargs.get('orderBy', '')
if orderBy:
column = self._getColumnMap(tableName, orderBy)
if not column:
column = orderBy
query += f" ORDER BY {column}"
if kwargs['mode']:
query += f" {kwargs['mode']}"
if 'start' in kwargs and 'limit' not in kwargs:
kwargs['limit'] = -1
if 'limit' in kwargs:
query += f" LIMIT {kwargs['limit']}"
if 'start' in kwargs:
query += f" OFFSET {kwargs['start']}"
# Properties Table
if 'classes' not in kwargs or kwargs['classes'] == PROPERTIES_TABLE:
res = self._con.execute(query)
while row := res.fetchone():
yield row
else: # Mapping the column names and including only the allowed columns
self._columnsMap[tableName] = {}
self._excludedColumns = {}
for row in self.iterTable(kwargs['classes']):
colName = row['column_name']
colType = row['label_property']
if row['class_name'] in ALLOWED_COLUMNS_TYPES:
self._columnsMap[tableName][colName] = colType
else:
self._excludedColumns[colName] = colType
def _row_factory(cursor, row):
fields = [column[0] for column in cursor.description]
rowFact = {self._columnsMap[tableName].get(k, k): v for k, v in zip(fields, row) if
k not in self._excludedColumns}
return rowFact
# Modify row factory to modify column names
self._con.row_factory = _row_factory
res = self._con.execute(query)
while row := res.fetchone():
yield row
# Restore row factory
self._con.row_factory = self._dictFactory
def _getColumnMap(self, tableName, column):
"""Get the column name that has been mapped"""
if tableName in self._columnsMap:
for key, value in self._columnsMap[tableName].items():
if value == column:
return key
return None
[docs] def getTableRow(self, tableName, rowIndex, **kwargs):
""" Get a given row by index. Extra args are passed to iterTable. """
kwargs['start'] = rowIndex
kwargs['limit'] = 1
for row in self.iterTable(tableName, **kwargs):
return row
[docs] def getTableWithAdditionalInfo(self):
"""Return a tuple with the table that need to show additional info and
the column that we need to show"""
return self._tableWithAdditionalInfo, ADITIONAL_INFO_DISPLAY_COLUMN_LIST
[docs] def createSubsetCallback(self, table: Table, objectType: str, objectManager):
"""Create a subset"""
selection = table.getSelection().getSelection()
tableName = table.getName()
elementsCount = len(selection)
if not elementsCount:
elementsCount = self._tableCount[tableName]
objectManager.getSelectedRangeRowsIds(tableName,
1, self._tableCount[tableName],
'id', 'ASC')
subsetName = objectManager.getGui().getSubsetName(objectType, elementsCount)
if subsetName:
timeFormat = '%Y%m%d%H%M%S'
now = datetime.now()
timestamp = now.strftime(timeFormat)
path = 'Logs/selection_%s.txt' % timestamp
self.writeSelection(table, path)
path += "," # Always add a comma, it is expected by the user subset protocol
if tableName != OBJECT_TABLE:
path += tableName.split('_Objects')[0]
self.sendSubsetCreationMessage(path, objectType, subsetName)
[docs] def sendSubsetCreationMessage(self, selectionFile, outputClassName, label):
import socket
# Create a client socket
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
# Connect to the local server where Scipion GUI is listening
clientSocket.connect(("127.0.0.1", int(self.getScipionPort())));
# We should create this message:
# run protocol ProtUserSubSet inputObject=380 sqliteFile='...','' outputClassName=SetOfTiltSeries other='' label='create subset'
data = f"run protocol ProtUserSubSet inputObject={self.getScipionObjectId()} " \
f"sqliteFile='{selectionFile}' outputClassName='{outputClassName}' other='' label='{label}'"
clientSocket.send(data.encode())
[docs] def getScipionPort(self):
""" Returns Scipion port or None if not in the environment"""
return os.getenv(SCIPION_PORT, None)
[docs] def getScipionObjectId(self):
""" Returns Scipion object id"""
return os.getenv(SCIPION_OBJECT_ID)
[docs] def writeSelection(self, table: Table, path):
""" Create a file with the selected rows ids"""
tableName = table.getName()
rowsIds = table.getSelection().getSelection().keys()
if not rowsIds:
rowsIds = [i + 1 for i in range(self._tableCount[tableName])]
try:
with open(path, 'w') as file:
for rowId in rowsIds:
file.write(str(rowId) + ' ')
file.close()
logger.debug(f"The file: {path} was created correctly.")
except Exception as e:
logger.error(f"Error creating the file: {e}")
[docs] def close(self):
if getattr(self, '_con', None):
self._con.close()
self._con = None
def _dictFactory(self, cursor, row):
fields = [column[0] for column in cursor.description]
return {key: value for key, value in zip(fields, row)}
[docs] @classmethod
def getCompatibleFileTypes(cls):
"""Return a list of compatible extension of files"""
logger.debug("Selected SqliteFile DAO")
return ['sqlite']
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def __del__(self):
self.close()