55from typing import Generic
66from typing import Optional
77from typing import TypeVar
8+ from typing import Union
89
910from pydantic import Field
1011from pydantic import ValidationInfo
2627from .message import Message
2728from .message import _get_resource_class
2829
29- T = TypeVar ("T" , bound = Resource )
30+ T = TypeVar ("T" , bound = Resource [ Any ] )
3031
3132
3233class PatchOperation (ComplexAttribute ):
@@ -143,7 +144,7 @@ class PatchOp(Message, Generic[T]):
143144 - Using PatchOp without a type parameter raises TypeError
144145 """
145146
146- def __new__ (cls , * args : Any , ** kwargs : Any ):
147+ def __new__ (cls , * args : Any , ** kwargs : Any ) -> Self :
147148 """Create new PatchOp instance with type parameter validation.
148149
149150 Only handles the case of direct instantiation without type parameter (PatchOp()).
@@ -162,39 +163,48 @@ def __new__(cls, *args: Any, **kwargs: Any):
162163
163164 return super ().__new__ (cls )
164165
165- def __class_getitem__ (cls , item ):
166+ def __class_getitem__ (
167+ cls , typevar_values : Union [type [Resource [Any ]], tuple [type [Resource [Any ]], ...]]
168+ ) -> Any :
166169 """Validate type parameter when creating parameterized type.
167170
168171 Ensures the type parameter is a concrete Resource subclass (not Resource itself)
169172 or a TypeVar bound to Resource. Rejects invalid types (str, int, etc.) and Union types.
170173 """
171- # Allow TypeVar as type parameter
172- if isinstance (item , TypeVar ):
174+ if isinstance (typevar_values , TypeVar ):
173175 # Check if TypeVar is bound to Resource or its subclass
174- if item .__bound__ is not None and (
175- item .__bound__ is Resource
176- or (isclass (item .__bound__ ) and issubclass (item .__bound__ , Resource ))
176+ if typevar_values .__bound__ is not None and (
177+ typevar_values .__bound__ is Resource
178+ or (
179+ isclass (typevar_values .__bound__ )
180+ and issubclass (typevar_values .__bound__ , Resource )
181+ )
177182 ):
178- return super ().__class_getitem__ (item )
183+ return super ().__class_getitem__ (typevar_values )
179184 else :
180185 raise TypeError (
181- f"PatchOp TypeVar must be bound to Resource or its subclass, got { item } . "
186+ f"PatchOp TypeVar must be bound to Resource or its subclass, got { typevar_values } . "
182187 "Example: T = TypeVar('T', bound=Resource)"
183188 )
184189
185190 # Check if type parameter is a concrete Resource subclass (not Resource itself)
186- if item is Resource :
191+ if typevar_values is Resource :
187192 raise TypeError (
188193 "PatchOp requires a concrete Resource subclass, not Resource itself. "
189194 "Use PatchOp[User], PatchOp[Group], etc. instead of PatchOp[Resource]."
190195 )
191- if not (isclass (item ) and issubclass (item , Resource ) and item is not Resource ):
196+
197+ if not (
198+ isclass (typevar_values )
199+ and issubclass (typevar_values , Resource )
200+ and typevar_values is not Resource
201+ ):
192202 raise TypeError (
193- f"PatchOp type parameter must be a concrete Resource subclass or TypeVar, got { item } . "
203+ f"PatchOp type parameter must be a concrete Resource subclass or TypeVar, got { typevar_values } . "
194204 "Use PatchOp[User], PatchOp[Group], etc."
195205 )
196206
197- return super ().__class_getitem__ (item )
207+ return super ().__class_getitem__ (typevar_values )
198208
199209 schemas : Annotated [list [str ], Required .true ] = [
200210 "urn:ietf:params:scim:api:messages:2.0:PatchOp"
@@ -254,7 +264,9 @@ def patch(self, resource: T) -> bool:
254264
255265 return modified
256266
257- def _apply_operation (self , resource : Resource , operation : PatchOperation ) -> bool :
267+ def _apply_operation (
268+ self , resource : Resource [Any ], operation : PatchOperation
269+ ) -> bool :
258270 """Apply a single patch operation to a resource.
259271
260272 :return: :data:`True` if the resource was modified, else :data:`False`.
@@ -266,7 +278,9 @@ def _apply_operation(self, resource: Resource, operation: PatchOperation) -> boo
266278
267279 raise ValueError (Error .make_invalid_value_error ().detail )
268280
269- def _apply_add_replace (self , resource : Resource , operation : PatchOperation ) -> bool :
281+ def _apply_add_replace (
282+ self , resource : Resource [Any ], operation : PatchOperation
283+ ) -> bool :
270284 """Apply an add or replace operation."""
271285 # RFC 7644 Section 3.5.2.1: "If path is specified, add/replace at that path"
272286 if operation .path is not None :
@@ -280,7 +294,7 @@ def _apply_add_replace(self, resource: Resource, operation: PatchOperation) -> b
280294 # RFC 7644 Section 3.5.2.1: "If no path specified, add/replace at root level"
281295 return self ._apply_root_attributes (resource , operation .value )
282296
283- def _apply_remove (self , resource : Resource , operation : PatchOperation ) -> bool :
297+ def _apply_remove (self , resource : Resource [ Any ] , operation : PatchOperation ) -> bool :
284298 """Apply a remove operation."""
285299 # RFC 7644 Section 3.5.2.3: "Path is required for remove operations"
286300 if operation .path is None :
@@ -313,7 +327,7 @@ def _apply_root_attributes(self, resource: BaseModel, value: Any) -> bool:
313327 return modified
314328
315329 def _set_value_at_path (
316- self , resource : Resource , path : str , value : Any , is_add : bool
330+ self , resource : Resource [ Any ] , path : str , value : Any , is_add : bool
317331 ) -> bool :
318332 """Set a value at a specific path."""
319333 target , attr_path = _resolve_path_to_target (resource , path )
@@ -384,7 +398,11 @@ def _handle_multivalued_add(
384398 return self ._add_single_value (resource , field_name , current_list , value )
385399
386400 def _add_multiple_values (
387- self , resource : BaseModel , field_name : str , current_list : list , values : list
401+ self ,
402+ resource : BaseModel ,
403+ field_name : str ,
404+ current_list : list [Any ],
405+ values : list [Any ],
388406 ) -> bool :
389407 """Add multiple values to a multi-valued attribute."""
390408 new_values = []
@@ -400,7 +418,7 @@ def _add_multiple_values(
400418 return True
401419
402420 def _add_single_value (
403- self , resource : BaseModel , field_name : str , current_list : list , value : Any
421+ self , resource : BaseModel , field_name : str , current_list : list [ Any ] , value : Any
404422 ) -> bool :
405423 """Add a single value to a multi-valued attribute."""
406424 # RFC 7644 Section 3.5.2.1: "Do not add duplicate values"
@@ -411,7 +429,7 @@ def _add_single_value(
411429 setattr (resource , field_name , current_list )
412430 return True
413431
414- def _value_exists_in_list (self , current_list : list , new_value : Any ) -> bool :
432+ def _value_exists_in_list (self , current_list : list [ Any ] , new_value : Any ) -> bool :
415433 """Check if a value already exists in a list."""
416434 return any (self ._values_match (item , new_value ) for item in current_list )
417435
@@ -425,7 +443,7 @@ def _create_parent_object(self, resource: BaseModel, parent_field_name: str) ->
425443 setattr (resource , parent_field_name , parent_obj )
426444 return parent_obj
427445
428- def _remove_value_at_path (self , resource : Resource , path : str ) -> bool :
446+ def _remove_value_at_path (self , resource : Resource [ Any ] , path : str ) -> bool :
429447 """Remove a value at a specific path."""
430448 target , attr_path = _resolve_path_to_target (resource , path )
431449
@@ -451,7 +469,7 @@ def _remove_value_at_path(self, resource: Resource, path: str) -> bool:
451469 return self ._remove_value_at_path (parent_obj , sub_path )
452470
453471 def _remove_specific_value (
454- self , resource : Resource , path : str , value_to_remove : Any
472+ self , resource : Resource [ Any ] , path : str , value_to_remove : Any
455473 ) -> bool :
456474 """Remove a specific value from a multi-valued attribute."""
457475 target , attr_path = _resolve_path_to_target (resource , path )
@@ -486,7 +504,7 @@ def _remove_specific_value(
486504 def _values_match (self , value1 : Any , value2 : Any ) -> bool :
487505 """Check if two values match, converting BaseModel to dict for comparison."""
488506
489- def to_dict (value ) :
507+ def to_dict (value : Any ) -> dict [ str , Any ] :
490508 return value .model_dump () if isinstance (value , BaseModel ) else value
491509
492510 return to_dict (value1 ) == to_dict (value2 )
0 commit comments