base.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. from __future__ import unicode_literals
  2. import os
  3. from io import BytesIO, StringIO, UnsupportedOperation
  4. from django.utils.encoding import smart_text
  5. from django.core.files.utils import FileProxyMixin
  6. from django.utils import six
  7. from django.utils.encoding import force_bytes, python_2_unicode_compatible
  8. @python_2_unicode_compatible
  9. class File(FileProxyMixin):
  10. DEFAULT_CHUNK_SIZE = 64 * 2 ** 10
  11. def __init__(self, file, name=None):
  12. self.file = file
  13. if name is None:
  14. name = getattr(file, 'name', None)
  15. self.name = name
  16. if hasattr(file, 'mode'):
  17. self.mode = file.mode
  18. def __str__(self):
  19. return smart_text(self.name or '')
  20. def __repr__(self):
  21. return "<%s: %s>" % (self.__class__.__name__, self or "None")
  22. def __bool__(self):
  23. return bool(self.name)
  24. def __nonzero__(self): # Python 2 compatibility
  25. return type(self).__bool__(self)
  26. def __len__(self):
  27. return self.size
  28. def _get_size(self):
  29. if not hasattr(self, '_size'):
  30. if hasattr(self.file, 'size'):
  31. self._size = self.file.size
  32. elif hasattr(self.file, 'name') and os.path.exists(self.file.name):
  33. self._size = os.path.getsize(self.file.name)
  34. elif hasattr(self.file, 'tell') and hasattr(self.file, 'seek'):
  35. pos = self.file.tell()
  36. self.file.seek(0, os.SEEK_END)
  37. self._size = self.file.tell()
  38. self.file.seek(pos)
  39. else:
  40. raise AttributeError("Unable to determine the file's size.")
  41. return self._size
  42. def _set_size(self, size):
  43. self._size = size
  44. size = property(_get_size, _set_size)
  45. def _get_closed(self):
  46. return not self.file or self.file.closed
  47. closed = property(_get_closed)
  48. def chunks(self, chunk_size=None):
  49. """
  50. Read the file and yield chucks of ``chunk_size`` bytes (defaults to
  51. ``UploadedFile.DEFAULT_CHUNK_SIZE``).
  52. """
  53. if not chunk_size:
  54. chunk_size = self.DEFAULT_CHUNK_SIZE
  55. try:
  56. self.seek(0)
  57. except (AttributeError, UnsupportedOperation):
  58. pass
  59. while True:
  60. data = self.read(chunk_size)
  61. if not data:
  62. break
  63. yield data
  64. def multiple_chunks(self, chunk_size=None):
  65. """
  66. Returns ``True`` if you can expect multiple chunks.
  67. NB: If a particular file representation is in memory, subclasses should
  68. always return ``False`` -- there's no good reason to read from memory in
  69. chunks.
  70. """
  71. if not chunk_size:
  72. chunk_size = self.DEFAULT_CHUNK_SIZE
  73. return self.size > chunk_size
  74. def __iter__(self):
  75. # Iterate over this file-like object by newlines
  76. buffer_ = None
  77. for chunk in self.chunks():
  78. chunk_buffer = BytesIO(chunk)
  79. for line in chunk_buffer:
  80. if buffer_:
  81. line = buffer_ + line
  82. buffer_ = None
  83. # If this is the end of a line, yield
  84. # otherwise, wait for the next round
  85. if line[-1:] in (b'\n', b'\r'):
  86. yield line
  87. else:
  88. buffer_ = line
  89. if buffer_ is not None:
  90. yield buffer_
  91. def __enter__(self):
  92. return self
  93. def __exit__(self, exc_type, exc_value, tb):
  94. self.close()
  95. def open(self, mode=None):
  96. if not self.closed:
  97. self.seek(0)
  98. elif self.name and os.path.exists(self.name):
  99. self.file = open(self.name, mode or self.mode)
  100. else:
  101. raise ValueError("The file cannot be reopened.")
  102. def close(self):
  103. self.file.close()
  104. @python_2_unicode_compatible
  105. class ContentFile(File):
  106. """
  107. A File-like object that takes just raw content, rather than an actual file.
  108. """
  109. def __init__(self, content, name=None):
  110. if six.PY3:
  111. stream_class = StringIO if isinstance(content, six.text_type) else BytesIO
  112. else:
  113. stream_class = BytesIO
  114. content = force_bytes(content)
  115. super(ContentFile, self).__init__(stream_class(content), name=name)
  116. self.size = len(content)
  117. def __str__(self):
  118. return 'Raw content'
  119. def __bool__(self):
  120. return True
  121. def __nonzero__(self): # Python 2 compatibility
  122. return type(self).__bool__(self)
  123. def open(self, mode=None):
  124. self.seek(0)
  125. def close(self):
  126. pass