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.
Files changed (192) hide show
  1. plain/CHANGELOG.md +684 -1
  2. plain/README.md +1 -1
  3. plain/agents/.claude/rules/plain.md +88 -0
  4. plain/agents/.claude/skills/plain-install/SKILL.md +26 -0
  5. plain/agents/.claude/skills/plain-upgrade/SKILL.md +35 -0
  6. plain/assets/compile.py +25 -12
  7. plain/assets/finders.py +24 -17
  8. plain/assets/fingerprints.py +10 -7
  9. plain/assets/urls.py +1 -1
  10. plain/assets/views.py +47 -33
  11. plain/chores/README.md +25 -23
  12. plain/chores/__init__.py +2 -1
  13. plain/chores/core.py +27 -0
  14. plain/chores/registry.py +23 -36
  15. plain/cli/README.md +185 -16
  16. plain/cli/__init__.py +2 -1
  17. plain/cli/agent.py +234 -0
  18. plain/cli/build.py +7 -8
  19. plain/cli/changelog.py +11 -5
  20. plain/cli/chores.py +32 -34
  21. plain/cli/core.py +110 -26
  22. plain/cli/docs.py +98 -21
  23. plain/cli/formatting.py +40 -17
  24. plain/cli/install.py +10 -54
  25. plain/cli/{agent/llmdocs.py → llmdocs.py} +45 -26
  26. plain/cli/output.py +6 -2
  27. plain/cli/preflight.py +27 -75
  28. plain/cli/print.py +4 -4
  29. plain/cli/registry.py +96 -10
  30. plain/cli/{agent/request.py → request.py} +67 -33
  31. plain/cli/runtime.py +45 -0
  32. plain/cli/scaffold.py +2 -7
  33. plain/cli/server.py +153 -0
  34. plain/cli/settings.py +53 -49
  35. plain/cli/shell.py +15 -12
  36. plain/cli/startup.py +9 -8
  37. plain/cli/upgrade.py +17 -104
  38. plain/cli/urls.py +12 -7
  39. plain/cli/utils.py +3 -3
  40. plain/csrf/README.md +65 -40
  41. plain/csrf/middleware.py +53 -43
  42. plain/debug.py +5 -2
  43. plain/exceptions.py +22 -114
  44. plain/forms/README.md +453 -24
  45. plain/forms/__init__.py +55 -4
  46. plain/forms/boundfield.py +15 -8
  47. plain/forms/exceptions.py +1 -1
  48. plain/forms/fields.py +346 -143
  49. plain/forms/forms.py +75 -45
  50. plain/http/README.md +356 -9
  51. plain/http/__init__.py +41 -26
  52. plain/http/cookie.py +15 -7
  53. plain/http/exceptions.py +65 -0
  54. plain/http/middleware.py +32 -0
  55. plain/http/multipartparser.py +99 -88
  56. plain/http/request.py +362 -250
  57. plain/http/response.py +99 -197
  58. plain/internal/__init__.py +8 -1
  59. plain/internal/files/base.py +35 -19
  60. plain/internal/files/locks.py +19 -11
  61. plain/internal/files/move.py +8 -3
  62. plain/internal/files/temp.py +25 -6
  63. plain/internal/files/uploadedfile.py +47 -28
  64. plain/internal/files/uploadhandler.py +64 -58
  65. plain/internal/files/utils.py +24 -10
  66. plain/internal/handlers/base.py +34 -23
  67. plain/internal/handlers/exception.py +68 -65
  68. plain/internal/handlers/wsgi.py +65 -54
  69. plain/internal/middleware/headers.py +37 -11
  70. plain/internal/middleware/hosts.py +11 -8
  71. plain/internal/middleware/https.py +17 -7
  72. plain/internal/middleware/slash.py +14 -9
  73. plain/internal/reloader.py +77 -0
  74. plain/json.py +2 -1
  75. plain/logs/README.md +161 -62
  76. plain/logs/__init__.py +1 -1
  77. plain/logs/{loggers.py → app.py} +71 -67
  78. plain/logs/configure.py +63 -14
  79. plain/logs/debug.py +17 -6
  80. plain/logs/filters.py +15 -0
  81. plain/logs/formatters.py +7 -4
  82. plain/packages/README.md +105 -23
  83. plain/packages/config.py +15 -7
  84. plain/packages/registry.py +27 -16
  85. plain/paginator.py +31 -21
  86. plain/preflight/README.md +209 -24
  87. plain/preflight/__init__.py +1 -0
  88. plain/preflight/checks.py +3 -1
  89. plain/preflight/files.py +3 -1
  90. plain/preflight/registry.py +26 -11
  91. plain/preflight/results.py +15 -7
  92. plain/preflight/security.py +15 -13
  93. plain/preflight/settings.py +54 -0
  94. plain/preflight/urls.py +4 -1
  95. plain/runtime/README.md +115 -47
  96. plain/runtime/__init__.py +10 -6
  97. plain/runtime/global_settings.py +34 -25
  98. plain/runtime/secret.py +20 -0
  99. plain/runtime/user_settings.py +110 -38
  100. plain/runtime/utils.py +1 -1
  101. plain/server/LICENSE +35 -0
  102. plain/server/README.md +155 -0
  103. plain/server/__init__.py +9 -0
  104. plain/server/app.py +52 -0
  105. plain/server/arbiter.py +555 -0
  106. plain/server/config.py +118 -0
  107. plain/server/errors.py +31 -0
  108. plain/server/glogging.py +292 -0
  109. plain/server/http/__init__.py +12 -0
  110. plain/server/http/body.py +283 -0
  111. plain/server/http/errors.py +155 -0
  112. plain/server/http/message.py +400 -0
  113. plain/server/http/parser.py +70 -0
  114. plain/server/http/unreader.py +88 -0
  115. plain/server/http/wsgi.py +421 -0
  116. plain/server/pidfile.py +92 -0
  117. plain/server/sock.py +240 -0
  118. plain/server/util.py +317 -0
  119. plain/server/workers/__init__.py +6 -0
  120. plain/server/workers/base.py +304 -0
  121. plain/server/workers/sync.py +212 -0
  122. plain/server/workers/thread.py +399 -0
  123. plain/server/workers/workertmp.py +50 -0
  124. plain/signals/README.md +170 -1
  125. plain/signals/__init__.py +0 -1
  126. plain/signals/dispatch/dispatcher.py +49 -27
  127. plain/signing.py +131 -35
  128. plain/templates/README.md +211 -20
  129. plain/templates/jinja/__init__.py +13 -5
  130. plain/templates/jinja/environments.py +5 -4
  131. plain/templates/jinja/extensions.py +12 -5
  132. plain/templates/jinja/filters.py +7 -2
  133. plain/templates/jinja/globals.py +2 -2
  134. plain/test/README.md +184 -22
  135. plain/test/client.py +340 -222
  136. plain/test/encoding.py +9 -6
  137. plain/test/exceptions.py +7 -2
  138. plain/urls/README.md +157 -73
  139. plain/urls/converters.py +18 -15
  140. plain/urls/exceptions.py +2 -2
  141. plain/urls/patterns.py +38 -22
  142. plain/urls/resolvers.py +35 -25
  143. plain/urls/utils.py +5 -1
  144. plain/utils/README.md +250 -3
  145. plain/utils/cache.py +17 -11
  146. plain/utils/crypto.py +21 -5
  147. plain/utils/datastructures.py +89 -56
  148. plain/utils/dateparse.py +9 -6
  149. plain/utils/deconstruct.py +15 -7
  150. plain/utils/decorators.py +5 -1
  151. plain/utils/dotenv.py +373 -0
  152. plain/utils/duration.py +8 -4
  153. plain/utils/encoding.py +14 -7
  154. plain/utils/functional.py +66 -49
  155. plain/utils/hashable.py +5 -1
  156. plain/utils/html.py +36 -22
  157. plain/utils/http.py +16 -9
  158. plain/utils/inspect.py +14 -6
  159. plain/utils/ipv6.py +7 -3
  160. plain/utils/itercompat.py +6 -1
  161. plain/utils/module_loading.py +7 -3
  162. plain/utils/regex_helper.py +37 -23
  163. plain/utils/safestring.py +14 -6
  164. plain/utils/text.py +41 -23
  165. plain/utils/timezone.py +33 -22
  166. plain/utils/tree.py +35 -19
  167. plain/validators.py +94 -52
  168. plain/views/README.md +156 -79
  169. plain/views/__init__.py +0 -1
  170. plain/views/base.py +25 -18
  171. plain/views/errors.py +13 -5
  172. plain/views/exceptions.py +4 -1
  173. plain/views/forms.py +6 -6
  174. plain/views/objects.py +52 -49
  175. plain/views/redirect.py +18 -15
  176. plain/views/templates.py +5 -3
  177. plain/wsgi.py +3 -1
  178. {plain-0.68.0.dist-info → plain-0.103.0.dist-info}/METADATA +4 -2
  179. plain-0.103.0.dist-info/RECORD +198 -0
  180. {plain-0.68.0.dist-info → plain-0.103.0.dist-info}/WHEEL +1 -1
  181. plain-0.103.0.dist-info/entry_points.txt +2 -0
  182. plain/AGENTS.md +0 -18
  183. plain/cli/agent/__init__.py +0 -20
  184. plain/cli/agent/docs.py +0 -80
  185. plain/cli/agent/md.py +0 -87
  186. plain/cli/agent/prompt.py +0 -45
  187. plain/csrf/views.py +0 -31
  188. plain/logs/utils.py +0 -46
  189. plain/templates/AGENTS.md +0 -3
  190. plain-0.68.0.dist-info/RECORD +0 -169
  191. plain-0.68.0.dist-info/entry_points.txt +0 -5
  192. {plain-0.68.0.dist-info → plain-0.103.0.dist-info}/licenses/LICENSE +0 -0
