|
13 | 13 | ExprCall, |
14 | 14 | ExprKeyword, |
15 | 15 | ExprName, |
| 16 | + ExprSubscript, |
| 17 | + ExprTuple, |
16 | 18 | Function, |
17 | 19 | Kind, |
18 | 20 | Module, |
@@ -80,8 +82,37 @@ def _process_attribute(attr: Attribute, cls: Class, *, processed: set[str]) -> N |
80 | 82 | if "class-attribute" in attr.labels and "instance-attribute" not in attr.labels: |
81 | 83 | return |
82 | 84 |
|
| 85 | + # Check if the annotation is Annotated[type, Field(...)] |
| 86 | + field_call = None |
| 87 | + if ( |
| 88 | + isinstance(attr.annotation, ExprSubscript) |
| 89 | + and attr.annotation.canonical_path == "typing.Annotated" |
| 90 | + and isinstance(attr.annotation.slice, ExprTuple) |
| 91 | + ): |
| 92 | + # Extract Field from Annotated's slice elements |
| 93 | + slice_elements = attr.annotation.slice.elements |
| 94 | + # Replace annotation with the actual type (first element) |
| 95 | + if len(slice_elements) > 0: |
| 96 | + attr.annotation = slice_elements[0] |
| 97 | + |
| 98 | + for element in slice_elements: |
| 99 | + if isinstance(element, ExprCall) and element.function.canonical_path == "pydantic.Field": |
| 100 | + field_call = element |
| 101 | + break |
| 102 | + |
83 | 103 | kwargs = {} |
84 | | - if isinstance(attr.value, ExprCall): |
| 104 | + if field_call is not None: |
| 105 | + # Extract kwargs from Field in Annotated |
| 106 | + kwargs = { |
| 107 | + argument.name: argument.value for argument in field_call.arguments if isinstance(argument, ExprKeyword) |
| 108 | + } |
| 109 | + if ( |
| 110 | + len(field_call.arguments) >= 1 |
| 111 | + and not isinstance(field_call.arguments[0], ExprKeyword) |
| 112 | + and field_call.arguments[0] != "..." # handle Field(...), i.e. no default |
| 113 | + ): |
| 114 | + kwargs["default"] = field_call.arguments[0] |
| 115 | + elif isinstance(attr.value, ExprCall): |
85 | 116 | kwargs = { |
86 | 117 | argument.name: argument.value for argument in attr.value.arguments if isinstance(argument, ExprKeyword) |
87 | 118 | } |
|
0 commit comments