@typespec/emitter-framework 0.15.0-dev.3 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -16
- package/dist/src/python/builtins.d.ts +14 -0
- package/dist/src/python/builtins.d.ts.map +1 -0
- package/dist/src/python/builtins.js +29 -0
- package/dist/src/python/builtins.js.map +1 -0
- package/dist/src/python/components/array-expression/array-expression.d.ts +6 -0
- package/dist/src/python/components/array-expression/array-expression.d.ts.map +1 -0
- package/dist/src/python/components/array-expression/array-expression.js +11 -0
- package/dist/src/python/components/array-expression/array-expression.js.map +1 -0
- package/dist/src/python/components/array-expression/array-expression.test.d.ts +2 -0
- package/dist/src/python/components/array-expression/array-expression.test.d.ts.map +1 -0
- package/dist/src/python/components/array-expression/array-expression.test.js +19 -0
- package/dist/src/python/components/array-expression/array-expression.test.js.map +1 -0
- package/dist/src/python/components/array-expression/index.d.ts +2 -0
- package/dist/src/python/components/array-expression/index.d.ts.map +1 -0
- package/dist/src/python/components/array-expression/index.js +2 -0
- package/dist/src/python/components/array-expression/index.js.map +1 -0
- package/dist/src/python/components/atom/atom.d.ts +33 -0
- package/dist/src/python/components/atom/atom.d.ts.map +1 -0
- package/dist/src/python/components/atom/atom.js +88 -0
- package/dist/src/python/components/atom/atom.js.map +1 -0
- package/dist/src/python/components/atom/atom.test.d.ts +2 -0
- package/dist/src/python/components/atom/atom.test.d.ts.map +1 -0
- package/dist/src/python/components/atom/atom.test.js +224 -0
- package/dist/src/python/components/atom/atom.test.js.map +1 -0
- package/dist/src/python/components/atom/index.d.ts +2 -0
- package/dist/src/python/components/atom/index.d.ts.map +1 -0
- package/dist/src/python/components/atom/index.js +2 -0
- package/dist/src/python/components/atom/index.js.map +1 -0
- package/dist/src/python/components/class-declaration/class-bases.d.ts +45 -0
- package/dist/src/python/components/class-declaration/class-bases.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/class-bases.js +84 -0
- package/dist/src/python/components/class-declaration/class-bases.js.map +1 -0
- package/dist/src/python/components/class-declaration/class-body.d.ts +16 -0
- package/dist/src/python/components/class-declaration/class-body.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/class-body.js +52 -0
- package/dist/src/python/components/class-declaration/class-body.js.map +1 -0
- package/dist/src/python/components/class-declaration/class-declaration.d.ts +23 -0
- package/dist/src/python/components/class-declaration/class-declaration.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/class-declaration.js +118 -0
- package/dist/src/python/components/class-declaration/class-declaration.js.map +1 -0
- package/dist/src/python/components/class-declaration/class-declaration.test.d.ts +2 -0
- package/dist/src/python/components/class-declaration/class-declaration.test.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/class-declaration.test.js +1527 -0
- package/dist/src/python/components/class-declaration/class-declaration.test.js.map +1 -0
- package/dist/src/python/components/class-declaration/class-member.d.ts +16 -0
- package/dist/src/python/components/class-declaration/class-member.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/class-member.js +71 -0
- package/dist/src/python/components/class-declaration/class-member.js.map +1 -0
- package/dist/src/python/components/class-declaration/class-member.test.d.ts +2 -0
- package/dist/src/python/components/class-declaration/class-member.test.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/class-member.test.js +202 -0
- package/dist/src/python/components/class-declaration/class-member.test.js.map +1 -0
- package/dist/src/python/components/class-declaration/class-method.d.ts +22 -0
- package/dist/src/python/components/class-declaration/class-method.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/class-method.js +76 -0
- package/dist/src/python/components/class-declaration/class-method.js.map +1 -0
- package/dist/src/python/components/class-declaration/class-method.test.d.ts +2 -0
- package/dist/src/python/components/class-declaration/class-method.test.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/class-method.test.js +298 -0
- package/dist/src/python/components/class-declaration/class-method.test.js.map +1 -0
- package/dist/src/python/components/class-declaration/index.d.ts +7 -0
- package/dist/src/python/components/class-declaration/index.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/index.js +7 -0
- package/dist/src/python/components/class-declaration/index.js.map +1 -0
- package/dist/src/python/components/class-declaration/primitive-initializer.d.ts +22 -0
- package/dist/src/python/components/class-declaration/primitive-initializer.d.ts.map +1 -0
- package/dist/src/python/components/class-declaration/primitive-initializer.js +67 -0
- package/dist/src/python/components/class-declaration/primitive-initializer.js.map +1 -0
- package/dist/src/python/components/doc-element/doc-element.d.ts +46 -0
- package/dist/src/python/components/doc-element/doc-element.d.ts.map +1 -0
- package/dist/src/python/components/doc-element/doc-element.js +59 -0
- package/dist/src/python/components/doc-element/doc-element.js.map +1 -0
- package/dist/src/python/components/doc-element/index.d.ts +2 -0
- package/dist/src/python/components/doc-element/index.d.ts.map +1 -0
- package/dist/src/python/components/doc-element/index.js +2 -0
- package/dist/src/python/components/doc-element/index.js.map +1 -0
- package/dist/src/python/components/enum-declaration/enum-declaration.d.ts +8 -0
- package/dist/src/python/components/enum-declaration/enum-declaration.d.ts.map +1 -0
- package/dist/src/python/components/enum-declaration/enum-declaration.js +80 -0
- package/dist/src/python/components/enum-declaration/enum-declaration.js.map +1 -0
- package/dist/src/python/components/enum-declaration/enum-declaration.test.d.ts +2 -0
- package/dist/src/python/components/enum-declaration/enum-declaration.test.d.ts.map +1 -0
- package/dist/src/python/components/enum-declaration/enum-declaration.test.js +344 -0
- package/dist/src/python/components/enum-declaration/enum-declaration.test.js.map +1 -0
- package/dist/src/python/components/enum-declaration/enum-member.d.ts +9 -0
- package/dist/src/python/components/enum-declaration/enum-member.d.ts.map +1 -0
- package/dist/src/python/components/enum-declaration/enum-member.js +22 -0
- package/dist/src/python/components/enum-declaration/enum-member.js.map +1 -0
- package/dist/src/python/components/enum-declaration/index.d.ts +3 -0
- package/dist/src/python/components/enum-declaration/index.d.ts.map +1 -0
- package/dist/src/python/components/enum-declaration/index.js +3 -0
- package/dist/src/python/components/enum-declaration/index.js.map +1 -0
- package/dist/src/python/components/function-declaration/function-declaration.d.ts +24 -0
- package/dist/src/python/components/function-declaration/function-declaration.d.ts.map +1 -0
- package/dist/src/python/components/function-declaration/function-declaration.js +68 -0
- package/dist/src/python/components/function-declaration/function-declaration.js.map +1 -0
- package/dist/src/python/components/function-declaration/function-declaration.test.d.ts +2 -0
- package/dist/src/python/components/function-declaration/function-declaration.test.d.ts.map +1 -0
- package/dist/src/python/components/function-declaration/function-declaration.test.js +682 -0
- package/dist/src/python/components/function-declaration/function-declaration.test.js.map +1 -0
- package/dist/src/python/components/function-declaration/index.d.ts +2 -0
- package/dist/src/python/components/function-declaration/index.d.ts.map +1 -0
- package/dist/src/python/components/function-declaration/index.js +2 -0
- package/dist/src/python/components/function-declaration/index.js.map +1 -0
- package/dist/src/python/components/index.d.ts +12 -0
- package/dist/src/python/components/index.d.ts.map +1 -0
- package/dist/src/python/components/index.js +12 -0
- package/dist/src/python/components/index.js.map +1 -0
- package/dist/src/python/components/protocol-declaration/callable-parameters.d.ts +25 -0
- package/dist/src/python/components/protocol-declaration/callable-parameters.d.ts.map +1 -0
- package/dist/src/python/components/protocol-declaration/callable-parameters.js +33 -0
- package/dist/src/python/components/protocol-declaration/callable-parameters.js.map +1 -0
- package/dist/src/python/components/protocol-declaration/index.d.ts +3 -0
- package/dist/src/python/components/protocol-declaration/index.d.ts.map +1 -0
- package/dist/src/python/components/protocol-declaration/index.js +3 -0
- package/dist/src/python/components/protocol-declaration/index.js.map +1 -0
- package/dist/src/python/components/protocol-declaration/protocol-declaration.d.ts +8 -0
- package/dist/src/python/components/protocol-declaration/protocol-declaration.d.ts.map +1 -0
- package/dist/src/python/components/protocol-declaration/protocol-declaration.js +86 -0
- package/dist/src/python/components/protocol-declaration/protocol-declaration.js.map +1 -0
- package/dist/src/python/components/protocol-declaration/protocol-declaration.test.d.ts +2 -0
- package/dist/src/python/components/protocol-declaration/protocol-declaration.test.d.ts.map +1 -0
- package/dist/src/python/components/protocol-declaration/protocol-declaration.test.js +117 -0
- package/dist/src/python/components/protocol-declaration/protocol-declaration.test.js.map +1 -0
- package/dist/src/python/components/record-expression/index.d.ts +2 -0
- package/dist/src/python/components/record-expression/index.d.ts.map +1 -0
- package/dist/src/python/components/record-expression/index.js +2 -0
- package/dist/src/python/components/record-expression/index.js.map +1 -0
- package/dist/src/python/components/record-expression/record-expression.d.ts +6 -0
- package/dist/src/python/components/record-expression/record-expression.d.ts.map +1 -0
- package/dist/src/python/components/record-expression/record-expression.js +13 -0
- package/dist/src/python/components/record-expression/record-expression.js.map +1 -0
- package/dist/src/python/components/record-expression/record-expression.test.d.ts +2 -0
- package/dist/src/python/components/record-expression/record-expression.test.d.ts.map +1 -0
- package/dist/src/python/components/record-expression/record-expression.test.js +19 -0
- package/dist/src/python/components/record-expression/record-expression.test.js.map +1 -0
- package/dist/src/python/components/type-alias-declaration/index.d.ts +2 -0
- package/dist/src/python/components/type-alias-declaration/index.d.ts.map +1 -0
- package/dist/src/python/components/type-alias-declaration/index.js +2 -0
- package/dist/src/python/components/type-alias-declaration/index.js.map +1 -0
- package/dist/src/python/components/type-alias-declaration/type-alias-declaration.d.ts +16 -0
- package/dist/src/python/components/type-alias-declaration/type-alias-declaration.d.ts.map +1 -0
- package/dist/src/python/components/type-alias-declaration/type-alias-declaration.js +75 -0
- package/dist/src/python/components/type-alias-declaration/type-alias-declaration.js.map +1 -0
- package/dist/src/python/components/type-alias-declaration/type-alias-declaration.test.d.ts +2 -0
- package/dist/src/python/components/type-alias-declaration/type-alias-declaration.test.d.ts.map +1 -0
- package/dist/src/python/components/type-alias-declaration/type-alias-declaration.test.js +140 -0
- package/dist/src/python/components/type-alias-declaration/type-alias-declaration.test.js.map +1 -0
- package/dist/src/python/components/type-declaration/index.d.ts +2 -0
- package/dist/src/python/components/type-declaration/index.d.ts.map +1 -0
- package/dist/src/python/components/type-declaration/index.js +2 -0
- package/dist/src/python/components/type-declaration/index.js.map +1 -0
- package/dist/src/python/components/type-declaration/type-declaration.d.ts +11 -0
- package/dist/src/python/components/type-declaration/type-declaration.d.ts.map +1 -0
- package/dist/src/python/components/type-declaration/type-declaration.js +31 -0
- package/dist/src/python/components/type-declaration/type-declaration.js.map +1 -0
- package/dist/src/python/components/type-declaration/type-declaration.test.d.ts +2 -0
- package/dist/src/python/components/type-declaration/type-declaration.test.d.ts.map +1 -0
- package/dist/src/python/components/type-declaration/type-declaration.test.js +69 -0
- package/dist/src/python/components/type-declaration/type-declaration.test.js.map +1 -0
- package/dist/src/python/components/type-expression/index.d.ts +2 -0
- package/dist/src/python/components/type-expression/index.d.ts.map +1 -0
- package/dist/src/python/components/type-expression/index.js +2 -0
- package/dist/src/python/components/type-expression/index.js.map +1 -0
- package/dist/src/python/components/type-expression/type-expression.d.ts +12 -0
- package/dist/src/python/components/type-expression/type-expression.d.ts.map +1 -0
- package/dist/src/python/components/type-expression/type-expression.js +348 -0
- package/dist/src/python/components/type-expression/type-expression.js.map +1 -0
- package/dist/src/python/components/type-expression/type-expression.test.d.ts +2 -0
- package/dist/src/python/components/type-expression/type-expression.test.d.ts.map +1 -0
- package/dist/src/python/components/type-expression/type-expression.test.js +503 -0
- package/dist/src/python/components/type-expression/type-expression.test.js.map +1 -0
- package/dist/src/python/index.d.ts +4 -0
- package/dist/src/python/index.d.ts.map +1 -0
- package/dist/src/python/index.js +4 -0
- package/dist/src/python/index.js.map +1 -0
- package/dist/src/python/lib.d.ts +68 -0
- package/dist/src/python/lib.d.ts.map +1 -0
- package/dist/src/python/lib.js +38 -0
- package/dist/src/python/lib.js.map +1 -0
- package/dist/src/python/test-utils.d.ts +8 -0
- package/dist/src/python/test-utils.d.ts.map +1 -0
- package/dist/src/python/test-utils.js +25 -0
- package/dist/src/python/test-utils.js.map +1 -0
- package/dist/src/python/utils/index.d.ts +4 -0
- package/dist/src/python/utils/index.d.ts.map +1 -0
- package/dist/src/python/utils/index.js +4 -0
- package/dist/src/python/utils/index.js.map +1 -0
- package/dist/src/python/utils/operation.d.ts +27 -0
- package/dist/src/python/utils/operation.d.ts.map +1 -0
- package/dist/src/python/utils/operation.js +139 -0
- package/dist/src/python/utils/operation.js.map +1 -0
- package/dist/src/python/utils/refkey.d.ts +23 -0
- package/dist/src/python/utils/refkey.d.ts.map +1 -0
- package/dist/src/python/utils/refkey.js +36 -0
- package/dist/src/python/utils/refkey.js.map +1 -0
- package/dist/src/python/utils/type.d.ts +19 -0
- package/dist/src/python/utils/type.d.ts.map +1 -0
- package/dist/src/python/utils/type.js +24 -0
- package/dist/src/python/utils/type.js.map +1 -0
- package/package.json +12 -5
- package/package.json.bak +14 -6
- package/src/python/builtins.ts +43 -0
- package/src/python/components/array-expression/array-expression.test.tsx +14 -0
- package/src/python/components/array-expression/array-expression.tsx +11 -0
- package/src/python/components/array-expression/index.ts +1 -0
- package/src/python/components/atom/atom.test.tsx +244 -0
- package/src/python/components/atom/atom.tsx +95 -0
- package/src/python/components/atom/index.ts +1 -0
- package/src/python/components/class-declaration/class-bases.tsx +92 -0
- package/src/python/components/class-declaration/class-body.tsx +56 -0
- package/src/python/components/class-declaration/class-declaration.test.tsx +1414 -0
- package/src/python/components/class-declaration/class-declaration.tsx +116 -0
- package/src/python/components/class-declaration/class-member.test.tsx +194 -0
- package/src/python/components/class-declaration/class-member.tsx +67 -0
- package/src/python/components/class-declaration/class-method.test.tsx +250 -0
- package/src/python/components/class-declaration/class-method.tsx +97 -0
- package/src/python/components/class-declaration/index.ts +6 -0
- package/src/python/components/class-declaration/primitive-initializer.tsx +62 -0
- package/src/python/components/doc-element/doc-element.tsx +83 -0
- package/src/python/components/doc-element/index.ts +1 -0
- package/src/python/components/enum-declaration/enum-declaration.test.tsx +319 -0
- package/src/python/components/enum-declaration/enum-declaration.tsx +77 -0
- package/src/python/components/enum-declaration/enum-member.tsx +21 -0
- package/src/python/components/enum-declaration/index.ts +2 -0
- package/src/python/components/function-declaration/function-declaration.test.tsx +582 -0
- package/src/python/components/function-declaration/function-declaration.tsx +90 -0
- package/src/python/components/function-declaration/index.ts +1 -0
- package/src/python/components/index.ts +11 -0
- package/src/python/components/protocol-declaration/callable-parameters.tsx +44 -0
- package/src/python/components/protocol-declaration/index.ts +2 -0
- package/src/python/components/protocol-declaration/protocol-declaration.test.tsx +106 -0
- package/src/python/components/protocol-declaration/protocol-declaration.tsx +73 -0
- package/src/python/components/record-expression/index.ts +1 -0
- package/src/python/components/record-expression/record-expression.test.tsx +14 -0
- package/src/python/components/record-expression/record-expression.tsx +13 -0
- package/src/python/components/type-alias-declaration/index.ts +1 -0
- package/src/python/components/type-alias-declaration/type-alias-declaration.test.tsx +117 -0
- package/src/python/components/type-alias-declaration/type-alias-declaration.tsx +67 -0
- package/src/python/components/type-declaration/index.ts +1 -0
- package/src/python/components/type-declaration/type-declaration.test.tsx +58 -0
- package/src/python/components/type-declaration/type-declaration.tsx +26 -0
- package/src/python/components/type-expression/index.ts +1 -0
- package/src/python/components/type-expression/type-expression.test.tsx +463 -0
- package/src/python/components/type-expression/type-expression.tsx +333 -0
- package/src/python/index.ts +3 -0
- package/src/python/lib.ts +40 -0
- package/src/python/test-utils.tsx +31 -0
- package/src/python/utils/index.ts +3 -0
- package/src/python/utils/operation.ts +161 -0
- package/src/python/utils/refkey.ts +36 -0
- package/src/python/utils/type.ts +31 -0
|
@@ -0,0 +1,1414 @@
|
|
|
1
|
+
import { Tester } from "#test/test-host.js";
|
|
2
|
+
import { List } from "@alloy-js/core";
|
|
3
|
+
import * as py from "@alloy-js/python";
|
|
4
|
+
import { t } from "@typespec/compiler/testing";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { ClassDeclaration } from "../../../../src/python/components/class-declaration/class-declaration.js";
|
|
7
|
+
import { Method } from "../../../../src/python/components/class-declaration/class-method.js";
|
|
8
|
+
import { EnumDeclaration } from "../../../../src/python/components/enum-declaration/enum-declaration.js";
|
|
9
|
+
import { getOutput } from "../../test-utils.js";
|
|
10
|
+
import { TypeAliasDeclaration } from "../type-alias-declaration/type-alias-declaration.js";
|
|
11
|
+
|
|
12
|
+
describe("Python Class from model", () => {
|
|
13
|
+
it("creates a class", async () => {
|
|
14
|
+
const { program, Widget } = await Tester.compile(t.code`
|
|
15
|
+
|
|
16
|
+
model ${t.model("Widget")} {
|
|
17
|
+
id: string;
|
|
18
|
+
weight: int32;
|
|
19
|
+
aliases: string[];
|
|
20
|
+
isActive: boolean;
|
|
21
|
+
color: "blue" | "red";
|
|
22
|
+
promotionalPrice: float64;
|
|
23
|
+
description?: string = "This is a widget";
|
|
24
|
+
createdAt: int64 = 1717334400;
|
|
25
|
+
tags: string[] = #["tag1", "tag2"];
|
|
26
|
+
isDeleted: boolean = false;
|
|
27
|
+
alternativeColor: "green" | "yellow" = "green";
|
|
28
|
+
price: float64 = 100.0;
|
|
29
|
+
}
|
|
30
|
+
`);
|
|
31
|
+
|
|
32
|
+
expect(getOutput(program, [<ClassDeclaration type={Widget} />])).toRenderTo(
|
|
33
|
+
`
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
from typing import Literal
|
|
36
|
+
from typing import Optional
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(kw_only=True)
|
|
40
|
+
class Widget:
|
|
41
|
+
id: str
|
|
42
|
+
weight: int
|
|
43
|
+
aliases: list[str]
|
|
44
|
+
is_active: bool
|
|
45
|
+
color: Literal["blue", "red"]
|
|
46
|
+
promotional_price: float
|
|
47
|
+
description: Optional[str] = "This is a widget"
|
|
48
|
+
created_at: int = 1717334400
|
|
49
|
+
tags: list[str] = ["tag1", "tag2"]
|
|
50
|
+
is_deleted: bool = False
|
|
51
|
+
alternative_color: Literal["green", "yellow"] = "green"
|
|
52
|
+
price: float = 100.0
|
|
53
|
+
|
|
54
|
+
`,
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("creates a class with non-default values followed by default values", async () => {
|
|
59
|
+
const { program, Widget } = await Tester.compile(t.code`
|
|
60
|
+
|
|
61
|
+
model ${t.model("Widget")} {
|
|
62
|
+
id: string;
|
|
63
|
+
description?: string = "This is a widget";
|
|
64
|
+
}
|
|
65
|
+
`);
|
|
66
|
+
|
|
67
|
+
expect(getOutput(program, [<ClassDeclaration type={Widget} />])).toRenderTo(
|
|
68
|
+
`
|
|
69
|
+
from dataclasses import dataclass
|
|
70
|
+
from typing import Optional
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(kw_only=True)
|
|
74
|
+
class Widget:
|
|
75
|
+
id: str
|
|
76
|
+
description: Optional[str] = "This is a widget"
|
|
77
|
+
|
|
78
|
+
`,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("creates a class with non-default values followed by default values", async () => {
|
|
83
|
+
const { program, Widget } = await Tester.compile(t.code`
|
|
84
|
+
|
|
85
|
+
model ${t.model("Widget")} {
|
|
86
|
+
description?: string = "This is a widget";
|
|
87
|
+
id: string;
|
|
88
|
+
}
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
expect(getOutput(program, [<ClassDeclaration type={Widget} />])).toRenderTo(
|
|
92
|
+
`
|
|
93
|
+
from dataclasses import dataclass
|
|
94
|
+
from typing import Optional
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(kw_only=True)
|
|
98
|
+
class Widget:
|
|
99
|
+
description: Optional[str] = "This is a widget"
|
|
100
|
+
id: str
|
|
101
|
+
|
|
102
|
+
`,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("declares a class with multi line docs", async () => {
|
|
107
|
+
const { program, Foo } = await Tester.compile(t.code`
|
|
108
|
+
/**
|
|
109
|
+
* This is a test
|
|
110
|
+
* with multiple lines
|
|
111
|
+
*/
|
|
112
|
+
model ${t.model("Foo")} {
|
|
113
|
+
knownProp: string;
|
|
114
|
+
}
|
|
115
|
+
`);
|
|
116
|
+
|
|
117
|
+
expect(getOutput(program, [<ClassDeclaration type={Foo} />])).toRenderTo(
|
|
118
|
+
`
|
|
119
|
+
from dataclasses import dataclass
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass(kw_only=True)
|
|
123
|
+
class Foo:
|
|
124
|
+
"""
|
|
125
|
+
This is a test
|
|
126
|
+
with multiple lines
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
known_prop: str
|
|
130
|
+
|
|
131
|
+
`,
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("declares a class overriding docs", async () => {
|
|
136
|
+
const { program, Foo } = await Tester.compile(t.code`
|
|
137
|
+
/**
|
|
138
|
+
* This is a test
|
|
139
|
+
* with multiple lines
|
|
140
|
+
*/
|
|
141
|
+
model ${t.model("Foo")} {
|
|
142
|
+
knownProp: string;
|
|
143
|
+
}
|
|
144
|
+
`);
|
|
145
|
+
|
|
146
|
+
expect(
|
|
147
|
+
getOutput(program, [
|
|
148
|
+
<ClassDeclaration
|
|
149
|
+
type={Foo}
|
|
150
|
+
doc={["This is an overridden doc comment\nwith multiple lines"]}
|
|
151
|
+
/>,
|
|
152
|
+
]),
|
|
153
|
+
).toRenderTo(
|
|
154
|
+
`
|
|
155
|
+
from dataclasses import dataclass
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@dataclass(kw_only=True)
|
|
159
|
+
class Foo:
|
|
160
|
+
"""
|
|
161
|
+
This is an overridden doc comment
|
|
162
|
+
with multiple lines
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
known_prop: str
|
|
166
|
+
|
|
167
|
+
`,
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("declares a class overriding docs with paragraphs array", async () => {
|
|
172
|
+
const { program, Foo } = await Tester.compile(t.code`
|
|
173
|
+
/**
|
|
174
|
+
* Base doc will be overridden
|
|
175
|
+
*/
|
|
176
|
+
model ${t.model("Foo")} {
|
|
177
|
+
knownProp: string;
|
|
178
|
+
}
|
|
179
|
+
`);
|
|
180
|
+
|
|
181
|
+
expect(
|
|
182
|
+
getOutput(program, [
|
|
183
|
+
<ClassDeclaration type={Foo} doc={["First paragraph", "Second paragraph"]} />,
|
|
184
|
+
]),
|
|
185
|
+
).toRenderTo(
|
|
186
|
+
`
|
|
187
|
+
from dataclasses import dataclass
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass(kw_only=True)
|
|
191
|
+
class Foo:
|
|
192
|
+
"""
|
|
193
|
+
First paragraph
|
|
194
|
+
|
|
195
|
+
Second paragraph
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
known_prop: str
|
|
199
|
+
|
|
200
|
+
`,
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("declares a class overriding docs with prebuilt ClassDoc", async () => {
|
|
205
|
+
const { program, Foo } = await Tester.compile(t.code`
|
|
206
|
+
/**
|
|
207
|
+
* Base doc will be overridden
|
|
208
|
+
*/
|
|
209
|
+
model ${t.model("Foo")} {
|
|
210
|
+
knownProp: string;
|
|
211
|
+
}
|
|
212
|
+
`);
|
|
213
|
+
|
|
214
|
+
expect(
|
|
215
|
+
getOutput(program, [
|
|
216
|
+
<ClassDeclaration type={Foo} doc={<py.ClassDoc description={[<>Alpha</>, <>Beta</>]} />} />,
|
|
217
|
+
]),
|
|
218
|
+
).toRenderTo(
|
|
219
|
+
`
|
|
220
|
+
from dataclasses import dataclass
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@dataclass(kw_only=True)
|
|
224
|
+
class Foo:
|
|
225
|
+
"""
|
|
226
|
+
Alpha
|
|
227
|
+
|
|
228
|
+
Beta
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
known_prop: str
|
|
232
|
+
|
|
233
|
+
`,
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("declares a class from a model with @doc", async () => {
|
|
238
|
+
const { program, Foo } = await Tester.compile(t.code`
|
|
239
|
+
@doc("This is a test")
|
|
240
|
+
model ${t.model("Foo")} {
|
|
241
|
+
knownProp: string;
|
|
242
|
+
}
|
|
243
|
+
`);
|
|
244
|
+
|
|
245
|
+
expect(getOutput(program, [<ClassDeclaration type={Foo} />])).toRenderTo(
|
|
246
|
+
`
|
|
247
|
+
from dataclasses import dataclass
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@dataclass(kw_only=True)
|
|
251
|
+
class Foo:
|
|
252
|
+
"""
|
|
253
|
+
This is a test
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
known_prop: str
|
|
257
|
+
|
|
258
|
+
`,
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("declares a model property with @doc", async () => {
|
|
263
|
+
const { program, Foo } = await Tester.compile(t.code`
|
|
264
|
+
/**
|
|
265
|
+
* This is a test
|
|
266
|
+
*/
|
|
267
|
+
model ${t.model("Foo")} {
|
|
268
|
+
@doc("This is a known property")
|
|
269
|
+
knownProp: string;
|
|
270
|
+
}
|
|
271
|
+
`);
|
|
272
|
+
|
|
273
|
+
expect(getOutput(program, [<ClassDeclaration type={Foo} />])).toRenderTo(
|
|
274
|
+
`
|
|
275
|
+
from dataclasses import dataclass
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dataclass(kw_only=True)
|
|
279
|
+
class Foo:
|
|
280
|
+
"""
|
|
281
|
+
This is a test
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
# This is a known property
|
|
285
|
+
known_prop: str
|
|
286
|
+
|
|
287
|
+
`,
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("throws error for model is Record<T>", async () => {
|
|
292
|
+
const { program, Person } = await Tester.compile(t.code`
|
|
293
|
+
model ${t.model("Person")} is Record<string>;
|
|
294
|
+
`);
|
|
295
|
+
|
|
296
|
+
expect(() => {
|
|
297
|
+
expect(getOutput(program, [<ClassDeclaration type={Person} />])).toRenderTo("");
|
|
298
|
+
}).toThrow(/Models with additional properties \(Record\[…\]\) are not supported/);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("throws error for model is Record<string> with properties", async () => {
|
|
302
|
+
const { program, Person } = await Tester.compile(t.code`
|
|
303
|
+
model ${t.model("Person")} is Record<string> {
|
|
304
|
+
name: string;
|
|
305
|
+
}
|
|
306
|
+
`);
|
|
307
|
+
|
|
308
|
+
expect(() => {
|
|
309
|
+
expect(getOutput(program, [<ClassDeclaration type={Person} />])).toRenderTo("");
|
|
310
|
+
}).toThrow(/Models with additional properties \(Record\[…\]\) are not supported/);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("throws error for model extends Record<string>", async () => {
|
|
314
|
+
const { program, Person } = await Tester.compile(t.code`
|
|
315
|
+
model ${t.model("Person")} extends Record<string> {
|
|
316
|
+
name: string;
|
|
317
|
+
}
|
|
318
|
+
`);
|
|
319
|
+
|
|
320
|
+
expect(() => {
|
|
321
|
+
expect(getOutput(program, [<ClassDeclaration type={Person} />])).toRenderTo("");
|
|
322
|
+
}).toThrow(/Models with additional properties \(Record\[…\]\) are not supported/);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("throws error for model with ...Record<string>", async () => {
|
|
326
|
+
const { program, Person } = await Tester.compile(t.code`
|
|
327
|
+
model ${t.model("Person")} {
|
|
328
|
+
age: int32;
|
|
329
|
+
...Record<string>;
|
|
330
|
+
}
|
|
331
|
+
`);
|
|
332
|
+
|
|
333
|
+
expect(() => {
|
|
334
|
+
expect(getOutput(program, [<ClassDeclaration type={Person} />])).toRenderTo("");
|
|
335
|
+
}).toThrow(/Models with additional properties \(Record\[…\]\) are not supported/);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("creates a class from a model that 'is' an array ", async () => {
|
|
339
|
+
const { program, Foo } = await Tester.compile(t.code`
|
|
340
|
+
model ${t.model("Foo")} is Array<string>;
|
|
341
|
+
`);
|
|
342
|
+
|
|
343
|
+
expect(getOutput(program, [<ClassDeclaration type={Foo} />])).toRenderTo(
|
|
344
|
+
`
|
|
345
|
+
class Foo(list[str]):
|
|
346
|
+
pass
|
|
347
|
+
|
|
348
|
+
`,
|
|
349
|
+
);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("handles a type reference to a union variant in a class property", async () => {
|
|
353
|
+
const { program, Color, Widget } = await Tester.compile(t.code`
|
|
354
|
+
union ${t.union("Color")} {
|
|
355
|
+
red: "RED",
|
|
356
|
+
blue: "BLUE",
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
model ${t.model("Widget")} {
|
|
360
|
+
id: string = "123";
|
|
361
|
+
weight: int32 = 100;
|
|
362
|
+
color: Color.blue;
|
|
363
|
+
}
|
|
364
|
+
`);
|
|
365
|
+
|
|
366
|
+
expect(
|
|
367
|
+
getOutput(program, [<EnumDeclaration type={Color} />, <ClassDeclaration type={Widget} />]),
|
|
368
|
+
).toRenderTo(
|
|
369
|
+
`
|
|
370
|
+
from dataclasses import dataclass
|
|
371
|
+
from enum import StrEnum
|
|
372
|
+
from typing import Literal
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
class Color(StrEnum):
|
|
376
|
+
RED = "RED"
|
|
377
|
+
BLUE = "BLUE"
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@dataclass(kw_only=True)
|
|
381
|
+
class Widget:
|
|
382
|
+
id: str = "123"
|
|
383
|
+
weight: int = 100
|
|
384
|
+
color: Literal[Color.BLUE]
|
|
385
|
+
|
|
386
|
+
`,
|
|
387
|
+
);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("handles a union of variant references in a class property", async () => {
|
|
391
|
+
const { program, Color, Widget } = await Tester.compile(t.code`
|
|
392
|
+
union ${t.union("Color")} {
|
|
393
|
+
red: "RED",
|
|
394
|
+
blue: "BLUE",
|
|
395
|
+
green: "GREEN",
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
model ${t.model("Widget")} {
|
|
399
|
+
id: string;
|
|
400
|
+
primaryColor: Color.red | Color.blue;
|
|
401
|
+
}
|
|
402
|
+
`);
|
|
403
|
+
|
|
404
|
+
expect(
|
|
405
|
+
getOutput(program, [<EnumDeclaration type={Color} />, <ClassDeclaration type={Widget} />]),
|
|
406
|
+
).toRenderTo(
|
|
407
|
+
`
|
|
408
|
+
from dataclasses import dataclass
|
|
409
|
+
from enum import StrEnum
|
|
410
|
+
from typing import Literal
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class Color(StrEnum):
|
|
414
|
+
RED = "RED"
|
|
415
|
+
BLUE = "BLUE"
|
|
416
|
+
GREEN = "GREEN"
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@dataclass(kw_only=True)
|
|
420
|
+
class Widget:
|
|
421
|
+
id: str
|
|
422
|
+
primary_color: Literal[Color.RED, Color.BLUE]
|
|
423
|
+
|
|
424
|
+
`,
|
|
425
|
+
);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it("handles a union of integer literals in a class property", async () => {
|
|
429
|
+
const { program, Widget } = await Tester.compile(t.code`
|
|
430
|
+
model ${t.model("Widget")} {
|
|
431
|
+
id: string;
|
|
432
|
+
priority: 1 | 2 | 3;
|
|
433
|
+
}
|
|
434
|
+
`);
|
|
435
|
+
|
|
436
|
+
expect(getOutput(program, [<ClassDeclaration type={Widget} />])).toRenderTo(
|
|
437
|
+
`
|
|
438
|
+
from dataclasses import dataclass
|
|
439
|
+
from typing import Literal
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
@dataclass(kw_only=True)
|
|
443
|
+
class Widget:
|
|
444
|
+
id: str
|
|
445
|
+
priority: Literal[1, 2, 3]
|
|
446
|
+
|
|
447
|
+
`,
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("handles a union of boolean literals in a class property", async () => {
|
|
452
|
+
const { program, Widget } = await Tester.compile(t.code`
|
|
453
|
+
model ${t.model("Widget")} {
|
|
454
|
+
id: string;
|
|
455
|
+
isActiveOrEnabled: true | false;
|
|
456
|
+
}
|
|
457
|
+
`);
|
|
458
|
+
|
|
459
|
+
expect(getOutput(program, [<ClassDeclaration type={Widget} />])).toRenderTo(
|
|
460
|
+
`
|
|
461
|
+
from dataclasses import dataclass
|
|
462
|
+
from typing import Literal
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@dataclass(kw_only=True)
|
|
466
|
+
class Widget:
|
|
467
|
+
id: str
|
|
468
|
+
is_active_or_enabled: Literal[True, False]
|
|
469
|
+
|
|
470
|
+
`,
|
|
471
|
+
);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("handles a mixed union of literals and variant references", async () => {
|
|
475
|
+
const { program, Color, Widget } = await Tester.compile(t.code`
|
|
476
|
+
union ${t.union("Color")} {
|
|
477
|
+
red: "RED",
|
|
478
|
+
blue: "BLUE",
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
model ${t.model("Widget")} {
|
|
482
|
+
id: string;
|
|
483
|
+
mixedValue: "custom" | 42 | true | Color.red;
|
|
484
|
+
}
|
|
485
|
+
`);
|
|
486
|
+
|
|
487
|
+
expect(
|
|
488
|
+
getOutput(program, [<EnumDeclaration type={Color} />, <ClassDeclaration type={Widget} />]),
|
|
489
|
+
).toRenderTo(
|
|
490
|
+
`
|
|
491
|
+
from dataclasses import dataclass
|
|
492
|
+
from enum import StrEnum
|
|
493
|
+
from typing import Literal
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
class Color(StrEnum):
|
|
497
|
+
RED = "RED"
|
|
498
|
+
BLUE = "BLUE"
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
@dataclass(kw_only=True)
|
|
502
|
+
class Widget:
|
|
503
|
+
id: str
|
|
504
|
+
mixed_value: Literal["custom", 42, True, Color.RED]
|
|
505
|
+
|
|
506
|
+
`,
|
|
507
|
+
);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it("renders a never-typed member as typing.Never", async () => {
|
|
511
|
+
const { program, Widget } = await Tester.compile(t.code`
|
|
512
|
+
model ${t.model("Widget")} {
|
|
513
|
+
property: never;
|
|
514
|
+
}
|
|
515
|
+
`);
|
|
516
|
+
|
|
517
|
+
expect(getOutput(program, [<ClassDeclaration type={Widget} />])).toRenderTo(`
|
|
518
|
+
from dataclasses import dataclass
|
|
519
|
+
from typing import Never
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
@dataclass(kw_only=True)
|
|
523
|
+
class Widget:
|
|
524
|
+
property: Never
|
|
525
|
+
|
|
526
|
+
`);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("can override class name", async () => {
|
|
530
|
+
const { program, Widget } = await Tester.compile(t.code`
|
|
531
|
+
model ${t.model("Widget")} {
|
|
532
|
+
id: string;
|
|
533
|
+
weight: int32;
|
|
534
|
+
color: "blue" | "red";
|
|
535
|
+
}
|
|
536
|
+
`);
|
|
537
|
+
|
|
538
|
+
expect(getOutput(program, [<ClassDeclaration name="MyOperations" type={Widget} />]))
|
|
539
|
+
.toRenderTo(`
|
|
540
|
+
from dataclasses import dataclass
|
|
541
|
+
from typing import Literal
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
@dataclass(kw_only=True)
|
|
545
|
+
class MyOperations:
|
|
546
|
+
id: str
|
|
547
|
+
weight: int
|
|
548
|
+
color: Literal["blue", "red"]
|
|
549
|
+
|
|
550
|
+
`);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("can add a members to the class", async () => {
|
|
554
|
+
const { program, Widget } = await Tester.compile(t.code`
|
|
555
|
+
model ${t.model("Widget")} {
|
|
556
|
+
id: string;
|
|
557
|
+
weight: int32;
|
|
558
|
+
color: "blue" | "red";
|
|
559
|
+
}
|
|
560
|
+
`);
|
|
561
|
+
|
|
562
|
+
expect(
|
|
563
|
+
getOutput(program, [
|
|
564
|
+
<ClassDeclaration name="MyOperations" type={Widget}>
|
|
565
|
+
<hbr />
|
|
566
|
+
<List>
|
|
567
|
+
<>custom_property: str</>
|
|
568
|
+
</List>
|
|
569
|
+
</ClassDeclaration>,
|
|
570
|
+
]),
|
|
571
|
+
).toRenderTo(`
|
|
572
|
+
from dataclasses import dataclass
|
|
573
|
+
from typing import Literal
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
@dataclass(kw_only=True)
|
|
577
|
+
class MyOperations:
|
|
578
|
+
id: str
|
|
579
|
+
weight: int
|
|
580
|
+
color: Literal["blue", "red"]
|
|
581
|
+
custom_property: str
|
|
582
|
+
|
|
583
|
+
`);
|
|
584
|
+
});
|
|
585
|
+
it("creates a class from a model with extends", async () => {
|
|
586
|
+
const { program, Widget, ErrorWidget } = await Tester.compile(t.code`
|
|
587
|
+
model ${t.model("Widget")} {
|
|
588
|
+
id: string;
|
|
589
|
+
weight: int32;
|
|
590
|
+
color: "blue" | "red";
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
model ${t.model("ErrorWidget")} extends Widget {
|
|
594
|
+
code: int32;
|
|
595
|
+
message: string;
|
|
596
|
+
}
|
|
597
|
+
`);
|
|
598
|
+
|
|
599
|
+
expect(
|
|
600
|
+
getOutput(program, [
|
|
601
|
+
<ClassDeclaration type={Widget} />,
|
|
602
|
+
<ClassDeclaration type={ErrorWidget} />,
|
|
603
|
+
]),
|
|
604
|
+
).toRenderTo(`
|
|
605
|
+
from dataclasses import dataclass
|
|
606
|
+
from typing import Literal
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
@dataclass(kw_only=True)
|
|
610
|
+
class Widget:
|
|
611
|
+
id: str
|
|
612
|
+
weight: int
|
|
613
|
+
color: Literal["blue", "red"]
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@dataclass(kw_only=True)
|
|
617
|
+
class ErrorWidget(Widget):
|
|
618
|
+
code: int
|
|
619
|
+
message: str
|
|
620
|
+
|
|
621
|
+
`);
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
describe("Python Class from interface", () => {
|
|
626
|
+
it("creates a class from an interface declaration", async () => {
|
|
627
|
+
const { program, WidgetOperations } = await Tester.compile(t.code`
|
|
628
|
+
interface ${t.interface("WidgetOperations")} {
|
|
629
|
+
op getName(id: string): string;
|
|
630
|
+
}
|
|
631
|
+
`);
|
|
632
|
+
|
|
633
|
+
expect(getOutput(program, [<ClassDeclaration type={WidgetOperations} />])).toRenderTo(`
|
|
634
|
+
from abc import ABC
|
|
635
|
+
from abc import abstractmethod
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
class WidgetOperations(ABC):
|
|
639
|
+
@abstractmethod
|
|
640
|
+
def get_name(self, id: str) -> str:
|
|
641
|
+
pass
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
`);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it("should handle spread and non spread interface parameters", async () => {
|
|
648
|
+
const { program, Foo, WidgetOperations } = await Tester.compile(t.code`
|
|
649
|
+
model ${t.model("Foo")} {
|
|
650
|
+
name: string
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
interface ${t.interface("WidgetOperations")} {
|
|
654
|
+
op getName(foo: Foo): string;
|
|
655
|
+
op getOtherName(...Foo): string
|
|
656
|
+
}
|
|
657
|
+
`);
|
|
658
|
+
|
|
659
|
+
expect(
|
|
660
|
+
getOutput(program, [
|
|
661
|
+
<ClassDeclaration type={Foo} />,
|
|
662
|
+
<ClassDeclaration type={WidgetOperations} />,
|
|
663
|
+
]),
|
|
664
|
+
).toRenderTo(`
|
|
665
|
+
from abc import ABC
|
|
666
|
+
from abc import abstractmethod
|
|
667
|
+
from dataclasses import dataclass
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
@dataclass(kw_only=True)
|
|
671
|
+
class Foo:
|
|
672
|
+
name: str
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
class WidgetOperations(ABC):
|
|
676
|
+
@abstractmethod
|
|
677
|
+
def get_name(self, foo: Foo) -> str:
|
|
678
|
+
pass
|
|
679
|
+
|
|
680
|
+
@abstractmethod
|
|
681
|
+
def get_other_name(self, name: str) -> str:
|
|
682
|
+
pass
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
`);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("creates a class from an interface with Model references", async () => {
|
|
689
|
+
const { program, WidgetOperations, Widget } = await Tester.compile(t.code`
|
|
690
|
+
/**
|
|
691
|
+
* Operations for Widget
|
|
692
|
+
*/
|
|
693
|
+
interface ${t.interface("WidgetOperations")} {
|
|
694
|
+
/**
|
|
695
|
+
* Get the name of the widget
|
|
696
|
+
*/
|
|
697
|
+
op getName(
|
|
698
|
+
/**
|
|
699
|
+
* The id of the widget
|
|
700
|
+
*/
|
|
701
|
+
id: string
|
|
702
|
+
): Widget;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
model ${t.model("Widget")} {
|
|
706
|
+
id: string;
|
|
707
|
+
weight: int32;
|
|
708
|
+
color: "blue" | "red";
|
|
709
|
+
}
|
|
710
|
+
`);
|
|
711
|
+
|
|
712
|
+
expect(
|
|
713
|
+
getOutput(program, [
|
|
714
|
+
<ClassDeclaration type={WidgetOperations} />,
|
|
715
|
+
<ClassDeclaration type={Widget} />,
|
|
716
|
+
]),
|
|
717
|
+
).toRenderTo(`
|
|
718
|
+
from abc import ABC
|
|
719
|
+
from abc import abstractmethod
|
|
720
|
+
from dataclasses import dataclass
|
|
721
|
+
from typing import Literal
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
class WidgetOperations(ABC):
|
|
725
|
+
"""
|
|
726
|
+
Operations for Widget
|
|
727
|
+
"""
|
|
728
|
+
|
|
729
|
+
@abstractmethod
|
|
730
|
+
def get_name(self, id: str) -> Widget:
|
|
731
|
+
"""
|
|
732
|
+
Get the name of the widget
|
|
733
|
+
"""
|
|
734
|
+
pass
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
@dataclass(kw_only=True)
|
|
739
|
+
class Widget:
|
|
740
|
+
id: str
|
|
741
|
+
weight: int
|
|
742
|
+
color: Literal["blue", "red"]
|
|
743
|
+
|
|
744
|
+
`);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
it("creates a class from an interface that extends another", async () => {
|
|
748
|
+
const { program, WidgetOperations, WidgetOperationsExtended, Widget } =
|
|
749
|
+
await Tester.compile(t.code`
|
|
750
|
+
interface ${t.interface("WidgetOperations")} {
|
|
751
|
+
op getName(id: string): Widget;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
interface ${t.interface("WidgetOperationsExtended")} extends WidgetOperations{
|
|
755
|
+
op delete(id: string): void;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
model ${t.model("Widget")} {
|
|
759
|
+
id: string;
|
|
760
|
+
weight: int32;
|
|
761
|
+
color: "blue" | "red";
|
|
762
|
+
}
|
|
763
|
+
`);
|
|
764
|
+
|
|
765
|
+
expect(
|
|
766
|
+
getOutput(program, [
|
|
767
|
+
<ClassDeclaration type={WidgetOperations} />,
|
|
768
|
+
<ClassDeclaration type={WidgetOperationsExtended} />,
|
|
769
|
+
<ClassDeclaration type={Widget} />,
|
|
770
|
+
]),
|
|
771
|
+
).toRenderTo(`
|
|
772
|
+
from abc import ABC
|
|
773
|
+
from abc import abstractmethod
|
|
774
|
+
from dataclasses import dataclass
|
|
775
|
+
from typing import Literal
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
class WidgetOperations(ABC):
|
|
779
|
+
@abstractmethod
|
|
780
|
+
def get_name(self, id: str) -> Widget:
|
|
781
|
+
pass
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
class WidgetOperationsExtended(ABC):
|
|
786
|
+
@abstractmethod
|
|
787
|
+
def get_name(self, id: str) -> Widget:
|
|
788
|
+
pass
|
|
789
|
+
|
|
790
|
+
@abstractmethod
|
|
791
|
+
def delete(self, id: str) -> None:
|
|
792
|
+
pass
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
@dataclass(kw_only=True)
|
|
797
|
+
class Widget:
|
|
798
|
+
id: str
|
|
799
|
+
weight: int
|
|
800
|
+
color: Literal["blue", "red"]
|
|
801
|
+
|
|
802
|
+
`);
|
|
803
|
+
});
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
describe("Python Class overrides", () => {
|
|
807
|
+
it("creates a class with a method if a model is provided and a class method is provided", async () => {
|
|
808
|
+
const { program, WidgetOperations } = await Tester.compile(t.code`
|
|
809
|
+
model ${t.model("WidgetOperations")} {
|
|
810
|
+
id: string;
|
|
811
|
+
weight: int32;
|
|
812
|
+
}
|
|
813
|
+
`);
|
|
814
|
+
|
|
815
|
+
expect(
|
|
816
|
+
getOutput(program, [
|
|
817
|
+
<ClassDeclaration type={WidgetOperations}>
|
|
818
|
+
<hbr />
|
|
819
|
+
<hbr />
|
|
820
|
+
<List>
|
|
821
|
+
<Method name="do_work" returnType="None" doc="This is a test" />
|
|
822
|
+
</List>
|
|
823
|
+
</ClassDeclaration>,
|
|
824
|
+
]),
|
|
825
|
+
).toRenderTo(`
|
|
826
|
+
from dataclasses import dataclass
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
@dataclass(kw_only=True)
|
|
830
|
+
class WidgetOperations:
|
|
831
|
+
id: str
|
|
832
|
+
weight: int
|
|
833
|
+
|
|
834
|
+
def do_work(self) -> None:
|
|
835
|
+
"""
|
|
836
|
+
This is a test
|
|
837
|
+
"""
|
|
838
|
+
pass
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
`);
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it("creates a class with a method if a model is provided and a class method is provided and methodType is set to method", async () => {
|
|
845
|
+
const { program, WidgetOperations } = await Tester.compile(t.code`
|
|
846
|
+
model ${t.model("WidgetOperations")} {
|
|
847
|
+
id: string;
|
|
848
|
+
weight: int32;
|
|
849
|
+
}
|
|
850
|
+
`);
|
|
851
|
+
|
|
852
|
+
expect(
|
|
853
|
+
getOutput(program, [
|
|
854
|
+
<ClassDeclaration type={WidgetOperations} methodType="method">
|
|
855
|
+
<hbr />
|
|
856
|
+
<hbr />
|
|
857
|
+
<List>
|
|
858
|
+
<Method name="do_work" returnType="None" doc="This is a test" />
|
|
859
|
+
</List>
|
|
860
|
+
</ClassDeclaration>,
|
|
861
|
+
]),
|
|
862
|
+
).toRenderTo(`
|
|
863
|
+
from dataclasses import dataclass
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
@dataclass(kw_only=True)
|
|
867
|
+
class WidgetOperations:
|
|
868
|
+
id: str
|
|
869
|
+
weight: int
|
|
870
|
+
|
|
871
|
+
def do_work(self) -> None:
|
|
872
|
+
"""
|
|
873
|
+
This is a test
|
|
874
|
+
"""
|
|
875
|
+
pass
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
`);
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it("creates a class with a classmethod if a model is provided, a class method is provided and methodType is set to class", async () => {
|
|
882
|
+
const { program, WidgetOperations } = await Tester.compile(t.code`
|
|
883
|
+
model ${t.model("WidgetOperations")} {
|
|
884
|
+
id: string;
|
|
885
|
+
weight: int32;
|
|
886
|
+
}
|
|
887
|
+
`);
|
|
888
|
+
|
|
889
|
+
expect(
|
|
890
|
+
getOutput(program, [
|
|
891
|
+
<ClassDeclaration type={WidgetOperations} methodType="class">
|
|
892
|
+
<hbr />
|
|
893
|
+
<hbr />
|
|
894
|
+
<List>
|
|
895
|
+
<Method name="do_work" returnType="None" doc="This is a test" />
|
|
896
|
+
</List>
|
|
897
|
+
</ClassDeclaration>,
|
|
898
|
+
]),
|
|
899
|
+
).toRenderTo(`
|
|
900
|
+
from dataclasses import dataclass
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
@dataclass(kw_only=True)
|
|
904
|
+
class WidgetOperations:
|
|
905
|
+
id: str
|
|
906
|
+
weight: int
|
|
907
|
+
|
|
908
|
+
@classmethod
|
|
909
|
+
def do_work(cls) -> None:
|
|
910
|
+
"""
|
|
911
|
+
This is a test
|
|
912
|
+
"""
|
|
913
|
+
pass
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
`);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
it("creates a class with a staticmethod if a model is provided, a class method is provided and methodType is set to static", async () => {
|
|
920
|
+
const { program, WidgetOperations } = await Tester.compile(t.code`
|
|
921
|
+
model ${t.model("WidgetOperations")} {
|
|
922
|
+
id: string;
|
|
923
|
+
weight: int32;
|
|
924
|
+
}
|
|
925
|
+
`);
|
|
926
|
+
|
|
927
|
+
expect(
|
|
928
|
+
getOutput(program, [
|
|
929
|
+
<ClassDeclaration type={WidgetOperations} methodType="static">
|
|
930
|
+
<hbr />
|
|
931
|
+
<hbr />
|
|
932
|
+
<List>
|
|
933
|
+
<Method name="do_work" returnType="None" doc="This is a test" />
|
|
934
|
+
</List>
|
|
935
|
+
</ClassDeclaration>,
|
|
936
|
+
]),
|
|
937
|
+
).toRenderTo(`
|
|
938
|
+
from dataclasses import dataclass
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
@dataclass(kw_only=True)
|
|
942
|
+
class WidgetOperations:
|
|
943
|
+
id: str
|
|
944
|
+
weight: int
|
|
945
|
+
|
|
946
|
+
@staticmethod
|
|
947
|
+
def do_work() -> None:
|
|
948
|
+
"""
|
|
949
|
+
This is a test
|
|
950
|
+
"""
|
|
951
|
+
pass
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
`);
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
it("creates a class with abstract method if an interface is provided", async () => {
|
|
958
|
+
const { program, WidgetOperations } = await Tester.compile(t.code`
|
|
959
|
+
interface ${t.interface("WidgetOperations")} {
|
|
960
|
+
op getName(id: string): string;
|
|
961
|
+
}
|
|
962
|
+
`);
|
|
963
|
+
|
|
964
|
+
expect(getOutput(program, [<ClassDeclaration type={WidgetOperations} />])).toRenderTo(`
|
|
965
|
+
from abc import ABC
|
|
966
|
+
from abc import abstractmethod
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
class WidgetOperations(ABC):
|
|
970
|
+
@abstractmethod
|
|
971
|
+
def get_name(self, id: str) -> str:
|
|
972
|
+
pass
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
`);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
it("creates a class with abstract method if an interface is provided and methodType is set to method", async () => {
|
|
979
|
+
const { program, WidgetOperations } = await Tester.compile(t.code`
|
|
980
|
+
interface ${t.interface("WidgetOperations")} {
|
|
981
|
+
op getName(id: string): string;
|
|
982
|
+
}
|
|
983
|
+
`);
|
|
984
|
+
|
|
985
|
+
expect(getOutput(program, [<ClassDeclaration type={WidgetOperations} methodType="method" />]))
|
|
986
|
+
.toRenderTo(`
|
|
987
|
+
from abc import ABC
|
|
988
|
+
from abc import abstractmethod
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
class WidgetOperations(ABC):
|
|
992
|
+
@abstractmethod
|
|
993
|
+
def get_name(self, id: str) -> str:
|
|
994
|
+
pass
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
`);
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
it("creates a class with abstract classmethod if an interface is provided and methodType is set to class", async () => {
|
|
1001
|
+
const { program, WidgetOperations } = await Tester.compile(t.code`
|
|
1002
|
+
interface ${t.interface("WidgetOperations")} {
|
|
1003
|
+
op getName(id: string): string;
|
|
1004
|
+
}
|
|
1005
|
+
`);
|
|
1006
|
+
|
|
1007
|
+
expect(getOutput(program, [<ClassDeclaration type={WidgetOperations} methodType="class" />]))
|
|
1008
|
+
.toRenderTo(`
|
|
1009
|
+
from abc import ABC
|
|
1010
|
+
from abc import abstractmethod
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
class WidgetOperations(ABC):
|
|
1014
|
+
@classmethod
|
|
1015
|
+
@abstractmethod
|
|
1016
|
+
def get_name(cls, id: str) -> str:
|
|
1017
|
+
pass
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
`);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it("creates a class with abstract staticmethod if an interface is provided and methodType is set to static", async () => {
|
|
1024
|
+
const { program, WidgetOperations } = await Tester.compile(t.code`
|
|
1025
|
+
interface ${t.interface("WidgetOperations")} {
|
|
1026
|
+
op getName(id: string): string;
|
|
1027
|
+
}
|
|
1028
|
+
`);
|
|
1029
|
+
|
|
1030
|
+
expect(getOutput(program, [<ClassDeclaration type={WidgetOperations} methodType="static" />]))
|
|
1031
|
+
.toRenderTo(`
|
|
1032
|
+
from abc import ABC
|
|
1033
|
+
from abc import abstractmethod
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
class WidgetOperations(ABC):
|
|
1037
|
+
@staticmethod
|
|
1038
|
+
@abstractmethod
|
|
1039
|
+
def get_name(id: str) -> str:
|
|
1040
|
+
pass
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
`);
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
it("Emits type alias to template instance as dataclass", async () => {
|
|
1047
|
+
const { program, StringResponse } = await Tester.compile(t.code`
|
|
1048
|
+
model Response<T> {
|
|
1049
|
+
data: T;
|
|
1050
|
+
status: string;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
alias ${t.type("StringResponse")} = Response<string>;
|
|
1054
|
+
`);
|
|
1055
|
+
|
|
1056
|
+
// Type alias to a template instance is emitted as a dataclass,
|
|
1057
|
+
// since Python doesn't support parameterized type aliases like TypeScript.
|
|
1058
|
+
// This is equivalent to: model StringResponse is Response<string>;
|
|
1059
|
+
expect(getOutput(program, [<TypeAliasDeclaration type={StringResponse} />])).toRenderTo(`
|
|
1060
|
+
from dataclasses import dataclass
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
@dataclass(kw_only=True)
|
|
1064
|
+
class StringResponse:
|
|
1065
|
+
data: str
|
|
1066
|
+
status: str
|
|
1067
|
+
|
|
1068
|
+
`);
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
it("Emits multiple concrete models from template instances using 'is'", async () => {
|
|
1072
|
+
const { program, StringResult, IntResult } = await Tester.compile(t.code`
|
|
1073
|
+
model Result<T, E> {
|
|
1074
|
+
value: T;
|
|
1075
|
+
error: E;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
model ${t.model("StringResult")} is Result<string, string>;
|
|
1079
|
+
model ${t.model("IntResult")} is Result<int32, string>;
|
|
1080
|
+
`);
|
|
1081
|
+
|
|
1082
|
+
// TypeSpec 'is' copies all properties from the template instance.
|
|
1083
|
+
// Each concrete model gets fully expanded properties with substituted types.
|
|
1084
|
+
expect(
|
|
1085
|
+
getOutput(program, [
|
|
1086
|
+
<ClassDeclaration type={StringResult} />,
|
|
1087
|
+
<ClassDeclaration type={IntResult} />,
|
|
1088
|
+
]),
|
|
1089
|
+
).toRenderTo(`
|
|
1090
|
+
from dataclasses import dataclass
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
@dataclass(kw_only=True)
|
|
1094
|
+
class StringResult:
|
|
1095
|
+
value: str
|
|
1096
|
+
error: str
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
@dataclass(kw_only=True)
|
|
1100
|
+
class IntResult:
|
|
1101
|
+
value: int
|
|
1102
|
+
error: str
|
|
1103
|
+
|
|
1104
|
+
`);
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it("Emits concrete model using 'is' from template instance", async () => {
|
|
1108
|
+
const { program, StringResponse } = await Tester.compile(t.code`
|
|
1109
|
+
model Response<T> {
|
|
1110
|
+
data: T;
|
|
1111
|
+
status: string;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
model ${t.model("StringResponse")} is Response<string>;
|
|
1115
|
+
`);
|
|
1116
|
+
|
|
1117
|
+
// Using 'is' copies all properties from the template instance.
|
|
1118
|
+
// StringResponse becomes a concrete model with all properties from Response<string>.
|
|
1119
|
+
expect(getOutput(program, [<ClassDeclaration type={StringResponse} />])).toRenderTo(`
|
|
1120
|
+
from dataclasses import dataclass
|
|
1121
|
+
|
|
1122
|
+
|
|
1123
|
+
@dataclass(kw_only=True)
|
|
1124
|
+
class StringResponse:
|
|
1125
|
+
data: str
|
|
1126
|
+
status: str
|
|
1127
|
+
|
|
1128
|
+
`);
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
it("Emits concrete interface extending template (operations use concrete types)", async () => {
|
|
1132
|
+
const { program, StringRepository } = await Tester.compile(t.code`
|
|
1133
|
+
interface Repository<T> {
|
|
1134
|
+
get(id: string): T;
|
|
1135
|
+
list(): T[];
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
interface ${t.interface("StringRepository")} extends Repository<string> {
|
|
1139
|
+
findByPrefix(prefix: string): string[];
|
|
1140
|
+
}
|
|
1141
|
+
`);
|
|
1142
|
+
|
|
1143
|
+
// TypeSpec flattens interface inheritance - StringRepository gets all operations
|
|
1144
|
+
// from Repository<string> with T replaced by string.
|
|
1145
|
+
expect(getOutput(program, [<ClassDeclaration type={StringRepository} />])).toRenderTo(`
|
|
1146
|
+
from abc import ABC
|
|
1147
|
+
from abc import abstractmethod
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
class StringRepository(ABC):
|
|
1151
|
+
@abstractmethod
|
|
1152
|
+
def get(self, id: str) -> str:
|
|
1153
|
+
pass
|
|
1154
|
+
|
|
1155
|
+
@abstractmethod
|
|
1156
|
+
def list(self) -> list[str]:
|
|
1157
|
+
pass
|
|
1158
|
+
|
|
1159
|
+
@abstractmethod
|
|
1160
|
+
def find_by_prefix(self, prefix: str) -> list[str]:
|
|
1161
|
+
pass
|
|
1162
|
+
|
|
1163
|
+
|
|
1164
|
+
`);
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
it("Emits multiple concrete interfaces (templates are macros, no generics)", async () => {
|
|
1168
|
+
const { program, UserRepository, ProductRepository } = await Tester.compile(t.code`
|
|
1169
|
+
interface Repository<T> {
|
|
1170
|
+
get(id: string): T;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
interface ${t.interface("UserRepository")} extends Repository<string> {
|
|
1174
|
+
findByEmail(email: string): string;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
interface ${t.interface("ProductRepository")} extends Repository<int32> {
|
|
1178
|
+
findByCategory(category: string): int32[];
|
|
1179
|
+
}
|
|
1180
|
+
`);
|
|
1181
|
+
|
|
1182
|
+
// Each concrete interface extends the template with different type arguments.
|
|
1183
|
+
// TypeSpec flattens the inheritance with concrete types substituted.
|
|
1184
|
+
expect(
|
|
1185
|
+
getOutput(program, [
|
|
1186
|
+
<ClassDeclaration type={UserRepository} />,
|
|
1187
|
+
<ClassDeclaration type={ProductRepository} />,
|
|
1188
|
+
]),
|
|
1189
|
+
).toRenderTo(`
|
|
1190
|
+
from abc import ABC
|
|
1191
|
+
from abc import abstractmethod
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
class UserRepository(ABC):
|
|
1195
|
+
@abstractmethod
|
|
1196
|
+
def get(self, id: str) -> str:
|
|
1197
|
+
pass
|
|
1198
|
+
|
|
1199
|
+
@abstractmethod
|
|
1200
|
+
def find_by_email(self, email: str) -> str:
|
|
1201
|
+
pass
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
class ProductRepository(ABC):
|
|
1206
|
+
@abstractmethod
|
|
1207
|
+
def get(self, id: str) -> int:
|
|
1208
|
+
pass
|
|
1209
|
+
|
|
1210
|
+
@abstractmethod
|
|
1211
|
+
def find_by_category(self, category: str) -> list[int]:
|
|
1212
|
+
pass
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
`);
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
it("Handles template instance with 'is' (copies properties)", async () => {
|
|
1219
|
+
const { program, CanadaAddress } = await Tester.compile(t.code`
|
|
1220
|
+
model Address<TState> {
|
|
1221
|
+
state: TState;
|
|
1222
|
+
city: string;
|
|
1223
|
+
street: string;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
model ${t.model("CanadaAddress")} is Address<never>;
|
|
1227
|
+
`);
|
|
1228
|
+
|
|
1229
|
+
// TypeSpec 'is' copies all properties from the template instance.
|
|
1230
|
+
// CanadaAddress is a concrete type with all properties from Address<never>.
|
|
1231
|
+
expect(getOutput(program, [<ClassDeclaration type={CanadaAddress} />])).toRenderTo(`
|
|
1232
|
+
from dataclasses import dataclass
|
|
1233
|
+
from typing import Never
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
@dataclass(kw_only=True)
|
|
1237
|
+
class CanadaAddress:
|
|
1238
|
+
state: Never
|
|
1239
|
+
city: str
|
|
1240
|
+
street: str
|
|
1241
|
+
|
|
1242
|
+
`);
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
it("Handles template with bounded type parameter using 'is'", async () => {
|
|
1246
|
+
const { program, StringContainer } = await Tester.compile(t.code`
|
|
1247
|
+
model Container<T extends string> {
|
|
1248
|
+
value: T;
|
|
1249
|
+
label: string;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
model ${t.model("StringContainer")} is Container<string>;
|
|
1253
|
+
`);
|
|
1254
|
+
|
|
1255
|
+
// Bounded type parameters (T extends string) work the same as unbounded -
|
|
1256
|
+
// the constraint is enforced by TypeSpec at compile time, and the concrete
|
|
1257
|
+
// type gets the substituted value.
|
|
1258
|
+
expect(getOutput(program, [<ClassDeclaration type={StringContainer} />])).toRenderTo(`
|
|
1259
|
+
from dataclasses import dataclass
|
|
1260
|
+
|
|
1261
|
+
|
|
1262
|
+
@dataclass(kw_only=True)
|
|
1263
|
+
class StringContainer:
|
|
1264
|
+
value: str
|
|
1265
|
+
label: str
|
|
1266
|
+
|
|
1267
|
+
`);
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
it("Handles template with multiple bounded and unbounded parameters using 'is'", async () => {
|
|
1271
|
+
const { program, MyResult } = await Tester.compile(t.code`
|
|
1272
|
+
model Result<T extends string, E> {
|
|
1273
|
+
value: T;
|
|
1274
|
+
error: E;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
model ${t.model("MyResult")} is Result<string, int32>;
|
|
1278
|
+
`);
|
|
1279
|
+
|
|
1280
|
+
// Mixed bounded/unbounded parameters are handled the same way -
|
|
1281
|
+
// TypeSpec expands the template with concrete types.
|
|
1282
|
+
expect(getOutput(program, [<ClassDeclaration type={MyResult} />])).toRenderTo(`
|
|
1283
|
+
from dataclasses import dataclass
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
@dataclass(kw_only=True)
|
|
1287
|
+
class MyResult:
|
|
1288
|
+
value: str
|
|
1289
|
+
error: int
|
|
1290
|
+
|
|
1291
|
+
`);
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
it("Handles 'extends' with concrete model (Python inheritance)", async () => {
|
|
1295
|
+
const { program, Address, CanadaAddress } = await Tester.compile(t.code`
|
|
1296
|
+
model ${t.model("Address")} {
|
|
1297
|
+
city: string;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
model ${t.model("CanadaAddress")} extends Address {
|
|
1301
|
+
street: string;
|
|
1302
|
+
}
|
|
1303
|
+
`);
|
|
1304
|
+
|
|
1305
|
+
// TypeSpec 'extends' creates Python class inheritance when the base is a concrete model.
|
|
1306
|
+
expect(
|
|
1307
|
+
getOutput(program, [
|
|
1308
|
+
<ClassDeclaration type={Address} />,
|
|
1309
|
+
<ClassDeclaration type={CanadaAddress} />,
|
|
1310
|
+
]),
|
|
1311
|
+
).toRenderTo(`
|
|
1312
|
+
from dataclasses import dataclass
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
@dataclass(kw_only=True)
|
|
1316
|
+
class Address:
|
|
1317
|
+
city: str
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
@dataclass(kw_only=True)
|
|
1321
|
+
class CanadaAddress(Address):
|
|
1322
|
+
street: str
|
|
1323
|
+
|
|
1324
|
+
`);
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
it("Handles 'extends' with template instance base (references concrete base)", async () => {
|
|
1328
|
+
const { program, Response, ConcreteResponse } = await Tester.compile(t.code`
|
|
1329
|
+
model ${t.model("Response")} {
|
|
1330
|
+
data: string;
|
|
1331
|
+
status: string;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
model ${t.model("ConcreteResponse")} extends Response {
|
|
1335
|
+
timestamp: string;
|
|
1336
|
+
}
|
|
1337
|
+
`);
|
|
1338
|
+
|
|
1339
|
+
// When extending a concrete model, Python inheritance is used.
|
|
1340
|
+
// The base class must be emitted first for the reference to resolve.
|
|
1341
|
+
expect(
|
|
1342
|
+
getOutput(program, [
|
|
1343
|
+
<ClassDeclaration type={Response} />,
|
|
1344
|
+
<ClassDeclaration type={ConcreteResponse} />,
|
|
1345
|
+
]),
|
|
1346
|
+
).toRenderTo(`
|
|
1347
|
+
from dataclasses import dataclass
|
|
1348
|
+
|
|
1349
|
+
|
|
1350
|
+
@dataclass(kw_only=True)
|
|
1351
|
+
class Response:
|
|
1352
|
+
data: str
|
|
1353
|
+
status: str
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
@dataclass(kw_only=True)
|
|
1357
|
+
class ConcreteResponse(Response):
|
|
1358
|
+
timestamp: str
|
|
1359
|
+
|
|
1360
|
+
`);
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
it("Handles template instance with 'extends' and never type using 'is' pattern", async () => {
|
|
1364
|
+
const { program, CanadaAddress } = await Tester.compile(t.code`
|
|
1365
|
+
model Address<TState> {
|
|
1366
|
+
state: TState;
|
|
1367
|
+
city: string;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// Using 'is' instead of 'extends' to copy all properties from the template instance
|
|
1371
|
+
model ${t.model("CanadaAddress")} is Address<never> {
|
|
1372
|
+
street: string;
|
|
1373
|
+
}
|
|
1374
|
+
`);
|
|
1375
|
+
|
|
1376
|
+
// When extending template instances, prefer 'is' pattern which copies all properties.
|
|
1377
|
+
// The 'extends' keyword with template instances would require the template declaration
|
|
1378
|
+
// to be emitted, which is not supported since templates are macros.
|
|
1379
|
+
expect(getOutput(program, [<ClassDeclaration type={CanadaAddress} />])).toRenderTo(`
|
|
1380
|
+
from dataclasses import dataclass
|
|
1381
|
+
from typing import Never
|
|
1382
|
+
|
|
1383
|
+
|
|
1384
|
+
@dataclass(kw_only=True)
|
|
1385
|
+
class CanadaAddress:
|
|
1386
|
+
state: Never
|
|
1387
|
+
city: str
|
|
1388
|
+
street: str
|
|
1389
|
+
|
|
1390
|
+
`);
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
it("creates an abstract dataclass when abstract prop is true with a model", async () => {
|
|
1394
|
+
const { program, BaseEntity } = await Tester.compile(t.code`
|
|
1395
|
+
model ${t.model("BaseEntity")} {
|
|
1396
|
+
id: string;
|
|
1397
|
+
createdAt: string;
|
|
1398
|
+
}
|
|
1399
|
+
`);
|
|
1400
|
+
|
|
1401
|
+
expect(getOutput(program, [<ClassDeclaration type={BaseEntity} abstract={true} />]))
|
|
1402
|
+
.toRenderTo(`
|
|
1403
|
+
from abc import ABC
|
|
1404
|
+
from dataclasses import dataclass
|
|
1405
|
+
|
|
1406
|
+
|
|
1407
|
+
@dataclass(kw_only=True)
|
|
1408
|
+
class BaseEntity(ABC):
|
|
1409
|
+
id: str
|
|
1410
|
+
created_at: str
|
|
1411
|
+
|
|
1412
|
+
`);
|
|
1413
|
+
});
|
|
1414
|
+
});
|