Fixed #30116 -- Dropped support for Python 3.5.

This commit is contained in:
Tim Graham 2019-01-18 10:04:29 -05:00
parent 5a5c77d55d
commit 7e6b214ed3
23 changed files with 25 additions and 141 deletions

View File

@ -1,6 +1,6 @@
Thanks for downloading Django. Thanks for downloading Django.
To install it, make sure you have Python 3.5 or greater installed. Then run To install it, make sure you have Python 3.6 or greater installed. Then run
this command from the command prompt: this command from the command prompt:
python setup.py install python setup.py install

View File

@ -77,10 +77,8 @@ def _check_lazy_references(apps, ignore=None):
""" """
operation, args, keywords = obj, [], {} operation, args, keywords = obj, [], {}
while hasattr(operation, 'func'): while hasattr(operation, 'func'):
# The or clauses are redundant but work around a bug (#25945) in args.extend(getattr(operation, 'args', []))
# functools.partial in Python <= 3.5.1. keywords.update(getattr(operation, 'keywords', {}))
args.extend(getattr(operation, 'args', []) or [])
keywords.update(getattr(operation, 'keywords', {}) or {})
operation = operation.func operation = operation.func
return operation, args, keywords return operation, args, keywords

View File

@ -160,10 +160,6 @@ class BaseDatabaseFeatures:
# Support for the DISTINCT ON clause # Support for the DISTINCT ON clause
can_distinct_on_fields = False can_distinct_on_fields = False
# Does the backend decide to commit before SAVEPOINT statements
# when autocommit is disabled? https://bugs.python.org/issue8145#msg109965
autocommits_when_autocommit_is_off = False
# Does the backend prevent running SQL queries in broken transactions? # Does the backend prevent running SQL queries in broken transactions?
atomic_transactions = True atomic_transactions = True

View File

@ -1,5 +1,3 @@
import sys
from django.db.backends.base.features import BaseDatabaseFeatures from django.db.backends.base.features import BaseDatabaseFeatures
from .base import Database from .base import Database
@ -15,7 +13,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_timezones = False supports_timezones = False
max_query_params = 999 max_query_params = 999
supports_mixed_date_datetime_comparisons = False supports_mixed_date_datetime_comparisons = False
autocommits_when_autocommit_is_off = sys.version_info < (3, 6)
can_introspect_autofield = True can_introspect_autofield = True
can_introspect_decimal_field = False can_introspect_decimal_field = False
can_introspect_duration_field = False can_introspect_duration_field = False

View File

@ -173,14 +173,6 @@ class Atomic(ContextDecorator):
connection.commit_on_exit = True connection.commit_on_exit = True
connection.needs_rollback = False connection.needs_rollback = False
if not connection.get_autocommit(): if not connection.get_autocommit():
# sqlite3 in Python < 3.6 doesn't handle transactions and
# savepoints properly when autocommit is off.
# Turning autocommit back on isn't an option; it would trigger
# a premature commit. Give up if that happens.
if connection.features.autocommits_when_autocommit_is_off:
raise TransactionManagementError(
"Your database backend doesn't behave properly when "
"autocommit is off. Turn it on before using 'atomic'.")
# Pretend we're already in an atomic block to bypass the code # Pretend we're already in an atomic block to bypass the code
# that disables autocommit to enter a transaction, and make a # that disables autocommit to enter a transaction, and make a
# note to deal with this case in __exit__. # note to deal with this case in __exit__.

View File

