plain 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain/assets/README.md +18 -37
- plain/assets/__init__.py +0 -6
- plain/assets/compile.py +111 -0
- plain/assets/finders.py +26 -218
- plain/assets/fingerprints.py +38 -0
- plain/assets/urls.py +31 -0
- plain/assets/views.py +263 -0
- plain/cli/cli.py +68 -5
- plain/packages/config.py +5 -5
- plain/packages/registry.py +1 -7
- plain/preflight/urls.py +0 -10
- plain/runtime/README.md +0 -1
- plain/runtime/global_settings.py +7 -14
- plain/runtime/user_settings.py +0 -49
- plain/templates/jinja/globals.py +1 -1
- plain/test/__init__.py +0 -8
- plain/test/client.py +36 -16
- plain/views/base.py +5 -3
- plain/views/errors.py +7 -0
- {plain-0.1.2.dist-info → plain-0.2.0.dist-info}/LICENSE +0 -24
- {plain-0.1.2.dist-info → plain-0.2.0.dist-info}/METADATA +1 -1
- {plain-0.1.2.dist-info → plain-0.2.0.dist-info}/RECORD +24 -34
- plain/assets/preflight.py +0 -14
- plain/assets/storage.py +0 -916
- plain/assets/utils.py +0 -52
- plain/assets/whitenoise/__init__.py +0 -5
- plain/assets/whitenoise/base.py +0 -259
- plain/assets/whitenoise/compress.py +0 -189
- plain/assets/whitenoise/media_types.py +0 -137
- plain/assets/whitenoise/middleware.py +0 -197
- plain/assets/whitenoise/responders.py +0 -286
- plain/assets/whitenoise/storage.py +0 -178
- plain/assets/whitenoise/string_utils.py +0 -13
- plain/internal/legacy/management/commands/__init__.py +0 -0
- plain/internal/legacy/management/commands/collectstatic.py +0 -297
- plain/test/utils.py +0 -255
- {plain-0.1.2.dist-info → plain-0.2.0.dist-info}/WHEEL +0 -0
- {plain-0.1.2.dist-info → plain-0.2.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
from plain.assets.finders import get_finders
|
|
4
|
-
from plain.assets.storage import FileSystemStorage, assets_storage
|
|
5
|
-
from plain.internal.legacy.management.base import BaseCommand, CommandError
|
|
6
|
-
from plain.internal.legacy.management.color import no_style
|
|
7
|
-
from plain.utils.functional import cached_property
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Command(BaseCommand):
|
|
11
|
-
"""
|
|
12
|
-
Copies static files from different locations to the
|
|
13
|
-
settings.ASSETS_ROOT.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
help = "Collect static files in a single location."
|
|
17
|
-
# requires_system_checks = [Tags.assets]
|
|
18
|
-
|
|
19
|
-
def __init__(self, *args, **kwargs):
|
|
20
|
-
super().__init__(*args, **kwargs)
|
|
21
|
-
self.copied_files = []
|
|
22
|
-
self.unmodified_files = []
|
|
23
|
-
self.post_processed_files = []
|
|
24
|
-
self.storage = assets_storage
|
|
25
|
-
self.style = no_style()
|
|
26
|
-
|
|
27
|
-
@cached_property
|
|
28
|
-
def local(self):
|
|
29
|
-
try:
|
|
30
|
-
self.storage.path("")
|
|
31
|
-
except NotImplementedError:
|
|
32
|
-
return False
|
|
33
|
-
return True
|
|
34
|
-
|
|
35
|
-
def add_arguments(self, parser):
|
|
36
|
-
parser.add_argument(
|
|
37
|
-
"--noinput",
|
|
38
|
-
"--no-input",
|
|
39
|
-
action="store_false",
|
|
40
|
-
dest="interactive",
|
|
41
|
-
help="Do NOT prompt the user for input of any kind.",
|
|
42
|
-
)
|
|
43
|
-
parser.add_argument(
|
|
44
|
-
"--no-post-process",
|
|
45
|
-
action="store_false",
|
|
46
|
-
dest="post_process",
|
|
47
|
-
help="Do NOT post process collected files.",
|
|
48
|
-
)
|
|
49
|
-
parser.add_argument(
|
|
50
|
-
"-i",
|
|
51
|
-
"--ignore",
|
|
52
|
-
action="append",
|
|
53
|
-
default=[],
|
|
54
|
-
dest="ignore_patterns",
|
|
55
|
-
metavar="PATTERN",
|
|
56
|
-
help="Ignore files or directories matching this glob-style "
|
|
57
|
-
"pattern. Use multiple times to ignore more.",
|
|
58
|
-
)
|
|
59
|
-
parser.add_argument(
|
|
60
|
-
"-n",
|
|
61
|
-
"--dry-run",
|
|
62
|
-
action="store_true",
|
|
63
|
-
help="Do everything except modify the filesystem.",
|
|
64
|
-
)
|
|
65
|
-
parser.add_argument(
|
|
66
|
-
"-c",
|
|
67
|
-
"--clear",
|
|
68
|
-
action="store_true",
|
|
69
|
-
help="Clear the existing files using the storage "
|
|
70
|
-
"before trying to copy or link the original file.",
|
|
71
|
-
)
|
|
72
|
-
parser.add_argument(
|
|
73
|
-
"--no-default-ignore",
|
|
74
|
-
action="store_false",
|
|
75
|
-
dest="use_default_ignore_patterns",
|
|
76
|
-
help=(
|
|
77
|
-
"Don't ignore the common private glob-style patterns (defaults to "
|
|
78
|
-
"'CVS', '.*' and '*~')."
|
|
79
|
-
),
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
def set_options(self, **options):
|
|
83
|
-
"""
|
|
84
|
-
Set instance variables based on an options dict
|
|
85
|
-
"""
|
|
86
|
-
self.interactive = options["interactive"]
|
|
87
|
-
self.verbosity = options["verbosity"]
|
|
88
|
-
self.clear = options["clear"]
|
|
89
|
-
self.dry_run = options["dry_run"]
|
|
90
|
-
ignore_patterns = options["ignore_patterns"]
|
|
91
|
-
if options["use_default_ignore_patterns"]:
|
|
92
|
-
ignore_patterns += ["CVS", ".*", "*~"]
|
|
93
|
-
self.ignore_patterns = list({os.path.normpath(p) for p in ignore_patterns})
|
|
94
|
-
self.post_process = options["post_process"]
|
|
95
|
-
|
|
96
|
-
def collect(self):
|
|
97
|
-
"""
|
|
98
|
-
Perform the bulk of the work of collectstatic.
|
|
99
|
-
|
|
100
|
-
Split off from handle() to facilitate testing.
|
|
101
|
-
"""
|
|
102
|
-
if self.clear:
|
|
103
|
-
self.clear_dir("")
|
|
104
|
-
|
|
105
|
-
found_files = {}
|
|
106
|
-
for finder in get_finders():
|
|
107
|
-
for path, storage in finder.list(self.ignore_patterns):
|
|
108
|
-
# Prefix the relative path if the source storage contains it
|
|
109
|
-
if getattr(storage, "prefix", None):
|
|
110
|
-
prefixed_path = os.path.join(storage.prefix, path)
|
|
111
|
-
else:
|
|
112
|
-
prefixed_path = path
|
|
113
|
-
|
|
114
|
-
if prefixed_path not in found_files:
|
|
115
|
-
found_files[prefixed_path] = (storage, path)
|
|
116
|
-
self.copy_file(path, prefixed_path, storage)
|
|
117
|
-
else:
|
|
118
|
-
self.log(
|
|
119
|
-
"Found another file with the destination path '%s'. It "
|
|
120
|
-
"will be ignored since only the first encountered file "
|
|
121
|
-
"is collected. If this is not what you want, make sure "
|
|
122
|
-
"every static file has a unique path." % prefixed_path,
|
|
123
|
-
level=1,
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
# Storage backends may define a post_process() method.
|
|
127
|
-
if self.post_process and hasattr(self.storage, "post_process"):
|
|
128
|
-
processor = self.storage.post_process(found_files, dry_run=self.dry_run)
|
|
129
|
-
for original_path, processed_path, processed in processor:
|
|
130
|
-
if isinstance(processed, Exception):
|
|
131
|
-
self.stderr.write("Post-processing '%s' failed!" % original_path)
|
|
132
|
-
# Add a blank line before the traceback, otherwise it's
|
|
133
|
-
# too easy to miss the relevant part of the error message.
|
|
134
|
-
self.stderr.write()
|
|
135
|
-
raise processed
|
|
136
|
-
if processed:
|
|
137
|
-
self.log(
|
|
138
|
-
f"Post-processed '{original_path}' as '{processed_path}'",
|
|
139
|
-
level=2,
|
|
140
|
-
)
|
|
141
|
-
self.post_processed_files.append(original_path)
|
|
142
|
-
else:
|
|
143
|
-
self.log("Skipped post-processing '%s'" % original_path)
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
"modified": self.copied_files,
|
|
147
|
-
"unmodified": self.unmodified_files,
|
|
148
|
-
"post_processed": self.post_processed_files,
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
def handle(self, **options):
|
|
152
|
-
self.set_options(**options)
|
|
153
|
-
message = ["\n"]
|
|
154
|
-
if self.dry_run:
|
|
155
|
-
message.append(
|
|
156
|
-
"You have activated the --dry-run option so no files will be "
|
|
157
|
-
"modified.\n\n"
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
message.append(
|
|
161
|
-
"You have requested to collect static files at the destination\n"
|
|
162
|
-
"location as specified in your settings"
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
if self.is_local_storage() and self.storage.location:
|
|
166
|
-
destination_path = self.storage.location
|
|
167
|
-
message.append(":\n\n %s\n\n" % destination_path)
|
|
168
|
-
should_warn_user = self.storage.exists(destination_path) and any(
|
|
169
|
-
self.storage.listdir(destination_path)
|
|
170
|
-
)
|
|
171
|
-
else:
|
|
172
|
-
destination_path = None
|
|
173
|
-
message.append(".\n\n")
|
|
174
|
-
# Destination files existence not checked; play it safe and warn.
|
|
175
|
-
should_warn_user = True
|
|
176
|
-
|
|
177
|
-
if self.interactive and should_warn_user:
|
|
178
|
-
if self.clear:
|
|
179
|
-
message.append("This will DELETE ALL FILES in this location!\n")
|
|
180
|
-
else:
|
|
181
|
-
message.append("This will overwrite existing files!\n")
|
|
182
|
-
|
|
183
|
-
message.append(
|
|
184
|
-
"Are you sure you want to do this?\n\n"
|
|
185
|
-
"Type 'yes' to continue, or 'no' to cancel: "
|
|
186
|
-
)
|
|
187
|
-
if input("".join(message)) != "yes":
|
|
188
|
-
raise CommandError("Collecting static files cancelled.")
|
|
189
|
-
|
|
190
|
-
collected = self.collect()
|
|
191
|
-
|
|
192
|
-
if self.verbosity >= 1:
|
|
193
|
-
modified_count = len(collected["modified"])
|
|
194
|
-
unmodified_count = len(collected["unmodified"])
|
|
195
|
-
post_processed_count = len(collected["post_processed"])
|
|
196
|
-
return (
|
|
197
|
-
"\n{modified_count} {identifier} {action}"
|
|
198
|
-
"{destination}{unmodified}{post_processed}."
|
|
199
|
-
).format(
|
|
200
|
-
modified_count=modified_count,
|
|
201
|
-
identifier="static file" + ("" if modified_count == 1 else "s"),
|
|
202
|
-
action="copied",
|
|
203
|
-
destination=" to '%s'" % destination_path if destination_path else "",
|
|
204
|
-
unmodified=", %s unmodified" % unmodified_count
|
|
205
|
-
if collected["unmodified"]
|
|
206
|
-
else "",
|
|
207
|
-
post_processed=collected["post_processed"]
|
|
208
|
-
and ", %s post-processed" % post_processed_count
|
|
209
|
-
or "",
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
def log(self, msg, level=2):
|
|
213
|
-
"""
|
|
214
|
-
Small log helper
|
|
215
|
-
"""
|
|
216
|
-
if self.verbosity >= level:
|
|
217
|
-
self.stdout.write(msg)
|
|
218
|
-
|
|
219
|
-
def is_local_storage(self):
|
|
220
|
-
return isinstance(self.storage, FileSystemStorage)
|
|
221
|
-
|
|
222
|
-
def clear_dir(self, path):
|
|
223
|
-
"""
|
|
224
|
-
Delete the given relative path using the destination storage backend.
|
|
225
|
-
"""
|
|
226
|
-
if not self.storage.exists(path):
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
dirs, files = self.storage.listdir(path)
|
|
230
|
-
for f in files:
|
|
231
|
-
fpath = os.path.join(path, f)
|
|
232
|
-
if self.dry_run:
|
|
233
|
-
self.log("Pretending to delete '%s'" % fpath, level=1)
|
|
234
|
-
else:
|
|
235
|
-
self.log("Deleting '%s'" % fpath, level=1)
|
|
236
|
-
self.storage.delete(fpath)
|
|
237
|
-
for d in dirs:
|
|
238
|
-
self.clear_dir(os.path.join(path, d))
|
|
239
|
-
|
|
240
|
-
def delete_file(self, path, prefixed_path, source_storage):
|
|
241
|
-
"""
|
|
242
|
-
Check if the target file should be deleted if it already exists.
|
|
243
|
-
"""
|
|
244
|
-
if self.storage.exists(prefixed_path):
|
|
245
|
-
try:
|
|
246
|
-
# When was the target file modified last time?
|
|
247
|
-
target_last_modified = self.storage.get_modified_time(prefixed_path)
|
|
248
|
-
except (OSError, NotImplementedError, AttributeError):
|
|
249
|
-
# The storage doesn't support get_modified_time() or failed
|
|
250
|
-
pass
|
|
251
|
-
else:
|
|
252
|
-
try:
|
|
253
|
-
# When was the source file modified last time?
|
|
254
|
-
source_last_modified = source_storage.get_modified_time(path)
|
|
255
|
-
except (OSError, NotImplementedError, AttributeError):
|
|
256
|
-
pass
|
|
257
|
-
else:
|
|
258
|
-
# In remote storages, skipping is only based on the
|
|
259
|
-
# modified times since symlinks aren't relevant.
|
|
260
|
-
can_skip_unmodified_files = not self.local
|
|
261
|
-
# Avoid sub-second precision (see #14665, #19540)
|
|
262
|
-
file_is_unmodified = target_last_modified.replace(
|
|
263
|
-
microsecond=0
|
|
264
|
-
) >= source_last_modified.replace(microsecond=0)
|
|
265
|
-
if file_is_unmodified and can_skip_unmodified_files:
|
|
266
|
-
if prefixed_path not in self.unmodified_files:
|
|
267
|
-
self.unmodified_files.append(prefixed_path)
|
|
268
|
-
self.log("Skipping '%s' (not modified)" % path)
|
|
269
|
-
return False
|
|
270
|
-
# Then delete the existing file if really needed
|
|
271
|
-
if self.dry_run:
|
|
272
|
-
self.log("Pretending to delete '%s'" % path)
|
|
273
|
-
else:
|
|
274
|
-
self.log("Deleting '%s'" % path)
|
|
275
|
-
self.storage.delete(prefixed_path)
|
|
276
|
-
return True
|
|
277
|
-
|
|
278
|
-
def copy_file(self, path, prefixed_path, source_storage):
|
|
279
|
-
"""
|
|
280
|
-
Attempt to copy ``path`` with storage
|
|
281
|
-
"""
|
|
282
|
-
# Skip this file if it was already copied earlier
|
|
283
|
-
if prefixed_path in self.copied_files:
|
|
284
|
-
return self.log("Skipping '%s' (already copied earlier)" % path)
|
|
285
|
-
# Delete the target file if needed or break
|
|
286
|
-
if not self.delete_file(path, prefixed_path, source_storage):
|
|
287
|
-
return
|
|
288
|
-
# The full path of the source file
|
|
289
|
-
source_path = source_storage.path(path)
|
|
290
|
-
# Finally start copying
|
|
291
|
-
if self.dry_run:
|
|
292
|
-
self.log("Pretending to copy '%s'" % source_path, level=1)
|
|
293
|
-
else:
|
|
294
|
-
self.log("Copying '%s'" % source_path, level=2)
|
|
295
|
-
with source_storage.open(path) as source_file:
|
|
296
|
-
self.storage.save(prefixed_path, source_file)
|
|
297
|
-
self.copied_files.append(prefixed_path)
|
plain/test/utils.py
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import warnings
|
|
3
|
-
from functools import wraps
|
|
4
|
-
from itertools import chain
|
|
5
|
-
from unittest import TestCase
|
|
6
|
-
|
|
7
|
-
from plain.packages import packages
|
|
8
|
-
from plain.runtime import settings
|
|
9
|
-
from plain.runtime.user_settings import UserSettingsHolder
|
|
10
|
-
|
|
11
|
-
__all__ = (
|
|
12
|
-
"ContextList",
|
|
13
|
-
"ignore_warnings",
|
|
14
|
-
"modify_settings",
|
|
15
|
-
"override_settings",
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ContextList(list):
|
|
20
|
-
"""
|
|
21
|
-
A wrapper that provides direct key access to context items contained
|
|
22
|
-
in a list of context objects.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def __getitem__(self, key):
|
|
26
|
-
if isinstance(key, str):
|
|
27
|
-
for subcontext in self:
|
|
28
|
-
if key in subcontext:
|
|
29
|
-
return subcontext[key]
|
|
30
|
-
raise KeyError(key)
|
|
31
|
-
else:
|
|
32
|
-
return super().__getitem__(key)
|
|
33
|
-
|
|
34
|
-
def get(self, key, default=None):
|
|
35
|
-
try:
|
|
36
|
-
return self.__getitem__(key)
|
|
37
|
-
except KeyError:
|
|
38
|
-
return default
|
|
39
|
-
|
|
40
|
-
def __contains__(self, key):
|
|
41
|
-
try:
|
|
42
|
-
self[key]
|
|
43
|
-
except KeyError:
|
|
44
|
-
return False
|
|
45
|
-
return True
|
|
46
|
-
|
|
47
|
-
def keys(self):
|
|
48
|
-
"""
|
|
49
|
-
Flattened keys of subcontexts.
|
|
50
|
-
"""
|
|
51
|
-
return set(chain.from_iterable(d for subcontext in self for d in subcontext))
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class TestContextDecorator:
|
|
55
|
-
"""
|
|
56
|
-
A base class that can either be used as a context manager during tests
|
|
57
|
-
or as a test function or unittest.TestCase subclass decorator to perform
|
|
58
|
-
temporary alterations.
|
|
59
|
-
|
|
60
|
-
`attr_name`: attribute assigned the return value of enable() if used as
|
|
61
|
-
a class decorator.
|
|
62
|
-
|
|
63
|
-
`kwarg_name`: keyword argument passing the return value of enable() if
|
|
64
|
-
used as a function decorator.
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
def __init__(self, attr_name=None, kwarg_name=None):
|
|
68
|
-
self.attr_name = attr_name
|
|
69
|
-
self.kwarg_name = kwarg_name
|
|
70
|
-
|
|
71
|
-
def enable(self):
|
|
72
|
-
raise NotImplementedError
|
|
73
|
-
|
|
74
|
-
def disable(self):
|
|
75
|
-
raise NotImplementedError
|
|
76
|
-
|
|
77
|
-
def __enter__(self):
|
|
78
|
-
return self.enable()
|
|
79
|
-
|
|
80
|
-
def __exit__(self, exc_type, exc_value, traceback):
|
|
81
|
-
self.disable()
|
|
82
|
-
|
|
83
|
-
def decorate_class(self, cls):
|
|
84
|
-
if issubclass(cls, TestCase):
|
|
85
|
-
decorated_setUp = cls.setUp
|
|
86
|
-
|
|
87
|
-
def setUp(inner_self):
|
|
88
|
-
context = self.enable()
|
|
89
|
-
inner_self.addCleanup(self.disable)
|
|
90
|
-
if self.attr_name:
|
|
91
|
-
setattr(inner_self, self.attr_name, context)
|
|
92
|
-
decorated_setUp(inner_self)
|
|
93
|
-
|
|
94
|
-
cls.setUp = setUp
|
|
95
|
-
return cls
|
|
96
|
-
raise TypeError("Can only decorate subclasses of unittest.TestCase")
|
|
97
|
-
|
|
98
|
-
def decorate_callable(self, func):
|
|
99
|
-
@wraps(func)
|
|
100
|
-
def inner(*args, **kwargs):
|
|
101
|
-
with self as context:
|
|
102
|
-
if self.kwarg_name:
|
|
103
|
-
kwargs[self.kwarg_name] = context
|
|
104
|
-
return func(*args, **kwargs)
|
|
105
|
-
|
|
106
|
-
return inner
|
|
107
|
-
|
|
108
|
-
def __call__(self, decorated):
|
|
109
|
-
if isinstance(decorated, type):
|
|
110
|
-
return self.decorate_class(decorated)
|
|
111
|
-
elif callable(decorated):
|
|
112
|
-
return self.decorate_callable(decorated)
|
|
113
|
-
raise TypeError("Cannot decorate object of type %s" % type(decorated))
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
class override_settings(TestContextDecorator):
|
|
117
|
-
"""
|
|
118
|
-
Act as either a decorator or a context manager. If it's a decorator, take a
|
|
119
|
-
function and return a wrapped function. If it's a contextmanager, use it
|
|
120
|
-
with the ``with`` statement. In either event, entering/exiting are called
|
|
121
|
-
before and after, respectively, the function/block is executed.
|
|
122
|
-
"""
|
|
123
|
-
|
|
124
|
-
enable_exception = None
|
|
125
|
-
|
|
126
|
-
def __init__(self, **kwargs):
|
|
127
|
-
self.options = kwargs
|
|
128
|
-
super().__init__()
|
|
129
|
-
|
|
130
|
-
def enable(self):
|
|
131
|
-
# Keep this code at the beginning to leave the settings unchanged
|
|
132
|
-
# in case it raises an exception because INSTALLED_PACKAGES is invalid.
|
|
133
|
-
if "INSTALLED_PACKAGES" in self.options:
|
|
134
|
-
try:
|
|
135
|
-
packages.set_installed_packages(self.options["INSTALLED_PACKAGES"])
|
|
136
|
-
except Exception:
|
|
137
|
-
packages.unset_installed_packages()
|
|
138
|
-
raise
|
|
139
|
-
override = UserSettingsHolder(settings._wrapped)
|
|
140
|
-
for key, new_value in self.options.items():
|
|
141
|
-
setattr(override, key, new_value)
|
|
142
|
-
self.wrapped = settings._wrapped
|
|
143
|
-
settings._wrapped = override
|
|
144
|
-
# for key, new_value in self.options.items():
|
|
145
|
-
# try:
|
|
146
|
-
# setting_changed.send(
|
|
147
|
-
# sender=settings._wrapped.__class__,
|
|
148
|
-
# setting=key,
|
|
149
|
-
# value=new_value,
|
|
150
|
-
# enter=True,
|
|
151
|
-
# )
|
|
152
|
-
# except Exception as exc:
|
|
153
|
-
# self.enable_exception = exc
|
|
154
|
-
# self.disable()
|
|
155
|
-
|
|
156
|
-
def disable(self):
|
|
157
|
-
if "INSTALLED_PACKAGES" in self.options:
|
|
158
|
-
packages.unset_installed_packages()
|
|
159
|
-
settings._wrapped = self.wrapped
|
|
160
|
-
del self.wrapped
|
|
161
|
-
responses = []
|
|
162
|
-
for key in self.options:
|
|
163
|
-
getattr(settings, key, None)
|
|
164
|
-
# responses_for_setting = setting_changed.send_robust(
|
|
165
|
-
# sender=settings._wrapped.__class__,
|
|
166
|
-
# setting=key,
|
|
167
|
-
# value=new_value,
|
|
168
|
-
# enter=False,
|
|
169
|
-
# )
|
|
170
|
-
# responses.extend(responses_for_setting)
|
|
171
|
-
if self.enable_exception is not None:
|
|
172
|
-
exc = self.enable_exception
|
|
173
|
-
self.enable_exception = None
|
|
174
|
-
raise exc
|
|
175
|
-
for _, response in responses:
|
|
176
|
-
if isinstance(response, Exception):
|
|
177
|
-
raise response
|
|
178
|
-
|
|
179
|
-
def save_options(self, test_func):
|
|
180
|
-
if test_func._overridden_settings is None:
|
|
181
|
-
test_func._overridden_settings = self.options
|
|
182
|
-
else:
|
|
183
|
-
# Duplicate dict to prevent subclasses from altering their parent.
|
|
184
|
-
test_func._overridden_settings = {
|
|
185
|
-
**test_func._overridden_settings,
|
|
186
|
-
**self.options,
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
class modify_settings(override_settings):
|
|
191
|
-
"""
|
|
192
|
-
Like override_settings, but makes it possible to append, prepend, or remove
|
|
193
|
-
items instead of redefining the entire list.
|
|
194
|
-
"""
|
|
195
|
-
|
|
196
|
-
def __init__(self, *args, **kwargs):
|
|
197
|
-
if args:
|
|
198
|
-
# Hack used when instantiating from SimpleTestCase.setUpClass.
|
|
199
|
-
assert not kwargs
|
|
200
|
-
self.operations = args[0]
|
|
201
|
-
else:
|
|
202
|
-
assert not args
|
|
203
|
-
self.operations = list(kwargs.items())
|
|
204
|
-
super(override_settings, self).__init__()
|
|
205
|
-
|
|
206
|
-
def save_options(self, test_func):
|
|
207
|
-
if test_func._modified_settings is None:
|
|
208
|
-
test_func._modified_settings = self.operations
|
|
209
|
-
else:
|
|
210
|
-
# Duplicate list to prevent subclasses from altering their parent.
|
|
211
|
-
test_func._modified_settings = (
|
|
212
|
-
list(test_func._modified_settings) + self.operations
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
def enable(self):
|
|
216
|
-
self.options = {}
|
|
217
|
-
for name, operations in self.operations:
|
|
218
|
-
try:
|
|
219
|
-
# When called from SimpleTestCase.setUpClass, values may be
|
|
220
|
-
# overridden several times; cumulate changes.
|
|
221
|
-
value = self.options[name]
|
|
222
|
-
except KeyError:
|
|
223
|
-
value = list(getattr(settings, name, []))
|
|
224
|
-
for action, items in operations.items():
|
|
225
|
-
# items my be a single value or an iterable.
|
|
226
|
-
if isinstance(items, str):
|
|
227
|
-
items = [items]
|
|
228
|
-
if action == "append":
|
|
229
|
-
value += [item for item in items if item not in value]
|
|
230
|
-
elif action == "prepend":
|
|
231
|
-
value = [item for item in items if item not in value] + value
|
|
232
|
-
elif action == "remove":
|
|
233
|
-
value = [item for item in value if item not in items]
|
|
234
|
-
else:
|
|
235
|
-
raise ValueError("Unsupported action: %s" % action)
|
|
236
|
-
self.options[name] = value
|
|
237
|
-
super().enable()
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
class ignore_warnings(TestContextDecorator):
|
|
241
|
-
def __init__(self, **kwargs):
|
|
242
|
-
self.ignore_kwargs = kwargs
|
|
243
|
-
if "message" in self.ignore_kwargs or "module" in self.ignore_kwargs:
|
|
244
|
-
self.filter_func = warnings.filterwarnings
|
|
245
|
-
else:
|
|
246
|
-
self.filter_func = warnings.simplefilter
|
|
247
|
-
super().__init__()
|
|
248
|
-
|
|
249
|
-
def enable(self):
|
|
250
|
-
self.catch_warnings = warnings.catch_warnings()
|
|
251
|
-
self.catch_warnings.__enter__()
|
|
252
|
-
self.filter_func("ignore", **self.ignore_kwargs)
|
|
253
|
-
|
|
254
|
-
def disable(self):
|
|
255
|
-
self.catch_warnings.__exit__(*sys.exc_info())
|
|
File without changes
|
|
File without changes
|