5
5
from typing import Generic
6
6
from typing import Optional
7
7
from typing import TypeVar
8
+ from typing import Union
8
9
9
10
from pydantic import Field
10
11
from pydantic import ValidationInfo
26
27
from .message import Message
27
28
from .message import _get_resource_class
28
29
29
- T = TypeVar ("T" , bound = Resource )
30
+ T = TypeVar ("T" , bound = Resource [ Any ] )
30
31
31
32
32
33
class PatchOperation (ComplexAttribute ):
@@ -143,7 +144,7 @@ class PatchOp(Message, Generic[T]):
143
144
- Using PatchOp without a type parameter raises TypeError
144
145
"""
145
146
146
- def __new__ (cls , * args : Any , ** kwargs : Any ):
147
+ def __new__ (cls , * args : Any , ** kwargs : Any ) -> Self :
147
148
"""Create new PatchOp instance with type parameter validation.
148
149
149
150
Only handles the case of direct instantiation without type parameter (PatchOp()).
@@ -162,39 +163,48 @@ def __new__(cls, *args: Any, **kwargs: Any):
162
163
163
164
return super ().__new__ (cls )
164
165
165
- def __class_getitem__ (cls , item ):
166
+ def __class_getitem__ (
167
+ cls , typevar_values : Union [type [Resource [Any ]], tuple [type [Resource [Any ]], ...]]
168
+ ) -> Any :
166
169
"""Validate type parameter when creating parameterized type.
167
170
168
171
Ensures the type parameter is a concrete Resource subclass (not Resource itself)
169
172
or a TypeVar bound to Resource. Rejects invalid types (str, int, etc.) and Union types.
170
173
"""
171
- # Allow TypeVar as type parameter
172
- if isinstance (item , TypeVar ):
174
+ if isinstance (typevar_values , TypeVar ):
173
175
# 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
+ )
177
182
):
178
- return super ().__class_getitem__ (item )
183
+ return super ().__class_getitem__ (typevar_values )
179
184
else :
180
185
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 } . "
182
187
"Example: T = TypeVar('T', bound=Resource)"
183
188
)
184
189
185
190
# Check if type parameter is a concrete Resource subclass (not Resource itself)
186
- if item is Resource :
191
+ if typevar_values is Resource :
187
192
raise TypeError (
188
193
"PatchOp requires a concrete Resource subclass, not Resource itself. "
189
194
"Use PatchOp[User], PatchOp[Group], etc. instead of PatchOp[Resource]."
190
195
)
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
+ ):
192
202
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 } . "
194
204
"Use PatchOp[User], PatchOp[Group], etc."
195
205
)
196
206
197
- return super ().__class_getitem__ (item )
207
+ return super ().__class_getitem__ (typevar_values )
198
208
199
209
schemas : Annotated [list [str ], Required .true ] = [
200
210
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
@@ -254,7 +264,9 @@ def patch(self, resource: T) -> bool:
254
264
255
265
return modified
256
266
257
- def _apply_operation (self , resource : Resource , operation : PatchOperation ) -> bool :
267
+ def _apply_operation (
268
+ self , resource : Resource [Any ], operation : PatchOperation
269
+ ) -> bool :
258
270
"""Apply a single patch operation to a resource.
259
271
260
272
:return: :data:`True` if the resource was modified, else :data:`False`.
@@ -266,7 +278,9 @@ def _apply_operation(self, resource: Resource, operation: PatchOperation) -> boo
266
278
267
279
raise ValueError (Error .make_invalid_value_error ().detail )
268
280
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 :
270
284
"""Apply an add or replace operation."""
271
285
# RFC 7644 Section 3.5.2.1: "If path is specified, add/replace at that path"
272
286
if operation .path is not None :
@@ -280,7 +294,7 @@ def _apply_add_replace(self, resource: Resource, operation: PatchOperation) -> b
280
294
# RFC 7644 Section 3.5.2.1: "If no path specified, add/replace at root level"
281
295
return self ._apply_root_attributes (resource , operation .value )
282
296
283
- def _apply_remove (self , resource : Resource , operation : PatchOperation ) -> bool :
297
+ def _apply_remove (self , resource : Resource [ Any ] , operation : PatchOperation ) -> bool :
284
298
"""Apply a remove operation."""
285
299
# RFC 7644 Section 3.5.2.3: "Path is required for remove operations"
286
300
if operation .path is None :
@@ -313,7 +327,7 @@ def _apply_root_attributes(self, resource: BaseModel, value: Any) -> bool:
313
327
return modified
314
328
315
329
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
317
331
) -> bool :
318
332
"""Set a value at a specific path."""
319
333
target , attr_path = _resolve_path_to_target (resource , path )
@@ -384,7 +398,11 @@ def _handle_multivalued_add(
384
398
return self ._add_single_value (resource , field_name , current_list , value )
385
399
386
400
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 ],
388
406
) -> bool :
389
407
"""Add multiple values to a multi-valued attribute."""
390
408
new_values = []
@@ -400,7 +418,7 @@ def _add_multiple_values(
400
418
return True
401
419
402
420
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
404
422
) -> bool :
405
423
"""Add a single value to a multi-valued attribute."""
406
424
# RFC 7644 Section 3.5.2.1: "Do not add duplicate values"
@@ -411,7 +429,7 @@ def _add_single_value(
411
429
setattr (resource , field_name , current_list )
412
430
return True
413
431
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 :
415
433
"""Check if a value already exists in a list."""
416
434
return any (self ._values_match (item , new_value ) for item in current_list )
417
435
@@ -425,7 +443,7 @@ def _create_parent_object(self, resource: BaseModel, parent_field_name: str) ->
425
443
setattr (resource , parent_field_name , parent_obj )
426
444
return parent_obj
427
445
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 :
429
447
"""Remove a value at a specific path."""
430
448
target , attr_path = _resolve_path_to_target (resource , path )
431
449
@@ -451,7 +469,7 @@ def _remove_value_at_path(self, resource: Resource, path: str) -> bool:
451
469
return self ._remove_value_at_path (parent_obj , sub_path )
452
470
453
471
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
455
473
) -> bool :
456
474
"""Remove a specific value from a multi-valued attribute."""
457
475
target , attr_path = _resolve_path_to_target (resource , path )
@@ -486,7 +504,7 @@ def _remove_specific_value(
486
504
def _values_match (self , value1 : Any , value2 : Any ) -> bool :
487
505
"""Check if two values match, converting BaseModel to dict for comparison."""
488
506
489
- def to_dict (value ) :
507
+ def to_dict (value : Any ) -> dict [ str , Any ] :
490
508
return value .model_dump () if isinstance (value , BaseModel ) else value
491
509
492
510
return to_dict (value1 ) == to_dict (value2 )
0 commit comments