@alloy-js/python 0.5.0-dev.0 → 0.5.0-dev.2

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 (276) hide show
  1. package/dist/dev/src/builtins/python.js +46 -0
  2. package/dist/dev/src/builtins/python.js.map +1 -1
  3. package/dist/dev/src/components/ClassDeclaration.js +19 -10
  4. package/dist/dev/src/components/ClassDeclaration.js.map +1 -1
  5. package/dist/dev/src/components/ClassMethodDeclaration.js +20 -5
  6. package/dist/dev/src/components/ClassMethodDeclaration.js.map +1 -1
  7. package/dist/dev/src/components/DataclassDeclaration.js +14 -12
  8. package/dist/dev/src/components/DataclassDeclaration.js.map +1 -1
  9. package/dist/dev/src/components/DecoratorList.js +55 -0
  10. package/dist/dev/src/components/DecoratorList.js.map +1 -0
  11. package/dist/dev/src/components/EnumDeclaration.js +21 -12
  12. package/dist/dev/src/components/EnumDeclaration.js.map +1 -1
  13. package/dist/dev/src/components/FunctionBase.js +21 -10
  14. package/dist/dev/src/components/FunctionBase.js.map +1 -1
  15. package/dist/dev/src/components/FutureStatement.js +1 -1
  16. package/dist/dev/src/components/MethodBase.js +16 -4
  17. package/dist/dev/src/components/MethodBase.js.map +1 -1
  18. package/dist/dev/src/components/PropertyDeclaration.js +68 -17
  19. package/dist/dev/src/components/PropertyDeclaration.js.map +1 -1
  20. package/dist/dev/src/components/PydanticClassDeclaration.js +136 -0
  21. package/dist/dev/src/components/PydanticClassDeclaration.js.map +1 -0
  22. package/dist/dev/src/components/SourceFile.js +44 -32
  23. package/dist/dev/src/components/SourceFile.js.map +1 -1
  24. package/dist/dev/src/components/StaticMethodDeclaration.js +19 -5
  25. package/dist/dev/src/components/StaticMethodDeclaration.js.map +1 -1
  26. package/dist/dev/src/components/index.js +1 -0
  27. package/dist/dev/src/components/index.js.map +1 -1
  28. package/dist/dev/test/callsignatures.test.js +471 -297
  29. package/dist/dev/test/callsignatures.test.js.map +1 -1
  30. package/dist/dev/test/class-method-declaration.test.js +21 -10
  31. package/dist/dev/test/class-method-declaration.test.js.map +1 -1
  32. package/dist/dev/test/classdeclarations.test.js +480 -381
  33. package/dist/dev/test/classdeclarations.test.js.map +1 -1
  34. package/dist/dev/test/classinstantiations.test.js +201 -168
  35. package/dist/dev/test/classinstantiations.test.js.map +1 -1
  36. package/dist/dev/test/constructordeclaration.test.js +22 -11
  37. package/dist/dev/test/constructordeclaration.test.js.map +1 -1
  38. package/dist/dev/test/dataclassdeclarations.test.js +345 -358
  39. package/dist/dev/test/dataclassdeclarations.test.js.map +1 -1
  40. package/dist/dev/test/decoratorlist.test.js +131 -0
  41. package/dist/dev/test/decoratorlist.test.js.map +1 -0
  42. package/dist/dev/test/dundermethoddeclaration.test.js +22 -11
  43. package/dist/dev/test/dundermethoddeclaration.test.js.map +1 -1
  44. package/dist/dev/test/enums.test.js +231 -166
  45. package/dist/dev/test/enums.test.js.map +1 -1
  46. package/dist/dev/test/externals.test.js +57 -45
  47. package/dist/dev/test/externals.test.js.map +1 -1
  48. package/dist/dev/test/factories.test.js +124 -50
  49. package/dist/dev/test/factories.test.js.map +1 -1
  50. package/dist/dev/test/functioncallexpressions.test.js +199 -164
  51. package/dist/dev/test/functioncallexpressions.test.js.map +1 -1
  52. package/dist/dev/test/functiondeclaration.test.js +452 -265
  53. package/dist/dev/test/functiondeclaration.test.js.map +1 -1
  54. package/dist/dev/test/imports.test.js +273 -221
  55. package/dist/dev/test/imports.test.js.map +1 -1
  56. package/dist/dev/test/memberexpressions.test.js +1237 -972
  57. package/dist/dev/test/memberexpressions.test.js.map +1 -1
  58. package/dist/dev/test/methoddeclaration.test.js +200 -45
  59. package/dist/dev/test/methoddeclaration.test.js.map +1 -1
  60. package/dist/dev/test/namepolicies.test.js +130 -94
  61. package/dist/dev/test/namepolicies.test.js.map +1 -1
  62. package/dist/dev/test/propertydeclaration.test.js +177 -46
  63. package/dist/dev/test/propertydeclaration.test.js.map +1 -1
  64. package/dist/dev/test/pydanticclassdeclarations.test.js +1089 -0
  65. package/dist/dev/test/pydanticclassdeclarations.test.js.map +1 -0
  66. package/dist/dev/test/pydocs.test.js +888 -715
  67. package/dist/dev/test/pydocs.test.js.map +1 -1
  68. package/dist/dev/test/references.test.js +42 -35
  69. package/dist/dev/test/references.test.js.map +1 -1
  70. package/dist/dev/test/sourcefiles.test.js +1109 -841
  71. package/dist/dev/test/sourcefiles.test.js.map +1 -1
  72. package/dist/dev/test/staticmethoddeclaration.test.js +21 -10
  73. package/dist/dev/test/staticmethoddeclaration.test.js.map +1 -1
  74. package/dist/dev/test/type-checking-imports.test.js +408 -359
  75. package/dist/dev/test/type-checking-imports.test.js.map +1 -1
  76. package/dist/dev/test/typereference.test.js +55 -40
  77. package/dist/dev/test/typereference.test.js.map +1 -1
  78. package/dist/dev/test/uniontypeexpression.test.js +222 -146
  79. package/dist/dev/test/uniontypeexpression.test.js.map +1 -1
  80. package/dist/dev/test/utils.js +39 -77
  81. package/dist/dev/test/utils.js.map +1 -1
  82. package/dist/dev/test/values.test.js +237 -101
  83. package/dist/dev/test/values.test.js.map +1 -1
  84. package/dist/dev/test/variables.test.js +321 -203
  85. package/dist/dev/test/variables.test.js.map +1 -1
  86. package/dist/dev/test/vitest.setup.js +2 -0
  87. package/dist/dev/test/vitest.setup.js.map +1 -0
  88. package/dist/src/builtins/python.d.ts +30 -0
  89. package/dist/src/builtins/python.d.ts.map +1 -1
  90. package/dist/src/builtins/python.js +46 -0
  91. package/dist/src/builtins/python.js.map +1 -1
  92. package/dist/src/components/ClassDeclaration.d.ts +21 -0
  93. package/dist/src/components/ClassDeclaration.d.ts.map +1 -1
  94. package/dist/src/components/ClassDeclaration.js +6 -1
  95. package/dist/src/components/ClassDeclaration.js.map +1 -1
  96. package/dist/src/components/ClassMethodDeclaration.d.ts +5 -1
  97. package/dist/src/components/ClassMethodDeclaration.d.ts.map +1 -1
  98. package/dist/src/components/ClassMethodDeclaration.js +14 -3
  99. package/dist/src/components/ClassMethodDeclaration.js.map +1 -1
  100. package/dist/src/components/DataclassDeclaration.d.ts.map +1 -1
  101. package/dist/src/components/DataclassDeclaration.js +10 -4
  102. package/dist/src/components/DataclassDeclaration.js.map +1 -1
  103. package/dist/src/components/DecoratorList.d.ts +43 -0
  104. package/dist/src/components/DecoratorList.d.ts.map +1 -0
  105. package/dist/src/components/DecoratorList.js +47 -0
  106. package/dist/src/components/DecoratorList.js.map +1 -0
  107. package/dist/src/components/EnumDeclaration.d.ts +9 -0
  108. package/dist/src/components/EnumDeclaration.d.ts.map +1 -1
  109. package/dist/src/components/EnumDeclaration.js +6 -1
  110. package/dist/src/components/EnumDeclaration.js.map +1 -1
  111. package/dist/src/components/FunctionBase.d.ts +31 -1
  112. package/dist/src/components/FunctionBase.d.ts.map +1 -1
  113. package/dist/src/components/FunctionBase.js +9 -2
  114. package/dist/src/components/FunctionBase.js.map +1 -1
  115. package/dist/src/components/FutureStatement.d.ts +1 -1
  116. package/dist/src/components/FutureStatement.js +1 -1
  117. package/dist/src/components/MethodBase.d.ts.map +1 -1
  118. package/dist/src/components/MethodBase.js +10 -2
  119. package/dist/src/components/MethodBase.js.map +1 -1
  120. package/dist/src/components/PropertyDeclaration.d.ts +29 -0
  121. package/dist/src/components/PropertyDeclaration.d.ts.map +1 -1
  122. package/dist/src/components/PropertyDeclaration.js +48 -1
  123. package/dist/src/components/PropertyDeclaration.js.map +1 -1
  124. package/dist/src/components/PydanticClassDeclaration.d.ts +120 -0
  125. package/dist/src/components/PydanticClassDeclaration.d.ts.map +1 -0
  126. package/dist/src/components/PydanticClassDeclaration.js +116 -0
  127. package/dist/src/components/PydanticClassDeclaration.js.map +1 -0
  128. package/dist/src/components/SourceFile.d.ts +2 -2
  129. package/dist/src/components/SourceFile.d.ts.map +1 -1
  130. package/dist/src/components/SourceFile.js +12 -0
  131. package/dist/src/components/SourceFile.js.map +1 -1
  132. package/dist/src/components/StaticMethodDeclaration.d.ts +3 -0
  133. package/dist/src/components/StaticMethodDeclaration.d.ts.map +1 -1
  134. package/dist/src/components/StaticMethodDeclaration.js +13 -3
  135. package/dist/src/components/StaticMethodDeclaration.js.map +1 -1
  136. package/dist/src/components/index.d.ts +1 -0
  137. package/dist/src/components/index.d.ts.map +1 -1
  138. package/dist/src/components/index.js +1 -0
  139. package/dist/src/components/index.js.map +1 -1
  140. package/dist/test/callsignatures.test.js +346 -272
  141. package/dist/test/callsignatures.test.js.map +1 -1
  142. package/dist/test/class-method-declaration.test.js +7 -4
  143. package/dist/test/class-method-declaration.test.js.map +1 -1
  144. package/dist/test/classdeclarations.test.js +316 -277
  145. package/dist/test/classdeclarations.test.js.map +1 -1
  146. package/dist/test/classinstantiations.test.js +112 -103
  147. package/dist/test/classinstantiations.test.js.map +1 -1
  148. package/dist/test/constructordeclaration.test.js +7 -4
  149. package/dist/test/constructordeclaration.test.js.map +1 -1
  150. package/dist/test/dataclassdeclarations.test.js +153 -178
  151. package/dist/test/dataclassdeclarations.test.js.map +1 -1
  152. package/dist/test/decoratorlist.test.d.ts +2 -0
  153. package/dist/test/decoratorlist.test.d.ts.map +1 -0
  154. package/dist/test/decoratorlist.test.js +83 -0
  155. package/dist/test/decoratorlist.test.js.map +1 -0
  156. package/dist/test/dundermethoddeclaration.test.js +7 -4
  157. package/dist/test/dundermethoddeclaration.test.js.map +1 -1
  158. package/dist/test/enums.test.js +172 -143
  159. package/dist/test/enums.test.js.map +1 -1
  160. package/dist/test/externals.test.js +24 -24
  161. package/dist/test/externals.test.js.map +1 -1
  162. package/dist/test/factories.test.js +75 -33
  163. package/dist/test/factories.test.js.map +1 -1
  164. package/dist/test/functioncallexpressions.test.js +117 -106
  165. package/dist/test/functioncallexpressions.test.js.map +1 -1
  166. package/dist/test/functiondeclaration.test.js +256 -173
  167. package/dist/test/functiondeclaration.test.js.map +1 -1
  168. package/dist/test/imports.test.js +171 -143
  169. package/dist/test/imports.test.js.map +1 -1
  170. package/dist/test/memberexpressions.test.js +582 -453
  171. package/dist/test/memberexpressions.test.js.map +1 -1
  172. package/dist/test/methoddeclaration.test.js +106 -19
  173. package/dist/test/methoddeclaration.test.js.map +1 -1
  174. package/dist/test/namepolicies.test.js +90 -78
  175. package/dist/test/namepolicies.test.js.map +1 -1
  176. package/dist/test/propertydeclaration.test.js +90 -15
  177. package/dist/test/propertydeclaration.test.js.map +1 -1
  178. package/dist/test/pydanticclassdeclarations.test.d.ts +2 -0
  179. package/dist/test/pydanticclassdeclarations.test.d.ts.map +1 -0
  180. package/dist/test/pydanticclassdeclarations.test.js +773 -0
  181. package/dist/test/pydanticclassdeclarations.test.js.map +1 -0
  182. package/dist/test/pydocs.test.js +573 -532
  183. package/dist/test/pydocs.test.js.map +1 -1
  184. package/dist/test/references.test.js +31 -28
  185. package/dist/test/references.test.js.map +1 -1
  186. package/dist/test/sourcefiles.test.js +700 -580
  187. package/dist/test/sourcefiles.test.js.map +1 -1
  188. package/dist/test/staticmethoddeclaration.test.js +7 -4
  189. package/dist/test/staticmethoddeclaration.test.js.map +1 -1
  190. package/dist/test/type-checking-imports.test.js +297 -284
  191. package/dist/test/type-checking-imports.test.js.map +1 -1
  192. package/dist/test/typereference.test.js +29 -22
  193. package/dist/test/typereference.test.js.map +1 -1
  194. package/dist/test/uniontypeexpression.test.js +124 -88
  195. package/dist/test/uniontypeexpression.test.js.map +1 -1
  196. package/dist/test/utils.d.ts +10 -17
  197. package/dist/test/utils.d.ts.map +1 -1
  198. package/dist/test/utils.js +32 -74
  199. package/dist/test/utils.js.map +1 -1
  200. package/dist/test/values.test.js +135 -67
  201. package/dist/test/values.test.js.map +1 -1
  202. package/dist/test/variables.test.js +201 -151
  203. package/dist/test/variables.test.js.map +1 -1
  204. package/dist/test/vitest.setup.d.ts +2 -0
  205. package/dist/test/vitest.setup.d.ts.map +1 -0
  206. package/dist/test/vitest.setup.js +2 -0
  207. package/dist/test/vitest.setup.js.map +1 -0
  208. package/dist/tsconfig.tsbuildinfo +1 -1
  209. package/docs/api/components/ClassDeclaration.md +10 -7
  210. package/docs/api/components/ClassEnumDeclaration.md +9 -6
  211. package/docs/api/components/ClassMethodDeclaration.md +7 -5
  212. package/docs/api/components/DataclassDeclaration.md +9 -5
  213. package/docs/api/components/DunderMethodDeclaration.md +7 -5
  214. package/docs/api/components/FunctionDeclaration.md +9 -5
  215. package/docs/api/components/FutureStatement.md +1 -1
  216. package/docs/api/components/MethodDeclaration.md +11 -6
  217. package/docs/api/components/PropertyDeclaration.md +11 -8
  218. package/docs/api/components/PydanticClassDeclaration.md +146 -0
  219. package/docs/api/components/SourceFile.md +20 -8
  220. package/docs/api/components/StaticMethodDeclaration.md +7 -5
  221. package/docs/api/components/index.md +1 -0
  222. package/docs/api/index.md +3 -3
  223. package/docs/api/types/CommonFunctionProps.md +4 -3
  224. package/docs/api/types/PydanticModelConfigDictProps.md +32 -0
  225. package/docs/api/types/index.md +1 -0
  226. package/docs/api/variables/index.md +3 -0
  227. package/docs/api/variables/pydanticModule.md +27 -0
  228. package/docs/api/variables/pydanticSettingsModule.md +7 -0
  229. package/docs/api/variables/typingModule.md +9 -0
  230. package/package.json +4 -4
  231. package/src/builtins/python.ts +539 -1
  232. package/src/components/ClassDeclaration.tsx +23 -0
  233. package/src/components/ClassMethodDeclaration.tsx +9 -1
  234. package/src/components/DataclassDeclaration.tsx +18 -11
  235. package/src/components/DecoratorList.tsx +50 -0
  236. package/src/components/EnumDeclaration.tsx +11 -0
  237. package/src/components/FunctionBase.tsx +34 -3
  238. package/src/components/FutureStatement.tsx +1 -1
  239. package/src/components/MethodBase.tsx +6 -2
  240. package/src/components/PropertyDeclaration.tsx +48 -1
  241. package/src/components/PydanticClassDeclaration.tsx +222 -0
  242. package/src/components/SourceFile.tsx +6 -1
  243. package/src/components/StaticMethodDeclaration.tsx +7 -1
  244. package/src/components/index.ts +1 -0
  245. package/temp/api.json +1158 -86
  246. package/test/callsignatures.test.tsx +309 -283
  247. package/test/class-method-declaration.test.tsx +3 -4
  248. package/test/classdeclarations.test.tsx +277 -235
  249. package/test/classinstantiations.test.tsx +115 -109
  250. package/test/constructordeclaration.test.tsx +9 -6
  251. package/test/dataclassdeclarations.test.tsx +256 -349
  252. package/test/decoratorlist.test.tsx +114 -0
  253. package/test/dundermethoddeclaration.test.tsx +3 -4
  254. package/test/enums.test.tsx +84 -71
  255. package/test/externals.test.tsx +25 -25
  256. package/test/factories.test.tsx +64 -22
  257. package/test/functioncallexpressions.test.tsx +123 -109
  258. package/test/functiondeclaration.test.tsx +218 -140
  259. package/test/imports.test.tsx +119 -91
  260. package/test/memberexpressions.test.tsx +265 -207
  261. package/test/methoddeclaration.test.tsx +115 -24
  262. package/test/namepolicies.test.tsx +69 -69
  263. package/test/propertydeclaration.test.tsx +71 -7
  264. package/test/pydanticclassdeclarations.test.tsx +704 -0
  265. package/test/pydocs.test.tsx +531 -579
  266. package/test/references.test.tsx +24 -23
  267. package/test/sourcefiles.test.tsx +527 -492
  268. package/test/staticmethoddeclaration.test.tsx +3 -4
  269. package/test/type-checking-imports.test.tsx +206 -218
  270. package/test/typereference.test.tsx +15 -12
  271. package/test/uniontypeexpression.test.tsx +74 -61
  272. package/test/utils.tsx +26 -110
  273. package/test/values.test.tsx +82 -32
  274. package/test/variables.test.tsx +162 -142
  275. package/test/vitest.setup.ts +1 -0
  276. package/vitest.config.ts +3 -0
