Database.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. import sqlite3
  2. from contextlib import closing
  3. from time import time
  4. from typing import Optional, List
  5. from pycs.database.File import File
  6. from pycs.database.LabelProvider import LabelProvider
  7. from pycs.database.Model import Model
  8. from pycs.database.Project import Project
  9. from pycs.database.Result import Result
  10. from pycs.database.discovery.LabelProviderDiscovery import discover as discover_label_providers
  11. from pycs.database.discovery.ModelDiscovery import discover as discover_models
  12. class Database:
  13. """
  14. opens an sqlite database and allows to access several objects
  15. """
  16. def __init__(self, path: str = ':memory:', discovery=True):
  17. """
  18. opens or creates a given sqlite database and creates all required tables
  19. :param path: path to sqlite database
  20. """
  21. # save properties
  22. self.path = path
  23. # initialize database connection
  24. self.con = sqlite3.connect(path)
  25. self.con.execute("PRAGMA foreign_keys = ON")
  26. # create tables
  27. with closing(self.con.cursor()) as cursor:
  28. cursor.execute('''
  29. CREATE TABLE IF NOT EXISTS models (
  30. id INTEGER PRIMARY KEY,
  31. name TEXT NOT NULL,
  32. description TEXT,
  33. root_folder TEXT NOT NULL UNIQUE,
  34. supports TEXT NOT NULL
  35. )
  36. ''')
  37. cursor.execute('''
  38. CREATE TABLE IF NOT EXISTS label_providers (
  39. id INTEGER PRIMARY KEY,
  40. name TEXT NOT NULL,
  41. description TEXT,
  42. root_folder TEXT NOT NULL UNIQUE
  43. )
  44. ''')
  45. cursor.execute('''
  46. CREATE TABLE IF NOT EXISTS projects (
  47. id INTEGER PRIMARY KEY,
  48. name TEXT NOT NULL,
  49. description TEXT,
  50. created INTEGER NOT NULL,
  51. model INTEGER,
  52. label_provider INTEGER,
  53. root_folder TEXT NOT NULL UNIQUE,
  54. external_data BOOL NOT NULL,
  55. data_folder TEXT NOT NULL,
  56. FOREIGN KEY (model) REFERENCES models(id)
  57. ON UPDATE CASCADE ON DELETE SET NULL,
  58. FOREIGN KEY (label_provider) REFERENCES label_providers(id)
  59. ON UPDATE CASCADE ON DELETE SET NULL
  60. )
  61. ''')
  62. cursor.execute('''
  63. CREATE TABLE IF NOT EXISTS labels (
  64. id INTEGER PRIMARY KEY,
  65. project INTEGER NOT NULL,
  66. parent INTEGER,
  67. created INTEGER NOT NULL,
  68. reference TEXT,
  69. name TEXT NOT NULL,
  70. FOREIGN KEY (project) REFERENCES projects(id)
  71. ON UPDATE CASCADE ON DELETE CASCADE,
  72. FOREIGN KEY (parent) REFERENCES labels(id)
  73. ON UPDATE CASCADE ON DELETE SET NULL,
  74. UNIQUE(project, reference)
  75. )
  76. ''')
  77. cursor.execute('''
  78. CREATE TABLE IF NOT EXISTS files (
  79. id INTEGER PRIMARY KEY,
  80. uuid TEXT NOT NULL,
  81. project INTEGER NOT NULL,
  82. type TEXT NOT NULL,
  83. name TEXT NOT NULL,
  84. extension TEXT NOT NULL,
  85. size INTEGER NOT NULL,
  86. created INTEGER NOT NULL,
  87. path TEXT NOT NULL,
  88. frames INTEGER,
  89. fps FLOAT,
  90. FOREIGN KEY (project) REFERENCES projects(id)
  91. ON UPDATE CASCADE ON DELETE CASCADE,
  92. UNIQUE(project, path)
  93. )
  94. ''')
  95. cursor.execute('''
  96. CREATE TABLE IF NOT EXISTS results (
  97. id INTEGER PRIMARY KEY,
  98. file INTEGER NOT NULL,
  99. origin TEXT NOT NULL,
  100. type TEXT NOT NULL,
  101. label INTEGER,
  102. data TEXT NOT NULL,
  103. FOREIGN KEY (file) REFERENCES files(id)
  104. ON UPDATE CASCADE ON DELETE CASCADE
  105. )
  106. ''')
  107. cursor.execute('''
  108. CREATE INDEX IF NOT EXISTS idx_results_label ON results(label)
  109. ''')
  110. # run discovery modules
  111. if discovery:
  112. with self:
  113. discover_models(self.con)
  114. discover_label_providers(self.con)
  115. def close(self):
  116. self.con.close()
  117. def __enter__(self):
  118. self.con.__enter__()
  119. def __exit__(self, exc_type, exc_val, exc_tb):
  120. self.con.__exit__(exc_type, exc_val, exc_tb)
  121. def models(self) -> List[Model]:
  122. """
  123. get a list of all available models
  124. :return: list of all available models
  125. """
  126. with closing(self.con.cursor()) as cursor:
  127. cursor.execute('SELECT * FROM models')
  128. return list(map(
  129. lambda row: Model(self, row),
  130. cursor.fetchall()
  131. ))
  132. def model(self, identifier: int) -> Optional[Model]:
  133. """
  134. get a model using its unique identifier
  135. :param identifier: unique identifier
  136. :return: model
  137. """
  138. with closing(self.con.cursor()) as cursor:
  139. cursor.execute('SELECT * FROM models WHERE id = ?', [identifier])
  140. row = cursor.fetchone()
  141. if row is not None:
  142. return Model(self, row)
  143. return None
  144. def label_providers(self) -> List[LabelProvider]:
  145. """
  146. get a list of all available label providers
  147. :return: list of all available label providers
  148. """
  149. with closing(self.con.cursor()) as cursor:
  150. cursor.execute('SELECT * FROM label_providers')
  151. return list(map(
  152. lambda row: LabelProvider(self, row),
  153. cursor.fetchall()
  154. ))
  155. def label_provider(self, identifier: int) -> Optional[LabelProvider]:
  156. """
  157. get a label provider using its unique identifier
  158. :param identifier: unique identifier
  159. :return: label provider
  160. """
  161. with closing(self.con.cursor()) as cursor:
  162. cursor.execute('SELECT * FROM label_providers WHERE id = ?', [identifier])
  163. row = cursor.fetchone()
  164. if row is not None:
  165. return LabelProvider(self, row)
  166. return None
  167. def projects(self) -> List[Project]:
  168. """
  169. get a list of all available projects
  170. :return: list of all available projects
  171. """
  172. with closing(self.con.cursor()) as cursor:
  173. cursor.execute('SELECT * FROM projects')
  174. return list(map(
  175. lambda row: Project(self, row),
  176. cursor.fetchall()
  177. ))
  178. def project(self, identifier: int) -> Optional[Project]:
  179. """
  180. get a project using its unique identifier
  181. :param identifier: unique identifier
  182. :return: project
  183. """
  184. with closing(self.con.cursor()) as cursor:
  185. cursor.execute('SELECT * FROM projects WHERE id = ?', [identifier])
  186. row = cursor.fetchone()
  187. if row is not None:
  188. return Project(self, row)
  189. return None
  190. def create_project(self,
  191. name: str,
  192. description: str,
  193. model: Model,
  194. label_provider: Optional[LabelProvider],
  195. root_folder: str,
  196. external_data: bool,
  197. data_folder: str):
  198. """
  199. insert a project into the database
  200. :param name: project name
  201. :param description: project description
  202. :param model: used model
  203. :param label_provider: used label provider (optional)
  204. :param root_folder: path to project folder
  205. :param external_data: whether an external data directory is used
  206. :param data_folder: path to data folder
  207. :return: created project
  208. """
  209. # prepare some values
  210. created = int(time())
  211. label_provider_id = label_provider.identifier if label_provider is not None else None
  212. # insert statement
  213. with closing(self.con.cursor()) as cursor:
  214. cursor.execute('''
  215. INSERT INTO projects (
  216. name, description, created, model, label_provider, root_folder, external_data,
  217. data_folder
  218. )
  219. VALUES (?, ?, ?, ?, ?, ?, ?, ?)
  220. ''', (name, description, created, model.identifier, label_provider_id, root_folder,
  221. external_data, data_folder))
  222. return self.project(cursor.lastrowid)
  223. def file(self, identifier) -> Optional[File]:
  224. """
  225. get a file using its unique identifier
  226. :param identifier: unique identifier
  227. :return: file
  228. """
  229. with closing(self.con.cursor()) as cursor:
  230. cursor.execute('SELECT * FROM files WHERE id = ?', [identifier])
  231. row = cursor.fetchone()
  232. if row is not None:
  233. return File(self, row)
  234. return None
  235. def result(self, identifier) -> Optional[Result]:
  236. """
  237. get a result using its unique identifier
  238. :param identifier: unique identifier
  239. :return: result
  240. """
  241. with closing(self.con.cursor()) as cursor:
  242. cursor.execute('SELECT * FROM results WHERE id = ?', [identifier])
  243. row = cursor.fetchone()
  244. if row is not None:
  245. return Result(self, row)
  246. return None