plain/views/objects.py CHANGED
@@ -1,9 +1,16 @@
1
+ from abc import ABC, abstractmethod
1
2
  from functools import cached_property
2
3
  from typing import Any
3
4
 
4
- from plain.exceptions import ImproperlyConfigured, ObjectDoesNotExist
5
- from plain.forms import Form
6
- from plain.http import Http404
5
+ from plain.exceptions import ImproperlyConfigured
6
+
7
+ try:
8
+ from plain.models.exceptions import ObjectDoesNotExist
9
+ except ImportError:
10
+ ObjectDoesNotExist = None # type: ignore[misc, assignment]
11
+
12
+ from plain.forms import BaseForm, Form
13
+ from plain.http import NotFoundError404
7
14
 
8
15
  from .forms import FormView
9
16
  from .templates import TemplateView
@@ -15,10 +22,10 @@ class CreateView(FormView):
15
22
  """
16
23
 
17
24
  # TODO? would rather you have to specify this...
18
- def get_success_url(self, form):
25
+ def get_success_url(self, form: BaseForm) -> str:
19
26
  """Return the URL to redirect to after processing a valid form."""
20
27
  if self.success_url:
21
- url = self.success_url.format(**self.object.__dict__)
28
+ url = str(self.success_url).format(**self.object.__dict__)
22
29
  else:
23
30
  try:
24
31
  url = self.object.get_absolute_url()
@@ -29,36 +36,45 @@ class CreateView(FormView):
29
36
  )
30
37
  return url
31
38
 
32
- def form_valid(self, form):
39
+ def form_valid(self, form: BaseForm) -> Any:
33
40
  """If the form is valid, save the associated model."""
34
- self.object = form.save()
41
+ self.object = form.save() # type: ignore[attr-defined]
35
42
  return super().form_valid(form)
36
43
 
37
44
 
38
- class ObjectTemplateViewMixin:
45
+ class DetailView(TemplateView, ABC):
46
+ """
47
+ Render a "detail" view of an object.
48
+
49
+ By default this is a model instance looked up from `self.queryset`, but the
50
+ view will support display of *any* object by overriding `self.get_object()`.
51
+ """
52
+
39
53
  context_object_name = ""
40
54
 
41
55
  @cached_property
42
56
  def object(self) -> Any:
43
57
  try:
44
58
  obj = self.get_object()
45
- except ObjectDoesNotExist:
46
- raise Http404
59
+ except Exception as e:
60
+ # If ObjectDoesNotExist is available and this is that exception, raise 404
61
+ if ObjectDoesNotExist and isinstance(e, ObjectDoesNotExist):
62
+ raise NotFoundError404
63
+ # Otherwise, let other exceptions bubble up
64
+ raise
47
65
 
48
66
  # Also raise 404 if get_object() returns None
49
67
  if not obj:
50
- raise Http404
68
+ raise NotFoundError404
51
69
 
52
70
  return obj
53
71
 
54
- def get_object(self) -> Any:
55
- raise NotImplementedError(
56
- f"get_object() is not implemented on {self.__class__.__name__}"
57
- )
72
+ @abstractmethod
73
+ def get_object(self) -> Any: ...
58
74
 
59
- def get_template_context(self) -> dict:
75
+ def get_template_context(self) -> dict[str, Any]:
60
76
  """Insert the single object into the context dict."""
61
- context = super().get_template_context() # type: ignore
77
+ context = super().get_template_context()
62
78
  context["object"] = (
63
79
  self.object
64
80
  ) # Some templates can benefit by always knowing a primary "object" can be present
@@ -67,24 +83,13 @@ class ObjectTemplateViewMixin:
67
83
  return context
68
84
 
69
85
 
70
- class DetailView(ObjectTemplateViewMixin, TemplateView):
71
- """
72
- Render a "detail" view of an object.
73
-
74
- By default this is a model instance looked up from `self.queryset`, but the
75
- view will support display of *any* object by overriding `self.get_object()`.
76
- """
77
-
78
- pass
79
-
80
-
81
- class UpdateView(ObjectTemplateViewMixin, FormView):
86
+ class UpdateView(DetailView, FormView):
82
87
  """View for updating an object, with a response rendered by a template."""
83
88
 
84
- def get_success_url(self, form):
89
+ def get_success_url(self, form: BaseForm) -> str:
85
90
  """Return the URL to redirect to after processing a valid form."""
86
91
  if self.success_url:
87
- url = self.success_url.format(**self.object.__dict__)
92
+ url = str(self.success_url).format(**self.object.__dict__)
88
93
  else:
89
94
  try:
90
95
  url = self.object.get_absolute_url()
@@ -95,47 +100,47 @@ class UpdateView(ObjectTemplateViewMixin, FormView):
95
100
  )
96
101
  return url
97
102
 
98
- def form_valid(self, form):
103
+ def form_valid(self, form: BaseForm) -> Any:
99
104
  """If the form is valid, save the associated model."""
100
- form.save()
105
+ form.save() # type: ignore[attr-defined]
101
106
  return super().form_valid(form)
102
107
 
103
- def get_form_kwargs(self):
108
+ def get_form_kwargs(self) -> dict[str, Any]:
104
109
  """Return the keyword arguments for instantiating the form."""
105
110
  kwargs = super().get_form_kwargs()
106
111
  kwargs.update({"instance": self.object})
107
112
  return kwargs
108
113
 
109
114
 
110
- class DeleteView(ObjectTemplateViewMixin, FormView):
115
+ class DeleteView(DetailView, FormView):
111
116
  """
