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