@ -3,8 +3,6 @@ import itertools
import operator import operator
from functools import total_ordering, wraps from functools import total_ordering, wraps
from django.utils.version import PY36, get_docs_version
# You can't trivially replace this with `functools.partial` because this binds # You can't trivially replace this with `functools.partial` because this binds
# to classes and returns bound instances, whereas functools.partial (on # to classes and returns bound instances, whereas functools.partial (on
@ -22,8 +20,8 @@ class cached_property:
A cached property can be made out of an existing method: A cached property can be made out of an existing method:
(e.g. ``url = cached_property(get_absolute_url)``). (e.g. ``url = cached_property(get_absolute_url)``).
On Python < 3.6, the optional ``name`` argument must be provided, e.g. The optional ``name`` argument is obsolete as of Python 3.6 and will be
``url = cached_property(get_absolute_url, name='url')``. deprecated in Django 4.0 (#30127).
""" """
name = None name = None
@ -34,29 +32,8 @@ class cached_property:
'__set_name__() on it.' '__set_name__() on it.'
) )
@staticmethod
def _is_mangled(name):
return name.startswith('__') and not name.endswith('__')
def __init__(self, func, name=None): def __init__(self, func, name=None):
if PY36: self.real_func = func
self.real_func = func
else:
func_name = func.__name__
name = name or func_name
if not (isinstance(name, str) and name.isidentifier()):
raise ValueError(
"%r can't be used as the name of a cached_property." % name,
)
if self._is_mangled(name):
raise ValueError(
'cached_property does not work with mangled methods on '
'Python < 3.6 without the appropriate `name` argument. See '
'https://docs.djangoproject.com/en/%s/ref/utils/'
'#cached-property-mangled-name' % get_docs_version(),
)
self.name = name
self.func = func
self.__doc__ = getattr(func, '__doc__') self.__doc__ = getattr(func, '__doc__')
def __set_name__(self, owner, name): def __set_name__(self, owner, name):

View File

@ -72,11 +72,10 @@ def module_has_submodule(package, module_name):
full_module_name = package_name + '.' + module_name full_module_name = package_name + '.' + module_name
try: try:
return importlib_find(full_module_name, package_path) is not None return importlib_find(full_module_name, package_path) is not None
except (ImportError, AttributeError): except (ModuleNotFoundError, AttributeError):
# When module_name is an invalid dotted path, Python raises ImportError # When module_name is an invalid dotted path, Python raises
# (or ModuleNotFoundError in Python 3.6+). AttributeError may be raised # ModuleNotFoundError. AttributeError is raised on PY36 (fixed in PY37)
# if the penultimate part of the path is not a package. # if the penultimate part of the path is not a package.
# (https://bugs.python.org/issue30436)
return False return False
@ -87,7 +86,7 @@ def module_dir(module):
Raise ValueError otherwise, e.g. for namespace packages that are split Raise ValueError otherwise, e.g. for namespace packages that are split
over several directories. over several directories.
""" """
# Convert to list because _NamespacePath does not support indexing on 3.3. # Convert to list because _NamespacePath does not support indexing.
paths = list(getattr(module, '__path__', [])) paths = list(getattr(module, '__path__', []))
if len(paths) == 1: if len(paths) == 1:
return paths[0] return paths[0]

View File

@ -2,7 +2,7 @@
How to install Django on Windows How to install Django on Windows
================================ ================================
This document will guide you through installing Python 3.5 and Django on This document will guide you through installing Python 3.7 and Django on
Windows. It also provides instructions for installing `virtualenv`_ and Windows. It also provides instructions for installing `virtualenv`_ and
`virtualenvwrapper`_, which make it easier to work on Python projects. This is `virtualenvwrapper`_, which make it easier to work on Python projects. This is
meant as a beginner's guide for users working on Django projects and does not meant as a beginner's guide for users working on Django projects and does not
@ -17,12 +17,12 @@ Install Python
============== ==============
Django is a Python web framework, thus requiring Python to be installed on your Django is a Python web framework, thus requiring Python to be installed on your
machine. At the time of writing, Python 3.5 is the latest version. machine. At the time of writing, Python 3.7 is the latest version.
To install Python on your machine go to https://python.org/downloads/. The To install Python on your machine go to https://python.org/downloads/. The
website should offer you a download button for the latest Python version. website should offer you a download button for the latest Python version.
Download the executable installer and run it. Check the box next to ``Add Download the executable installer and run it. Check the box next to ``Add
Python 3.5 to PATH`` and then click ``Install Now``. Python 3.7 to PATH`` and then click ``Install Now``.
After installation, open the command prompt and check that the Python version After installation, open the command prompt and check that the Python version
matches the version you installed by executing:: matches the version you installed by executing::

