otterapi 0.0.5__py3-none-any.whl → 0.0.6__py3-none-any.whl

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 (52) hide show
  1. README.md +581 -8
  2. otterapi/__init__.py +73 -0
  3. otterapi/cli.py +327 -29
  4. otterapi/codegen/__init__.py +115 -0
  5. otterapi/codegen/ast_utils.py +134 -5
  6. otterapi/codegen/client.py +1271 -0
  7. otterapi/codegen/codegen.py +1736 -0
  8. otterapi/codegen/dataframes.py +392 -0
  9. otterapi/codegen/emitter.py +473 -0
  10. otterapi/codegen/endpoints.py +2597 -343
  11. otterapi/codegen/pagination.py +1026 -0
  12. otterapi/codegen/schema.py +593 -0
  13. otterapi/codegen/splitting.py +1397 -0
  14. otterapi/codegen/types.py +1345 -0
  15. otterapi/codegen/utils.py +180 -1
  16. otterapi/config.py +1017 -24
  17. otterapi/exceptions.py +231 -0
  18. otterapi/openapi/__init__.py +46 -0
  19. otterapi/openapi/v2/__init__.py +86 -0
  20. otterapi/openapi/v2/spec.json +1607 -0
  21. otterapi/openapi/v2/v2.py +1776 -0
  22. otterapi/openapi/v3/__init__.py +131 -0
  23. otterapi/openapi/v3/spec.json +1651 -0
  24. otterapi/openapi/v3/v3.py +1557 -0
  25. otterapi/openapi/v3_1/__init__.py +133 -0
  26. otterapi/openapi/v3_1/spec.json +1411 -0
  27. otterapi/openapi/v3_1/v3_1.py +798 -0
  28. otterapi/openapi/v3_2/__init__.py +133 -0
  29. otterapi/openapi/v3_2/spec.json +1666 -0
  30. otterapi/openapi/v3_2/v3_2.py +777 -0
  31. otterapi/tests/__init__.py +3 -0
  32. otterapi/tests/fixtures/__init__.py +455 -0
  33. otterapi/tests/test_ast_utils.py +680 -0
  34. otterapi/tests/test_codegen.py +610 -0
  35. otterapi/tests/test_dataframe.py +1038 -0
  36. otterapi/tests/test_exceptions.py +493 -0
  37. otterapi/tests/test_openapi_support.py +616 -0
  38. otterapi/tests/test_openapi_upgrade.py +215 -0
  39. otterapi/tests/test_pagination.py +1101 -0
  40. otterapi/tests/test_splitting_config.py +319 -0
  41. otterapi/tests/test_splitting_integration.py +427 -0
  42. otterapi/tests/test_splitting_resolver.py +512 -0
  43. otterapi/tests/test_splitting_tree.py +525 -0
  44. otterapi-0.0.6.dist-info/METADATA +627 -0
  45. otterapi-0.0.6.dist-info/RECORD +48 -0
  46. {otterapi-0.0.5.dist-info → otterapi-0.0.6.dist-info}/WHEEL +1 -1
  47. otterapi/codegen/generator.py +0 -358
  48. otterapi/codegen/openapi_processor.py +0 -27
  49. otterapi/codegen/type_generator.py +0 -559
  50. otterapi-0.0.5.dist-info/METADATA +0 -54
  51. otterapi-0.0.5.dist-info/RECORD +0 -16
  52. {otterapi-0.0.5.dist-info → otterapi-0.0.6.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,680 @@
1
+ """Test suite for ast_utils module.
2
+
3
+ This module tests all AST helper functions that are used to generate
4
+ Python AST nodes for code generation.
5
+ """
6
+
7
+ import ast
8
+
9
+ import pytest
10
+
11
+ from otterapi.codegen.ast_utils import (
12
+ _all,
13
+ _argument,
14
+ _assign,
15
+ _async_func,
16
+ _attr,
17
+ _call,
18
+ _func,
19
+ _import,
20
+ _name,
21
+ _optional_expr,
22
+ _subscript,
23
+ _union_expr,
24
+ )
25
+
26
+
27
+ class TestNameFunction:
28
+ """Tests for _name() function."""
29
+
30
+ def test_creates_name_node(self):
31
+ """Test that _name creates an ast.Name node."""
32
+ result = _name('foo')
33
+ assert isinstance(result, ast.Name)
34
+
35
+ def test_name_has_correct_id(self):
36
+ """Test that the Name node has the correct id."""
37
+ result = _name('my_variable')
38
+ assert result.id == 'my_variable'
39
+
40
+ def test_name_has_load_context(self):
41
+ """Test that the Name node has Load context."""
42
+ result = _name('bar')
43
+ assert isinstance(result.ctx, ast.Load)
44
+
45
+ @pytest.mark.parametrize(
46
+ 'name',
47
+ ['a', 'variable_name', 'CamelCase', 'snake_case', '_private', '__dunder__'],
48
+ )
49
+ def test_various_valid_names(self, name):
50
+ """Test _name with various valid Python identifiers."""
51
+ result = _name(name)
52
+ assert result.id == name
53
+ assert isinstance(result, ast.Name)
54
+
55
+
56
+ class TestAttrFunction:
57
+ """Tests for _attr() function."""
58
+
59
+ def test_creates_attribute_node(self):
60
+ """Test that _attr creates an ast.Attribute node."""
61
+ result = _attr('obj', 'attr')
62
+ assert isinstance(result, ast.Attribute)
63
+
64
+ def test_attr_with_string_value(self):
65
+ """Test _attr with a string value (gets converted to Name)."""
66
+ result = _attr('myobj', 'myattr')
67
+ assert isinstance(result.value, ast.Name)
68
+ assert result.value.id == 'myobj'
69
+ assert result.attr == 'myattr'
70
+
71
+ def test_attr_with_ast_expr_value(self):
72
+ """Test _attr with an existing ast.expr value."""
73
+ name_node = _name('base')
74
+ result = _attr(name_node, 'property')
75
+ assert result.value is name_node
76
+ assert result.attr == 'property'
77
+
78
+ def test_chained_attributes(self):
79
+ """Test creating chained attributes like obj.attr1.attr2."""
80
+ first = _attr('obj', 'attr1')
81
+ second = _attr(first, 'attr2')
82
+ assert isinstance(second, ast.Attribute)
83
+ assert isinstance(second.value, ast.Attribute)
84
+ assert second.attr == 'attr2'
85
+ assert second.value.attr == 'attr1'
86
+
87
+
88
+ class TestSubscriptFunction:
89
+ """Tests for _subscript() function."""
90
+
91
+ def test_creates_subscript_node(self):
92
+ """Test that _subscript creates an ast.Subscript node."""
93
+ inner = _name('int')
94
+ result = _subscript('list', inner)
95
+ assert isinstance(result, ast.Subscript)
96
+
97
+ def test_subscript_structure(self):
98
+ """Test the structure of a subscript node."""
99
+ inner = _name('str')
100
+ result = _subscript('List', inner)
101
+ assert isinstance(result.value, ast.Name)
102
+ assert result.value.id == 'List'
103
+ assert result.slice is inner
104
+
105
+ def test_nested_subscript(self):
106
+ """Test nested subscripts like List[Dict[str, int]]."""
107
+ str_node = _name('str')
108
+ int_node = _name('int')
109
+ dict_node = _subscript('Dict', ast.Tuple(elts=[str_node, int_node]))
110
+ result = _subscript('List', dict_node)
111
+ assert isinstance(result, ast.Subscript)
112
+ assert isinstance(result.slice, ast.Subscript)
113
+
114
+
115
+ class TestUnionExprFunction:
116
+ """Tests for _union_expr() function."""
117
+
118
+ def test_creates_union_binop(self):
119
+ """Test that _union_expr creates a BinOp with BitOr (pipe operator)."""
120
+ types = [_name('int'), _name('str')]
121
+ result = _union_expr(types)
122
+ assert isinstance(result, ast.BinOp)
123
+ assert isinstance(result.op, ast.BitOr)
124
+
125
+ def test_union_contains_types(self):
126
+ """Test that union contains the provided types via BinOp chain."""
127
+ types = [_name('int'), _name('str'), _name('bool')]
128
+ result = _union_expr(types)
129
+ # Result is: (int | str) | bool
130
+ assert isinstance(result, ast.BinOp)
131
+ assert isinstance(result.right, ast.Name)
132
+ assert result.right.id == 'bool'
133
+
134
+ def test_union_with_two_types(self):
135
+ """Test Union with two types."""
136
+ types = [_name('int'), _name('None')]
137
+ result = _union_expr(types)
138
+ assert isinstance(result, ast.BinOp)
139
+ assert isinstance(result.op, ast.BitOr)
140
+ assert result.left.id == 'int'
141
+ assert result.right.id == 'None'
142
+
143
+ def test_union_with_many_types(self):
144
+ """Test Union with many types builds a chain of BinOps."""
145
+ types = [_name(t) for t in ['int', 'str', 'float', 'bool', 'None']]
146
+ result = _union_expr(types)
147
+ # Count the depth of BinOps - should be 4 for 5 types
148
+ count = 0
149
+ node = result
150
+ while isinstance(node, ast.BinOp):
151
+ count += 1
152
+ node = node.left
153
+ assert count == 4
154
+
155
+
156
+ class TestOptionalExprFunction:
157
+ """Tests for _optional_expr() function."""
158
+
159
+ def test_creates_optional_subscript(self):
160
+ """Test that _optional_expr creates an Optional subscript."""
161
+ inner = _name('str')
162
+ result = _optional_expr(inner)
163
+ assert isinstance(result, ast.Subscript)
164
+ assert result.value.id == 'Optional'
165
+
166
+ def test_optional_with_simple_type(self):
167
+ """Test Optional with a simple type."""
168
+ inner = _name('int')
169
+ result = _optional_expr(inner)
170
+ assert result.slice is inner
171
+
172
+ def test_optional_with_complex_type(self):
173
+ """Test Optional with a complex type like List[str]."""
174
+ list_str = _subscript('List', _name('str'))
175
+ result = _optional_expr(list_str)
176
+ assert isinstance(result.slice, ast.Subscript)
177
+ assert result.value.id == 'Optional'
178
+
179
+
180
+ class TestArgumentFunction:
181
+ """Tests for _argument() function."""
182
+
183
+ def test_creates_arg_node(self):
184
+ """Test that _argument creates an ast.arg node."""
185
+ result = _argument('param')
186
+ assert isinstance(result, ast.arg)
187
+
188
+ def test_argument_without_annotation(self):
189
+ """Test argument without type annotation."""
190
+ result = _argument('x')
191
+ assert result.arg == 'x'
192
+ assert result.annotation is None
193
+
194
+ def test_argument_with_annotation(self):
195
+ """Test argument with type annotation."""
196
+ annotation = _name('int')
197
+ result = _argument('x', annotation)
198
+ assert result.arg == 'x'
199
+ assert result.annotation is annotation
200
+
201
+ @pytest.mark.parametrize(
202
+ 'name',
203
+ ['arg', 'parameter', 'self', 'cls', 'value', '_private_arg'],
204
+ )
205
+ def test_various_argument_names(self, name):
206
+ """Test _argument with various parameter names."""
207
+ result = _argument(name)
208
+ assert result.arg == name
209
+
210
+
211
+ class TestAssignFunction:
212
+ """Tests for _assign() function."""
213
+
214
+ def test_creates_assign_node(self):
215
+ """Test that _assign creates an ast.Assign node."""
216
+ target = _name('x')
217
+ value = ast.Constant(value=42)
218
+ result = _assign(target, value)
219
+ assert isinstance(result, ast.Assign)
220
+
221
+ def test_assign_structure(self):
222
+ """Test the structure of an assignment."""
223
+ target = _name('result')
224
+ value = ast.Constant(value='hello')
225
+ result = _assign(target, value)
226
+ assert len(result.targets) == 1
227
+ # Target gets converted to Store context, so check the id instead
228
+ assert isinstance(result.targets[0], ast.Name)
229
+ assert result.targets[0].id == 'result'
230
+ assert isinstance(result.targets[0].ctx, ast.Store)
231
+ assert result.value is value
232
+
233
+ def test_assign_with_complex_value(self):
234
+ """Test assignment with complex value expression."""
235
+ target = _name('data')
236
+ value = _call(_name('dict'))
237
+ result = _assign(target, value)
238
+ assert isinstance(result.value, ast.Call)
239
+
240
+ def test_assign_with_attribute_target(self):
241
+ """Test assignment to an attribute (e.g., obj.attr = value)."""
242
+ target = _attr('obj', 'attr')
243
+ value = ast.Constant(value=100)
244
+ result = _assign(target, value)
245
+ assert len(result.targets) == 1
246
+ assert isinstance(result.targets[0], ast.Attribute)
247
+ assert isinstance(result.targets[0].ctx, ast.Store)
248
+ assert result.targets[0].attr == 'attr'
249
+
250
+
251
+ class TestImportFunction:
252
+ """Tests for _import() function."""
253
+
254
+ def test_creates_importfrom_node(self):
255
+ """Test that _import creates an ast.ImportFrom node."""
256
+ result = _import('typing', ['List'])
257
+ assert isinstance(result, ast.ImportFrom)
258
+
259
+ def test_import_single_name(self):
260
+ """Test importing a single name from a module."""
261
+ result = _import('os', ['path'])
262
+ assert result.module == 'os'
263
+ assert len(result.names) == 1
264
+ assert result.names[0].name == 'path'
265
+
266
+ def test_import_multiple_names(self):
267
+ """Test importing multiple names from a module."""
268
+ result = _import('typing', ['Dict', 'List', 'Optional'])
269
+ assert result.module == 'typing'
270
+ assert len(result.names) == 3
271
+ assert [alias.name for alias in result.names] == ['Dict', 'List', 'Optional']
272
+
273
+ def test_import_level_zero(self):
274
+ """Test that import level is 0 (absolute import)."""
275
+ result = _import('collections', ['defaultdict'])
276
+ assert result.level == 0
277
+
278
+ def test_import_creates_aliases(self):
279
+ """Test that imported names are ast.alias objects."""
280
+ result = _import('json', ['dumps', 'loads'])
281
+ for alias in result.names:
282
+ assert isinstance(alias, ast.alias)
283
+
284
+
285
+ class TestCallFunction:
286
+ """Tests for _call() function."""
287
+
288
+ def test_creates_call_node(self):
289
+ """Test that _call creates an ast.Call node."""
290
+ func = _name('print')
291
+ result = _call(func)
292
+ assert isinstance(result, ast.Call)
293
+
294
+ def test_call_without_args(self):
295
+ """Test function call without arguments."""
296
+ func = _name('foo')
297
+ result = _call(func)
298
+ assert result.func is func
299
+ assert result.args == []
300
+ assert result.keywords == []
301
+
302
+ def test_call_with_positional_args(self):
303
+ """Test function call with positional arguments."""
304
+ func = _name('max')
305
+ args = [ast.Constant(value=1), ast.Constant(value=2)]
306
+ result = _call(func, args=args)
307
+ assert len(result.args) == 2
308
+ assert result.args == args
309
+
310
+ def test_call_with_keywords(self):
311
+ """Test function call with keyword arguments."""
312
+ func = _name('dict')
313
+ keywords = [ast.keyword(arg='key', value=ast.Constant(value='value'))]
314
+ result = _call(func, keywords=keywords)
315
+ assert len(result.keywords) == 1
316
+ assert result.keywords == keywords
317
+
318
+ def test_call_with_args_and_keywords(self):
319
+ """Test function call with both positional and keyword arguments."""
320
+ func = _name('sorted')
321
+ args = [_name('items')]
322
+ keywords = [ast.keyword(arg='reverse', value=ast.Constant(value=True))]
323
+ result = _call(func, args=args, keywords=keywords)
324
+ assert len(result.args) == 1
325
+ assert len(result.keywords) == 1
326
+
327
+ def test_call_with_attribute_func(self):
328
+ """Test calling a method (attribute function)."""
329
+ func = _attr('obj', 'method')
330
+ result = _call(func)
331
+ assert isinstance(result.func, ast.Attribute)
332
+
333
+
334
+ class TestFuncFunction:
335
+ """Tests for _func() function."""
336
+
337
+ def test_creates_functiondef_node(self):
338
+ """Test that _func creates an ast.FunctionDef node."""
339
+ body = [ast.Pass()]
340
+ result = _func('foo', [], body)
341
+ assert isinstance(result, ast.FunctionDef)
342
+
343
+ def test_func_basic_structure(self):
344
+ """Test basic function structure."""
345
+ body = [ast.Return(value=ast.Constant(value=42))]
346
+ result = _func('get_answer', [], body)
347
+ assert result.name == 'get_answer'
348
+ assert result.body == body
349
+ assert result.decorator_list == []
350
+
351
+ def test_func_with_args(self):
352
+ """Test function with arguments."""
353
+ args = [_argument('x'), _argument('y')]
354
+ body = [ast.Pass()]
355
+ result = _func('add', args, body)
356
+ assert len(result.args.args) == 2
357
+ assert result.args.args[0].arg == 'x'
358
+ assert result.args.args[1].arg == 'y'
359
+
360
+ def test_func_with_return_annotation(self):
361
+ """Test function with return type annotation."""
362
+ body = [ast.Pass()]
363
+ returns = _name('int')
364
+ result = _func('compute', [], body, returns=returns)
365
+ assert result.returns is returns
366
+
367
+ def test_func_with_kwargs(self):
368
+ """Test function with **kwargs parameter."""
369
+ body = [ast.Pass()]
370
+ kwargs = _argument('kwargs')
371
+ result = _func('process', [], body, kwargs=kwargs)
372
+ assert result.args.kwarg is kwargs
373
+
374
+ def test_func_with_kwonly_args(self):
375
+ """Test function with keyword-only arguments."""
376
+ body = [ast.Pass()]
377
+ kwonlyargs = [_argument('force'), _argument('verbose')]
378
+ kw_defaults = [ast.Constant(value=False), ast.Constant(value=True)]
379
+ result = _func('run', [], body, kwonlyargs=kwonlyargs, kw_defaults=kw_defaults)
380
+ assert len(result.args.kwonlyargs) == 2
381
+ assert len(result.args.kw_defaults) == 2
382
+
383
+ def test_func_arguments_structure(self):
384
+ """Test that function arguments have correct structure."""
385
+ args = [_argument('a')]
386
+ body = [ast.Pass()]
387
+ result = _func('test', args, body)
388
+ assert isinstance(result.args, ast.arguments)
389
+ assert result.args.posonlyargs == []
390
+ assert result.args.defaults == []
391
+
392
+ def test_func_complex_signature(self):
393
+ """Test function with complex signature."""
394
+ args = [_argument('x', _name('int')), _argument('y', _name('str'))]
395
+ body = [ast.Return(value=_name('x'))]
396
+ returns = _name('int')
397
+ kwargs = _argument('options')
398
+ result = _func('complex_func', args, body, returns=returns, kwargs=kwargs)
399
+ assert len(result.args.args) == 2
400
+ assert result.returns is returns
401
+ assert result.args.kwarg is kwargs
402
+
403
+
404
+ class TestAsyncFuncFunction:
405
+ """Tests for _async_func() function."""
406
+
407
+ def test_creates_async_functiondef_node(self):
408
+ """Test that _async_func creates an ast.AsyncFunctionDef node."""
409
+ body = [ast.Pass()]
410
+ result = _async_func('async_foo', [], body)
411
+ assert isinstance(result, ast.AsyncFunctionDef)
412
+
413
+ def test_async_func_basic_structure(self):
414
+ """Test basic async function structure."""
415
+ body = [ast.Return(value=ast.Constant(value='result'))]
416
+ result = _async_func('fetch_data', [], body)
417
+ assert result.name == 'fetch_data'
418
+ assert result.body == body
419
+ assert result.decorator_list == []
420
+
421
+ def test_async_func_with_args(self):
422
+ """Test async function with arguments."""
423
+ args = [_argument('url'), _argument('timeout')]
424
+ body = [ast.Pass()]
425
+ result = _async_func('fetch', args, body)
426
+ assert len(result.args.args) == 2
427
+
428
+ def test_async_func_with_return_annotation(self):
429
+ """Test async function with return type annotation."""
430
+ body = [ast.Pass()]
431
+ returns = _subscript('Awaitable', _name('dict'))
432
+ result = _async_func('get_json', [], body, returns=returns)
433
+ assert result.returns is returns
434
+
435
+ def test_async_func_with_kwargs(self):
436
+ """Test async function with **kwargs."""
437
+ body = [ast.Pass()]
438
+ kwargs = _argument('kwargs')
439
+ result = _async_func('async_process', [], body, kwargs=kwargs)
440
+ assert result.args.kwarg is kwargs
441
+
442
+ def test_async_func_with_kwonly_args(self):
443
+ """Test async function with keyword-only arguments."""
444
+ body = [ast.Pass()]
445
+ kwonlyargs = [_argument('retry')]
446
+ kw_defaults = [ast.Constant(value=True)]
447
+ result = _async_func(
448
+ 'async_run', [], body, kwonlyargs=kwonlyargs, kw_defaults=kw_defaults
449
+ )
450
+ assert len(result.args.kwonlyargs) == 1
451
+ assert len(result.args.kw_defaults) == 1
452
+
453
+
454
+ class TestAllFunction:
455
+ """Tests for _all() function."""
456
+
457
+ def test_creates_assign_node(self):
458
+ """Test that _all creates an ast.Assign node."""
459
+ result = _all(['foo', 'bar'])
460
+ assert isinstance(result, ast.Assign)
461
+
462
+ def test_all_target_is_dunder_all(self):
463
+ """Test that __all__ is the assignment target."""
464
+ result = _all(['module_func'])
465
+ assert len(result.targets) == 1
466
+ target = result.targets[0]
467
+ assert isinstance(target, ast.Name)
468
+ assert target.id == '__all__'
469
+
470
+ def test_all_value_is_tuple(self):
471
+ """Test that the value is a tuple."""
472
+ result = _all(['func1', 'func2', 'Class1'])
473
+ assert isinstance(result.value, ast.Tuple)
474
+ assert isinstance(result.value.ctx, ast.Load)
475
+
476
+ def test_all_contains_string_constants(self):
477
+ """Test that tuple elements are string constants."""
478
+ names = ['public_func', 'PublicClass', 'PUBLIC_CONSTANT']
479
+ result = _all(names)
480
+ elements = result.value.elts
481
+ assert len(elements) == 3
482
+ for i, name in enumerate(names):
483
+ assert isinstance(elements[i], ast.Constant)
484
+ assert elements[i].value == name
485
+
486
+ def test_all_with_empty_list(self):
487
+ """Test _all with an empty list."""
488
+ result = _all([])
489
+ assert len(result.value.elts) == 0
490
+
491
+ def test_all_with_single_name(self):
492
+ """Test _all with a single exported name."""
493
+ result = _all(['only_export'])
494
+ assert len(result.value.elts) == 1
495
+ assert result.value.elts[0].value == 'only_export'
496
+
497
+ def test_all_with_iterable(self):
498
+ """Test _all with different iterable types."""
499
+ # Test with generator
500
+ result = _all(name for name in ['a', 'b'])
501
+ assert len(result.value.elts) == 2
502
+
503
+ # Test with set
504
+ result = _all({'x', 'y', 'z'})
505
+ assert len(result.value.elts) == 3
506
+
507
+
508
+ class TestASTNodeCompilation:
509
+ """Integration tests to verify generated AST nodes can be compiled."""
510
+
511
+ def test_compile_simple_function(self):
512
+ """Test that generated function AST can be compiled."""
513
+ func = _func(
514
+ 'simple',
515
+ [_argument('x', _name('int'))],
516
+ [ast.Return(value=_name('x'))],
517
+ returns=_name('int'),
518
+ )
519
+ module = ast.Module(body=[func], type_ignores=[])
520
+ ast.fix_missing_locations(module)
521
+ code = compile(module, '<test>', 'exec')
522
+ assert code is not None
523
+
524
+ def test_compile_async_function(self):
525
+ """Test that generated async function AST can be compiled."""
526
+ func = _async_func(
527
+ 'async_simple',
528
+ [_argument('x')],
529
+ [ast.Return(value=_name('x'))],
530
+ )
531
+ module = ast.Module(body=[func], type_ignores=[])
532
+ ast.fix_missing_locations(module)
533
+ code = compile(module, '<test>', 'exec')
534
+ assert code is not None
535
+
536
+ def test_compile_import_statement(self):
537
+ """Test that generated import AST can be compiled."""
538
+ import_node = _import('typing', ['List', 'Dict'])
539
+ module = ast.Module(body=[import_node], type_ignores=[])
540
+ ast.fix_missing_locations(module)
541
+ code = compile(module, '<test>', 'exec')
542
+ assert code is not None
543
+
544
+ def test_compile_assignment(self):
545
+ """Test that generated assignment AST can be compiled."""
546
+ assign = _assign(_name('x'), ast.Constant(value=10))
547
+ module = ast.Module(body=[assign], type_ignores=[])
548
+ ast.fix_missing_locations(module)
549
+ code = compile(module, '<test>', 'exec')
550
+ assert code is not None
551
+
552
+ def test_compile_all_export(self):
553
+ """Test that generated __all__ AST can be compiled."""
554
+ all_node = _all(['func1', 'func2', 'Class1'])
555
+ module = ast.Module(body=[all_node], type_ignores=[])
556
+ ast.fix_missing_locations(module)
557
+ code = compile(module, '<test>', 'exec')
558
+ assert code is not None
559
+
560
+ def test_compile_complex_type_annotation(self):
561
+ """Test that complex type annotations compile correctly."""
562
+ # Create Dict[str, Optional[List[int]]]
563
+ list_int = _subscript('List', _name('int'))
564
+ optional_list = _optional_expr(list_int)
565
+ dict_type = _subscript(
566
+ 'Dict', ast.Tuple(elts=[_name('str'), optional_list], ctx=ast.Load())
567
+ )
568
+
569
+ func = _func(
570
+ 'complex_type',
571
+ [_argument('data', dict_type)],
572
+ [ast.Pass()],
573
+ )
574
+ module = ast.Module(body=[func], type_ignores=[])
575
+ ast.fix_missing_locations(module)
576
+ code = compile(module, '<test>', 'exec')
577
+ assert code is not None
578
+
579
+
580
+ class TestASTNodeExecution:
581
+ """Integration tests that execute generated code."""
582
+
583
+ def test_execute_simple_function(self):
584
+ """Test executing a generated function."""
585
+ func = _func(
586
+ 'add',
587
+ [_argument('a'), _argument('b')],
588
+ [
589
+ ast.Return(
590
+ value=ast.BinOp(left=_name('a'), op=ast.Add(), right=_name('b'))
591
+ )
592
+ ],
593
+ )
594
+ module = ast.Module(body=[func], type_ignores=[])
595
+ ast.fix_missing_locations(module)
596
+
597
+ namespace = {}
598
+ exec(compile(module, '<test>', 'exec'), namespace)
599
+
600
+ assert 'add' in namespace
601
+ assert namespace['add'](2, 3) == 5
602
+
603
+ def test_execute_all_export(self):
604
+ """Test executing __all__ definition."""
605
+ all_node = _all(['foo', 'bar', 'baz'])
606
+ module = ast.Module(body=[all_node], type_ignores=[])
607
+ ast.fix_missing_locations(module)
608
+
609
+ namespace = {}
610
+ exec(compile(module, '<test>', 'exec'), namespace)
611
+
612
+ assert '__all__' in namespace
613
+ assert namespace['__all__'] == ('foo', 'bar', 'baz')
614
+
615
+ def test_execute_assignment(self):
616
+ """Test executing an assignment."""
617
+ assign = _assign(_name('result'), ast.Constant(value=42))
618
+ module = ast.Module(body=[assign], type_ignores=[])
619
+ ast.fix_missing_locations(module)
620
+
621
+ namespace = {}
622
+ exec(compile(module, '<test>', 'exec'), namespace)
623
+
624
+ assert 'result' in namespace
625
+ assert namespace['result'] == 42
626
+
627
+ def test_execute_function_call(self):
628
+ """Test executing a function call."""
629
+ # Create: result = max(5, 3)
630
+ call = _call(_name('max'), args=[ast.Constant(value=5), ast.Constant(value=3)])
631
+ assign = _assign(_name('result'), call)
632
+ module = ast.Module(body=[assign], type_ignores=[])
633
+ ast.fix_missing_locations(module)
634
+
635
+ namespace = {'max': max}
636
+ exec(compile(module, '<test>', 'exec'), namespace)
637
+
638
+ assert namespace['result'] == 5
639
+
640
+
641
+ class TestEdgeCases:
642
+ """Tests for edge cases and boundary conditions."""
643
+
644
+ def test_name_with_unicode(self):
645
+ """Test name with unicode characters (valid in Python 3)."""
646
+ result = _name('variable_α')
647
+ assert result.id == 'variable_α'
648
+
649
+ def test_empty_function_body_requires_pass(self):
650
+ """Test that empty function body with Pass statement works."""
651
+ func = _func('empty', [], [ast.Pass()])
652
+ assert len(func.body) == 1
653
+ assert isinstance(func.body[0], ast.Pass)
654
+
655
+ def test_union_with_single_type(self):
656
+ """Test Union with a single type returns the type directly."""
657
+ result = _union_expr([_name('str')])
658
+ # With a single type, _union_expr returns the type itself, not a BinOp
659
+ assert isinstance(result, ast.Name)
660
+ assert result.id == 'str'
661
+
662
+ def test_call_with_none_defaults(self):
663
+ """Test _call when explicitly passing None for defaults."""
664
+ func = _name('test')
665
+ result = _call(func, args=None, keywords=None)
666
+ assert result.args == []
667
+ assert result.keywords == []
668
+
669
+ def test_func_with_none_defaults(self):
670
+ """Test _func when explicitly passing None for optional params."""
671
+ result = _func('test', [], [ast.Pass()], returns=None, kwargs=None)
672
+ assert result.returns is None
673
+ assert result.args.kwarg is None
674
+
675
+ def test_nested_function_definitions(self):
676
+ """Test creating nested function definitions."""
677
+ inner = _func('inner', [], [ast.Return(value=ast.Constant(value=1))])
678
+ outer = _func('outer', [], [inner, ast.Return(value=_call(_name('inner')))])
679
+ assert len(outer.body) == 2
680
+ assert isinstance(outer.body[0], ast.FunctionDef)