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/exceptions.py ADDED
@@ -0,0 +1,242 @@
1
+ """
2
+ Global Plain exception and warning classes.
3
+ """
4
+ import operator
5
+
6
+ from plain.utils.hashable import make_hashable
7
+
8
+
9
+ class FieldDoesNotExist(Exception):
10
+ """The requested model field does not exist"""
11
+
12
+ pass
13
+
14
+
15
+ class PackageRegistryNotReady(Exception):
16
+ """The plain.packages registry is not populated yet"""
17
+
18
+ pass
19
+
20
+
21
+ class ObjectDoesNotExist(Exception):
22
+ """The requested object does not exist"""
23
+
24
+ silent_variable_failure = True
25
+
26
+
27
+ class MultipleObjectsReturned(Exception):
28
+ """The query returned multiple objects when only one was expected."""
29
+
30
+ pass
31
+
32
+
33
+ class SuspiciousOperation(Exception):
34
+ """The user did something suspicious"""
35
+
36
+
37
+ class SuspiciousMultipartForm(SuspiciousOperation):
38
+ """Suspect MIME request in multipart form data"""
39
+
40
+ pass
41
+
42
+
43
+ class SuspiciousFileOperation(SuspiciousOperation):
44
+ """A Suspicious filesystem operation was attempted"""
45
+
46
+ pass
47
+
48
+
49
+ class DisallowedHost(SuspiciousOperation):
50
+ """HTTP_HOST header contains invalid value"""
51
+
52
+ pass
53
+
54
+
55
+ class DisallowedRedirect(SuspiciousOperation):
56
+ """Redirect to scheme not in allowed list"""
57
+
58
+ pass
59
+
60
+
61
+ class TooManyFieldsSent(SuspiciousOperation):
62
+ """
63
+ The number of fields in a GET or POST request exceeded
64
+ settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.
65
+ """
66
+
67
+ pass
68
+
69
+
70
+ class TooManyFilesSent(SuspiciousOperation):
71
+ """
72
+ The number of fields in a GET or POST request exceeded
73
+ settings.DATA_UPLOAD_MAX_NUMBER_FILES.
74
+ """
75
+
76
+ pass
77
+
78
+
79
+ class RequestDataTooBig(SuspiciousOperation):
80
+ """
81
+ The size of the request (excluding any file uploads) exceeded
82
+ settings.DATA_UPLOAD_MAX_MEMORY_SIZE.
83
+ """
84
+
85
+ pass
86
+
87
+
88
+ class RequestAborted(Exception):
89
+ """The request was closed before it was completed, or timed out."""
90
+
91
+ pass
92
+
93
+
94
+ class BadRequest(Exception):
95
+ """The request is malformed and cannot be processed."""
96
+
97
+ pass
98
+
99
+
100
+ class PermissionDenied(Exception):
101
+ """The user did not have permission to do that"""
102
+
103
+ pass
104
+
105
+
106
+ class ViewDoesNotExist(Exception):
107
+ """The requested view does not exist"""
108
+
109
+ pass
110
+
111
+
112
+ class ImproperlyConfigured(Exception):
113
+ """Plain is somehow improperly configured"""
114
+
115
+ pass
116
+
117
+
118
+ class FieldError(Exception):
119
+ """Some kind of problem with a model field."""
120
+
121
+ pass
122
+
123
+
124
+ NON_FIELD_ERRORS = "__all__"
125
+
126
+
127
+ class ValidationError(Exception):
128
+ """An error while validating data."""
129
+
130
+ def __init__(self, message, code=None, params=None):
131
+ """
132
+ The `message` argument can be a single error, a list of errors, or a
133
+ dictionary that maps field names to lists of errors. What we define as
134
+ an "error" can be either a simple string or an instance of
135
+ ValidationError with its message attribute set, and what we define as
136
+ list or dictionary can be an actual `list` or `dict` or an instance
137
+ of ValidationError with its `error_list` or `error_dict` attribute set.
138
+ """
139
+ super().__init__(message, code, params)
140
+
141
+ if isinstance(message, ValidationError):
142
+ if hasattr(message, "error_dict"):
143
+ message = message.error_dict
144
+ elif not hasattr(message, "message"):
145
+ message = message.error_list
146
+ else:
147
+ message, code, params = message.message, message.code, message.params
148
+
149
+ if isinstance(message, dict):
150
+ self.error_dict = {}
151
+ for field, messages in message.items():
152
+ if not isinstance(messages, ValidationError):
153
+ messages = ValidationError(messages)
154
+ self.error_dict[field] = messages.error_list
155
+
156
+ elif isinstance(message, list):
157
+ self.error_list = []
158
+ for message in message:
159
+ # Normalize plain strings to instances of ValidationError.
160
+ if not isinstance(message, ValidationError):
161
+ message = ValidationError(message)
162
+ if hasattr(message, "error_dict"):
163
+ self.error_list.extend(sum(message.error_dict.values(), []))
164
+ else:
165
+ self.error_list.extend(message.error_list)
166
+
167
+ else:
168
+ self.message = message
169
+ self.code = code
170
+ self.params = params
171
+ self.error_list = [self]
172
+
173
+ @property
174
+ def message_dict(self):
175
+ # Trigger an AttributeError if this ValidationError
176
+ # doesn't have an error_dict.
177
+ getattr(self, "error_dict")
178
+
179
+ return dict(self)
180
+
181
+ @property
182
+ def messages(self):
183
+ if hasattr(self, "error_dict"):
184
+ return sum(dict(self).values(), [])
185
+ return list(self)
186
+
187
+ def update_error_dict(self, error_dict):
188
+ if hasattr(self, "error_dict"):
189
+ for field, error_list in self.error_dict.items():
190
+ error_dict.setdefault(field, []).extend(error_list)
191
+ else:
192
+ error_dict.setdefault(NON_FIELD_ERRORS, []).extend(self.error_list)
193
+ return error_dict
194
+
195
+ def __iter__(self):
196
+ if hasattr(self, "error_dict"):
197
+ for field, errors in self.error_dict.items():
198
+ yield field, list(ValidationError(errors))
199
+ else:
200
+ for error in self.error_list:
201
+ message = error.message
202
+ if error.params:
203
+ message %= error.params
204
+ yield str(message)
205
+
206
+ def __str__(self):
207
+ if hasattr(self, "error_dict"):
208
+ return repr(dict(self))
209
+ return repr(list(self))
210
+
211
+ def __repr__(self):
212
+ return "ValidationError(%s)" % self
213
+
214
+ def __eq__(self, other):
215
+ if not isinstance(other, ValidationError):
216
+ return NotImplemented
217
+ return hash(self) == hash(other)
218
+
219
+ def __hash__(self):
220
+ if hasattr(self, "message"):
221
+ return hash(
222
+ (
223
+ self.message,
224
+ self.code,
225
+ make_hashable(self.params),
226
+ )
227
+ )
228
+ if hasattr(self, "error_dict"):
229
+ return hash(make_hashable(self.error_dict))
230
+ return hash(tuple(sorted(self.error_list, key=operator.attrgetter("message"))))
231
+
232
+
233
+ class EmptyResultSet(Exception):
234
+ """A database query predicate is impossible."""
235
+
236
+ pass
237
+
238
+
239
+ class FullResultSet(Exception):
240
+ """A database query predicate is matches everything."""
241
+
242
+ pass
plain/forms/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # Forms
2
+
3
+ Handle user input.
4
+
5
+ - forms don't render themselves
6
+ - registered scripts vs media?
7
+ - move model save logic into form.save() - disconnect them
8
+ - Form: validates input from request, saves it
9
+ - Form field: validates field, knows how to parse from html data ?
10
+ - starter includes form components - no markup in python
11
+
12
+ ## Re-using input styles
13
+
14
+ plain-elements, or includes
@@ -0,0 +1,8 @@
1
+ """
2
+ Plain validation and HTML form handling.
3
+ """
4
+
5
+ from .boundfield import BoundField # NOQA
6
+ from .exceptions import FormFieldMissingError, ValidationError # NOQA
7
+ from .fields import * # NOQA
8
+ from .forms import Form # NOQA
@@ -0,0 +1,58 @@
1
+ from functools import cached_property
2
+
3
+ __all__ = ("BoundField",)
4
+
5
+
6
+ class BoundField:
7
+ "A Field plus data"
8
+
9
+ def __init__(self, form, field, name):
10
+ self._form = form
11
+ self.field = field
12
+ self.name = name
13
+ self.html_name = form.add_prefix(name)
14
+ self.html_id = form.add_prefix(self._auto_id)
15
+
16
+ def __repr__(self):
17
+ return f'<{self.__class__.__name__} "{self.html_name}">'
18
+
19
+ @property
20
+ def errors(self):
21
+ """
22
+ Return an error list (empty if there are no errors) for this field.
23
+ """
24
+ return self._form.errors.get(self.name, [])
25
+
26
+ def value(self):
27
+ """
28
+ Return the value for this BoundField, using the initial value if
29
+ the form is not bound or the data otherwise.
30
+ """
31
+ data = self.initial
32
+ if self._form.is_bound:
33
+ data = self.field.bound_data(
34
+ self._form._field_data_value(self.field, self.html_name), data
35
+ )
36
+ return self.field.prepare_value(data)
37
+
38
+ @cached_property
39
+ def initial(self):
40
+ return self._form.get_initial_for_field(self.field, self.name)
41
+
42
+ def _has_changed(self):
43
+ return self.field.has_changed(
44
+ self.initial, self._form._field_data_value(self.field, self.html_name)
45
+ )
46
+
47
+ @property
48
+ def _auto_id(self):
49
+ """
50
+ Calculate and return the ID attribute for this BoundField, if the
51
+ associated Form has specified auto_id. Return an empty string otherwise.
52
+ """
53
+ auto_id = self._form._auto_id # Boolean or string
54
+ if auto_id and "%s" in str(auto_id):
55
+ return auto_id % self.html_name
56
+ elif auto_id:
57
+ return self.html_name
58
+ return ""
@@ -0,0 +1,11 @@
1
+ from plain.exceptions import ValidationError
2
+
3
+
4
+ class FormFieldMissingError(Exception):
5
+ pass
6
+
7
+
8
+ __all__ = [
9
+ "ValidationError",
10
+ "FormFieldMissingError",
11
+ ]