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

Drop support for Python 2 #709

Merged
merged 5 commits into from
Jun 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@ matrix:
fast_finish: true
include:
- env: TOXENV=flake8
- python: 2.7
env: TOXENV=integration
- python: 3.5
env: TOXENV=integration
- python: 2.7
env: TOXENV=py27-django111
- python: 3.4
env: TOXENV=py34-django111
- python: 3.5
env: TOXENV=py35-django111
- python: 3.6
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
django-storages CHANGELOG
=========================

UNRELEASED
**********

- Removed support for end-of-life Python 2.7 and 3.4.

1.9.1 (2020-02-03)
******************

Expand Down
22 changes: 10 additions & 12 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
#
# django-storages documentation build configuration file, created by
# sphinx-quickstart on Sun Aug 28 13:44:45 2011.
#
Expand Down Expand Up @@ -41,8 +39,8 @@
master_doc = 'index'

# General information about the project.
project = u'django-storages'
copyright = u'2011-2017, David Larlet, et. al.'
project = 'django-storages'
copyright = '2011-2017, David Larlet, et. al.'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down Expand Up @@ -179,8 +177,8 @@
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'django-storages.tex', u'django-storages Documentation',
u'David Larlet, et. al.', 'manual'),
('index', 'django-storages.tex', 'django-storages Documentation',
'David Larlet, et. al.', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
Expand Down Expand Up @@ -212,18 +210,18 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'django-storages', u'django-storages Documentation',
[u'David Larlet, et. al.'], 1)
('index', 'django-storages', 'django-storages Documentation',
['David Larlet, et. al.'], 1)
]


# -- Options for Epub output ---------------------------------------------------

# Bibliographic Dublin Core info.
epub_title = u'django-storages'
epub_author = u'David Larlet, et. al.'
epub_publisher = u'David Larlet, et. al.'
epub_copyright = u'2011-2017, David Larlet, et. al.'
epub_title = 'django-storages'
epub_author = 'David Larlet, et. al.'
epub_publisher = 'David Larlet, et. al.'
epub_copyright = '2011-2017, David Larlet, et. al.'

# The language of the text. It defaults to the language option
# or en if the language is not set.
Expand Down
9 changes: 2 additions & 7 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,16 @@ classifiers =
License :: OSI Approved :: BSD License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8

[options]
zip_safe=False
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
python_requires = >=3.5
install_requires =
Django >= 1.11
packages =
Expand All @@ -50,9 +48,6 @@ libcloud =
sftp =
paramiko

[bdist_wheel]
universal=1

[flake8]
exclude =
.tox,
Expand Down
10 changes: 2 additions & 8 deletions storages/backends/apache_libcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,14 @@
#
import io
import os
from urllib.parse import urljoin

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import File
from django.core.files.storage import Storage
from django.utils.deconstruct import deconstructible

try:
from django.utils.six import string_types
from django.utils.six.moves.urllib.parse import urljoin
except ImportError:
string_types = str
from urllib.parse import urljoin

try:
from libcloud.storage.providers import get_driver
from libcloud.storage.types import ObjectDoesNotExistError, Provider
Expand Down Expand Up @@ -44,7 +38,7 @@ def __init__(self, provider_name=None, option=None):
extra_kwargs['project'] = self.provider['project']
try:
provider_type = self.provider['type']
if isinstance(provider_type, string_types):
if isinstance(provider_type, str):
module_path, tag = provider_type.rsplit('.', 1)
if module_path != 'libcloud.storage.types.Provider':
raise ValueError("Invalid module path")
Expand Down
10 changes: 4 additions & 6 deletions storages/backends/azure_storage.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import unicode_literals

import mimetypes
from datetime import datetime, timedelta
from tempfile import SpooledTemporaryFile
Expand Down Expand Up @@ -62,15 +60,15 @@ def _set_file(self, value):
def read(self, *args, **kwargs):
if 'r' not in self._mode and 'a' not in self._mode:
raise AttributeError("File was not opened in read mode.")
return super(AzureStorageFile, self).read(*args, **kwargs)
return super().read(*args, **kwargs)

