summaryrefslogtreecommitdiffstats
path: root/contrib/python/Flask/py3/flask/json
diff options
context:
space:
mode:
authornkozlovskiy <[email protected]>2023-09-29 12:24:06 +0300
committernkozlovskiy <[email protected]>2023-09-29 12:41:34 +0300
commite0e3e1717e3d33762ce61950504f9637a6e669ed (patch)
treebca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/Flask/py3/flask/json
parent38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff)
add ydb deps
Diffstat (limited to 'contrib/python/Flask/py3/flask/json')
-rw-r--r--contrib/python/Flask/py3/flask/json/__init__.py363
-rw-r--r--contrib/python/Flask/py3/flask/json/tag.py312
2 files changed, 675 insertions, 0 deletions
diff --git a/contrib/python/Flask/py3/flask/json/__init__.py b/contrib/python/Flask/py3/flask/json/__init__.py
new file mode 100644
index 00000000000..ccb9efb174c
--- /dev/null
+++ b/contrib/python/Flask/py3/flask/json/__init__.py
@@ -0,0 +1,363 @@
+import decimal
+import io
+import json as _json
+import typing as t
+import uuid
+import warnings
+from datetime import date
+
+from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps
+from werkzeug.http import http_date
+
+from ..globals import current_app
+from ..globals import request
+
+if t.TYPE_CHECKING:
+ from ..app import Flask
+ from ..wrappers import Response
+
+try:
+ import dataclasses
+except ImportError:
+ # Python < 3.7
+ dataclasses = None # type: ignore
+
+
+class JSONEncoder(_json.JSONEncoder):
+ """The default JSON encoder. Handles extra types compared to the
+ built-in :class:`json.JSONEncoder`.
+
+ - :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.
+
+ Assign a subclass of this to :attr:`flask.Flask.json_encoder` or
+ :attr:`flask.Blueprint.json_encoder` to override the default.
+ """
+
+ def default(self, o: t.Any) -> t.Any:
+ """Convert ``o`` to a JSON serializable type. See
+ :meth:`json.JSONEncoder.default`. Python does not support
+ overriding how basic types like ``str`` or ``list`` are
+ serialized, they are handled before this method.
+ """
+ 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)
+ if hasattr(o, "__html__"):
+ return str(o.__html__())
+ return super().default(o)
+
+
+class JSONDecoder(_json.JSONDecoder):
+ """The default JSON decoder.
+
+ This does not change any behavior from the built-in
+ :class:`json.JSONDecoder`.
+
+ Assign a subclass of this to :attr:`flask.Flask.json_decoder` or
+ :attr:`flask.Blueprint.json_decoder` to override the default.
+ """
+
+
+def _dump_arg_defaults(
+ kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
+) -> None:
+ """Inject default arguments for dump functions."""
+ if app is None:
+ app = current_app
+
+ if app:
+ cls = app.json_encoder
+ bp = app.blueprints.get(request.blueprint) if request else None # type: ignore
+ if bp is not None and bp.json_encoder is not None:
+ cls = bp.json_encoder
+
+ # Only set a custom encoder if it has custom behavior. This is
+ # faster on PyPy.
+ if cls is not _json.JSONEncoder:
+ kwargs.setdefault("cls", cls)
+
+ kwargs.setdefault("cls", cls)
+ kwargs.setdefault("ensure_ascii", app.config["JSON_AS_ASCII"])
+ kwargs.setdefault("sort_keys", app.config["JSON_SORT_KEYS"])
+ else:
+ kwargs.setdefault("sort_keys", True)
+ kwargs.setdefault("cls", JSONEncoder)
+
+
+def _load_arg_defaults(
+ kwargs: t.Dict[str, t.Any], app: t.Optional["Flask"] = None
+) -> None:
+ """Inject default arguments for load functions."""
+ if app is None:
+ app = current_app
+
+ if app:
+ cls = app.json_decoder
+ bp = app.blueprints.get(request.blueprint) if request else None # type: ignore
+ if bp is not None and bp.json_decoder is not None:
+ cls = bp.json_decoder
+
+ # Only set a custom decoder if it has custom behavior. This is
+ # faster on PyPy.
+ if cls not in {JSONDecoder, _json.JSONDecoder}:
+ kwargs.setdefault("cls", cls)
+
+
+def dumps(obj: t.Any, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> str:
+ """Serialize an object to a string of JSON.
+
+ Takes the same arguments as the built-in :func:`json.dumps`, with
+ some defaults from application configuration.
+
+ :param obj: Object to serialize to JSON.
+ :param app: Use this app's config instead of the active app context
+ or defaults.
+ :param kwargs: Extra arguments passed to :func:`json.dumps`.
+
+ .. versionchanged:: 2.0.2
+ :class:`decimal.Decimal` is supported by converting to a string.
+
+ .. versionchanged:: 2.0
+ ``encoding`` is deprecated and will be removed in Flask 2.1.
+
+ .. versionchanged:: 1.0.3
+ ``app`` can be passed directly, rather than requiring an app
+ context for configuration.
+ """
+ _dump_arg_defaults(kwargs, app=app)
+ encoding = kwargs.pop("encoding", None)
+ rv = _json.dumps(obj, **kwargs)
+
+ if encoding is not None:
+ warnings.warn(
+ "'encoding' is deprecated and will be removed in Flask 2.1.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ if isinstance(rv, str):
+ return rv.encode(encoding) # type: ignore
+
+ return rv
+
+
+def dump(
+ obj: t.Any, fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any
+) -> None:
+ """Serialize an object to JSON written to a file object.
+
+ Takes the same arguments as the built-in :func:`json.dump`, with
+ some defaults from application configuration.
+
+ :param obj: Object to serialize to JSON.
+ :param fp: File object to write JSON to.
+ :param app: Use this app's config instead of the active app context
+ or defaults.
+ :param kwargs: Extra arguments passed to :func:`json.dump`.
+
+ .. versionchanged:: 2.0
+ Writing to a binary file, and the ``encoding`` argument, is
+ deprecated and will be removed in Flask 2.1.
+ """
+ _dump_arg_defaults(kwargs, app=app)
+ encoding = kwargs.pop("encoding", None)
+ show_warning = encoding is not None
+
+ try:
+ fp.write("")
+ except TypeError:
+ show_warning = True
+ fp = io.TextIOWrapper(fp, encoding or "utf-8") # type: ignore
+
+ if show_warning:
+ warnings.warn(
+ "Writing to a binary file, and the 'encoding' argument, is"
+ " deprecated and will be removed in Flask 2.1.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ _json.dump(obj, fp, **kwargs)
+
+
+def loads(s: str, app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any:
+ """Deserialize an object from a string of JSON.
+
+ Takes the same arguments as the built-in :func:`json.loads`, with
+ some defaults from application configuration.
+
+ :param s: JSON string to deserialize.
+ :param app: Use this app's config instead of the active app context
+ or defaults.
+ :param kwargs: Extra arguments passed to :func:`json.loads`.
+
+ .. versionchanged:: 2.0
+ ``encoding`` is deprecated and 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.
+ """
+ _load_arg_defaults(kwargs, app=app)
+ encoding = kwargs.pop("encoding", None)
+
+ if encoding is not None:
+ warnings.warn(
+ "'encoding' is deprecated and will be removed in Flask 2.1."
+ " The data must be a string or UTF-8 bytes.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ if isinstance(s, bytes):
+ s = s.decode(encoding)
+
+ return _json.loads(s, **kwargs)
+
+
+def load(fp: t.IO[str], app: t.Optional["Flask"] = None, **kwargs: t.Any) -> t.Any:
+ """Deserialize an object from JSON read from a file object.
+
+ Takes the same arguments as the built-in :func:`json.load`, with
+ some defaults from application configuration.
+
+ :param fp: File object to read JSON from.
+ :param app: Use this app's config instead of the active app context
+ or defaults.
+ :param kwargs: Extra arguments passed to :func:`json.load`.
+
+ .. versionchanged:: 2.0
+ ``encoding`` is deprecated and will be removed in Flask 2.1. The
+ file must be text mode, or binary mode with UTF-8 bytes.
+ """
+ _load_arg_defaults(kwargs, app=app)
+ encoding = kwargs.pop("encoding", None)
+
+ if encoding is not None:
+ warnings.warn(
+ "'encoding' is deprecated and will be removed in Flask 2.1."
+ " The file must be text mode, or binary mode with UTF-8"
+ " bytes.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ if isinstance(fp.read(0), bytes):
+ fp = io.TextIOWrapper(fp, encoding) # type: ignore
+
+ return _json.load(fp, **kwargs)
+
+
+def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str:
+ """Serialize an object to a string of JSON with :func:`dumps`, then
+ replace HTML-unsafe characters with Unicode escapes and mark the
+ result safe with :class:`~markupsafe.Markup`.
+
+ This is available in templates as the ``|tojson`` filter.
+
+ The returned string is safe to render in HTML documents and
+ ``<script>`` tags. The exception is in HTML attributes that are
+ double quoted; either use single quotes or the ``|forceescape``
+ filter.
+
+ .. versionchanged:: 2.0
+ Uses :func:`jinja2.utils.htmlsafe_json_dumps`. The returned
+ value is marked safe by wrapping in :class:`~markupsafe.Markup`.
+
+ .. versionchanged:: 0.10
+ Single quotes are escaped, making this safe to use in HTML,
+ ``<script>`` tags, and single-quoted attributes without further
+ escaping.
+ """
+ return _jinja_htmlsafe_dumps(obj, dumps=dumps, **kwargs)
+
+
+def htmlsafe_dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
+ """Serialize an object to JSON written to a file object, replacing
+ HTML-unsafe characters with Unicode escapes. See
+ :func:`htmlsafe_dumps` and :func:`dumps`.
+ """
+ fp.write(htmlsafe_dumps(obj, **kwargs))
+
+
+def jsonify(*args: t.Any, **kwargs: t.Any) -> "Response":
+ """Serialize data to JSON and wrap it in a :class:`~flask.Response`
+ with the :mimetype:`application/json` mimetype.
+
+ Uses :func:`dumps` to serialize the data, but ``args`` and
+ ``kwargs`` are treated as data rather than arguments to
+ :func:`json.dumps`.
+
+ 1. Single argument: Treated as a single value.
+ 2. Multiple arguments: Treated as a list of values.
+ ``jsonify(1, 2, 3)`` is the same as ``jsonify([1, 2, 3])``.
+ 3. Keyword arguments: Treated as a dict of values.
+ ``jsonify(data=data, errors=errors)`` is the same as
+ ``jsonify({"data": data, "errors": errors})``.
+ 4. Passing both arguments and keyword arguments is not allowed as
+ it's not clear what should happen.
+
+ .. code-block:: python
+
+ from flask import jsonify
+
+ @app.route("/users/me")
+ def get_current_user():
+ return jsonify(
+ username=g.user.username,
+ email=g.user.email,
+ id=g.user.id,
+ )
+
+ Will return a JSON response like this:
+
+ .. code-block:: javascript
+
+ {
+ "username": "admin",
+ "email": "admin@localhost",
+ "id": 42
+ }
+
+ The default output omits indents and spaces after separators. In
+ debug mode or if :data:`JSONIFY_PRETTYPRINT_REGULAR` is ``True``,
+ the output will be formatted to be easier to read.
+
+ .. 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 introduces
+ a security risk in ancient browsers. See :ref:`security-json`.
+
+ .. versionadded:: 0.2
+ """
+ indent = None
+ separators = (",", ":")
+
+ if current_app.config["JSONIFY_PRETTYPRINT_REGULAR"] or current_app.debug:
+ indent = 2
+ separators = (", ", ": ")
+
+ if args and kwargs:
+ raise TypeError("jsonify() behavior undefined when passed both args and kwargs")
+ elif len(args) == 1: # single args are passed directly to dumps()
+ data = args[0]
+ else:
+ data = args or kwargs
+
+ return current_app.response_class(
+ f"{dumps(data, indent=indent, separators=separators)}\n",
+ mimetype=current_app.config["JSONIFY_MIMETYPE"],
+ )
diff --git a/contrib/python/Flask/py3/flask/json/tag.py b/contrib/python/Flask/py3/flask/json/tag.py
new file mode 100644
index 00000000000..97f365a9b09
--- /dev/null
+++ b/contrib/python/Flask/py3/flask/json/tag.py
@@ -0,0 +1,312 @@
+"""
+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)
+"""
+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 ``None``, this tag is
+ #: only used as an intermediate step during tagging.
+ key: t.Optional[str] = None
+
+ 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) -> 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: t.Dict[str, JSONTag] = {}
+ self.order: t.List[JSONTag] = []
+
+ for cls in self.default_tags:
+ self.register(cls)
+
+ def register(
+ self,
+ tag_class: t.Type[JSONTag],
+ force: bool = False,
+ index: t.Optional[int] = 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 is not None:
+ 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.Dict[str, 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: t.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 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 loads(value, object_hook=self.untag)