@alloy-js/python 0.3.0-dev.1 → 0.3.0-dev.3

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 (34) hide show
  1. package/dist/src/builtins/python.d.ts +3 -0
  2. package/dist/src/builtins/python.d.ts.map +1 -1
  3. package/dist/src/builtins/python.js +6 -0
  4. package/dist/src/builtins/python.js.map +1 -1
  5. package/dist/src/components/DataclassDeclaration.d.ts +41 -0
  6. package/dist/src/components/DataclassDeclaration.d.ts.map +1 -0
  7. package/dist/src/components/DataclassDeclaration.js +153 -0
  8. package/dist/src/components/DataclassDeclaration.js.map +1 -0
  9. package/dist/src/components/PyDoc.d.ts.map +1 -1
  10. package/dist/src/components/PyDoc.js +59 -15
  11. package/dist/src/components/PyDoc.js.map +1 -1
  12. package/dist/src/components/UnionTypeExpression.d.ts.map +1 -1
  13. package/dist/src/components/UnionTypeExpression.js +6 -6
  14. package/dist/src/components/UnionTypeExpression.js.map +1 -1
  15. package/dist/src/components/index.d.ts +1 -0
  16. package/dist/src/components/index.d.ts.map +1 -1
  17. package/dist/src/components/index.js +1 -0
  18. package/dist/src/components/index.js.map +1 -1
  19. package/dist/test/dataclassdeclarations.test.d.ts +2 -0
  20. package/dist/test/dataclassdeclarations.test.d.ts.map +1 -0
  21. package/dist/test/dataclassdeclarations.test.js +696 -0
  22. package/dist/test/dataclassdeclarations.test.js.map +1 -0
  23. package/dist/test/pydocs.test.js +11 -19
  24. package/dist/test/pydocs.test.js.map +1 -1
  25. package/dist/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +1 -1
  27. package/src/builtins/python.ts +7 -0
  28. package/src/components/DataclassDeclaration.tsx +190 -0
  29. package/src/components/PyDoc.tsx +66 -20
  30. package/src/components/UnionTypeExpression.tsx +4 -7
  31. package/src/components/index.ts +1 -0
  32. package/temp/api.json +267 -0
  33. package/test/dataclassdeclarations.test.tsx +646 -0
  34. package/test/pydocs.test.tsx +11 -24