View File

@ -90,12 +90,12 @@ In addition to the default environments, ``tox`` supports running unit tests
for other versions of Python and other database backends. Since Django's test for other versions of Python and other database backends. Since Django's test
suite doesn't bundle a settings file for database backends other than SQLite, suite doesn't bundle a settings file for database backends other than SQLite,
however, you must :ref:`create and provide your own test settings however, you must :ref:`create and provide your own test settings
<running-unit-tests-settings>`. For example, to run the tests on Python 3.5 <running-unit-tests-settings>`. For example, to run the tests on Python 3.7
using PostgreSQL:: using PostgreSQL::
$ tox -e py35-postgres -- --settings=my_postgres_settings $ tox -e py37-postgres -- --settings=my_postgres_settings
This command sets up a Python 3.5 virtual environment, installs Django's This command sets up a Python 3.7 virtual environment, installs Django's
test suite dependencies (including those for PostgreSQL), and calls test suite dependencies (including those for PostgreSQL), and calls
``runtests.py`` with the supplied arguments (in this case, ``runtests.py`` with the supplied arguments (in this case,
``--settings=my_postgres_settings``). ``--settings=my_postgres_settings``).

View File

@ -219,8 +219,8 @@ this. For a small app like polls, this process isn't too difficult.
'License :: OSI Approved :: BSD License', # example license 'License :: OSI Approved :: BSD License', # example license
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
], ],

View File

@ -23,7 +23,7 @@ in a shell prompt (indicated by the $ prefix):
If Django is installed, you should see the version of your installation. If it If Django is installed, you should see the version of your installation. If it
isn't, you'll get an error telling "No module named django". isn't, you'll get an error telling "No module named django".
This tutorial is written for Django |version|, which supports Python 3.5 and This tutorial is written for Django |version|, which supports Python 3.6 and
later. If the Django version doesn't match, you can refer to the tutorial for later. If the Django version doesn't match, you can refer to the tutorial for
your version of Django by using the version switcher at the bottom right corner your version of Django by using the version switcher at the bottom right corner
of this page, or update Django to the newest version. If you're using an older of this page, or update Django to the newest version. If you're using an older

View File

@ -514,18 +514,6 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004
z = person.friends # does not call z = person.friends # does not call
x is z # is True x is z # is True
.. warning::
.. _cached-property-mangled-name:
On Python < 3.6, ``cached_property`` doesn't work properly with a
mangled__ name unless it's passed a ``name`` of the form
``_Class__attribute``::
__friends = cached_property(get_friends, name='_Person__friends')
__ https://docs.python.org/faq/programming.html#i-try-to-use-spam-and-i-get-an-error-about-someclassname-spam
.. function:: keep_lazy(func, *resultclasses) .. function:: keep_lazy(func, *resultclasses)
Django offers many utility functions (particularly in ``django.utils``) Django offers many utility functions (particularly in ``django.utils``)

View File

