From 72f965d9a43f38eec27f9841f8b6dab40853856e Mon Sep 17 00:00:00 2001 From: Sven Grunewaldt Date: Sat, 22 Jan 2022 14:11:55 +0100 Subject: [PATCH] Update supported Python and Django versions --- .gitignore | 5 +++++ .tool-versions | 1 + HISTORY.rst | 10 ++++++++++ README.rst | 11 +++++------ django_gcloud_storage/__init__.py | 26 ++++---------------------- setup.py | 13 +++++-------- tests/conftest.py | 9 +-------- tests/test_class.py | 8 ++------ tests/test_django.py | 8 +------- tox.ini | 9 +++------ 10 files changed, 37 insertions(+), 63 deletions(-) create mode 100644 .tool-versions diff --git a/.gitignore b/.gitignore index 86be4f4..fbb3809 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # py.test .cache +.pytest_cache # tox .tox* @@ -27,3 +28,7 @@ test_app/db.sqlite3 # venv .venv + +# direnv +.direnv +.envrc diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..9a97727 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.7.12 3.8.12 3.9.10 3.10.2 pypy3.7-7.3.7 pypy3.8-7.3.7 diff --git a/HISTORY.rst b/HISTORY.rst index da36373..77f7302 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,16 @@ History ------- +Unreleased +~~~~~~~~~~ + +* Added support for Python 3.8, 3.9 and 3.10 +* Added support for PyPy 3.7 and 3.8 +* Added support for Django 2.2 and 3.2 +* Dropped suport for now unsupported Python and Django versions: + * Python 2.7, 3.4, 3.5 and 3.6 + * Django 1.11, 2.1 and 3.0 + 0.4.0 (2019-06-27) ~~~~~~~~~~~~~~~~~~ diff --git a/README.rst b/README.rst index c3327cd..18f9fc6 100644 --- a/README.rst +++ b/README.rst @@ -31,12 +31,11 @@ Install django-gcloud-storage:: pip install django-gcloud-storage Create a GCS service account JSON keyfile and a bucket for your application. -Check the documentation of google-cloud-python and Google Cloud Platform for -more details: +Check the documentation of Google Cloud Platform for more details: -https://googlecloudplatform.github.io/google-cloud-python/latest/core/auth.html#setting-up-a-service-account +https://cloud.google.com/iam/docs/creating-managing-service-accounts#creating -https://cloud.google.com/storage/docs/authentication#generating-a-private-key +https://cloud.google.com/iam/docs/creating-managing-service-account-keys#creating Update your Django settings and use it like any other Django storage module:: @@ -49,8 +48,8 @@ Update your Django settings and use it like any other Django storage module:: Features -------- -* Fully tested on Python 2.7, 3.4 - 3.7, PyPy 2.7-7.1.1 and PyPy 3.6-7.1.1 with - Django 1.11 and 2.1 - 2.2 +* Fully tested on Python 3.7 - 3.10, PyPy 3.7-7.3.7 and PyPy 3.8-7.3.7 with + Django 2.2 and 3.2 * Files are locally downloaded as SpooledTemporaryFile objects to avoid memory abuse * Changed files will automatically be reuploaded to GCS when closed diff --git a/django_gcloud_storage/__init__.py b/django_gcloud_storage/__init__.py index 51fd429..150a8bc 100644 --- a/django_gcloud_storage/__init__.py +++ b/django_gcloud_storage/__init__.py @@ -6,6 +6,7 @@ import re from tempfile import SpooledTemporaryFile import mimetypes +import urllib.parse import django from django.conf import settings @@ -21,15 +22,6 @@ __version__ = '0.4.0' -DJANGO_17 = django.get_version().startswith('1.7.') - -try: - # For Python 3.0 and later - from urllib import parse as urlparse -except ImportError: - # Fall back to Python 2's urllib2 - import urlparse - def safe_join(base, path): base = force_text(base).replace("\\", "/").lstrip("/").rstrip("/") + "/" @@ -39,7 +31,7 @@ def safe_join(base, path): if base == "/": base = "" - resolved_url = urlparse.urljoin(base, path) + resolved_url = urllib.parse.urljoin(base, path) resolved_url = re.sub("//+", "/", resolved_url) @@ -84,13 +76,7 @@ def __init__(self, blob, maxsize=1000): def _update_blob(self): # Specify explicit size to avoid problems with not yet spooled temporary files # Djangos File.size property already knows how to handle cases like this - - if DJANGO_17 and self._tmpfile.name is None: # Django bug #22307 - size = self._tmpfile.tell() - else: - size = self.size - - self._blob.upload_from_file(self._tmpfile, size=size, rewind=True) + self._blob.upload_from_file(self._tmpfile, size=self.size, rewind=True) def write(self, content): self._dirty = True @@ -228,7 +214,7 @@ def size(self, name): return blob.size if blob is not None else None - def modified_time(self, name): + def get_modified_time(self, name): name = safe_join(self.bucket_subdir, name) name = prepare_name(name) @@ -236,10 +222,6 @@ def modified_time(self, name): return blob.updated if blob is not None else None - def get_modified_time(self, name): - # In Django>=1.10, modified_time is deprecated, and modified_time will be removed in Django 2.0. - return self.modified_time(name) - def listdir(self, path): path = safe_join(self.bucket_subdir, path) path = prepare_name(path) diff --git a/setup.py b/setup.py index 94fa6ed..84b8332 100755 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ def get_version(*file_paths): include_package_data=True, install_requires=[ "google-cloud-storage>=1.10.0", - "django>=1.11" + "django>=2.2" ], license="BSD", zip_safe=False, @@ -49,18 +49,15 @@ def get_version(*file_paths): classifiers=[ 'Development Status :: 3 - Alpha', 'Framework :: Django', - 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.1', 'Framework :: Django :: 2.2', + 'Framework :: Django :: 3.2', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', ], ) diff --git a/tests/conftest.py b/tests/conftest.py index 6486f17..ad95321 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,4 @@ # coding=utf-8 -from __future__ import unicode_literals - import os import string @@ -44,15 +42,10 @@ def storage(request): ) # Make sure the bucket exists - bucket = Bucket(storage.client, bucket_name) - bucket.create( - location=request.config.getoption("--gcs-bucket-location") - ) + storage.client.create_bucket(bucket_name, location=request.config.getoption("--gcs-bucket-location")) yield storage - storage.bucket.delete_blobs(storage.bucket.list_blobs()) - storage.bucket.delete(force=True) @pytest.fixture(scope="module") diff --git a/tests/test_class.py b/tests/test_class.py index 9c05b0c..9da93e6 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -1,10 +1,7 @@ # coding=utf-8 -from __future__ import unicode_literals - import datetime import ssl import sys -from tempfile import TemporaryFile import google.cloud.exceptions import pytest @@ -134,7 +131,6 @@ def test_should_return_created_time(self, storage, test_file): assert isinstance(storage.created_time(test_file), datetime.datetime) def test_should_return_modified_time(self, storage, test_file): - assert isinstance(storage.modified_time(test_file), datetime.datetime) assert isinstance(storage.get_modified_time(test_file), datetime.datetime) def test_should_be_able_to_delete_files(self, storage): @@ -192,7 +188,7 @@ def test_changed_files_should_be_reuploaded(self, storage): file_content = "gcloud".encode("ascii") upload_test_file(storage, file_name, "") - first_modified_time = storage.modified_time(file_name) + first_modified_time = storage.get_modified_time(file_name) local_tmpfile = storage.open(file_name) assert local_tmpfile.read() == "".encode("ascii") @@ -202,7 +198,7 @@ def test_changed_files_should_be_reuploaded(self, storage): local_tmpfile.close() assert storage.open(file_name).read() == file_content - assert storage.modified_time(file_name) != first_modified_time + assert storage.get_modified_time(file_name) != first_modified_time def test_open_should_be_able_to_create_new_file(self, storage): file_name = "test_open_creates_file" diff --git a/tests/test_django.py b/tests/test_django.py index 72c763a..ec67ae1 100644 --- a/tests/test_django.py +++ b/tests/test_django.py @@ -1,10 +1,9 @@ # coding=utf-8 -from __future__ import unicode_literals - import contextlib import os import shutil import tempfile +from tempfile import TemporaryDirectory import pytest from django.core.files import File @@ -12,11 +11,6 @@ from test_app.app.models import ModelWithFileField -try: - from tempfile import TemporaryDirectory -except ImportError: - from setuptools.py31compat import TemporaryDirectory - @contextlib.contextmanager def make_temp_directory(): diff --git a/tox.ini b/tox.ini index d8ac03b..93959aa 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,12 @@ [tox] envlist = - {py27,py34,py35,py36,py37,pypy2,pypy3}-django111, - {py35,py36,py37,pypy3}-{django21,django22}, - {py36,py37,py38,pypy3}-{django3}, + {py37,py38,py39,pypy37,pypy38}-{django22}, + {py37,py38,py39,pypy37,pypy38}-{django32}, [testenv] deps = -rrequirements-test.txt - django111: django<1.12 - django21: django<2.2 django22: django<2.3 - django3: django==3.0a1 + django32: django<3.3 commands = py.test {posargs} [pytest] DJANGO_SETTINGS_MODULE = test_app.app.settings