Coverage for /Users/davegaeddert/Development/dropseed/plain/plain/plain/internal/files/uploadedfile.py: 44%

1"""

2 Classes representing uploaded files.

3"""

4

5 import os

6 from io import BytesIO

7

8 from plain.internal.files import temp as tempfile

9 from plain.internal.files.base import File

10 from plain.internal.files.utils import validate_file_name

11 from plain.runtime import settings

12

13 __all__ = (

14 "UploadedFile",

15 "TemporaryUploadedFile",

16 "InMemoryUploadedFile",

17 "SimpleUploadedFile",

18)

19

20

21 class UploadedFile(File):

22 """

23 An abstract uploaded file (``TemporaryUploadedFile`` and

24 ``InMemoryUploadedFile`` are the built-in concrete subclasses).

25

26 An ``UploadedFile`` object behaves somewhat like a file object and

27 represents some file data that the user submitted with a form.

28 """

29

30 def __init__(

31 self,

32 file=None,

33 name=None,

34 content_type=None,

35 size=None,

36 charset=None,

37 content_type_extra=None,

38 ):

39 super().__init__(file, name)

40 self.size = size

41 self.content_type = content_type

42 self.charset = charset

43 self.content_type_extra = content_type_extra

44

45 def __repr__(self):

46 return f"<{self.__class__.__name__}: {self.name} ({self.content_type})>"

47

48 def _get_name(self):

49 return self._name

50

51 def _set_name(self, name):

52 # Sanitize the file name so that it can't be dangerous.

53 if name is not None:

54 # Just use the basename of the file -- anything else is dangerous.

55 name = os.path.basename(name)

56

57 # File names longer than 255 characters can cause problems on older OSes.

58 if len(name) > 255:

59 name, ext = os.path.splitext(name)

60 ext = ext[:255]

61 name = name[: 255 - len(ext)] + ext

62

63 name = validate_file_name(name)

64

65 self._name = name

66

67 name = property(_get_name, _set_name)

68

69

70 class TemporaryUploadedFile(UploadedFile):

71 """

72 A file uploaded to a temporary location (i.e. stream-to-disk).

73 """

74

75 def __init__(self, name, content_type, size, charset, content_type_extra=None):

76 _, ext = os.path.splitext(name)

77 file = tempfile.NamedTemporaryFile(

78 suffix=".upload" + ext, dir=settings.FILE_UPLOAD_TEMP_DIR

79 )

80 super().__init__(file, name, content_type, size, charset, content_type_extra)

81

82 def temporary_file_path(self):

83 """Return the full path of this file."""

84 return self.file.name

85

86 def close(self):

87 try:

88 return self.file.close()

89 except FileNotFoundError:

90 # The file was moved or deleted before the tempfile could unlink

91 # it. Still sets self.file.close_called and calls

92 # self.file.file.close() before the exception.

93 pass

94

95

96 class InMemoryUploadedFile(UploadedFile):

97 """

98 A file uploaded into memory (i.e. stream-to-memory).

99 """

100

101 def __init__(

102 self,

103 file,

104 field_name,

105 name,

106 content_type,

107 size,

108 charset,

109 content_type_extra=None,

110 ):

111 super().__init__(file, name, content_type, size, charset, content_type_extra)

112 self.field_name = field_name

113

114 def open(self, mode=None):

115 self.file.seek(0)

116 return self

117

118 def chunks(self, chunk_size=None):

119 self.file.seek(0)

120 yield self.read()

121

122 def multiple_chunks(self, chunk_size=None):

123 # Since it's in memory, we'll never have multiple chunks.

124 return False

125

126

127 class SimpleUploadedFile(InMemoryUploadedFile):

128 """

129 A simple representation of a file, which just has content, size, and a name.

130 """

131

132 def __init__(self, name, content, content_type="text/plain"):

133 content = content or b""

134 super().__init__(

135 BytesIO(content), None, name, content_type, len(content), None, None

136 )

137

138 @classmethod

139 def from_dict(cls, file_dict):

140 """

141 Create a SimpleUploadedFile object from a dictionary with keys:

142 - filename

143 - content-type

144 - content

145 """

146 return cls(

147 file_dict["filename"],

148 file_dict["content"],

149 file_dict.get("content-type", "text/plain"),

150 )