@ -1329,23 +1329,6 @@ The decorator can also be applied to test case classes::
decorator. For a given class, :func:`~django.test.modify_settings` is decorator. For a given class, :func:`~django.test.modify_settings` is
always applied after :func:`~django.test.override_settings`. always applied after :func:`~django.test.override_settings`.
.. admonition:: Considerations with Python 3.5
If using Python 3.5 (or older, if using an older version of Django), avoid
mixing ``remove`` with ``append`` and ``prepend`` in
:func:`~django.test.modify_settings`. In some cases it matters whether a
value is first added and then removed or vice versa, and dictionary key
order isn't preserved until Python 3.6. Instead, apply the decorator twice
to guarantee the order of operations. For example, to ensure that
``SessionMiddleware`` appears first in ``MIDDLEWARE``::
@modify_settings(MIDDLEWARE={
'remove': ['django.contrib.sessions.middleware.SessionMiddleware'],
)
@modify_settings(MIDDLEWARE={
'prepend': ['django.contrib.sessions.middleware.SessionMiddleware'],
})
.. warning:: .. warning::
The settings file contains some settings that are only consulted during The settings file contains some settings that are only consulted during

View File

@ -5,7 +5,7 @@ from distutils.sysconfig import get_python_lib
from setuptools import find_packages, setup from setuptools import find_packages, setup
CURRENT_PYTHON = sys.version_info[:2] CURRENT_PYTHON = sys.version_info[:2]
REQUIRED_PYTHON = (3, 5) REQUIRED_PYTHON = (3, 6)
# This check and everything above must remain compatible with Python 2.7. # This check and everything above must remain compatible with Python 2.7.
if CURRENT_PYTHON < REQUIRED_PYTHON: if CURRENT_PYTHON < REQUIRED_PYTHON:
@ -98,7 +98,6 @@ setup(
'Operating System :: OS Independent', 'Operating System :: OS Independent',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3 :: Only',

View File

@ -32,7 +32,6 @@ from django.db.migrations.recorder import MigrationRecorder
from django.test import ( from django.test import (
LiveServerTestCase, SimpleTestCase, TestCase, override_settings, LiveServerTestCase, SimpleTestCase, TestCase, override_settings,
) )
from django.utils.version import PY36
custom_templates_dir = os.path.join(os.path.dirname(__file__), 'custom_templates') custom_templates_dir = os.path.join(os.path.dirname(__file__), 'custom_templates')
@ -1145,7 +1144,7 @@ class ManageCheck(AdminScriptTestCase):
args = ['check'] args = ['check']
out, err = self.run_manage(args) out, err = self.run_manage(args)
self.assertNoOutput(out) self.assertNoOutput(out)
self.assertOutput(err, 'ModuleNotFoundError' if PY36 else 'ImportError') self.assertOutput(err, 'ModuleNotFoundError')
self.assertOutput(err, 'No module named') self.assertOutput(err, 'No module named')
self.assertOutput(err, 'admin_scriptz') self.assertOutput(err, 'admin_scriptz')

View File

@ -1211,10 +1211,7 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
threading.Thread.__init__(self) threading.Thread.__init__(self)
# New kwarg added in Python 3.5; default switching to False in 3.6. smtpd.SMTPServer.__init__(self, *args, decode_data=True, **kwargs)
# Setting a value only silences a deprecation warning in Python 3.5.
kwargs['decode_data'] = True
smtpd.SMTPServer.__init__(self, *args, **kwargs)
self._sink = [] self._sink = []
self.active = False self.active = False
self.active_lock = threading.Lock() self.active_lock = threading.Lock()

View File

@ -22,7 +22,6 @@ from django.utils.deconstruct import deconstructible
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from django.utils.timezone import get_default_timezone, get_fixed_timezone, utc from django.utils.timezone import get_default_timezone, get_fixed_timezone, utc
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.utils.version import PY36
from .models import FoodManager, FoodQuerySet from .models import FoodManager, FoodQuerySet
@ -413,10 +412,7 @@ class WriterTests(SimpleTestCase):
# Test a string regex with flag # Test a string regex with flag
validator = RegexValidator(r'^[0-9]+$', flags=re.S) validator = RegexValidator(r'^[0-9]+$', flags=re.S)
string = MigrationWriter.serialize(validator)[0] string = MigrationWriter.serialize(validator)[0]
if PY36: self.assertEqual(string, "django.core.validators.RegexValidator('^[0-9]+$', flags=re.RegexFlag(16))")
self.assertEqual(string, "django.core.validators.RegexValidator('^[0-9]+$', flags=re.RegexFlag(16))")
else:
self.assertEqual(string, "django.core.validators.RegexValidator('^[0-9]+$', flags=16)")
self.serialize_round_trip(validator) self.serialize_round_trip(validator)
# Test message and code # Test message and code

View File

@ -1,11 +1,9 @@
import unittest
from operator import attrgetter from operator import attrgetter
from django.core.exceptions import FieldError, ValidationError from django.core.exceptions import FieldError, ValidationError
from django.db import connection, models from django.db import connection, models
from django.test import SimpleTestCase, TestCase from django.test import SimpleTestCase, TestCase
from django.test.utils import CaptureQueriesContext, isolate_apps from django.test.utils import CaptureQueriesContext, isolate_apps
from django.utils.version import PY36
from .models import ( from .models import (
Base, Chef, CommonInfo, GrandChild, GrandParent, ItalianRestaurant, Base, Chef, CommonInfo, GrandChild, GrandParent, ItalianRestaurant,
@ -176,7 +174,6 @@ class ModelInheritanceTests(TestCase):
self.assertIs(C._meta.parents[A], C._meta.get_field('a')) self.assertIs(C._meta.parents[A], C._meta.get_field('a'))
@unittest.skipUnless(PY36, 'init_subclass is new in Python 3.6')
@isolate_apps('model_inheritance') @isolate_apps('model_inheritance')
def test_init_subclass(self): def test_init_subclass(self):
saved_kwargs = {} saved_kwargs = {}
@ -193,7 +190,6 @@ class ModelInheritanceTests(TestCase):
self.assertEqual(saved_kwargs, kwargs) self.assertEqual(saved_kwargs, kwargs)
@unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
@isolate_apps('model_inheritance') @isolate_apps('model_inheritance')
def test_set_name(self): def test_set_name(self):
class ClassAttr: class ClassAttr:

View File

@ -234,7 +234,7 @@ def teardown(state):
# Discard the multiprocessing.util finalizer that tries to remove a # Discard the multiprocessing.util finalizer that tries to remove a
# temporary directory that's already removed by this script's # temporary directory that's already removed by this script's
# atexit.register(shutil.rmtree, TMPDIR) handler. Prevents # atexit.register(shutil.rmtree, TMPDIR) handler. Prevents
# FileNotFoundError at the end of a test run on Python 3.6+ (#27890). # FileNotFoundError at the end of a test run (#27890).
from multiprocessing.util import _finalizer_registry from multiprocessing.util import _finalizer_registry
_finalizer_registry.pop((-100, 0), None) _finalizer_registry.pop((-100, 0), None)

View File

@ -228,7 +228,6 @@ class AtomicInsideTransactionTests(AtomicTests):
self.atomic.__exit__(*sys.exc_info()) self.atomic.__exit__(*sys.exc_info())
@skipIfDBFeature('autocommits_when_autocommit_is_off')
class AtomicWithoutAutocommitTests(AtomicTests): class AtomicWithoutAutocommitTests(AtomicTests):
"""All basic tests for atomic should also pass when autocommit is turned off.""" """All basic tests for atomic should also pass when autocommit is turned off."""
@ -480,7 +479,6 @@ class AtomicMiscTests(TransactionTestCase):
Reporter.objects.create() Reporter.objects.create()
@skipIfDBFeature('autocommits_when_autocommit_is_off')
class NonAutocommitTests(TransactionTestCase): class NonAutocommitTests(TransactionTestCase):
available_apps = [] available_apps = []

View File

@ -1,8 +1,5 @@
import unittest
from django.test import SimpleTestCase from django.test import SimpleTestCase
from django.utils.functional import cached_property, lazy from django.utils.functional import cached_property, lazy
from django.utils.version import PY36
class FunctionalTests(SimpleTestCase): class FunctionalTests(SimpleTestCase):
@ -104,7 +101,6 @@ class FunctionalTests(SimpleTestCase):
for attr in attrs: for attr in attrs:
self.assertCachedPropertyWorks(attr, Class) self.assertCachedPropertyWorks(attr, Class)
@unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
def test_cached_property_auto_name(self): def test_cached_property_auto_name(self):
""" """
cached_property caches its value and behaves like a property cached_property caches its value and behaves like a property
@ -132,7 +128,6 @@ class FunctionalTests(SimpleTestCase):
obj.other2 obj.other2
self.assertFalse(hasattr(obj, 'different_name')) self.assertFalse(hasattr(obj, 'different_name'))
@unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
def test_cached_property_reuse_different_names(self): def test_cached_property_reuse_different_names(self):
"""Disallow this case because the decorated function wouldn't be cached.""" """Disallow this case because the decorated function wouldn't be cached."""
with self.assertRaises(RuntimeError) as ctx: with self.assertRaises(RuntimeError) as ctx:
@ -151,7 +146,6 @@ class FunctionalTests(SimpleTestCase):
)) ))
) )
@unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
def test_cached_property_reuse_same_name(self): def test_cached_property_reuse_same_name(self):
""" """
Reusing a cached_property on different classes under the same name is Reusing a cached_property on different classes under the same name is
@ -177,7 +171,6 @@ class FunctionalTests(SimpleTestCase):
self.assertEqual(b.cp, 2) self.assertEqual(b.cp, 2)
self.assertEqual(a.cp, 1) self.assertEqual(a.cp, 1)
@unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
def test_cached_property_set_name_not_called(self): def test_cached_property_set_name_not_called(self):
cp = cached_property(lambda s: None) cp = cached_property(lambda s: None)
@ -189,29 +182,6 @@ class FunctionalTests(SimpleTestCase):
with self.assertRaisesMessage(TypeError, msg): with self.assertRaisesMessage(TypeError, msg):
Foo().cp Foo().cp
@unittest.skipIf(PY36, '__set_name__ is new in Python 3.6')
def test_cached_property_mangled_error(self):
msg = (
'cached_property does not work with mangled methods on '
'Python < 3.6 without the appropriate `name` argument.'
)
with self.assertRaisesMessage(ValueError, msg):
@cached_property
def __value(self):
pass
with self.assertRaisesMessage(ValueError, msg):
def func(self):
pass
cached_property(func, name='__value')
@unittest.skipIf(PY36, '__set_name__ is new in Python 3.6')
def test_cached_property_name_validation(self):
msg = "%s can't be used as the name of a cached_property."
with self.assertRaisesMessage(ValueError, msg % "'<lambda>'"):
cached_property(lambda x: None)
with self.assertRaisesMessage(ValueError, msg % 42):
cached_property(str, name=42)
def test_lazy_equality(self): def test_lazy_equality(self):
""" """
== and != work correctly for Promises. == and != work correctly for Promises.

