plain 0.68.1__py3-none-any.whl → 0.70.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/AGENTS.md +1 -1
- plain/CHANGELOG.md +23 -0
- plain/assets/compile.py +20 -7
- plain/assets/finders.py +15 -11
- plain/assets/fingerprints.py +6 -5
- plain/assets/urls.py +1 -1
- plain/assets/views.py +23 -17
- plain/chores/registry.py +14 -9
- plain/cli/agent/__init__.py +1 -1
- plain/cli/agent/docs.py +7 -6
- plain/cli/agent/llmdocs.py +18 -8
- plain/cli/agent/md.py +19 -14
- plain/cli/agent/prompt.py +1 -1
- plain/cli/agent/request.py +37 -17
- plain/cli/build.py +2 -2
- plain/cli/changelog.py +8 -4
- plain/cli/chores.py +4 -4
- plain/cli/core.py +8 -5
- plain/cli/docs.py +2 -2
- plain/cli/formatting.py +10 -7
- plain/cli/output.py +6 -2
- plain/cli/preflight.py +3 -3
- plain/cli/print.py +1 -1
- plain/cli/registry.py +10 -6
- plain/cli/scaffold.py +1 -1
- plain/cli/settings.py +1 -1
- plain/cli/shell.py +10 -7
- plain/cli/startup.py +3 -3
- plain/cli/urls.py +10 -4
- plain/cli/utils.py +2 -2
- plain/csrf/middleware.py +15 -5
- plain/csrf/views.py +11 -8
- plain/debug.py +5 -2
- plain/exceptions.py +20 -51
- plain/forms/__init__.py +1 -1
- plain/forms/boundfield.py +14 -7
- plain/forms/exceptions.py +1 -1
- plain/forms/fields.py +139 -97
- plain/forms/forms.py +55 -39
- plain/http/cookie.py +15 -7
- plain/http/multipartparser.py +50 -30
- plain/http/request.py +97 -73
- plain/http/response.py +99 -80
- plain/internal/__init__.py +8 -1
- plain/internal/files/base.py +34 -18
- plain/internal/files/locks.py +19 -11
- plain/internal/files/move.py +8 -3
- plain/internal/files/temp.py +23 -5
- plain/internal/files/uploadedfile.py +42 -26
- plain/internal/files/uploadhandler.py +48 -27
- plain/internal/files/utils.py +13 -6
- plain/internal/handlers/base.py +20 -6
- plain/internal/handlers/exception.py +19 -5
- plain/internal/handlers/wsgi.py +30 -18
- plain/internal/middleware/headers.py +11 -2
- plain/internal/middleware/hosts.py +10 -2
- plain/internal/middleware/https.py +13 -3
- plain/internal/middleware/slash.py +15 -5
- plain/json.py +2 -1
- plain/logs/configure.py +3 -1
- plain/logs/debug.py +16 -5
- plain/logs/formatters.py +6 -3
- plain/logs/loggers.py +56 -52
- plain/logs/utils.py +19 -9
- plain/packages/config.py +14 -6
- plain/packages/registry.py +27 -12
- plain/paginator.py +31 -21
- plain/preflight/checks.py +3 -1
- plain/preflight/files.py +3 -1
- plain/preflight/registry.py +25 -10
- plain/preflight/results.py +10 -4
- plain/preflight/security.py +7 -5
- plain/preflight/urls.py +4 -1
- plain/runtime/__init__.py +4 -3
- plain/runtime/global_settings.py +1 -1
- plain/runtime/user_settings.py +26 -17
- plain/runtime/utils.py +1 -1
- plain/signals/dispatch/dispatcher.py +39 -17
- plain/signing.py +49 -30
- plain/templates/jinja/__init__.py +13 -5
- plain/templates/jinja/environments.py +4 -3
- plain/templates/jinja/extensions.py +9 -3
- plain/templates/jinja/filters.py +7 -2
- plain/templates/jinja/globals.py +1 -1
- plain/test/client.py +246 -174
- plain/test/encoding.py +9 -6
- plain/test/exceptions.py +10 -2
- plain/urls/converters.py +13 -10
- plain/urls/patterns.py +32 -20
- plain/urls/resolvers.py +32 -22
- plain/urls/utils.py +5 -1
- plain/utils/cache.py +14 -8
- plain/utils/crypto.py +21 -5
- plain/utils/datastructures.py +84 -54
- plain/utils/dateparse.py +10 -7
- plain/utils/deconstruct.py +12 -4
- plain/utils/decorators.py +5 -1
- plain/utils/duration.py +8 -4
- plain/utils/encoding.py +14 -7
- plain/utils/functional.py +62 -47
- plain/utils/hashable.py +5 -1
- plain/utils/html.py +21 -14
- plain/utils/http.py +16 -9
- plain/utils/inspect.py +14 -6
- plain/utils/ipv6.py +7 -3
- plain/utils/itercompat.py +6 -1
- plain/utils/module_loading.py +7 -3
- plain/utils/regex_helper.py +23 -13
- plain/utils/safestring.py +14 -6
- plain/utils/text.py +34 -18
- plain/utils/timezone.py +30 -19
- plain/utils/tree.py +31 -18
- plain/validators.py +71 -44
- plain/views/base.py +16 -6
- plain/views/errors.py +11 -4
- plain/views/exceptions.py +4 -1
- plain/views/objects.py +27 -17
- plain/views/redirect.py +14 -10
- plain/views/templates.py +1 -1
- plain/wsgi.py +3 -1
- {plain-0.68.1.dist-info → plain-0.70.0.dist-info}/METADATA +1 -1
- plain-0.70.0.dist-info/RECORD +169 -0
- plain-0.68.1.dist-info/RECORD +0 -169
- {plain-0.68.1.dist-info → plain-0.70.0.dist-info}/WHEEL +0 -0
- {plain-0.68.1.dist-info → plain-0.70.0.dist-info}/entry_points.txt +0 -0
- {plain-0.68.1.dist-info → plain-0.70.0.dist-info}/licenses/LICENSE +0 -0
plain/debug.py
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from pprint import pformat
|
4
|
+
from typing import Any, NoReturn
|
2
5
|
|
3
6
|
from markupsafe import Markup, escape
|
4
7
|
|
@@ -6,7 +9,7 @@ from plain.http import Response
|
|
6
9
|
from plain.views.exceptions import ResponseException
|
7
10
|
|
8
11
|
|
9
|
-
def dd(*objs):
|
12
|
+
def dd(*objs: Any) -> NoReturn:
|
10
13
|
"""
|
11
14
|
Dump and die.
|
12
15
|
|
@@ -25,5 +28,5 @@ def dd(*objs):
|
|
25
28
|
response = Response()
|
26
29
|
response.status_code = 500
|
27
30
|
response.content = combined_dump_str
|
28
|
-
response.
|
31
|
+
response.headers["Content-Type"] = "text/html"
|
29
32
|
raise ResponseException(response)
|
plain/exceptions.py
CHANGED
@@ -2,11 +2,15 @@
|
|
2
2
|
Global Plain exception and warning classes.
|
3
3
|
"""
|
4
4
|
|
5
|
+
from __future__ import annotations
|
6
|
+
|
5
7
|
import operator
|
8
|
+
from collections.abc import Iterator
|
9
|
+
from typing import Any
|
6
10
|
|
7
11
|
from plain.utils.hashable import make_hashable
|
8
12
|
|
9
|
-
# MARK: Configuration and Registry
|
13
|
+
# MARK: Configuration and Package Registry
|
10
14
|
|
11
15
|
|
12
16
|
class PackageRegistryNotReady(Exception):
|
@@ -21,33 +25,6 @@ class ImproperlyConfigured(Exception):
|
|
21
25
|
pass
|
22
26
|
|
23
27
|
|
24
|
-
# MARK: Model and Field Errors
|
25
|
-
|
26
|
-
|
27
|
-
class FieldDoesNotExist(Exception):
|
28
|
-
"""The requested model field does not exist"""
|
29
|
-
|
30
|
-
pass
|
31
|
-
|
32
|
-
|
33
|
-
class FieldError(Exception):
|
34
|
-
"""Some kind of problem with a model field."""
|
35
|
-
|
36
|
-
pass
|
37
|
-
|
38
|
-
|
39
|
-
class ObjectDoesNotExist(Exception):
|
40
|
-
"""The requested object does not exist"""
|
41
|
-
|
42
|
-
pass
|
43
|
-
|
44
|
-
|
45
|
-
class MultipleObjectsReturned(Exception):
|
46
|
-
"""The query returned multiple objects when only one was expected."""
|
47
|
-
|
48
|
-
pass
|
49
|
-
|
50
|
-
|
51
28
|
# MARK: Security and Suspicious Operations
|
52
29
|
|
53
30
|
|
@@ -117,7 +94,12 @@ NON_FIELD_ERRORS = "__all__"
|
|
117
94
|
class ValidationError(Exception):
|
118
95
|
"""An error while validating data."""
|
119
96
|
|
120
|
-
def __init__(
|
97
|
+
def __init__(
|
98
|
+
self,
|
99
|
+
message: str | list[Any] | dict[str, Any] | ValidationError,
|
100
|
+
code: str | None = None,
|
101
|
+
params: dict[str, Any] | None = None,
|
102
|
+
):
|
121
103
|
"""
|
122
104
|
The `message` argument can be a single error, a list of errors, or a
|
123
105
|
dictionary that maps field names to lists of errors. What we define as
|
@@ -161,12 +143,14 @@ class ValidationError(Exception):
|
|
161
143
|
self.error_list = [self]
|
162
144
|
|
163
145
|
@property
|
164
|
-
def messages(self):
|
146
|
+
def messages(self) -> list[str]:
|
165
147
|
if hasattr(self, "error_dict"):
|
166
148
|
return sum(dict(self).values(), [])
|
167
149
|
return list(self)
|
168
150
|
|
169
|
-
def update_error_dict(
|
151
|
+
def update_error_dict(
|
152
|
+
self, error_dict: dict[str, list[ValidationError]]
|
153
|
+
) -> dict[str, list[ValidationError]]:
|
170
154
|
if hasattr(self, "error_dict"):
|
171
155
|
for field, error_list in self.error_dict.items():
|
172
156
|
error_dict.setdefault(field, []).extend(error_list)
|
@@ -174,7 +158,7 @@ class ValidationError(Exception):
|
|
174
158
|
error_dict.setdefault(NON_FIELD_ERRORS, []).extend(self.error_list)
|
175
159
|
return error_dict
|
176
160
|
|
177
|
-
def __iter__(self):
|
161
|
+
def __iter__(self) -> Iterator[tuple[str, list[str]] | str]:
|
178
162
|
if hasattr(self, "error_dict"):
|
179
163
|
for field, errors in self.error_dict.items():
|
180
164
|
yield field, list(ValidationError(errors))
|
@@ -185,20 +169,20 @@ class ValidationError(Exception):
|
|
185
169
|
message %= error.params
|
186
170
|
yield str(message)
|
187
171
|
|
188
|
-
def __str__(self):
|
172
|
+
def __str__(self) -> str:
|
189
173
|
if hasattr(self, "error_dict"):
|
190
174
|
return repr(dict(self))
|
191
175
|
return repr(list(self))
|
192
176
|
|
193
|
-
def __repr__(self):
|
177
|
+
def __repr__(self) -> str:
|
194
178
|
return f"ValidationError({self})"
|
195
179
|
|
196
|
-
def __eq__(self, other):
|
180
|
+
def __eq__(self, other: object) -> bool:
|
197
181
|
if not isinstance(other, ValidationError):
|
198
182
|
return NotImplemented
|
199
183
|
return hash(self) == hash(other)
|
200
184
|
|
201
|
-
def __hash__(self):
|
185
|
+
def __hash__(self) -> int:
|
202
186
|
if hasattr(self, "message"):
|
203
187
|
return hash(
|
204
188
|
(
|
@@ -210,18 +194,3 @@ class ValidationError(Exception):
|
|
210
194
|
if hasattr(self, "error_dict"):
|
211
195
|
return hash(make_hashable(self.error_dict))
|
212
196
|
return hash(tuple(sorted(self.error_list, key=operator.attrgetter("message"))))
|
213
|
-
|
214
|
-
|
215
|
-
# MARK: Database
|
216
|
-
|
217
|
-
|
218
|
-
class EmptyResultSet(Exception):
|
219
|
-
"""A database query predicate is impossible."""
|
220
|
-
|
221
|
-
pass
|
222
|
-
|
223
|
-
|
224
|
-
class FullResultSet(Exception):
|
225
|
-
"""A database query predicate is matches everything."""
|
226
|
-
|
227
|
-
pass
|
plain/forms/__init__.py
CHANGED
plain/forms/boundfield.py
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from functools import cached_property
|
4
|
+
from typing import TYPE_CHECKING, Any
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from .fields import Field
|
8
|
+
from .forms import BaseForm
|
2
9
|
|
3
10
|
__all__ = ("BoundField",)
|
4
11
|
|
@@ -6,24 +13,24 @@ __all__ = ("BoundField",)
|
|
6
13
|
class BoundField:
|
7
14
|
"A Field plus data"
|
8
15
|
|
9
|
-
def __init__(self, form, field, name):
|
16
|
+
def __init__(self, form: BaseForm, field: Field, name: str):
|
10
17
|
self._form = form
|
11
18
|
self.field = field
|
12
19
|
self.name = name
|
13
20
|
self.html_name = form.add_prefix(name)
|
14
21
|
self.html_id = form.add_prefix(self._auto_id)
|
15
22
|
|
16
|
-
def __repr__(self):
|
23
|
+
def __repr__(self) -> str:
|
17
24
|
return f'<{self.__class__.__name__} "{self.html_name}">'
|
18
25
|
|
19
26
|
@property
|
20
|
-
def errors(self):
|
27
|
+
def errors(self) -> list[str]:
|
21
28
|
"""
|
22
29
|
Return an error list (empty if there are no errors) for this field.
|
23
30
|
"""
|
24
31
|
return self._form.errors.get(self.name, [])
|
25
32
|
|
26
|
-
def value(self):
|
33
|
+
def value(self) -> Any:
|
27
34
|
"""
|
28
35
|
Return the value for this BoundField, using the initial value if
|
29
36
|
the form is not bound or the data otherwise.
|
@@ -36,16 +43,16 @@ class BoundField:
|
|
36
43
|
return self.field.prepare_value(data)
|
37
44
|
|
38
45
|
@cached_property
|
39
|
-
def initial(self):
|
46
|
+
def initial(self) -> Any:
|
40
47
|
return self._form.get_initial_for_field(self.field, self.name)
|
41
48
|
|
42
|
-
def _has_changed(self):
|
49
|
+
def _has_changed(self) -> bool:
|
43
50
|
return self.field.has_changed(
|
44
51
|
self.initial, self._form._field_data_value(self.field, self.html_name)
|
45
52
|
)
|
46
53
|
|
47
54
|
@property
|
48
|
-
def _auto_id(self):
|
55
|
+
def _auto_id(self) -> str:
|
49
56
|
"""
|
50
57
|
Calculate and return the ID attribute for this BoundField, if the
|
51
58
|
associated Form has specified auto_id. Return an empty string otherwise.
|
plain/forms/exceptions.py
CHANGED
@@ -2,7 +2,7 @@ from plain.exceptions import ValidationError
|
|
2
2
|
|
3
3
|
|
4
4
|
class FormFieldMissingError(Exception):
|
5
|
-
def __init__(self, field_name):
|
5
|
+
def __init__(self, field_name: str):
|
6
6
|
self.field_name = field_name
|
7
7
|
self.message = f'The "{self.field_name}" field is missing from the form data.'
|
8
8
|
|