Fixed an issue that prevented the password reset tokens from working. Added email templates for password reset success and new account creation. Added more dynamic email template support.
89 lines
2.3 KiB
Python
89 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
import binascii
|
|
import codecs
|
|
import os
|
|
import typing
|
|
from io import BytesIO
|
|
|
|
from .fields import _TYPE_FIELD_VALUE_TUPLE, RequestField
|
|
|
|
writer = codecs.lookup("utf-8")[3]
|
|
|
|
_TYPE_FIELDS_SEQUENCE = typing.Sequence[
|
|
typing.Union[typing.Tuple[str, _TYPE_FIELD_VALUE_TUPLE], RequestField]
|
|
]
|
|
_TYPE_FIELDS = typing.Union[
|
|
_TYPE_FIELDS_SEQUENCE,
|
|
typing.Mapping[str, _TYPE_FIELD_VALUE_TUPLE],
|
|
]
|
|
|
|
|
|
def choose_boundary() -> str:
|
|
"""
|
|
Our embarrassingly-simple replacement for mimetools.choose_boundary.
|
|
"""
|
|
return binascii.hexlify(os.urandom(16)).decode()
|
|
|
|
|
|
def iter_field_objects(fields: _TYPE_FIELDS) -> typing.Iterable[RequestField]:
|
|
"""
|
|
Iterate over fields.
|
|
|
|
Supports list of (k, v) tuples and dicts, and lists of
|
|
:class:`~urllib3.fields.RequestField`.
|
|
|
|
"""
|
|
iterable: typing.Iterable[RequestField | tuple[str, _TYPE_FIELD_VALUE_TUPLE]]
|
|
|
|
if isinstance(fields, typing.Mapping):
|
|
iterable = fields.items()
|
|
else:
|
|
iterable = fields
|
|
|
|
for field in iterable:
|
|
if isinstance(field, RequestField):
|
|
yield field
|
|
else:
|
|
yield RequestField.from_tuples(*field)
|
|
|
|
|
|
def encode_multipart_formdata(
|
|
fields: _TYPE_FIELDS, boundary: str | None = None
|
|
) -> tuple[bytes, str]:
|
|
"""
|
|
Encode a dictionary of ``fields`` using the multipart/form-data MIME format.
|
|
|
|
:param fields:
|
|
Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`).
|
|
Values are processed by :func:`urllib3.fields.RequestField.from_tuples`.
|
|
|
|
:param boundary:
|
|
If not specified, then a random boundary will be generated using
|
|
:func:`urllib3.filepost.choose_boundary`.
|
|
"""
|
|
body = BytesIO()
|
|
if boundary is None:
|
|
boundary = choose_boundary()
|
|
|
|
for field in iter_field_objects(fields):
|
|
body.write(f"--{boundary}\r\n".encode("latin-1"))
|
|
|
|
writer(body).write(field.render_headers())
|
|
data = field.data
|
|
|
|
if isinstance(data, int):
|
|
data = str(data) # Backwards compatibility
|
|
|
|
if isinstance(data, str):
|
|
writer(body).write(data)
|
|
else:
|
|
body.write(data)
|
|
|
|
body.write(b"\r\n")
|
|
|
|
body.write(f"--{boundary}--\r\n".encode("latin-1"))
|
|
|
|
content_type = f"multipart/form-data; boundary={boundary}"
|
|
|
|
return body.getvalue(), content_type
|