View File

@ -17,7 +17,6 @@ from django.test.utils import LoggingCaptureMixin
from django.urls import path, reverse from django.urls import path, reverse
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.version import PY36
from django.views.debug import ( from django.views.debug import (
CLEANSED_SUBSTITUTE, CallableSettingWrapper, ExceptionReporter, CLEANSED_SUBSTITUTE, CallableSettingWrapper, ExceptionReporter,
cleanse_setting, technical_500_response, cleanse_setting, technical_500_response,
@ -515,7 +514,7 @@ class ExceptionReporterTests(SimpleTestCase):
exc_type, exc_value, tb = sys.exc_info() exc_type, exc_value, tb = sys.exc_info()
reporter = ExceptionReporter(request, exc_type, exc_value, tb) reporter = ExceptionReporter(request, exc_type, exc_value, tb)
html = reporter.get_traceback_html() html = reporter.get_traceback_html()
self.assertInHTML('<h1>%sError at /test_view/</h1>' % ('ModuleNotFound' if PY36 else 'Import'), html) self.assertInHTML('<h1>ModuleNotFoundError at /test_view/</h1>', html)
def test_ignore_traceback_evaluation_exceptions(self): def test_ignore_traceback_evaluation_exceptions(self):
""" """

View File

@ -21,7 +21,7 @@ passenv = DJANGO_SETTINGS_MODULE PYTHONPATH HOME DISPLAY
setenv = setenv =
PYTHONDONTWRITEBYTECODE=1 PYTHONDONTWRITEBYTECODE=1
deps = deps =
py{3,35,36,37}: -rtests/requirements/py3.txt py{3,36,37}: -rtests/requirements/py3.txt
postgres: -rtests/requirements/postgres.txt postgres: -rtests/requirements/postgres.txt
mysql: -rtests/requirements/mysql.txt mysql: -rtests/requirements/mysql.txt
oracle: -rtests/requirements/oracle.txt oracle: -rtests/requirements/oracle.txt