Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions packages/quicktype-core/src/language/Python/JSONPythonRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,20 @@ export class JSONPythonRenderer extends PythonRenderer {
);
}

protected typingTypeHint(tvar: Sourcelike): Sourcelike {
if (!this.pyOptions.features.typingType) {
return [];
}

return this.typeHint(": ", this.withTyping("Type"), "[", tvar, "]");
}

protected emitToEnumConverter(): void {
const tvar = this.enumTypeVar();
this.emitBlock(
[
"def to_enum(c",
this.typeHint(": ", this.withTyping("Type"), "[", tvar, "]"),
this.typingTypeHint(tvar),
", ",
this.typingDecl("x", "Any"),
")",
Expand Down Expand Up @@ -393,7 +401,7 @@ export class JSONPythonRenderer extends PythonRenderer {
this.emitBlock(
[
"def to_class(c",
this.typeHint(": ", this.withTyping("Type"), "[", tvar, "]"),
this.typingTypeHint(tvar),
", ",
this.typingDecl("x", "Any"),
")",
Expand Down Expand Up @@ -500,7 +508,7 @@ export class JSONPythonRenderer extends PythonRenderer {
this.emitBlock(
[
"def is_type(t",
this.typeHint(": ", this.withTyping("Type"), "[", tvar, "]"),
this.typingTypeHint(tvar),
", ",
this.typingDecl("x", "Any"),
")",
Expand Down
7 changes: 7 additions & 0 deletions packages/quicktype-core/src/language/Python/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface PythonFeatures {
builtinGenerics: boolean;
dataClasses: boolean;
typeHints: boolean;
/** `typing.Type`, unavailable in Python 3.6.0 */
typingType: boolean;
/** PEP 604 union operators (`str | None`), Python 3.10+ */
unionOperators: boolean;
}
Expand All @@ -36,30 +38,35 @@ export const pythonOptions = {
{
"3.5": {
typeHints: false,
typingType: false,
dataClasses: false,
builtinGenerics: false,
unionOperators: false,
},
"3.6": {
typeHints: true,
typingType: false,
dataClasses: false,
builtinGenerics: false,
unionOperators: false,
},
"3.7": {
typeHints: true,
typingType: true,
dataClasses: true,
builtinGenerics: false,
unionOperators: false,
},
"3.9": {
typeHints: true,
typingType: true,
dataClasses: true,
builtinGenerics: true,
unionOperators: false,
},
"3.10": {
typeHints: true,
typingType: true,
dataClasses: true,
builtinGenerics: true,
unionOperators: true,
Expand Down
79 changes: 79 additions & 0 deletions test/unit/python-typing-type.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Python fixtures exercise every version preset, but cannot verify that the
// generated imports also work on the first Python 3.6 micro-version.
import { describe, expect, test } from "vitest";

import {
InputData,
JSONSchemaInput,
jsonInputForTargetLanguage,
quicktype,
} from "quicktype-core";

const schema = {
type: "object",
properties: {
child: {
type: "object",
properties: { name: { type: "string" } },
required: ["name"],
},
status: { type: "string", enum: ["ready", "done"] },
value: { oneOf: [{ type: "integer" }, { type: "string" }] },
},
required: ["child", "status", "value"],
};

async function pythonFor(version: "3.6" | "3.7"): Promise<string> {
const schemaInput = new JSONSchemaInput(undefined);
await schemaInput.addSource({
name: "TopLevel",
schema: JSON.stringify(schema),
});
const inputData = new InputData();
inputData.addInput(schemaInput);
const schemaResult = await quicktype({
inputData,
lang: "python",
rendererOptions: { "python-version": version },
});

// An inferred integer-string union exercises the is_type helper.
const jsonInput = jsonInputForTargetLanguage("python");
await jsonInput.addSource({
name: "MixedValues",
samples: ['{"mixed":[null,1,"1",{}]}'],
});
const jsonInputData = new InputData();
jsonInputData.addInput(jsonInput);
const jsonResult = await quicktype({
inputData: jsonInputData,
lang: "python",
rendererOptions: { "python-version": version },
});

return [...schemaResult.lines, ...jsonResult.lines].join("\n");
}

describe("Python typing.Type compatibility (issue #1728)", () => {
test("Python 3.6 does not import or use typing.Type", async () => {
const output = await pythonFor("3.6");

expect(output).toContain('T = TypeVar("T")');
expect(output).not.toMatch(/from typing import [^\n]*\bType\b/);
expect(output).not.toMatch(/\bType\[/);
expect(output).toContain("def to_class(c, x: Any) -> dict:");
expect(output).toContain("def to_enum(c, x: Any) -> EnumT:");
expect(output).toContain("def is_type(t, x: Any) -> T:");
});

test("Python 3.7 keeps typing.Type annotations", async () => {
const output = await pythonFor("3.7");

expect(output).toMatch(/from typing import [^\n]*\bType\b/);
expect(output).toContain("def to_class(c: Type[T], x: Any) -> dict:");
expect(output).toContain(
"def to_enum(c: Type[EnumT], x: Any) -> EnumT:",
);
expect(output).toContain("def is_type(t: Type[T], x: Any) -> T:");
});
});
Loading