2424from ..attributes import MultiValuedComplexAttribute
2525from ..attributes import is_complex_attribute
2626from ..base import BaseModel
27- from ..base import BaseModelType
2827from ..context import Context
2928from ..reference import Reference
3029from ..scim_object import ScimObject
@@ -104,6 +103,8 @@ def from_schema(cls, schema: "Schema") -> type["Extension"]:
104103
105104AnyExtension = TypeVar ("AnyExtension" , bound = "Extension" )
106105
106+ _PARAMETERIZED_CLASSES : dict [tuple [type , tuple ], type ] = {}
107+
107108
108109def extension_serializer (
109110 value : Any , handler : SerializerFunctionWrapHandler , info : SerializationInfo
@@ -122,33 +123,7 @@ def extension_serializer(
122123 return result or None
123124
124125
125- class ResourceMetaclass (BaseModelType ):
126- def __new__ (cls , name : str , bases : tuple , attrs : dict , ** kwargs : Any ) -> type :
127- """Dynamically add a field for each extension."""
128- if "__pydantic_generic_metadata__" in kwargs :
129- extensions = kwargs ["__pydantic_generic_metadata__" ]["args" ][0 ]
130- extensions = (
131- get_args (extensions )
132- if get_origin (extensions ) in UNION_TYPES
133- else [extensions ]
134- )
135- for extension in extensions :
136- schema = extension .model_fields ["schemas" ].default [0 ]
137- attrs .setdefault ("__annotations__" , {})[extension .__name__ ] = Annotated [
138- Optional [extension ],
139- WrapSerializer (extension_serializer ),
140- ]
141- attrs [extension .__name__ ] = Field (
142- None ,
143- serialization_alias = schema ,
144- validation_alias = normalize_attribute_name (schema ),
145- )
146-
147- klass = super ().__new__ (cls , name , bases , attrs , ** kwargs )
148- return klass
149-
150-
151- class Resource (ScimObject , Generic [AnyExtension ], metaclass = ResourceMetaclass ):
126+ class Resource (ScimObject , Generic [AnyExtension ]):
152127 # Common attributes as defined by
153128 # https://www.rfc-editor.org/rfc/rfc7643#section-3.1
154129
@@ -171,6 +146,71 @@ class Resource(ScimObject, Generic[AnyExtension], metaclass=ResourceMetaclass):
171146 meta : Annotated [Optional [Meta ], Mutability .read_only , Returned .default ] = None
172147 """A complex attribute containing resource metadata."""
173148
149+ @classmethod
150+ def __class_getitem__ (cls , item : Any ) -> type ["Resource" ]:
151+ """Create a Resource class with extension fields dynamically added."""
152+ if hasattr (cls , "__scim_extension_metadata__" ):
153+ return cls
154+
155+ extensions = get_args (item ) if get_origin (item ) in UNION_TYPES else [item ]
156+
157+ # Skip TypeVar parameters (used for generic class definitions)
158+ valid_extensions = [
159+ extension for extension in extensions if not isinstance (extension , TypeVar )
160+ ]
161+
162+ if not valid_extensions :
163+ return cls
164+
165+ cache_key = (cls , tuple (valid_extensions ))
166+ if cache_key in _PARAMETERIZED_CLASSES :
167+ return _PARAMETERIZED_CLASSES [cache_key ]
168+
169+ for extension in valid_extensions :
170+ if not (isinstance (extension , type ) and issubclass (extension , Extension )):
171+ raise TypeError (f"{ extension } is not a valid Extension type" )
172+
173+ class_name = (
174+ f"{ cls .__name__ } [{ ', ' .join (ext .__name__ for ext in valid_extensions )} ]"
175+ )
176+
177+ class_attrs = {
178+ "__scim_extension_metadata__" : {
179+ "args" : (item ,),
180+ "origin" : cls ,
181+ "extensions" : valid_extensions ,
182+ }
183+ }
184+
185+ for extension in valid_extensions :
186+ schema = extension .model_fields ["schemas" ].default [0 ]
187+ class_attrs [extension .__name__ ] = Field (
188+ None ,
189+ serialization_alias = schema ,
190+ validation_alias = normalize_attribute_name (schema ),
191+ )
192+
193+ new_annotations = {
194+ extension .__name__ : Annotated [
195+ Optional [extension ],
196+ WrapSerializer (extension_serializer ),
197+ ]
198+ for extension in valid_extensions
199+ }
200+
201+ new_class = type (
202+ class_name ,
203+ (cls ,),
204+ {
205+ "__annotations__" : new_annotations ,
206+ ** class_attrs ,
207+ },
208+ )
209+
210+ _PARAMETERIZED_CLASSES [cache_key ] = new_class
211+
212+ return new_class
213+
174214 def __getitem__ (self , item : Any ) -> Optional [Extension ]:
175215 if not isinstance (item , type ) or not issubclass (item , Extension ):
176216 raise KeyError (f"{ item } is not a valid extension type" )
@@ -186,13 +226,7 @@ def __setitem__(self, item: Any, value: "Resource") -> None:
186226 @classmethod
187227 def get_extension_models (cls ) -> dict [str , type [Extension ]]:
188228 """Return extension a dict associating extension models with their schemas."""
189- generic_args : Any = cls .__pydantic_generic_metadata__ .get ("args" , [])
190- extension_models = (
191- get_args (generic_args [0 ])
192- if len (generic_args ) == 1 and get_origin (generic_args [0 ]) in UNION_TYPES
193- else generic_args
194- )
195-
229+ extension_models = getattr (cls , "__scim_extension_metadata__" , [])
196230 by_schema = {
197231 ext .model_fields ["schemas" ].default [0 ]: ext for ext in extension_models
198232 }
0 commit comments