File.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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.base import NamedBaseModel
  11. from pycs.database.util import commit_on_return
  12. class File(NamedBaseModel):
  13. """
  14. database class for files
  15. """
  16. # table columns
  17. uuid = db.Column(db.String, nullable=False)
  18. extension = db.Column(db.String, nullable=False)
  19. type = db.Column(db.String, nullable=False)
  20. size = db.Column(db.String, nullable=False)
  21. created = db.Column(db.DateTime, default=datetime.utcnow,
  22. index=True, nullable=False)
  23. path = db.Column(db.String, nullable=False)
  24. frames = db.Column(db.Integer)
  25. fps = db.Column(db.Float)
  26. project_id = db.Column(
  27. db.Integer,
  28. db.ForeignKey("project.id", ondelete="CASCADE"),
  29. nullable=False)
  30. collection_id = db.Column(
  31. db.Integer,
  32. db.ForeignKey("collection.id", ondelete="SET NULL"))
  33. # contraints
  34. __table_args__ = (
  35. db.UniqueConstraint('project_id', 'path'),
  36. )
  37. results = db.relationship("Result", backref="file",
  38. lazy="dynamic", passive_deletes=True)
  39. serialize_only = NamedBaseModel.serialize_only + (
  40. "uuid",
  41. "extension",
  42. "type",
  43. "size",
  44. "created",
  45. "path",
  46. "frames",
  47. "fps",
  48. "project_id",
  49. "collection_id",
  50. )
  51. @property
  52. def filename(self):
  53. return f"{self.name}{self.extension}"
  54. @property
  55. def absolute_path(self) -> str:
  56. path = Path(self.path)
  57. if path.is_absolute():
  58. return str(path)
  59. return str(Path.cwd() / path)
  60. def delete(self, commit: bool = True):
  61. super().delete(commit=commit)
  62. # remove file from folder
  63. os.remove(self.path)
  64. # TODO: remove temp files
  65. warnings.warn("Temporary files may still exist!")
  66. @commit_on_return
  67. def set_collection(self, collection_id: T.Optional[int]):
  68. """
  69. set this file's collection
  70. :param collection_id: new collection id
  71. :return:
  72. """
  73. self.collection_id = collection_id
  74. @commit_on_return
  75. def set_collection_by_reference(self, collection_reference: T.Optional[str]):
  76. """
  77. set this file's collection
  78. :param collection_reference: collection reference
  79. :return:
  80. """
  81. if self.collection_reference is None:
  82. self.set_collection(None)
  83. collection = Collection.query.filter_by(reference=collection_reference).one()
  84. self.collection_id = collection.id
  85. def _get_another_file(self, *query) -> T.Optional[File]:
  86. """
  87. get the first file matching the query ordered by descending id
  88. :return: another file or None
  89. """
  90. return File.query.filter(File.project_id == self.project_id, *query)
  91. def next(self) -> T.Optional[File]:
  92. """
  93. get the successor of this file
  94. :return: another file or None
  95. """
  96. return self._get_another_file(File.id > self.id)\
  97. .order_by(File.id).first()
  98. def previous(self) -> T.Optional[File]:
  99. """
  100. get the predecessor of this file
  101. :return: another file or None
  102. """
  103. return self._get_another_file(File.id < self.id)\
  104. .order_by(File.id.desc()).first()
  105. def next_in_collection(self) -> T.Optional[File]:
  106. """
  107. get the predecessor of this file
  108. :return: another file or None
  109. """
  110. return self._get_another_file(
  111. File.id > self.id, File.collection_id == self.collection_id)\
  112. .order_by(File.id).first()
  113. def previous_in_collection(self) -> T.Optional[File]:
  114. """
  115. get the predecessor of this file
  116. :return: another file or None
  117. """
  118. return self._get_another_file(
  119. File.id < self.id, File.collection_id == self.collection_id)\
  120. .order_by(File.id.desc()).first()
  121. def result(self, id: int) -> T.Optional[Result]:
  122. return self.results.get(id)
  123. @commit_on_return
  124. def create_result(self,
  125. origin: str,
  126. result_type: str,
  127. label: T.Optional[T.Union[Label, int]] = None,
  128. data: T.Optional[dict] = None) -> Result:
  129. result = Result.new(commit=False,
  130. file_id=self.id,
  131. origin=origin,
  132. type=result_type)
  133. result.data = data
  134. if label is not None:
  135. assert isinstance(label, (int, Label)), f"Wrong label type: {type(label)}"
  136. if isinstance(label, Label):
  137. label = label.id
  138. result.label_id = label
  139. return result
  140. def remove_results(self, origin='pipeline') -> T.List[Result]:
  141. results = Result.query.filter(
  142. Result.file_id == self.id,
  143. Result.origin == origin)
  144. _results = results.all()
  145. results.delete()
  146. return _results