diff --git a/django/apps/config.py b/django/apps/config.py index 1bc684b3cac..48455570511 100644 --- a/django/apps/config.py +++ b/django/apps/config.py @@ -57,8 +57,8 @@ class AppConfig: """Attempt to determine app's filesystem path from its module.""" # See #21874 for extended discussion of the behavior of this method in # various cases. - # Convert paths to list because Python 3's _NamespacePath does not - # support indexing. + # Convert paths to list because Python's _NamespacePath doesn't support + # indexing. paths = list(getattr(module, '__path__', [])) if len(paths) != 1: filename = getattr(module, '__file__', None) diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py index c52f31db59a..49b4fa5b14b 100644 --- a/django/contrib/auth/hashers.py +++ b/django/contrib/auth/hashers.py @@ -419,8 +419,7 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher): # Hash the password prior to using bcrypt to prevent password # truncation as described in #20138. if self.digest is not None: - # Use binascii.hexlify() because a hex encoded bytestring is - # Unicode on Python 3. + # Use binascii.hexlify() because a hex encoded bytestring is str. password = binascii.hexlify(self.digest(force_bytes(password)).digest()) else: password = force_bytes(password) diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 6208b5fe414..c0e102234de 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -298,7 +298,7 @@ def password_reset_confirm(request, uidb64=None, token=None, else: post_reset_redirect = resolve_url(post_reset_redirect) try: - # urlsafe_base64_decode() decodes to bytestring on Python 3 + # urlsafe_base64_decode() decodes to bytestring uid = force_text(urlsafe_base64_decode(uidb64)) user = UserModel._default_manager.get(pk=uid) except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist): @@ -442,7 +442,7 @@ class PasswordResetConfirmView(PasswordContextMixin, FormView): def get_user(self, uidb64): try: - # urlsafe_base64_decode() decodes to bytestring on Python 3 + # urlsafe_base64_decode() decodes to bytestring uid = force_text(urlsafe_base64_decode(uidb64)) user = UserModel._default_manager.get(pk=uid) except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist): diff --git a/django/core/mail/message.py b/django/core/mail/message.py index f4f3eb1352e..ae2e017b6e4 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -324,8 +324,8 @@ class EmailMessage: try: content = content.decode('utf-8') except UnicodeDecodeError: - # If mimetype suggests the file is text but it's actually - # binary, read() will raise a UnicodeDecodeError on Python 3. + # If mimetype suggests the file is text but it's + # actually binary, read() raises a UnicodeDecodeError. mimetype = DEFAULT_ATTACHMENT_MIME_TYPE self.attachments.append((filename, content, mimetype)) diff --git a/django/core/management/commands/shell.py b/django/core/management/commands/shell.py index ce4d9daa7e9..4b91c1b9774 100644 --- a/django/core/management/commands/shell.py +++ b/django/core/management/commands/shell.py @@ -53,7 +53,7 @@ class Command(BaseCommand): import rlcompleter readline.set_completer(rlcompleter.Completer(imported_objects).complete) # Enable tab completion on systems using libedit (e.g. Mac OSX). - # These lines are copied from Lib/site.py on Python 3.4. + # These lines are copied from Python's Lib/site.py. readline_doc = getattr(readline, '__doc__', '') if readline_doc is not None and 'libedit' in readline_doc: readline.parse_and_bind("bind ^I rl_complete") diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 0cc21b639b7..d19f1893589 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -322,8 +322,7 @@ class OracleParam: param = Oracle_datetime.from_datetime(param) string_size = 0 - # Oracle doesn't recognize True and False correctly in Python 3. - # The conversion done below works both in 2 and 3. + # Oracle doesn't recognize True and False correctly. if param is True: param = 1 elif param is False: diff --git a/django/db/migrations/serializer.py b/django/db/migrations/serializer.py index fac8e4c189a..f56b5053816 100644 --- a/django/db/migrations/serializer.py +++ b/django/db/migrations/serializer.py @@ -8,7 +8,6 @@ import math import re import types import uuid -from importlib import import_module from django.db import models from django.db.migrations.operations.base import Operation @@ -155,20 +154,15 @@ class FunctionTypeSerializer(BaseSerializer): raise ValueError("Cannot serialize function: lambda") if self.value.__module__ is None: raise ValueError("Cannot serialize function %r: No module" % self.value) - # Python 3 is a lot easier, and only uses this branch if it's not local. - if getattr(self.value, "__qualname__", None) and getattr(self.value, "__module__", None): - if "<" not in self.value.__qualname__: # Qualname can include - return "%s.%s" % \ - (self.value.__module__, self.value.__qualname__), {"import %s" % self.value.__module__} - # Fallback version + module_name = self.value.__module__ - # Make sure it's actually there - module = import_module(module_name) - if not hasattr(module, self.value.__name__): - raise ValueError( - "Could not find function %s in %s.\n" % (self.value.__name__, module_name) - ) - return "%s.%s" % (module_name, self.value.__name__), {"import %s" % module_name} + + if '<' not in self.value.__qualname__: # Qualname can include + return '%s.%s' % (module_name, self.value.__qualname__), {'import %s' % self.value.__module__} + + raise ValueError( + 'Could not find function %s in %s.\n' % (self.value.__name__, module_name) + ) class FunctoolsPartialSerializer(BaseSerializer): diff --git a/django/test/runner.py b/django/test/runner.py index 38578e7e0cc..cb1893c56a1 100644 --- a/django/test/runner.py +++ b/django/test/runner.py @@ -258,8 +258,7 @@ def default_test_processes(): """ # The current implementation of the parallel test runner requires # multiprocessing to start subprocesses with fork(). - # On Python 3.4+: if multiprocessing.get_start_method() != 'fork': - if not hasattr(os, 'fork'): + if multiprocessing.get_start_method() != 'fork': return 1 try: return int(os.environ['DJANGO_TEST_PROCESSES']) diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt index 6a91069abce..8316beea759 100644 --- a/docs/internals/howto-release-django.txt +++ b/docs/internals/howto-release-django.txt @@ -303,7 +303,7 @@ Now you're ready to actually put the release out there. To do this: $ pip install https://www.djangoproject.com/m/releases/$MAJOR_VERSION/Django-$RELEASE_VERSION.tar.gz $ deactivate $ mktmpenv - $ pip install https://www.djangoproject.com/m/releases/$MAJOR_VERSION/Django-$RELEASE_VERSION-py2.py3-none-any.whl + $ pip install https://www.djangoproject.com/m/releases/$MAJOR_VERSION/Django-$RELEASE_VERSION-py3-none-any.whl $ deactivate This just tests that the tarballs are available (i.e. redirects are up) and diff --git a/docs/ref/contrib/gis/install/geodjango_setup.bat b/docs/ref/contrib/gis/install/geodjango_setup.bat deleted file mode 100644 index b3e6cc6822a..00000000000 --- a/docs/ref/contrib/gis/install/geodjango_setup.bat +++ /dev/null @@ -1,8 +0,0 @@ -set OSGEO4W_ROOT=C:\OSGeo4W -set PYTHON_ROOT=C:\Python27 -set GDAL_DATA=%OSGEO4W_ROOT%\share\gdal -set PROJ_LIB=%OSGEO4W_ROOT%\share\proj -set PATH=%PATH%;%PYTHON_ROOT%;%OSGEO4W_ROOT%\bin -reg ADD "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path /t REG_EXPAND_SZ /f /d "%PATH%" -reg ADD "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v GDAL_DATA /t REG_EXPAND_SZ /f /d "%GDAL_DATA%" -reg ADD "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v PROJ_LIB /t REG_EXPAND_SZ /f /d "%PROJ_LIB%" diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt index 06990943ef3..53cee2920ae 100644 --- a/docs/ref/contrib/gis/install/index.txt +++ b/docs/ref/contrib/gis/install/index.txt @@ -463,7 +463,7 @@ executable with ``cmd.exe``, will set this up: .. code-block:: bat set OSGEO4W_ROOT=C:\OSGeo4W - set PYTHON_ROOT=C:\Python27 + set PYTHON_ROOT=C:\Python3X set GDAL_DATA=%OSGEO4W_ROOT%\share\gdal set PROJ_LIB=%OSGEO4W_ROOT%\share\proj set PATH=%PATH%;%PYTHON_ROOT%;%OSGEO4W_ROOT%\bin @@ -471,15 +471,12 @@ executable with ``cmd.exe``, will set this up: reg ADD "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v GDAL_DATA /t REG_EXPAND_SZ /f /d "%GDAL_DATA%" reg ADD "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v PROJ_LIB /t REG_EXPAND_SZ /f /d "%PROJ_LIB%" -For your convenience, these commands are available in the executable batch -script, :download:`geodjango_setup.bat`. - .. note:: Administrator privileges are required to execute these commands. - To do this, right-click on :download:`geodjango_setup.bat` and select - :menuselection:`Run as administrator`. You need to log out and log back in again - for the settings to take effect. + To do this, create a ``bat`` script with the commands, right-click it, and + select :menuselection:`Run as administrator`. You need to log out and log + back in again for the settings to take effect. .. note:: diff --git a/setup.cfg b/setup.cfg index d84a4aefc76..5b37a54c11a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,6 +18,3 @@ not_skip = __init__.py [metadata] license-file = LICENSE - -[wheel] -universal = 1 diff --git a/setup.py b/setup.py index d8dfa58a17d..e9cfa4f0938 100644 --- a/setup.py +++ b/setup.py @@ -61,8 +61,6 @@ setup( '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.5', diff --git a/tests/README.rst b/tests/README.rst index 7d4ddb513a0..7f64afe6fcb 100644 --- a/tests/README.rst +++ b/tests/README.rst @@ -3,7 +3,7 @@ install some requirements and run the tests:: $ cd tests $ pip install -e .. - $ pip install -r requirements/py3.txt # or py2.txt + $ pip install -r requirements/py3.txt $ ./runtests.py For more information about the test suite, see diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py index 4e7bae280de..aec7021cab0 100644 --- a/tests/auth_tests/test_hashers.py +++ b/tests/auth_tests/test_hashers.py @@ -434,7 +434,6 @@ class TestUtilsHashPass(SimpleTestCase): def test_load_library_importerror(self): PlainHasher = type('PlainHasher', (BasePasswordHasher,), {'algorithm': 'plain', 'library': 'plain'}) - # Python 3 adds quotes around module name msg = "Couldn't load 'PlainHasher' algorithm library: No module named 'plain'" with self.assertRaisesMessage(ValueError, msg): PlainHasher()._load_library() diff --git a/tests/cache/tests.py b/tests/cache/tests.py index d519080c9a8..6b462f29c24 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -1912,10 +1912,6 @@ class CacheI18nTest(TestCase): get_cache_data = FetchFromCacheMiddleware().process_request(request) self.assertIsNone(get_cache_data) - # This test passes on Python < 3.3 even without the corresponding code - # in UpdateCacheMiddleware, because pickling a StreamingHttpResponse - # fails (http://bugs.python.org/issue14288). LocMemCache silently - # swallows the exception and doesn't store the response in cache. content = ['Check for cache with streaming content.'] response = StreamingHttpResponse(content) UpdateCacheMiddleware().process_response(request, response) diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index 69d0236fad7..c0acd3f9f0d 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -16,6 +16,7 @@ from django.core.management.commands.makemessages import \ from django.core.management.utils import find_command from django.test import SimpleTestCase, override_settings from django.test.utils import captured_stderr, captured_stdout +from django.utils._os import symlinks_supported from django.utils.translation import TranslatorCommentWarning from .utils import POFileAssertionMixin, RunInTmpDirMixin, copytree @@ -382,7 +383,10 @@ class BasicExtractorTests(ExtractorTests): cmd.gettext_version def test_po_file_encoding_when_updating(self): - """Update of PO file doesn't corrupt it with non-UTF-8 encoding on Python3+Windows (#23271)""" + """ + Update of PO file doesn't corrupt it with non-UTF-8 encoding on Windows + (#23271). + """ BR_PO_BASE = 'locale/pt_BR/LC_MESSAGES/django' shutil.copyfile(BR_PO_BASE + '.pristine', BR_PO_BASE + '.po') management.call_command('makemessages', locale=['pt_BR'], verbosity=0) @@ -472,29 +476,20 @@ class SymlinkExtractorTests(ExtractorTests): self.symlinked_dir = os.path.join(self.test_dir, 'templates_symlinked') def test_symlink(self): - # On Python < 3.2 os.symlink() exists only on Unix - if hasattr(os, 'symlink'): - if os.path.exists(self.symlinked_dir): - self.assertTrue(os.path.islink(self.symlinked_dir)) - else: - # On Python >= 3.2) os.symlink() exists always but then can - # fail at runtime when user hasn't the needed permissions on - # Windows versions that support symbolink links (>= 6/Vista). - # See Python issue 9333 (http://bugs.python.org/issue9333). - # Skip the test in that case - try: - os.symlink(os.path.join(self.test_dir, 'templates'), self.symlinked_dir) - except (OSError, NotImplementedError): - self.skipTest("os.symlink() is available on this OS but can't be used by this user.") - os.chdir(self.test_dir) - management.call_command('makemessages', locale=[LOCALE], verbosity=0, symlinks=True) - self.assertTrue(os.path.exists(self.PO_FILE)) - with open(self.PO_FILE, 'r') as fp: - po_contents = fp.read() - self.assertMsgId('This literal should be included.', po_contents) - self.assertLocationCommentPresent(self.PO_FILE, None, 'templates_symlinked', 'test.html') + if os.path.exists(self.symlinked_dir): + self.assertTrue(os.path.islink(self.symlinked_dir)) else: - self.skipTest("os.symlink() not available on this OS + Python version combination.") + if symlinks_supported(): + os.symlink(os.path.join(self.test_dir, 'templates'), self.symlinked_dir) + else: + self.skipTest("os.symlink() not available on this OS + Python version combination.") + os.chdir(self.test_dir) + management.call_command('makemessages', locale=[LOCALE], verbosity=0, symlinks=True) + self.assertTrue(os.path.exists(self.PO_FILE)) + with open(self.PO_FILE, 'r') as fp: + po_contents = fp.read() + self.assertMsgId('This literal should be included.', po_contents) + self.assertLocationCommentPresent(self.PO_FILE, None, 'templates_symlinked', 'test.html') class CopyPluralFormsExtractorTests(ExtractorTests): diff --git a/tests/mail/tests.py b/tests/mail/tests.py index 60758a637b7..a902e5435c1 100644 --- a/tests/mail/tests.py +++ b/tests/mail/tests.py @@ -192,7 +192,6 @@ class MailTests(HeadersCheckMixin, SimpleTestCase): 'Content', 'from@example.com', ['to@example.com'] ) message = email.message() - # Note that in Python 3, maximum line length has increased from 76 to 78 self.assertEqual( message['Subject'].encode(), b'Long subject lines that get wrapped should contain a space continuation\n' @@ -1157,8 +1156,8 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread): if mailfrom != maddr: # According to the spec, mailfrom does not necessarily match the - # From header - on Python 3 this is the case where the local part - # isn't encoded, so try to correct that. + # From header - this is the case where the local part isn't + # encoded, so try to correct that. lp, domain = mailfrom.split('@', 1) lp = Header(lp, 'utf-8').encode() mailfrom = '@'.join([lp, domain]) diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index d5d31b7c7c1..af83eb0a6ae 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -672,8 +672,8 @@ class MakeMigrationsTests(MigrationTestBase): module = 'migrations.test_migrations_order' with self.temporary_migration_module(module=module) as migration_dir: if hasattr(importlib, 'invalidate_caches'): - # Python 3 importlib caches os.listdir() on some platforms like - # Mac OS X (#23850). + # importlib caches os.listdir() on some platforms like Mac OS X + # (#23850). importlib.invalidate_caches() call_command('makemigrations', 'migrations', '--empty', '-n', 'a', '-v', '0') self.assertTrue(os.path.exists(os.path.join(migration_dir, '0002_a.py'))) @@ -1202,8 +1202,8 @@ class MakeMigrationsTests(MigrationTestBase): content = cmd("0001", migration_name_0001) self.assertIn("dependencies=[\n]", content) - # Python 3 importlib caches os.listdir() on some platforms like - # Mac OS X (#23850). + # importlib caches os.listdir() on some platforms like Mac OS X + # (#23850). if hasattr(importlib, 'invalidate_caches'): importlib.invalidate_caches() diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index 1de0fec0aaa..32751d89237 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -40,6 +40,12 @@ class Money(decimal.Decimal): ) +class TestModel1(object): + def upload_to(self): + return '/somewhere/dynamic/' + thing = models.FileField(upload_to=upload_to) + + class OperationWriterTests(SimpleTestCase): def test_empty_signature(self): @@ -472,21 +478,12 @@ class WriterTests(SimpleTestCase): self.assertEqual(string, 'range') self.assertEqual(imports, set()) - def test_serialize_local_function_reference(self): - """ - Neither py2 or py3 can serialize a reference in a local scope. - """ - class TestModel2: - def upload_to(self): - return "somewhere dynamic" - thing = models.FileField(upload_to=upload_to) - with self.assertRaises(ValueError): - self.serialize_round_trip(TestModel2.thing) + def test_serialize_unbound_method_reference(self): + """An unbound method used within a class body can be serialized.""" + self.serialize_round_trip(TestModel1.thing) - def test_serialize_local_function_reference_message(self): - """ - Make sure user is seeing which module/function is the issue - """ + def test_serialize_local_function_reference(self): + """A reference in a local scope can't be serialized.""" class TestModel2: def upload_to(self): return "somewhere dynamic" diff --git a/tests/test_client/test_conditional_content_removal.py b/tests/test_client/test_conditional_content_removal.py index 72bf7f238bb..bd26b8fb521 100644 --- a/tests/test_client/test_conditional_content_removal.py +++ b/tests/test_client/test_conditional_content_removal.py @@ -1,19 +1,10 @@ import gzip -import io from django.http import HttpRequest, HttpResponse, StreamingHttpResponse from django.test import SimpleTestCase from django.test.client import conditional_content_removal -# based on Python 3.3's gzip.compress -def gzip_compress(data): - buf = io.BytesIO() - with gzip.GzipFile(fileobj=buf, mode='wb', compresslevel=0) as f: - f.write(data) - return buf.getvalue() - - class ConditionalContentTests(SimpleTestCase): def test_conditional_content_removal(self): @@ -43,7 +34,7 @@ class ConditionalContentTests(SimpleTestCase): self.assertEqual(b''.join(res), b'') # Issue #20472 - abc = gzip_compress(b'abc') + abc = gzip.compress(b'abc') res = HttpResponse(abc, status=304) res['Content-Encoding'] = 'gzip' conditional_content_removal(req, res) diff --git a/tox.ini b/tox.ini index e9892a75a42..b9ed6c9db96 100644 --- a/tox.ini +++ b/tox.ini @@ -11,10 +11,7 @@ envlist = docs isort -# Add environments to use default python2 and python3 installations -[testenv:py2] -basepython = python2 - +# Add environment to use the default python3 installation [testenv:py3] basepython = python3 @@ -24,7 +21,6 @@ passenv = DJANGO_SETTINGS_MODULE PYTHONPATH HOME DISPLAY setenv = PYTHONDONTWRITEBYTECODE=1 deps = - py{2,27}: -rtests/requirements/py2.txt py{3,34,35,36}: -rtests/requirements/py3.txt postgres: -rtests/requirements/postgres.txt mysql: -rtests/requirements/mysql.txt @@ -41,8 +37,7 @@ changedir = {toxinidir} commands = flake8 . [testenv:docs] -# On OS X, as of pyenchant 1.6.6, the docs build only works under Python 2. -basepython = python2 +basepython = python3 usedevelop = false whitelist_externals = make