@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.
- package/dist/dev/src/builtins/python.js +46 -0
- package/dist/dev/src/builtins/python.js.map +1 -1
- package/dist/dev/src/components/ClassDeclaration.js +19 -10
- package/dist/dev/src/components/ClassDeclaration.js.map +1 -1
- package/dist/dev/src/components/ClassMethodDeclaration.js +20 -5
- package/dist/dev/src/components/ClassMethodDeclaration.js.map +1 -1
- package/dist/dev/src/components/DataclassDeclaration.js +14 -12
- package/dist/dev/src/components/DataclassDeclaration.js.map +1 -1
- package/dist/dev/src/components/DecoratorList.js +55 -0
- package/dist/dev/src/components/DecoratorList.js.map +1 -0
- package/dist/dev/src/components/EnumDeclaration.js +21 -12
- package/dist/dev/src/components/EnumDeclaration.js.map +1 -1
- package/dist/dev/src/components/FunctionBase.js +21 -10
- package/dist/dev/src/components/FunctionBase.js.map +1 -1
- package/dist/dev/src/components/FutureStatement.js +1 -1
- package/dist/dev/src/components/MethodBase.js +16 -4
- package/dist/dev/src/components/MethodBase.js.map +1 -1
- package/dist/dev/src/components/PropertyDeclaration.js +68 -17
- package/dist/dev/src/components/PropertyDeclaration.js.map +1 -1
- package/dist/dev/src/components/PydanticClassDeclaration.js +136 -0
- package/dist/dev/src/components/PydanticClassDeclaration.js.map +1 -0
- package/dist/dev/src/components/StaticMethodDeclaration.js +19 -5
- package/dist/dev/src/components/StaticMethodDeclaration.js.map +1 -1
- package/dist/dev/src/components/index.js +1 -0
- package/dist/dev/src/components/index.js.map +1 -1
- package/dist/dev/test/classdeclarations.test.js +85 -52
- package/dist/dev/test/classdeclarations.test.js.map +1 -1
- package/dist/dev/test/dataclassdeclarations.test.js +122 -89
- package/dist/dev/test/dataclassdeclarations.test.js.map +1 -1
- package/dist/dev/test/decoratorlist.test.js +84 -0
- package/dist/dev/test/decoratorlist.test.js.map +1 -0
- package/dist/dev/test/enums.test.js +41 -10
- package/dist/dev/test/enums.test.js.map +1 -1
- package/dist/dev/test/functiondeclaration.test.js +81 -61
- package/dist/dev/test/functiondeclaration.test.js.map +1 -1
- package/dist/dev/test/methoddeclaration.test.js +117 -26
- package/dist/dev/test/methoddeclaration.test.js.map +1 -1
- package/dist/dev/test/propertydeclaration.test.js +109 -7
- package/dist/dev/test/propertydeclaration.test.js.map +1 -1
- package/dist/dev/test/pydanticclassdeclarations.test.js +1137 -0
- package/dist/dev/test/pydanticclassdeclarations.test.js.map +1 -0
- package/dist/src/builtins/python.d.ts +30 -0
- package/dist/src/builtins/python.d.ts.map +1 -1
- package/dist/src/builtins/python.js +46 -0
- package/dist/src/builtins/python.js.map +1 -1
- package/dist/src/components/ClassDeclaration.d.ts +21 -0
- package/dist/src/components/ClassDeclaration.d.ts.map +1 -1
- package/dist/src/components/ClassDeclaration.js +6 -1
- package/dist/src/components/ClassDeclaration.js.map +1 -1
- package/dist/src/components/ClassMethodDeclaration.d.ts +5 -1
- package/dist/src/components/ClassMethodDeclaration.d.ts.map +1 -1
- package/dist/src/components/ClassMethodDeclaration.js +14 -3
- package/dist/src/components/ClassMethodDeclaration.js.map +1 -1
- package/dist/src/components/DataclassDeclaration.d.ts.map +1 -1
- package/dist/src/components/DataclassDeclaration.js +10 -4
- package/dist/src/components/DataclassDeclaration.js.map +1 -1
- package/dist/src/components/DecoratorList.d.ts +43 -0
- package/dist/src/components/DecoratorList.d.ts.map +1 -0
- package/dist/src/components/DecoratorList.js +47 -0
- package/dist/src/components/DecoratorList.js.map +1 -0
- package/dist/src/components/EnumDeclaration.d.ts +9 -0
- package/dist/src/components/EnumDeclaration.d.ts.map +1 -1
- package/dist/src/components/EnumDeclaration.js +6 -1
- package/dist/src/components/EnumDeclaration.js.map +1 -1
- package/dist/src/components/FunctionBase.d.ts +31 -1
- package/dist/src/components/FunctionBase.d.ts.map +1 -1
- package/dist/src/components/FunctionBase.js +9 -2
- package/dist/src/components/FunctionBase.js.map +1 -1
- package/dist/src/components/FutureStatement.d.ts +1 -1
- package/dist/src/components/FutureStatement.js +1 -1
- package/dist/src/components/MethodBase.d.ts.map +1 -1
- package/dist/src/components/MethodBase.js +10 -2
- package/dist/src/components/MethodBase.js.map +1 -1
- package/dist/src/components/PropertyDeclaration.d.ts +29 -0
- package/dist/src/components/PropertyDeclaration.d.ts.map +1 -1
- package/dist/src/components/PropertyDeclaration.js +48 -1
- package/dist/src/components/PropertyDeclaration.js.map +1 -1
- package/dist/src/components/PydanticClassDeclaration.d.ts +120 -0
- package/dist/src/components/PydanticClassDeclaration.d.ts.map +1 -0
- package/dist/src/components/PydanticClassDeclaration.js +116 -0
- package/dist/src/components/PydanticClassDeclaration.js.map +1 -0
- package/dist/src/components/StaticMethodDeclaration.d.ts +3 -0
- package/dist/src/components/StaticMethodDeclaration.d.ts.map +1 -1
- package/dist/src/components/StaticMethodDeclaration.js +13 -3
- package/dist/src/components/StaticMethodDeclaration.js.map +1 -1
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/components/index.js +1 -0
- package/dist/src/components/index.js.map +1 -1
- package/dist/test/classdeclarations.test.js +25 -0
- package/dist/test/classdeclarations.test.js.map +1 -1
- package/dist/test/dataclassdeclarations.test.js +25 -0
- package/dist/test/dataclassdeclarations.test.js.map +1 -1
- package/dist/test/decoratorlist.test.d.ts +2 -0
- package/dist/test/decoratorlist.test.d.ts.map +1 -0
- package/dist/test/decoratorlist.test.js +60 -0
- package/dist/test/decoratorlist.test.js.map +1 -0
- package/dist/test/enums.test.js +27 -0
- package/dist/test/enums.test.js.map +1 -1
- package/dist/test/functiondeclaration.test.js +16 -0
- package/dist/test/functiondeclaration.test.js.map +1 -1
- package/dist/test/methoddeclaration.test.js +67 -0
- package/dist/test/methoddeclaration.test.js.map +1 -1
- package/dist/test/propertydeclaration.test.js +71 -1
- package/dist/test/propertydeclaration.test.js.map +1 -1
- package/dist/test/pydanticclassdeclarations.test.d.ts +2 -0
- package/dist/test/pydanticclassdeclarations.test.d.ts.map +1 -0
- package/dist/test/pydanticclassdeclarations.test.js +829 -0
- package/dist/test/pydanticclassdeclarations.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/api/components/ClassDeclaration.md +10 -7
- package/docs/api/components/ClassEnumDeclaration.md +9 -6
- package/docs/api/components/ClassMethodDeclaration.md +7 -5
- package/docs/api/components/DataclassDeclaration.md +9 -5
- package/docs/api/components/DunderMethodDeclaration.md +7 -5
- package/docs/api/components/FunctionDeclaration.md +9 -5
- package/docs/api/components/FutureStatement.md +1 -1
- package/docs/api/components/MethodDeclaration.md +11 -6
- package/docs/api/components/PropertyDeclaration.md +11 -8
- package/docs/api/components/PydanticClassDeclaration.md +146 -0
- package/docs/api/components/StaticMethodDeclaration.md +7 -5
- package/docs/api/components/index.md +1 -0
- package/docs/api/index.md +3 -3
- package/docs/api/types/CommonFunctionProps.md +4 -3
- package/docs/api/types/PydanticModelConfigDictProps.md +32 -0
- package/docs/api/types/index.md +1 -0
- package/docs/api/variables/index.md +3 -0
- package/docs/api/variables/pydanticModule.md +27 -0
- package/docs/api/variables/pydanticSettingsModule.md +7 -0
- package/docs/api/variables/typingModule.md +9 -0
- package/package.json +4 -4
- package/src/builtins/python.ts +539 -1
- package/src/components/ClassDeclaration.tsx +23 -0
- package/src/components/ClassMethodDeclaration.tsx +9 -1
- package/src/components/DataclassDeclaration.tsx +18 -11
- package/src/components/DecoratorList.tsx +50 -0
- package/src/components/EnumDeclaration.tsx +11 -0
- package/src/components/FunctionBase.tsx +34 -3
- package/src/components/FutureStatement.tsx +1 -1
- package/src/components/MethodBase.tsx +6 -2
- package/src/components/PropertyDeclaration.tsx +48 -1
- package/src/components/PydanticClassDeclaration.tsx +222 -0
- package/src/components/StaticMethodDeclaration.tsx +7 -1
- package/src/components/index.ts +1 -0
- package/temp/api.json +1142 -84
- package/test/classdeclarations.test.tsx +27 -0
- package/test/dataclassdeclarations.test.tsx +25 -0
- package/test/decoratorlist.test.tsx +95 -0
- package/test/enums.test.tsx +29 -0
- package/test/functiondeclaration.test.tsx +17 -0
- package/test/methoddeclaration.test.tsx +70 -0
- package/test/propertydeclaration.test.tsx +66 -1
- 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
|