library.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. """Pydantic schemas for library (File Manager) functionality."""
  2. from datetime import datetime
  3. from pydantic import BaseModel, Field
  4. # ============ Folder Schemas ============
  5. class FolderCreate(BaseModel):
  6. """Schema for creating a new folder."""
  7. name: str = Field(..., min_length=1, max_length=255)
  8. parent_id: int | None = None
  9. project_id: int | None = None
  10. archive_id: int | None = None
  11. class ExternalFolderCreate(BaseModel):
  12. """Schema for linking an external folder."""
  13. name: str = Field(..., min_length=1, max_length=255)
  14. external_path: str = Field(..., min_length=1, max_length=500)
  15. readonly: bool = True
  16. show_hidden: bool = False
  17. parent_id: int | None = None
  18. class FolderUpdate(BaseModel):
  19. """Schema for updating a folder."""
  20. name: str | None = Field(None, min_length=1, max_length=255)
  21. parent_id: int | None = None
  22. project_id: int | None = None # 0 to unlink
  23. archive_id: int | None = None # 0 to unlink
  24. class FolderResponse(BaseModel):
  25. """Schema for folder response."""
  26. id: int
  27. name: str
  28. parent_id: int | None
  29. project_id: int | None = None
  30. archive_id: int | None = None
  31. project_name: str | None = None
  32. archive_name: str | None = None
  33. is_external: bool = False
  34. external_path: str | None = None
  35. external_readonly: bool = False
  36. external_show_hidden: bool = False
  37. file_count: int = 0 # Computed field
  38. # max(folder.updated_at, max(immediate-child file.updated_at)). Used by the
  39. # File Manager folder tree's "sort by recent activity" mode (#1770) so that
  40. # adding a file inside a folder bubbles it up — folder.updated_at alone only
  41. # tracks rename/move events. Recursion across subfolders is intentionally
  42. # left out to keep the route a single GROUP BY rather than a recursive CTE.
  43. latest_activity_at: datetime | None = None
  44. created_at: datetime
  45. updated_at: datetime
  46. class Config:
  47. from_attributes = True
  48. class FolderReadmeResponse(BaseModel):
  49. """Markdown sidebar payload for a folder (#1268).
  50. ``filename`` is the on-disk name (so the UI can show "README.md") and
  51. ``content`` is the raw markdown — the FE renders it. ``truncated`` is
  52. True when the source file was clipped at the size cap.
  53. """
  54. filename: str
  55. content: str
  56. truncated: bool
  57. class FolderTreeItem(BaseModel):
  58. """Schema for folder tree item (includes children)."""
  59. id: int
  60. name: str
  61. parent_id: int | None
  62. project_id: int | None = None
  63. archive_id: int | None = None
  64. project_name: str | None = None
  65. archive_name: str | None = None
  66. is_external: bool = False
  67. external_path: str | None = None
  68. external_readonly: bool = False
  69. file_count: int = 0
  70. # See FolderResponse.latest_activity_at — #1770 folder sort source.
  71. latest_activity_at: datetime | None = None
  72. children: list["FolderTreeItem"] = []
  73. class Config:
  74. from_attributes = True
  75. # ============ File Schemas ============
  76. class FileCreate(BaseModel):
  77. """Schema for creating a file entry (internal use after upload)."""
  78. filename: str
  79. file_path: str
  80. file_type: str
  81. file_size: int
  82. file_hash: str | None = None
  83. thumbnail_path: str | None = None
  84. metadata: dict | None = None
  85. folder_id: int | None = None
  86. project_id: int | None = None
  87. class FileUpdate(BaseModel):
  88. """Schema for updating a file."""
  89. filename: str | None = Field(None, min_length=1, max_length=255)
  90. folder_id: int | None = None
  91. project_id: int | None = None
  92. notes: str | None = None
  93. class FileDuplicate(BaseModel):
  94. """Reference to a duplicate file."""
  95. id: int
  96. filename: str
  97. folder_id: int | None
  98. folder_name: str | None
  99. created_at: datetime
  100. class FileResponse(BaseModel):
  101. """Schema for file response."""
  102. id: int
  103. folder_id: int | None
  104. folder_name: str | None = None
  105. project_id: int | None
  106. project_name: str | None = None
  107. is_external: bool = False
  108. filename: str
  109. file_path: str
  110. file_type: str
  111. file_size: int
  112. file_hash: str | None
  113. thumbnail_path: str | None
  114. metadata: dict | None
  115. print_count: int
  116. last_printed_at: datetime | None
  117. notes: str | None
  118. # Duplicate detection
  119. duplicates: list[FileDuplicate] | None = None
  120. duplicate_count: int = 0
  121. # User tracking (Issue #206)
  122. created_by_id: int | None = None
  123. created_by_username: str | None = None
  124. created_at: datetime
  125. updated_at: datetime
  126. # Metadata fields
  127. print_name: str | None = None
  128. print_time_seconds: int | None = None
  129. filament_used_grams: float | None = None
  130. sliced_for_model: str | None = None
  131. class Config:
  132. from_attributes = True
  133. class FileListResponse(BaseModel):
  134. """Schema for file list item (lighter than full response)."""
  135. id: int
  136. folder_id: int | None
  137. is_external: bool = False
  138. filename: str
  139. file_type: str
  140. file_size: int
  141. thumbnail_path: str | None
  142. print_count: int
  143. duplicate_count: int = 0
  144. # User tracking (Issue #206)
  145. created_by_id: int | None = None
  146. created_by_username: str | None = None
  147. created_at: datetime
  148. # Key metadata fields for display
  149. print_name: str | None = None
  150. print_time_seconds: int | None = None
  151. filament_used_grams: float | None = None
  152. sliced_for_model: str | None = None
  153. class Config:
  154. from_attributes = True
  155. class FileMoveRequest(BaseModel):
  156. """Schema for moving files to a folder."""
  157. file_ids: list[int]
  158. folder_id: int | None = None # None = move to root
  159. class FilePrintRequest(BaseModel):
  160. """Schema for printing a file from the library.
  161. Note: printer_id is passed as a query parameter, not in the body.
  162. """
  163. # Print options (same as archive reprint)
  164. plate_id: int | None = None
  165. plate_name: str | None = None
  166. ams_mapping: list[int] | None = None
  167. bed_levelling: bool = True
  168. flow_cali: bool = False
  169. vibration_cali: bool = True
  170. layer_inspect: bool = False
  171. timelapse: bool = False
  172. use_ams: bool = True
  173. nozzle_offset_cali: bool = True # Dual-nozzle printers only — MQTT-gated (#1682)
  174. # Project to associate the resulting archive with
  175. project_id: int | None = None
  176. # When true, delete the LibraryFile row + disk file after the archive has
  177. # been created and the print has been dispatched. Used by the Printers-page
  178. # Direct-Print flow (click / drag-drop a file onto a printer card) so the
  179. # transient upload doesn't linger in File Manager. Cleanup is skipped on
  180. # external library files.
  181. cleanup_library_after_dispatch: bool = False
  182. class FileUploadResponse(BaseModel):
  183. """Schema for file upload response."""
  184. id: int
  185. filename: str
  186. file_type: str
  187. file_size: int
  188. thumbnail_path: str | None
  189. duplicate_of: int | None = None # ID of existing file with same hash
  190. metadata: dict | None = None
  191. # ============ Bulk Operations ============
  192. class BulkDeleteRequest(BaseModel):
  193. """Schema for bulk delete operations."""
  194. file_ids: list[int] = []
  195. folder_ids: list[int] = []
  196. class BulkDeleteResponse(BaseModel):
  197. """Schema for bulk delete response."""
  198. deleted_files: int
  199. deleted_folders: int
  200. # ============ Queue Operations ============
  201. class AddToQueueRequest(BaseModel):
  202. """Schema for adding library files to the print queue."""
  203. file_ids: list[int] = Field(..., min_length=1)
  204. class AddToQueueResult(BaseModel):
  205. """Result for a single file added to queue."""
  206. file_id: int
  207. filename: str
  208. queue_item_id: int
  209. class AddToQueueError(BaseModel):
  210. """Error for a file that couldn't be added to queue."""
  211. file_id: int
  212. filename: str
  213. error: str
  214. class AddToQueueResponse(BaseModel):
  215. """Schema for add-to-queue response."""
  216. added: list[AddToQueueResult]
  217. errors: list[AddToQueueError]
  218. # ============ ZIP Extraction ============
  219. class ZipExtractResult(BaseModel):
  220. """Result for a single file extracted from ZIP."""
  221. filename: str
  222. file_id: int
  223. folder_id: int | None = None
  224. class ZipExtractError(BaseModel):
  225. """Error for a file that couldn't be extracted."""
  226. filename: str
  227. error: str
  228. class ZipExtractResponse(BaseModel):
  229. """Schema for ZIP extraction response."""
  230. extracted: int
  231. folders_created: int
  232. files: list[ZipExtractResult]
  233. errors: list[ZipExtractError]
  234. # ============ STL Thumbnail Generation ============
  235. class BatchThumbnailRequest(BaseModel):
  236. """Schema for batch STL thumbnail generation request."""
  237. file_ids: list[int] | None = None
  238. folder_id: int | None = None
  239. all_missing: bool = False
  240. class BatchThumbnailResult(BaseModel):
  241. """Result for a single file thumbnail generation."""
  242. file_id: int
  243. filename: str
  244. success: bool
  245. error: str | None = None
  246. class BatchThumbnailResponse(BaseModel):
  247. """Schema for batch thumbnail generation response."""
  248. processed: int
  249. succeeded: int
  250. failed: int
  251. results: list[BatchThumbnailResult]