@typespec/emitter-framework 0.15.0-dev.2 → 0.15.0-dev.4

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.
Files changed (259) hide show
  1. package/CHANGELOG.md +1 -16
  2. package/dist/src/core/components/overrides/component-overrides.d.ts.map +1 -1
  3. package/dist/src/core/components/overrides/component-overrides.js.map +1 -1
  4. package/dist/src/python/builtins.d.ts +14 -0
  5. package/dist/src/python/builtins.d.ts.map +1 -0
  6. package/dist/src/python/builtins.js +29 -0
  7. package/dist/src/python/builtins.js.map +1 -0
  8. package/dist/src/python/components/array-expression/array-expression.d.ts +6 -0
  9. package/dist/src/python/components/array-expression/array-expression.d.ts.map +1 -0
  10. package/dist/src/python/components/array-expression/array-expression.js +11 -0
  11. package/dist/src/python/components/array-expression/array-expression.js.map +1 -0
  12. package/dist/src/python/components/array-expression/array-expression.test.d.ts +2 -0
  13. package/dist/src/python/components/array-expression/array-expression.test.d.ts.map +1 -0
  14. package/dist/src/python/components/array-expression/array-expression.test.js +19 -0
  15. package/dist/src/python/components/array-expression/array-expression.test.js.map +1 -0
  16. package/dist/src/python/components/array-expression/index.d.ts +2 -0
  17. package/dist/src/python/components/array-expression/index.d.ts.map +1 -0
  18. package/dist/src/python/components/array-expression/index.js +2 -0
  19. package/dist/src/python/components/array-expression/index.js.map +1 -0
  20. package/dist/src/python/components/atom/atom.d.ts +33 -0
  21. package/dist/src/python/components/atom/atom.d.ts.map +1 -0
  22. package/dist/src/python/components/atom/atom.js +88 -0
  23. package/dist/src/python/components/atom/atom.js.map +1 -0
  24. package/dist/src/python/components/atom/atom.test.d.ts +2 -0
  25. package/dist/src/python/components/atom/atom.test.d.ts.map +1 -0
  26. package/dist/src/python/components/atom/atom.test.js +224 -0
  27. package/dist/src/python/components/atom/atom.test.js.map +1 -0
  28. package/dist/src/python/components/atom/index.d.ts +2 -0
  29. package/dist/src/python/components/atom/index.d.ts.map +1 -0
  30. package/dist/src/python/components/atom/index.js +2 -0
  31. package/dist/src/python/components/atom/index.js.map +1 -0
  32. package/dist/src/python/components/class-declaration/class-bases.d.ts +45 -0
  33. package/dist/src/python/components/class-declaration/class-bases.d.ts.map +1 -0
  34. package/dist/src/python/components/class-declaration/class-bases.js +84 -0
  35. package/dist/src/python/components/class-declaration/class-bases.js.map +1 -0
  36. package/dist/src/python/components/class-declaration/class-body.d.ts +16 -0
  37. package/dist/src/python/components/class-declaration/class-body.d.ts.map +1 -0
  38. package/dist/src/python/components/class-declaration/class-body.js +52 -0
  39. package/dist/src/python/components/class-declaration/class-body.js.map +1 -0
  40. package/dist/src/python/components/class-declaration/class-declaration.d.ts +23 -0
  41. package/dist/src/python/components/class-declaration/class-declaration.d.ts.map +1 -0
  42. package/dist/src/python/components/class-declaration/class-declaration.js +118 -0
  43. package/dist/src/python/components/class-declaration/class-declaration.js.map +1 -0
  44. package/dist/src/python/components/class-declaration/class-declaration.test.d.ts +2 -0
  45. package/dist/src/python/components/class-declaration/class-declaration.test.d.ts.map +1 -0
  46. package/dist/src/python/components/class-declaration/class-declaration.test.js +1527 -0
  47. package/dist/src/python/components/class-declaration/class-declaration.test.js.map +1 -0
  48. package/dist/src/python/components/class-declaration/class-member.d.ts +16 -0
  49. package/dist/src/python/components/class-declaration/class-member.d.ts.map +1 -0
  50. package/dist/src/python/components/class-declaration/class-member.js +71 -0
  51. package/dist/src/python/components/class-declaration/class-member.js.map +1 -0
  52. package/dist/src/python/components/class-declaration/class-member.test.d.ts +2 -0
  53. package/dist/src/python/components/class-declaration/class-member.test.d.ts.map +1 -0
  54. package/dist/src/python/components/class-declaration/class-member.test.js +202 -0
  55. package/dist/src/python/components/class-declaration/class-member.test.js.map +1 -0
  56. package/dist/src/python/components/class-declaration/class-method.d.ts +22 -0
  57. package/dist/src/python/components/class-declaration/class-method.d.ts.map +1 -0
  58. package/dist/src/python/components/class-declaration/class-method.js +76 -0
  59. package/dist/src/python/components/class-declaration/class-method.js.map +1 -0
  60. package/dist/src/python/components/class-declaration/class-method.test.d.ts +2 -0
  61. package/dist/src/python/components/class-declaration/class-method.test.d.ts.map +1 -0
  62. package/dist/src/python/components/class-declaration/class-method.test.js +298 -0
  63. package/dist/src/python/components/class-declaration/class-method.test.js.map +1 -0
  64. package/dist/src/python/components/class-declaration/index.d.ts +7 -0
  65. package/dist/src/python/components/class-declaration/index.d.ts.map +1 -0
  66. package/dist/src/python/components/class-declaration/index.js +7 -0
  67. package/dist/src/python/components/class-declaration/index.js.map +1 -0
  68. package/dist/src/python/components/class-declaration/primitive-initializer.d.ts +22 -0
  69. package/dist/src/python/components/class-declaration/primitive-initializer.d.ts.map +1 -0
  70. package/dist/src/python/components/class-declaration/primitive-initializer.js +67 -0
  71. package/dist/src/python/components/class-declaration/primitive-initializer.js.map +1 -0
  72. package/dist/src/python/components/doc-element/doc-element.d.ts +46 -0
  73. package/dist/src/python/components/doc-element/doc-element.d.ts.map +1 -0
  74. package/dist/src/python/components/doc-element/doc-element.js +59 -0
  75. package/dist/src/python/components/doc-element/doc-element.js.map +1 -0
  76. package/dist/src/python/components/doc-element/index.d.ts +2 -0
  77. package/dist/src/python/components/doc-element/index.d.ts.map +1 -0
  78. package/dist/src/python/components/doc-element/index.js +2 -0
  79. package/dist/src/python/components/doc-element/index.js.map +1 -0
  80. package/dist/src/python/components/enum-declaration/enum-declaration.d.ts +8 -0
  81. package/dist/src/python/components/enum-declaration/enum-declaration.d.ts.map +1 -0
  82. package/dist/src/python/components/enum-declaration/enum-declaration.js +80 -0
  83. package/dist/src/python/components/enum-declaration/enum-declaration.js.map +1 -0
  84. package/dist/src/python/components/enum-declaration/enum-declaration.test.d.ts +2 -0
  85. package/dist/src/python/components/enum-declaration/enum-declaration.test.d.ts.map +1 -0
  86. package/dist/src/python/components/enum-declaration/enum-declaration.test.js +344 -0
  87. package/dist/src/python/components/enum-declaration/enum-declaration.test.js.map +1 -0
  88. package/dist/src/python/components/enum-declaration/enum-member.d.ts +9 -0
  89. package/dist/src/python/components/enum-declaration/enum-member.d.ts.map +1 -0
  90. package/dist/src/python/components/enum-declaration/enum-member.js +22 -0
  91. package/dist/src/python/components/enum-declaration/enum-member.js.map +1 -0
  92. package/dist/src/python/components/enum-declaration/index.d.ts +3 -0
  93. package/dist/src/python/components/enum-declaration/index.d.ts.map +1 -0
  94. package/dist/src/python/components/enum-declaration/index.js +3 -0
  95. package/dist/src/python/components/enum-declaration/index.js.map +1 -0
  96. package/dist/src/python/components/function-declaration/function-declaration.d.ts +24 -0
  97. package/dist/src/python/components/function-declaration/function-declaration.d.ts.map +1 -0
  98. package/dist/src/python/components/function-declaration/function-declaration.js +68 -0
  99. package/dist/src/python/components/function-declaration/function-declaration.js.map +1 -0
  100. package/dist/src/python/components/function-declaration/function-declaration.test.d.ts +2 -0
  101. package/dist/src/python/components/function-declaration/function-declaration.test.d.ts.map +1 -0
  102. package/dist/src/python/components/function-declaration/function-declaration.test.js +682 -0
  103. package/dist/src/python/components/function-declaration/function-declaration.test.js.map +1 -0
  104. package/dist/src/python/components/function-declaration/index.d.ts +2 -0
  105. package/dist/src/python/components/function-declaration/index.d.ts.map +1 -0
  106. package/dist/src/python/components/function-declaration/index.js +2 -0
  107. package/dist/src/python/components/function-declaration/index.js.map +1 -0
  108. package/dist/src/python/components/index.d.ts +12 -0
  109. package/dist/src/python/components/index.d.ts.map +1 -0
  110. package/dist/src/python/components/index.js +12 -0
  111. package/dist/src/python/components/index.js.map +1 -0
  112. package/dist/src/python/components/protocol-declaration/callable-parameters.d.ts +25 -0
  113. package/dist/src/python/components/protocol-declaration/callable-parameters.d.ts.map +1 -0
  114. package/dist/src/python/components/protocol-declaration/callable-parameters.js +33 -0
  115. package/dist/src/python/components/protocol-declaration/callable-parameters.js.map +1 -0
  116. package/dist/src/python/components/protocol-declaration/index.d.ts +3 -0
  117. package/dist/src/python/components/protocol-declaration/index.d.ts.map +1 -0
  118. package/dist/src/python/components/protocol-declaration/index.js +3 -0
  119. package/dist/src/python/components/protocol-declaration/index.js.map +1 -0
  120. package/dist/src/python/components/protocol-declaration/protocol-declaration.d.ts +8 -0
  121. package/dist/src/python/components/protocol-declaration/protocol-declaration.d.ts.map +1 -0
  122. package/dist/src/python/components/protocol-declaration/protocol-declaration.js +86 -0
  123. package/dist/src/python/components/protocol-declaration/protocol-declaration.js.map +1 -0
  124. package/dist/src/python/components/protocol-declaration/protocol-declaration.test.d.ts +2 -0
  125. package/dist/src/python/components/protocol-declaration/protocol-declaration.test.d.ts.map +1 -0
  126. package/dist/src/python/components/protocol-declaration/protocol-declaration.test.js +117 -0
  127. package/dist/src/python/components/protocol-declaration/protocol-declaration.test.js.map +1 -0
  128. package/dist/src/python/components/record-expression/index.d.ts +2 -0
  129. package/dist/src/python/components/record-expression/index.d.ts.map +1 -0
  130. package/dist/src/python/components/record-expression/index.js +2 -0
  131. package/dist/src/python/components/record-expression/index.js.map +1 -0
  132. package/dist/src/python/components/record-expression/record-expression.d.ts +6 -0
  133. package/dist/src/python/components/record-expression/record-expression.d.ts.map +1 -0
  134. package/dist/src/python/components/record-expression/record-expression.js +13 -0
  135. package/dist/src/python/components/record-expression/record-expression.js.map +1 -0
  136. package/dist/src/python/components/record-expression/record-expression.test.d.ts +2 -0
  137. package/dist/src/python/components/record-expression/record-expression.test.d.ts.map +1 -0
  138. package/dist/src/python/components/record-expression/record-expression.test.js +19 -0
  139. package/dist/src/python/components/record-expression/record-expression.test.js.map +1 -0
  140. package/dist/src/python/components/type-alias-declaration/index.d.ts +2 -0
  141. package/dist/src/python/components/type-alias-declaration/index.d.ts.map +1 -0
  142. package/dist/src/python/components/type-alias-declaration/index.js +2 -0
  143. package/dist/src/python/components/type-alias-declaration/index.js.map +1 -0
  144. package/dist/src/python/components/type-alias-declaration/type-alias-declaration.d.ts +16 -0
  145. package/dist/src/python/components/type-alias-declaration/type-alias-declaration.d.ts.map +1 -0
  146. package/dist/src/python/components/type-alias-declaration/type-alias-declaration.js +75 -0
  147. package/dist/src/python/components/type-alias-declaration/type-alias-declaration.js.map +1 -0
  148. package/dist/src/python/components/type-alias-declaration/type-alias-declaration.test.d.ts +2 -0
  149. package/dist/src/python/components/type-alias-declaration/type-alias-declaration.test.d.ts.map +1 -0
  150. package/dist/src/python/components/type-alias-declaration/type-alias-declaration.test.js +140 -0
  151. package/dist/src/python/components/type-alias-declaration/type-alias-declaration.test.js.map +1 -0
  152. package/dist/src/python/components/type-declaration/index.d.ts +2 -0
  153. package/dist/src/python/components/type-declaration/index.d.ts.map +1 -0
  154. package/dist/src/python/components/type-declaration/index.js +2 -0
  155. package/dist/src/python/components/type-declaration/index.js.map +1 -0
  156. package/dist/src/python/components/type-declaration/type-declaration.d.ts +11 -0
  157. package/dist/src/python/components/type-declaration/type-declaration.d.ts.map +1 -0
  158. package/dist/src/python/components/type-declaration/type-declaration.js +31 -0
  159. package/dist/src/python/components/type-declaration/type-declaration.js.map +1 -0
  160. package/dist/src/python/components/type-declaration/type-declaration.test.d.ts +2 -0
  161. package/dist/src/python/components/type-declaration/type-declaration.test.d.ts.map +1 -0
  162. package/dist/src/python/components/type-declaration/type-declaration.test.js +69 -0
  163. package/dist/src/python/components/type-declaration/type-declaration.test.js.map +1 -0
  164. package/dist/src/python/components/type-expression/index.d.ts +2 -0
  165. package/dist/src/python/components/type-expression/index.d.ts.map +1 -0
  166. package/dist/src/python/components/type-expression/index.js +2 -0
  167. package/dist/src/python/components/type-expression/index.js.map +1 -0
  168. package/dist/src/python/components/type-expression/type-expression.d.ts +12 -0
  169. package/dist/src/python/components/type-expression/type-expression.d.ts.map +1 -0
  170. package/dist/src/python/components/type-expression/type-expression.js +348 -0
  171. package/dist/src/python/components/type-expression/type-expression.js.map +1 -0
  172. package/dist/src/python/components/type-expression/type-expression.test.d.ts +2 -0
  173. package/dist/src/python/components/type-expression/type-expression.test.d.ts.map +1 -0
  174. package/dist/src/python/components/type-expression/type-expression.test.js +503 -0
  175. package/dist/src/python/components/type-expression/type-expression.test.js.map +1 -0
  176. package/dist/src/python/index.d.ts +4 -0
  177. package/dist/src/python/index.d.ts.map +1 -0
  178. package/dist/src/python/index.js +4 -0
  179. package/dist/src/python/index.js.map +1 -0
  180. package/dist/src/python/lib.d.ts +68 -0
  181. package/dist/src/python/lib.d.ts.map +1 -0
  182. package/dist/src/python/lib.js +38 -0
  183. package/dist/src/python/lib.js.map +1 -0
  184. package/dist/src/python/test-utils.d.ts +8 -0
  185. package/dist/src/python/test-utils.d.ts.map +1 -0
  186. package/dist/src/python/test-utils.js +25 -0
  187. package/dist/src/python/test-utils.js.map +1 -0
  188. package/dist/src/python/utils/index.d.ts +4 -0
  189. package/dist/src/python/utils/index.d.ts.map +1 -0
  190. package/dist/src/python/utils/index.js +4 -0
  191. package/dist/src/python/utils/index.js.map +1 -0
  192. package/dist/src/python/utils/operation.d.ts +27 -0
  193. package/dist/src/python/utils/operation.d.ts.map +1 -0
  194. package/dist/src/python/utils/operation.js +139 -0
  195. package/dist/src/python/utils/operation.js.map +1 -0
  196. package/dist/src/python/utils/refkey.d.ts +23 -0
  197. package/dist/src/python/utils/refkey.d.ts.map +1 -0
  198. package/dist/src/python/utils/refkey.js +36 -0
  199. package/dist/src/python/utils/refkey.js.map +1 -0
  200. package/dist/src/python/utils/type.d.ts +19 -0
  201. package/dist/src/python/utils/type.d.ts.map +1 -0
  202. package/dist/src/python/utils/type.js +24 -0
  203. package/dist/src/python/utils/type.js.map +1 -0
  204. package/dist/src/typescript/components/function-declaration.d.ts.map +1 -1
  205. package/dist/src/typescript/components/function-declaration.js.map +1 -1
  206. package/package.json +12 -4
  207. package/package.json.bak +13 -4
  208. package/src/core/components/overrides/component-overrides.tsx +9 -6
  209. package/src/python/builtins.ts +43 -0
  210. package/src/python/components/array-expression/array-expression.test.tsx +14 -0
  211. package/src/python/components/array-expression/array-expression.tsx +11 -0
  212. package/src/python/components/array-expression/index.ts +1 -0
  213. package/src/python/components/atom/atom.test.tsx +244 -0
  214. package/src/python/components/atom/atom.tsx +95 -0
  215. package/src/python/components/atom/index.ts +1 -0
  216. package/src/python/components/class-declaration/class-bases.tsx +92 -0
  217. package/src/python/components/class-declaration/class-body.tsx +56 -0
  218. package/src/python/components/class-declaration/class-declaration.test.tsx +1414 -0
  219. package/src/python/components/class-declaration/class-declaration.tsx +116 -0
  220. package/src/python/components/class-declaration/class-member.test.tsx +194 -0
  221. package/src/python/components/class-declaration/class-member.tsx +67 -0
  222. package/src/python/components/class-declaration/class-method.test.tsx +250 -0
  223. package/src/python/components/class-declaration/class-method.tsx +97 -0
  224. package/src/python/components/class-declaration/index.ts +6 -0
  225. package/src/python/components/class-declaration/primitive-initializer.tsx +62 -0
  226. package/src/python/components/doc-element/doc-element.tsx +83 -0
  227. package/src/python/components/doc-element/index.ts +1 -0
  228. package/src/python/components/enum-declaration/enum-declaration.test.tsx +319 -0
  229. package/src/python/components/enum-declaration/enum-declaration.tsx +77 -0
  230. package/src/python/components/enum-declaration/enum-member.tsx +21 -0
  231. package/src/python/components/enum-declaration/index.ts +2 -0
  232. package/src/python/components/function-declaration/function-declaration.test.tsx +582 -0
  233. package/src/python/components/function-declaration/function-declaration.tsx +90 -0
  234. package/src/python/components/function-declaration/index.ts +1 -0
  235. package/src/python/components/index.ts +11 -0
  236. package/src/python/components/protocol-declaration/callable-parameters.tsx +44 -0
  237. package/src/python/components/protocol-declaration/index.ts +2 -0
  238. package/src/python/components/protocol-declaration/protocol-declaration.test.tsx +106 -0
  239. package/src/python/components/protocol-declaration/protocol-declaration.tsx +73 -0
  240. package/src/python/components/record-expression/index.ts +1 -0
  241. package/src/python/components/record-expression/record-expression.test.tsx +14 -0
  242. package/src/python/components/record-expression/record-expression.tsx +13 -0
  243. package/src/python/components/type-alias-declaration/index.ts +1 -0
  244. package/src/python/components/type-alias-declaration/type-alias-declaration.test.tsx +117 -0
  245. package/src/python/components/type-alias-declaration/type-alias-declaration.tsx +67 -0
  246. package/src/python/components/type-declaration/index.ts +1 -0
  247. package/src/python/components/type-declaration/type-declaration.test.tsx +58 -0
  248. package/src/python/components/type-declaration/type-declaration.tsx +26 -0
  249. package/src/python/components/type-expression/index.ts +1 -0
  250. package/src/python/components/type-expression/type-expression.test.tsx +463 -0
  251. package/src/python/components/type-expression/type-expression.tsx +333 -0
  252. package/src/python/index.ts +3 -0
  253. package/src/python/lib.ts +40 -0
  254. package/src/python/test-utils.tsx +31 -0
  255. package/src/python/utils/index.ts +3 -0
  256. package/src/python/utils/operation.ts +161 -0
  257. package/src/python/utils/refkey.ts +36 -0
  258. package/src/python/utils/type.ts +31 -0
  259. package/src/typescript/components/function-declaration.tsx +4 -2
@@ -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
+ });