plain 0.69.0__py3-none-any.whl → 0.71.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 +28 -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 +19 -8
- 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/README.md +1 -1
- plain/http/__init__.py +4 -4
- plain/http/cookie.py +15 -7
- plain/http/multipartparser.py +50 -30
- plain/http/request.py +156 -108
- 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 +50 -29
- plain/internal/files/utils.py +13 -6
- plain/internal/handlers/base.py +21 -7
- plain/internal/handlers/exception.py +19 -5
- plain/internal/handlers/wsgi.py +33 -21
- plain/internal/middleware/headers.py +11 -2
- plain/internal/middleware/hosts.py +12 -4
- 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 +7 -6
- plain/runtime/global_settings.py +6 -9
- 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 +249 -177
- 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 -8
- plain/views/errors.py +11 -4
- plain/views/exceptions.py +4 -1
- plain/views/objects.py +15 -15
- plain/views/redirect.py +14 -10
- plain/views/templates.py +1 -1
- plain/wsgi.py +3 -1
- {plain-0.69.0.dist-info → plain-0.71.0.dist-info}/METADATA +1 -1
- plain-0.71.0.dist-info/RECORD +169 -0
- plain-0.69.0.dist-info/RECORD +0 -169
- {plain-0.69.0.dist-info → plain-0.71.0.dist-info}/WHEEL +0 -0
- {plain-0.69.0.dist-info → plain-0.71.0.dist-info}/entry_points.txt +0 -0
- {plain-0.69.0.dist-info → plain-0.71.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,7 +2,11 @@
|
|
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
|
|
@@ -90,7 +94,12 @@ NON_FIELD_ERRORS = "__all__"
|
|
90
94
|
class ValidationError(Exception):
|
91
95
|
"""An error while validating data."""
|
92
96
|
|
93
|
-
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
|
+
):
|
94
103
|
"""
|
95
104
|
The `message` argument can be a single error, a list of errors, or a
|
96
105
|
dictionary that maps field names to lists of errors. What we define as
|
@@ -134,12 +143,14 @@ class ValidationError(Exception):
|
|
134
143
|
self.error_list = [self]
|
135
144
|
|
136
145
|
@property
|
137
|
-
def messages(self):
|
146
|
+
def messages(self) -> list[str]:
|
138
147
|
if hasattr(self, "error_dict"):
|
139
148
|
return sum(dict(self).values(), [])
|
140
149
|
return list(self)
|
141
150
|
|
142
|
-
def update_error_dict(
|
151
|
+
def update_error_dict(
|
152
|
+
self, error_dict: dict[str, list[ValidationError]]
|
153
|
+
) -> dict[str, list[ValidationError]]:
|
143
154
|
if hasattr(self, "error_dict"):
|
144
155
|
for field, error_list in self.error_dict.items():
|
145
156
|
error_dict.setdefault(field, []).extend(error_list)
|
@@ -147,7 +158,7 @@ class ValidationError(Exception):
|
|
147
158
|
error_dict.setdefault(NON_FIELD_ERRORS, []).extend(self.error_list)
|
148
159
|
return error_dict
|
149
160
|
|
150
|
-
def __iter__(self):
|
161
|
+
def __iter__(self) -> Iterator[tuple[str, list[str]] | str]:
|
151
162
|
if hasattr(self, "error_dict"):
|
152
163
|
for field, errors in self.error_dict.items():
|
153
164
|
yield field, list(ValidationError(errors))
|
@@ -158,20 +169,20 @@ class ValidationError(Exception):
|
|
158
169
|
message %= error.params
|
159
170
|
yield str(message)
|
160
171
|
|
161
|
-
def __str__(self):
|
172
|
+
def __str__(self) -> str:
|
162
173
|
if hasattr(self, "error_dict"):
|
163
174
|
return repr(dict(self))
|
164
175
|
return repr(list(self))
|
165
176
|
|
166
|
-
def __repr__(self):
|
177
|
+
def __repr__(self) -> str:
|
167
178
|
return f"ValidationError({self})"
|
168
179
|
|
169
|
-
def __eq__(self, other):
|
180
|
+
def __eq__(self, other: object) -> bool:
|
170
181
|
if not isinstance(other, ValidationError):
|
171
182
|
return NotImplemented
|
172
183
|
return hash(self) == hash(other)
|
173
184
|
|
174
|
-
def __hash__(self):
|
185
|
+
def __hash__(self) -> int:
|
175
186
|
if hasattr(self, "message"):
|
176
187
|
return hash(
|
177
188
|
(
|
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
|
|