IMP logo
IMP Reference Guide  2.25.0
The Integrative Modeling Platform
database.py
1 """@namespace IMP.EMageFit.database
2  Utility functions to manage SQL databases with sqlite3.
3 """
4 
5 import sqlite3 as sqlite
6 import os
7 import csv
8 import logging
9 
10 log = logging.getLogger("Database")
11 
12 
13 class Database2:
14 
15  """ Class to manage a SQL database built with sqlite3 """
16 
17  def __init__(self):
18  # Connection to the database
19  self.connection = None
20  # Cursor of actions
21  self.cursor = None
22  # Dictionary of tablenames and types (used to convert values when
23  # storing data)
24 
25  def create(self, filename, overwrite=False):
26  """ Creates a database by simply connecting to the file """
27  log.info("Creating database")
28  if overwrite and os.path.exists(filename):
29  os.remove(filename)
30  sqlite.connect(filename)
31 
32  def connect(self, filename):
33  """ Connects to the database in filename """
34  if not os.path.isfile(filename):
35  raise IOError("Database file not found: %s" % filename)
36  self.connection = sqlite.connect(filename)
37  self.cursor = self.connection.cursor()
38 
40  """ Checks if the class is connected to the database filename """
41  if self.connection is None:
42  raise ValueError("The database has not been created "
43  "or connection not established ")
44 
45  def create_table(self, table_name, column_names, column_types):
46  """ Creates a table. It expects a sorted dictionary
47  of (data_field,typename) entries """
48  log.info("Creating table %s", table_name)
50  sql_command = "CREATE TABLE %s (" % (table_name)
51  for name, data_type in zip(column_names, column_types):
52  sql_typename = get_sql_type_name(data_type)
53  sql_command += "%s %s," % (name, sql_typename)
54  # replace last comma for a parenthesis
55  n = len(sql_command)
56  sql_command = sql_command[0:n - 1] + ")"
57  log.debug(sql_command)
58  self.cursor.execute(sql_command)
59  self.connection.commit()
60 
61  def drop_table(self, table_name):
62  """
63  Delete a table if it exists
64  """
65  log.info("Deleting table %s", table_name)
67  sql_command = "DROP TABLE IF EXISTS %s" % (table_name)
68  log.debug(sql_command)
69  self.cursor.execute(sql_command)
70  self.connection.commit()
71 
72  def store_dataV1(self, table_name, data):
73  """ Inserts information in a given table of the database.
74  The info must be a list of tuples containing as many values
75  as columns in the table
76  Conversion of values is done AUTOMATICALLY after checking the
77  types stored in the table
78  """
80  n = len(data[0]) # number of columns for each row inserted
81  tuple_format = "(" + "?," * (n - 1) + "?)"
82  sql_command = "INSERT INTO %s VALUES %s " % (table_name, tuple_format)
83  # Fill the table with the info in the tuples
84  types = self.get_table_types(table_name)
85  for x in data:
86  # convert (applies the types stored in the table dictionary to each
87  # value in x
88  y = [apply_type(i) for i, apply_type in zip(x, types)]
89  self.cursor.execute(sql_command, y)
90  self.connection.commit()
91 
92  def store_data(self, table_name, data):
93  """ Inserts information in a given table of the database.
94  The info must be a list of tuples containing as many values
95  as columns in the table
96  Conversion of values is done AUTOMATICALLY after checking the
97  types stored in the table
98  """
99  if len(data) == 0:
100  log.warning("Inserting empty data")
101  return
102  self.check_if_is_connected()
103  n = len(data[0]) # number of columns for each row inserted
104  tuple_format = "(" + "?," * (n - 1) + "?)"
105  sql_command = "INSERT INTO %s VALUES %s " % (table_name, tuple_format)
106  # Fill the table with the info in the tuples
107  types = self.get_table_types(table_name)
108 # log.debug("Storing types: %s", types)
109  for i in range(len(data)):
110  data[i] = [apply_type(d) for d, apply_type in zip(data[i], types)]
111  self.cursor.executemany(sql_command, data)
112  self.connection.commit()
113 
114  def retrieve_data(self, sql_command):
115  """ Retrieves data from the database using the sql_command
116  returns the records as a list of tuples"""
117  self.check_if_is_connected()
118  log.debug("Retrieving data: %s" % sql_command)
119  self.cursor.execute(sql_command)
120  return self.cursor.fetchall()
121 
122  def update_data(self, table_name,
123  updated_fields,
124  updated_values,
125  condition_fields,
126  condition_values):
127  """ updates the register in the table identified by the condition
128  values for the condition fields
129  """
130  self.check_if_is_connected()
131  sql_command = "UPDATE %s SET " % (table_name)
132  for field, value in zip(updated_fields, updated_values):
133  sql_command += "%s=%s," % (field, value)
134  sql_command = sql_command.rstrip(",") + " WHERE "
135  s = self.get_condition_string(condition_fields, condition_values)
136  sql_command = sql_command + s
137  # print sql_command
138  log.debug("Updating %s: %s", table_name, sql_command)
139  self.cursor.execute(sql_command)
140  self.connection.commit()
141 
142  def create_view(self, view_name, table_name,
143  condition_fields, condition_values):
144  """ creates a view of the given table where the values are selected
145  using the condition values. See the help for update_data()
146  """
147  try: # if this fails is because the view already exist
148  self.drop_view(view_name)
149  except: # noqa: E722
150  pass
151  sql_command = 'CREATE VIEW %s AS SELECT * FROM %s WHERE ' % (
152  view_name, table_name)
153  condition = self.get_condition_string(
154  condition_fields, condition_values)
155  sql_command += condition
156  log.info("Creating view %s", sql_command)
157  self.cursor.execute(sql_command)
158 
159  def create_view_of_best_records(
160  self,
161  view_name,
162  table_name,
163  orderby,
164  n_records):
165  try: # if this fails is because the view already exist
166  self.drop_view(view_name)
167  except: # noqa: E722
168  pass
169  sql_command = """CREATE VIEW %s AS SELECT * FROM %s
170  ORDER BY %s ASC LIMIT %d """ % (view_name, table_name,
171  orderby, n_records)
172  log.info("Creating view %s", sql_command)
173  self.cursor.execute(sql_command)
174 
175  def drop_view(self, view_name):
176  """ Removes a view from the database """
177  self.cursor.execute('DROP VIEW %s' % view_name)
178 
179  def get_table(self, table_name, fields=False, orderby=False):
180  """ Returns th fields requested from the table """
181  fields = self.get_fields_string(fields)
182  sql_command = "SELECT %s FROM %s " % (fields, table_name)
183  if orderby:
184  sql_command += " ORDER BY %s ASC" % orderby
185  data = self.retrieve_data(sql_command)
186  return data
187 
188  def get_fields_string(self, fields, field_delim=","):
189  if fields:
190  return field_delim.join(fields)
191  return "*"
192 
193  def close(self):
194  """ Closes the database """
195  self.check_if_is_connected()
196  self.cursor.close()
197  self.connection.close()
198 
199  def get_condition_string(self, fields, values):
200  """ creates a condition applying each value to each field
201  """
202  s = ""
203  for field, value in zip(fields, values):
204  s += "%s=%s AND " % (field, value)
205  # remove last AND
206  n = len(s)
207  s = s[0:n - 5]
208  return s
209 
210  def get_table_types(self, name):
211  """
212  Gets info about a table and returns all the types in it
213  """
214  self.check_if_is_connected()
215  sql_command = "PRAGMA table_info(%s)" % name
216  self.cursor.execute(sql_command)
217  info = self.cursor.fetchall()
218  types = []
219  for row in info:
220  if row[2] == "INT":
221  types.append(int)
222  elif row[2] == "DOUBLE":
223  types.append(float)
224  elif row[2][0:7] == "VARCHAR":
225  types.append(str)
226  return types
227 
228  def get_table_column_names(self, name):
229  """
230  Get the names of the columns for a given table
231  """
232  self.check_if_is_connected()
233  sql_command = "PRAGMA table_info(%s)" % name
234  self.cursor.execute(sql_command)
235  info = self.cursor.fetchall()
236  return [row[1] for row in info]
237 
238  def execute_sql_command(self, sql_command):
239  self.check_if_is_connected()
240  self.cursor.execute(sql_command)
241  self.connection.commit()
242 
243  def add_column(self, table, column, data_type):
244  """
245  Add a column to a table
246  column - the name of the column.
247  data_type - the type: int, float, str
248  """
249  sql_typename = get_sql_type_name(data_type)
250  sql_command = "ALTER TABLE %s ADD %s %s" % (
251  table, column, sql_typename)
252  self.execute_sql_command(sql_command)
253 
254  def add_columns(self, table, names, types, check=True):
255  """
256  Add columns to the database. If check=True, columns with names
257  already in the database are skipped. If check=False no check
258  is done and trying to add a column that already exists will
259  raise and exception
260  """
261  col_names = self.get_table_column_names(table)
262  if check:
263  for name, dtype in zip(names, types):
264  if name not in col_names:
265  self.add_column(table, name, dtype)
266  else:
267  for name, dtype in zip(names, types):
268  self.add_column(table, name, dtype)
269 
270  def get_tables_names(self):
271  sql_command = """ SELECT tbl_name FROM sqlite_master """
272  data = self.retrieve_data(sql_command)
273  names = [d[0] for d in data]
274  return names
275 
276  def select_table(self):
277  """
278  Prompt for tables so the user can choose one
279  """
280  table_name = ""
281  self.check_if_is_connected()
282  tables = self.get_tables_names()
283  for t in tables:
284  say = ''
285  while say not in ('n', 'y'):
286  say = input("Use table %s (y/n) " % t)
287  if say == 'y':
288  table_name = t
289  columns = self.get_table_column_names(t)
290  break
291  return table_name, columns
292 
293  def drop_columns(self, table, columns):
294 
295  cnames = self.get_table_column_names(table)
296  for name in columns:
297  cnames.remove(name)
298  names_txt = ", ".join(cnames)
299  sql_command = [
300  "CREATE TEMPORARY TABLE backup(%s);" % names_txt,
301  "INSERT INTO backup SELECT %s FROM %s" % (names_txt, table),
302  "DROP TABLE %s;" % table,
303  "CREATE TABLE %s(%s);" % (table, names_txt),
304  "INSERT INTO %s SELECT * FROM backup;" % table,
305  "DROP TABLE backup;",
306  ]
307  for command in sql_command:
308  log.debug(command)
309 # print command
310  self.cursor.execute(command)
311 
312 
313 def print_data(data, delimiter=" "):
314  """ Prints the data recovered from a database """
315  for row in data:
316  line = delimiter.join([str(x) for x in row])
317  print(line)
318 
319 
320 def write_data(data, output_file, delimiter=" "):
321  """writes data to a file. The output file is expected to be a python
322  file object """
323  w = csv.writer(output_file, delimiter=delimiter)
324  for row in data:
325  w.writerow(row)
326 
327 
328 def get_sql_type_name(data_type):
329  if data_type == int:
330  return "INT"
331  elif data_type == float:
332  return "DOUBLE"
333  elif data_type == str:
334  return (
335  # 10 is a random number, SQLITE does not chop strings
336  "VARCHAR(10)"
337  )
338 
339 
340 def open(fn_database):
341  db = Database2()
342  db.connect(fn_database)
343  return db
344 
345 
346 def read_data(fn_database, sql_command):
347  db = Database2()
348  db.connect(fn_database)
349  data = db.retrieve_data(sql_command)
350  db.close()
351  return data
352 
353 
355  """ Return indices that sort the list ls"""
356  pairs = sorted([(element, i) for i, element in enumerate(ls)])
357  indices = [p[1] for p in pairs]
358  return indices
359 
360 
361 def merge_databases(fns, fn_output, tbl):
362  """
363  Reads a table from a set of database files into a single file
364  Makes sure to reorder all column names if necessary before merging
365  """
366  # Get names and types of the columns from first database file
367  db = Database2()
368  db.connect(fns[0])
369  names = db.get_table_column_names(tbl)
370  types = db.get_table_types(tbl)
371  indices = get_sorting_indices(names)
372  sorted_names = [names[i] for i in indices]
373  sorted_types = [types[i] for i in indices]
374  log.info("Merging databases. Saving to %s", fn_output)
375  out_db = Database2()
376  out_db.create(fn_output, overwrite=True)
377  out_db.connect(fn_output)
378  out_db.create_table(tbl, sorted_names, sorted_types)
379  for fn in fns:
380  log.debug("Reading %s", fn)
381  db.connect(fn)
382  names = sorted(db.get_table_column_names(tbl))
383  they_are_sorted = ",".join(names)
384  log.debug("Retrieving %s", they_are_sorted)
385  sql_command = "SELECT %s FROM %s" % (they_are_sorted, tbl)
386  data = db.retrieve_data(sql_command)
387  out_db.store_data(tbl, data)
388  db.close()
389  out_db.close()
def get_table_column_names
Get the names of the columns for a given table.
Definition: database.py:228
def close
Closes the database.
Definition: database.py:193
Class to manage a SQL database built with sqlite3.
Definition: database.py:13
def get_table_types
Gets info about a table and returns all the types in it.
Definition: database.py:210
def get_sorting_indices
Return indices that sort the list ls.
Definition: database.py:354
def store_data
Inserts information in a given table of the database.
Definition: database.py:92
def get_condition_string
creates a condition applying each value to each field
Definition: database.py:199
def create_table
Creates a table.
Definition: database.py:45
def retrieve_data
Retrieves data from the database using the sql_command returns the records as a list of tuples...
Definition: database.py:114
def create
Creates a database by simply connecting to the file.
Definition: database.py:25
def print_data
Prints the data recovered from a database.
Definition: database.py:313
def get_table
Returns th fields requested from the table.
Definition: database.py:179
def drop_table
Delete a table if it exists.
Definition: database.py:61
def create_view
creates a view of the given table where the values are selected using the condition values...
Definition: database.py:142
def connect
Connects to the database in filename.
Definition: database.py:32
def add_column
Add a column to a table column - the name of the column.
Definition: database.py:243
def store_dataV1
Inserts information in a given table of the database.
Definition: database.py:72
def select_table
Prompt for tables so the user can choose one.
Definition: database.py:276
def merge_databases
Reads a table from a set of database files into a single file Makes sure to reorder all column names ...
Definition: database.py:361
def add_columns
Add columns to the database.
Definition: database.py:254
def drop_view
Removes a view from the database.
Definition: database.py:175
def update_data
updates the register in the table identified by the condition values for the condition fields ...
Definition: database.py:122
def check_if_is_connected
Checks if the class is connected to the database filename.
Definition: database.py:39
def write_data
writes data to a file.
Definition: database.py:320