def write(self, content):
if ('w' not in self._mode and
'+' not in self._mode and
'a' not in self._mode):
raise AttributeError("File was not opened in write mode.")
self._is_dirty = True
return super(AzureStorageFile, self).write(force_bytes(content))
return super().write(force_bytes(content))

def close(self):
if self._file is None:
Expand Down Expand Up @@ -123,7 +121,7 @@ def _get_valid_path(s):
@deconstructible
class AzureStorage(BaseStorage):
def __init__(self, **settings):
super(AzureStorage, self).__init__(**settings)
super().__init__(**settings)
self._service = None
self._custom_service = None

Expand Down Expand Up @@ -217,7 +215,7 @@ def get_available_name(self, name, max_length=_AZURE_NAME_MAX_LEN):
name = clean_name(name)
if self.overwrite_files:
return get_available_overwrite_name(name, max_length)
return super(AzureStorage, self).get_available_name(name, max_length)
return super().get_available_name(name, max_length)

def exists(self, name):
return self.service.exists(
Expand Down
2 changes: 0 additions & 2 deletions storages/backends/dropbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
# DROPBOX_OAUTH2_TOKEN = 'YourOauthToken'
# DROPBOX_ROOT_PATH = '/dir/'

from __future__ import absolute_import

from io import BytesIO
from shutil import copyfileobj
from tempfile import SpooledTemporaryFile
Expand Down
10 changes: 3 additions & 7 deletions storages/backends/ftp.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io
import os
from datetime import datetime
from urllib.parse import urljoin, urlparse

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
Expand All @@ -27,11 +28,6 @@

from storages.utils import setting

try:
from django.utils.six.moves.urllib import parse as urlparse
except ImportError:
from urllib import parse as urlparse


class FTPStorageException(Exception):
pass
Expand All @@ -56,7 +52,7 @@ def __init__(self, location=None, base_url=None, encoding=None):

def _decode_location(self, location):
"""Return splitted configuration data from location."""
splitted_url = urlparse.urlparse(location)
splitted_url = urlparse(location)
config = {}

if splitted_url.scheme not in ('ftp', 'aftp'):
Expand Down Expand Up @@ -248,7 +244,7 @@ def size(self, name):
def url(self, name):
if self._base_url is None:
raise ValueError("This file is not accessible via a URL.")
return urlparse.urljoin(self._base_url, name).replace('\\', '/')
return urljoin(self._base_url, name).replace('\\', '/')


class FTPStorageFile(File):
Expand Down
37 changes: 16 additions & 21 deletions storages/backends/gcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.core.files.base import File
from django.utils import timezone
from django.utils.deconstruct import deconstructible
from django.utils.encoding import force_bytes, smart_str
from django.utils.encoding import force_bytes

from storages.base import BaseStorage
from storages.utils import (
Expand Down Expand Up @@ -67,13 +67,13 @@ def read(self, num_bytes=None):
if num_bytes is None:
num_bytes = -1

return super(GoogleCloudFile, self).read(num_bytes)
return super().read(num_bytes)

def write(self, content):
if 'w' not in self._mode:
raise AttributeError("File was not opened in write mode.")
self._is_dirty = True
return super(GoogleCloudFile, self).write(force_bytes(content))
return super().write(force_bytes(content))

def close(self):
if self._file is not None:
Expand All @@ -88,7 +88,7 @@ def close(self):
@deconstructible
class GoogleCloudStorage(BaseStorage):
def __init__(self, **settings):
super(GoogleCloudStorage, self).__init__(**settings)
super().__init__(**settings)

check_location(self)

Expand All @@ -115,7 +115,6 @@ def get_default_settings(self):
"auto_create_acl": setting('GS_AUTO_CREATE_ACL', 'projectPrivate'),
"default_acl": setting('GS_DEFAULT_ACL'),
"expiration": setting('GS_EXPIRATION', timedelta(seconds=86400)),
"file_name_charset": setting('GS_FILE_NAME_CHARSET', 'utf-8'),
"file_overwrite": setting('GS_FILE_OVERWRITE', True),
"cache_control": setting('GS_CACHE_CONTROL'),
# The max amount of memory a returned file can take up before being
Expand Down Expand Up @@ -169,23 +168,19 @@ def _normalize_name(self, name):
raise SuspiciousOperation("Attempted access to '%s' denied." %
name)

def _encode_name(self, name):
return smart_str(name, encoding=self.file_name_charset)

def _open(self, name, mode='rb'):
name = self._normalize_name(clean_name(name))
file_object = GoogleCloudFile(name, mode, self)
if not file_object.blob:
raise IOError(u'File does not exist: %s' % name)
raise FileNotFoundError('File does not exist: %s' % name)
return file_object

def _save(self, name, content):
cleaned_name = clean_name(name)
name = self._normalize_name(cleaned_name)

content.name = cleaned_name
encoded_name = self._encode_name(name)
file = GoogleCloudFile(encoded_name, 'rw', self)
file = GoogleCloudFile(name, 'rw', self)
file.blob.cache_control = self.cache_control
file.blob.upload_from_file(
content, rewind=True, size=content.size,
Expand All @@ -194,7 +189,7 @@ def _save(self, name, content):

def delete(self, name):
name = self._normalize_name(clean_name(name))
self.bucket.delete_blob(self._encode_name(name))
self.bucket.delete_blob(name)

def exists(self, name):
if not name: # root element aka the bucket
Expand All @@ -205,7 +200,7 @@ def exists(self, name):
return False

name = self._normalize_name(clean_name(name))
return bool(self.bucket.get_blob(self._encode_name(name)))
return bool(self.bucket.get_blob(name))

def listdir(self, name):
name = self._normalize_name(clean_name(name))
Expand All @@ -214,7 +209,7 @@ def listdir(self, name):
if name and not name.endswith('/'):
name += '/'

iterator = self.bucket.list_blobs(prefix=self._encode_name(name), delimiter='/')
iterator = self.bucket.list_blobs(prefix=name, delimiter='/')
blobs = list(iterator)
prefixes = iterator.prefixes

Expand All @@ -235,23 +230,23 @@ def _get_blob(self, name):
blob = self.bucket.get_blob(name)

if blob is None:
raise NotFound(u'File does not exist: {}'.format(name))
raise NotFound('File does not exist: {}'.format(name))

return blob

def size(self, name):
name = self._normalize_name(clean_name(name))
blob = self._get_blob(self._encode_name(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(self._encode_name(name))
blob = self._get_blob(name)
return timezone.make_naive(blob.updated)

def get_modified_time(self, name):
name = self._normalize_name(clean_name(name))
blob = self._get_blob(self._encode_name(name))
blob = self._get_blob(name)
updated = blob.updated
return updated if setting('USE_TZ') else timezone.make_naive(updated)

Expand All @@ -261,7 +256,7 @@ def get_created_time(self, name):
The datetime will be timezone-aware if USE_TZ=True.
"""
name = self._normalize_name(clean_name(name))
blob = self._get_blob(self._encode_name(name))
blob = self._get_blob(name)
created = blob.time_created
return created if setting('USE_TZ') else timezone.make_naive(created)

Expand All @@ -272,7 +267,7 @@ def url(self, name):
for many use cases.
"""
name = self._normalize_name(clean_name(name))
blob = self.bucket.blob(self._encode_name(name))
blob = self.bucket.blob(name)

if not self.custom_endpoint and self.default_acl == 'publicRead':
return blob.public_url
Expand All @@ -293,4 +288,4 @@ def get_available_name(self, name, max_length=None):
name = clean_name(name)
if self.file_overwrite:
return get_available_overwrite_name(name, max_length)
return super(GoogleCloudStorage, self).get_available_name(name, max_length)
return super().get_available_name(name, max_length)
Loading