@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,116 @@
1
+ import * as py from "@alloy-js/python";
2
+ import { type Interface, type Model } from "@typespec/compiler";
3
+ import { useTsp } from "../../../core/context/tsp-context.js";
4
+ import { reportDiagnostic } from "../../../lib.js";
5
+ import { declarationRefkeys } from "../../utils/refkey.js";
6
+ import { DocElement } from "../doc-element/doc-element.js";
7
+ import { ClassBases } from "./class-bases.js";
8
+ import { ClassBody } from "./class-body.js";
9
+ import { MethodProvider } from "./class-method.js";
10
+
11
+ export interface ClassDeclarationPropsWithType extends Omit<py.ClassDeclarationProps, "name"> {
12
+ type: Model | Interface;
13
+ name?: string;
14
+ abstract?: boolean; // Global override for the abstract flag
15
+ methodType?: "method" | "class" | "static"; // Global override for the method type
16
+ }
17
+
18
+ export type ClassDeclarationProps = ClassDeclarationPropsWithType | py.ClassDeclarationProps;
19
+
20
+ function isTypedClassDeclarationProps(
21
+ props: ClassDeclarationProps,
22
+ ): props is ClassDeclarationPropsWithType {
23
+ return "type" in props;
24
+ }
25
+
26
+ /**
27
+ * Converts TypeSpec Models and Interfaces to Python classes.
28
+ *
29
+ * - **Models** are converted into Dataclasses with `@dataclass(kw_only=True)` + fields
30
+ * - **Interfaces** are converted into Abstract classes (ABC) with abstract methods
31
+ * - For models that extends another model, we convert that into Python class inheritance
32
+ * - For interfaces that extends another interface, there's no inheritance, since
33
+ * TypeSpec flattens the inheritance
34
+ *
35
+ * @param props - The props for the class declaration.
36
+ * @returns The class declaration.
37
+ */
38
+ export function ClassDeclaration(props: ClassDeclarationProps) {
39
+ const { $ } = useTsp();
40
+
41
+ // Interfaces are rendered as abstract classes (ABC) with abstract methods
42
+ // Models are rendered as concrete dataclasses with fields
43
+ // If we are explicitly overriding the class as abstract or the type is not a model, we need to create an abstract class
44
+ const abstract =
45
+ ("abstract" in props && props.abstract) || ("type" in props && !$.model.is(props.type));
46
+
47
+ const type = "type" in props ? props.type : undefined;
48
+ const docSource = props.doc ?? (type ? $.type.getDoc(type) : undefined);
49
+ const docElement = docSource ? <DocElement doc={docSource} component={py.ClassDoc} /> : undefined;
50
+
51
+ // TODO: When TypeSpec adds true generics support, pass extraBases with Generic[T, ...] here.
52
+ // Currently, TypeSpec templates are macros that expand to concrete types, so we don't
53
+ // generate Python generics (TypeVar/Generic) for template declarations.
54
+ const basesType = ClassBases({
55
+ type: "type" in props ? props.type : undefined,
56
+ bases: props.bases,
57
+ abstract,
58
+ });
59
+
60
+ if (!isTypedClassDeclarationProps(props)) {
61
+ return (
62
+ <py.ClassDeclaration
63
+ {...props}
64
+ doc={docElement}
65
+ {...(basesType.length ? { bases: basesType } : {})}
66
+ />
67
+ );
68
+ }
69
+
70
+ const namePolicy = py.usePythonNamePolicy();
71
+
72
+ let name = props.name ?? props.type.name;
73
+ if (!name) {
74
+ reportDiagnostic($.program, { code: "type-declaration-missing-name", target: props.type });
75
+ }
76
+ name = namePolicy.getName(name, "class");
77
+
78
+ const refkeys = declarationRefkeys(props.refkey, props.type);
79
+
80
+ // Check for models with additional properties (Record-based scenarios)
81
+ // This check must happen here (in addition to ClassBody) because models with no properties
82
+ // (e.g., `model Foo is Record<string>`) won't render a ClassBody, so the error would never be thrown
83
+ if ($.model.is(props.type)) {
84
+ const additionalPropsRecord = $.model.getAdditionalPropertiesRecord(props.type);
85
+ if (additionalPropsRecord) {
86
+ throw new Error("Models with additional properties (Record[…]) are not supported");
87
+ }
88
+ }
89
+
90
+ // Array-based models (e.g., model Foo is Array<T>) use regular classes, not dataclasses,
91
+ // since Array models in TypeSpec can't have properties, so they behave more like a class
92
+ // that inherits from a list.
93
+ // Similarly, interfaces should use regular classes (ABC) not dataclasses, since interfaces
94
+ // only define abstract methods, not fields.
95
+ const isArrayModel = $.model.is(props.type) && $.array.is(props.type);
96
+ const isInterface = props.type.kind === "Interface";
97
+ const useDataclass = !isArrayModel && !isInterface;
98
+
99
+ const ClassComponent = useDataclass ? py.DataclassDeclaration : py.ClassDeclaration;
100
+
101
+ return (
102
+ <MethodProvider value={props.methodType}>
103
+ <ClassComponent
104
+ doc={docElement}
105
+ name={name}
106
+ {...(basesType.length ? { bases: basesType } : {})}
107
+ refkey={refkeys}
108
+ kwOnly={useDataclass ? true : undefined}
109
+ >
110
+ <ClassBody type={props.type} abstract={abstract} methodType={props.methodType}>
111
+ {props.children}
112
+ </ClassBody>
113
+ </ClassComponent>
114
+ </MethodProvider>
115
+ );
116
+ }
@@ -0,0 +1,194 @@
1
+ import { Tester } from "#test/test-host.js";
2
+ import { t } from "@typespec/compiler/testing";
3
+ import { describe, expect, it } from "vitest";
4
+ import { ClassDeclaration } from "../../../../src/python/components/class-declaration/class-declaration.js";
5
+ import { getOutput } from "../../test-utils.js";
6
+
7
+ describe("Python Class Members", () => {
8
+ describe("default values", () => {
9
+ it("renders string default values", async () => {
10
+ const { program, MyModel } = await Tester.compile(t.code`
11
+ model ${t.model("MyModel")} {
12
+ name: string = "default";
13
+ description?: string = "optional with default";
14
+ emptyString: string = "";
15
+ }
16
+ `);
17
+
18
+ expect(getOutput(program, [<ClassDeclaration type={MyModel} />])).toRenderTo(
19
+ `
20
+ from dataclasses import dataclass
21
+ from typing import Optional
22
+
23
+
24
+ @dataclass(kw_only=True)
25
+ class MyModel:
26
+ name: str = "default"
27
+ description: Optional[str] = "optional with default"
28
+ empty_string: str = ""
29
+
30
+ `,
31
+ );
32
+ });
33
+
34
+ it("renders boolean default values", async () => {
35
+ const { program, BooleanModel } = await Tester.compile(t.code`
36
+ model ${t.model("BooleanModel")} {
37
+ isActive: boolean = true;
38
+ isDeleted: boolean = false;
39
+ optional?: boolean = true;
40
+ }
41
+ `);
42
+
43
+ expect(getOutput(program, [<ClassDeclaration type={BooleanModel} />])).toRenderTo(
44
+ `
45
+ from dataclasses import dataclass
46
+ from typing import Optional
47
+
48
+
49
+ @dataclass(kw_only=True)
50
+ class BooleanModel:
51
+ is_active: bool = True
52
+ is_deleted: bool = False
53
+ optional: Optional[bool] = True
54
+
55
+ `,
56
+ );
57
+ });
58
+
59
+ it("renders array default values", async () => {
60
+ const { program, ArrayModel } = await Tester.compile(t.code`
61
+ model ${t.model("ArrayModel")} {
62
+ tags: string[] = #["tag1", "tag2"];
63
+ emptyArray: int32[] = #[];
64
+ numbers: int32[] = #[1, 2, 3];
65
+ }
66
+ `);
67
+
68
+ expect(getOutput(program, [<ClassDeclaration type={ArrayModel} />])).toRenderTo(
69
+ `
70
+ from dataclasses import dataclass
71
+
72
+
73
+ @dataclass(kw_only=True)
74
+ class ArrayModel:
75
+ tags: list[str] = ["tag1", "tag2"]
76
+ empty_array: list[int] = []
77
+ numbers: list[int] = [1, 2, 3]
78
+
79
+ `,
80
+ );
81
+ });
82
+
83
+ it("renders integer default values without .0 suffix", async () => {
84
+ const { program, IntegerModel } = await Tester.compile(t.code`
85
+ model ${t.model("IntegerModel")} {
86
+ count: int32 = 42;
87
+ bigNumber: int64 = 1000000;
88
+ smallNumber: int8 = 127;
89
+ unsignedValue: uint32 = 100;
90
+ safeIntValue: safeint = 999;
91
+ }
92
+ `);
93
+
94
+ expect(getOutput(program, [<ClassDeclaration type={IntegerModel} />])).toRenderTo(
95
+ `
96
+ from dataclasses import dataclass
97
+
98
+
99
+ @dataclass(kw_only=True)
100
+ class IntegerModel:
101
+ count: int = 42
102
+ big_number: int = 1000000
103
+ small_number: int = 127
104
+ unsigned_value: int = 100
105
+ safe_int_value: int = 999
106
+
107
+ `,
108
+ );
109
+ });
110
+
111
+ it("renders float and decimal default values correctly", async () => {
112
+ const { program, NumericDefaults } = await Tester.compile(t.code`
113
+
114
+ scalar customFloat extends float;
115
+ scalar customDecimal extends decimal;
116
+
117
+ model ${t.model("NumericDefaults")} {
118
+ // Float variants with decimal values
119
+ floatBase: float = 1.5;
120
+ float32Value: float32 = 2.5;
121
+ float64Value: float64 = 3.5;
122
+ customFloatValue: customFloat = 4.5;
123
+
124
+ // Float variants with integer values (should render with .0)
125
+ floatInt: float = 10;
126
+ float32Int: float32 = 20;
127
+ float64Int: float64 = 30;
128
+
129
+ // Decimal variants
130
+ decimalBase: decimal = 100.25;
131
+ decimal128Value: decimal128 = 200.75;
132
+ customDecimalValue: customDecimal = 300.125;
133
+
134
+ // Decimal with integer values (should render with .0)
135
+ decimalInt: decimal = 400;
136
+ decimal128Int: decimal128 = 500;
137
+ }
138
+ `);
139
+
140
+ expect(getOutput(program, [<ClassDeclaration type={NumericDefaults} />])).toRenderTo(
141
+ `
142
+ from dataclasses import dataclass
143
+ from decimal import Decimal
144
+
145
+
146
+ @dataclass(kw_only=True)
147
+ class NumericDefaults:
148
+ float_base: float = 1.5
149
+ float32_value: float = 2.5
150
+ float64_value: float = 3.5
151
+ custom_float_value: float = 4.5
152
+ float_int: float = 10.0
153
+ float32_int: float = 20.0
154
+ float64_int: float = 30.0
155
+ decimal_base: Decimal = 100.25
156
+ decimal128_value: Decimal = 200.75
157
+ custom_decimal_value: Decimal = 300.125
158
+ decimal_int: Decimal = 400.0
159
+ decimal128_int: Decimal = 500.0
160
+
161
+ `,
162
+ );
163
+ });
164
+
165
+ it("distinguishes between integer and float types with same numeric value", async () => {
166
+ const { program, MixedNumeric } = await Tester.compile(t.code`
167
+ model ${t.model("MixedNumeric")} {
168
+ intValue: int32 = 100;
169
+ int64Value: int64 = 100;
170
+ floatValue: float = 100;
171
+ float64Value: float64 = 100;
172
+ decimalValue: decimal = 100;
173
+ }
174
+ `);
175
+
176
+ expect(getOutput(program, [<ClassDeclaration type={MixedNumeric} />])).toRenderTo(
177
+ `
178
+ from dataclasses import dataclass
179
+ from decimal import Decimal
180
+
181
+
182
+ @dataclass(kw_only=True)
183
+ class MixedNumeric:
184
+ int_value: int = 100
185
+ int64_value: int = 100
186
+ float_value: float = 100.0
187
+ float64_value: float = 100.0
188
+ decimal_value: Decimal = 100.0
189
+
190
+ `,
191
+ );
192
+ });
193
+ });
194
+ });
@@ -0,0 +1,67 @@
1
+ import { typingModule } from "#python/builtins.js";
2
+ import { type Children } from "@alloy-js/core";
3
+ import * as py from "@alloy-js/python";
4
+ import { type ModelProperty, type Operation } from "@typespec/compiler";
5
+ import { useTsp } from "../../../core/context/tsp-context.js";
6
+ import { TypeExpression } from "../type-expression/type-expression.js";
7
+ import { Method } from "./class-method.js";
8
+ import { PrimitiveInitializer } from "./primitive-initializer.js";
9
+
10
+ export interface ClassMemberProps {
11
+ type: ModelProperty | Operation;
12
+ doc?: Children;
13
+ optional?: boolean;
14
+ methodType?: "method" | "class" | "static";
15
+ abstract?: boolean;
16
+ }
17
+
18
+ /**
19
+ * Creates the class member for the property.
20
+ * @param props - The props for the class member.
21
+ * @returns The class member.
22
+ */
23
+ export function ClassMember(props: ClassMemberProps) {
24
+ const { $ } = useTsp();
25
+ const namer = py.usePythonNamePolicy();
26
+ const name = namer.getName(props.type.name, "class-member");
27
+ const doc = props.doc ?? $.type.getDoc(props.type);
28
+
29
+ if ($.modelProperty.is(props.type)) {
30
+ // Map never-typed properties to typing.Never
31
+
32
+ const unpackedType = props.type.type;
33
+ const isOptional = props.optional ?? props.type.optional ?? false;
34
+ const defaultValue = props.type.defaultValue;
35
+ const initializer = defaultValue ? (
36
+ <PrimitiveInitializer defaultValue={defaultValue} propertyType={unpackedType} />
37
+ ) : undefined;
38
+ const unpackedTypeNode: Children = <TypeExpression type={unpackedType} />;
39
+ const typeNode = isOptional ? (
40
+ <py.TypeReference
41
+ refkey={typingModule["."].Optional}
42
+ typeArgs={[unpackedTypeNode]}
43
+ ></py.TypeReference>
44
+ ) : (
45
+ unpackedTypeNode
46
+ );
47
+
48
+ const classMemberProps = {
49
+ doc,
50
+ name,
51
+ optional: isOptional,
52
+ ...(typeNode ? { type: typeNode } : {}),
53
+ ...(initializer ? { initializer } : {}),
54
+ omitNone: !isOptional,
55
+ };
56
+ return <py.VariableDeclaration {...classMemberProps} />;
57
+ }
58
+
59
+ if ($.operation.is(props.type)) {
60
+ return (
61
+ <Method type={props.type} doc={doc} methodType={props.methodType} abstract={props.abstract} />
62
+ );
63
+ }
64
+
65
+ // If type is neither ModelProperty nor Operation, return empty fragment
66
+ return <></>;
67
+ }
@@ -0,0 +1,250 @@
1
+ import { Tester } from "#test/test-host.js";
2
+ import { getProgram } from "#test/utils.js";
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 { getOutput } from "../../test-utils.js";
9
+
10
+ describe("interface methods with a `type` prop", () => {
11
+ it("creates a class method from an interface method", async () => {
12
+ const { program, getName } = await Tester.compile(t.code`
13
+ @test op ${t.op("getName")}(id: string): string;
14
+ `);
15
+
16
+ expect(
17
+ getOutput(program, [
18
+ <ClassDeclaration name="basicInterface">
19
+ <Method async type={getName} />
20
+ </ClassDeclaration>,
21
+ ]),
22
+ ).toRenderTo(`
23
+ class BasicInterface:
24
+ async def get_name(self, id: str) -> str:
25
+ pass
26
+
27
+
28
+ `);
29
+ });
30
+
31
+ it("creates a class method that is a classmethod", async () => {
32
+ const { program, getName } = await Tester.compile(t.code`
33
+ @test op ${t.op("getName")}(id: string): string;
34
+ `);
35
+
36
+ expect(
37
+ getOutput(program, [
38
+ <ClassDeclaration name="basicInterface">
39
+ <Method async type={getName} methodType="class" />
40
+ </ClassDeclaration>,
41
+ ]),
42
+ ).toRenderTo(`
43
+ class BasicInterface:
44
+ @classmethod
45
+ async def get_name(cls, id: str) -> str:
46
+ pass
47
+
48
+
49
+ `);
50
+ });
51
+
52
+ it("creates a class method that is a staticmethod", async () => {
53
+ const { program, getName } = await Tester.compile(t.code`
54
+ @test op ${t.op("getName")}(id: string): string;
55
+ `);
56
+
57
+ expect(
58
+ getOutput(program, [
59
+ <ClassDeclaration name="basicInterface">
60
+ <Method async type={getName} methodType="static" />
61
+ </ClassDeclaration>,
62
+ ]),
63
+ ).toRenderTo(`
64
+ class BasicInterface:
65
+ @staticmethod
66
+ async def get_name(id: str) -> str:
67
+ pass
68
+
69
+
70
+ `);
71
+ });
72
+
73
+ it("creates an async class method from an asyncinterface method", async () => {
74
+ const { program, getName } = await Tester.compile(t.code`
75
+ @test op ${t.op("getName")}(id: string): string;
76
+ `);
77
+
78
+ expect(
79
+ getOutput(program, [
80
+ <ClassDeclaration name="basicInterface">
81
+ <Method async type={getName} />
82
+ </ClassDeclaration>,
83
+ ]),
84
+ ).toRenderTo(`
85
+ class BasicInterface:
86
+ async def get_name(self, id: str) -> str:
87
+ pass
88
+
89
+
90
+ `);
91
+ });
92
+
93
+ it("can append extra keyword-only parameters", async () => {
94
+ const { program, getName } = await Tester.compile(t.code`
95
+ @test op ${t.op("getName")}(id: string): string;
96
+ `);
97
+
98
+ expect(
99
+ getOutput(program, [
100
+ <ClassDeclaration name="basicInterface">
101
+ <Method type={getName} parameters={[{ name: "foo", type: "string" }]} />
102
+ </ClassDeclaration>,
103
+ ]),
104
+ ).toRenderTo(`
105
+ class BasicInterface:
106
+ def get_name(self, id: str, *, foo: str) -> str:
107
+ pass
108
+
109
+
110
+ `);
111
+ });
112
+
113
+ it("can add extra keyword-only parameters", async () => {
114
+ const { program, getName } = await Tester.compile(t.code`
115
+ @test op ${t.op("getName")}(id: string): string;
116
+ `);
117
+
118
+ expect(
119
+ getOutput(program, [
120
+ <ClassDeclaration name="basicInterface">
121
+ <Method type={getName} parameters={[{ name: "foo", type: "string" }]} />
122
+ </ClassDeclaration>,
123
+ ]),
124
+ ).toRenderTo(`
125
+ class BasicInterface:
126
+ def get_name(self, id: str, *, foo: str) -> str:
127
+ pass
128
+
129
+
130
+ `);
131
+ });
132
+
133
+ it("can replace parameters with raw params provided", async () => {
134
+ const { program, getName } = await Tester.compile(t.code`
135
+ @test op ${t.op("getName")}(id: string): string;
136
+ `);
137
+
138
+ expect(
139
+ getOutput(program, [
140
+ <ClassDeclaration name="basicInterface">
141
+ <Method
142
+ type={getName}
143
+ replaceParameters={true}
144
+ parameters={[
145
+ { name: "foo", type: "string" },
146
+ { name: "bar", type: "number" },
147
+ ]}
148
+ />
149
+ </ClassDeclaration>,
150
+ ]),
151
+ ).toRenderTo(`
152
+ class BasicInterface:
153
+ def get_name(self, *, foo: str, bar: float) -> str:
154
+ pass
155
+
156
+
157
+ `);
158
+ });
159
+
160
+ it("can replace parameters with params having defaults (requires * marker)", async () => {
161
+ const { program, getName } = await Tester.compile(t.code`
162
+ @test op ${t.op("getName")}(id: string): string;
163
+ `);
164
+
165
+ expect(
166
+ getOutput(program, [
167
+ <ClassDeclaration name="basicInterface">
168
+ <Method
169
+ type={getName}
170
+ replaceParameters={true}
171
+ parameters={[
172
+ { name: "foo", type: "string", default: <py.Atom jsValue={"default"} /> },
173
+ { name: "bar", type: "number", default: <py.Atom jsValue={42} /> },
174
+ ]}
175
+ />
176
+ </ClassDeclaration>,
177
+ ]),
178
+ ).toRenderTo(`
179
+ class BasicInterface:
180
+ def get_name(self, *, foo: str = "default", bar: float = 42) -> str:
181
+ pass
182
+
183
+
184
+ `);
185
+ });
186
+
187
+ it("can override return type in a class method", async () => {
188
+ const { program, getName } = await Tester.compile(t.code`
189
+ @test op ${t.op("getName")}(id: string): string;
190
+ `);
191
+
192
+ expect(
193
+ getOutput(program, [
194
+ <ClassDeclaration name="basicInterface">
195
+ <Method type={getName} returnType="ASpecialString" />
196
+ </ClassDeclaration>,
197
+ ]),
198
+ ).toRenderTo(`
199
+ class BasicInterface:
200
+ def get_name(self, id: str) -> ASpecialString:
201
+ pass
202
+
203
+
204
+ `);
205
+ });
206
+
207
+ it("can override method name in a class method", async () => {
208
+ const { program, getName } = await Tester.compile(t.code`
209
+ @test op ${t.op("getName")}(id: string): string;
210
+ `);
211
+
212
+ expect(
213
+ getOutput(program, [
214
+ <ClassDeclaration name="basicInterface">
215
+ <Method type={getName} name="get_name_custom" />
216
+ </ClassDeclaration>,
217
+ ]),
218
+ ).toRenderTo(`
219
+ class BasicInterface:
220
+ def get_name_custom(self, id: str) -> str:
221
+ pass
222
+
223
+
224
+ `);
225
+ });
226
+ });
227
+
228
+ describe("interface methods without a `type` prop", () => {
229
+ it("renders a plain interface method from a class method without a `type` prop", async () => {
230
+ const program = await getProgram("");
231
+
232
+ expect(
233
+ getOutput(program, [
234
+ <ClassDeclaration name="basicInterface">
235
+ <Method
236
+ name="plainMethod"
237
+ parameters={[{ name: "param1", type: "string" }]}
238
+ returnType="number"
239
+ />
240
+ </ClassDeclaration>,
241
+ ]),
242
+ ).toRenderTo(`
243
+ class BasicInterface:
244
+ def plain_method(self, param1: string) -> number:
245
+ pass
246
+
247
+
248
+ `);
249
+ });
250
+ });