Fixed #32316 -- Deferred accessing __file__.
Deferred accessing the module-global variable __file__ because the Python import API does not guarantee it always exists—in particular, it does not exist in certain "frozen" environments. The following changes advanced this goal. Thanks to Carlton Gibson, Tom Forbes, Mariusz Felisiak, and Shreyas Ravi for review and feedback.
This commit is contained in:
parent
cfe47b7686
commit
9ee693bd6c
|
@ -8,7 +8,7 @@ from django.conf import settings
|
||||||
from django.core.exceptions import (
|
from django.core.exceptions import (
|
||||||
FieldDoesNotExist, ImproperlyConfigured, ValidationError,
|
FieldDoesNotExist, ImproperlyConfigured, ValidationError,
|
||||||
)
|
)
|
||||||
from django.utils.functional import lazy
|
from django.utils.functional import cached_property, lazy
|
||||||
from django.utils.html import format_html, format_html_join
|
from django.utils.html import format_html, format_html_join
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
from django.utils.translation import gettext as _, ngettext
|
from django.utils.translation import gettext as _, ngettext
|
||||||
|
@ -167,9 +167,14 @@ class CommonPasswordValidator:
|
||||||
https://gist.github.com/roycewilliams/281ce539915a947a23db17137d91aeb7
|
https://gist.github.com/roycewilliams/281ce539915a947a23db17137d91aeb7
|
||||||
The password list must be lowercased to match the comparison in validate().
|
The password list must be lowercased to match the comparison in validate().
|
||||||
"""
|
"""
|
||||||
DEFAULT_PASSWORD_LIST_PATH = Path(__file__).resolve().parent / 'common-passwords.txt.gz'
|
|
||||||
|
@cached_property
|
||||||
|
def DEFAULT_PASSWORD_LIST_PATH(self):
|
||||||
|
return Path(__file__).resolve().parent / 'common-passwords.txt.gz'
|
||||||
|
|
||||||
def __init__(self, password_list_path=DEFAULT_PASSWORD_LIST_PATH):
|
def __init__(self, password_list_path=DEFAULT_PASSWORD_LIST_PATH):
|
||||||
|
if password_list_path is CommonPasswordValidator.DEFAULT_PASSWORD_LIST_PATH:
|
||||||
|
password_list_path = self.DEFAULT_PASSWORD_LIST_PATH
|
||||||
try:
|
try:
|
||||||
with gzip.open(password_list_path, 'rt', encoding='utf-8') as f:
|
with gzip.open(password_list_path, 'rt', encoding='utf-8') as f:
|
||||||
self.passwords = {x.strip() for x in f}
|
self.passwords = {x.strip() for x in f}
|
||||||
|
|
|
@ -7,8 +7,6 @@ from django.template.loader import get_template
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
|
||||||
ROOT = Path(__file__).parent
|
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def get_default_renderer():
|
def get_default_renderer():
|
||||||
|
@ -33,7 +31,7 @@ class EngineMixin:
|
||||||
def engine(self):
|
def engine(self):
|
||||||
return self.backend({
|
return self.backend({
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'DIRS': [ROOT / self.backend.app_dirname],
|
'DIRS': [Path(__file__).parent / self.backend.app_dirname],
|
||||||
'NAME': 'djangoforms',
|
'NAME': 'djangoforms',
|
||||||
'OPTIONS': {},
|
'OPTIONS': {},
|
||||||
})
|
})
|
||||||
|
|
|
@ -77,6 +77,10 @@ def get_git_changeset():
|
||||||
This value isn't guaranteed to be unique, but collisions are very unlikely,
|
This value isn't guaranteed to be unique, but collisions are very unlikely,
|
||||||
so it's sufficient for generating the development version numbers.
|
so it's sufficient for generating the development version numbers.
|
||||||
"""
|
"""
|
||||||
|
# Repository may not be found if __file__ is undefined, e.g. in a frozen
|
||||||
|
# module.
|
||||||
|
if '__file__' not in globals():
|
||||||
|
return None
|
||||||
repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
repo_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
git_log = subprocess.run(
|
git_log = subprocess.run(
|
||||||
'git log --pretty=format:%ct --quiet -1 HEAD',
|
'git log --pretty=format:%ct --quiet -1 HEAD',
|
||||||
|
|
|
@ -26,7 +26,15 @@ DEBUG_ENGINE = Engine(
|
||||||
libraries={'i18n': 'django.templatetags.i18n'},
|
libraries={'i18n': 'django.templatetags.i18n'},
|
||||||
)
|
)
|
||||||
|
|
||||||
CURRENT_DIR = Path(__file__).parent
|
|
||||||
|
def builtin_template_path(name):
|
||||||
|
"""
|
||||||
|
Return a path to a builtin template.
|
||||||
|
|
||||||
|
Avoid calling this function at the module level or in a class-definition
|
||||||
|
because __file__ may not exist, e.g. in frozen environments.
|
||||||
|
"""
|
||||||
|
return Path(__file__).parent / 'templates' / name
|
||||||
|
|
||||||
|
|
||||||
class ExceptionCycleWarning(UserWarning):
|
class ExceptionCycleWarning(UserWarning):
|
||||||
|
@ -248,11 +256,11 @@ class ExceptionReporter:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def html_template_path(self):
|
def html_template_path(self):
|
||||||
return CURRENT_DIR / 'templates' / 'technical_500.html'
|
return builtin_template_path('technical_500.html')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text_template_path(self):
|
def text_template_path(self):
|
||||||
return CURRENT_DIR / 'templates' / 'technical_500.txt'
|
return builtin_template_path('technical_500.txt')
|
||||||
|
|
||||||
def __init__(self, request, exc_type, exc_value, tb, is_email=False):
|
def __init__(self, request, exc_type, exc_value, tb, is_email=False):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
@ -534,7 +542,7 @@ def technical_404_response(request, exception):
|
||||||
module = obj.__module__
|
module = obj.__module__
|
||||||
caller = '%s.%s' % (module, caller)
|
caller = '%s.%s' % (module, caller)
|
||||||
|
|
||||||
with Path(CURRENT_DIR, 'templates', 'technical_404.html').open(encoding='utf-8') as fh:
|
with builtin_template_path('technical_404.html').open(encoding='utf-8') as fh:
|
||||||
t = DEBUG_ENGINE.from_string(fh.read())
|
t = DEBUG_ENGINE.from_string(fh.read())
|
||||||
reporter_filter = get_default_exception_reporter_filter()
|
reporter_filter = get_default_exception_reporter_filter()
|
||||||
c = Context({
|
c = Context({
|
||||||
|
@ -553,7 +561,7 @@ def technical_404_response(request, exception):
|
||||||
|
|
||||||
def default_urlconf(request):
|
def default_urlconf(request):
|
||||||
"""Create an empty URLconf 404 error response."""
|
"""Create an empty URLconf 404 error response."""
|
||||||
with Path(CURRENT_DIR, 'templates', 'default_urlconf.html').open(encoding='utf-8') as fh:
|
with builtin_template_path('default_urlconf.html').open(encoding='utf-8') as fh:
|
||||||
t = DEBUG_ENGINE.from_string(fh.read())
|
t = DEBUG_ENGINE.from_string(fh.read())
|
||||||
c = Context({
|
c = Context({
|
||||||
'version': get_docs_version(),
|
'version': get_docs_version(),
|
||||||
|
|
|
@ -1,17 +1,37 @@
|
||||||
|
from unittest import skipUnless
|
||||||
|
|
||||||
|
import django.utils.version
|
||||||
from django import get_version
|
from django import get_version
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
from django.utils.version import get_complete_version, get_version_tuple
|
from django.utils.version import (
|
||||||
|
get_complete_version, get_git_changeset, get_version_tuple,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VersionTests(SimpleTestCase):
|
class VersionTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_development(self):
|
def test_development(self):
|
||||||
|
get_git_changeset.cache_clear()
|
||||||
ver_tuple = (1, 4, 0, 'alpha', 0)
|
ver_tuple = (1, 4, 0, 'alpha', 0)
|
||||||
# This will return a different result when it's run within or outside
|
# This will return a different result when it's run within or outside
|
||||||
# of a git clone: 1.4.devYYYYMMDDHHMMSS or 1.4.
|
# of a git clone: 1.4.devYYYYMMDDHHMMSS or 1.4.
|
||||||
ver_string = get_version(ver_tuple)
|
ver_string = get_version(ver_tuple)
|
||||||
self.assertRegex(ver_string, r'1\.4(\.dev[0-9]+)?')
|
self.assertRegex(ver_string, r'1\.4(\.dev[0-9]+)?')
|
||||||
|
|
||||||
|
@skipUnless(
|
||||||
|
hasattr(django.utils.version, '__file__'),
|
||||||
|
'test_development() checks the same when __file__ is already missing, '
|
||||||
|
'e.g. in a frozen environments'
|
||||||
|
)
|
||||||
|
def test_development_no_file(self):
|
||||||
|
get_git_changeset.cache_clear()
|
||||||
|
version_file = django.utils.version.__file__
|
||||||
|
try:
|
||||||
|
del django.utils.version.__file__
|
||||||
|
self.test_development()
|
||||||
|
finally:
|
||||||
|
django.utils.version.__file__ = version_file
|
||||||
|
|
||||||
def test_releases(self):
|
def test_releases(self):
|
||||||
tuples_to_strings = (
|
tuples_to_strings = (
|
||||||
((1, 4, 0, 'alpha', 1), '1.4a1'),
|
((1, 4, 0, 'alpha', 1), '1.4a1'),
|
||||||
|
|
Loading…
Reference in New Issue