112
117
  View for deleting an object retrieved with self.get_object(), with a
113
118
  response rendered by a template.
114
119
  """
115
120
 
116
121
  class EmptyDeleteForm(Form):
117
- def __init__(self, instance, *args, **kwargs):
122
+ def __init__(self, instance: Any, **kwargs: Any) -> None:
118
123
  self.instance = instance
119
- super().__init__(*args, **kwargs)
124
+ super().__init__(**kwargs)
120
125
 
121
- def save(self):
126
+ def save(self) -> None:
122
127
  self.instance.delete()
123
128
 
124
129
  form_class = EmptyDeleteForm
125
130
 
126
- def get_form_kwargs(self):
131
+ def get_form_kwargs(self) -> dict[str, Any]:
127
132
  """Return the keyword arguments for instantiating the form."""
128
133
  kwargs = super().get_form_kwargs()
129
134
  kwargs.update({"instance": self.object})
130
135
  return kwargs
131
136
 
132
- def form_valid(self, form):
137
+ def form_valid(self, form: BaseForm) -> Any:
133
138
  """If the form is valid, save the associated model."""
134
- form.save()
139
+ form.save() # type: ignore[attr-defined]
135
140
  return super().form_valid(form)
136
141
 
137
142
 
138
- class ListView(TemplateView):
143
+ class ListView(TemplateView, ABC):
139
144
  """
140
145
  Render some list of objects, set by `self.get_queryset()`, with a response
141
146
  rendered by a template.
@@ -144,17 +149,15 @@ class ListView(TemplateView):
144
149
  context_object_name = ""
145
150
 
146
151
  @cached_property
147
- def objects(self):
152
+ def objects(self) -> Any:
148
153
  return self.get_objects()
149
154
 
150
- def get_objects(self):
151
- raise NotImplementedError(
152
- f"get_objects() is not implemented on {self.__class__.__name__}"
153
- )
155
+ @abstractmethod
156
+ def get_objects(self) -> Any: ...
154
157
 
155
- def get_template_context(self) -> dict:
158
+ def get_template_context(self) -> dict[str, Any]:
156
159
  """Insert the single object into the context dict."""
157
- context = super().get_template_context() # type: ignore
160
+ context = super().get_template_context()
158
161
  context["objects"] = self.objects
159
162
  if self.context_object_name:
160
163
  context[self.context_object_name] = self.objects
plain/views/redirect.py CHANGED
@@ -1,4 +1,4 @@
1
- from plain.http import ResponseRedirect
1
+ from plain.http import RedirectResponse
2
2
  from plain.urls import reverse
3
3
 
4
4
  from .base import View
@@ -13,8 +13,12 @@ class RedirectView(View):
13
13
  preserve_query_params = False
14
14
 
15
15
  def __init__(
16
- self, url=None, status_code=None, url_name=None, preserve_query_params=None
17
- ):
16
+ self,
17
+ url: str | None = None,
18
+ status_code: int | None = None,
19
+ url_name: str | None = None,
20
+ preserve_query_params: bool | None = None,
21
+ ) -> None:
18
22
  # Allow attributes to be set in RedirectView.as_view(url="...", status_code=301, etc.)
19
23
  self.url = url or self.url
20
24
  self.status_code = status_code if status_code is not None else self.status_code
@@ -25,7 +29,7 @@ class RedirectView(View):
25
29
  else self.preserve_query_params
26
30
  )
27
31
 
