done
This commit is contained in:
		
							
								
								
									
										170
									
								
								lib/python3.11/site-packages/flask/json/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								lib/python3.11/site-packages/flask/json/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,170 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import json as _json | ||||
| import typing as t | ||||
|  | ||||
| from ..globals import current_app | ||||
| from .provider import _default | ||||
|  | ||||
| if t.TYPE_CHECKING:  # pragma: no cover | ||||
|     from ..wrappers import Response | ||||
|  | ||||
|  | ||||
| def dumps(obj: t.Any, **kwargs: t.Any) -> str: | ||||
|     """Serialize data as JSON. | ||||
|  | ||||
|     If :data:`~flask.current_app` is available, it will use its | ||||
|     :meth:`app.json.dumps() <flask.json.provider.JSONProvider.dumps>` | ||||
|     method, otherwise it will use :func:`json.dumps`. | ||||
|  | ||||
|     :param obj: The data to serialize. | ||||
|     :param kwargs: Arguments passed to the ``dumps`` implementation. | ||||
|  | ||||
|     .. versionchanged:: 2.3 | ||||
|         The ``app`` parameter was removed. | ||||
|  | ||||
|     .. versionchanged:: 2.2 | ||||
|         Calls ``current_app.json.dumps``, allowing an app to override | ||||
|         the behavior. | ||||
|  | ||||
|     .. versionchanged:: 2.0.2 | ||||
|         :class:`decimal.Decimal` is supported by converting to a string. | ||||
|  | ||||
|     .. versionchanged:: 2.0 | ||||
|         ``encoding`` will be removed in Flask 2.1. | ||||
|  | ||||
|     .. versionchanged:: 1.0.3 | ||||
|         ``app`` can be passed directly, rather than requiring an app | ||||
|         context for configuration. | ||||
|     """ | ||||
|     if current_app: | ||||
|         return current_app.json.dumps(obj, **kwargs) | ||||
|  | ||||
|     kwargs.setdefault("default", _default) | ||||
|     return _json.dumps(obj, **kwargs) | ||||
|  | ||||
|  | ||||
| def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: | ||||
|     """Serialize data as JSON and write to a file. | ||||
|  | ||||
|     If :data:`~flask.current_app` is available, it will use its | ||||
|     :meth:`app.json.dump() <flask.json.provider.JSONProvider.dump>` | ||||
|     method, otherwise it will use :func:`json.dump`. | ||||
|  | ||||
|     :param obj: The data to serialize. | ||||
|     :param fp: A file opened for writing text. Should use the UTF-8 | ||||
|         encoding to be valid JSON. | ||||
|     :param kwargs: Arguments passed to the ``dump`` implementation. | ||||
|  | ||||
|     .. versionchanged:: 2.3 | ||||
|         The ``app`` parameter was removed. | ||||
|  | ||||
|     .. versionchanged:: 2.2 | ||||
|         Calls ``current_app.json.dump``, allowing an app to override | ||||
|         the behavior. | ||||
|  | ||||
|     .. versionchanged:: 2.0 | ||||
|         Writing to a binary file, and the ``encoding`` argument, will be | ||||
|         removed in Flask 2.1. | ||||
|     """ | ||||
|     if current_app: | ||||
|         current_app.json.dump(obj, fp, **kwargs) | ||||
|     else: | ||||
|         kwargs.setdefault("default", _default) | ||||
|         _json.dump(obj, fp, **kwargs) | ||||
|  | ||||
|  | ||||
| def loads(s: str | bytes, **kwargs: t.Any) -> t.Any: | ||||
|     """Deserialize data as JSON. | ||||
|  | ||||
|     If :data:`~flask.current_app` is available, it will use its | ||||
|     :meth:`app.json.loads() <flask.json.provider.JSONProvider.loads>` | ||||
|     method, otherwise it will use :func:`json.loads`. | ||||
|  | ||||
|     :param s: Text or UTF-8 bytes. | ||||
|     :param kwargs: Arguments passed to the ``loads`` implementation. | ||||
|  | ||||
|     .. versionchanged:: 2.3 | ||||
|         The ``app`` parameter was removed. | ||||
|  | ||||
|     .. versionchanged:: 2.2 | ||||
|         Calls ``current_app.json.loads``, allowing an app to override | ||||
|         the behavior. | ||||
|  | ||||
|     .. versionchanged:: 2.0 | ||||
|         ``encoding`` will be removed in Flask 2.1. The data must be a | ||||
|         string or UTF-8 bytes. | ||||
|  | ||||
|     .. versionchanged:: 1.0.3 | ||||
|         ``app`` can be passed directly, rather than requiring an app | ||||
|         context for configuration. | ||||
|     """ | ||||
|     if current_app: | ||||
|         return current_app.json.loads(s, **kwargs) | ||||
|  | ||||
|     return _json.loads(s, **kwargs) | ||||
|  | ||||
|  | ||||
| def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: | ||||
|     """Deserialize data as JSON read from a file. | ||||
|  | ||||
|     If :data:`~flask.current_app` is available, it will use its | ||||
|     :meth:`app.json.load() <flask.json.provider.JSONProvider.load>` | ||||
|     method, otherwise it will use :func:`json.load`. | ||||
|  | ||||
|     :param fp: A file opened for reading text or UTF-8 bytes. | ||||
|     :param kwargs: Arguments passed to the ``load`` implementation. | ||||
|  | ||||
|     .. versionchanged:: 2.3 | ||||
|         The ``app`` parameter was removed. | ||||
|  | ||||
|     .. versionchanged:: 2.2 | ||||
|         Calls ``current_app.json.load``, allowing an app to override | ||||
|         the behavior. | ||||
|  | ||||
|     .. versionchanged:: 2.2 | ||||
|         The ``app`` parameter will be removed in Flask 2.3. | ||||
|  | ||||
|     .. versionchanged:: 2.0 | ||||
|         ``encoding`` will be removed in Flask 2.1. The file must be text | ||||
|         mode, or binary mode with UTF-8 bytes. | ||||
|     """ | ||||
|     if current_app: | ||||
|         return current_app.json.load(fp, **kwargs) | ||||
|  | ||||
|     return _json.load(fp, **kwargs) | ||||
|  | ||||
|  | ||||
| def jsonify(*args: t.Any, **kwargs: t.Any) -> Response: | ||||
|     """Serialize the given arguments as JSON, and return a | ||||
|     :class:`~flask.Response` object with the ``application/json`` | ||||
|     mimetype. A dict or list returned from a view will be converted to a | ||||
|     JSON response automatically without needing to call this. | ||||
|  | ||||
|     This requires an active request or application context, and calls | ||||
|     :meth:`app.json.response() <flask.json.provider.JSONProvider.response>`. | ||||
|  | ||||
|     In debug mode, the output is formatted with indentation to make it | ||||
|     easier to read. This may also be controlled by the provider. | ||||
|  | ||||
|     Either positional or keyword arguments can be given, not both. | ||||
|     If no arguments are given, ``None`` is serialized. | ||||
|  | ||||
|     :param args: A single value to serialize, or multiple values to | ||||
|         treat as a list to serialize. | ||||
|     :param kwargs: Treat as a dict to serialize. | ||||
|  | ||||
|     .. versionchanged:: 2.2 | ||||
|         Calls ``current_app.json.response``, allowing an app to override | ||||
|         the behavior. | ||||
|  | ||||
|     .. versionchanged:: 2.0.2 | ||||
|         :class:`decimal.Decimal` is supported by converting to a string. | ||||
|  | ||||
|     .. versionchanged:: 0.11 | ||||
|         Added support for serializing top-level arrays. This was a | ||||
|         security risk in ancient browsers. See :ref:`security-json`. | ||||
|  | ||||
|     .. versionadded:: 0.2 | ||||
|     """ | ||||
|     return current_app.json.response(*args, **kwargs)  # type: ignore[return-value] | ||||
							
								
								
									
										215
									
								
								lib/python3.11/site-packages/flask/json/provider.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								lib/python3.11/site-packages/flask/json/provider.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,215 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import dataclasses | ||||