@@ -0,0 +1,646 @@
1
+ import { Prose, namekey, refkey } from "@alloy-js/core";
2
+ import { d } from "@alloy-js/core/testing";
3
+ import { describe, expect, it } from "vitest";
4
+ import { dataclassesModule } from "../src/builtins/python.js";
5
+ import * as py from "../src/index.js";
6
+ import {
7
+ assertFileContents,
8
+ toSourceText,
9
+ toSourceTextMultiple,
10
+ } from "./utils.jsx";
11
+
12
+ describe("DataclassDeclaration", () => {
13
+ it("Creates a dataclass with a class doc", () => {
14
+ const doc = (
15
+ <py.ClassDoc description={[<Prose>Represents a user.</Prose>]} />
16
+ );
17
+ const res = toSourceText(
18
+ [
19
+ <py.SourceFile path="user.py">
20
+ <py.DataclassDeclaration name="User" doc={doc} />
21
+ </py.SourceFile>,
22
+ ],
23
+ { externals: [dataclassesModule] },
24
+ );
25
+
26
+ expect(res).toRenderTo(
27
+ d`
28
+ from dataclasses import dataclass
29
+
30
+ @dataclass
31
+ class User:
32
+ """
33
+ Represents a user.
34
+ """
35
+
36
+ pass
37
+
38
+
39
+ `,
40
+ );
41
+ });
42
+
43
+ it("Creates a dataclass with fields and defaults", () => {
44
+ const res = toSourceText(
45
+ [
46
+ <py.SourceFile path="user.py">
47
+ <py.DataclassDeclaration name="User">
48
+ <py.VariableDeclaration
49
+ instanceVariable
50
+ omitNone
51
+ name="id"
52
+ type="int"
53
+ />
54
+ <py.VariableDeclaration
55
+ instanceVariable
56
+ name={namekey("_", { ignoreNamePolicy: true })}
57
+ type={dataclassesModule["."].KW_ONLY}
58
+ omitNone
59
+ />
60
+ <py.VariableDeclaration
61
+ instanceVariable
62
+ name="name"
63
+ type="str"
64
+ initializer={"Anonymous"}
65
+ />
66
+ </py.DataclassDeclaration>
67
+ </py.SourceFile>,
68
+ ],
69
+ { externals: [dataclassesModule] },
70
+ );
71
+
72
+ expect(res).toRenderTo(
73
+ d`
74
+ from dataclasses import dataclass
75
+ from dataclasses import KW_ONLY
76
+
77
+ @dataclass
78
+ class User:
79
+ id: int
80
+ _: KW_ONLY
81
+ name: str = "Anonymous"
82
+
83
+
84
+ `,
85
+ );
86
+ });
87
+
88
+ it("Creates a dataclass with keyword arguments", () => {
89
+ const res = toSourceText(
90
+ [
91
+ <py.SourceFile path="user.py">
92
+ <py.DataclassDeclaration name="User" frozen slots kwOnly>
93
+ <py.VariableDeclaration
94
+ instanceVariable
95
+ omitNone
96
+ name="id"
97
+ type="int"
98
+ />
99
+ </py.DataclassDeclaration>
100
+ </py.SourceFile>,
101
+ ],
102
+ { externals: [dataclassesModule] },
103
+ );
104
+
105
+ expect(res).toRenderTo(
106
+ d`
107
+ from dataclasses import dataclass
108
+
109
+ @dataclass(frozen=True, slots=True, kw_only=True)
110
+ class User:
111
+ id: int
112
+
113
+
114
+ `,
115
+ );
116
+ });
117
+
118
+ it("Creates a dataclass with all keyword arguments", () => {
119
+ const res = toSourceText(
120
+ [
121
+ <py.SourceFile path="user.py">
122
+ <py.DataclassDeclaration
123
+ name="User"
124
+ init
125
+ repr={false}
126
+ eq
127
+ order={false}
128
+ unsafeHash
129
+ frozen
130
+ matchArgs={false}
131
+ kwOnly
132
+ slots
133
+ weakrefSlot={false}
134
+ />
135
+ </py.SourceFile>,
136
+ ],
137
+ { externals: [dataclassesModule] },
138
+ );
139
+
140
+ expect(res).toRenderTo(
141
+ d`
142
+ from dataclasses import dataclass
143
+
144
+ @dataclass(init=True, repr=False, eq=True, order=False, unsafe_hash=True, frozen=True, match_args=False, kw_only=True, slots=True, weakref_slot=False)
145
+ class User:
146
+ pass
147
+
148
+
149
+ `,
150
+ );
151
+ });
152
+
153
+ it("Throws error when weakref_slot=True without slots=True", () => {
154
+ expect(() =>
155
+ toSourceText(
156
+ [
157
+ <py.SourceFile path="user.py">
158
+ <py.DataclassDeclaration name="User" weakrefSlot />
159
+ </py.SourceFile>,
160
+ ],
161
+ { externals: [dataclassesModule] },
162
+ ),
163
+ ).toThrowError(
164
+ /weakref_slot=True requires slots=True in @dataclass decorator/,
165
+ );
166
+ });
167
+
168
+ it("Allows weakref_slot=True when slots=True", () => {
169
+ const res = toSourceText(
170
+ [
171
+ <py.SourceFile path="user.py">
172
+ <py.DataclassDeclaration name="User" slots weakrefSlot />
173
+ </py.SourceFile>,
174
+ ],
175
+ { externals: [dataclassesModule] },
176
+ );
177
+ expect(res).toRenderTo(
178
+ d`
179
+ from dataclasses import dataclass
180
+
181
+ @dataclass(slots=True, weakref_slot=True)
182
+ class User:
183
+ pass
184
+
185
+
186
+ `,
187
+ );
188
+ });
189
+
190
+ it("Throws error when order=True and eq=False", () => {
191
+ expect(() =>
192
+ toSourceText(
193
+ [
194
+ <py.SourceFile path="user.py">
195
+ <py.DataclassDeclaration name="User" order eq={false} />
196
+ </py.SourceFile>,
197
+ ],
198
+ { externals: [dataclassesModule] },
199
+ ),
200
+ ).toThrowError(/order=True requires eq=True/);
201
+ });
202
+
203
+ it("Creates a dataclass with order=True and no conflicting methods", () => {
204
+ const res = toSourceText(
205
+ [
206
+ <py.SourceFile path="user.py">
207
+ <py.DataclassDeclaration name="User" order />
208
+ </py.SourceFile>,
209
+ ],
210
+ { externals: [dataclassesModule] },
211
+ );
212
+ expect(res).toRenderTo(
213
+ d`
214
+ from dataclasses import dataclass
215
+
216
+ @dataclass(order=True)
217
+ class User:
218
+ pass
219
+
220
+
221
+ `,
222
+ );
223
+ });
224
+
225
+ it("Throws error when order=True and class defines __lt__", () => {
226
+ expect(() =>
227
+ toSourceText(
228
+ [
229
+ <py.SourceFile path="user.py">
230
+ <py.DataclassDeclaration name="User" order>
231
+ <py.DunderMethodDeclaration name="__lt__" />
232
+ </py.DataclassDeclaration>
233
+ </py.SourceFile>,
234
+ ],
235
+ { externals: [dataclassesModule] },
236
+ ),
237
+ ).toThrowError(
238
+ /Cannot specify order=True when the class already defines __lt__\(\)/,
239
+ );
240
+ });
241
+
242
+ it("Throws error when order=True and class defines __le__", () => {
243
+ expect(() =>
244
+ toSourceText(
245
+ [
246
+ <py.SourceFile path="user.py">
247
+ <py.DataclassDeclaration name="User" order>
248
+ <py.DunderMethodDeclaration name="__le__" />
249
+ </py.DataclassDeclaration>
250
+ </py.SourceFile>,
251
+ ],
252
+ { externals: [dataclassesModule] },
253
+ ),
254
+ ).toThrowError(
255
+ /Cannot specify order=True when the class already defines __le__\(\)/,
256
+ );
257
+ });
258
+
259
+ it("Throws error when order=True and class defines __gt__", () => {
260
+ expect(() =>
261
+ toSourceText(
262
+ [
263
+ <py.SourceFile path="user.py">
264
+ <py.DataclassDeclaration name="User" order>
265
+ <py.DunderMethodDeclaration name="__gt__" />
266
+ </py.DataclassDeclaration>
267
+ </py.SourceFile>,
268
+ ],
269
+ { externals: [dataclassesModule] },
270
+ ),
271
+ ).toThrowError(
272
+ /Cannot specify order=True when the class already defines __gt__\(\)/,
273
+ );
274
+ });
275
+
276
+ it("Throws error when order=True and class defines __ge__", () => {
277
+ expect(() =>
278
+ toSourceText(
279
+ [
280
+ <py.SourceFile path="user.py">
281
+ <py.DataclassDeclaration name="User" order>
282
+ <py.DunderMethodDeclaration name="__ge__" />
283
+ </py.DataclassDeclaration>
284
+ </py.SourceFile>,
285
+ ],
286
+ { externals: [dataclassesModule] },
287
+ ),
288
+ ).toThrowError(
289
+ /Cannot specify order=True when the class already defines __ge__\(\)/,
290
+ );
291
+ });
292
+
293
+ it("Throws error when order=True and a wrapper defines __lt__", () => {
294
+ function Wrapper() {
295
+ return <py.DunderMethodDeclaration name="__lt__" />;
296
+ }
297
+ expect(() =>
298
+ toSourceText(
299
+ [
300
+ <py.SourceFile path="user.py">
301
+ <py.DataclassDeclaration name="User" order>
302
+ <Wrapper />
303
+ </py.DataclassDeclaration>
304
+ </py.SourceFile>,
305
+ ],
306
+ { externals: [dataclassesModule] },
307
+ ),
308
+ ).toThrowError(
309
+ /Cannot specify order=True when the class already defines __lt__\(\)/,
310
+ );
311
+ });
312
+
313
+ it("Throws error when unsafe_hash=True and class defines __hash__", () => {
314
+ expect(() =>
315
+ toSourceText(
316
+ [
317
+ <py.SourceFile path="user.py">
318
+ <py.DataclassDeclaration name="User" unsafeHash>
319
+ <py.DunderMethodDeclaration name="__hash__" />
320
+ </py.DataclassDeclaration>
321
+ </py.SourceFile>,
322
+ ],
323
+ { externals: [dataclassesModule] },
324
+ ),
325
+ ).toThrowError(
326
+ /Cannot specify unsafe_hash=True when the class already defines __hash__\(\)/,
327
+ );
328
+ });
329
+
330
+ it("Throws error when frozen=True and class defines __setattr__", () => {
331
+ expect(() =>
332
+ toSourceText(
333
+ [
334
+ <py.SourceFile path="user.py">
335
+ <py.DataclassDeclaration name="User" frozen>
336
+ <py.DunderMethodDeclaration name="__setattr__" />
337
+ </py.DataclassDeclaration>
338
+ </py.SourceFile>,
339
+ ],
340
+ { externals: [dataclassesModule] },
341
+ ),
342
+ ).toThrowError(
343
+ /Cannot specify frozen=True when the class already defines __setattr__\(\)/,
344
+ );
345
+ });
346
+
347
+ it("Throws errorwhen frozen=True and class defines __delattr__", () => {
348
+ expect(() =>
349
+ toSourceText(
350
+ [
351
+ <py.SourceFile path="user.py">
352
+ <py.DataclassDeclaration name="User" frozen>
353
+ <py.DunderMethodDeclaration name="__delattr__" />
354
+ </py.DataclassDeclaration>
355
+ </py.SourceFile>,
356
+ ],
357
+ { externals: [dataclassesModule] },
358
+ ),
359
+ ).toThrowError(
360
+ /Cannot specify frozen=True when the class already defines __delattr__\(\)/,
361
+ );
362
+ });
363
+
364
+ it("Throws error when slots=True and class defines __slots__", () => {
365
+ expect(() =>
366
+ toSourceText(
367
+ [
368
+ <py.SourceFile path="user.py">
369
+ <py.DataclassDeclaration name="User" slots>
370
+ <py.DunderMethodDeclaration name="__slots__" />
371
+ </py.DataclassDeclaration>
372
+ </py.SourceFile>,
373
+ ],
374
+ { externals: [dataclassesModule] },
375
+ ),
376
+ ).toThrowError(
377
+ /Cannot specify slots=True when the class already defines __slots__\(\)/,
378
+ );
379
+ });
380
+
381
+ it("Creates a dataclass with kw_only=True on decorator (sentinel not used)", () => {
382
+ const res = toSourceText(
383
+ [
384
+ <py.SourceFile path="user.py">
385
+ <py.DataclassDeclaration name="User" kwOnly>
386
+ <py.VariableDeclaration
387
+ instanceVariable
388
+ omitNone
389
+ name="id"
390
+ type="int"
391
+ />
392
+ </py.DataclassDeclaration>
393
+ </py.SourceFile>,
394
+ ],
395
+ { externals: [dataclassesModule] },
396
+ );
397
+ expect(res).toRenderTo(
398
+ d`
399
+ from dataclasses import dataclass
400
+
401
+ @dataclass(kw_only=True)
402
+ class User:
403
+ id: int
404
+
405
+
406
+ `,
407
+ );
408
+ });
409
+
410
+ it("Creates a dataclass with base classes", () => {
411
+ const res = toSourceText(
412
+ [
413
+ <py.SourceFile path="user.py">
414
+ <py.DataclassDeclaration name="User" bases={["Base"]} />
415
+ </py.SourceFile>,
416
+ ],
417
+ { externals: [dataclassesModule] },
418
+ );
419
+
420
+ expect(res).toRenderTo(
421
+ d`
422
+ from dataclasses import dataclass
423
+
424
+ @dataclass
425
+ class User(Base):
426
+ pass
427
+
428
+
429
+ `,
430
+ );
431
+ });
432
+
433
+ it("Throws error when more than one KW_ONLY sentinel is present", () => {
434
+ expect(() =>
435
+ toSourceText(
436
+ [
437
+ <py.SourceFile path="user.py">
438
+ <py.DataclassDeclaration name="User">
439
+ <py.VariableDeclaration
440
+ instanceVariable
441
+ name={namekey("_", { ignoreNamePolicy: true })}
442
+ type={dataclassesModule["."].KW_ONLY}
443
+ omitNone
444
+ />
445
+ <py.VariableDeclaration
446
+ instanceVariable
447
+ name={namekey("_", { ignoreNamePolicy: true })}
448
+ type={dataclassesModule["."].KW_ONLY}
449
+ omitNone
450
+ />
451
+ </py.DataclassDeclaration>
452
+ </py.SourceFile>,
453
+ ],
454
+ { externals: [dataclassesModule] },
455
+ ),
456
+ ).toThrowError(/Only one KW_ONLY sentinel is allowed per dataclass body/);
457
+ });
458
+
459
+ it("Will raise arg validation errors first over member conflicts", () => {
460
+ expect(() =>
461
+ toSourceText(
462
+ [
463
+ <py.SourceFile path="user.py">
464
+ <py.DataclassDeclaration name="User" order eq={false}>
465
+ <py.DunderMethodDeclaration name="__lt__" />
466
+ </py.DataclassDeclaration>
467
+ </py.SourceFile>,
468
+ ],
469
+ { externals: [dataclassesModule] },
470
+ ),
471
+ ).toThrowError(/order=True requires eq=True/);
472
+ });
473
+
474
+ it("Does not raise errors for member conflict checks without the equivalent kwargs", () => {
475
+ expect(() =>
476
+ toSourceText(
477
+ [
478
+ <py.SourceFile path="user.py">
479
+ <py.DataclassDeclaration name="User">
480
+ <py.DunderMethodDeclaration name="__lt__" />
481
+ <py.DunderMethodDeclaration name="__slots__" />
482
+ <py.DunderMethodDeclaration name="__hash__" />
483
+ <py.DunderMethodDeclaration name="__setattr__" />
484
+ <py.DunderMethodDeclaration name="__delattr__" />
485
+ </py.DataclassDeclaration>
486
+ </py.SourceFile>,
487
+ ],
488
+ { externals: [dataclassesModule] },
489
+ ),
490
+ ).not.toThrow();
491
+ });
492
+
493
+ it("Allows unsafe_hash=True when no __hash__ is defined", () => {
494
+ const res = toSourceText(
495
+ [
496
+ <py.SourceFile path="user.py">
497
+ <py.DataclassDeclaration name="User" unsafeHash />
498
+ </py.SourceFile>,
499
+ ],
500
+ { externals: [dataclassesModule] },
501
+ );
502
+ expect(res).toRenderTo(
503
+ d`
504
+ from dataclasses import dataclass
505
+
506
+ @dataclass(unsafe_hash=True)
507
+ class User:
508
+ pass
509
+
510
+
511
+ `,
512
+ );
513
+ });
514
+
515
+ it("Counts KW_ONLY sentinels through wrappers (symbol-level)", () => {
516
+ function Wrapper() {
517
+ return (
518
+ <py.VariableDeclaration
519
+ instanceVariable
520
+ name={namekey("_", { ignoreNamePolicy: true })}
521
+ type={dataclassesModule["."].KW_ONLY}
522
+ omitNone
523
+ />
524
+ );
525
+ }
526
+ expect(() =>
527
+ toSourceText(
528
+ [
529
+ <py.SourceFile path="user.py">
530
+ <py.DataclassDeclaration name="User">
531
+ <py.VariableDeclaration
532
+ instanceVariable
533
+ name={namekey("_", { ignoreNamePolicy: true })}
534
+ type={dataclassesModule["."].KW_ONLY}
535
+ omitNone
536
+ />
537
+ <Wrapper />
538
+ </py.DataclassDeclaration>
539
+ </py.SourceFile>,
540
+ ],
541
+ { externals: [dataclassesModule] },
542
+ ),
543
+ ).toThrowError(/Only one KW_ONLY sentinel is allowed per dataclass body/);
544
+ });
545
+
546
+ it("Allows frozen=True when no conflicting dunders exist", () => {
547
+ const res = toSourceText(
548
+ [
549
+ <py.SourceFile path="user.py">
550
+ <py.DataclassDeclaration name="User" frozen />
551
+ </py.SourceFile>,
552
+ ],
553
+ { externals: [dataclassesModule] },
554
+ );
555
+ expect(res).toRenderTo(
556
+ d`
557
+ from dataclasses import dataclass
558
+
559
+ @dataclass(frozen=True)
560
+ class User:
561
+ pass
562
+
563
+
564
+ `,
565
+ );
566
+ });
567
+
568
+ it("Allows slots=True when no __slots__ is defined", () => {
569
+ const res = toSourceText(
570
+ [
571
+ <py.SourceFile path="user.py">
572
+ <py.DataclassDeclaration name="User" slots />
573
+ </py.SourceFile>,
574
+ ],
575
+ { externals: [dataclassesModule] },
576
+ );
577
+ expect(res).toRenderTo(
578
+ d`
579
+ from dataclasses import dataclass
580
+
581
+ @dataclass(slots=True)
582
+ class User:
583
+ pass
584
+
585
+
586
+ `,
587
+ );
588
+ });
589
+
590
+ it("Forwards refkey prop for symbol resolution in type references", () => {
591
+ const userRefkey = refkey();
592
+ const res = toSourceTextMultiple(
593
+ [
594
+ <py.SourceFile path="models.py">
595
+ <py.DataclassDeclaration name="User" refkey={userRefkey}>
596
+ <py.VariableDeclaration
597
+ instanceVariable
598
+ omitNone
599
+ name="id"
600
+ type="int"
601
+ />
602
+ <py.VariableDeclaration
603
+ instanceVariable
604
+ omitNone
605
+ name="name"
606
+ type="str"
607
+ />
608
+ </py.DataclassDeclaration>
609
+ </py.SourceFile>,
610
+ <py.SourceFile path="services.py">
611
+ <py.FunctionDeclaration name="get_user" returnType={userRefkey}>
612
+ <py.VariableDeclaration
613
+ name="user"
614
+ type={userRefkey}
615
+ initializer={
616
+ <py.ClassInstantiation target="User" args={["1", '"Alice"']} />
617
+ }
618
+ />
619
+ <hbr />
620
+ {"return user"}
621
+ </py.FunctionDeclaration>
622
+ </py.SourceFile>,
623
+ ],
624
+ { externals: [dataclassesModule] },
625
+ );
626
+ assertFileContents(res, {
627
+ "models.py": `
628
+ from dataclasses import dataclass
629
+
630
+ @dataclass
631
+ class User:
632
+ id: int
633
+ name: str
634
+
635
+ `,
636
+ "services.py": `
637
+ from models import User
638
+
639
+ def get_user() -> User:
640
+ user: User = User(1, "Alice")
641
+ return user
642
+
643
+ `,
644
+ });
645
+ });
646
+ });