@@ -0,0 +1,704 @@
1
+ import { Prose, code, refkey } from "@alloy-js/core";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ pydanticModule,
5
+ pydanticSettingsModule,
6
+ typingModule,
7
+ } from "../src/builtins/python.js";
8
+ import * as py from "../src/index.js";
9
+ import { TestOutput, TestOutputDirectory } from "./utils.js";
10
+
11
+ describe("PydanticClassDeclaration", () => {
12
+ it("forwards class-level decorators above `class`", () => {
13
+ expect(
14
+ <TestOutput path="models.py" externals={[pydanticModule, typingModule]}>
15
+ <py.PydanticClassDeclaration
16
+ name="User"
17
+ decorators={[code`@${typingModule["."].final}`]}
18
+ >
19
+ <py.VariableDeclaration
20
+ instanceVariable
21
+ omitNone
22
+ name="id"
23
+ type="int"
24
+ />
25
+ </py.PydanticClassDeclaration>
26
+ </TestOutput>,
27
+ ).toRenderTo(
28
+ `
29
+ from pydantic import BaseModel
30
+ from typing import final
31
+
32
+
33
+ @final
34
+ class User(BaseModel):
35
+ id: int
36
+
37
+ `,
38
+ );
39
+ });
40
+
41
+ it("emits a pydantic model with BaseModel, fields, and Field import", () => {
42
+ expect(
43
+ <TestOutput path="models.py" externals={[pydanticModule]}>
44
+ <py.PydanticClassDeclaration name="User">
45
+ <py.VariableDeclaration
46
+ instanceVariable
47
+ omitNone
48
+ name="id"
49
+ type="int"
50
+ />
51
+ <py.VariableDeclaration
52
+ instanceVariable
53
+ name="name"
54
+ type="str"
55
+ initializer={code`${pydanticModule["."].Field}(default="anon")`}
56
+ />
57
+ </py.PydanticClassDeclaration>
58
+ </TestOutput>,
59
+ ).toRenderTo(
60
+ `
61
+ from pydantic import BaseModel
62
+ from pydantic import Field
63
+
64
+
65
+ class User(BaseModel):
66
+ id: int
67
+ name: str = Field(default="anon")
68
+
69
+ `,
70
+ );
71
+ });
72
+
73
+ it("inherits from another pydantic model via bases", () => {
74
+ const baseRef = refkey();
75
+ expect(
76
+ <TestOutput path="models.py" externals={[pydanticModule]}>
77
+ <py.StatementList>
78
+ <py.PydanticClassDeclaration name="User" refkey={baseRef}>
79
+ <py.VariableDeclaration
80
+ instanceVariable
81
+ omitNone
82
+ name="id"
83
+ type="int"
84
+ />
85
+ </py.PydanticClassDeclaration>
86
+ <py.PydanticClassDeclaration name="Admin" bases={[baseRef]}>
87
+ <py.VariableDeclaration
88
+ instanceVariable
89
+ omitNone
90
+ name="role"
91
+ type="str"
92
+ />
93
+ </py.PydanticClassDeclaration>
94
+ </py.StatementList>
95
+ </TestOutput>,
96
+ ).toRenderTo(
97
+ `
98
+ from pydantic import BaseModel
99
+
100
+
101
+ class User(BaseModel):
102
+ id: int
103
+
104
+ class Admin(User):
105
+ role: str
106
+
107
+ `,
108
+ );
109
+ });
110
+
111
+ it("supports required, optional, Field default, and plain default", () => {
112
+ expect(
113
+ <TestOutput path="models.py" externals={[pydanticModule]}>
114
+ <py.PydanticClassDeclaration name="Item">
115
+ <py.VariableDeclaration
116
+ instanceVariable
117
+ omitNone
118
+ name="sku"
119
+ type="str"
120
+ />
121
+ <py.VariableDeclaration
122
+ instanceVariable
123
+ name="notes"
124
+ type={<py.UnionTypeExpression children={["str", "None"]} />}
125
+ />
126
+ <py.VariableDeclaration
127
+ instanceVariable
128
+ name="label"
129
+ type="str"
130
+ initializer={code`${pydanticModule["."].Field}(default="untitled")`}
131
+ />
132
+ <py.VariableDeclaration
133
+ instanceVariable
134
+ name="qty"
135
+ type="int"
136
+ initializer={1}
137
+ />
138
+ </py.PydanticClassDeclaration>
139
+ </TestOutput>,
140
+ ).toRenderTo(
141
+ `
142
+ from pydantic import BaseModel
143
+ from pydantic import Field
144
+
145
+
146
+ class Item(BaseModel):
147
+ sku: str
148
+ notes: str | None = None
149
+ label: str = Field(default="untitled")
150
+ qty: int = 1
151
+
152
+ `,
153
+ );
154
+ });
155
+
156
+ it("emits class docstring", () => {
157
+ const doc = (
158
+ <py.ClassDoc description={[<Prose>Payload for an API request.</Prose>]} />
159
+ );
160
+ expect(
161
+ <TestOutput path="models.py" externals={[pydanticModule]}>
162
+ <py.PydanticClassDeclaration name="RequestBody" doc={doc}>
163
+ <py.VariableDeclaration
164
+ instanceVariable
165
+ omitNone
166
+ name="value"
167
+ type="str"
168
+ />
169
+ </py.PydanticClassDeclaration>
170
+ </TestOutput>,
171
+ ).toRenderTo(
172
+ `
173
+ from pydantic import BaseModel
174
+
175
+
176
+ class RequestBody(BaseModel):
177
+ """
178
+ Payload for an API request.
179
+ """
180
+
181
+ value: str
182
+
183
+ `,
184
+ );
185
+ });
186
+
187
+ it("resolves refkey across files with pydantic imports", () => {
188
+ const modelRef = refkey();
189
+ expect(
190
+ <TestOutputDirectory externals={[pydanticModule]}>
191
+ <py.SourceFile path="models.py">
192
+ <py.PydanticClassDeclaration name="User" refkey={modelRef}>
193
+ <py.VariableDeclaration
194
+ instanceVariable
195
+ omitNone
196
+ name="id"
197
+ type="int"
198
+ />
199
+ </py.PydanticClassDeclaration>
200
+ </py.SourceFile>
201
+ <py.SourceFile path="service.py">
202
+ <py.FunctionDeclaration
203
+ name="load_user"
204
+ returnType={modelRef}
205
+ parameters={[{ name: "user_id", type: "int" }]}
206
+ >
207
+ {"return User(id=user_id)"}
208
+ </py.FunctionDeclaration>
209
+ </py.SourceFile>
210
+ </TestOutputDirectory>,
211
+ ).toRenderTo({
212
+ "models.py": `
213
+ from pydantic import BaseModel
214
+
215
+
216
+ class User(BaseModel):
217
+ id: int
218
+
219
+ `,
220
+ "service.py": `
221
+ from typing import TYPE_CHECKING
222
+
223
+ if TYPE_CHECKING:
224
+ from models import User
225
+
226
+
227
+ def load_user(user_id: int) -> User:
228
+ return User(id=user_id)
229
+
230
+ `,
231
+ });
232
+ });
233
+
234
+ it("emits model_config = ConfigDict(...) from top-level config props", () => {
235
+ expect(
236
+ <TestOutput path="models.py" externals={[pydanticModule]}>
237
+ <py.PydanticClassDeclaration
238
+ name="User"
239
+ frozen
240
+ extra="forbid"
241
+ validateAssignment
242
+ >
243
+ <py.VariableDeclaration
244
+ instanceVariable
245
+ omitNone
246
+ name="id"
247
+ type="int"
248
+ />
249
+ </py.PydanticClassDeclaration>
250
+ </TestOutput>,
251
+ ).toRenderTo(
252
+ `
253
+ from pydantic import BaseModel
254
+ from pydantic import ConfigDict
255
+
256
+
257
+ class User(BaseModel):
258
+ model_config = ConfigDict(frozen=True, extra="forbid", validate_assignment=True)
259
+ id: int
260
+
261
+ `,
262
+ );
263
+ });
264
+
265
+ it("emits model_config = ConfigDict(...) from modelConfig", () => {
266
+ expect(
267
+ <TestOutput path="models.py" externals={[pydanticModule]}>
268
+ <py.PydanticClassDeclaration
269
+ name="User"
270
+ modelConfig={{
271
+ frozen: true,
272
+ extra: "forbid",
273
+ validateAssignment: true,
274
+ }}
275
+ >
276
+ <py.VariableDeclaration
277
+ instanceVariable
278
+ omitNone
279
+ name="id"
280
+ type="int"
281
+ />
282
+ </py.PydanticClassDeclaration>
283
+ </TestOutput>,
284
+ ).toRenderTo(
285
+ `
286
+ from pydantic import BaseModel
287
+ from pydantic import ConfigDict
288
+
289
+
290
+ class User(BaseModel):
291
+ model_config = ConfigDict(frozen=True, extra="forbid", validate_assignment=True)
292
+ id: int
293
+
294
+ `,
295
+ );
296
+ });
297
+
298
+ it("gives precedence to top-level config props over modelConfig", () => {
299
+ expect(
300
+ <TestOutput path="models.py" externals={[pydanticModule]}>
301
+ <py.PydanticClassDeclaration
302
+ name="User"
303
+ modelConfig={{ frozen: false, extra: "allow" }}
304
+ frozen
305
+ extra="forbid"
306
+ />
307
+ </TestOutput>,
308
+ ).toRenderTo(
309
+ `
310
+ from pydantic import BaseModel
311
+ from pydantic import ConfigDict
312
+
313
+
314
+ class User(BaseModel):
315
+ model_config = ConfigDict(frozen=True, extra="forbid")
316
+
317
+ `,
318
+ );
319
+ });
320
+
321
+ it("supports additional typed ConfigDict props", () => {
322
+ expect(
323
+ <TestOutput path="models.py" externals={[pydanticModule]}>
324
+ <py.PydanticClassDeclaration
325
+ name="User"
326
+ useEnumValues
327
+ coerceNumbersToStr
328
+ validateReturn
329
+ strMinLength={1}
330
+ strMaxLength={128}
331
+ serJsonBytes="base64"
332
+ valJsonBytes="hex"
333
+ />
334
+ </TestOutput>,
335
+ ).toRenderTo(
336
+ `
337
+ from pydantic import BaseModel
338
+ from pydantic import ConfigDict
339
+
340
+
341
+ class User(BaseModel):
342
+ model_config = ConfigDict(coerce_numbers_to_str=True, ser_json_bytes="base64", str_min_length=1, str_max_length=128, use_enum_values=True, val_json_bytes="hex", validate_return=True)
343
+
344
+ `,
345
+ );
346
+ });
347
+
348
+ it("imports SecretStr when used as a field type", () => {
349
+ expect(
350
+ <TestOutput path="models.py" externals={[pydanticModule]}>
351
+ <py.PydanticClassDeclaration name="Credentials">
352
+ <py.VariableDeclaration
353
+ instanceVariable
354
+ omitNone
355
+ name="token"
356
+ type={pydanticModule["."].SecretStr}
357
+ />
358
+ </py.PydanticClassDeclaration>
359
+ </TestOutput>,
360
+ ).toRenderTo(
361
+ `
362
+ from pydantic import BaseModel
363
+ from typing import TYPE_CHECKING
364
+
365
+ if TYPE_CHECKING:
366
+ from pydantic import SecretStr
367
+
368
+
369
+ class Credentials(BaseModel):
370
+ token: SecretStr
371
+
372
+ `,
373
+ );
374
+ });
375
+
376
+ it("emits arbitrary model_config via modelConfigExpression", () => {
377
+ expect(
378
+ <TestOutput path="models.py" externals={[pydanticModule]}>
379
+ <py.PydanticClassDeclaration
380
+ name="M"
381
+ modelConfigExpression={code`${pydanticModule["."].ConfigDict}(frozen=True, extra="allow")`}
382
+ />
383
+ </TestOutput>,
384
+ ).toRenderTo(
385
+ `
386
+ from pydantic import BaseModel
387
+ from pydantic import ConfigDict
388
+
389
+
390
+ class M(BaseModel):
391
+ model_config = ConfigDict(frozen=True, extra="allow")
392
+
393
+ `,
394
+ );
395
+ });
396
+
397
+ it("supports RootModel as explicit bases entry", () => {
398
+ expect(
399
+ <TestOutput path="models.py" externals={[pydanticModule]}>
400
+ <py.PydanticClassDeclaration
401
+ name="Tags"
402
+ bases={[code`${pydanticModule["."].RootModel}[list[str]]`]}
403
+ />
404
+ </TestOutput>,
405
+ ).toRenderTo(
406
+ `
407
+ from pydantic import RootModel
408
+
409
+
410
+ class Tags(RootModel[list[str]]):
411
+ pass
412
+
413
+ `,
414
+ );
415
+ });
416
+
417
+ it("places Pydantic validators above classmethod", () => {
418
+ expect(
419
+ <TestOutput path="models.py" externals={[pydanticModule]}>
420
+ <py.PydanticClassDeclaration name="User">
421
+ <py.VariableDeclaration
422
+ instanceVariable
423
+ omitNone
424
+ name="name"
425
+ type="str"
426
+ />
427
+ <py.ClassMethodDeclaration
428
+ name="strip_name"
429
+ decorators={[
430
+ code`@${pydanticModule["."].field_validator}("name", mode="before")`,
431
+ ]}
432
+ parameters={[{ name: "value", type: "str" }]}
433
+ returnType="str"
434
+ >
435
+ {"return value.strip()"}
436
+ </py.ClassMethodDeclaration>
437
+ </py.PydanticClassDeclaration>
438
+ </TestOutput>,
439
+ ).toRenderTo(
440
+ `
441
+ from pydantic import BaseModel
442
+ from pydantic import field_validator
443
+
444
+
445
+ class User(BaseModel):
446
+ name: str
447
+ @field_validator("name", mode="before")
448
+ @classmethod
449
+ def strip_name(cls, value: str) -> str:
450
+ return value.strip()
451
+
452
+
453
+ `,
454
+ );
455
+ });
456
+ });
457
+
458
+ describe("Pydantic ecosystem emitters", () => {
459
+ it("typing module resolves Any and similar annotations", () => {
460
+ expect(
461
+ <TestOutput path="models.py" externals={[typingModule]}>
462
+ <py.FunctionDeclaration
463
+ name="identity"
464
+ parameters={[{ name: "x", type: typingModule["."].Any }]}
465
+ returnType={typingModule["."].Any}
466
+ >
467
+ {"return x"}
468
+ </py.FunctionDeclaration>
469
+ </TestOutput>,
470
+ ).toRenderTo(
471
+ `
472
+ from typing import TYPE_CHECKING
473
+
474
+ if TYPE_CHECKING:
475
+ from typing import Any
476
+
477
+
478
+ def identity(x: Any) -> Any:
479
+ return x
480
+
481
+ `,
482
+ );
483
+ });
484
+
485
+ it("pydantic.types constrains field annotations", () => {
486
+ expect(
487
+ <TestOutput path="models.py" externals={[pydanticModule]}>
488
+ <py.PydanticClassDeclaration name="Score">
489
+ <py.VariableDeclaration
490
+ instanceVariable
491
+ omitNone
492
+ name="points"
493
+ type={pydanticModule.types.PositiveInt}
494
+ />
495
+ </py.PydanticClassDeclaration>
496
+ </TestOutput>,
497
+ ).toRenderTo(
498
+ `
499
+ from pydantic import BaseModel
500
+ from typing import TYPE_CHECKING
501
+
502
+ if TYPE_CHECKING:
503
+ from pydantic.types import PositiveInt
504
+
505
+
506
+ class Score(BaseModel):
507
+ points: PositiveInt
508
+
509
+ `,
510
+ );
511
+ });
512
+
513
+ it("postponed annotations support forward references in fields", () => {
514
+ expect(
515
+ <TestOutputDirectory externals={[pydanticModule]}>
516
+ <py.SourceFile
517
+ path="models.py"
518
+ futureImports={[<py.FutureStatement feature="annotations" />]}
519
+ >
520
+ <py.PydanticClassDeclaration name="Node">
521
+ <py.VariableDeclaration
522
+ instanceVariable
523
+ omitNone
524
+ name="label"
525
+ type="str"
526
+ />
527
+ <py.VariableDeclaration
528
+ instanceVariable
529
+ name="child"
530
+ type={code`"Node" | None`}
531
+ />
532
+ </py.PydanticClassDeclaration>
533
+ </py.SourceFile>
534
+ </TestOutputDirectory>,
535
+ ).toRenderTo({
536
+ "models.py": `
537
+ from __future__ import annotations
538
+
539
+ from pydantic import BaseModel
540
+
541
+
542
+ class Node(BaseModel):
543
+ label: str
544
+ child: "Node" | None = None
545
+
546
+ `,
547
+ });
548
+ });
549
+
550
+ it("model_config can use pydantic.alias_generators", () => {
551
+ expect(
552
+ <TestOutput path="models.py" externals={[pydanticModule]}>
553
+ <py.PydanticClassDeclaration
554
+ name="M"
555
+ modelConfigExpression={code`${pydanticModule["."].ConfigDict}(alias_generator=${pydanticModule.alias_generators.to_camel})`}
556
+ />
557
+ </TestOutput>,
558
+ ).toRenderTo(
559
+ `
560
+ from pydantic import BaseModel
561
+ from pydantic import ConfigDict
562
+ from pydantic.alias_generators import to_camel
563
+
564
+
565
+ class M(BaseModel):
566
+ model_config = ConfigDict(alias_generator=to_camel)
567
+
568
+ `,
569
+ );
570
+ });
571
+
572
+ it("pydantic_settings exposes BaseSettings", () => {
573
+ expect(
574
+ <TestOutput path="config.py" externals={[pydanticSettingsModule]}>
575
+ <py.ClassDeclaration
576
+ name="AppSettings"
577
+ bases={[pydanticSettingsModule["."].BaseSettings]}
578
+ />
579
+ </TestOutput>,
580
+ ).toRenderTo(
581
+ `
582
+ from pydantic_settings import BaseSettings
583
+
584
+
585
+ class AppSettings(BaseSettings):
586
+ pass
587
+
588
+ `,
589
+ );
590
+ });
591
+
592
+ it("emits @computed_field above @property via PropertyDeclaration", () => {
593
+ expect(
594
+ <TestOutput path="models.py" externals={[pydanticModule]}>
595
+ <py.PydanticClassDeclaration name="Square">
596
+ <py.VariableDeclaration
597
+ instanceVariable
598
+ omitNone
599
+ name="width"
600
+ type="float"
601
+ />
602
+ <py.PropertyDeclaration
603
+ name="area"
604
+ type="float"
605
+ decorators={[code`@${pydanticModule["."].computed_field}`]}
606
+ >
607
+ {"return self.width ** 2"}
608
+ </py.PropertyDeclaration>
609
+ </py.PydanticClassDeclaration>
610
+ </TestOutput>,
611
+ ).toRenderTo(
612
+ `
613
+ from pydantic import BaseModel
614
+ from pydantic import computed_field
615
+
616
+
617
+ class Square(BaseModel):
618
+ width: float
619
+ @computed_field
620
+ @property
621
+ def area(self) -> float:
622
+ return self.width ** 2
623
+
624
+
625
+ `,
626
+ );
627
+ });
628
+
629
+ it("emits computed_field on an instance method", () => {
630
+ expect(
631
+ <TestOutput path="models.py" externals={[pydanticModule]}>
632
+ <py.PydanticClassDeclaration name="Square">
633
+ <py.VariableDeclaration
634
+ instanceVariable
635
+ omitNone
636
+ name="width"
637
+ type="float"
638
+ />
639
+ <py.MethodDeclaration
640
+ name="area"
641
+ decorators={[code`@${pydanticModule["."].computed_field}`]}
642
+ returnType="float"
643
+ >
644
+ {"return self.width ** 2"}
645
+ </py.MethodDeclaration>
646
+ </py.PydanticClassDeclaration>
647
+ </TestOutput>,
648
+ ).toRenderTo(
649
+ `
650
+ from pydantic import BaseModel
651
+ from pydantic import computed_field
652
+
653
+
654
+ class Square(BaseModel):
655
+ width: float
656
+ @computed_field
657
+ def area(self) -> float:
658
+ return self.width ** 2
659
+
660
+
661
+ `,
662
+ );
663
+ });
664
+
665
+ it("emits model_validator above classmethod", () => {
666
+ expect(
667
+ <TestOutput path="models.py" externals={[pydanticModule]}>
668
+ <py.PydanticClassDeclaration name="Bag">
669
+ <py.VariableDeclaration
670
+ instanceVariable
671
+ omitNone
672
+ name="items"
673
+ type="list"
674
+ />
675
+ <py.ClassMethodDeclaration
676
+ name="ensure_items"
677
+ decorators={[
678
+ code`@${pydanticModule["."].model_validator}(mode="before")`,
679
+ ]}
680
+ parameters={[{ name: "data", type: "dict" }]}
681
+ returnType="dict"
682
+ >
683
+ {"return data"}
684
+ </py.ClassMethodDeclaration>
685
+ </py.PydanticClassDeclaration>
686
+ </TestOutput>,
687
+ ).toRenderTo(
688
+ `
689
+ from pydantic import BaseModel
690
+ from pydantic import model_validator
691
+
692
+
693
+ class Bag(BaseModel):
694
+ items: list
695
+ @model_validator(mode="before")
696
+ @classmethod
697
+ def ensure_items(cls, data: dict) -> dict:
698
+ return data
699
+
700
+
701
+ `,
702
+ );
703
+ });
704
+ });