@alloy-js/python 0.4.0 → 0.5.0-dev.1

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 (153) 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/StaticMethodDeclaration.js +19 -5
  23. package/dist/dev/src/components/StaticMethodDeclaration.js.map +1 -1
  24. package/dist/dev/src/components/index.js +1 -0
  25. package/dist/dev/src/components/index.js.map +1 -1
  26. package/dist/dev/test/classdeclarations.test.js +85 -52
  27. package/dist/dev/test/classdeclarations.test.js.map +1 -1
  28. package/dist/dev/test/dataclassdeclarations.test.js +122 -89
  29. package/dist/dev/test/dataclassdeclarations.test.js.map +1 -1
  30. package/dist/dev/test/decoratorlist.test.js +84 -0
  31. package/dist/dev/test/decoratorlist.test.js.map +1 -0
  32. package/dist/dev/test/enums.test.js +41 -10
  33. package/dist/dev/test/enums.test.js.map +1 -1
  34. package/dist/dev/test/functiondeclaration.test.js +81 -61
  35. package/dist/dev/test/functiondeclaration.test.js.map +1 -1
  36. package/dist/dev/test/methoddeclaration.test.js +117 -26
  37. package/dist/dev/test/methoddeclaration.test.js.map +1 -1
  38. package/dist/dev/test/propertydeclaration.test.js +109 -7
  39. package/dist/dev/test/propertydeclaration.test.js.map +1 -1
  40. package/dist/dev/test/pydanticclassdeclarations.test.js +1137 -0
  41. package/dist/dev/test/pydanticclassdeclarations.test.js.map +1 -0
  42. package/dist/src/builtins/python.d.ts +30 -0
  43. package/dist/src/builtins/python.d.ts.map +1 -1
  44. package/dist/src/builtins/python.js +46 -0
  45. package/dist/src/builtins/python.js.map +1 -1
  46. package/dist/src/components/ClassDeclaration.d.ts +21 -0
  47. package/dist/src/components/ClassDeclaration.d.ts.map +1 -1
  48. package/dist/src/components/ClassDeclaration.js +6 -1
  49. package/dist/src/components/ClassDeclaration.js.map +1 -1
  50. package/dist/src/components/ClassMethodDeclaration.d.ts +5 -1
  51. package/dist/src/components/ClassMethodDeclaration.d.ts.map +1 -1
  52. package/dist/src/components/ClassMethodDeclaration.js +14 -3
  53. package/dist/src/components/ClassMethodDeclaration.js.map +1 -1
  54. package/dist/src/components/DataclassDeclaration.d.ts.map +1 -1
  55. package/dist/src/components/DataclassDeclaration.js +10 -4
  56. package/dist/src/components/DataclassDeclaration.js.map +1 -1
  57. package/dist/src/components/DecoratorList.d.ts +43 -0
  58. package/dist/src/components/DecoratorList.d.ts.map +1 -0
  59. package/dist/src/components/DecoratorList.js +47 -0
  60. package/dist/src/components/DecoratorList.js.map +1 -0
  61. package/dist/src/components/EnumDeclaration.d.ts +9 -0
  62. package/dist/src/components/EnumDeclaration.d.ts.map +1 -1
  63. package/dist/src/components/EnumDeclaration.js +6 -1
  64. package/dist/src/components/EnumDeclaration.js.map +1 -1
  65. package/dist/src/components/FunctionBase.d.ts +31 -1
  66. package/dist/src/components/FunctionBase.d.ts.map +1 -1
  67. package/dist/src/components/FunctionBase.js +9 -2
  68. package/dist/src/components/FunctionBase.js.map +1 -1
  69. package/dist/src/components/FutureStatement.d.ts +1 -1
  70. package/dist/src/components/FutureStatement.js +1 -1
  71. package/dist/src/components/MethodBase.d.ts.map +1 -1
  72. package/dist/src/components/MethodBase.js +10 -2
  73. package/dist/src/components/MethodBase.js.map +1 -1
  74. package/dist/src/components/PropertyDeclaration.d.ts +29 -0
  75. package/dist/src/components/PropertyDeclaration.d.ts.map +1 -1
  76. package/dist/src/components/PropertyDeclaration.js +48 -1
  77. package/dist/src/components/PropertyDeclaration.js.map +1 -1
  78. package/dist/src/components/PydanticClassDeclaration.d.ts +120 -0
  79. package/dist/src/components/PydanticClassDeclaration.d.ts.map +1 -0
  80. package/dist/src/components/PydanticClassDeclaration.js +116 -0
  81. package/dist/src/components/PydanticClassDeclaration.js.map +1 -0
  82. package/dist/src/components/StaticMethodDeclaration.d.ts +3 -0
  83. package/dist/src/components/StaticMethodDeclaration.d.ts.map +1 -1
  84. package/dist/src/components/StaticMethodDeclaration.js +13 -3
  85. package/dist/src/components/StaticMethodDeclaration.js.map +1 -1
  86. package/dist/src/components/index.d.ts +1 -0
  87. package/dist/src/components/index.d.ts.map +1 -1
  88. package/dist/src/components/index.js +1 -0
  89. package/dist/src/components/index.js.map +1 -1
  90. package/dist/test/classdeclarations.test.js +25 -0
  91. package/dist/test/classdeclarations.test.js.map +1 -1
  92. package/dist/test/dataclassdeclarations.test.js +25 -0
  93. package/dist/test/dataclassdeclarations.test.js.map +1 -1
  94. package/dist/test/decoratorlist.test.d.ts +2 -0
  95. package/dist/test/decoratorlist.test.d.ts.map +1 -0
  96. package/dist/test/decoratorlist.test.js +60 -0
  97. package/dist/test/decoratorlist.test.js.map +1 -0
  98. package/dist/test/enums.test.js +27 -0
  99. package/dist/test/enums.test.js.map +1 -1
  100. package/dist/test/functiondeclaration.test.js +16 -0
  101. package/dist/test/functiondeclaration.test.js.map +1 -1
  102. package/dist/test/methoddeclaration.test.js +67 -0
  103. package/dist/test/methoddeclaration.test.js.map +1 -1
  104. package/dist/test/propertydeclaration.test.js +71 -1
  105. package/dist/test/propertydeclaration.test.js.map +1 -1
  106. package/dist/test/pydanticclassdeclarations.test.d.ts +2 -0
  107. package/dist/test/pydanticclassdeclarations.test.d.ts.map +1 -0
  108. package/dist/test/pydanticclassdeclarations.test.js +829 -0
  109. package/dist/test/pydanticclassdeclarations.test.js.map +1 -0
  110. package/dist/tsconfig.tsbuildinfo +1 -1
  111. package/docs/api/components/ClassDeclaration.md +10 -7
  112. package/docs/api/components/ClassEnumDeclaration.md +9 -6
  113. package/docs/api/components/ClassMethodDeclaration.md +7 -5
  114. package/docs/api/components/DataclassDeclaration.md +9 -5
  115. package/docs/api/components/DunderMethodDeclaration.md +7 -5
  116. package/docs/api/components/FunctionDeclaration.md +9 -5
  117. package/docs/api/components/FutureStatement.md +1 -1
  118. package/docs/api/components/MethodDeclaration.md +11 -6
  119. package/docs/api/components/PropertyDeclaration.md +11 -8
  120. package/docs/api/components/PydanticClassDeclaration.md +146 -0
  121. package/docs/api/components/StaticMethodDeclaration.md +7 -5
  122. package/docs/api/components/index.md +1 -0
  123. package/docs/api/index.md +3 -3
  124. package/docs/api/types/CommonFunctionProps.md +4 -3
  125. package/docs/api/types/PydanticModelConfigDictProps.md +32 -0
  126. package/docs/api/types/index.md +1 -0
  127. package/docs/api/variables/index.md +3 -0
  128. package/docs/api/variables/pydanticModule.md +27 -0
  129. package/docs/api/variables/pydanticSettingsModule.md +7 -0
  130. package/docs/api/variables/typingModule.md +9 -0
  131. package/package.json +6 -6
  132. package/src/builtins/python.ts +539 -1
  133. package/src/components/ClassDeclaration.tsx +23 -0
  134. package/src/components/ClassMethodDeclaration.tsx +9 -1
  135. package/src/components/DataclassDeclaration.tsx +18 -11
  136. package/src/components/DecoratorList.tsx +50 -0
  137. package/src/components/EnumDeclaration.tsx +11 -0
  138. package/src/components/FunctionBase.tsx +34 -3
  139. package/src/components/FutureStatement.tsx +1 -1
  140. package/src/components/MethodBase.tsx +6 -2
  141. package/src/components/PropertyDeclaration.tsx +48 -1
  142. package/src/components/PydanticClassDeclaration.tsx +222 -0
  143. package/src/components/StaticMethodDeclaration.tsx +7 -1
  144. package/src/components/index.ts +1 -0
  145. package/temp/api.json +1142 -84
  146. package/test/classdeclarations.test.tsx +27 -0
  147. package/test/dataclassdeclarations.test.tsx +25 -0
  148. package/test/decoratorlist.test.tsx +95 -0
  149. package/test/enums.test.tsx +29 -0
  150. package/test/functiondeclaration.test.tsx +17 -0
  151. package/test/methoddeclaration.test.tsx +70 -0
  152. package/test/propertydeclaration.test.tsx +66 -1
  153. package/test/pydanticclassdeclarations.test.tsx +836 -0
