File.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. from __future__ import annotations
  2. import os
  3. import typing as T
  4. import warnings
  5. from datetime import datetime
  6. from pathlib import Path
  7. from pycs import db
  8. from pycs.database.Collection import Collection
  9. from pycs.database.Result import Result
  10. from pycs.database.Label import Label
  11. from pycs.database.base import NamedBaseModel
  12. from pycs.database.util import commit_on_return
  13. class File(NamedBaseModel):
  14. """ DB Model for files """
  15. # table columns
  16. uuid = db.Column(db.String, nullable=False)
  17. extension = db.Column(db.String, nullable=False)
  18. type = db.Column(db.String, nullable=False)
  19. size = db.Column(db.Integer, nullable=False)
  20. created = db.Column(db.DateTime, default=datetime.utcnow,
  21. index=True, nullable=False)
  22. path = db.Column(db.String, nullable=False)
  23. frames = db.Column(db.Integer)
  24. fps = db.Column(db.Float)
  25. project_id = db.Column(
  26. db.Integer,
  27. db.ForeignKey("project.id", ondelete="CASCADE"),
  28. nullable=False)
  29. collection_id = db.Column(
  30. db.Integer,
  31. db.ForeignKey("collection.id", ondelete="SET NULL"))
  32. # contraints
  33. __table_args__ = (
  34. db.UniqueConstraint('project_id', 'path'),
  35. )
  36. results = db.relationship("Result",
  37. backref="file",
  38. lazy="dynamic",
  39. passive_deletes=True,
  40. )
  41. serialize_only = NamedBaseModel.serialize_only + (
  42. "uuid",
  43. "extension",
  44. "type",
  45. "size",
  46. "created",
  47. "path",
  48. "frames",
  49. "fps",
  50. "project_id",
  51. "collection_id",
  52. )
  53. @property
  54. def filename(self):
  55. """ filename consisting of a name and an extension """
  56. return f"{self.name}{self.extension}"
  57. @property
  58. def absolute_path(self) -> str:
  59. """ returns an absolute of the file """
  60. path = Path(self.path)
  61. if path.is_absolute():
  62. return str(path)
  63. return str(Path.cwd() / path)
  64. # pylint: disable=arguments-differ
  65. def delete(self, commit: bool = True):
  66. """
  67. after the object is deleted, the according physical file
  68. is also delete if commit was True
  69. """
  70. # pylint: disable=unexpected-keyword-arg
  71. dump = super().delete(commit=commit)
  72. if commit:
  73. os.remove(self.path)
  74. # TODO: remove temp files
  75. warnings.warn("Temporary files may still exist!")
  76. return dump
  77. @commit_on_return
  78. def set_collection(self, collection_id: T.Optional[int]):
  79. """
  80. set this file's collection
  81. :param collection_id: new collection id
  82. :return:
  83. """
  84. self.collection_id = collection_id
  85. @commit_on_return
  86. def set_collection_by_reference(self, collection_reference: T.Optional[str]):
  87. """
  88. set this file's collection
  89. :param collection_reference: collection reference
  90. :return:
  91. """
  92. if self.collection_reference is None:
  93. self.set_collection(None)
  94. collection = Collection.query.filter_by(reference=collection_reference).one()
  95. self.collection_id = collection.id
  96. def _get_another_file(self, *query) -> T.Optional[File]:
  97. """
  98. get the first file matching the query ordered by descending id
  99. :return: another file or None
  100. """
  101. return File.query.filter(File.project_id == self.project_id, *query)
  102. def next(self) -> T.Optional[File]:
  103. """
  104. get the successor of this file
  105. :return: another file or None
  106. """
  107. return self._get_another_file(File.id > self.id)\
  108. .order_by(File.id).first()
  109. def previous(self) -> T.Optional[File]:
  110. """
  111. get the predecessor of this file
  112. :return: another file or None
  113. """
  114. # pylint: disable=no-member
  115. return self._get_another_file(File.id < self.id)\
  116. .order_by(File.id.desc()).first()
  117. def next_in_collection(self) -> T.Optional[File]:
  118. """
  119. get the predecessor of this file
  120. :return: another file or None
  121. """
  122. return self._get_another_file(
  123. File.id > self.id, File.collection_id == self.collection_id)\
  124. .order_by(File.id).first()
  125. def previous_in_collection(self) -> T.Optional[File]:
  126. """
  127. get the predecessor of this file
  128. :return: another file or None
  129. """
  130. # pylint: disable=no-member
  131. return self._get_another_file(
  132. File.id < self.id, File.collection_id == self.collection_id)\
  133. .order_by(File.id.desc()).first()
  134. def result(self, identifier: int) -> T.Optional[Result]:
  135. """
  136. get one of the file's results
  137. :return: result object or None
  138. """
  139. return self.results.get(identifier)
  140. @commit_on_return
  141. def create_result(self,
  142. origin: str,
  143. result_type: str,
  144. label: T.Optional[T.Union[Label, int]] = None,
  145. data: T.Optional[dict] = None) -> Result:
  146. """
  147. Creates a result and returns the created object
  148. :return: result object
  149. """
  150. result = Result.new(commit=False,
  151. file_id=self.id,
  152. origin=origin,
  153. type=result_type)
  154. result.data = data
  155. if label is not None:
  156. assert isinstance(label, (int, Label)), f"Wrong label type: {type(label)}"
  157. if isinstance(label, Label):
  158. label = label.id
  159. result.label_id = label
  160. return result
  161. def remove_results(self, origin='pipeline') -> T.List[Result]:
  162. """
  163. Remove assigned results, but return them.
  164. :return: list of result objects
  165. """
  166. results = Result.query.filter(
  167. Result.file_id == self.id,
  168. Result.origin == origin)
  169. _results = [r.serialize() for r in results.all()]
  170. results.delete()
  171. return _results