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
97 changes: 94 additions & 3 deletions packages/quicktype-core/src/language/Python/JSONPythonRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export type ConverterFunction =
| "to-class"
| "dict"
| "union"
| "from-date"
| "from-time"
| "from-datetime"
| "from-stringified-bool"
| "is-type";
Expand Down Expand Up @@ -458,13 +460,64 @@ export class JSONPythonRenderer extends PythonRenderer {
assert False`);
}

protected emitFromDateConverter(): void {
this.emitBlock(
[
"def from_date(",
this.typingDecl("x", "Any"),
")",
this.typeHint(" -> ", [
this.withModuleImport("datetime"),
".date",
]),
":",
],
() => {
this._haveDateutil = true;
this.emitLine(
"assert isinstance(x, str) and ",
this.withModuleImport("re"),
'.match(r"^\\d{4}-\\d{2}-\\d{2}$", x)',
);
this.emitLine("return dateutil.parser.parse(x).date()");
},
);
}

protected emitFromTimeConverter(): void {
this.emitBlock(
[
"def from_time(",
this.typingDecl("x", "Any"),
")",
this.typeHint(" -> ", [
this.withModuleImport("datetime"),
".time",
]),
":",
],
() => {
this._haveDateutil = true;
this.emitLine(
"assert isinstance(x, str) and ",
this.withModuleImport("re"),
'.match(r"^\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-]\\d{2}:\\d{2})?$", x)',
);
this.emitLine("return dateutil.parser.parse(x).time()");
},
);
}

protected emitFromDatetimeConverter(): void {
this.emitBlock(
[
"def from_datetime(",
this.typingDecl("x", "Any"),
")",
this.typeHint(" -> ", this.withImport("datetime", "datetime")),
this.typeHint(" -> ", [
this.withModuleImport("datetime"),
".datetime",
]),
":",
],
() => {
Expand Down Expand Up @@ -571,6 +624,16 @@ export class JSONPythonRenderer extends PythonRenderer {
return;
}

case "from-date": {
this.emitFromDateConverter();
return;
}

case "from-time": {
this.emitFromTimeConverter();
return;
}

case "from-datetime": {
this.emitFromDatetimeConverter();
return;
Expand Down Expand Up @@ -628,8 +691,16 @@ export class JSONPythonRenderer extends PythonRenderer {
(enumType) => this.nameForNamedType(enumType),
(_unionType) => undefined,
(transformedStringType) => {
if (transformedStringType.kind === "date") {
return [this.withModuleImport("datetime"), ".date"];
}

if (transformedStringType.kind === "time") {
return [this.withModuleImport("datetime"), ".time"];
}

if (transformedStringType.kind === "date-time") {
return this.withImport("datetime", "datetime");
return [this.withModuleImport("datetime"), ".datetime"];
}

if (transformedStringType.kind === "uuid") {
Expand Down Expand Up @@ -731,6 +802,12 @@ export class JSONPythonRenderer extends PythonRenderer {
immediateTargetType,
);
break;
case "date":
vol = this.convFn("from-date", inputTransformer);
break;
case "time":
vol = this.convFn("from-time", inputTransformer);
break;
case "date-time":
vol = this.convFn("from-datetime", inputTransformer);
break;
Expand Down Expand Up @@ -767,6 +844,8 @@ export class JSONPythonRenderer extends PythonRenderer {
case "enum":
vol = this.serializer(inputTransformer, xfer.sourceType);
break;
case "date":
case "time":
case "date-time":
vol = compose(inputTransformer, (v) => [v, ".isoformat()"]);
break;
Expand Down Expand Up @@ -851,6 +930,14 @@ export class JSONPythonRenderer extends PythonRenderer {
},
(transformedStringType) => {
// FIXME: handle via transformers
if (transformedStringType.kind === "date") {
return this.convFn("from-date", value);
}

if (transformedStringType.kind === "time") {
return this.convFn("from-time", value);
}

if (transformedStringType.kind === "date-time") {
return this.convFn("from-datetime", value);
}
Expand Down Expand Up @@ -942,7 +1029,11 @@ export class JSONPythonRenderer extends PythonRenderer {
]);
},
(transformedStringType) => {
if (transformedStringType.kind === "date-time") {
if (
transformedStringType.kind === "date" ||
transformedStringType.kind === "time" ||
transformedStringType.kind === "date-time"
) {
return compose(value, (v) => [v, ".isoformat()"]);
}

Expand Down
20 changes: 19 additions & 1 deletion packages/quicktype-core/src/language/Python/PythonRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import { classNameStyle, snakeNameStyle } from "./utils.js";
export class PythonRenderer extends ConvenienceRenderer {
private readonly imports: Map<string, Set<string>> = new Map();

private readonly moduleImports: Set<string> = new Set();

private readonly declaredTypes: Set<Type> = new Set();

public constructor(
Expand Down Expand Up @@ -132,6 +134,11 @@ export class PythonRenderer extends ConvenienceRenderer {
return name;
}

protected withModuleImport(module: string): Sourcelike {
this.moduleImports.add(module);
return module;
}

protected withTyping(name: string): Sourcelike {
return this.withImport("typing", name);
}
Expand Down Expand Up @@ -319,8 +326,16 @@ export class PythonRenderer extends ConvenienceRenderer {
];
},
(transformedStringType) => {
if (transformedStringType.kind === "date") {
return [this.withModuleImport("datetime"), ".date"];
}

if (transformedStringType.kind === "time") {
return [this.withModuleImport("datetime"), ".time"];
}

if (transformedStringType.kind === "date-time") {
return this.withImport("datetime", "datetime");
return [this.withModuleImport("datetime"), ".datetime"];
}

if (transformedStringType.kind === "uuid") {
Expand Down Expand Up @@ -481,6 +496,9 @@ export class PythonRenderer extends ConvenienceRenderer {
}

protected emitImports(): void {
this.moduleImports.forEach((module) => {
this.emitLine("import ", module);
});
this.imports.forEach((names, module) => {
this.emitLine(
"from ",
Expand Down
7 changes: 3 additions & 4 deletions packages/quicktype-core/src/language/Python/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,9 @@ export class PythonTargetLanguage extends TargetLanguage<
public get stringTypeMapping(): StringTypeMapping {
const mapping: Map<TransformedStringTypeKind, PrimitiveStringTypeKind> =
new Map();
const dateTimeType = "date-time";
mapping.set("date", dateTimeType);
mapping.set("time", dateTimeType);
mapping.set("date-time", dateTimeType);
mapping.set("date", "date");
mapping.set("time", "time");
mapping.set("date-time", "date-time");
mapping.set("uuid", "uuid");
mapping.set("integer-string", "integer-string");
mapping.set("bool-string", "bool-string");
Expand Down
10 changes: 9 additions & 1 deletion test/fixtures/python/main.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import quicktype
import datetime
import json
import sys
import io

f = io.open(sys.argv[1], mode="r", encoding="utf-8")
obj = quicktype.top_level_from_dict(json.load(f))
input_obj = json.load(f)
obj = quicktype.top_level_from_dict(input_obj)

if isinstance(input_obj, dict) and {"date", "time", "date-time"} <= input_obj.keys():
assert type(obj.date) is datetime.date
assert type(obj.time) is datetime.time
assert type(obj.date_time) is datetime.datetime

print(json.dumps(quicktype.top_level_to_dict(obj)))
Loading