| import decimal | ||||
| import json | ||||
| import typing as t | ||||
| import uuid | ||||
| import weakref | ||||
| from datetime import date | ||||
|  | ||||
| from werkzeug.http import http_date | ||||
|  | ||||
| if t.TYPE_CHECKING:  # pragma: no cover | ||||
|     from werkzeug.sansio.response import Response | ||||
|  | ||||
|     from ..sansio.app import App | ||||
|  | ||||
|  | ||||
| class JSONProvider: | ||||
|     """A standard set of JSON operations for an application. Subclasses | ||||
|     of this can be used to customize JSON behavior or use different | ||||
|     JSON libraries. | ||||
|  | ||||
|     To implement a provider for a specific library, subclass this base | ||||
|     class and implement at least :meth:`dumps` and :meth:`loads`. All | ||||
|     other methods have default implementations. | ||||
|  | ||||
|     To use a different provider, either subclass ``Flask`` and set | ||||
|     :attr:`~flask.Flask.json_provider_class` to a provider class, or set | ||||
|     :attr:`app.json <flask.Flask.json>` to an instance of the class. | ||||
|  | ||||
|     :param app: An application instance. This will be stored as a | ||||
|         :class:`weakref.proxy` on the :attr:`_app` attribute. | ||||
|  | ||||
|     .. versionadded:: 2.2 | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, app: App) -> None: | ||||
|         self._app: App = weakref.proxy(app) | ||||
|  | ||||
|     def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: | ||||
|         """Serialize data as JSON. | ||||
|  | ||||
|         :param obj: The data to serialize. | ||||
|         :param kwargs: May be passed to the underlying JSON library. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None: | ||||
|         """Serialize data as JSON and write to a file. | ||||
|  | ||||
|         :param obj: The data to serialize. | ||||
|         :param fp: A file opened for writing text. Should use the UTF-8 | ||||
|             encoding to be valid JSON. | ||||
|         :param kwargs: May be passed to the underlying JSON library. | ||||
|         """ | ||||
|         fp.write(self.dumps(obj, **kwargs)) | ||||
|  | ||||
|     def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: | ||||
|         """Deserialize data as JSON. | ||||
|  | ||||
|         :param s: Text or UTF-8 bytes. | ||||
|         :param kwargs: May be passed to the underlying JSON library. | ||||
|         """ | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any: | ||||
|         """Deserialize data as JSON read from a file. | ||||
|  | ||||
|         :param fp: A file opened for reading text or UTF-8 bytes. | ||||
|         :param kwargs: May be passed to the underlying JSON library. | ||||
|         """ | ||||
|         return self.loads(fp.read(), **kwargs) | ||||
|  | ||||
|     def _prepare_response_obj( | ||||
|         self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any] | ||||
|     ) -> t.Any: | ||||
|         if args and kwargs: | ||||
|             raise TypeError("app.json.response() takes either args or kwargs, not both") | ||||
|  | ||||
|         if not args and not kwargs: | ||||
|             return None | ||||
|  | ||||
|         if len(args) == 1: | ||||
|             return args[0] | ||||
|  | ||||
|         return args or kwargs | ||||
|  | ||||
|     def response(self, *args: t.Any, **kwargs: t.Any) -> Response: | ||||
|         """Serialize the given arguments as JSON, and return a | ||||
|         :class:`~flask.Response` object with the ``application/json`` | ||||
|         mimetype. | ||||
|  | ||||
|         The :func:`~flask.json.jsonify` function calls this method for | ||||
|         the current application. | ||||
|  | ||||
|         Either positional or keyword arguments can be given, not both. | ||||
|         If no arguments are given, ``None`` is serialized. | ||||
|  | ||||
|         :param args: A single value to serialize, or multiple values to | ||||
|             treat as a list to serialize. | ||||
|         :param kwargs: Treat as a dict to serialize. | ||||
|         """ | ||||
|         obj = self._prepare_response_obj(args, kwargs) | ||||
|         return self._app.response_class(self.dumps(obj), mimetype="application/json") | ||||
|  | ||||
|  | ||||
| def _default(o: t.Any) -> t.Any: | ||||
|     if isinstance(o, date): | ||||
|         return http_date(o) | ||||
|  | ||||
|     if isinstance(o, (decimal.Decimal, uuid.UUID)): | ||||
|         return str(o) | ||||
|  | ||||
|     if dataclasses and dataclasses.is_dataclass(o): | ||||
|         return dataclasses.asdict(o)  # type: ignore[arg-type] | ||||
|  | ||||
|     if hasattr(o, "__html__"): | ||||
|         return str(o.__html__()) | ||||
|  | ||||
|     raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable") | ||||
|  | ||||
|  | ||||
| class DefaultJSONProvider(JSONProvider): | ||||
|     """Provide JSON operations using Python's built-in :mod:`json` | ||||
|     library. Serializes the following additional data types: | ||||
|  | ||||
|     -   :class:`datetime.datetime` and :class:`datetime.date` are | ||||
|         serialized to :rfc:`822` strings. This is the same as the HTTP | ||||
|         date format. | ||||
|     -   :class:`uuid.UUID` is serialized to a string. | ||||
|     -   :class:`dataclasses.dataclass` is passed to | ||||
|         :func:`dataclasses.asdict`. | ||||
|     -   :class:`~markupsafe.Markup` (or any object with a ``__html__`` | ||||
|         method) will call the ``__html__`` method to get a string. | ||||
|     """ | ||||
|  | ||||
|     default: t.Callable[[t.Any], t.Any] = staticmethod(_default)  # type: ignore[assignment] | ||||
|     """Apply this function to any object that :meth:`json.dumps` does | ||||
|     not know how to serialize. It should return a valid JSON type or | ||||
|     raise a ``TypeError``. | ||||
|     """ | ||||
|  | ||||
|     ensure_ascii = True | ||||
|     """Replace non-ASCII characters with escape sequences. This may be | ||||
|     more compatible with some clients, but can be disabled for better | ||||
|     performance and size. | ||||
|     """ | ||||
|  | ||||
|     sort_keys = True | ||||
|     """Sort the keys in any serialized dicts. This may be useful for | ||||
|     some caching situations, but can be disabled for better performance. | ||||
|     When enabled, keys must all be strings, they are not converted | ||||
|     before sorting. | ||||
|     """ | ||||
|  | ||||
|     compact: bool | None = None | ||||
|     """If ``True``, or ``None`` out of debug mode, the :meth:`response` | ||||
|     output will not add indentation, newlines, or spaces. If ``False``, | ||||
|     or ``None`` in debug mode, it will use a non-compact representation. | ||||
|     """ | ||||
|  | ||||
|     mimetype = "application/json" | ||||
|     """The mimetype set in :meth:`response`.""" | ||||
|  | ||||
|     def dumps(self, obj: t.Any, **kwargs: t.Any) -> str: | ||||
|         """Serialize data as JSON to a string. | ||||
|  | ||||
|         Keyword arguments are passed to :func:`json.dumps`. Sets some | ||||
|         parameter defaults from the :attr:`default`, | ||||
|         :attr:`ensure_ascii`, and :attr:`sort_keys` attributes. | ||||
|  | ||||
|         :param obj: The data to serialize. | ||||
|         :param kwargs: Passed to :func:`json.dumps`. | ||||
|         """ | ||||
|         kwargs.setdefault("default", self.default) | ||||
|         kwargs.setdefault("ensure_ascii", self.ensure_ascii) | ||||
|         kwargs.setdefault("sort_keys", self.sort_keys) | ||||
|         return json.dumps(obj, **kwargs) | ||||
|  | ||||
|     def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any: | ||||
|         """Deserialize data as JSON from a string or bytes. | ||||
|  | ||||
|         :param s: Text or UTF-8 bytes. | ||||
|         :param kwargs: Passed to :func:`json.loads`. | ||||
|         """ | ||||
|         return json.loads(s, **kwargs) | ||||
|  | ||||
|     def response(self, *args: t.Any, **kwargs: t.Any) -> Response: | ||||
|         """Serialize the given arguments as JSON, and return a | ||||
|         :class:`~flask.Response` object with it. The response mimetype | ||||
|         will be "application/json" and can be changed with | ||||
|         :attr:`mimetype`. | ||||
|  | ||||
|         If :attr:`compact` is ``False`` or debug mode is enabled, the | ||||
|         output will be formatted to be easier to read. | ||||
|  | ||||
|         Either positional or keyword arguments can be given, not both. | ||||
|         If no arguments are given, ``None`` is serialized. | ||||
|  | ||||
|         :param args: A single value to serialize, or multiple values to | ||||
|             treat as a list to serialize. | ||||
|         :param kwargs: Treat as a dict to serialize. | ||||
|         """ | ||||
|         obj = self._prepare_response_obj(args, kwargs) | ||||
|         dump_args: dict[str, t.Any] = {} | ||||
|  | ||||
|         if (self.compact is None and self._app.debug) or self.compact is False: | ||||
|             dump_args.setdefault("indent", 2) | ||||
|         else: | ||||
|             dump_args.setdefault("separators", (",", ":")) | ||||
|  | ||||
|         return self._app.response_class( | ||||
|             f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype | ||||
|         ) | ||||
							
								
								
									
										327
									
								
								lib/python3.11/site-packages/flask/json/tag.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								lib/python3.11/site-packages/flask/json/tag.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,327 @@ | ||||
