Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new Django time metadata interface where possible #1240

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 7 additions & 24 deletions storages/backends/azure_storage.py
Expand Up @@ -2,6 +2,7 @@
import re
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from tempfile import SpooledTemporaryFile

from azure.core.exceptions import ResourceNotFoundError
Expand All @@ -12,10 +13,10 @@
from azure.storage.blob import generate_blob_sas
from django.core.exceptions import SuspiciousOperation
from django.core.files.base import File
from django.utils import timezone
from django.utils.deconstruct import deconstructible

from storages.base import BaseStorage
from storages.time import TimeStorageMixin
from storages.utils import clean_name
from storages.utils import get_available_overwrite_name
from storages.utils import safe_join
Expand Down Expand Up @@ -120,7 +121,9 @@ def _get_valid_path(s):


@deconstructible
class AzureStorage(BaseStorage):
class AzureStorage(TimeStorageMixin, BaseStorage):
_default_timezone = timezone.utc

def __init__(self, **settings):
super().__init__(**settings)
self._service_client = None
Expand Down Expand Up @@ -353,30 +356,10 @@ def get_object_parameters(self, name):
"""
return self.object_parameters.copy()

def get_modified_time(self, name):
"""
Returns an (aware) datetime object containing the last modified time if
USE_TZ is True, otherwise returns a naive datetime in the local timezone.
"""
def _modified_time(self, name: str) -> datetime:
blob_client = self.client.get_blob_client(self._get_valid_path(name))
properties = blob_client.get_blob_properties(timeout=self.timeout)
if not setting('USE_TZ', False):
return timezone.make_naive(properties.last_modified)

tz = timezone.get_current_timezone()
if timezone.is_naive(properties.last_modified):
return timezone.make_aware(properties.last_modified, tz)

# `last_modified` is in UTC time_zone, we
# must convert it to settings time_zone
return properties.last_modified.astimezone(tz)

def modified_time(self, name):
"""Returns a naive datetime object containing the last modified time."""
mtime = self.get_modified_time(name)
if timezone.is_naive(mtime):
return mtime
return timezone.make_naive(mtime)
return properties.last_modified

def list_all(self, path=''):
"""Return all files for a given path"""
Expand Down
11 changes: 8 additions & 3 deletions storages/backends/dropbox.py
Expand Up @@ -3,6 +3,8 @@
# License: BSD

import warnings
from datetime import datetime
from datetime import timezone
from io import BytesIO
from shutil import copyfileobj
from tempfile import SpooledTemporaryFile
Expand All @@ -19,6 +21,7 @@
from dropbox.files import WriteMode

from storages.base import BaseStorage
from storages.time import TimeStorageMixin
from storages.utils import get_available_overwrite_name
from storages.utils import setting

Expand Down Expand Up @@ -68,10 +71,12 @@ def _set_file(self, value):


@deconstructible
class DropboxStorage(BaseStorage):
class DropboxStorage(TimeStorageMixin, BaseStorage):
"""Dropbox Storage class for Django pluggable storage system."""
CHUNK_SIZE = 4 * 1024 * 1024

_default_timezone = timezone.utc

def __init__(self, oauth2_access_token=None, **settings):
super().__init__(oauth2_access_token=oauth2_access_token, **settings)

Expand Down Expand Up @@ -144,11 +149,11 @@ def size(self, name):
metadata = self.client.files_get_metadata(self._full_path(name))
return metadata.size

def modified_time(self, name):
def _modified_time(self, name: str) -> datetime:
metadata = self.client.files_get_metadata(self._full_path(name))
return metadata.server_modified

def accessed_time(self, name):
def _accessed_time(self, name: str) -> datetime:
metadata = self.client.files_get_metadata(self._full_path(name))
return metadata.client_modified

Expand Down
5 changes: 3 additions & 2 deletions storages/backends/ftp.py
Expand Up @@ -27,6 +27,7 @@
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible

from storages.time import TimeStorageMixin
from storages.utils import setting


Expand All @@ -35,7 +36,7 @@ class FTPStorageException(Exception):


@deconstructible
class FTPStorage(Storage):
class FTPStorage(TimeStorageMixin, Storage):
"""FTP Storage class for Django pluggable storage system."""

def __init__(self, location=None, base_url=None, encoding=None):
Expand Down Expand Up @@ -181,7 +182,7 @@ def _get_dir_details(self, path):
except ftplib.all_errors:
raise FTPStorageException('Error getting listing for %s' % path)

def modified_time(self, name):
def _modified_time(self, name: str) -> datetime:
self._start_connection()
resp = self._connection.sendcmd('MDTM ' + name)
if resp[:3] == '213':
Expand Down
27 changes: 10 additions & 17 deletions storages/backends/gcloud.py
@@ -1,17 +1,19 @@
import gzip
import io
import mimetypes
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from tempfile import SpooledTemporaryFile

from django.core.exceptions import ImproperlyConfigured
from django.core.exceptions import SuspiciousOperation
from django.core.files.base import File
from django.utils import timezone
from django.utils.deconstruct import deconstructible

from storages.base import BaseStorage
from storages.compress import CompressedFileMixin
from storages.time import TimeStorageMixin
from storages.utils import check_location
from storages.utils import clean_name
from storages.utils import get_available_overwrite_name
Expand Down Expand Up @@ -101,7 +103,9 @@ def close(self):


@deconstructible
class GoogleCloudStorage(BaseStorage):
class GoogleCloudStorage(TimeStorageMixin, BaseStorage):
_default_timezone = timezone.utc

def __init__(self, **settings):
super().__init__(**settings)

Expand Down Expand Up @@ -273,26 +277,15 @@ def size(self, name):
blob = self._get_blob(name)
return blob.size

def modified_time(self, name):
name = self._normalize_name(clean_name(name))
blob = self._get_blob(name)
return timezone.make_naive(blob.updated)

def get_modified_time(self, name):
def _modified_time(self, name: str) -> datetime:
name = self._normalize_name(clean_name(name))
blob = self._get_blob(name)
updated = blob.updated
return updated if setting('USE_TZ') else timezone.make_naive(updated)
return blob.updated

def get_created_time(self, name):
"""
Return the creation time (as a datetime) of the file specified by name.
The datetime will be timezone-aware if USE_TZ=True.
"""
def _created_time(self, name: str) -> datetime:
name = self._normalize_name(clean_name(name))
blob = self._get_blob(name)
created = blob.time_created
return created if setting('USE_TZ') else timezone.make_naive(created)
return blob.time_created

def url(self, name, parameters=None):
"""
Expand Down
27 changes: 7 additions & 20 deletions storages/backends/s3boto3.py
Expand Up @@ -5,6 +5,7 @@
import threading
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from tempfile import SpooledTemporaryFile
from urllib.parse import parse_qsl
from urllib.parse import urlencode
Expand All @@ -16,12 +17,11 @@
from django.core.files.base import File
from django.utils.deconstruct import deconstructible
from django.utils.encoding import filepath_to_uri
from django.utils.timezone import is_naive
from django.utils.timezone import make_naive