@@ -0,0 +1,1137 @@
1
+ import { createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
2
+ import { Prose, code, refkey } from "@alloy-js/core";
3
+ import { d } from "@alloy-js/core/testing";
4
+ import { describe, expect, it } from "vitest";
5
+ import { pydanticModule, pydanticSettingsModule, typingModule } from "../src/builtins/python.js";
6
+ import * as py from "../src/index.js";
7
+ import { assertFileContents, toSourceText, toSourceTextMultiple } from "./utils.js";
8
+ describe("PydanticClassDeclaration", () => {
9
+ it("forwards class-level decorators above `class`", () => {
10
+ const res = toSourceText([_$createComponent(py.SourceFile, {
11
+ path: "models.py",
12
+ get children() {
13
+ return _$createComponent(py.PydanticClassDeclaration, {
14
+ name: "User",
15
+ get decorators() {
16
+ return [code`@${typingModule["."].final}`];
17
+ },
18
+ get children() {
19
+ return _$createComponent(py.VariableDeclaration, {
20
+ instanceVariable: true,
21
+ omitNone: true,
22
+ name: "id",
23
+ type: "int"
24
+ }, {
25
+ fileName: import.meta.url,
26
+ lineNumber: 25,
27
+ columnNumber: 13
28
+ });
29
+ }
30
+ }, {
31
+ fileName: import.meta.url,
32
+ lineNumber: 21,
33
+ columnNumber: 11
34
+ });
35
+ }
36
+ }, {
37
+ fileName: import.meta.url,
38
+ lineNumber: 20,
39
+ columnNumber: 9
40
+ })], {
41
+ externals: [pydanticModule, typingModule]
42
+ });
43
+ expect(res).toRenderTo(d`
44
+ from pydantic import BaseModel
45
+ from typing import final
46
+
47
+
48
+ @final
49
+ class User(BaseModel):
50
+ id: int
51
+
52
+
53
+ `);
54
+ });
55
+ it("emits a pydantic model with BaseModel, fields, and Field import", () => {
56
+ const res = toSourceText([_$createComponent(py.SourceFile, {
57
+ path: "models.py",
58
+ get children() {
59
+ return _$createComponent(py.PydanticClassDeclaration, {
60
+ name: "User",
61
+ get children() {
62
+ return [_$createComponent(py.VariableDeclaration, {
63
+ instanceVariable: true,
64
+ omitNone: true,
65
+ name: "id",
66
+ type: "int"
67
+ }, {
68
+ fileName: import.meta.url,
69
+ lineNumber: 57,
70
+ columnNumber: 13
71
+ }), _$createComponent(py.VariableDeclaration, {
72
+ instanceVariable: true,
73
+ name: "name",
74
+ type: "str",
75
+ get initializer() {
76
+ return code`${pydanticModule["."].Field}(default="anon")`;
77
+ }
78
+ }, {
79
+ fileName: import.meta.url,
80
+ lineNumber: 63,
81
+ columnNumber: 13
82
+ })];
83
+ }
84
+ }, {
85
+ fileName: import.meta.url,
86
+ lineNumber: 56,
87
+ columnNumber: 11
88
+ });
89
+ }
90
+ }, {
91
+ fileName: import.meta.url,
92
+ lineNumber: 55,
93
+ columnNumber: 9
94
+ })], {
95
+ externals: [pydanticModule]
96
+ });
97
+ expect(res).toRenderTo(d`
98
+ from pydantic import BaseModel
99
+ from pydantic import Field
100
+
101
+
102
+ class User(BaseModel):
103
+ id: int
104
+ name: str = Field(default="anon")
105
+
106
+
107
+ `);
108
+ });
109
+ it("inherits from another pydantic model via bases", () => {
110
+ const baseRef = refkey();
111
+ const res = toSourceText([_$createComponent(py.SourceFile, {
112
+ path: "models.py",
113
+ get children() {
114
+ return _$createComponent(py.StatementList, {
115
+ get children() {
116
+ return [_$createComponent(py.PydanticClassDeclaration, {
117
+ name: "User",
118
+ refkey: baseRef,
119
+ get children() {
120
+ return _$createComponent(py.VariableDeclaration, {
121
+ instanceVariable: true,
122
+ omitNone: true,
123
+ name: "id",
124
+ type: "int"
125
+ }, {
126
+ fileName: import.meta.url,
127
+ lineNumber: 97,
128
+ columnNumber: 15
129
+ });
130
+ }
131
+ }, {
132
+ fileName: import.meta.url,
133
+ lineNumber: 96,
134
+ columnNumber: 13
135
+ }), _$createComponent(py.PydanticClassDeclaration, {
136
+ name: "Admin",
137
+ bases: [baseRef],
138
+ get children() {
139
+ return _$createComponent(py.VariableDeclaration, {
140
+ instanceVariable: true,
141
+ omitNone: true,
142
+ name: "role",
143
+ type: "str"
144
+ }, {
145
+ fileName: import.meta.url,
146
+ lineNumber: 105,
147
+ columnNumber: 15
148
+ });
149
+ }
150
+ }, {
151
+ fileName: import.meta.url,
152
+ lineNumber: 104,
153
+ columnNumber: 13
154
+ })];
155
+ }
156
+ }, {
157
+ fileName: import.meta.url,
158
+ lineNumber: 95,
159
+ columnNumber: 11
160
+ });
161
+ }
162
+ }, {
163
+ fileName: import.meta.url,
164
+ lineNumber: 94,
165
+ columnNumber: 9
166
+ })], {
167
+ externals: [pydanticModule]
168
+ });
169
+ expect(res).toRenderTo(d`
170
+ from pydantic import BaseModel
171
+
172
+
173
+ class User(BaseModel):
174
+ id: int
175
+
176
+ class Admin(User):
177
+ role: str
178
+
179
+
180
+ `);
181
+ });
182
+ it("supports required, optional, Field default, and plain default", () => {
183
+ const res = toSourceText([_$createComponent(py.SourceFile, {
184
+ path: "models.py",
185
+ get children() {
186
+ return _$createComponent(py.PydanticClassDeclaration, {
187
+ name: "Item",
188
+ get children() {
189
+ return [_$createComponent(py.VariableDeclaration, {
190
+ instanceVariable: true,
191
+ omitNone: true,
192
+ name: "sku",
193
+ type: "str"
194
+ }, {
195
+ fileName: import.meta.url,
196
+ lineNumber: 139,
197
+ columnNumber: 13
198
+ }), _$createComponent(py.VariableDeclaration, {
199
+ instanceVariable: true,
200
+ name: "notes",
201
+ get type() {
202
+ return _$createComponent(py.UnionTypeExpression, {
203
+ children: ["str", "None"]
204
+ }, {
205
+ fileName: import.meta.url,
206
+ lineNumber: 148,
207
+ columnNumber: 21
208
+ });
209
+ }
210
+ }, {
211
+ fileName: import.meta.url,
212
+ lineNumber: 145,
213
+ columnNumber: 13
214
+ }), _$createComponent(py.VariableDeclaration, {
215
+ instanceVariable: true,
216
+ name: "label",
217
+ type: "str",
218
+ get initializer() {
219
+ return code`${pydanticModule["."].Field}(default="untitled")`;
220
+ }
221
+ }, {
222
+ fileName: import.meta.url,
223
+ lineNumber: 150,
224
+ columnNumber: 13
225
+ }), _$createComponent(py.VariableDeclaration, {
226
+ instanceVariable: true,
227
+ name: "qty",
228
+ type: "int",
229
+ initializer: 1
230
+ }, {
231
+ fileName: import.meta.url,
232
+ lineNumber: 156,
233
+ columnNumber: 13
234
+ })];
235
+ }
236
+ }, {
237
+ fileName: import.meta.url,
238
+ lineNumber: 138,
239
+ columnNumber: 11
240
+ });
241
+ }
242
+ }, {
243
+ fileName: import.meta.url,
244
+ lineNumber: 137,
245
+ columnNumber: 9
246
+ })], {
247
+ externals: [pydanticModule]
248
+ });
249
+ expect(res).toRenderTo(d`
250
+ from pydantic import BaseModel
251
+ from pydantic import Field
252
+
253
+
254
+ class Item(BaseModel):
255
+ sku: str
256
+ notes: str | None = None
257
+ label: str = Field(default="untitled")
258
+ qty: int = 1
259
+
260
+
261
+ `);
262
+ });
263
+ it("emits class docstring", () => {
264
+ const doc = _$createComponent(py.ClassDoc, {
265
+ get description() {
266
+ return [_$createComponent(Prose, {
267
+ children: "Payload for an API request."
268
+ }, {
269
+ fileName: import.meta.url,
270
+ lineNumber: 187,
271
+ columnNumber: 34
272
+ })];
273
+ }
274
+ }, {
275
+ fileName: import.meta.url,
276
+ lineNumber: 187,
277
+ columnNumber: 7
278
+ });
279
+ const res = toSourceText([_$createComponent(py.SourceFile, {
280
+ path: "models.py",
281
+ get children() {
282
+ return _$createComponent(py.PydanticClassDeclaration, {
283
+ name: "RequestBody",
284
+ doc: doc,
285
+ get children() {
286
+ return _$createComponent(py.VariableDeclaration, {
287
+ instanceVariable: true,
288
+ omitNone: true,
289
+ name: "value",
290
+ type: "str"
291
+ }, {
292
+ fileName: import.meta.url,
293
+ lineNumber: 193,
294
+ columnNumber: 13
295
+ });
296
+ }
297
+ }, {
298
+ fileName: import.meta.url,
299
+ lineNumber: 192,
300
+ columnNumber: 11
301
+ });
302
+ }
303
+ }, {
304
+ fileName: import.meta.url,
305
+ lineNumber: 191,
306
+ columnNumber: 9
307
+ })], {
308
+ externals: [pydanticModule]
309
+ });
310
+ expect(res).toRenderTo(d`
311
+ from pydantic import BaseModel
312
+
313
+
314
+ class RequestBody(BaseModel):
315
+ """
316
+ Payload for an API request.
317
+ """
318
+
319
+ value: str
320
+
321
+
322
+ `);
323
+ });
324
+ it("resolves refkey across files with pydantic imports", () => {
325
+ const modelRef = refkey();
326
+ const res = toSourceTextMultiple([_$createComponent(py.SourceFile, {
327
+ path: "models.py",
328
+ get children() {
329
+ return _$createComponent(py.PydanticClassDeclaration, {
330
+ name: "User",
331
+ refkey: modelRef,
332
+ get children() {
333
+ return _$createComponent(py.VariableDeclaration, {
334
+ instanceVariable: true,
335
+ omitNone: true,
336
+ name: "id",
337
+ type: "int"
338
+ }, {
339
+ fileName: import.meta.url,
340
+ lineNumber: 228,
341
+ columnNumber: 13
342
+ });
343
+ }
344
+ }, {
345
+ fileName: import.meta.url,
346
+ lineNumber: 227,
347
+ columnNumber: 11
348
+ });
349
+ }
350
+ }, {
351
+ fileName: import.meta.url,
352
+ lineNumber: 226,
353
+ columnNumber: 9
354
+ }), _$createComponent(py.SourceFile, {
355
+ path: "service.py",
356
+ get children() {
357
+ return _$createComponent(py.FunctionDeclaration, {
358
+ name: "load_user",
359
+ returnType: modelRef,
360
+ parameters: [{
361
+ name: "user_id",
362
+ type: "int"
363
+ }],
364
+ children: "return User(id=user_id)"
365
+ }, {
366
+ fileName: import.meta.url,
367
+ lineNumber: 237,
368
+ columnNumber: 11
369
+ });
370
+ }
371
+ }, {
372
+ fileName: import.meta.url,
373
+ lineNumber: 236,
374
+ columnNumber: 9
375
+ })], {
376
+ externals: [pydanticModule]
377
+ });
378
+ assertFileContents(res, {
379
+ "models.py": `
380
+ from pydantic import BaseModel
381
+
382
+
383
+ class User(BaseModel):
384
+ id: int
385
+
386
+ `,
387
+ "service.py": `
388
+ from typing import TYPE_CHECKING
389
+
390
+ if TYPE_CHECKING:
391
+ from models import User
392
+
393
+
394
+ def load_user(user_id: int) -> User:
395
+ return User(id=user_id)
396
+
397
+ `
398
+ });
399
+ });
400
+ it("emits model_config = ConfigDict(...) from top-level config props", () => {
401
+ const res = toSourceText([_$createComponent(py.SourceFile, {
402
+ path: "models.py",
403
+ get children() {
404
+ return _$createComponent(py.PydanticClassDeclaration, {
405
+ name: "User",
406
+ frozen: true,
407
+ extra: "forbid",
408
+ validateAssignment: true,
409
+ get children() {
410
+ return _$createComponent(py.VariableDeclaration, {
411
+ instanceVariable: true,
412
+ omitNone: true,
413
+ name: "id",
414
+ type: "int"
415
+ }, {
416
+ fileName: import.meta.url,
417
+ lineNumber: 282,
418
+ columnNumber: 13
419
+ });
420
+ }
421
+ }, {
422
+ fileName: import.meta.url,
423
+ lineNumber: 276,
424
+ columnNumber: 11
425
+ });
426
+ }
427
+ }, {
428
+ fileName: import.meta.url,
429
+ lineNumber: 275,
430
+ columnNumber: 9
431
+ })], {
432
+ externals: [pydanticModule]
433
+ });
434
+ expect(res).toRenderTo(d`
435
+ from pydantic import BaseModel
436
+ from pydantic import ConfigDict
437
+
438
+
439
+ class User(BaseModel):
440
+ model_config = ConfigDict(frozen=True, extra="forbid", validate_assignment=True)
441
+ id: int
442
+
443
+
444
+ `);
445
+ });
446
+ it("emits model_config = ConfigDict(...) from modelConfig", () => {
447
+ const res = toSourceText([_$createComponent(py.SourceFile, {
448
+ path: "models.py",
449
+ get children() {
450
+ return _$createComponent(py.PydanticClassDeclaration, {
451
+ name: "User",
452
+ modelConfig: {
453
+ frozen: true,
454
+ extra: "forbid",
455
+ validateAssignment: true
456
+ },
457
+ get children() {
458
+ return _$createComponent(py.VariableDeclaration, {
459
+ instanceVariable: true,
460
+ omitNone: true,
461
+ name: "id",
462
+ type: "int"
463
+ }, {
464
+ fileName: import.meta.url,
465
+ lineNumber: 321,
466
+ columnNumber: 13
467
+ });
468
+ }
469
+ }, {
470
+ fileName: import.meta.url,
471
+ lineNumber: 313,
472
+ columnNumber: 11
473
+ });
474
+ }
475
+ }, {
476
+ fileName: import.meta.url,
477
+ lineNumber: 312,
478
+ columnNumber: 9
479
+ })], {
480
+ externals: [pydanticModule]
481
+ });
482
+ expect(res).toRenderTo(d`
483
+ from pydantic import BaseModel
484
+ from pydantic import ConfigDict
485
+
486
+
487
+ class User(BaseModel):
488
+ model_config = ConfigDict(frozen=True, extra="forbid", validate_assignment=True)
489
+ id: int
490
+
491
+
492
+ `);
493
+ });
494
+ it("gives precedence to top-level config props over modelConfig", () => {
495
+ const res = toSourceText([_$createComponent(py.SourceFile, {
496
+ path: "models.py",
497
+ get children() {
498
+ return _$createComponent(py.PydanticClassDeclaration, {
499
+ name: "User",
500
+ modelConfig: {
501
+ frozen: false,
502
+ extra: "allow"
503
+ },
504
+ frozen: true,
505
+ extra: "forbid"
506
+ }, {
507
+ fileName: import.meta.url,
508
+ lineNumber: 352,
509
+ columnNumber: 11
510
+ });
511
+ }
512
+ }, {
513
+ fileName: import.meta.url,
514
+ lineNumber: 351,
515
+ columnNumber: 9
516
+ })], {
517
+ externals: [pydanticModule]
518
+ });
519
+ expect(res).toRenderTo(d`
520
+ from pydantic import BaseModel
521
+ from pydantic import ConfigDict
522
+
523
+
524
+ class User(BaseModel):
525
+ model_config = ConfigDict(frozen=True, extra="forbid")
526
+
527
+
528
+ `);
529
+ });
530
+ it("supports additional typed ConfigDict props", () => {
531
+ const res = toSourceText([_$createComponent(py.SourceFile, {
532
+ path: "models.py",
533
+ get children() {
534
+ return _$createComponent(py.PydanticClassDeclaration, {
535
+ name: "User",
536
+ useEnumValues: true,
537
+ coerceNumbersToStr: true,
538
+ validateReturn: true,
539
+ strMinLength: 1,
540
+ strMaxLength: 128,
541
+ serJsonBytes: "base64",
542
+ valJsonBytes: "hex"
543
+ }, {
544
+ fileName: import.meta.url,
545
+ lineNumber: 381,
546
+ columnNumber: 11
547
+ });
548
+ }
549
+ }, {
550
+ fileName: import.meta.url,
551
+ lineNumber: 380,
552
+ columnNumber: 9
553
+ })], {
554
+ externals: [pydanticModule]
555
+ });
556
+ expect(res).toRenderTo(d`
557
+ from pydantic import BaseModel
558
+ from pydantic import ConfigDict
559
+
560
+
561
+ class User(BaseModel):
562
+ 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)
563
+
564
+
565
+ `);
566
+ });
567
+ it("imports SecretStr when used as a field type", () => {
568
+ const res = toSourceText([_$createComponent(py.SourceFile, {
569
+ path: "models.py",
570
+ get children() {
571
+ return _$createComponent(py.PydanticClassDeclaration, {
572
+ name: "Credentials",
573
+ get children() {
574
+ return _$createComponent(py.VariableDeclaration, {
575
+ instanceVariable: true,
576
+ omitNone: true,
577
+ name: "token",
578
+ get type() {
579
+ return pydanticModule["."].SecretStr;
580
+ }
581
+ }, {
582
+ fileName: import.meta.url,
583
+ lineNumber: 415,
584
+ columnNumber: 13
585
+ });
586
+ }
587
+ }, {
588
+ fileName: import.meta.url,
589
+ lineNumber: 414,
590
+ columnNumber: 11
591
+ });
592
+ }
593
+ }, {
594
+ fileName: import.meta.url,
595
+ lineNumber: 413,
596
+ columnNumber: 9
597
+ })], {
598
+ externals: [pydanticModule]
599
+ });
600
+ expect(res).toRenderTo(d`
601
+ from pydantic import BaseModel
602
+ from typing import TYPE_CHECKING
603
+
604
+ if TYPE_CHECKING:
605
+ from pydantic import SecretStr
606
+
607
+
608
+ class Credentials(BaseModel):
609
+ token: SecretStr
610
+
611
+
612
+ `);
613
+ });
614
+ it("emits arbitrary model_config via modelConfigExpression", () => {
615
+ const res = toSourceText([_$createComponent(py.SourceFile, {
616
+ path: "models.py",
617
+ get children() {
618
+ return _$createComponent(py.PydanticClassDeclaration, {
619
+ name: "M",
620
+ get modelConfigExpression() {
621
+ return code`${pydanticModule["."].ConfigDict}(frozen=True, extra="allow")`;
622
+ }
623
+ }, {
624
+ fileName: import.meta.url,
625
+ lineNumber: 448,
626
+ columnNumber: 11
627
+ });
628
+ }
629
+ }, {
630
+ fileName: import.meta.url,
631
+ lineNumber: 447,
632
+ columnNumber: 9
633
+ })], {
634
+ externals: [pydanticModule]
635
+ });
636
+ expect(res).toRenderTo(d`
637
+ from pydantic import BaseModel
638
+ from pydantic import ConfigDict
639
+
640
+
641
+ class M(BaseModel):
642
+ model_config = ConfigDict(frozen=True, extra="allow")
643
+
644
+
645
+ `);
646
+ });
647
+ it("supports RootModel as explicit bases entry", () => {
648
+ const res = toSourceText([_$createComponent(py.SourceFile, {
649
+ path: "models.py",
650
+ get children() {
651
+ return _$createComponent(py.PydanticClassDeclaration, {
652
+ name: "Tags",
653
+ get bases() {
654
+ return [code`${pydanticModule["."].RootModel}[list[str]]`];
655
+ }
656
+ }, {
657
+ fileName: import.meta.url,
658
+ lineNumber: 475,
659
+ columnNumber: 11
660
+ });
661
+ }
662
+ }, {
663
+ fileName: import.meta.url,
664
+ lineNumber: 474,
665
+ columnNumber: 9
666
+ })], {
667
+ externals: [pydanticModule]
668
+ });
669
+ expect(res).toRenderTo(d`
670
+ from pydantic import RootModel
671
+
672
+
673
+ class Tags(RootModel[list[str]]):
674
+ pass
675
+
676
+
677
+ `);
678
+ });
679
+ it("places Pydantic validators above classmethod", () => {
680
+ const res = toSourceText([_$createComponent(py.SourceFile, {
681
+ path: "models.py",
682
+ get children() {
683
+ return _$createComponent(py.PydanticClassDeclaration, {
684
+ name: "User",
685
+ get children() {
686
+ return [_$createComponent(py.VariableDeclaration, {
687
+ instanceVariable: true,
688
+ omitNone: true,
689
+ name: "name",
690
+ type: "str"
691
+ }, {
692
+ fileName: import.meta.url,
693
+ lineNumber: 502,
694
+ columnNumber: 13
695
+ }), _$createComponent(py.ClassMethodDeclaration, {
696
+ name: "strip_name",
697
+ get decorators() {
698
+ return [code`@${pydanticModule["."].field_validator}("name", mode="before")`];
699
+ },
700
+ parameters: [{
701
+ name: "value",
702
+ type: "str"
703
+ }],
704
+ returnType: "str",
705
+ children: "return value.strip()"
706
+ }, {
707
+ fileName: import.meta.url,
708
+ lineNumber: 508,
709
+ columnNumber: 13
710
+ })];
711
+ }
712
+ }, {
713
+ fileName: import.meta.url,
714
+ lineNumber: 501,
715
+ columnNumber: 11
716
+ });
717
+ }
718
+ }, {
719
+ fileName: import.meta.url,
720
+ lineNumber: 500,
721
+ columnNumber: 9
722
+ })], {
723
+ externals: [pydanticModule]
724
+ });
725
+ expect(res).toRenderTo(d`
726
+ from pydantic import BaseModel
727
+ from pydantic import field_validator
728
+
729
+
730
+ class User(BaseModel):
731
+ name: str
732
+ @field_validator("name", mode="before")
733
+ @classmethod
734
+ def strip_name(cls, value: str) -> str:
735
+ return value.strip()
736
+
737
+
738
+
739
+ `);
740
+ });
741
+ });
742
+ describe("Pydantic ecosystem emitters", () => {
743
+ it("typing module resolves Any and similar annotations", () => {
744
+ const res = toSourceText([_$createComponent(py.SourceFile, {
745
+ path: "models.py",
746
+ get children() {
747
+ return _$createComponent(py.FunctionDeclaration, {
748
+ name: "identity",
749
+ get parameters() {
750
+ return [{
751
+ name: "x",
752
+ type: typingModule["."].Any
753
+ }];
754
+ },
755
+ get returnType() {
756
+ return typingModule["."].Any;
757
+ },
758
+ children: "return x"
759
+ }, {
760
+ fileName: import.meta.url,
761
+ lineNumber: 549,
762
+ columnNumber: 11
763
+ });
764
+ }
765
+ }, {
766
+ fileName: import.meta.url,
767
+ lineNumber: 548,
768
+ columnNumber: 9
769
+ })], {
770
+ externals: [typingModule]
771
+ });
772
+ expect(res).toRenderTo(d`
773
+ from typing import TYPE_CHECKING
774
+
775
+ if TYPE_CHECKING:
776
+ from typing import Any
777
+
778
+
779
+ def identity(x: Any) -> Any:
780
+ return x
781
+
782
+
783
+ `);
784
+ });
785
+ it("pydantic.types constrains field annotations", () => {
786
+ const res = toSourceText([_$createComponent(py.SourceFile, {
787
+ path: "models.py",
788
+ get children() {
789
+ return _$createComponent(py.PydanticClassDeclaration, {
790
+ name: "Score",
791
+ get children() {
792
+ return _$createComponent(py.VariableDeclaration, {
793
+ instanceVariable: true,
794
+ omitNone: true,
795
+ name: "points",
796
+ get type() {
797
+ return pydanticModule.types.PositiveInt;
798
+ }
799
+ }, {
800
+ fileName: import.meta.url,
801
+ lineNumber: 582,
802
+ columnNumber: 13
803
+ });
804
+ }
805
+ }, {
806
+ fileName: import.meta.url,
807
+ lineNumber: 581,
808
+ columnNumber: 11
809
+ });
810
+ }
811
+ }, {
812
+ fileName: import.meta.url,
813
+ lineNumber: 580,
814
+ columnNumber: 9
815
+ })], {
816
+ externals: [pydanticModule]
817
+ });
818
+ expect(res).toRenderTo(d`
819
+ from pydantic import BaseModel
820
+ from typing import TYPE_CHECKING
821
+
822
+ if TYPE_CHECKING:
823
+ from pydantic.types import PositiveInt
824
+
825
+
826
+ class Score(BaseModel):
827
+ points: PositiveInt
828
+
829
+
830
+ `);
831
+ });
832
+ it("postponed annotations support forward references in fields", () => {
833
+ const res = toSourceText([_$createComponent(py.SourceFile, {
834
+ path: "models.py",
835
+ get futureImports() {
836
+ return [_$createComponent(py.FutureStatement, {
837
+ feature: "annotations"
838
+ }, {
839
+ fileName: import.meta.url,
840
+ lineNumber: 616,
841
+ columnNumber: 27
842
+ })];
843
+ },
844
+ get children() {
845
+ return _$createComponent(py.PydanticClassDeclaration, {
846
+ name: "Node",
847
+ get children() {
848
+ return [_$createComponent(py.VariableDeclaration, {
849
+ instanceVariable: true,
850
+ omitNone: true,
851
+ name: "label",
852
+ type: "str"
853
+ }, {
854
+ fileName: import.meta.url,
855
+ lineNumber: 619,
856
+ columnNumber: 13
857
+ }), _$createComponent(py.VariableDeclaration, {
858
+ instanceVariable: true,
859
+ name: "child",
860
+ type: code`"Node" | None`
861
+ }, {
862
+ fileName: import.meta.url,
863
+ lineNumber: 625,
864
+ columnNumber: 13
865
+ })];
866
+ }
867
+ }, {
868
+ fileName: import.meta.url,
869
+ lineNumber: 618,
870
+ columnNumber: 11
871
+ });
872
+ }
873
+ }, {
874
+ fileName: import.meta.url,
875
+ lineNumber: 614,
876
+ columnNumber: 9
877
+ })], {
878
+ externals: [pydanticModule]
879
+ });
880
+ expect(res).toRenderTo(d`
881
+ from __future__ import annotations
882
+
883
+ from pydantic import BaseModel
884
+
885
+
886
+ class Node(BaseModel):
887
+ label: str
888
+ child: "Node" | None = None
889
+
890
+
891
+ `);
892
+ });
893
+ it("model_config can use pydantic.alias_generators", () => {
894
+ const res = toSourceText([_$createComponent(py.SourceFile, {
895
+ path: "models.py",
896
+ get children() {
897
+ return _$createComponent(py.PydanticClassDeclaration, {
898
+ name: "M",
899
+ get modelConfigExpression() {
900
+ return code`${pydanticModule["."].ConfigDict}(alias_generator=${pydanticModule.alias_generators.to_camel})`;
901
+ }
902
+ }, {
903
+ fileName: import.meta.url,
904
+ lineNumber: 656,
905
+ columnNumber: 11
906
+ });
907
+ }
908
+ }, {
909
+ fileName: import.meta.url,
910
+ lineNumber: 655,
911
+ columnNumber: 9
912
+ })], {
913
+ externals: [pydanticModule]
914
+ });
915
+ expect(res).toRenderTo(d`
916
+ from pydantic import BaseModel
917
+ from pydantic import ConfigDict
918
+ from pydantic.alias_generators import to_camel
919
+
920
+
921
+ class M(BaseModel):
922
+ model_config = ConfigDict(alias_generator=to_camel)
923
+
924
+
925
+ `);
926
+ });
927
+ it("pydantic_settings exposes BaseSettings", () => {
928
+ const res = toSourceText([_$createComponent(py.SourceFile, {
929
+ path: "config.py",
930
+ get children() {
931
+ return _$createComponent(py.ClassDeclaration, {
932
+ name: "AppSettings",
933
+ get bases() {
934
+ return [pydanticSettingsModule["."].BaseSettings];
935
+ }
936
+ }, {
937
+ fileName: import.meta.url,
938
+ lineNumber: 684,
939
+ columnNumber: 11
940
+ });
941
+ }
942
+ }, {
943
+ fileName: import.meta.url,
944
+ lineNumber: 683,
945
+ columnNumber: 9
946
+ })], {
947
+ externals: [pydanticSettingsModule]
948
+ });
949
+ expect(res).toRenderTo(d`
950
+ from pydantic_settings import BaseSettings
951
+
952
+
953
+ class AppSettings(BaseSettings):
954
+ pass
955
+
956
+
957
+ `);
958
+ });
959
+ it("emits @computed_field above @property via PropertyDeclaration", () => {
960
+ const res = toSourceText([_$createComponent(py.SourceFile, {
961
+ path: "models.py",
962
+ get children() {
963
+ return _$createComponent(py.PydanticClassDeclaration, {
964
+ name: "Square",
965
+ get children() {
966
+ return [_$createComponent(py.VariableDeclaration, {
967
+ instanceVariable: true,
968
+ omitNone: true,
969
+ name: "width",
970
+ type: "float"
971
+ }, {
972
+ fileName: import.meta.url,
973
+ lineNumber: 711,
974
+ columnNumber: 13
975
+ }), _$createComponent(py.PropertyDeclaration, {
976
+ name: "area",
977
+ type: "float",
978
+ get decorators() {
979
+ return [code`@${pydanticModule["."].computed_field}`];
980
+ },
981
+ children: "return self.width ** 2"
982
+ }, {
983
+ fileName: import.meta.url,
984
+ lineNumber: 717,
985
+ columnNumber: 13
986
+ })];
987
+ }
988
+ }, {
989
+ fileName: import.meta.url,
990
+ lineNumber: 710,
991
+ columnNumber: 11
992
+ });
993
+ }
994
+ }, {
995
+ fileName: import.meta.url,
996
+ lineNumber: 709,
997
+ columnNumber: 9
998
+ })], {
999
+ externals: [pydanticModule]
1000
+ });
1001
+ expect(res).toRenderTo(d`
1002
+ from pydantic import BaseModel
1003
+ from pydantic import computed_field
1004
+
1005
+
1006
+ class Square(BaseModel):
1007
+ width: float
1008
+ @computed_field
1009
+ @property
1010
+ def area(self) -> float:
1011
+ return self.width ** 2
1012
+
1013
+
1014
+
1015
+ `);
1016
+ });
1017
+ it("emits computed_field on an instance method", () => {
1018
+ const res = toSourceText([_$createComponent(py.SourceFile, {
1019
+ path: "models.py",
1020
+ get children() {
1021
+ return _$createComponent(py.PydanticClassDeclaration, {
1022
+ name: "Square",
1023
+ get children() {
1024
+ return [_$createComponent(py.VariableDeclaration, {
1025
+ instanceVariable: true,
1026
+ omitNone: true,
1027
+ name: "width",
1028
+ type: "float"
1029
+ }, {
1030
+ fileName: import.meta.url,
1031
+ lineNumber: 754,
1032
+ columnNumber: 13
1033
+ }), _$createComponent(py.MethodDeclaration, {
1034
+ name: "area",
1035
+ get decorators() {
1036
+ return [code`@${pydanticModule["."].computed_field}`];
1037
+ },
1038
+ returnType: "float",
1039
+ children: "return self.width ** 2"
1040
+ }, {
1041
+ fileName: import.meta.url,
1042
+ lineNumber: 760,
1043
+ columnNumber: 13
1044
+ })];
1045
+ }
1046
+ }, {
1047
+ fileName: import.meta.url,
1048
+ lineNumber: 753,
1049
+ columnNumber: 11
1050
+ });
1051
+ }
1052
+ }, {
1053
+ fileName: import.meta.url,
1054
+ lineNumber: 752,
1055
+ columnNumber: 9
1056
+ })], {
1057
+ externals: [pydanticModule]
1058
+ });
1059
+ expect(res).toRenderTo(d`
1060
+ from pydantic import BaseModel
1061
+ from pydantic import computed_field
1062
+
1063
+
1064
+ class Square(BaseModel):
1065
+ width: float
1066
+ @computed_field
1067
+ def area(self) -> float:
1068
+ return self.width ** 2
1069
+
1070
+
1071
+
1072
+ `);
1073
+ });
1074
+ it("emits model_validator above classmethod", () => {
1075
+ const res = toSourceText([_$createComponent(py.SourceFile, {
1076
+ path: "models.py",
1077
+ get children() {
1078
+ return _$createComponent(py.PydanticClassDeclaration, {
1079
+ name: "Bag",
1080
+ get children() {
1081
+ return [_$createComponent(py.VariableDeclaration, {
1082
+ instanceVariable: true,
1083
+ omitNone: true,
1084
+ name: "items",
1085
+ type: "list"
1086
+ }, {
1087
+ fileName: import.meta.url,
1088
+ lineNumber: 796,
1089
+ columnNumber: 13
1090
+ }), _$createComponent(py.ClassMethodDeclaration, {
1091
+ name: "ensure_items",
1092
+ get decorators() {
1093
+ return [code`@${pydanticModule["."].model_validator}(mode="before")`];
1094
+ },
1095
+ parameters: [{
1096
+ name: "data",
1097
+ type: "dict"
1098
+ }],
1099
+ returnType: "dict",
1100
+ children: "return data"
1101
+ }, {
1102
+ fileName: import.meta.url,
1103
+ lineNumber: 802,
1104
+ columnNumber: 13
1105
+ })];
1106
+ }
1107
+ }, {
1108
+ fileName: import.meta.url,
1109
+ lineNumber: 795,
1110
+ columnNumber: 11
1111
+ });
1112
+ }
1113
+ }, {
1114
+ fileName: import.meta.url,
1115
+ lineNumber: 794,
1116
+ columnNumber: 9
1117
+ })], {
1118
+ externals: [pydanticModule]
1119
+ });
1120
+ expect(res).toRenderTo(d`
1121
+ from pydantic import BaseModel
1122
+ from pydantic import model_validator
1123
+
1124
+
1125
+ class Bag(BaseModel):
1126
+ items: list
1127
+ @model_validator(mode="before")
1128
+ @classmethod
1129
+ def ensure_items(cls, data: dict) -> dict:
1130
+ return data
1131
+
1132
+
1133
+
1134
+ `);
1135
+ });
1136
+ });
1137
+ //# sourceMappingURL=pydanticclassdeclarations.test.js.map