| """ | ||||
| Tagged JSON | ||||
| ~~~~~~~~~~~ | ||||
|  | ||||
| A compact representation for lossless serialization of non-standard JSON | ||||
| types. :class:`~flask.sessions.SecureCookieSessionInterface` uses this | ||||
| to serialize the session data, but it may be useful in other places. It | ||||
| can be extended to support other types. | ||||
|  | ||||
| .. autoclass:: TaggedJSONSerializer | ||||
|     :members: | ||||
|  | ||||
| .. autoclass:: JSONTag | ||||
|     :members: | ||||
|  | ||||
| Let's see an example that adds support for | ||||
| :class:`~collections.OrderedDict`. Dicts don't have an order in JSON, so | ||||
| to handle this we will dump the items as a list of ``[key, value]`` | ||||
| pairs. Subclass :class:`JSONTag` and give it the new key ``' od'`` to | ||||
| identify the type. The session serializer processes dicts first, so | ||||
| insert the new tag at the front of the order since ``OrderedDict`` must | ||||
| be processed before ``dict``. | ||||
|  | ||||
| .. code-block:: python | ||||
|  | ||||
|     from flask.json.tag import JSONTag | ||||
|  | ||||
|     class TagOrderedDict(JSONTag): | ||||
|         __slots__ = ('serializer',) | ||||
|         key = ' od' | ||||
|  | ||||
|         def check(self, value): | ||||
|             return isinstance(value, OrderedDict) | ||||
|  | ||||
|         def to_json(self, value): | ||||
|             return [[k, self.serializer.tag(v)] for k, v in iteritems(value)] | ||||
|  | ||||
|         def to_python(self, value): | ||||
|             return OrderedDict(value) | ||||
|  | ||||
|     app.session_interface.serializer.register(TagOrderedDict, index=0) | ||||
| """ | ||||
|  | ||||
| from __future__ import annotations | ||||
|  | ||||
| import typing as t | ||||
| from base64 import b64decode | ||||
| from base64 import b64encode | ||||
| from datetime import datetime | ||||
| from uuid import UUID | ||||
|  | ||||
| from markupsafe import Markup | ||||
| from werkzeug.http import http_date | ||||
| from werkzeug.http import parse_date | ||||
|  | ||||
| from ..json import dumps | ||||
| from ..json import loads | ||||
|  | ||||
|  | ||||
| class JSONTag: | ||||
|     """Base class for defining type tags for :class:`TaggedJSONSerializer`.""" | ||||
|  | ||||
|     __slots__ = ("serializer",) | ||||
|  | ||||
|     #: The tag to mark the serialized object with. If empty, this tag is | ||||
|     #: only used as an intermediate step during tagging. | ||||
|     key: str = "" | ||||
|  | ||||
|     def __init__(self, serializer: TaggedJSONSerializer) -> None: | ||||
|         """Create a tagger for the given serializer.""" | ||||
|         self.serializer = serializer | ||||
|  | ||||
|     def check(self, value: t.Any) -> bool: | ||||
|         """Check if the given value should be tagged by this tag.""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def to_json(self, value: t.Any) -> t.Any: | ||||
|         """Convert the Python object to an object that is a valid JSON type. | ||||
|         The tag will be added later.""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def to_python(self, value: t.Any) -> t.Any: | ||||
|         """Convert the JSON representation back to the correct type. The tag | ||||
|         will already be removed.""" | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def tag(self, value: t.Any) -> dict[str, t.Any]: | ||||
|         """Convert the value to a valid JSON type and add the tag structure | ||||
|         around it.""" | ||||
|         return {self.key: self.to_json(value)} | ||||
|  | ||||
|  | ||||
| class TagDict(JSONTag): | ||||
|     """Tag for 1-item dicts whose only key matches a registered tag. | ||||
|  | ||||
|     Internally, the dict key is suffixed with `__`, and the suffix is removed | ||||
|     when deserializing. | ||||
|     """ | ||||
|  | ||||
|     __slots__ = () | ||||
|     key = " di" | ||||
|  | ||||
|     def check(self, value: t.Any) -> bool: | ||||
|         return ( | ||||
|             isinstance(value, dict) | ||||
|             and len(value) == 1 | ||||
|             and next(iter(value)) in self.serializer.tags | ||||
|         ) | ||||
|  | ||||
|     def to_json(self, value: t.Any) -> t.Any: | ||||
|         key = next(iter(value)) | ||||
|         return {f"{key}__": self.serializer.tag(value[key])} | ||||
|  | ||||
|     def to_python(self, value: t.Any) -> t.Any: | ||||
|         key = next(iter(value)) | ||||
|         return {key[:-2]: value[key]} | ||||
|  | ||||
|  | ||||
| class PassDict(JSONTag): | ||||
|     __slots__ = () | ||||
|  | ||||
|     def check(self, value: t.Any) -> bool: | ||||
|         return isinstance(value, dict) | ||||
|  | ||||
|     def to_json(self, value: t.Any) -> t.Any: | ||||
|         # JSON objects may only have string keys, so don't bother tagging the | ||||
|         # key here. | ||||
|         return {k: self.serializer.tag(v) for k, v in value.items()} | ||||
|  | ||||
|     tag = to_json | ||||
|  | ||||
|  | ||||
| class TagTuple(JSONTag): | ||||
|     __slots__ = () | ||||
|     key = " t" | ||||
|  | ||||
|     def check(self, value: t.Any) -> bool: | ||||
|         return isinstance(value, tuple) | ||||
|  | ||||
|     def to_json(self, value: t.Any) -> t.Any: | ||||
|         return [self.serializer.tag(item) for item in value] | ||||
|  | ||||
|     def to_python(self, value: t.Any) -> t.Any: | ||||
|         return tuple(value) | ||||
|  | ||||
|  | ||||
| class PassList(JSONTag): | ||||
|     __slots__ = () | ||||
|  | ||||
|     def check(self, value: t.Any) -> bool: | ||||
|         return isinstance(value, list) | ||||
|  | ||||
|     def to_json(self, value: t.Any) -> t.Any: | ||||
|         return [self.serializer.tag(item) for item in value] | ||||
|  | ||||
|     tag = to_json | ||||
|  | ||||
|  | ||||
| class TagBytes(JSONTag): | ||||
|     __slots__ = () | ||||
|     key = " b" | ||||
|  | ||||
|     def check(self, value: t.Any) -> bool: | ||||
|         return isinstance(value, bytes) | ||||
|  | ||||
|     def to_json(self, value: t.Any) -> t.Any: | ||||
|         return b64encode(value).decode("ascii") | ||||
|  | ||||
|     def to_python(self, value: t.Any) -> t.Any: | ||||
|         return b64decode(value) | ||||
|  | ||||
|  | ||||
| class TagMarkup(JSONTag): | ||||
|     """Serialize anything matching the :class:`~markupsafe.Markup` API by | ||||
|     having a ``__html__`` method to the result of that method. Always | ||||
|     deserializes to an instance of :class:`~markupsafe.Markup`.""" | ||||
|  | ||||
|     __slots__ = () | ||||
|     key = " m" | ||||
|  | ||||
|     def check(self, value: t.Any) -> bool: | ||||
|         return callable(getattr(value, "__html__", None)) | ||||
|  | ||||
|     def to_json(self, value: t.Any) -> t.Any: | ||||
|         return str(value.__html__()) | ||||
|  | ||||
|     def to_python(self, value: t.Any) -> t.Any: | ||||
|         return Markup(value) | ||||
|  | ||||
|  | ||||
| class TagUUID(JSONTag): | ||||
|     __slots__ = () | ||||
|     key = " u" | ||||
|  | ||||
|     def check(self, value: t.Any) -> bool: | ||||
|         return isinstance(value, UUID) | ||||
|  | ||||
|     def to_json(self, value: t.Any) -> t.Any: | ||||
|         return value.hex | ||||
|  | ||||
|     def to_python(self, value: t.Any) -> t.Any: | ||||
|         return UUID(value) | ||||
|  | ||||
|  | ||||
| class TagDateTime(JSONTag): | ||||
|     __slots__ = () | ||||
|     key = " d" | ||||
|  | ||||
|     def check(self, value: t.Any) -> bool: | ||||
|         return isinstance(value, datetime) | ||||
|  | ||||
|     def to_json(self, value: t.Any) -> t.Any: | ||||
|         return http_date(value) | ||||
|  | ||||
|     def to_python(self, value: t.Any) -> t.Any: | ||||
|         return parse_date(value) | ||||
|  | ||||
|  | ||||
| class TaggedJSONSerializer: | ||||
|     """Serializer that uses a tag system to compactly represent objects that | ||||
|     are not JSON types. Passed as the intermediate serializer to | ||||
|     :class:`itsdangerous.Serializer`. | ||||
|  | ||||
|     The following extra types are supported: | ||||
|  | ||||
|     * :class:`dict` | ||||
|     * :class:`tuple` | ||||
|     * :class:`bytes` | ||||
|     * :class:`~markupsafe.Markup` | ||||
|     * :class:`~uuid.UUID` | ||||
|     * :class:`~datetime.datetime` | ||||
|     """ | ||||
|  | ||||
|     __slots__ = ("tags", "order") | ||||
|  | ||||
|     #: Tag classes to bind when creating the serializer. Other tags can be | ||||
|     #: added later using :meth:`~register`. | ||||
|     default_tags = [ | ||||
|         TagDict, | ||||
|         PassDict, | ||||
|         TagTuple, | ||||
|         PassList, | ||||
|         TagBytes, | ||||
|         TagMarkup, | ||||
|         TagUUID, | ||||
|         TagDateTime, | ||||
|     ] | ||||
|  | ||||
|     def __init__(self) -> None: | ||||
|         self.tags: dict[str, JSONTag] = {} | ||||
|         self.order: list[JSONTag] = [] | ||||
|  | ||||
|         for cls in self.default_tags: | ||||
|             self.register(cls) | ||||
|  | ||||
|     def register( | ||||
|         self, | ||||
|         tag_class: type[JSONTag], | ||||
|         force: bool = False, | ||||
|         index: int | None = None, | ||||
|     ) -> None: | ||||
|         """Register a new tag with this serializer. | ||||
|  | ||||
|         :param tag_class: tag class to register. Will be instantiated with this | ||||
|             serializer instance. | ||||
|         :param force: overwrite an existing tag. If false (default), a | ||||
|             :exc:`KeyError` is raised. | ||||
|         :param index: index to insert the new tag in the tag order. Useful when | ||||
|             the new tag is a special case of an existing tag. If ``None`` | ||||
|             (default), the tag is appended to the end of the order. | ||||
|  | ||||
|         :raise KeyError: if the tag key is already registered and ``force`` is | ||||
|             not true. | ||||
|         """ | ||||
|         tag = tag_class(self) | ||||
|         key = tag.key | ||||
|  | ||||
|         if key: | ||||
|             if not force and key in self.tags: | ||||
|                 raise KeyError(f"Tag '{key}' is already registered.") | ||||
|  | ||||
|             self.tags[key] = tag | ||||
|  | ||||
|         if index is None: | ||||
|             self.order.append(tag) | ||||
|         else: | ||||
|             self.order.insert(index, tag) | ||||
|  | ||||
|     def tag(self, value: t.Any) -> t.Any: | ||||
|         """Convert a value to a tagged representation if necessary.""" | ||||
|         for tag in self.order: | ||||
|             if tag.check(value): | ||||
|                 return tag.tag(value) | ||||
|  | ||||
|         return value | ||||
|  | ||||
|     def untag(self, value: dict[str, t.Any]) -> t.Any: | ||||
|         """Convert a tagged representation back to the original type.""" | ||||
|         if len(value) != 1: | ||||
|             return value | ||||
|  | ||||
|         key = next(iter(value)) | ||||
|  | ||||
|         if key not in self.tags: | ||||
|             return value | ||||
|  | ||||
|         return self.tags[key].to_python(value[key]) | ||||
|  | ||||
|     def _untag_scan(self, value: t.Any) -> t.Any: | ||||
|         if isinstance(value, dict): | ||||
|             # untag each item recursively | ||||
|             value = {k: self._untag_scan(v) for k, v in value.items()} | ||||
|             # untag the dict itself | ||||
|             value = self.untag(value) | ||||
|         elif isinstance(value, list): | ||||
|             # untag each item recursively | ||||
|             value = [self._untag_scan(item) for item in value] | ||||
|  | ||||
|         return value | ||||
|  | ||||
|     def dumps(self, value: t.Any) -> str: | ||||
|         """Tag the value and dump it to a compact JSON string.""" | ||||
|         return dumps(self.tag(value), separators=(",", ":")) | ||||
|  | ||||
|     def loads(self, value: str) -> t.Any: | ||||
|         """Load data from a JSON string and deserialized any tagged objects.""" | ||||
|         return self._untag_scan(loads(value)) | ||||
		Reference in New Issue
	
	Block a user