sqlite.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (c) 2021 Project CHIP Authors
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. """Wrapper and utility functions around sqlite3"""
  18. import sqlite3
  19. from typing import List, Optional
  20. import pandas as pd # type: ignore
  21. from memdf import Config, ConfigDescription
  22. CONFIG: ConfigDescription = {
  23. Config.group_def('database'): {
  24. 'title': 'database options',
  25. },
  26. 'database.file': {
  27. 'help': 'Sqlite3 file',
  28. 'metavar': 'FILENAME',
  29. 'default': None,
  30. 'argparse': {
  31. 'alias': ['--db'],
  32. },
  33. },
  34. }
  35. class Database:
  36. """Wrapper and utility functions around sqlite3"""
  37. on_open: Optional[List[str]] = None
  38. on_writable: Optional[List[str]] = None
  39. def __init__(self, filename: str, writable: bool = True):
  40. self.filename = filename
  41. self.writable = writable
  42. self.con: Optional[sqlite3.Connection] = None
  43. def __enter__(self):
  44. return self.open()
  45. def __exit__(self, et, ev, traceback):
  46. self.close()
  47. return False
  48. def open(self):
  49. """Open and initialize the database connection."""
  50. if not self.con:
  51. db = 'file:' + self.filename
  52. if not self.writable:
  53. db += '?mode=ro'
  54. self.con = sqlite3.connect(db, uri=True)
  55. if self.on_open:
  56. for i in self.on_open:
  57. self.con.execute(i)
  58. if self.writable and self.on_writable:
  59. for i in self.on_writable:
  60. self.con.execute(i)
  61. return self
  62. def close(self):
  63. if self.con:
  64. self.con.close()
  65. self.con = None
  66. return self
  67. def connection(self) -> sqlite3.Connection:
  68. assert self.con
  69. return self.con
  70. def execute(self, query, parameters=None):
  71. if parameters:
  72. return self.con.execute(query, parameters)
  73. return self.con.execute(query)
  74. def commit(self):
  75. self.con.commit()
  76. return self
  77. def store(self, table: str, **kwargs):
  78. """Insert the data if it does not already exist."""
  79. q = (f"INSERT INTO {table} ({','.join(kwargs.keys())})"
  80. f" VALUES ({','.join('?' * len(kwargs))})"
  81. f" ON CONFLICT DO NOTHING")
  82. v = list(kwargs.values())
  83. self.connection().execute(q, v)
  84. def get_matching(self, table: str, columns: List[str], **kwargs):
  85. q = (f"SELECT {','.join(columns)} FROM {table}"
  86. f" WHERE {'=? AND '.join(kwargs.keys())}=?")
  87. v = list(kwargs.values())
  88. return self.connection().execute(q, v)
  89. def get_matching_id(self, table: str, **kwargs):
  90. cur = self.get_matching(table, ['id'], **kwargs)
  91. row = cur.fetchone()
  92. if row:
  93. return row[0]
  94. return None
  95. def store_and_return_id(self, table: str, **kwargs) -> Optional[int]:
  96. self.store(table, **kwargs)
  97. return self.get_matching_id(table, **kwargs)
  98. def data_frame(self, query, parameters=None) -> pd.DataFrame:
  99. """Return the results of a query as a DataFrame."""
  100. cur = self.execute(query, parameters)
  101. columns = [i[0] for i in cur.description]
  102. df = pd.DataFrame(cur.fetchall(), columns=columns)
  103. self.commit()
  104. df.attrs = {'title': query}
  105. return df