"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JSONPythonRenderer = void 0;
const collection_utils_1 = require("collection-utils");
const ConvenienceRenderer_js_1 = require("../../ConvenienceRenderer.js");
const Naming_js_1 = require("../../Naming.js");
const Source_js_1 = require("../../Source.js");
const Support_js_1 = require("../../support/Support.js");
const Transformers_js_1 = require("../../Transformers.js");
const TypeUtils_js_1 = require("../../Type/TypeUtils.js");
const PythonRenderer_js_1 = require("./PythonRenderer.js");
const utils_js_1 = require("./utils.js");
function compose(input, f) {
    if (typeof f === "function") {
        if (input.value !== undefined) {
            // `input` is a value, so just apply `f` to its source form.
            return { value: f(makeValue(input)) };
        }
        if (input.lambda !== undefined) {
            // `input` is a lambda, so build `lambda x: f(input(x))`.
            return {
                lambda: (0, Source_js_1.multiWord)(" ", "lambda x:", f([(0, Source_js_1.parenIfNeeded)(input.lambda), "(x)"])),
                value: undefined,
            };
        }
        // `input` is the identify function, so the composition is `lambda x: f(x)`.
        return {
            lambda: (0, Source_js_1.multiWord)(" ", "lambda x:", f("x")),
            value: undefined,
        };
    }
    if (f.value !== undefined) {
        return (0, Support_js_1.panic)("Cannot compose into a value");
    }
    if (f.lambda === undefined) {
        // `f` is the identity function, so the result is just `input`.
        return input;
    }
    if (input.value === undefined) {
        // `input` is a lambda
        if (input.lambda === undefined) {
            // `input` is the identity function, so the result is just `f`.
            return f;
        }
        // `input` is a lambda, so the result is `lambda x: f(input(x))`.
        return {
            lambda: (0, Source_js_1.multiWord)("", "lambda x: ", (0, Source_js_1.parenIfNeeded)(f.lambda), "(", (0, Source_js_1.parenIfNeeded)(input.lambda), "(x))"),
            value: undefined,
        };
    }
    // `input` is a value, so return `f(input)`.
    return { lambda: f.lambda, value: makeValue(input) };
}
const identity = { value: undefined };
// If `vol` is a lambda, return it in its source form.  If it's
// a value, return a `lambda` that returns the value.
function makeLambda(vol) {
    if (vol.lambda !== undefined) {
        if (vol.value === undefined) {
            return vol.lambda;
        }
        return (0, Source_js_1.multiWord)("", "lambda x: ", (0, Source_js_1.parenIfNeeded)(vol.lambda), "(", vol.value, ")");
    }
    if (vol.value !== undefined) {
        return (0, Source_js_1.multiWord)(" ", "lambda x:", vol.value);
    }
    return (0, Source_js_1.multiWord)(" ", "lambda x:", "x");
}
// If `vol` is a value, return the value in its source form.
// Calling this with `vol` being a lambda is not allowed.
function makeValue(vol) {
    if (vol.value === undefined) {
        return (0, Support_js_1.panic)("Cannot make value from lambda without value");
    }
    if (vol.lambda !== undefined) {
        return [(0, Source_js_1.parenIfNeeded)(vol.lambda), "(", vol.value, ")"];
    }
    return vol.value;
}
class JSONPythonRenderer extends PythonRenderer_js_1.PythonRenderer {
    constructor() {
        super(...arguments);
        this._deserializerFunctions = new Set();
        this._converterNamer = (0, Naming_js_1.funPrefixNamer)("converter", (s) => (0, utils_js_1.snakeNameStyle)(s, false, this.pyOptions.nicePropertyNames));
        this._topLevelConverterNames = new Map();
        this._haveTypeVar = false;
        this._haveEnumTypeVar = false;
        this._haveDateutil = false;
    }
    emitTypeVar(tvar, constraints) {
        if (!this.pyOptions.features.typeHints) {
            return;
        }
        this.emitLine(tvar, " = ", this.withTyping("TypeVar"), "(", this.string(tvar), constraints, ")");
    }
    typeVar() {
        this._haveTypeVar = true;
        // FIXME: This is ugly, but the code that requires the type variables, in
        // `emitImports` actually runs after imports have been imported.  The proper
        // solution would be to either allow more complex dependencies, or to
        // gather-emit the type variable declarations, too.  Unfortunately the
        // gather-emit is a bit buggy with blank lines, and I can't be bothered to
        // fix it now.
        this.withTyping("TypeVar");
        return "T";
    }
    enumTypeVar() {
        this._haveEnumTypeVar = true;
        // See the comment above.
        this.withTyping("TypeVar");
        this.withImport("enum", "Enum");
        return "EnumT";
    }
    cast(type, v) {
        if (!this.pyOptions.features.typeHints) {
            return v;
        }
        return [this.withTyping("cast"), "(", type, ", ", v, ")"];
    }
    emitNoneConverter() {
        // FIXME: We can't return the None type here because mypy thinks that means
        // We're not returning any value, when we're actually returning `None`.
        this.emitBlock([
            "def from_none(",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> ", this.withTyping("Any")),
            ":",
        ], () => {
            this.emitLine("assert x is None");
            this.emitLine("return x");
        });
    }
    emitBoolConverter() {
        this.emitBlock([
            "def from_bool(",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> bool"),
            ":",
        ], () => {
            this.emitLine("assert isinstance(x, bool)");
            this.emitLine("return x");
        });
    }
    emitIntConverter() {
        this.emitBlock([
            "def from_int(",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> int"),
            ":",
        ], () => {
            this.emitLine("assert isinstance(x, int) and not isinstance(x, bool)");
            this.emitLine("return x");
        });
    }
    emitFromFloatConverter() {
        this.emitBlock([
            "def from_float(",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> float"),
            ":",
        ], () => {
            this.emitLine("assert isinstance(x, (float, int)) and not isinstance(x, bool)");
            this.emitLine("return float(x)");
        });
    }
    emitToFloatConverter() {
        this.emitBlock([
            "def to_float(",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> float"),
            ":",
        ], () => {
            this.emitLine("assert isinstance(x, (int, float))");
            this.emitLine("return x");
        });
    }
    emitStrConverter() {
        this.emitBlock([
            "def from_str(",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> str"),
            ":",
        ], () => {
            const strType = "str";
            this.emitLine("assert isinstance(x, ", strType, ")");
            this.emitLine("return x");
        });
    }
    emitToEnumConverter() {
        const tvar = this.enumTypeVar();
        this.emitBlock([
            "def to_enum(c",
            this.typeHint(": ", this.withTyping("Type"), "[", tvar, "]"),
            ", ",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> ", tvar),
            ":",
        ], () => {
            this.emitLine("assert isinstance(x, c)");
            this.emitLine("return x.value");
        });
    }
    emitListConverter() {
        const tvar = this.typeVar();
        this.emitBlock([
            "def from_list(f",
            this.typeHint(": ", this.withTyping("Callable"), "[[", this.withTyping("Any"), "], ", tvar, "]"),
            ", ",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> ", this.withTyping("List"), "[", tvar, "]"),
            ":",
        ], () => {
            this.emitLine("assert isinstance(x, list)");
            this.emitLine("return [f(y) for y in x]");
        });
    }
    emitToClassConverter() {
        const tvar = this.typeVar();
        this.emitBlock([
            "def to_class(c",
            this.typeHint(": ", this.withTyping("Type"), "[", tvar, "]"),
            ", ",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> dict"),
            ":",
        ], () => {
            this.emitLine("assert isinstance(x, c)");
            this.emitLine("return ", this.cast(this.withTyping("Any"), "x"), ".to_dict()");
        });
    }
    emitDictConverter() {
        const tvar = this.typeVar();
        this.emitBlock([
            "def from_dict(f",
            this.typeHint(": ", this.withTyping("Callable"), "[[", this.withTyping("Any"), "], ", tvar, "]"),
            ", ",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> ", this.withTyping("Dict"), "[str, ", tvar, "]"),
            ":",
        ], () => {
            this.emitLine("assert isinstance(x, dict)");
            this.emitLine("return { k: f(v) for (k, v) in x.items() }");
        });
    }
    // This is not easily idiomatically typeable in Python.  See
    // https://stackoverflow.com/questions/51066468/computed-types-in-mypy/51084497
    emitUnionConverter() {
        this.emitMultiline(`def from_union(fs, x):
    for f in fs:
        try:
            return f(x)
        except:
            pass
    assert False`);
    }
    emitFromDatetimeConverter() {
        this.emitBlock([
            "def from_datetime(",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> ", this.withImport("datetime", "datetime")),
            ":",
        ], () => {
            this._haveDateutil = true;
            this.emitLine("return dateutil.parser.parse(x)");
        });
    }
    emitFromStringifiedBoolConverter() {
        this.emitBlock([
            "def from_stringified_bool(x",
            this.typeHint(": str"),
            ")",
            this.typeHint(" -> bool"),
            ":",
        ], () => {
            this.emitBlock('if x == "true":', () => this.emitLine("return True"));
            this.emitBlock('if x == "false":', () => this.emitLine("return False"));
            this.emitLine("assert False");
        });
    }
    emitIsTypeConverter() {
        const tvar = this.typeVar();
        this.emitBlock([
            "def is_type(t",
            this.typeHint(": ", this.withTyping("Type"), "[", tvar, "]"),
            ", ",
            this.typingDecl("x", "Any"),
            ")",
            this.typeHint(" -> ", tvar),
            ":",
        ], () => {
            this.emitLine("assert isinstance(x, t)");
            this.emitLine("return x");
        });
    }
    emitConverter(cf) {
        switch (cf) {
            case "none": {
                this.emitNoneConverter();
                return;
            }
            case "bool": {
                this.emitBoolConverter();
                return;
            }
            case "int": {
                this.emitIntConverter();
                return;
            }
            case "from-float": {
                this.emitFromFloatConverter();
                return;
            }
            case "to-float": {
                this.emitToFloatConverter();
                return;
            }
            case "str": {
                this.emitStrConverter();
                return;
            }
            case "to-enum": {
                this.emitToEnumConverter();
                return;
            }
            case "list": {
                this.emitListConverter();
                return;
            }
            case "to-class": {
                this.emitToClassConverter();
                return;
            }
            case "dict": {
                this.emitDictConverter();
                return;
            }
            case "union": {
                this.emitUnionConverter();
                return;
            }
            case "from-datetime": {
                this.emitFromDatetimeConverter();
                return;
            }
            case "from-stringified-bool": {
                this.emitFromStringifiedBoolConverter();
                return;
            }
            case "is-type": {
                this.emitIsTypeConverter();
                return;
            }
            default:
                (0, Support_js_1.assertNever)(cf);
        }
    }
    // Return the name of the Python converter function `cf`.
    conv(cf) {
        this._deserializerFunctions.add(cf);
        const name = cf.replace(/-/g, "_");
        if (cf.startsWith("from-") ||
            cf.startsWith("to-") ||
            cf.startsWith("is-"))
            return name;
        return ["from_", name];
    }
    // Applies the converter function to `arg`
    convFn(cf, arg) {
        return compose(arg, {
            lambda: (0, Source_js_1.singleWord)(this.conv(cf)),
            value: undefined,
        });
    }
    typeObject(t) {
        const s = (0, TypeUtils_js_1.matchType)(t, (_anyType) => undefined, (_nullType) => "type(None)", (_boolType) => "bool", (_integerType) => "int", (_doubleType) => "float", (_stringType) => "str", (_arrayType) => "List", (classType) => this.nameForNamedType(classType), (_mapType) => "dict", (enumType) => this.nameForNamedType(enumType), (_unionType) => undefined, (transformedStringType) => {
            if (transformedStringType.kind === "date-time") {
                return this.withImport("datetime", "datetime");
            }
            if (transformedStringType.kind === "uuid") {
                return this.withImport("uuid", "UUID");
            }
            return undefined;
        });
        if (s === undefined) {
            return (0, Support_js_1.panic)(`No type object for ${t.kind}`);
        }
        return s;
    }
    transformer(inputTransformer, xfer, targetType) {
        const consume = (consumer, vol) => {
            if (consumer === undefined) {
                return vol;
            }
            return this.transformer(vol, consumer, targetType);
        };
        const isType = (t, valueToCheck) => {
            return compose(valueToCheck, (v) => [
                this.conv("is-type"),
                "(",
                this.typeObject(t),
                ", ",
                v,
                ")",
            ]);
        };
        if (xfer instanceof Transformers_js_1.DecodingChoiceTransformer ||
            xfer instanceof Transformers_js_1.ChoiceTransformer) {
            const lambdas = xfer.transformers.map((x) => makeLambda(this.transformer(identity, x, targetType))
                .source);
            return compose(inputTransformer, (v) => [
                this.conv("union"),
                "([",
                (0, collection_utils_1.arrayIntercalate)(", ", lambdas),
                "], ",
                v,
                ")",
            ]);
        }
        if (xfer instanceof Transformers_js_1.DecodingTransformer) {
            const consumer = xfer.consumer;
            const vol = this.deserializer(inputTransformer, xfer.sourceType);
            return consume(consumer, vol);
        }
        if (xfer instanceof Transformers_js_1.EncodingTransformer) {
            return this.serializer(inputTransformer, xfer.sourceType);
        }
        if (xfer instanceof Transformers_js_1.UnionInstantiationTransformer) {
            return inputTransformer;
        }
        if (xfer instanceof Transformers_js_1.UnionMemberMatchTransformer) {
            const consumer = xfer.transformer;
            const vol = isType(xfer.memberType, inputTransformer);
            return consume(consumer, vol);
        }
        if (xfer instanceof Transformers_js_1.ParseStringTransformer) {
            const consumer = xfer.consumer;
            const immediateTargetType = consumer === undefined ? targetType : consumer.sourceType;
            let vol;
            switch (immediateTargetType.kind) {
                case "integer":
                    vol = compose(inputTransformer, (v) => ["int(", v, ")"]);
                    break;
                case "bool":
                    vol = this.convFn("from-stringified-bool", inputTransformer);
                    break;
                case "enum":
                    vol = this.deserializer(inputTransformer, immediateTargetType);
                    break;
                case "date-time":
                    vol = this.convFn("from-datetime", inputTransformer);
                    break;
                case "uuid":
                    vol = compose(inputTransformer, (v) => [
                        this.withImport("uuid", "UUID"),
                        "(",
                        v,
                        ")",
                    ]);
                    break;
                default:
                    return (0, Support_js_1.panic)(`Parsing of ${immediateTargetType.kind} in a transformer is not supported`);
            }
            return consume(consumer, vol);
        }
        if (xfer instanceof Transformers_js_1.StringifyTransformer) {
            const consumer = xfer.consumer;
            let vol;
            switch (xfer.sourceType.kind) {
                case "integer":
                    vol = compose(inputTransformer, (v) => ["str(", v, ")"]);
                    break;
                case "bool":
                    vol = compose(inputTransformer, (v) => [
                        "str(",
                        v,
                        ").lower()",
                    ]);
                    break;
                case "enum":
                    vol = this.serializer(inputTransformer, xfer.sourceType);
                    break;
                case "date-time":
                    vol = compose(inputTransformer, (v) => [v, ".isoformat()"]);
                    break;
                case "uuid":
                    vol = compose(inputTransformer, (v) => ["str(", v, ")"]);
                    break;
                default:
                    return (0, Support_js_1.panic)(`Parsing of ${xfer.sourceType.kind} in a transformer is not supported`);
            }
            return consume(consumer, vol);
        }
        return (0, Support_js_1.panic)(`Transformer ${xfer.kind} is not supported`);
    }
    // Returns the code to deserialize `value` as type `t`.  If `t` has
    // an associated transformer, the code for that transformer is
    // returned.
    deserializer(value, t) {
        const xf = (0, Transformers_js_1.transformationForType)(t);
        if (xf !== undefined) {
            return this.transformer(value, xf.transformer, xf.targetType);
        }
        return (0, TypeUtils_js_1.matchType)(t, (_anyType) => value, (_nullType) => this.convFn("none", value), (_boolType) => this.convFn("bool", value), (_integerType) => this.convFn("int", value), (_doubleType) => this.convFn("from-float", value), (_stringType) => this.convFn("str", value), (arrayType) => compose(value, (v) => [
            this.conv("list"),
            "(",
            makeLambda(this.deserializer(identity, arrayType.items))
                .source,
            ", ",
            v,
            ")",
        ]), (classType) => compose(value, {
            lambda: (0, Source_js_1.singleWord)(this.nameForNamedType(classType), ".from_dict"),
            value: undefined,
        }), (mapType) => compose(value, (v) => [
            this.conv("dict"),
            "(",
            makeLambda(this.deserializer(identity, mapType.values))
                .source,
            ", ",
            v,
            ")",
        ]), (enumType) => compose(value, {
            lambda: (0, Source_js_1.singleWord)(this.nameForNamedType(enumType)),
            value: undefined,
        }), (unionType) => {
            // FIXME: handle via transformers
            const deserializers = Array.from(unionType.members).map((m) => makeLambda(this.deserializer(identity, m)).source);
            return compose(value, (v) => [
                this.conv("union"),
                "([",
                (0, collection_utils_1.arrayIntercalate)(", ", deserializers),
                "], ",
                v,
                ")",
            ]);
        }, (transformedStringType) => {
            // FIXME: handle via transformers
            if (transformedStringType.kind === "date-time") {
                return this.convFn("from-datetime", value);
            }
            if (transformedStringType.kind === "uuid") {
                return compose(value, (v) => [
                    this.withImport("uuid", "UUID"),
                    "(",
                    v,
                    ")",
                ]);
            }
            return (0, Support_js_1.panic)(`Transformed type ${transformedStringType.kind} not supported`);
        });
    }
    serializer(value, t) {
        const xf = (0, Transformers_js_1.transformationForType)(t);
        if (xf !== undefined) {
            const reverse = xf.reverse;
            return this.transformer(value, reverse.transformer, reverse.targetType);
        }
        return (0, TypeUtils_js_1.matchType)(t, (_anyType) => value, (_nullType) => this.convFn("none", value), (_boolType) => this.convFn("bool", value), (_integerType) => this.convFn("int", value), (_doubleType) => this.convFn("to-float", value), (_stringType) => this.convFn("str", value), (arrayType) => compose(value, (v) => [
            this.conv("list"),
            "(",
            makeLambda(this.serializer(identity, arrayType.items))
                .source,
            ", ",
            v,
            ")",
        ]), (classType) => compose(value, (v) => [
            this.conv("to-class"),
            "(",
            this.nameForNamedType(classType),
            ", ",
            v,
            ")",
        ]), (mapType) => compose(value, (v) => [
            this.conv("dict"),
            "(",
            makeLambda(this.serializer(identity, mapType.values))
                .source,
            ", ",
            v,
            ")",
        ]), (enumType) => compose(value, (v) => [
            this.conv("to-enum"),
            "(",
            this.nameForNamedType(enumType),
            ", ",
            v,
            ")",
        ]), (unionType) => {
            const serializers = Array.from(unionType.members).map((m) => makeLambda(this.serializer(identity, m)).source);
            return compose(value, (v) => [
                this.conv("union"),
                "([",
                (0, collection_utils_1.arrayIntercalate)(", ", serializers),
                "], ",
                v,
                ")",
            ]);
        }, (transformedStringType) => {
            if (transformedStringType.kind === "date-time") {
                return compose(value, (v) => [v, ".isoformat()"]);
            }
            if (transformedStringType.kind === "uuid") {
                return compose(value, (v) => ["str(", v, ")"]);
            }
            return (0, Support_js_1.panic)(`Transformed type ${transformedStringType.kind} not supported`);
        });
    }
    emitClassMembers(t) {
        super.emitClassMembers(t);
        this.ensureBlankLine();
        const className = this.nameForNamedType(t);
        this.emitLine("@staticmethod");
        this.emitBlock([
            "def from_dict(",
            this.typingDecl("obj", "Any"),
            ")",
            this.typeHint(" -> ", this.namedType(t)),
            ":",
        ], () => {
            const args = [];
            this.emitLine("assert isinstance(obj, dict)");
            this.forEachClassProperty(t, "none", (name, jsonName, cp) => {
                const property = {
                    value: ["obj.get(", this.string(jsonName), ")"],
                };
                this.emitLine(name, " = ", makeValue(this.deserializer(property, cp.type)));
                args.push(name);
            });
            this.emitLine("return ", className, "(", (0, collection_utils_1.arrayIntercalate)(", ", args), ")");
        });
        this.ensureBlankLine();
        this.emitBlock(["def to_dict(self)", this.typeHint(" -> dict"), ":"], () => {
            this.emitLine("result", this.typeHint(": dict"), " = {}");
            this.forEachClassProperty(t, "none", (name, jsonName, cp) => {
                const property = { value: ["self.", name] };
                if (cp.isOptional) {
                    this.emitBlock(["if self.", name, " is not None:"], () => {
                        this.emitLine("result[", this.string(jsonName), "] = ", makeValue(this.serializer(property, cp.type)));
                    });
                }
                else {
                    this.emitLine("result[", this.string(jsonName), "] = ", makeValue(this.serializer(property, cp.type)));
                }
            });
            this.emitLine("return result");
        });
    }
    emitImports() {
        super.emitImports();
        if (this._haveDateutil) {
            this.emitLine("import dateutil.parser");
        }
        if (!this._haveTypeVar && !this._haveEnumTypeVar)
            return;
        this.ensureBlankLine(2);
        if (this._haveTypeVar) {
            this.emitTypeVar(this.typeVar(), []);
        }
        if (this._haveEnumTypeVar) {
            this.emitTypeVar(this.enumTypeVar(), [
                ", bound=",
                this.withImport("enum", "Enum"),
            ]);
        }
    }
    emitSupportCode() {
        const map = Array.from(this._deserializerFunctions).map((f) => [f, f]);
        this.forEachWithBlankLines(map, ["interposing", 2], (cf) => {
            this.emitConverter(cf);
        });
    }
    makeTopLevelDependencyNames(_t, topLevelName) {
        const fromDict = new Naming_js_1.DependencyName(this._converterNamer, ConvenienceRenderer_js_1.topLevelNameOrder, (l) => `${l(topLevelName)}_from_dict`);
        const toDict = new Naming_js_1.DependencyName(this._converterNamer, ConvenienceRenderer_js_1.topLevelNameOrder, (l) => `${l(topLevelName)}_to_dict`);
        this._topLevelConverterNames.set(topLevelName, { fromDict, toDict });
        return [fromDict, toDict];
    }
    emitDefaultLeadingComments() {
        this.ensureBlankLine();
        if (this._haveDateutil) {
            this.emitCommentLines([
                "This code parses date/times, so please",
                "",
                "    pip install python-dateutil",
                "",
            ]);
        }
        this.emitCommentLines([
            "To use this code, make sure you",
            "",
            "    import json",
            "",
            "and then, to convert JSON from a string, do",
            "",
        ]);
        this.forEachTopLevel("none", (_, name) => {
            const { fromDict } = (0, Support_js_1.defined)(this._topLevelConverterNames.get(name));
            this.emitLine(this.commentLineStart, "    result = ", fromDict, "(json.loads(json_string))");
        });
    }
    emitClosingCode() {
        this.forEachTopLevel(["interposing", 2], (t, name) => {
            const { fromDict, toDict } = (0, Support_js_1.defined)(this._topLevelConverterNames.get(name));
            const pythonType = this.pythonType(t);
            this.emitBlock([
                "def ",
                fromDict,
                "(",
                this.typingDecl("s", "Any"),
                ")",
                this.typeHint(" -> ", pythonType),
                ":",
            ], () => {
                this.emitLine("return ", makeValue(this.deserializer({ value: "s" }, t)));
            });
            this.ensureBlankLine(2);
            this.emitBlock([
                "def ",
                toDict,
                "(x",
                this.typeHint(": ", pythonType),
                ")",
                this.typingReturn("Any"),
                ":",
            ], () => {
                this.emitLine("return ", makeValue(this.serializer({ value: "x" }, t)));
            });
        });
    }
}
exports.JSONPythonRenderer = JSONPythonRenderer;