from storages.base import BaseStorage
from storages.compress import CompressedFileMixin
from storages.compress import CompressStorageMixin
from storages.time import TimeStorageMixin
from storages.utils import check_location
from storages.utils import clean_name
from storages.utils import get_available_overwrite_name
Expand Down Expand Up @@ -243,7 +243,7 @@ def close(self):


@deconstructible
class S3Boto3Storage(CompressStorageMixin, BaseStorage):
class S3Boto3Storage(TimeStorageMixin, CompressStorageMixin, BaseStorage):
"""
Amazon Simple Storage Service using Boto3

Expand All @@ -255,6 +255,8 @@ class S3Boto3Storage(CompressStorageMixin, BaseStorage):
# If config provided in init, signature_version and addressing_style settings/args are ignored.
config = None

_default_timezone = timezone.utc

def __init__(self, **settings):
super().__init__(**settings)

Expand Down Expand Up @@ -498,25 +500,10 @@ def get_object_parameters(self, name):
"""
return self.object_parameters.copy()

def get_modified_time(self, name):
"""
Returns an (aware) datetime object containing the last modified time if
USE_TZ is True, otherwise returns a naive datetime in the local timezone.
"""
def _modified_time(self, name: str) -> datetime:
name = self._normalize_name(clean_name(name))
entry = self.bucket.Object(name)
if setting('USE_TZ'):
# boto3 returns TZ aware timestamps
return entry.last_modified
else:
return make_naive(entry.last_modified)

def modified_time(self, name):
"""Returns a naive datetime object containing the last modified time."""
# If USE_TZ=False then get_modified_time will return a naive datetime
# so we just return that, else we have to localize and strip the tz
mtime = self.get_modified_time(name)
return mtime if is_naive(mtime) else make_naive(mtime)
return entry.last_modified

def _strip_signing_parameters(self, url):
# Boto3 does not currently support generating URLs that are unsigned. Instead we
Expand Down
7 changes: 4 additions & 3 deletions storages/backends/sftpstorage.py
Expand Up @@ -17,12 +17,13 @@
from django.utils.deconstruct import deconstructible

from storages.base import BaseStorage
from storages.time import TimeStorageMixin
from storages.utils import is_seekable
from storages.utils import setting


@deconstructible
class SFTPStorage(BaseStorage):
class SFTPStorage(TimeStorageMixin, BaseStorage):
def __init__(self, **settings):
super().__init__(**settings)
self._host = self.host
Expand Down Expand Up @@ -174,12 +175,12 @@ def size(self, name):
remote_path = self._remote_path(name)
return self.sftp.stat(remote_path).st_size

def accessed_time(self, name):
def _accessed_time(self, name: str) -> datetime:
remote_path = self._remote_path(name)
utime = self.sftp.stat(remote_path).st_atime
return datetime.fromtimestamp(utime)

def modified_time(self, name):
def _modified_time(self, name: str) -> datetime:
remote_path = self._remote_path(name)
utime = self.sftp.stat(remote_path).st_mtime
return datetime.fromtimestamp(utime)
Expand Down