diff options
| author | James Taylor <user234683@users.noreply.github.com> | 2019-06-16 16:16:03 -0700 | 
|---|---|---|
| committer | James Taylor <user234683@users.noreply.github.com> | 2019-06-16 16:16:03 -0700 | 
| commit | 2db58930a6f8c955c4d437657bd07e2939a705f2 (patch) | |
| tree | b1d388bd4adc1d3134d255cd0c4d8746d7b2468b /python/flask/json | |
| parent | 9f93b9429c77e631972186049fbc7518e2cf5d4b (diff) | |
| download | yt-local-2db58930a6f8c955c4d437657bd07e2939a705f2.tar.lz yt-local-2db58930a6f8c955c4d437657bd07e2939a705f2.tar.xz yt-local-2db58930a6f8c955c4d437657bd07e2939a705f2.zip | |
Convert watch page to flask framework
Diffstat (limited to 'python/flask/json')
| -rw-r--r-- | python/flask/json/__init__.py | 357 | ||||
| -rw-r--r-- | python/flask/json/tag.py | 300 | 
2 files changed, 657 insertions, 0 deletions
| diff --git a/python/flask/json/__init__.py b/python/flask/json/__init__.py new file mode 100644 index 0000000..c24286c --- /dev/null +++ b/python/flask/json/__init__.py @@ -0,0 +1,357 @@ +# -*- coding: utf-8 -*- +""" +flask.json +~~~~~~~~~~ + +:copyright: © 2010 by the Pallets team. +:license: BSD, see LICENSE for more details. +""" +import codecs +import io +import uuid +from datetime import date, datetime +from flask.globals import current_app, request +from flask._compat import text_type, PY2 + +from werkzeug.http import http_date +from jinja2 import Markup + +# Use the same json implementation as itsdangerous on which we +# depend anyways. +from itsdangerous import json as _json + + +# Figure out if simplejson escapes slashes.  This behavior was changed +# from one version to another without reason. +_slash_escape = '\\/' not in _json.dumps('/') + + +__all__ = ['dump', 'dumps', 'load', 'loads', 'htmlsafe_dump', +           'htmlsafe_dumps', 'JSONDecoder', 'JSONEncoder', +           'jsonify'] + + +def _wrap_reader_for_text(fp, encoding): +    if isinstance(fp.read(0), bytes): +        fp = io.TextIOWrapper(io.BufferedReader(fp), encoding) +    return fp + + +def _wrap_writer_for_text(fp, encoding): +    try: +        fp.write('') +    except TypeError: +        fp = io.TextIOWrapper(fp, encoding) +    return fp + + +class JSONEncoder(_json.JSONEncoder): +    """The default Flask JSON encoder.  This one extends the default simplejson +    encoder by also supporting ``datetime`` objects, ``UUID`` as well as +    ``Markup`` objects which are serialized as RFC 822 datetime strings (same +    as the HTTP date format).  In order to support more data types override the +    :meth:`default` method. +    """ + +    def default(self, o): +        """Implement this method in a subclass such that it returns a +        serializable object for ``o``, or calls the base implementation (to +        raise a :exc:`TypeError`). + +        For example, to support arbitrary iterators, you could implement +        default like this:: + +            def default(self, o): +                try: +                    iterable = iter(o) +                except TypeError: +                    pass +                else: +                    return list(iterable) +                return JSONEncoder.default(self, o) +        """ +        if isinstance(o, datetime): +            return http_date(o.utctimetuple()) +        if isinstance(o, date): +            return http_date(o.timetuple()) +        if isinstance(o, uuid.UUID): +            return str(o) +        if hasattr(o, '__html__'): +            return text_type(o.__html__()) +        return _json.JSONEncoder.default(self, o) + + +class JSONDecoder(_json.JSONDecoder): +    """The default JSON decoder.  This one does not change the behavior from +    the default simplejson decoder.  Consult the :mod:`json` documentation +    for more information.  This decoder is not only used for the load +    functions of this module but also :attr:`~flask.Request`. +    """ + + +def _dump_arg_defaults(kwargs, app=None): +    """Inject default arguments for dump functions.""" +    if app is None: +        app = current_app + +    if app: +        bp = app.blueprints.get(request.blueprint) if request else None +        kwargs.setdefault( +            'cls', bp.json_encoder if bp and bp.json_encoder else app.json_encoder +        ) + +        if not app.config['JSON_AS_ASCII']: +            kwargs.setdefault('ensure_ascii', False) + +        kwargs.setdefault('sort_keys', app.config['JSON_SORT_KEYS']) +    else: +        kwargs.setdefault('sort_keys', True) +        kwargs.setdefault('cls', JSONEncoder) + + +def _load_arg_defaults(kwargs, app=None): +    """Inject default arguments for load functions.""" +    if app is None: +        app = current_app + +    if app: +        bp = app.blueprints.get(request.blueprint) if request else None +        kwargs.setdefault( +            'cls', +            bp.json_decoder if bp and bp.json_decoder +                else app.json_decoder +        ) +    else: +        kwargs.setdefault('cls', JSONDecoder) + + +def detect_encoding(data): +    """Detect which UTF codec was used to encode the given bytes. + +    The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is +    accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big +    or little endian. Some editors or libraries may prepend a BOM. + +    :param data: Bytes in unknown UTF encoding. +    :return: UTF encoding name +    """ +    head = data[:4] + +    if head[:3] == codecs.BOM_UTF8: +        return 'utf-8-sig' + +    if b'\x00' not in head: +        return 'utf-8' + +    if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE): +        return 'utf-32' + +    if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE): +        return 'utf-16' + +    if len(head) == 4: +        if head[:3] == b'\x00\x00\x00': +            return 'utf-32-be' + +        if head[::2] == b'\x00\x00': +            return 'utf-16-be' + +        if head[1:] == b'\x00\x00\x00': +            return 'utf-32-le' + +        if head[1::2] == b'\x00\x00': +            return 'utf-16-le' + +    if len(head) == 2: +        return 'utf-16-be' if head.startswith(b'\x00') else 'utf-16-le' + +    return 'utf-8' + + +def dumps(obj, app=None, **kwargs): +    """Serialize ``obj`` to a JSON-formatted string. If there is an +    app context pushed, use the current app's configured encoder +    (:attr:`~flask.Flask.json_encoder`), or fall back to the default +    :class:`JSONEncoder`. + +    Takes the same arguments as the built-in :func:`json.dumps`, and +    does some extra configuration based on the application. If the +    simplejson package is installed, it is preferred. + +    :param obj: Object to serialize to JSON. +    :param app: App instance to use to configure the JSON encoder. +        Uses ``current_app`` if not given, and falls back to the default +        encoder when not in an app context. +    :param kwargs: Extra arguments passed to :func:`json.dumps`. + +    .. 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 and isinstance(rv, text_type): +        rv = rv.encode(encoding) +    return rv + + +def dump(obj, fp, app=None, **kwargs): +    """Like :func:`dumps` but writes into a file object.""" +    _dump_arg_defaults(kwargs, app=app) +    encoding = kwargs.pop('encoding', None) +    if encoding is not None: +        fp = _wrap_writer_for_text(fp, encoding) +    _json.dump(obj, fp, **kwargs) + + +def loads(s, app=None, **kwargs): +    """Deserialize an object from a JSON-formatted string ``s``. If +    there is an app context pushed, use the current app's configured +    decoder (:attr:`~flask.Flask.json_decoder`), or fall back to the +    default :class:`JSONDecoder`. + +    Takes the same arguments as the built-in :func:`json.loads`, and +    does some extra configuration based on the application. If the +    simplejson package is installed, it is preferred. + +    :param s: JSON string to deserialize. +    :param app: App instance to use to configure the JSON decoder. +        Uses ``current_app`` if not given, and falls back to the default +        encoder when not in an app context. +    :param kwargs: Extra arguments passed to :func:`json.dumps`. + +    .. versionchanged:: 1.0.3 + +        ``app`` can be passed directly, rather than requiring an app +        context for configuration. +    """ +    _load_arg_defaults(kwargs, app=app) +    if isinstance(s, bytes): +        encoding = kwargs.pop('encoding', None) +        if encoding is None: +            encoding = detect_encoding(s) +        s = s.decode(encoding) +    return _json.loads(s, **kwargs) + + +def load(fp, app=None, **kwargs): +    """Like :func:`loads` but reads from a file object.""" +    _load_arg_defaults(kwargs, app=app) +    if not PY2: +        fp = _wrap_reader_for_text(fp, kwargs.pop('encoding', None) or 'utf-8') +    return _json.load(fp, **kwargs) + + +def htmlsafe_dumps(obj, **kwargs): +    """Works exactly like :func:`dumps` but is safe for use in ``<script>`` +    tags.  It accepts the same arguments and returns a JSON string.  Note that +    this is available in templates through the ``|tojson`` filter which will +    also mark the result as safe.  Due to how this function escapes certain +    characters this is safe even if used outside of ``<script>`` tags. + +    The following characters are escaped in strings: + +    -   ``<`` +    -   ``>`` +    -   ``&`` +    -   ``'`` + +    This makes it safe to embed such strings in any place in HTML with the +    notable exception of double quoted attributes.  In that case single +    quote your attributes or HTML escape it in addition. + +    .. versionchanged:: 0.10 +       This function's return value is now always safe for HTML usage, even +       if outside of script tags or if used in XHTML.  This rule does not +       hold true when using this function in HTML attributes that are double +       quoted.  Always single quote attributes if you use the ``|tojson`` +       filter.  Alternatively use ``|tojson|forceescape``. +    """ +    rv = dumps(obj, **kwargs) \ +        .replace(u'<', u'\\u003c') \ +        .replace(u'>', u'\\u003e') \ +        .replace(u'&', u'\\u0026') \ +        .replace(u"'", u'\\u0027') +    if not _slash_escape: +        rv = rv.replace('\\/', '/') +    return rv + + +def htmlsafe_dump(obj, fp, **kwargs): +    """Like :func:`htmlsafe_dumps` but writes into a file object.""" +    fp.write(text_type(htmlsafe_dumps(obj, **kwargs))) + + +def jsonify(*args, **kwargs): +    """This function wraps :func:`dumps` to add a few enhancements that make +    life easier.  It turns the JSON output into a :class:`~flask.Response` +    object with the :mimetype:`application/json` mimetype.  For convenience, it +    also converts multiple arguments into an array or multiple keyword arguments +    into a dict.  This means that both ``jsonify(1,2,3)`` and +    ``jsonify([1,2,3])`` serialize to ``[1,2,3]``. + +    For clarity, the JSON serialization behavior has the following differences +    from :func:`dumps`: + +    1. Single argument: Passed straight through to :func:`dumps`. +    2. Multiple arguments: Converted to an array before being passed to +       :func:`dumps`. +    3. Multiple keyword arguments: Converted to a dict before being passed to +       :func:`dumps`. +    4. Both args and kwargs: Behavior undefined and will throw an exception. + +    Example usage:: + +        from flask import jsonify + +        @app.route('/_get_current_user') +        def get_current_user(): +            return jsonify(username=g.user.username, +                           email=g.user.email, +                           id=g.user.id) + +    This will send a JSON response like this to the browser:: + +        { +            "username": "admin", +            "email": "admin@localhost", +            "id": 42 +        } + + +    .. versionchanged:: 0.11 +       Added support for serializing top-level arrays. This introduces a +       security risk in ancient browsers. See :ref:`json-security` for details. + +    This function's response will be pretty printed if the +    ``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the +    Flask app is running in debug mode. Compressed (not pretty) formatting +    currently means no indents and no spaces after separators. + +    .. 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( +        dumps(data, indent=indent, separators=separators) + '\n', +        mimetype=current_app.config['JSONIFY_MIMETYPE'] +    ) + + +def tojson_filter(obj, **kwargs): +    return Markup(htmlsafe_dumps(obj, **kwargs)) diff --git a/python/flask/json/tag.py b/python/flask/json/tag.py new file mode 100644 index 0000000..11c966c --- /dev/null +++ b/python/flask/json/tag.py @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- +""" +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 seen an example that adds support for :class:`~collections.OrderedDict`. +Dicts don't have an order in Python or 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``. :: + +    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) + +:copyright: © 2010 by the Pallets team. +:license: BSD, see LICENSE for more details. +""" + +from base64 import b64decode, b64encode +from datetime import datetime +from uuid import UUID + +from jinja2 import Markup +from werkzeug.http import http_date, parse_date + +from flask._compat import iteritems, text_type +from flask.json import dumps, loads + + +class JSONTag(object): +    """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 = None + +    def __init__(self, serializer): +        """Create a tagger for the given serializer.""" +        self.serializer = serializer + +    def check(self, value): +        """Check if the given value should be tagged by this tag.""" +        raise NotImplementedError + +    def to_json(self, value): +        """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): +        """Convert the JSON representation back to the correct type. The tag +        will already be removed.""" +        raise NotImplementedError + +    def tag(self, value): +        """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): +        return ( +            isinstance(value, dict) +            and len(value) == 1 +            and next(iter(value)) in self.serializer.tags +        ) + +    def to_json(self, value): +        key = next(iter(value)) +        return {key + '__': self.serializer.tag(value[key])} + +    def to_python(self, value): +        key = next(iter(value)) +        return {key[:-2]: value[key]} + + +class PassDict(JSONTag): +    __slots__ = () + +    def check(self, value): +        return isinstance(value, dict) + +    def to_json(self, value): +        # JSON objects may only have string keys, so don't bother tagging the +        # key here. +        return dict((k, self.serializer.tag(v)) for k, v in iteritems(value)) + +    tag = to_json + + +class TagTuple(JSONTag): +    __slots__ = () +    key = ' t' + +    def check(self, value): +        return isinstance(value, tuple) + +    def to_json(self, value): +        return [self.serializer.tag(item) for item in value] + +    def to_python(self, value): +        return tuple(value) + + +class PassList(JSONTag): +    __slots__ = () + +    def check(self, value): +        return isinstance(value, list) + +    def to_json(self, value): +        return [self.serializer.tag(item) for item in value] + +    tag = to_json + + +class TagBytes(JSONTag): +    __slots__ = () +    key = ' b' + +    def check(self, value): +        return isinstance(value, bytes) + +    def to_json(self, value): +        return b64encode(value).decode('ascii') + +    def to_python(self, value): +        return b64decode(value) + + +class TagMarkup(JSONTag): +    """Serialize anything matching the :class:`~flask.Markup` API by +    having a ``__html__`` method to the result of that method. Always +    deserializes to an instance of :class:`~flask.Markup`.""" + +    __slots__ = () +    key = ' m' + +    def check(self, value): +        return callable(getattr(value, '__html__', None)) + +    def to_json(self, value): +        return text_type(value.__html__()) + +    def to_python(self, value): +        return Markup(value) + + +class TagUUID(JSONTag): +    __slots__ = () +    key = ' u' + +    def check(self, value): +        return isinstance(value, UUID) + +    def to_json(self, value): +        return value.hex + +    def to_python(self, value): +        return UUID(value) + + +class TagDateTime(JSONTag): +    __slots__ = () +    key = ' d' + +    def check(self, value): +        return isinstance(value, datetime) + +    def to_json(self, value): +        return http_date(value) + +    def to_python(self, value): +        return parse_date(value) + + +class TaggedJSONSerializer(object): +    """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:`~flask.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): +        self.tags = {} +        self.order = [] + +        for cls in self.default_tags: +            self.register(cls) + +    def register(self, tag_class, force=False, index=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("Tag '{0}' is already registered.".format(key)) + +            self.tags[key] = tag + +        if index is None: +            self.order.append(tag) +        else: +            self.order.insert(index, tag) + +    def tag(self, value): +        """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): +        """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): +        """Tag the value and dump it to a compact JSON string.""" +        return dumps(self.tag(value), separators=(',', ':')) + +    def loads(self, value): +        """Load data from a JSON string and deserialized any tagged objects.""" +        return loads(value, object_hook=self.untag) | 
