From 25157471c805dcfb7bf53b8ac216ac56e1425489 Mon Sep 17 00:00:00 2001 From: Jeff Gordon Date: Tue, 20 Aug 2019 12:03:41 -0600 Subject: [PATCH 1/4] Added file object to '_closable_objects' to close at end of request This commit fixes ResourceWarnings in Python 3 where file objects do not appear to be closed at the end of the request, and the Django server will emit ResourceWarnings about unclosed files. The `_closable_objects` list is managed by the base FileResponse class in Django and will call `.close()` on objects at the end of the request. --- ranged_fileresponse/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ranged_fileresponse/__init__.py b/ranged_fileresponse/__init__.py index bf3fec8..de8b2e2 100644 --- a/ranged_fileresponse/__init__.py +++ b/ranged_fileresponse/__init__.py @@ -107,6 +107,9 @@ def __init__(self, request, file, *args, **kwargs): """ self.ranged_file = RangedFileReader(file) super(RangedFileResponse, self).__init__(self.ranged_file, *args, **kwargs) + # Close file object at end of request + if hasattr(file, 'close') and hasattr(self, '_closable_objects'): + self._closable_objects.append(file) if 'HTTP_RANGE' in request.META: self.add_range_headers(request.META['HTTP_RANGE']) From a096beb147678456c2ee7360fb46737910dace92 Mon Sep 17 00:00:00 2001 From: Mike D <5084545+devmonkey22@users.noreply.github.com> Date: Mon, 3 Feb 2020 10:34:54 -0500 Subject: [PATCH 2/4] Close underlying file more naturally; init size without reading file Added RangedFileReader.close() method, which allows underlying StreamingHttpResponse to add to `_closable_objects` more naturally to fix unclosed files. Also initialized `RangedFileReader.size` using `file_like.size` property (if available, i.e. Django File/FieldFile) rather than reading entire file contents. --- ranged_fileresponse/__init__.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ranged_fileresponse/__init__.py b/ranged_fileresponse/__init__.py index de8b2e2..6466a4d 100644 --- a/ranged_fileresponse/__init__.py +++ b/ranged_fileresponse/__init__.py @@ -19,7 +19,12 @@ def __init__(self, file_like, start=0, stop=float('inf'), block_size=None): block_size (Optional[int]): The block_size to read with. """ self.f = file_like - self.size = len(self.f.read()) + + # If size property available (ie: Django File, FieldFile, etc) and not zero (if zero, double-check anyway) + if hasattr(self.f, 'size') and self.f.size > 0: + self.size = self.f.size + else: + self.size = len(self.f.read()) self.block_size = block_size or RangedFileReader.block_size self.start = start self.stop = stop @@ -88,12 +93,19 @@ def parse_range_header(self, header, resource_size): return ranges + def close(self): + """Close underlying file object""" + if hasattr(self.f, 'close'): + self.f.close() class RangedFileResponse(FileResponse): """ This is a modified FileResponse that returns `Content-Range` headers with the response, so browsers that request the file, can stream the response properly. + + Note: When using RangedFileResponse with django tests, make sure to read the response.streaming_content after the + request, otherwise the underlying file won't be auto-closed. """ def __init__(self, request, file, *args, **kwargs): From 9b97ea30d03dff59e34d20c46988c2f70a96f140 Mon Sep 17 00:00:00 2001 From: Mike D <5084545+devmonkey22@users.noreply.github.com> Date: Mon, 3 Feb 2020 10:38:48 -0500 Subject: [PATCH 3/4] Remove explicit `_closable_objects` due to new close() method No longer need to add file to `_closable_objects` explicitly with new reader close() method. --- ranged_fileresponse/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ranged_fileresponse/__init__.py b/ranged_fileresponse/__init__.py index 6466a4d..722f3ac 100644 --- a/ranged_fileresponse/__init__.py +++ b/ranged_fileresponse/__init__.py @@ -119,10 +119,6 @@ def __init__(self, request, file, *args, **kwargs): """ self.ranged_file = RangedFileReader(file) super(RangedFileResponse, self).__init__(self.ranged_file, *args, **kwargs) - # Close file object at end of request - if hasattr(file, 'close') and hasattr(self, '_closable_objects'): - self._closable_objects.append(file) - if 'HTTP_RANGE' in request.META: self.add_range_headers(request.META['HTTP_RANGE']) From b8f8136149380b7f90e20d4fd103bfbb42c48d37 Mon Sep 17 00:00:00 2001 From: Mike D <5084545+devmonkey22@users.noreply.github.com> Date: Mon, 3 Feb 2020 15:18:24 -0500 Subject: [PATCH 4/4] Cleanup flake errors --- ranged_fileresponse/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ranged_fileresponse/__init__.py b/ranged_fileresponse/__init__.py index 722f3ac..2c6fa39 100644 --- a/ranged_fileresponse/__init__.py +++ b/ranged_fileresponse/__init__.py @@ -19,7 +19,6 @@ def __init__(self, file_like, start=0, stop=float('inf'), block_size=None): block_size (Optional[int]): The block_size to read with. """ self.f = file_like - # If size property available (ie: Django File, FieldFile, etc) and not zero (if zero, double-check anyway) if hasattr(self.f, 'size') and self.f.size > 0: self.size = self.f.size @@ -98,12 +97,13 @@ def close(self): if hasattr(self.f, 'close'): self.f.close() + class RangedFileResponse(FileResponse): """ This is a modified FileResponse that returns `Content-Range` headers with the response, so browsers that request the file, can stream the response properly. - + Note: When using RangedFileResponse with django tests, make sure to read the response.streaming_content after the request, otherwise the underlying file won't be auto-closed. """