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