11"""Base SCIM object classes with schema identification."""
22
3+ from typing import TYPE_CHECKING
34from typing import Annotated
45from typing import Any
56from typing import Optional
67
78from .annotations import Required
89from .base import BaseModel
9- from .base import validate_attribute_urn
1010from .context import Context
11+ from .utils import normalize_attribute_name
12+
13+ if TYPE_CHECKING :
14+ from .rfc7643 .resource import Resource
15+
16+
17+ def validate_model_attribute (model : type ["BaseModel" ], attribute_base : str ) -> None :
18+ """Validate that an attribute name or a sub-attribute path exist for a given model."""
19+ attribute_name , * sub_attribute_blocks = attribute_base .split ("." )
20+ sub_attribute_base = "." .join (sub_attribute_blocks )
21+
22+ aliases = {field .validation_alias for field in model .model_fields .values ()}
23+
24+ if normalize_attribute_name (attribute_name ) not in aliases :
25+ raise ValueError (
26+ f"Model '{ model .__name__ } ' has no attribute named '{ attribute_name } '"
27+ )
28+
29+ if sub_attribute_base :
30+ attribute_type = model .get_field_root_type (attribute_name )
31+
32+ if not attribute_type or not issubclass (attribute_type , BaseModel ):
33+ raise ValueError (
34+ f"Attribute '{ attribute_name } ' is not a complex attribute, and cannot have a '{ sub_attribute_base } ' sub-attribute"
35+ )
36+
37+ validate_model_attribute (attribute_type , sub_attribute_base )
38+
39+
40+ def extract_schema_and_attribute_base (attribute_urn : str ) -> tuple [str , str ]:
41+ """Extract the schema urn part and the attribute name part from attribute name.
42+
43+ As defined in :rfc:`RFC7644 §3.10 <7644#section-3.10>`.
44+ """
45+ * urn_blocks , attribute_base = attribute_urn .split (":" )
46+ schema = ":" .join (urn_blocks )
47+ return schema , attribute_base
48+
49+
50+ def validate_attribute_urn (
51+ attribute_name : str ,
52+ default_resource : Optional [type ["Resource" ]] = None ,
53+ resource_types : Optional [list [type ["Resource" ]]] = None ,
54+ ) -> str :
55+ """Validate that an attribute urn is valid or not.
56+
57+ :param attribute_name: The attribute urn to check.
58+ :default_resource: The default resource if `attribute_name` is not an absolute urn.
59+ :resource_types: The available resources in which to look for the attribute.
60+ :return: The normalized attribute URN.
61+ """
62+ from .rfc7643 .resource import Resource
63+
64+ if not resource_types :
65+ resource_types = []
66+
67+ if default_resource and default_resource not in resource_types :
68+ resource_types .append (default_resource )
69+
70+ default_schema = (
71+ default_resource .model_fields ["schemas" ].default [0 ]
72+ if default_resource
73+ else None
74+ )
75+
76+ schema : Optional [Any ]
77+ schema , attribute_base = extract_schema_and_attribute_base (attribute_name )
78+ if not schema :
79+ schema = default_schema
80+
81+ if not schema :
82+ raise ValueError ("No default schema and relative URN" )
83+
84+ resource = Resource .get_by_schema (resource_types , schema )
85+ if not resource :
86+ raise ValueError (f"No resource matching schema '{ schema } '" )
87+
88+ validate_model_attribute (resource , attribute_base )
89+
90+ return f"{ schema } :{ attribute_base } "
1191
1292
1393class ScimObject (BaseModel ):
@@ -17,46 +97,19 @@ class ScimObject(BaseModel):
1797 SCIM schemas that define the attributes present in the current JSON
1898 structure."""
1999
20- def _prepare_model_dump (
21- self ,
22- scim_ctx : Optional [Context ] = Context .DEFAULT ,
23- attributes : Optional [list [str ]] = None ,
24- excluded_attributes : Optional [list [str ]] = None ,
25- ** kwargs : Any ,
26- ) -> dict [str , Any ]:
27- kwargs = super ()._prepare_model_dump (scim_ctx , ** kwargs )
28- kwargs ["context" ]["scim_attributes" ] = [
29- validate_attribute_urn (attribute , self .__class__ )
30- for attribute in (attributes or [])
31- ]
32- kwargs ["context" ]["scim_excluded_attributes" ] = [
33- validate_attribute_urn (attribute , self .__class__ )
34- for attribute in (excluded_attributes or [])
35- ]
36- return kwargs
37-
38100 def model_dump (
39101 self ,
40102 * args : Any ,
41103 scim_ctx : Optional [Context ] = Context .DEFAULT ,
42- attributes : Optional [list [str ]] = None ,
43- excluded_attributes : Optional [list [str ]] = None ,
44104 ** kwargs : Any ,
45105 ) -> dict :
46106 """Create a model representation that can be included in SCIM messages by using Pydantic :code:`BaseModel.model_dump`.
47107
48108 :param scim_ctx: If a SCIM context is passed, some default values of
49109 Pydantic :code:`BaseModel.model_dump` are tuned to generate valid SCIM
50110 messages. Pass :data:`None` to get the default Pydantic behavior.
51- :param attributes: A multi-valued list of strings indicating the names of resource
52- attributes to return in the response, overriding the set of attributes that
53- would be returned by default.
54- :param excluded_attributes: A multi-valued list of strings indicating the names of resource
55- attributes to be removed from the default set of attributes to return.
56111 """
57- dump_kwargs = self ._prepare_model_dump (
58- scim_ctx , attributes , excluded_attributes , ** kwargs
59- )
112+ dump_kwargs = self ._prepare_model_dump (scim_ctx , ** kwargs )
60113 if scim_ctx :
61114 dump_kwargs .setdefault ("mode" , "json" )
62115 return super (BaseModel , self ).model_dump (* args , ** dump_kwargs )
@@ -65,22 +118,13 @@ def model_dump_json(
65118 self ,
66119 * args : Any ,
67120 scim_ctx : Optional [Context ] = Context .DEFAULT ,
68- attributes : Optional [list [str ]] = None ,
69- excluded_attributes : Optional [list [str ]] = None ,
70121 ** kwargs : Any ,
71122 ) -> str :
72123 """Create a JSON model representation that can be included in SCIM messages by using Pydantic :code:`BaseModel.model_dump_json`.
73124
74125 :param scim_ctx: If a SCIM context is passed, some default values of
75126 Pydantic :code:`BaseModel.model_dump` are tuned to generate valid SCIM
76127 messages. Pass :data:`None` to get the default Pydantic behavior.
77- :param attributes: A multi-valued list of strings indicating the names of resource
78- attributes to return in the response, overriding the set of attributes that
79- would be returned by default.
80- :param excluded_attributes: A multi-valued list of strings indicating the names of resource
81- attributes to be removed from the default set of attributes to return.
82128 """
83- dump_kwargs = self ._prepare_model_dump (
84- scim_ctx , attributes , excluded_attributes , ** kwargs
85- )
129+ dump_kwargs = self ._prepare_model_dump (scim_ctx , ** kwargs )
86130 return super (BaseModel , self ).model_dump_json (* args , ** dump_kwargs )
0 commit comments