@@ -54,7 +54,35 @@ class BaseModel(PydanticBaseModel):
5454
5555 @classmethod
5656 def get_field_annotation (cls , field_name : str , annotation_type : type ) -> Any :
57- """Return the annotation of type 'annotation_type' of the field 'field_name'."""
57+ """Return the annotation of type 'annotation_type' of the field 'field_name'.
58+
59+ This method extracts SCIM-specific annotations from a field's metadata,
60+ such as :class:`~scim2_models.Mutability`, :class:`~scim2_models.Required`,
61+ or :class:`~scim2_models.Returned` annotations.
62+
63+ :return: The annotation instance if found, otherwise the annotation type's default value
64+
65+ >>> from scim2_models.resources.user import User
66+ >>> from scim2_models.annotations import Mutability, Required
67+
68+ Get the mutability annotation of the 'id' field:
69+
70+ >>> mutability = User.get_field_annotation("id", Mutability)
71+ >>> mutability
72+ <Mutability.read_only: 'readOnly'>
73+
74+ Get the required annotation of the 'user_name' field:
75+
76+ >>> required = User.get_field_annotation("user_name", Required)
77+ >>> required
78+ <Required.true: True>
79+
80+ If no annotation is found, returns the default value:
81+
82+ >>> missing = User.get_field_annotation("display_name", Required)
83+ >>> missing
84+ <Required.false: False>
85+ """
5886 field_metadata = cls .model_fields [field_name ].metadata
5987
6088 default_value = getattr (annotation_type , "_default" , None )
@@ -71,8 +99,34 @@ def annotation_type_filter(item: Any) -> bool:
7199 def get_field_root_type (cls , attribute_name : str ) -> Optional [type ]:
72100 """Extract the root type from a model field.
73101
74- For example, return 'GroupMember' for
75- 'Optional[List[GroupMember]]'
102+ This method unwraps complex type annotations to find the underlying
103+ type, removing Optional and List wrappers to get to the actual type
104+ of the field's content.
105+
106+ :return: The root type of the field, or None if not found
107+
108+ >>> from scim2_models.resources.user import User
109+ >>> from scim2_models.resources.group import Group
110+
111+ Simple type:
112+
113+ >>> User.get_field_root_type("user_name")
114+ <class 'str'>
115+
116+ ``Optional`` type unwraps to the underlying type:
117+
118+ >>> User.get_field_root_type("display_name")
119+ <class 'str'>
120+
121+ ``List`` type unwraps to the element type:
122+
123+ >>> User.get_field_root_type("emails") # doctest: +ELLIPSIS
124+ <class 'scim2_models.resources.user.Email'>
125+
126+ ``Optional[List[T]]`` unwraps to ``T``:
127+
128+ >>> Group.get_field_root_type("members") # doctest: +ELLIPSIS
129+ <class 'scim2_models.resources.group.GroupMember'>
76130 """
77131 attribute_type = cls .model_fields [attribute_name ].annotation
78132
@@ -89,7 +143,20 @@ def get_field_root_type(cls, attribute_name: str) -> Optional[type]:
89143
90144 @classmethod
91145 def get_field_multiplicity (cls , attribute_name : str ) -> bool :
92- """Indicate whether a field holds multiple values."""
146+ """Indicate whether a field holds multiple values.
147+
148+ This method determines if a field is defined as a list type,
149+ which indicates it can contain multiple values. It handles
150+ Optional wrappers correctly.
151+
152+ :return: True if the field holds multiple values (is a list), False otherwise
153+
154+ >>> from scim2_models.resources.user import User
155+ >>> User.get_field_multiplicity("user_name")
156+ False
157+ >>> User.get_field_multiplicity("emails")
158+ True
159+ """
93160 attribute_type = cls .model_fields [attribute_name ].annotation
94161
95162 # extract 'x' from 'Optional[x]'
0 commit comments