cloud.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. from typing import Literal
  2. from pydantic import BaseModel, Field
  3. Region = Literal["global", "china"]
  4. class CloudLoginRequest(BaseModel):
  5. """Request to initiate cloud login."""
  6. email: str = Field(..., description="Bambu Lab account email")
  7. password: str = Field(..., description="Account password")
  8. region: Region = Field(default="global", description="Region: 'global' or 'china'")
  9. class CloudVerifyRequest(BaseModel):
  10. """Request to verify login with 2FA code (email or TOTP)."""
  11. email: str = Field(..., description="Bambu Lab account email")
  12. code: str = Field(..., description="6-digit verification code")
  13. tfa_key: str | None = Field(None, description="TFA key for TOTP verification (from login response)")
  14. region: Region = Field(default="global", description="Region: 'global' or 'china'")
  15. class CloudLoginResponse(BaseModel):
  16. """Response from login attempt."""
  17. success: bool
  18. needs_verification: bool = False
  19. message: str
  20. verification_type: str | None = None # "email" or "totp"
  21. tfa_key: str | None = None # Key needed for TOTP verification
  22. # Machine-readable cause of a failure, when we know it. Currently only
  23. # "captcha" — Bambu's anti-abuse layer is challenging this network and no
  24. # credential will be accepted until it clears (#2790). The UI needs this to
  25. # explain the situation in place, rather than flashing ``message`` as a
  26. # toast that vanishes and leaves the user retrying a password that is fine.
  27. reason: str | None = None
  28. class CloudAuthStatus(BaseModel):
  29. """Current authentication status."""
  30. is_authenticated: bool
  31. email: str | None = None
  32. region: Region | None = None
  33. # True when a token is stored but Bambu no longer accepts it. Both this and
  34. # "never signed in" render the login form, but only this one warrants
  35. # telling the user why it came back.
  36. sign_in_expired: bool = False
  37. class CloudTokenRequest(BaseModel):
  38. """Request to set access token directly."""
  39. access_token: str = Field(..., description="Bambu Lab access token")
  40. region: Region = Field(default="global", description="Region: 'global' or 'china'")
  41. class SlicerSetting(BaseModel):
  42. """A slicer setting/preset."""
  43. setting_id: str
  44. name: str
  45. type: str # filament, printer, process
  46. version: str | None = None
  47. user_id: str | None = None
  48. updated_time: str | None = None
  49. is_custom: bool = False
  50. class SlicerSettingsResponse(BaseModel):
  51. """Response containing slicer settings."""
  52. filament: list[SlicerSetting] = []
  53. printer: list[SlicerSetting] = []
  54. process: list[SlicerSetting] = []
  55. class CloudDevice(BaseModel):
  56. """A bound printer device."""
  57. dev_id: str
  58. name: str
  59. dev_model_name: str | None = None
  60. dev_product_name: str | None = None
  61. online: bool = False
  62. class SlicerSettingCreate(BaseModel):
  63. """Request to create a new slicer preset."""
  64. type: str = Field(..., description="Preset type: 'filament', 'print', or 'printer'")
  65. name: str = Field(..., description="Display name for the preset")
  66. base_id: str = Field(..., description="Base preset ID to inherit from")
  67. version: str = Field(default="2.0.0.0", description="Version string for the preset")
  68. setting: dict = Field(default_factory=dict, description="Setting key-value pairs (delta from base)")
  69. class SlicerSettingUpdate(BaseModel):
  70. """Request to update an existing slicer preset."""
  71. name: str | None = Field(None, description="New display name")
  72. setting: dict | None = Field(None, description="Setting key-value pairs to update")
  73. class SlicerSettingDetail(BaseModel):
  74. """Detailed slicer setting/preset response."""
  75. message: str | None = None
  76. code: str | None = None
  77. error: str | None = None
  78. public: bool = False
  79. version: str | None = None
  80. type: str
  81. name: str
  82. update_time: str | None = None
  83. nickname: str | None = None
  84. base_id: str | None = None
  85. setting: dict = Field(default_factory=dict)
  86. filament_id: str | None = None
  87. setting_id: str | None = None # For response after create
  88. class SlicerSettingDeleteResponse(BaseModel):
  89. """Response from deleting a preset."""
  90. success: bool
  91. message: str
  92. class FirmwareUpdateInfo(BaseModel):
  93. """Firmware update information for a device."""
  94. device_id: str = Field(..., description="Device ID")
  95. device_name: str = Field(..., description="Device name")
  96. current_version: str | None = Field(None, description="Currently installed firmware version")
  97. latest_version: str | None = Field(None, description="Latest available firmware version")
  98. update_available: bool = Field(False, description="Whether an update is available")
  99. release_notes: str | None = Field(None, description="Release notes for the latest version")
  100. class FirmwareUpdatesResponse(BaseModel):
  101. """Response containing firmware updates for all devices."""
  102. updates: list[FirmwareUpdateInfo] = Field(default_factory=list)
  103. updates_available: int = Field(0, description="Total number of devices with updates available")