plain 0.68.0__py3-none-any.whl → 0.103.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/CHANGELOG.md +684 -1
- plain/README.md +1 -1
- plain/agents/.claude/rules/plain.md +88 -0
- plain/agents/.claude/skills/plain-install/SKILL.md +26 -0
- plain/agents/.claude/skills/plain-upgrade/SKILL.md +35 -0
- plain/assets/compile.py +25 -12
- plain/assets/finders.py +24 -17
- plain/assets/fingerprints.py +10 -7
- plain/assets/urls.py +1 -1
- plain/assets/views.py +47 -33
- plain/chores/README.md +25 -23
- plain/chores/__init__.py +2 -1
- plain/chores/core.py +27 -0
- plain/chores/registry.py +23 -36
- plain/cli/README.md +185 -16
- plain/cli/__init__.py +2 -1
- plain/cli/agent.py +234 -0
- plain/cli/build.py +7 -8
- plain/cli/changelog.py +11 -5
- plain/cli/chores.py +32 -34
- plain/cli/core.py +110 -26
- plain/cli/docs.py +98 -21
- plain/cli/formatting.py +40 -17
- plain/cli/install.py +10 -54
- plain/cli/{agent/llmdocs.py → llmdocs.py} +45 -26
- plain/cli/output.py +6 -2
- plain/cli/preflight.py +27 -75
- plain/cli/print.py +4 -4
- plain/cli/registry.py +96 -10
- plain/cli/{agent/request.py → request.py} +67 -33
- plain/cli/runtime.py +45 -0
- plain/cli/scaffold.py +2 -7
- plain/cli/server.py +153 -0
- plain/cli/settings.py +53 -49
- plain/cli/shell.py +15 -12
- plain/cli/startup.py +9 -8
- plain/cli/upgrade.py +17 -104
- plain/cli/urls.py +12 -7
- plain/cli/utils.py +3 -3
- plain/csrf/README.md +65 -40
- plain/csrf/middleware.py +53 -43
- plain/debug.py +5 -2
- plain/exceptions.py +22 -114
- plain/forms/README.md +453 -24
- plain/forms/__init__.py +55 -4
- plain/forms/boundfield.py +15 -8
- plain/forms/exceptions.py +1 -1
- plain/forms/fields.py +346 -143
- plain/forms/forms.py +75 -45
- plain/http/README.md +356 -9
- plain/http/__init__.py +41 -26
- plain/http/cookie.py +15 -7
- plain/http/exceptions.py +65 -0
- plain/http/middleware.py +32 -0
- plain/http/multipartparser.py +99 -88
- plain/http/request.py +362 -250
- plain/http/response.py +99 -197
- plain/internal/__init__.py +8 -1
- plain/internal/files/base.py +35 -19
- plain/internal/files/locks.py +19 -11
- plain/internal/files/move.py +8 -3
- plain/internal/files/temp.py +25 -6
- plain/internal/files/uploadedfile.py +47 -28
- plain/internal/files/uploadhandler.py +64 -58
- plain/internal/files/utils.py +24 -10
- plain/internal/handlers/base.py +34 -23
- plain/internal/handlers/exception.py +68 -65
- plain/internal/handlers/wsgi.py +65 -54
- plain/internal/middleware/headers.py +37 -11
- plain/internal/middleware/hosts.py +11 -8
- plain/internal/middleware/https.py +17 -7
- plain/internal/middleware/slash.py +14 -9
- plain/internal/reloader.py +77 -0
- plain/json.py +2 -1
- plain/logs/README.md +161 -62
- plain/logs/__init__.py +1 -1
- plain/logs/{loggers.py → app.py} +71 -67
- plain/logs/configure.py +63 -14
- plain/logs/debug.py +17 -6
- plain/logs/filters.py +15 -0
- plain/logs/formatters.py +7 -4
- plain/packages/README.md +105 -23
- plain/packages/config.py +15 -7
- plain/packages/registry.py +27 -16
- plain/paginator.py +31 -21
- plain/preflight/README.md +209 -24
- plain/preflight/__init__.py +1 -0
- plain/preflight/checks.py +3 -1
- plain/preflight/files.py +3 -1
- plain/preflight/registry.py +26 -11
- plain/preflight/results.py +15 -7
- plain/preflight/security.py +15 -13
- plain/preflight/settings.py +54 -0
- plain/preflight/urls.py +4 -1
- plain/runtime/README.md +115 -47
- plain/runtime/__init__.py +10 -6
- plain/runtime/global_settings.py +34 -25
- plain/runtime/secret.py +20 -0
- plain/runtime/user_settings.py +110 -38
- plain/runtime/utils.py +1 -1
- plain/server/LICENSE +35 -0
- plain/server/README.md +155 -0
- plain/server/__init__.py +9 -0
- plain/server/app.py +52 -0
- plain/server/arbiter.py +555 -0
- plain/server/config.py +118 -0
- plain/server/errors.py +31 -0
- plain/server/glogging.py +292 -0
- plain/server/http/__init__.py +12 -0
- plain/server/http/body.py +283 -0
- plain/server/http/errors.py +155 -0
- plain/server/http/message.py +400 -0
- plain/server/http/parser.py +70 -0
- plain/server/http/unreader.py +88 -0
- plain/server/http/wsgi.py +421 -0
- plain/server/pidfile.py +92 -0
- plain/server/sock.py +240 -0
- plain/server/util.py +317 -0
- plain/server/workers/__init__.py +6 -0
- plain/server/workers/base.py +304 -0
- plain/server/workers/sync.py +212 -0
- plain/server/workers/thread.py +399 -0
- plain/server/workers/workertmp.py +50 -0
- plain/signals/README.md +170 -1
- plain/signals/__init__.py +0 -1
- plain/signals/dispatch/dispatcher.py +49 -27
- plain/signing.py +131 -35
- plain/templates/README.md +211 -20
- plain/templates/jinja/__init__.py +13 -5
- plain/templates/jinja/environments.py +5 -4
- plain/templates/jinja/extensions.py +12 -5
- plain/templates/jinja/filters.py +7 -2
- plain/templates/jinja/globals.py +2 -2
- plain/test/README.md +184 -22
- plain/test/client.py +340 -222
- plain/test/encoding.py +9 -6
- plain/test/exceptions.py +7 -2
- plain/urls/README.md +157 -73
- plain/urls/converters.py +18 -15
- plain/urls/exceptions.py +2 -2
- plain/urls/patterns.py +38 -22
- plain/urls/resolvers.py +35 -25
- plain/urls/utils.py +5 -1
- plain/utils/README.md +250 -3
- plain/utils/cache.py +17 -11
- plain/utils/crypto.py +21 -5
- plain/utils/datastructures.py +89 -56
- plain/utils/dateparse.py +9 -6
- plain/utils/deconstruct.py +15 -7
- plain/utils/decorators.py +5 -1
- plain/utils/dotenv.py +373 -0
- plain/utils/duration.py +8 -4
- plain/utils/encoding.py +14 -7
- plain/utils/functional.py +66 -49
- plain/utils/hashable.py +5 -1
- plain/utils/html.py +36 -22
- 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 +37 -23
- plain/utils/safestring.py +14 -6
- plain/utils/text.py +41 -23
- plain/utils/timezone.py +33 -22
- plain/utils/tree.py +35 -19
- plain/validators.py +94 -52
- plain/views/README.md +156 -79
- plain/views/__init__.py +0 -1
- plain/views/base.py +25 -18
- plain/views/errors.py +13 -5
- plain/views/exceptions.py +4 -1
- plain/views/forms.py +6 -6
- plain/views/objects.py +52 -49
- plain/views/redirect.py +18 -15
- plain/views/templates.py +5 -3
- plain/wsgi.py +3 -1
- {plain-0.68.0.dist-info → plain-0.103.0.dist-info}/METADATA +4 -2
- plain-0.103.0.dist-info/RECORD +198 -0
- {plain-0.68.0.dist-info → plain-0.103.0.dist-info}/WHEEL +1 -1
- plain-0.103.0.dist-info/entry_points.txt +2 -0
- plain/AGENTS.md +0 -18
- plain/cli/agent/__init__.py +0 -20
- plain/cli/agent/docs.py +0 -80
- plain/cli/agent/md.py +0 -87
- plain/cli/agent/prompt.py +0 -45
- plain/csrf/views.py +0 -31
- plain/logs/utils.py +0 -46
- plain/templates/AGENTS.md +0 -3
- plain-0.68.0.dist-info/RECORD +0 -169
- plain-0.68.0.dist-info/entry_points.txt +0 -5
- {plain-0.68.0.dist-info → plain-0.103.0.dist-info}/licenses/LICENSE +0 -0
plain/forms/__init__.py
CHANGED
|
@@ -2,7 +2,58 @@
|
|
|
2
2
|
Plain validation and HTML form handling.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from .boundfield import BoundField
|
|
6
|
-
from .exceptions import FormFieldMissingError, ValidationError
|
|
7
|
-
from .fields import
|
|
8
|
-
|
|
5
|
+
from .boundfield import BoundField
|
|
6
|
+
from .exceptions import FormFieldMissingError, ValidationError
|
|
7
|
+
from .fields import (
|
|
8
|
+
BooleanField,
|
|
9
|
+
CharField,
|
|
10
|
+
ChoiceField,
|
|
11
|
+
DateField,
|
|
12
|
+
DateTimeField,
|
|
13
|
+
DecimalField,
|
|
14
|
+
DurationField,
|
|
15
|
+
EmailField,
|
|
16
|
+
Field,
|
|
17
|
+
FileField,
|
|
18
|
+
FloatField,
|
|
19
|
+
ImageField,
|
|
20
|
+
IntegerField,
|
|
21
|
+
JSONField,
|
|
22
|
+
MultipleChoiceField,
|
|
23
|
+
NullBooleanField,
|
|
24
|
+
RegexField,
|
|
25
|
+
TimeField,
|
|
26
|
+
TypedChoiceField,
|
|
27
|
+
URLField,
|
|
28
|
+
UUIDField,
|
|
29
|
+
)
|
|
30
|
+
from .forms import BaseForm, Form
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"BoundField",
|
|
34
|
+
"FormFieldMissingError",
|
|
35
|
+
"ValidationError",
|
|
36
|
+
"BooleanField",
|
|
37
|
+
"CharField",
|
|
38
|
+
"ChoiceField",
|
|
39
|
+
"DateField",
|
|
40
|
+
"DateTimeField",
|
|
41
|
+
"DecimalField",
|
|
42
|
+
"DurationField",
|
|
43
|
+
"EmailField",
|
|
44
|
+
"Field",
|
|
45
|
+
"FileField",
|
|
46
|
+
"FloatField",
|
|
47
|
+
"ImageField",
|
|
48
|
+
"IntegerField",
|
|
49
|
+
"JSONField",
|
|
50
|
+
"MultipleChoiceField",
|
|
51
|
+
"NullBooleanField",
|
|
52
|
+
"RegexField",
|
|
53
|
+
"TimeField",
|
|
54
|
+
"TypedChoiceField",
|
|
55
|
+
"URLField",
|
|
56
|
+
"UUIDField",
|
|
57
|
+
"BaseForm",
|
|
58
|
+
"Form",
|
|
59
|
+
]
|
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,22 +43,22 @@ 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.
|
|
52
59
|
"""
|
|
53
60
|
auto_id = self._form._auto_id # Boolean or string
|
|
54
|
-
if auto_id and "%s" in
|
|
61
|
+
if auto_id and isinstance(auto_id, str) and "%s" in auto_id:
|
|
55
62
|
return auto_id % self.html_name
|
|
56
63
|
elif auto_id:
|
|
57
64
|
return self.html_name
|
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
|
|