slicer_pipeline.py 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. """Pydantic schemas for the Slicer Pipeline API (#1425, PR A).
  2. A pipeline bundles printer / process / filament(s) / bed-type picks under a
  3. reusable name. PR A surfaces only the bundle; target_kind / target_printer_id /
  4. target_model_class / fanout_strategy are persisted but the API treats them as
  5. opaque defaults — they come alive in PR B (single-target dispatch) and PR C
  6. (multi-copy + class targeting + fanout).
  7. """
  8. from datetime import datetime
  9. from typing import Literal
  10. from pydantic import BaseModel, Field
  11. from backend.app.schemas.not_null import IgnoresNullForRequiredColumns
  12. from backend.app.schemas.slicer import PresetRef
  13. class SlicerPipelineBase(BaseModel):
  14. """Fields editable on create + update."""
  15. name: str = Field(..., min_length=1, max_length=200)
  16. description: str | None = Field(default=None, max_length=1000)
  17. printer_preset: PresetRef
  18. process_preset: PresetRef
  19. filament_presets: list[PresetRef] = Field(
  20. ...,
  21. min_length=1,
  22. description="One PresetRef per AMS slot. Order matches the source plate's filament-slot order.",
  23. )
  24. bed_type: str | None = Field(default=None, max_length=64)
  25. class SlicerPipelineCreate(SlicerPipelineBase):
  26. """Payload for POST /slicer-pipelines."""
  27. class SlicerPipelineUpdate(IgnoresNullForRequiredColumns):
  28. """Payload for PUT /slicer-pipelines/{id}. All fields optional; only those
  29. present are written. Preset and filament list are replaced wholesale when
  30. set (we don't support partial filament-slot edits)."""
  31. __orm_table__ = "slicer_pipelines"
  32. name: str | None = Field(default=None, min_length=1, max_length=200)
  33. description: str | None = Field(default=None, max_length=1000)
  34. printer_preset: PresetRef | None = None
  35. process_preset: PresetRef | None = None
  36. filament_presets: list[PresetRef] | None = Field(default=None, min_length=1)
  37. bed_type: str | None = Field(default=None, max_length=64)
  38. # PR B target binding. ``target_kind='specific_printer'`` requires
  39. # ``target_printer_id`` to be set OR cleared in the same payload (route
  40. # handler enforces). ``target_kind='printer_class'`` is wired by PR C
  41. # together with ``target_model_class`` (a Bambu model code like 'X1C')
  42. # and the fanout strategy that distributes copies across matching
  43. # printers.
  44. target_kind: Literal["specific_printer", "printer_class"] | None = None
  45. target_printer_id: int | None = None
  46. target_model_class: str | None = Field(default=None, max_length=20)
  47. fanout_strategy: Literal["max_parallel", "fill_one_first", "round_robin"] | None = None
  48. class SlicerPipelineResponse(SlicerPipelineBase):
  49. """A single pipeline as returned by the API."""
  50. id: int
  51. created_by: int | None
  52. created_at: datetime
  53. updated_at: datetime
  54. # Echoed for PR B+ readiness; PR A always returns the persisted defaults.
  55. target_kind: Literal["specific_printer", "printer_class"] = "printer_class"
  56. target_printer_id: int | None = None
  57. target_model_class: str | None = None
  58. fanout_strategy: Literal["max_parallel", "fill_one_first", "round_robin"] = "max_parallel"
  59. model_config = {"from_attributes": True}
  60. class SlicerPipelineListResponse(BaseModel):
  61. """Wraps the list so the response stays additive when run/job counts get
  62. surfaced in PR B+ (e.g. a ``meta`` field for last-run timestamps)."""
  63. pipelines: list[SlicerPipelineResponse] = []