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.
- plain/README.md +33 -0
- plain/__main__.py +5 -0
- plain/assets/README.md +56 -0
- plain/assets/__init__.py +6 -0
- plain/assets/finders.py +233 -0
- plain/assets/preflight.py +14 -0
- plain/assets/storage.py +916 -0
- plain/assets/utils.py +52 -0
- plain/assets/whitenoise/__init__.py +5 -0
- plain/assets/whitenoise/base.py +259 -0
- plain/assets/whitenoise/compress.py +189 -0
- plain/assets/whitenoise/media_types.py +137 -0
- plain/assets/whitenoise/middleware.py +197 -0
- plain/assets/whitenoise/responders.py +286 -0
- plain/assets/whitenoise/storage.py +178 -0
- plain/assets/whitenoise/string_utils.py +13 -0
- plain/cli/README.md +123 -0
- plain/cli/__init__.py +3 -0
- plain/cli/cli.py +439 -0
- plain/cli/formatting.py +61 -0
- plain/cli/packages.py +73 -0
- plain/cli/print.py +9 -0
- plain/cli/startup.py +33 -0
- plain/csrf/README.md +3 -0
- plain/csrf/middleware.py +466 -0
- plain/csrf/views.py +10 -0
- plain/debug.py +23 -0
- plain/exceptions.py +242 -0
- plain/forms/README.md +14 -0
- plain/forms/__init__.py +8 -0
- plain/forms/boundfield.py +58 -0
- plain/forms/exceptions.py +11 -0
- plain/forms/fields.py +1030 -0
- plain/forms/forms.py +297 -0
- plain/http/README.md +1 -0
- plain/http/__init__.py +51 -0
- plain/http/cookie.py +20 -0
- plain/http/multipartparser.py +743 -0
- plain/http/request.py +754 -0
- plain/http/response.py +719 -0
- plain/internal/__init__.py +0 -0
- plain/internal/files/README.md +3 -0
- plain/internal/files/__init__.py +3 -0
- plain/internal/files/base.py +161 -0
- plain/internal/files/locks.py +127 -0
- plain/internal/files/move.py +102 -0
- plain/internal/files/temp.py +79 -0
- plain/internal/files/uploadedfile.py +150 -0
- plain/internal/files/uploadhandler.py +254 -0
- plain/internal/files/utils.py +78 -0
- plain/internal/handlers/__init__.py +0 -0
- plain/internal/handlers/base.py +133 -0
- plain/internal/handlers/exception.py +145 -0
- plain/internal/handlers/wsgi.py +216 -0
- plain/internal/legacy/__init__.py +0 -0
- plain/internal/legacy/__main__.py +12 -0
- plain/internal/legacy/management/__init__.py +414 -0
- plain/internal/legacy/management/base.py +692 -0
- plain/internal/legacy/management/color.py +113 -0
- plain/internal/legacy/management/commands/__init__.py +0 -0
- plain/internal/legacy/management/commands/collectstatic.py +297 -0
- plain/internal/legacy/management/sql.py +67 -0
- plain/internal/legacy/management/utils.py +175 -0
- plain/json.py +40 -0
- plain/logs/README.md +24 -0
- plain/logs/__init__.py +5 -0
- plain/logs/configure.py +39 -0
- plain/logs/loggers.py +74 -0
- plain/logs/utils.py +46 -0
- plain/middleware/README.md +3 -0
- plain/middleware/__init__.py +0 -0
- plain/middleware/clickjacking.py +52 -0
- plain/middleware/common.py +87 -0
- plain/middleware/gzip.py +64 -0
- plain/middleware/security.py +64 -0
- plain/packages/README.md +41 -0
- plain/packages/__init__.py +4 -0
- plain/packages/config.py +259 -0
- plain/packages/registry.py +438 -0
- plain/paginator.py +187 -0
- plain/preflight/README.md +3 -0
- plain/preflight/__init__.py +38 -0
- plain/preflight/compatibility/__init__.py +0 -0
- plain/preflight/compatibility/django_4_0.py +20 -0
- plain/preflight/files.py +19 -0
- plain/preflight/messages.py +88 -0
- plain/preflight/registry.py +72 -0
- plain/preflight/security/__init__.py +0 -0
- plain/preflight/security/base.py +268 -0
- plain/preflight/security/csrf.py +40 -0
- plain/preflight/urls.py +117 -0
- plain/runtime/README.md +75 -0
- plain/runtime/__init__.py +61 -0
- plain/runtime/global_settings.py +199 -0
- plain/runtime/user_settings.py +353 -0
- plain/signals/README.md +14 -0
- plain/signals/__init__.py +5 -0
- plain/signals/dispatch/__init__.py +9 -0
- plain/signals/dispatch/dispatcher.py +320 -0
- plain/signals/dispatch/license.txt +35 -0
- plain/signing.py +299 -0
- plain/templates/README.md +20 -0
- plain/templates/__init__.py +6 -0
- plain/templates/core.py +24 -0
- plain/templates/jinja/README.md +227 -0
- plain/templates/jinja/__init__.py +22 -0
- plain/templates/jinja/defaults.py +119 -0
- plain/templates/jinja/extensions.py +39 -0
- plain/templates/jinja/filters.py +28 -0
- plain/templates/jinja/globals.py +19 -0
- plain/test/README.md +3 -0
- plain/test/__init__.py +16 -0
- plain/test/client.py +985 -0
- plain/test/utils.py +255 -0
- plain/urls/README.md +3 -0
- plain/urls/__init__.py +40 -0
- plain/urls/base.py +118 -0
- plain/urls/conf.py +94 -0
- plain/urls/converters.py +66 -0
- plain/urls/exceptions.py +9 -0
- plain/urls/resolvers.py +731 -0
- plain/utils/README.md +3 -0
- plain/utils/__init__.py +0 -0
- plain/utils/_os.py +52 -0
- plain/utils/cache.py +327 -0
- plain/utils/connection.py +84 -0
- plain/utils/crypto.py +76 -0
- plain/utils/datastructures.py +345 -0
- plain/utils/dateformat.py +329 -0
- plain/utils/dateparse.py +154 -0
- plain/utils/dates.py +76 -0
- plain/utils/deconstruct.py +54 -0
- plain/utils/decorators.py +90 -0
- plain/utils/deprecation.py +6 -0
- plain/utils/duration.py +44 -0
- plain/utils/email.py +12 -0
- plain/utils/encoding.py +235 -0
- plain/utils/functional.py +456 -0
- plain/utils/hashable.py +26 -0
- plain/utils/html.py +401 -0
- plain/utils/http.py +374 -0
- plain/utils/inspect.py +73 -0
- plain/utils/ipv6.py +46 -0
- plain/utils/itercompat.py +8 -0
- plain/utils/module_loading.py +69 -0
- plain/utils/regex_helper.py +353 -0
- plain/utils/safestring.py +72 -0
- plain/utils/termcolors.py +221 -0
- plain/utils/text.py +518 -0
- plain/utils/timesince.py +138 -0
- plain/utils/timezone.py +244 -0
- plain/utils/tree.py +126 -0
- plain/validators.py +603 -0
- plain/views/README.md +268 -0
- plain/views/__init__.py +18 -0
- plain/views/base.py +107 -0
- plain/views/csrf.py +24 -0
- plain/views/errors.py +25 -0
- plain/views/exceptions.py +4 -0
- plain/views/forms.py +76 -0
- plain/views/objects.py +229 -0
- plain/views/redirect.py +72 -0
- plain/views/templates.py +66 -0
- plain/wsgi.py +11 -0
- plain-0.1.0.dist-info/LICENSE +85 -0
- plain-0.1.0.dist-info/METADATA +51 -0
- plain-0.1.0.dist-info/RECORD +169 -0
- plain-0.1.0.dist-info/WHEEL +4 -0
- 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
|