plain 0.1.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 (169) hide show
  1. plain/README.md +33 -0
  2. plain/__main__.py +5 -0
  3. plain/assets/README.md +56 -0
  4. plain/assets/__init__.py +6 -0
  5. plain/assets/finders.py +233 -0
  6. plain/assets/preflight.py +14 -0
  7. plain/assets/storage.py +916 -0
  8. plain/assets/utils.py +52 -0
  9. plain/assets/whitenoise/__init__.py +5 -0
  10. plain/assets/whitenoise/base.py +259 -0
  11. plain/assets/whitenoise/compress.py +189 -0
  12. plain/assets/whitenoise/media_types.py +137 -0
  13. plain/assets/whitenoise/middleware.py +197 -0
  14. plain/assets/whitenoise/responders.py +286 -0
  15. plain/assets/whitenoise/storage.py +178 -0
  16. plain/assets/whitenoise/string_utils.py +13 -0
  17. plain/cli/README.md +123 -0
  18. plain/cli/__init__.py +3 -0
  19. plain/cli/cli.py +439 -0
  20. plain/cli/formatting.py +61 -0
  21. plain/cli/packages.py +73 -0
  22. plain/cli/print.py +9 -0
  23. plain/cli/startup.py +33 -0
  24. plain/csrf/README.md +3 -0
  25. plain/csrf/middleware.py +466 -0
  26. plain/csrf/views.py +10 -0
  27. plain/debug.py +23 -0
  28. plain/exceptions.py +242 -0
  29. plain/forms/README.md +14 -0
  30. plain/forms/__init__.py +8 -0
  31. plain/forms/boundfield.py +58 -0
  32. plain/forms/exceptions.py +11 -0
  33. plain/forms/fields.py +1030 -0
  34. plain/forms/forms.py +297 -0
  35. plain/http/README.md +1 -0
  36. plain/http/__init__.py +51 -0
  37. plain/http/cookie.py +20 -0
  38. plain/http/multipartparser.py +743 -0
  39. plain/http/request.py +754 -0
  40. plain/http/response.py +719 -0
  41. plain/internal/__init__.py +0 -0
  42. plain/internal/files/README.md +3 -0
  43. plain/internal/files/__init__.py +3 -0
  44. plain/internal/files/base.py +161 -0
  45. plain/internal/files/locks.py +127 -0
  46. plain/internal/files/move.py +102 -0
  47. plain/internal/files/temp.py +79 -0
  48. plain/internal/files/uploadedfile.py +150 -0
  49. plain/internal/files/uploadhandler.py +254 -0
  50. plain/internal/files/utils.py +78 -0
  51. plain/internal/handlers/__init__.py +0 -0
  52. plain/internal/handlers/base.py +133 -0
  53. plain/internal/handlers/exception.py +145 -0
  54. plain/internal/handlers/wsgi.py +216 -0
  55. plain/internal/legacy/__init__.py +0 -0
  56. plain/internal/legacy/__main__.py +12 -0
  57. plain/internal/legacy/management/__init__.py +414 -0
  58. plain/internal/legacy/management/base.py +692 -0
  59. plain/internal/legacy/management/color.py +113 -0
  60. plain/internal/legacy/management/commands/__init__.py +0 -0
  61. plain/internal/legacy/management/commands/collectstatic.py +297 -0
  62. plain/internal/legacy/management/sql.py +67 -0
  63. plain/internal/legacy/management/utils.py +175 -0
  64. plain/json.py +40 -0
  65. plain/logs/README.md +24 -0
  66. plain/logs/__init__.py +5 -0
  67. plain/logs/configure.py +39 -0
  68. plain/logs/loggers.py +74 -0
  69. plain/logs/utils.py +46 -0
  70. plain/middleware/README.md +3 -0
  71. plain/middleware/__init__.py +0 -0
  72. plain/middleware/clickjacking.py +52 -0
  73. plain/middleware/common.py +87 -0
  74. plain/middleware/gzip.py +64 -0
  75. plain/middleware/security.py +64 -0
  76. plain/packages/README.md +41 -0
  77. plain/packages/__init__.py +4 -0
  78. plain/packages/config.py +259 -0
  79. plain/packages/registry.py +438 -0
  80. plain/paginator.py +187 -0
  81. plain/preflight/README.md +3 -0
  82. plain/preflight/__init__.py +38 -0
  83. plain/preflight/compatibility/__init__.py +0 -0
  84. plain/preflight/compatibility/django_4_0.py +20 -0
  85. plain/preflight/files.py +19 -0
  86. plain/preflight/messages.py +88 -0
  87. plain/preflight/registry.py +72 -0
  88. plain/preflight/security/__init__.py +0 -0
  89. plain/preflight/security/base.py +268 -0
  90. plain/preflight/security/csrf.py +40 -0
  91. plain/preflight/urls.py +117 -0
  92. plain/runtime/README.md +75 -0
  93. plain/runtime/__init__.py +61 -0
  94. plain/runtime/global_settings.py +199 -0
  95. plain/runtime/user_settings.py +353 -0
  96. plain/signals/README.md +14 -0
  97. plain/signals/__init__.py +5 -0
  98. plain/signals/dispatch/__init__.py +9 -0
  99. plain/signals/dispatch/dispatcher.py +320 -0
  100. plain/signals/dispatch/license.txt +35 -0
  101. plain/signing.py +299 -0
  102. plain/templates/README.md +20 -0
  103. plain/templates/__init__.py +6 -0
  104. plain/templates/core.py +24 -0
  105. plain/templates/jinja/README.md +227 -0
  106. plain/templates/jinja/__init__.py +22 -0
  107. plain/templates/jinja/defaults.py +119 -0
  108. plain/templates/jinja/extensions.py +39 -0
  109. plain/templates/jinja/filters.py +28 -0
  110. plain/templates/jinja/globals.py +19 -0
  111. plain/test/README.md +3 -0
  112. plain/test/__init__.py +16 -0
  113. plain/test/client.py +985 -0
  114. plain/test/utils.py +255 -0
  115. plain/urls/README.md +3 -0
  116. plain/urls/__init__.py +40 -0
  117. plain/urls/base.py +118 -0
  118. plain/urls/conf.py +94 -0
  119. plain/urls/converters.py +66 -0
  120. plain/urls/exceptions.py +9 -0
  121. plain/urls/resolvers.py +731 -0
  122. plain/utils/README.md +3 -0
  123. plain/utils/__init__.py +0 -0
  124. plain/utils/_os.py +52 -0
  125. plain/utils/cache.py +327 -0
  126. plain/utils/connection.py +84 -0
  127. plain/utils/crypto.py +76 -0
  128. plain/utils/datastructures.py +345 -0
  129. plain/utils/dateformat.py +329 -0
  130. plain/utils/dateparse.py +154 -0
  131. plain/utils/dates.py +76 -0
  132. plain/utils/deconstruct.py +54 -0
  133. plain/utils/decorators.py +90 -0
  134. plain/utils/deprecation.py +6 -0
  135. plain/utils/duration.py +44 -0
  136. plain/utils/email.py +12 -0
  137. plain/utils/encoding.py +235 -0
  138. plain/utils/functional.py +456 -0
  139. plain/utils/hashable.py +26 -0
  140. plain/utils/html.py +401 -0
  141. plain/utils/http.py +374 -0
  142. plain/utils/inspect.py +73 -0
  143. plain/utils/ipv6.py +46 -0
  144. plain/utils/itercompat.py +8 -0
  145. plain/utils/module_loading.py +69 -0
  146. plain/utils/regex_helper.py +353 -0
  147. plain/utils/safestring.py +72 -0
  148. plain/utils/termcolors.py +221 -0
  149. plain/utils/text.py +518 -0
  150. plain/utils/timesince.py +138 -0
  151. plain/utils/timezone.py +244 -0
  152. plain/utils/tree.py +126 -0
  153. plain/validators.py +603 -0
  154. plain/views/README.md +268 -0
  155. plain/views/__init__.py +18 -0
  156. plain/views/base.py +107 -0
  157. plain/views/csrf.py +24 -0
  158. plain/views/errors.py +25 -0
  159. plain/views/exceptions.py +4 -0
  160. plain/views/forms.py +76 -0
  161. plain/views/objects.py +229 -0
  162. plain/views/redirect.py +72 -0
  163. plain/views/templates.py +66 -0
  164. plain/wsgi.py +11 -0
  165. plain-0.1.0.dist-info/LICENSE +85 -0
  166. plain-0.1.0.dist-info/METADATA +51 -0
  167. plain-0.1.0.dist-info/RECORD +169 -0
  168. plain-0.1.0.dist-info/WHEEL +4 -0
  169. plain-0.1.0.dist-info/entry_points.txt +3 -0
