Coverage for polar/file/schemas.py: 90%

65 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-12-05 15:52 +0000

1from datetime import datetime 1a

2from typing import Annotated, Any, Literal, Self 1a

3 

4from pydantic import UUID4, Discriminator, Field, TypeAdapter, computed_field 1a

5 

6from polar.integrations.aws.s3.schemas import ( 1a

7 S3DownloadURL, 

8 S3File, 

9 S3FileCreate, 

10 S3FileDownload, 

11 S3FileUpload, 

12 S3FileUploadCompleted, 

13) 

14from polar.kit.schemas import ClassName, MergeJSONSchema, Schema, SetSchemaReference 1a

15from polar.models.file import File, FileServiceTypes 1a

16 

17from .s3 import S3_SERVICES 1a

18 

19 

20class FileCreateBase(S3FileCreate): 1a

21 service: FileServiceTypes 1a

22 version: str | None = None 1a

23 

24 

25class DownloadableFileCreate(FileCreateBase): 1a

26 """Schema to create a file to be associated with the downloadables benefit.""" 

27 

28 service: Literal[FileServiceTypes.downloadable] 1a

29 

30 

31class ProductMediaFileCreate(FileCreateBase): 1a

32 """Schema to create a file to be used as a product media file.""" 

33 

34 service: Literal[FileServiceTypes.product_media] 1a

35 mime_type: str = Field( 1a

36 description=( 

37 "MIME type of the file. Only images are supported for this type of file." 

38 ), 

39 pattern=r"^image\/(jpeg|png|gif|webp|svg\+xml)$", 

40 ) 

41 size: int = Field( 1a

42 description=( 

43 "Size of the file. A maximum of 10 MB is allowed for this type of file." 

44 ), 

45 le=10 * 1024 * 1024, 

46 ) 

47 

48 

49class OrganizationAvatarFileCreate(FileCreateBase): 1a

50 """Schema to create a file to be used as an organization avatar.""" 

51 

52 service: Literal[FileServiceTypes.organization_avatar] 1a

53 mime_type: str = Field( 1a

54 description=( 

55 "MIME type of the file. Only images are supported for this type of file." 

56 ), 

57 pattern=r"^image\/(jpeg|png|gif|webp|svg\+xml)$", 

58 ) 

59 size: int = Field( 1a

60 description=( 

61 "Size of the file. A maximum of 1 MB is allowed for this type of file." 

62 ), 

63 le=1 * 1024 * 1024, 

64 ) 

65 

66 

67FileCreate = Annotated[ 1a

68 DownloadableFileCreate | ProductMediaFileCreate | OrganizationAvatarFileCreate, 

69 Discriminator("service"), 

70 SetSchemaReference("FileCreate"), 

71] 

72 

73 

74class FileReadBase(S3File): 1a

75 version: str | None 1a

76 service: FileServiceTypes 1a

77 is_uploaded: bool 1a

78 created_at: datetime 1a

79 

80 

81class DownloadableFileRead(FileReadBase): 1a

82 """File to be associated with the downloadables benefit.""" 

83 

84 service: Literal[FileServiceTypes.downloadable] 1a

85 

86 

87class PublicFileReadBase(FileReadBase): 1a

88 @computed_field # type: ignore[prop-decorator] 1a

89 @property 1a

90 def public_url(self) -> str: 1a

91 return S3_SERVICES[self.service].get_public_url(self.path) 

92 

93 

94class ProductMediaFileRead(PublicFileReadBase): 1a

95 """File to be used as a product media file.""" 

96 

97 service: Literal[FileServiceTypes.product_media] 1a

98 

99 

100class OrganizationAvatarFileRead(PublicFileReadBase): 1a

101 """File to be used as an organization avatar.""" 

102 

103 service: Literal[FileServiceTypes.organization_avatar] 1a

104 

105 

106FileRead = Annotated[ 1a

107 DownloadableFileRead | ProductMediaFileRead | OrganizationAvatarFileRead, 

108 Discriminator("service"), 

109 MergeJSONSchema({"title": "FileRead"}), 

110 ClassName("FileRead"), 

111] 

112 

113FileReadAdapter: TypeAdapter[FileRead] = TypeAdapter[FileRead](FileRead) 1a

114 

115 

116class FileUpload(S3FileUpload): 1a

117 version: str | None 1a

118 is_uploaded: bool = False 1a

119 service: FileServiceTypes 1a

120 

121 

122class FileUploadCompleted(S3FileUploadCompleted): ... 1a

123 

124 

125class FileDownload(S3FileDownload): 1a

126 version: str | None 1a

127 is_uploaded: bool 1a

128 service: FileServiceTypes 1a

129 

130 @classmethod 1a

131 def from_presigned(cls, file: File, url: str, expires_at: datetime) -> Self: 1a

132 file_dict: dict[str, Any] = dict( 

133 id=file.id, 

134 organization_id=file.organization_id, 

135 name=file.name, 

136 path=file.path, 

137 mime_type=file.mime_type, 

138 size=file.size, 

139 version=file.version, 

140 service=file.service, 

141 checksum_etag=file.checksum_etag, 

142 last_modified_at=file.last_modified_at, 

143 storage_version=file.storage_version, 

144 is_uploaded=file.is_uploaded, 

145 created_at=file.created_at, 

146 ) 

147 if file.checksum_sha256_base64 and file.checksum_sha256_hex: 

148 file_dict.update( 

149 checksum_sha256_base64=file.checksum_sha256_base64, 

150 checksum_sha256_hex=file.checksum_sha256_hex, 

151 ) 

152 

153 return cls( 

154 **file_dict, 

155 download=S3DownloadURL( 

156 url=url, 

157 expires_at=expires_at, 

158 ), 

159 ) 

160 

161 

162class FileUpdate(Schema): 1a

163 id: UUID4 1a

164 version: str | None 1a

165 checksum_etag: str 1a

166 last_modified_at: datetime 1a

167 storage_version: str | None 1a

168 checksum_sha256_base64: str | None 1a

169 checksum_sha256_hex: str | None 1a

170 

171 

172class FilePatch(Schema): 1a

173 name: str | None = None 1a

174 version: str | None = None 1a