Coverage for polar/file/endpoints.py: 88%

45 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 12:42 +0000

1from typing import Annotated 

2 

3from fastapi import Depends, Path, Query 

4from pydantic import UUID4 

5 

6from polar.exceptions import NotPermitted, ResourceNotFound 

7from polar.kit.pagination import ListResource, PaginationParamsQuery 

8from polar.kit.schemas import MultipleQueryFilter 

9from polar.models import File 

10from polar.openapi import APITag 

11from polar.organization.resolver import get_payload_organization 

12from polar.organization.schemas import OrganizationID 

13from polar.postgres import ( 

14 AsyncReadSession, 

15 AsyncSession, 

16 get_db_read_session, 

17 get_db_session, 

18) 

19from polar.routing import APIRouter 

20 

21from . import auth 

22from .schemas import ( 

23 FileCreate, 

24 FilePatch, 

25 FileRead, 

26 FileReadAdapter, 

27 FileUpload, 

28 FileUploadCompleted, 

29) 

30from .service import file as file_service 

31 

32router = APIRouter(prefix="/files", tags=["files", APITag.public]) 

33 

34FileID = Annotated[UUID4, Path(description="The file ID.")] 

35FileNotFound = {"description": "File not found.", "model": ResourceNotFound.schema()} 

36 

37 

38@router.get("/", summary="List Files", response_model=ListResource[FileRead]) 

39async def list( 

40 auth_subject: auth.FileRead, 

41 pagination: PaginationParamsQuery, 

42 organization_id: MultipleQueryFilter[OrganizationID] | None = Query( 

43 None, title="OrganizationID Filter", description="Filter by organization ID." 

44 ), 

45 ids: MultipleQueryFilter[UUID4] | None = Query( 

46 None, title="FileID Filter", description="Filter by file ID." 

47 ), 

48 session: AsyncReadSession = Depends(get_db_read_session), 

49) -> ListResource[FileRead]: 

50 """List files.""" 

51 results, count = await file_service.list( 

52 session, 

53 auth_subject, 

54 organization_id=organization_id, 

55 ids=ids, 

56 pagination=pagination, 

57 ) 

58 return ListResource.from_paginated_results( 

59 [FileReadAdapter.validate_python(result) for result in results], 

60 count, 

61 pagination, 

62 ) 

63 

64 

65@router.post( 

66 "/", 

67 response_model=FileUpload, 

68 summary="Create File", 

69 status_code=201, 

70 responses={201: {"description": "File created."}}, 

71) 

72async def create( 

73 file_create: FileCreate, 

74 auth_subject: auth.FileWrite, 

75 session: AsyncSession = Depends(get_db_session), 

76) -> FileUpload: 

77 """Create a file.""" 

78 organization = await get_payload_organization(session, auth_subject, file_create) 

79 

80 file_create.organization_id = organization.id 

81 return await file_service.generate_presigned_upload( 

82 session, 

83 organization=organization, 

84 create_schema=file_create, 

85 ) 

86 

87 

88@router.post( 

89 "/{id}/uploaded", 

90 summary="Complete File Upload", 

91 response_model=FileRead, 

92 responses={ 

93 200: {"description": "File upload completed."}, 

94 403: { 

95 "description": "You don't have the permission to update this file.", 

96 "model": NotPermitted.schema(), 

97 }, 

98 404: FileNotFound, 

99 }, 

100) 

101async def uploaded( 

102 id: FileID, 

103 completed_schema: FileUploadCompleted, 

104 auth_subject: auth.FileWrite, 

105 session: AsyncSession = Depends(get_db_session), 

106) -> File: 

107 """Complete a file upload.""" 

108 file = await file_service.get(session, auth_subject, id) 

109 if file is None: 109 ↛ 112line 109 didn't jump to line 112 because the condition on line 109 was always true

110 raise ResourceNotFound() 

111 

112 return await file_service.complete_upload( 

113 session, file=file, completed_schema=completed_schema 

114 ) 

115 

116 

117# Re-introduce with changing version 

118@router.patch( 

119 "/{id}", 

120 summary="Update File", 

121 response_model=FileRead, 

122 responses={ 

123 200: {"description": "File updated."}, 

124 403: { 

125 "description": "You don't have the permission to update this file.", 

126 "model": NotPermitted.schema(), 

127 }, 

128 404: FileNotFound, 

129 }, 

130) 

131async def update( 

132 auth_subject: auth.FileWrite, 

133 id: FileID, 

134 patches: FilePatch, 

135 session: AsyncSession = Depends(get_db_session), 

136) -> File: 

137 """Update a file.""" 

138 file = await file_service.get(session, auth_subject, id) 

139 if file is None: 139 ↛ 142line 139 didn't jump to line 142 because the condition on line 139 was always true

140 raise ResourceNotFound() 

141 

142 return await file_service.patch(session, file=file, patches=patches) 

143 

144 

145@router.delete( 

146 "/{id}", 

147 summary="Delete File", 

148 status_code=204, 

149 responses={ 

150 204: {"description": "File deleted."}, 

151 403: { 

152 "description": "You don't have the permission to delete this file.", 

153 "model": NotPermitted.schema(), 

154 }, 

155 404: FileNotFound, 

156 }, 

157) 

158async def delete( 

159 auth_subject: auth.FileWrite, 

160 id: UUID4, 

161 session: AsyncSession = Depends(get_db_session), 

162) -> None: 

163 """Delete a file.""" 

164 file = await file_service.get(session, auth_subject, id) 

165 if file is None: 165 ↛ 168line 165 didn't jump to line 168 because the condition on line 165 was always true

166 raise ResourceNotFound() 

167 

168 await file_service.delete(session, file=file)