plain/forms/forms.py ADDED
@@ -0,0 +1,297 @@
1
+ """
2
+ Form classes
3
+ """
4
+
5
+ import copy
6
+
7
+ from plain.exceptions import NON_FIELD_ERRORS
8
+ from plain.utils.datastructures import MultiValueDict
9
+ from plain.utils.functional import cached_property
10
+
11
+ from .exceptions import ValidationError
12
+ from .fields import Field, FileField
13
+
14
+ __all__ = ("BaseForm", "Form")
15
+
16
+
17
+ class DeclarativeFieldsMetaclass(type):
18
+ """Collect Fields declared on the base classes."""
19
+
20
+ def __new__(mcs, name, bases, attrs):
21
+ # Collect fields from current class and remove them from attrs.
22
+ attrs["declared_fields"] = {
23
+ key: attrs.pop(key)
24
+ for key, value in list(attrs.items())
25
+ if isinstance(value, Field)
26
+ }
27
+
28
+ new_class = super().__new__(mcs, name, bases, attrs)
29
+
30
+ # Walk through the MRO.
31
+ declared_fields = {}
32
+ for base in reversed(new_class.__mro__):
33
+ # Collect fields from base class.
34
+ if hasattr(base, "declared_fields"):
35
+ declared_fields.update(base.declared_fields)
36
+
37
+ # Field shadowing.
38
+ for attr, value in base.__dict__.items():
39
+ if value is None and attr in declared_fields:
40
+ declared_fields.pop(attr)
41
+
42
+ new_class.base_fields = declared_fields
43
+ new_class.declared_fields = declared_fields
44
+
45
+ return new_class
46
+
47
+
48
+ class BaseForm:
49
+ """
50
+ The main implementation of all the Form logic. Note that this class is
51
+ different than Form. See the comments by the Form class for more info. Any
52
+ improvements to the form API should be made to this class, not to the Form
53
+ class.
54
+ """
55
+
56
+ field_order = None
57
+ prefix = None
58
+
59
+ def __init__(
60
+ self,
61
+ data=None,
62
+ files=None,
63
+ auto_id="id_%s",
64
+ prefix=None,
65
+ initial=None,
66
+ ):
67
+ self.is_bound = data is not None or files is not None
68
+ self.data = MultiValueDict() if data is None else data
69
+ self.files = MultiValueDict() if files is None else files
70
+ self._auto_id = auto_id
71
+ if prefix is not None:
72
+ self.prefix = prefix
73
+ self.initial = initial or {}
74
+ self._errors = None # Stores the errors after clean() has been called.
75
+
76
+ # The base_fields class attribute is the *class-wide* definition of
77
+ # fields. Because a particular *instance* of the class might want to
78
+ # alter self.fields, we create self.fields here by copying base_fields.
79
+ # Instances should always modify self.fields; they should not modify
80
+ # self.base_fields.
81
+ self.fields = copy.deepcopy(self.base_fields)
82
+ self._bound_fields_cache = {}
83
+
84
+ def __repr__(self):
85
+ if self._errors is None:
86
+ is_valid = "Unknown"
87
+ else:
88
+ is_valid = self.is_bound and not self._errors
89
+ return "<{cls} bound={bound}, valid={valid}, fields=({fields})>".format(
90
+ cls=self.__class__.__name__,
91
+ bound=self.is_bound,
92
+ valid=is_valid,
93
+ fields=";".join(self.fields),
94
+ )
95
+
96
+ def _bound_items(self):
97
+ """Yield (name, bf) pairs, where bf is a BoundField object."""
98
+ for name in self.fields:
99
+ yield name, self[name]
100
+
101
+ def __iter__(self):
102
+ """Yield the form's fields as BoundField objects."""
103
+ for name in self.fields:
104
+ yield self[name]
105
+
106
+ def __getitem__(self, name):
107
+ """Return a BoundField with the given name."""
108
+ try:
109
+ field = self.fields[name]
110
+ except KeyError:
111
+ raise KeyError(
112
+ "Key '{}' not found in '{}'. Choices are: {}.".format(
113
+ name,
114
+ self.__class__.__name__,
115
+ ", ".join(sorted(self.fields)),
116
+ )
117
+ )
118
+ if name not in self._bound_fields_cache:
119
+ self._bound_fields_cache[name] = field.get_bound_field(self, name)
120
+ return self._bound_fields_cache[name]
121
+
122
+ @property
123
+ def errors(self):
124
+ """Return an error dict for the data provided for the form."""
125
+ if self._errors is None:
126
+ self.full_clean()
127
+ return self._errors
128
+
129
+ def is_valid(self):
130
+ """Return True if the form has no errors, or False otherwise."""
131
+ return self.is_bound and not self.errors
132
+
133
+ def add_prefix(self, field_name):
134
+ """
135
+ Return the field name with a prefix appended, if this Form has a
136
+ prefix set.
137
+
138
+ Subclasses may wish to override.
139
+ """
140
+ return f"{self.prefix}-{field_name}" if self.prefix else field_name
141
+
142
+ @property
143
+ def non_field_errors(self):
144
+ """
145
+ Return a list of errors that aren't associated with a particular
146
+ field -- i.e., from Form.clean(). Return an empty list if there
147
+ are none.
148
+ """
149
+ return self.errors.get(
150
+ NON_FIELD_ERRORS,
151
+ [],
152
+ )
153
+
154
+ def add_error(self, field, error):
155
+ """
156
+ Update the content of `self._errors`.
157
+
158
+ The `field` argument is the name of the field to which the errors
159
+ should be added. If it's None, treat the errors as NON_FIELD_ERRORS.
160
+
161
+ The `error` argument can be a single error, a list of errors, or a
162
+ dictionary that maps field names to lists of errors. An "error" can be
163
+ either a simple string or an instance of ValidationError with its
164
+ message attribute set and a "list or dictionary" can be an actual
165
+ `list` or `dict` or an instance of ValidationError with its
166
+ `error_list` or `error_dict` attribute set.
167
+
168
+ If `error` is a dictionary, the `field` argument *must* be None and
169
+ errors will be added to the fields that correspond to the keys of the
170
+ dictionary.
171
+ """
172
+ if not isinstance(error, ValidationError):
173
+ raise TypeError(
174
+ "The argument `error` must be an instance of "
175
+ "`ValidationError`, not `%s`." % type(error).__name__
176
+ )
177
+
178
+ if hasattr(error, "error_dict"):
179
+ if field is not None:
180
+ raise TypeError(
181
+ "The argument `field` must be `None` when the `error` "
182
+ "argument contains errors for multiple fields."
183
+ )
184
+ else:
185
+ error = error.error_dict
186
+ else:
187
+ error = {field or NON_FIELD_ERRORS: error.error_list}
188
+
189
+ class ValidationErrors(list):
190
+ def __iter__(self):
191
+ for err in super().__iter__():
192
+ # TODO make sure this works...
193
+ yield next(iter(err))
194
+
195
+ for field, error_list in error.items():
196
+ if field not in self.errors:
197
+ if field != NON_FIELD_ERRORS and field not in self.fields:
198
+ raise ValueError(
199
+ f"'{self.__class__.__name__}' has no field named '{field}'."
200
+ )
201
+ self._errors[field] = ValidationErrors()
202
+
203
+ self._errors[field].extend(error_list)
204
+
205
+ # The field had an error, so removed it from the final data
206
+ if field in self.cleaned_data:
207
+ del self.cleaned_data[field]
208
+
209
+ def full_clean(self):
210
+ """
211
+ Clean all of self.data and populate self._errors and self.cleaned_data.
212
+ """
213
+ self._errors = {}
214
+ if not self.is_bound: # Stop further processing.
215
+ return
216
+ self.cleaned_data = {}
217
+
218
+ self._clean_fields()
219
+ self._clean_form()
220
+ self._post_clean()
221
+
222
+ def _field_data_value(self, field, html_name):
223
+ if hasattr(self, "parse_%s" % html_name):
224
+ # Allow custom parsing from form data/files at the form level
225
+ return getattr(self, "parse_%s" % html_name)()
226
+
227
+ return field.value_from_form_data(self.data, self.files, html_name)
228
+
229
+ def _clean_fields(self):
230
+ for name, bf in self._bound_items():
231
+ field = bf.field
232
+
233
+ if field.disabled:
234
+ value = bf.initial
235
+ else:
236
+ value = self._field_data_value(bf.field, bf.html_name)
237
+
238
+ try:
239
+ if isinstance(field, FileField):
240
+ value = field.clean(value, bf.initial)
241
+ else:
242
+ value = field.clean(value)
243
+ self.cleaned_data[name] = value
244
+ if hasattr(self, "clean_%s" % name):
245
+ value = getattr(self, "clean_%s" % name)()
246
+ self.cleaned_data[name] = value
247
+ except ValidationError as e:
248
+ self.add_error(name, e)
249
+
250
+ def _clean_form(self):
251
+ try:
252
+ cleaned_data = self.clean()
253
+ except ValidationError as e:
254
+ self.add_error(None, e)
255
+ else:
256
+ if cleaned_data is not None:
257
+ self.cleaned_data = cleaned_data
258
+
259
+ def _post_clean(self):
260
+ """
261
+ An internal hook for performing additional cleaning after form cleaning
262
+ is complete. Used for model validation in model forms.
263
+ """
264
+ pass
265
+
266
+ def clean(self):
267
+ """
268
+ Hook for doing any extra form-wide cleaning after Field.clean() has been
269
+ called on every field. Any ValidationError raised by this method will
270
+ not be associated with a particular field; it will have a special-case
271
+ association with the field named '__all__'.
272
+ """
273
+ return self.cleaned_data
274
+
275
+ @cached_property
276
+ def changed_data(self):
277
+ return [name for name, bf in self._bound_items() if bf._has_changed()]
278
+
279
+ def get_initial_for_field(self, field, field_name):
280
+ """
281
+ Return initial data for field on form. Use initial data from the form
282
+ or the field, in that order. Evaluate callable values.
283
+ """
284
+ value = self.initial.get(field_name, field.initial)
285
+ if callable(value):
286
+ value = value()
287
+ return value
288
+
289
+
290
+ class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass):
291
+ "A collection of Fields, plus their associated data."
292
+
293
+ # This is a separate class from BaseForm in order to abstract the way
294
+ # self.fields is specified. This class (Form) is the one that does the
295
+ # fancy metaclass stuff purely for the semantic sugar -- it allows one
296
+ # to define a form using declarative syntax.
297
+ # BaseForm itself has no way of designating self.fields.
plain/http/README.md ADDED
@@ -0,0 +1 @@
1
+ # HTTP
plain/http/__init__.py ADDED
@@ -0,0 +1,51 @@
1
+ from plain.http.cookie import parse_cookie
2
+ from plain.http.request import (
3
+ HttpHeaders,
4
+ HttpRequest,
5
+ QueryDict,
6
+ RawPostDataException,
7
+ UnreadablePostError,
8
+ )
9
+ from plain.http.response import (
10
+ BadHeaderError,
11
+ FileResponse,
12
+ Http404,
13
+ JsonResponse,
14
+ Response,
15
+ ResponseBadRequest,
16
+ ResponseBase,
17
+ ResponseForbidden,
18
+ ResponseGone,
19
+ ResponseNotAllowed,
20
+ ResponseNotFound,
21
+ ResponseNotModified,
22
+ ResponsePermanentRedirect,
23
+ ResponseRedirect,
24
+ ResponseServerError,
25
+ StreamingResponse,
26
+ )
27
+
28
+ __all__ = [
29
+ "parse_cookie",
30
+ "HttpHeaders",
31
+ "HttpRequest",
32
+ "QueryDict",
33
+ "RawPostDataException",
34
+ "UnreadablePostError",
35
+ "Response",
36
+ "ResponseBase",
37
+ "StreamingResponse",
38
+ "ResponseRedirect",
39
+ "ResponsePermanentRedirect",
40
+ "ResponseNotModified",
41
+ "ResponseBadRequest",
42
+ "ResponseForbidden",
43
+ "ResponseNotFound",
44
+ "ResponseNotAllowed",
45
+ "ResponseGone",
46
+ "ResponseServerError",
47
+ "Http404",
48
+ "BadHeaderError",
49
+ "JsonResponse",
50
+ "FileResponse",
51
+ ]
plain/http/cookie.py ADDED
@@ -0,0 +1,20 @@
1
+ from http import cookies
2
+
3
+
4
+ def parse_cookie(cookie):
5
+ """
6
+ Return a dictionary parsed from a `Cookie:` header string.
7
+ """
8
+ cookiedict = {}
9
+ for chunk in cookie.split(";"):
10
+ if "=" in chunk:
11
+ key, val = chunk.split("=", 1)
12
+ else:
13
+ # Assume an empty name per
14
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=169091
15
+ key, val = "", chunk
16
+ key, val = key.strip(), val.strip()
17
+ if key or val:
18
+ # unquote using Python's algorithm.
19
+ cookiedict[key] = cookies._unquote(val)
20
+ return cookiedict