28
- def get_redirect_url(self):
32
+ def get_redirect_url(self) -> str:
29
33
  """
30
34
  Return the URL redirect to. Keyword arguments from the URL pattern
31
35
  match generating the redirect request are provided as kwargs to this
@@ -38,29 +42,28 @@ class RedirectView(View):
38
42
  else:
39
43
  raise ValueError("RedirectView requires either url or url_name to be set")
40
44
 
41
- args = self.request.meta.get("QUERY_STRING", "")
42
- if args and self.preserve_query_params:
43
- url = f"{url}?{args}"
45
+ if self.preserve_query_params and self.request.query_string:
46
+ url = f"{url}?{self.request.query_string}"
44
47
  return url
45
48
 
46
- def get(self):
49
+ def get(self) -> RedirectResponse:
47
50
  url = self.get_redirect_url()
48
- return ResponseRedirect(url, status_code=self.status_code)
51
+ return RedirectResponse(url, status_code=self.status_code)
49
52
 
50
- def head(self):
53
+ def head(self) -> RedirectResponse:
51
54
  return self.get()
52
55
 
53
- def post(self):
56
+ def post(self) -> RedirectResponse:
54
57
  return self.get()
55
58
 
56
- def options(self):
59
+ def options(self) -> RedirectResponse:
57
60
  return self.get()
58
61
 
59
- def delete(self):
62
+ def delete(self) -> RedirectResponse:
60
63
  return self.get()
61
64
 
62
- def put(self):
65
+ def put(self) -> RedirectResponse:
63
66
  return self.get()
64
67
 
65
- def patch(self):
68
+ def patch(self) -> RedirectResponse:
66
69
  return self.get()
plain/views/templates.py CHANGED
@@ -1,3 +1,5 @@
1
+ from typing import Any
2
+
1
3
  from plain.exceptions import ImproperlyConfigured
2
4
  from plain.http import Response
3
5
  from plain.runtime import settings
@@ -13,11 +15,11 @@ class TemplateView(View):
13
15
 
14
16
  template_name: str | None = None
15
17
 
16
- def __init__(self, template_name=None):
18
+ def __init__(self, template_name: str | None = None) -> None:
17
19
  # Allow template_name to be passed in as_view()
18
20
  self.template_name = template_name or self.template_name
19
21
 
20
- def get_template_context(self) -> dict:
22
+ def get_template_context(self) -> dict[str, Any]:
21
23
  return {
22
24
  "request": self.request,
23
25
  "template_names": self.get_template_names(),
@@ -58,5 +60,5 @@ class TemplateView(View):
58
60
  def render_template(self) -> str:
59
61
  return self.get_template().render(self.get_template_context())
60
62
 
61
- def get(self) -> Response:
63
+ def get(self) -> Response | Any:
62
64
  return Response(self.render_template())
plain/wsgi.py CHANGED
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import plain.runtime
2
4
  from plain.internal.handlers.wsgi import WSGIHandler
3
5
 
4
6
 
5
- def _get_wsgi_application():
7
+ def _get_wsgi_application() -> WSGIHandler:
6
8
  plain.runtime.setup()
7
9
  return WSGIHandler()
8
10
 
@@ -1,14 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.68.0
3
+ Version: 0.103.0
4
4
  Summary: A web framework for building products with Python.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
+ License-Expression: BSD-3-Clause
6
7
  License-File: LICENSE
7
8
  Requires-Python: >=3.13
8
9
  Requires-Dist: click>=8.0.0
9
10
  Requires-Dist: jinja2>=3.1.2
10
11
  Requires-Dist: opentelemetry-api>=1.34.1
11
12
  Requires-Dist: opentelemetry-semantic-conventions>=0.55b1
13
+ Requires-Dist: watchfiles>=0.18.0
12
14
  Description-Content-Type: text/markdown
13
15
 
14
16
  # Plain
@@ -44,7 +46,7 @@ The `plain` package includes everything you need to start handling web requests
44
46
  - [plain.cache](/plain-cache/plain/cache/README.md) - A database-driven general purpose cache.
45
47
  - [plain.email](/plain-email/plain/email/README.md) - Send emails with SMTP or custom backends.
46
48
  - [plain.sessions](/plain-sessions/plain/sessions/README.md) - User sessions and cookies.
47
- - [plain.worker](/plain-worker/plain/worker/README.md) - Background jobs stored in the database.
49
+ - [plain.jobs](/plain-jobs/plain/jobs/README.md) - Background jobs stored in the database.
48
50
  - [plain.api](/plain-api/plain/api/README.md) - Build APIs with Plain views.
49
51
 
50
52
  ## Auth Packages
@@ -0,0 +1,198 @@
1
+ plain/CHANGELOG.md,sha256=FDaTDlnefZUH4Yq9XefuoXUf3CLLA0QKAM6Y0ln_q8I,57385
2
+ plain/README.md,sha256=VvzhXNvf4I6ddmjBP9AExxxFXxs7RwyoxdgFm-W5dsg,4072
3
+ plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
4
+ plain/debug.py,sha256=C2OnFHtRGMrpCiHSt-P2r58JypgQZ62qzDBpV4mhtFM,855
5
+ plain/exceptions.py,sha256=zV_HXXrxA11YwP0CVHLTPd_5YA7N311V32lFrO3LtB4,4523
6
+ plain/json.py,sha256=SC0CWPBhlQs3F_xlMcvZGUGxVBEMqA378uqIC3Q00aI,1297
7
+ plain/paginator.py,sha256=uModWWSnXISNt1Ecb5C17yoaKiTcHltFHLrfqQ-lio0,6240
8
+ plain/signing.py,sha256=aFwfZew9Ot7_26F88wSOU82MdNUQ3i1A7kI1qKS1q-4,11634
9
+ plain/validators.py,sha256=JBeycZFilWs69ai-QQb_4snpDgs1fjMGT0ICwVB1mvE,21297
10
+ plain/wsgi.py,sha256=MWQ09DFNV2GxX752hYJMWka9LTEwzCp6XEC_4TdXF9g,287
11
+ plain/agents/.claude/rules/plain.md,sha256=CgNYXI1pA3DWZv1T5iuempiMIJLD8w9VWbYDlJNuXNI,3617
12
+ plain/agents/.claude/skills/plain-install/SKILL.md,sha256=lPhUMmKAQpjrVMp73T5pBkwkIaJjWTxAqtyS8Hio8mM,746
13
+ plain/agents/.claude/skills/plain-upgrade/SKILL.md,sha256=79otYHv68Tfk3j08kRbg7o9eiNzzFZFyXCfLuIlT2Ho,927
14
+ plain/assets/README.md,sha256=iRZHHoXZCEFooXoDYSomF12XTcDFl7oNbRHpAFwxTM8,4349
15
+ plain/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ plain/assets/compile.py,sha256=a-e_nKHJLN3fOp9nw1MF7f7oP6q5qX8oYelowXKb_EE,3587
17
+ plain/assets/finders.py,sha256=Ldcn3bB3opab1AhRHef1arMEjOYDXH9TAt8sLYyieo0,1689
18
+ plain/assets/fingerprints.py,sha256=efOh65ueJxEP73teil_e_5wvn6K8kCzCi3RgPdT9pik,1515
19
+ plain/assets/urls.py,sha256=Qe0ctXYAQjAlLIKuX6JeLVBOFqzJxggR0RSGkRXmX78,1173
20
+ plain/assets/views.py,sha256=wVhFn4wIYzYwZN26HmgFgbNXHs4CmGZkc7tWB6mPQHk,10383
21
+ plain/chores/README.md,sha256=7Dv5MCyqPaohQxAk74HRzKiTvt_yR-IOhbu7R5bdz6M,2437
22
+ plain/chores/__init__.py,sha256=wEdt-oAKS1kz7Ln-puhcCt8XGNdfCP4S3wHESaPU8nI,100
23
+ plain/chores/core.py,sha256=BxsCSJDQvMjYsAH4QhEoW9ZUEAUIwJgTYyHovPGSLjk,578
24
+ plain/chores/registry.py,sha256=IRpx3f6Z1qlqcEpHTe6O6JocNmaplLu7BVOqGrafSXU,1221
25
+ plain/cli/README.md,sha256=8OmOhvKvgFkndwGn8lW6q62mvIB6Zq_NAhEhBf7PVV0,6305
26
+ plain/cli/__init__.py,sha256=o-dmnnNmXM3fZrKwW1qcoAPlcfG5pcFTFYWEBCocf8A,117
27
+ plain/cli/agent.py,sha256=ZjCmeroPhX8Dc9Y0J_-ePzdZIZ4kicC3adsV6GL4xNI,7683
28
+ plain/cli/build.py,sha256=UxgyAris4xtOZYS4FLMIOQLQ9DflHZkkenUJ650kZuA,3142
29
+ plain/cli/changelog.py,sha256=ckuP99HtnUSJXf_AELWgmD9S0XjldCzh5urxPiDpRcI,3647
30
+ plain/cli/chores.py,sha256=jhLKxFs_YpyQDvo5chNGiKnFc2_KK5atq53EpkGmMo0,2542
31
+ plain/cli/core.py,sha256=_Kg_YnYeDZB3EKlzdejl4eJepzRd2aF1LSs0Ei2uCI8,6923
32
+ plain/cli/docs.py,sha256=XeLt_Zf43NZV4yCkj0nqYcT5ejY7NR_NvyouClZTL0c,4321
33
+ plain/cli/formatting.py,sha256=t0kxJ-r-his8kGcY38g38p699CfFGUgqEI1tJkEbjGs,3802
34
+ plain/cli/install.py,sha256=28lXobloqqN50duuKlUqalAmNHUJdz-JlYYmdpXpzCs,1260
35
+ plain/cli/llmdocs.py,sha256=05lMHg6pYUb4zi6CTW5DDAidHFOkZWfG4ReMgUB42aM,5824
36
+ plain/cli/output.py,sha256=uZTHZR-Axeoi2r6fgcDCpDA7iQSRrktBtTf1yBT5djI,1426
37
+ plain/cli/preflight.py,sha256=zWnw9RFmN0CQQByAB2vWLbwlDNrQzUQTtKKySVzRucE,7036
38
+ plain/cli/print.py,sha256=s_yNxtA4vg-AWn4C9TtFH-gOMnzMsXuEr7ak4-oOFPI,265
39
+ plain/cli/registry.py,sha256=Xk6uwVT0j0GeweFtYd4k4PnxXs6-jDLjEl-fFd0_e5Y,3849
40
+ plain/cli/request.py,sha256=vHtVN50in7Z3OQFxr-ezHRPYPn46koWQ3-2ITbDY22Y,6680
41
+ plain/cli/runtime.py,sha256=xpGBlLV70VC541DpyKL4cwqiL2xr8xz_cCuSZ3RC33s,1124
42
+ plain/cli/scaffold.py,sha256=v0bbtelz8-j2LP48W0uXvL1xgZ9ce6IfAYDxqKXqesk,1203
43
+ plain/cli/server.py,sha256=2MIy5TwzqhZMzy-wPUlNqAhOTlsnR1ZYh6Ja9eghXJM,3637
44
+ plain/cli/settings.py,sha256=aVGttxVIpVmxE-vfOW4r3JCLWlhZEA65SwHl6SfUSv8,1916
45
+ plain/cli/shell.py,sha256=QOh2g4bkkdWdR6VxUQogqgh1ehHFPqi5gAS9DsviRvY,1946
46
+ plain/cli/startup.py,sha256=sdnIpCP1ruyMtZFoVf1sff9sb5IE44pyoAuGc7nYTXI,1105
47
+ plain/cli/upgrade.py,sha256=rQMSIgkWBdO3F9Fgv2279y6cKeZE52kPoAbrUmVrybk,2375
48
+ plain/cli/urls.py,sha256=hKzRfXxZxUT4ePDMThEvy-X9XNqfIeVhrxK2vAEqjbI,3976
49
+ plain/cli/utils.py,sha256=CuEAN41fmxr0KS_Rek_9zWri6frh8MkJUrmN-JVAfEo,292
50
+ plain/csrf/README.md,sha256=plU22iqc2XOxrZ1ipRlrZf6evIxGZBluudRGJqUJzic,5130
51
+ plain/csrf/middleware.py,sha256=A4Ko6K0rBYo3IdizRuZZgoALqOIsbTbdd5QkFOi5KiA,6038
52
+ plain/forms/README.md,sha256=IDRkWVxjipTvhhOW6fnVeNj-MJ7CKGZ-P71QfE0mlqE,13445
53
+ plain/forms/__init__.py,sha256=gZrZY-_se_8PRgu3MwkSaYQ3S9YC-neZugMI0U1VvIg,1075
54
+ plain/forms/boundfield.py,sha256=Wkzo_4w_G4d372oxCGGDhwhggaA7boPmoHbRgDhOjNA,1991
55
+ plain/forms/exceptions.py,sha256=MuMUF-33Qsc_HWk5zI3rcWzdvXzQbEOKAZvJKkbrL58,320
56
+ plain/forms/fields.py,sha256=RE3erdMUyN5wPPT7HXuQ9UJNfi5uT-Od1r1GTzefBz0,42313
57
+ plain/forms/forms.py,sha256=ZMTCpZUzq3kwP7L-RkStfuw9FqkH90a6DsH_8n4AXfU,11976
58
+ plain/http/README.md,sha256=PFLkg8aljvdzZeXYiG9zxIZzE4_DwqjFHpgC_eu_XYA,9428
59
+ plain/http/__init__.py,sha256=ny0Rr4coLrQG7OvV1J2yLI1HlgvwJBboWiXlBvkIaP4,1429
60
+ plain/http/cookie.py,sha256=x13G3LIr0jxnPK1NQRptmi0DrAq9PsivQnQTm4LKaW0,2191
61
+ plain/http/exceptions.py,sha256=XJUckCrDhrTCAacmH2Qt-1xr0UZ1-LgpEYizyeL5kpQ,1446
62
+ plain/http/middleware.py,sha256=TPs585IIFjgp-5uUAJtIoigH6uwTS3FJqwFSsQdayd4,960
63
+ plain/http/multipartparser.py,sha256=Jm7dQzIL8ulYRh1MBjmQuUWF6KBhgZBLAVBBBZOTeaA,28133
64
+ plain/http/request.py,sha256=8HFHUw1UMEoXwkVY1rxqekZuq0mQ4HWbbkri6uZwxaY,28584
65
+ plain/http/response.py,sha256=b1jFpOtVmJMFI3oQ9NMGXK45BAsxz_r6udM8tsbSQI0,22079
66
+ plain/internal/__init__.py,sha256=n2AgdfNelt_tp8CS9JDzHMy_aiTUMPGZiFFwKmNz2fg,262
67
+ plain/internal/reloader.py,sha256=n7B-F-WeUXp37pAnvzKX9tcEbUxHSlYqa4gItyA_zko,2662
68
+ plain/internal/files/__init__.py,sha256=VctFgox4Q1AWF3klPaoCC5GIw5KeLafYjY5JmN8mAVw,63
69
+ plain/internal/files/base.py,sha256=fp8lo-4ec3RofJCClIkR3yZE-zyW_gDy9NeWTlV3ID8,4713
70
+ plain/internal/files/locks.py,sha256=jvLL9kroOo50kUo8dbuajDiFvgSL5NH6x5hudRPPjiQ,4022
71
+ plain/internal/files/move.py,sha256=qE1nAVdJO8PJyxcZyoPORFYNPLf2EEt_dyOyk7cg9HE,3338
72
+ plain/internal/files/temp.py,sha256=Y7Kb4j26NqZQuNqWMe91d3KJ63DP289prM31iRyAN3w,2969
73
+ plain/internal/files/uploadedfile.py,sha256=LohB2bBjfh3BwN9qCo_JtdnZe3knUBtT_vHAMlXErWE,4940
74
+ plain/internal/files/uploadhandler.py,sha256=bOTa-no1e2Q1ZCMSywhYaH7II8uOj9lQUB9j9FgGky8,7537
75
+ plain/internal/files/utils.py,sha256=ERcjMX5Qalpt9EKOonkfOc2Lw8fGgpu2eXLOCAd9aiY,3003
76
+ plain/internal/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
+ plain/internal/handlers/base.py,sha256=9H4bWTq0Q-X_RXOHOYgR5Vgmw2RstBZ-sUXxbeJK6Js,6541
78
+ plain/internal/handlers/exception.py,sha256=oGA5Gg1GHc0eTOxOMayKlQOMytuACWcWWmZRstHG7qw,5070
79
+ plain/internal/handlers/wsgi.py,sha256=UAeny1rKCCClPZKE6JGemEEFKKbfeMBKd4RzHyS0DZs,8950
80
+ plain/internal/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
+ plain/internal/middleware/headers.py,sha256=6ortXSnoZ8c7d-s2pWdajLaVIjQ3Glw9HMrFObXG7Kk,2002
82
+ plain/internal/middleware/hosts.py,sha256=ZGG4TVSVLv7LBm5ckZEvfqeBFRAANbP0F9d3SYmUoO8,5890
83
+ plain/internal/middleware/https.py,sha256=-R1Aa9Auai3GyhQHUggaUQI_TuMR_35J9u3xE9HX2xQ,1117
84
+ plain/internal/middleware/slash.py,sha256=6Srrmyaleu8PIRbHeodCmc8oa51gDDdX_OwPz5H1DEQ,3073
85
+ plain/logs/README.md,sha256=dw8Za2nOaqV116D2LMIzJUpGYnn5nW67rXzYYxq0h1Q,7195
86
+ plain/logs/__init__.py,sha256=QzurbfNXbx04QjZBsYxnIc3pwD9uN_mnuDArksEDsw8,54
87
+ plain/logs/app.py,sha256=T5ofCgMPpE3glf1GluZ0xu3yzzgGTm-r9kVggM-jITc,5605
88
+ plain/logs/configure.py,sha256=nz1bAbg4XDyJWpbdsvTKTJ6geUeTex4z__PP-GzWRN8,2717
89
+ plain/logs/debug.py,sha256=vz6GYy5QJTBLYHyIsYvPq5BOziPJSzo0pxqTid4beDs,1372
90
+ plain/logs/filters.py,sha256=7e90DSsqDg6BibywiEnh1x-R9JqcOkdQuMF2uQyurEs,457
91
+ plain/logs/formatters.py,sha256=8EULZzGY7xUXM4X9ZOwG3QURwkVlHpjMkL6UEBt9Gsg,2529
92
+ plain/packages/README.md,sha256=ZbEKfx_zwB0ihvQGe6GrjpDvVfqvHfzxSoTZEdiJXfM,4852
93
+ plain/packages/__init__.py,sha256=OpQny0xLplPdPpozVUUkrW2gB-IIYyDT1b4zMzOcCC4,160
94
+ plain/packages/config.py,sha256=0c53dWlaTHNPdIOHP7-TWwz0xcIpLQBY8d7NOWNasBs,3167
95
+ plain/packages/registry.py,sha256=PIPmFFrTnk8O0tdMZAy4Ogk61IeSxd-TIohWxmjRsU4,9036
96
+ plain/preflight/README.md,sha256=-Z2pMt3en6H-92HFw6uHNYN4hypSv77ww93GfttNhvM,7521
97
+ plain/preflight/__init__.py,sha256=6jDCcm5QosjKne_HKcTZdUv5X_jk_NCYxrzupJEsoTE,467
98
+ plain/preflight/checks.py,sha256=kJcr-Hq5bsjKw1BUYR6r6nFg3Ecgrd1DS5SudUr4rSU,289
99
+ plain/preflight/files.py,sha256=OjD76e-l_cDXJGHMk21LsoPp6V_HxD5v0zyvOKkEDu0,840
100
+ plain/preflight/registry.py,sha256=87FvZRsbbbuFJGKJ2PC5-o9aZ1MIKgLPYOTR3ghlzK4,2920
101
+ plain/preflight/results.py,sha256=V2LeyZeL8tM4ixnGZjsHuI7kYCIbaoaWHP_t0BjH5yA,1121
102
+ plain/preflight/security.py,sha256=mTVRACvAmGuK9LvPSFuuW0XHdhRRREmLzfkDcoh6snE,3067
103
+ plain/preflight/settings.py,sha256=Qr_aOdxDh6_XL6HwAKflDICdeawmd3oGcGU2wmESELo,2083
104
+ plain/preflight/urls.py,sha256=Asw_vq-70NRqr15yuBAYL0JCZ04liumORYT3I3KmF_k,437
105
+ plain/runtime/README.md,sha256=AXj420OhD7ecdoVQ4I-7XY7VzlXY35_wBNm4yoN4Prk,5989
106
+ plain/runtime/__init__.py,sha256=qwZuhx4hpJsW9YkgtGzumm0YXY-dK29X0V_p9ugJags,2605
107
+ plain/runtime/global_settings.py,sha256=3b4Sy6vC4Z4Ho75zT-fBHOHv9DTOu0BfioLH2l5j36U,6457
108
+ plain/runtime/secret.py,sha256=UTlKNhM4ut_IHtQ8T0eXSvhrI-O7r_X20fEdpSIb3EI,435
109
+ plain/runtime/user_settings.py,sha256=sHfPlCgMDKDcqgaHLwOu9_M8h0uVTXLLbmkAdJpTHbs,14054
110
+ plain/runtime/utils.py,sha256=sHOv9SWCalBtg32GtZofimM2XaQtf_jAyyf6RQuOlGc,851
111
+ plain/server/LICENSE,sha256=Xt_dw4qYwQI9qSi2u8yMZeb4HuMRp5tESRKhtvvJBgA,1707
112
+ plain/server/README.md,sha256=HQEIDN8zJ10tYXopKrFTjTXGPsIJNFL75BLGbz55suU,5622
113
+ plain/server/__init__.py,sha256=DtRgEcr4IxF4mrtCHloIprk_Q4k1oju4F2VHoyvu4ow,212
114
+ plain/server/app.py,sha256=ozaqdb-a_T3ps7T5EJwIPM63F_497J4o7kw9Pbq7Ga0,1229
115
+ plain/server/arbiter.py,sha256=ChlA_qQPT8sAVsOkD5f_d18Q8cpoR-uSoa92iBxolG4,17461
116
+ plain/server/config.py,sha256=-T1w8dbUwwLd898P1HNufe8Kw8VJDYJaeKY-UuKzvTo,3221
117
+ plain/server/errors.py,sha256=sKl_OJ5Uw-a_r_dZ2o4I8JaKeTrjvY_LR12F6B_p4-g,956
118
+ plain/server/glogging.py,sha256=9FS-kRPja3BfNAdjyCghoS2st8VXHUc1--4dvQ3Ph18,9662
119
+ plain/server/pidfile.py,sha256=3BrA5DgFqAp3jWQdZjRdnVS4vdTytg209jHN4JCGDNI,2548
120
+ plain/server/sock.py,sha256=PXmlVNTv4lJR8Rfe0t7VeXqm0MtZgudNM_84NkwsR58,7098
121
+ plain/server/util.py,sha256=BetOuZ344wXu6VwgbRDcp1SIWBSCDY1L8m38L_y7eU4,8873
122
+ plain/server/http/__init__.py,sha256=kQwTk1l3hYJwVrzr1p-XNAbWYe0icsD7l0ZyGRXMbOI,300
123
+ plain/server/http/body.py,sha256=fxl9qV8uIYxwXbsJxcmsVX2aRfIb5jpSJxKm9Ug69B0,8352
124
+ plain/server/http/errors.py,sha256=hoy9NnXLWWbYJ4DkMnM0lcFEqaggI-KfCVgaAYIB7Dw,3827
125
+ plain/server/http/message.py,sha256=9bntA2glNchIJUK6O4NNNCCkrtr2xsDUqldvPTnP0gk,15135
126
+ plain/server/http/parser.py,sha256=8z1WZNsEvKh7qSePy3YkSSsRySGj8uz7Hz1N10bk2DQ,1803
127
+ plain/server/http/unreader.py,sha256=jD2PGZ574FGmQOZlqWfs1VWzp-ttfIso_GzYaId6KYQ,2238
128
+ plain/server/http/wsgi.py,sha256=ERn8JQeoFIA9g7Os1gPIRg32p_CYqtcipYvnIx9U1is,13923
129
+ plain/server/workers/__init__.py,sha256=sLq8nrIIf9Wjw5_qQsh6cnHnY7eIqCpzSedKrNmmn7s,145
130
+ plain/server/workers/base.py,sha256=aeZIfZ2pBjlnYdVuCIBsDUXGAg-tJ1vIQQTfYwQk0sQ,9721
131
+ plain/server/workers/sync.py,sha256=xzE-t-6LW1DtGmwhIRVBgTnc3SwBe1CgL81MJQ7WFnY,7292
132
+ plain/server/workers/thread.py,sha256=xx0lq0JKI4UPn4RUjTETdOEOyWWVR_X0n34JIY82yek,13860
133
+ plain/server/workers/workertmp.py,sha256=egGReVvldlOBQfQGcpLpjt0zvPwR4C_N-UJKG-U_6w4,1299
134
+ plain/signals/README.md,sha256=5HR6of7tgwBiZQqIdZMrwgiMjUH9qjY8inl-tSmR8Lo,5500
135
+ plain/signals/__init__.py,sha256=VDhotllLUQVg3eA1LuAJM9pwGTaf_bzRGzc4mJhd-sY,98
136
+ plain/signals/dispatch/__init__.py,sha256=FzEygqV9HsM6gopio7O2Oh_X230nA4d5Q9s0sUjMq0E,292
137
+ plain/signals/dispatch/dispatcher.py,sha256=ofpu8wMEx8O-I9SO9Rkk1ABH09-71QnmGZlt8vsZAw8,12317
138
+ plain/signals/dispatch/license.txt,sha256=o9EhDhsC4Q5HbmD-IfNGVTEkXtNE33r5rIt3lleJ8gc,1727
139
+ plain/templates/README.md,sha256=zhAayenfxtwq6r1-NjoaD-wjaazyhm76BCacGF24ACc,8803
140
+ plain/templates/__init__.py,sha256=bX76FakE9T7mfK3N0deN85HlwHNQpeigytSC9Z8LcOs,451
141
+ plain/templates/core.py,sha256=mbcH0yTeFOI3XOg9dYSroXRIcdv9sETEy4HzY-ugwco,1258
142
+ plain/templates/jinja/__init__.py,sha256=7odJxZfn1p2zhzuuehdcLPIZm8jZDXMYAKnu-fpt2r8,2199
143
+ plain/templates/jinja/environments.py,sha256=vadQQ7UhZOJPbeyI3ErKYE4_IwpWyABSDYS_aq5PA10,2141
144
+ plain/templates/jinja/extensions.py,sha256=qFSNUoV1rxdmKORvQhu698KxbSayltcnaGVbA9YMviE,1566
145
+ plain/templates/jinja/filters.py,sha256=g70cw1jzvYco2v-u4SeceOWBX_qxHI5k9AODMn8ewsY,1590
146
+ plain/templates/jinja/globals.py,sha256=2UwXclm0q_VILYvkQzTmoFaMS30FVZPmk9QZ8wKB7QA,572
147
+ plain/test/README.md,sha256=0XEKluidRikGk2V3wQJgvVChxRh4eXOeUSklTQS-msc,5844
148
+ plain/test/__init__.py,sha256=MhNHtp7MYBl9kq-pMRGY11kJ6kU1I6vOkjNkit1TYRg,94
149
+ plain/test/client.py,sha256=79M2aUYCqcegIW_DazP1Vx-TczzNC-pnMFPHZEvyNRU,30853
150
+ plain/test/encoding.py,sha256=txj_FCbC4GxH-JCkopW5LaZz8cGsrKQiculjFkjkzuY,3372
151
+ plain/test/exceptions.py,sha256=VHet3oylks4JdWWAiVUrhPMdDr1Dx0uuQE6irXS-e-U,392
152
+ plain/urls/README.md,sha256=O-3CFXJnliMJ5bUmvq75Tbi1_Yx2vdbVMgEIVZSGPxE,6896
153
+ plain/urls/__init__.py,sha256=DFO2OL1IllHW5USPIb5uYvvzf_G-Bl0Qu1zrRLHmWyM,542
154
+ plain/urls/converters.py,sha256=7_1eiQcmhLf57O3AioC279_BrclSqi1ZNESXOPed7Y4,1410
155
+ plain/urls/exceptions.py,sha256=eRLlvH11txmTUlBITXtJ_FDz0W7Cn0Ikmciuoj6flF4,132
156
+ plain/urls/patterns.py,sha256=CSuJEOCOjBwIFeJ2BTZbb2Fw-pgbFnWOj8dsszwH4KE,9036
157
+ plain/urls/resolvers.py,sha256=XhURKhoeZ562htjv1TF6Ja5Zh5JSggn05teB_-1S3uI,13280
158
+ plain/urls/routers.py,sha256=B1fX-FQTxmr-b_YeHQUMPuilLVqOi6-RqgPgu6MtFDY,2831
159
+ plain/urls/utils.py,sha256=VP91p0ilpWUimQSfiILUKjBnWolKXgYvgZD9X374R1g,1326
160
+ plain/utils/README.md,sha256=MyNOfltVv24C5m13dEFnKEV3Adq7uwjYoHv3RGEsLxw,7080
161
+ plain/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
162
+ plain/utils/cache.py,sha256=e5bfuEqNfna3dZTE9ZDGKwQKtbRJFRSJeEXXKq2LbGo,5642
163
+ plain/utils/crypto.py,sha256=JU8ZrCz8gKusmBoPvbZEg9W9Am3K6GAfI_uijvg7u3c,2819
164
+ plain/utils/datastructures.py,sha256=cZGG7181A2BakVfBma89Pek2VbrSyYpBEh52Ws19x6I,11937
165
+ plain/utils/dateparse.py,sha256=Z330Erc5FEXkes5ndI6drkvL-2sDf4z6-3SUR_WrfQk,5569
166
+ plain/utils/deconstruct.py,sha256=FnNtkOoEPtMYOWhveIJkVlCbKeZX7snutJJpK2sMa04,2164
167
+ plain/utils/decorators.py,sha256=IoJ-oH3JtJaLH8ndeP6uU06BbkOAzMmtUdabG9FkIdw,455
168
+ plain/utils/dotenv.py,sha256=3ZW4S7u-GId1XHSwcLinp6Mqs5od4qXe3K0N2KexDNE,11417
169
+ plain/utils/duration.py,sha256=QBFh-iLvRXuw0N0UUdFqabeJvZVqkgFME_c2gRSLwhI,1340
170
+ plain/utils/encoding.py,sha256=40siECldXUFyZ_IkyQCeOkyQuXb7i4Rpys3nFWFHnuc,4482
171
+ plain/utils/functional.py,sha256=ujpUXAeo1M4sTTBMouJZi2dq3N8SflNzmD6ER7Vh3z4,14833
172
+ plain/utils/hashable.py,sha256=1Kh_SFxsaR2d3xQMNEtHb4eKSHugtNt66iZ3ZvKjt50,811
173
+ plain/utils/html.py,sha256=pjmEuhoVt_nikyjzwfJNFfnZaKeC9xe1vHZwsX-fHGs,4255
174
+ plain/utils/http.py,sha256=SlcrPXgCUGlZirceKWE3uCO2Z32p6v2k-CBncAd7HS4,5847
175
+ plain/utils/inspect.py,sha256=3RyZG4J9A16Lx3eDcn5vyRiTowixMID4cI5QJu8sSDc,1478
176
+ plain/utils/ipv6.py,sha256=TLOQVN0aqKGM4eS_HwarTgO-bGIql-k5AipDPgtQdCA,1352
177
+ plain/utils/itercompat.py,sha256=2v8UaAMgRCju_ZUH3CKg6Q4XYUaQPqZ7_cRKSgUYs8w,258
178
+ plain/utils/module_loading.py,sha256=JRltNNz7XoQpaMdd7RYHuEWVN2ycsrLJ4Yw7XTp97uQ,1776
179
+ plain/utils/regex_helper.py,sha256=zoPaCHo8EfT_Y7EgXknv9dpXkSaQTKSfC6iT-E0H7kA,13390
180
+ plain/utils/safestring.py,sha256=6YR1yimQVmSsk8tNiYxKuhsSIiOP2PFV1jSKc6--x_c,2131
181
+ plain/utils/text.py,sha256=cuACozKwAeR3x5POAOvNt3KYwn3jSbJJYnEVR-OFmuY,10169
182
+ plain/utils/timesince.py,sha256=a_-ZoPK_s3Pt998CW4rWp0clZ1XyK2x04hCqak2giII,5928
183
+ plain/utils/timezone.py,sha256=2q9ayPO_991CHlyYFHcRMngbWskYSieGVni6FnKXrSw,6877
184
+ plain/utils/tree.py,sha256=FbgAJ_eH4WsBEDWrxuo1SHXaMCBgZ3M_SH5eL4QKAow,4858
185
+ plain/views/README.md,sha256=7C3tn3zvbwT38x1dRWpspTQcUbtUrejFFhqB4xp83U4,9944
186
+ plain/views/__init__.py,sha256=M-R2AgHnlGk75KdL4ZN_Cz2HuSOg2mV8gGnPhhvhsYA,371
187
+ plain/views/base.py,sha256=pWm_P0Dlbk7uZ1pyOOI7r0T4ojAAs_pnCBRkkYdZaP0,4487
188
+ plain/views/errors.py,sha256=3Vvl_Xg7fnVQMPazHup8s5Cu4CneLa05qycWccY9HoU,1501
189
+ plain/views/exceptions.py,sha256=-YKH1Jd9Zm_yXiz797PVjJB6VWaPCTXClHIUkG2fq78,198
190
+ plain/views/forms.py,sha256=j5WAMLeW1efU9M0q1W6SFHMDMBZfkFfW4WRLKwrXjqk,2435
191
+ plain/views/objects.py,sha256=qKK8lYQKK7DTBPrMUebZ2HevgU9MmyWeXFWG1lT5ZbM,5393
192
+ plain/views/redirect.py,sha256=rt5RF1Rs2yYFLole3Mznu5iU9aeumu49VAaX4WCd_xk,2139
193
+ plain/views/templates.py,sha256=ElyqgpbkoJt72yU1gF7b626TB3R8viDAf-LYloulUBA,1925
194
+ plain-0.103.0.dist-info/METADATA,sha256=SQquYimnpZsPSt2gxBB7kw08UrCwGUgqYq0btTNOP34,4550
195
+ plain-0.103.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
196
+ plain-0.103.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
197
+ plain-0.103.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
198
+ plain-0.103.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ plain = plain.cli.core:cli
plain/AGENTS.md DELETED
@@ -1,18 +0,0 @@
1
- # Plain AGENTS.md
2
-
3
- Plain is a Python web framework that was originally forked from Django. While it still has a lot in common with Django, there are also significant changes -- don't solely rely on knowledge of Django when working with Plain.
4
-
5
- ## Commands
6
-
7
- The `plain` CLI is the main entrypoint for the framework. If `plain` is not available by itself, try `uv run plain`.
8
-
9
- - `plain shell -c <command>`: Run a Python command with Plain configured.
10
- - `plain run <filename>`: Run a Python script with Plain configured.
11
- - `plain agent docs <package>`: Show README.md and symbolicated source files for a specific package.
12
- - `plain agent docs --list`: List packages with docs available.
13
- - `plain agent request <path> --user <user_id>`: Make an authenticated request to the application and inspect the output.
14
- - `plain --help`: List all available commands (including those from installed packages).
15
-
16
- ## Code style
17
-
18
- - Imports should be at the top of the file, unless there is a specific reason to import later (e.g. to avoid circular imports).
@@ -1,20 +0,0 @@
1
- import click
2
-
3
- from .docs import docs
4
- from .md import md
5
- from .request import request
6
-
7
-
8
- @click.group("agent", invoke_without_command=True)
9
- @click.pass_context
10
- def agent(ctx):
11
- """Tools for coding agents."""
12
- if ctx.invoked_subcommand is None:
13
- # If no subcommand provided, show all AGENTS.md files
14
- ctx.invoke(md, show_all=True, show_list=False, package="")
15
-
16
-
17
- # Add commands to the group
18
- agent.add_command(docs)
19
- agent.add_command(md)
20
- agent.add_command(request)