makerworld.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. """Pydantic schemas for the MakerWorld integration routes."""
  2. from __future__ import annotations
  3. from typing import Any
  4. from pydantic import BaseModel, Field
  5. class MakerWorldResolveRequest(BaseModel):
  6. """Body for POST /makerworld/resolve."""
  7. url: str = Field(..., description="Any MakerWorld model URL (scheme optional)")
  8. class MakerWorldResolvedModel(BaseModel):
  9. """Structured result of URL resolution.
  10. ``design`` and ``instances`` are passed through verbatim from MakerWorld's
  11. API — we don't re-shape them because the frontend needs access to fields
  12. MakerWorld may add over time (badges, license variants, etc.). Keeping
  13. them as opaque dicts avoids brittle coupling.
  14. """
  15. model_id: int
  16. profile_id: int | None = Field(
  17. default=None,
  18. description="Specific profile from the URL's #profileId- fragment, if any",
  19. )
  20. design: dict[str, Any]
  21. instances: list[dict[str, Any]]
  22. already_imported_library_ids: list[int] = Field(
  23. default_factory=list,
  24. description="LibraryFile IDs that were previously imported from this model URL",
  25. )
  26. class MakerWorldImportRequest(BaseModel):
  27. """Body for POST /makerworld/import."""
  28. model_id: int = Field(
  29. ...,
  30. description="The MakerWorld design ID (the number in /models/{id}).",
  31. )
  32. profile_id: int | None = Field(
  33. default=None,
  34. description=(
  35. "The profileId of the selected instance (plate configuration). Each "
  36. "instance in `/design/{id}/instances` carries a `profileId` field — "
  37. "the frontend forwards the picked one here. If omitted, the backend "
  38. "falls back to the first available instance of the model."
  39. ),
  40. )
  41. instance_id: int | None = Field(
  42. default=None,
  43. description="Retained for backwards compatibility; no longer used by the download flow.",
  44. )
  45. folder_id: int | None = Field(default=None, description="Target library folder; null = root")
  46. class MakerWorldRecentImport(BaseModel):
  47. """One row in the 'recent MakerWorld imports' list."""
  48. library_file_id: int
  49. filename: str
  50. folder_id: int | None
  51. thumbnail_path: str | None = Field(
  52. default=None,
  53. description="Relative path under /api/v1/library/files/{id}/thumbnail — "
  54. "the frontend wraps it with a stream token to render.",
  55. )
  56. source_url: str | None = Field(
  57. default=None,
  58. description="Canonical MakerWorld URL (``https://makerworld.com/models/{id}"
  59. "#profileId-{pid}``). The frontend uses it to build an 'Open on MakerWorld' "
  60. "link and to extract model/profile ids without a second API round-trip.",
  61. )
  62. created_at: str
  63. class MakerWorldImportResponse(BaseModel):
  64. """Result of a MakerWorld import."""
  65. library_file_id: int
  66. filename: str
  67. folder_id: int | None = Field(
  68. default=None,
  69. description=(
  70. "Folder the file was saved to — the auto-created 'MakerWorld' folder "
  71. "by default, or whichever folder the caller specified. Surfaced so the "
  72. "frontend can deep-link to File Manager → that folder after import."
  73. ),
  74. )
  75. profile_id: int | None = Field(
  76. default=None,
  77. description=(
  78. "The MakerWorld profile (plate) id that was imported. Surfaced so the "
  79. "frontend can match the response back to the plate row in the UI and "
  80. "render inline 'view in library' / 'open in slicer' controls there."
  81. ),
  82. )
  83. was_existing: bool = Field(
  84. description="True if a prior import from the same source URL was reused (no re-download)"
  85. )
  86. class MakerWorldStatus(BaseModel):
  87. """Integration health + auth status surfaced to the frontend."""
  88. has_cloud_token: bool = Field(description="Whether the caller's account has a stored Bambu Cloud token")
  89. can_download: bool = Field(description="Shortcut: has_cloud_token AND it looks valid. Downloads require it.")