scim2-models 0.2.9__py3-none-any.whl → 0.2.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,7 +26,9 @@ class Manager(ComplexAttribute):
26
26
 
27
27
 
28
28
  class EnterpriseUser(Extension):
29
- schemas: list[str] = ["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"]
29
+ schemas: Annotated[list[str], Required.true] = [
30
+ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
31
+ ]
30
32
 
31
33
  employee_number: Optional[str] = None
32
34
  """Numeric or alphanumeric identifier assigned to a person, typically based
@@ -8,6 +8,7 @@ from pydantic import Field
8
8
  from ..base import MultiValuedComplexAttribute
9
9
  from ..base import Mutability
10
10
  from ..base import Reference
11
+ from ..base import Required
11
12
  from .resource import Resource
12
13
 
13
14
 
@@ -31,7 +32,9 @@ class GroupMember(MultiValuedComplexAttribute):
31
32
 
32
33
 
33
34
  class Group(Resource):
34
- schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:Group"]
35
+ schemas: Annotated[list[str], Required.true] = [
36
+ "urn:ietf:params:scim:schemas:core:2.0:Group"
37
+ ]
35
38
 
36
39
  display_name: Optional[str] = None
37
40
  """A human-readable name for the Group."""
@@ -136,7 +136,7 @@ class ResourceMetaclass(BaseModelType):
136
136
 
137
137
 
138
138
  class Resource(BaseModel, Generic[AnyExtension], metaclass=ResourceMetaclass):
139
- schemas: list[str]
139
+ schemas: Annotated[list[str], Required.true]
140
140
  """The "schemas" attribute is a REQUIRED attribute and is an array of
141
141
  Strings containing URIs that are used to indicate the namespaces of the
142
142
  SCIM schemas that define the attributes present in the current JSON
@@ -229,7 +229,7 @@ class Resource(BaseModel, Generic[AnyExtension], metaclass=ResourceMetaclass):
229
229
  return Resource.get_by_schema(resource_types, schema, **kwargs)
230
230
 
231
231
  @field_serializer("schemas")
232
- def set_extension_schemas(self, schemas: list[str]):
232
+ def set_extension_schemas(self, schemas: Annotated[list[str], Required.true]):
233
233
  """Add model extension ids to the 'schemas' attribute."""
234
234
  extension_schemas = self.get_extension_models().keys()
235
235
  schemas = self.schemas + [
@@ -35,7 +35,9 @@ class SchemaExtension(ComplexAttribute):
35
35
 
36
36
 
37
37
  class ResourceType(Resource):
38
- schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"]
38
+ schemas: Annotated[list[str], Required.true] = [
39
+ "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
40
+ ]
39
41
 
40
42
  name: Annotated[Optional[str], Mutability.read_only, Required.true] = None
41
43
  """The resource type name.
@@ -9,7 +9,6 @@ from typing import Optional
9
9
  from typing import Union
10
10
  from typing import get_origin
11
11
 
12
- from pydantic import Base64Bytes
13
12
  from pydantic import Field
14
13
  from pydantic import create_model
15
14
  from pydantic import field_validator
@@ -30,6 +29,7 @@ from ..base import Uniqueness
30
29
  from ..base import URIReference
31
30
  from ..base import is_complex_attribute
32
31
  from ..constants import RESERVED_WORDS
32
+ from ..utils import Base64Bytes
33
33
  from ..utils import normalize_attribute_name
34
34
  from .resource import Extension
35
35
  from .resource import Resource
@@ -65,7 +65,7 @@ def make_python_model(
65
65
  if attr.name
66
66
  }
67
67
  pydantic_attributes["schemas"] = (
68
- Optional[list[str]],
68
+ Annotated[list[str], Required.true],
69
69
  Field(default=[obj.id]),
70
70
  )
71
71
 
@@ -240,7 +240,9 @@ class Attribute(ComplexAttribute):
240
240
 
241
241
 
242
242
  class Schema(Resource):
243
- schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:Schema"]
243
+ schemas: Annotated[list[str], Required.true] = [
244
+ "urn:ietf:params:scim:schemas:core:2.0:Schema"
245
+ ]
244
246
 
245
247
  id: Annotated[Optional[str], Mutability.read_only, Required.true] = None
246
248
  """The unique URI of the schema."""
@@ -264,3 +266,10 @@ class Schema(Resource):
264
266
  def urn_id(cls, value: str) -> str:
265
267
  """Ensure that schema ids are URI, as defined in RFC7643 §7."""
266
268
  return str(Url(value))
269
+
270
+ def get_attribute(self, attribute_name: str) -> Optional[Attribute]:
271
+ """Find an attribute by its name."""
272
+ for attribute in self.attributes or []:
273
+ if attribute.name == attribute_name:
274
+ return attribute
275
+ return None
@@ -94,7 +94,9 @@ class AuthenticationScheme(ComplexAttribute):
94
94
 
95
95
 
96
96
  class ServiceProviderConfig(Resource):
97
- schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"]
97
+ schemas: Annotated[list[str], Required.true] = [
98
+ "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
99
+ ]
98
100
 
99
101
  id: Annotated[
100
102
  Optional[str], Mutability.read_only, Returned.default, Uniqueness.global_
@@ -4,7 +4,6 @@ from typing import Literal
4
4
  from typing import Optional
5
5
  from typing import Union
6
6
 
7
- from pydantic import Base64Bytes
8
7
  from pydantic import EmailStr
9
8
  from pydantic import Field
10
9
 
@@ -17,6 +16,7 @@ from ..base import Reference
17
16
  from ..base import Required
18
17
  from ..base import Returned
19
18
  from ..base import Uniqueness
19
+ from ..utils import Base64Bytes
20
20
  from .resource import Resource
21
21
 
22
22
 
@@ -214,7 +214,9 @@ class X509Certificate(MultiValuedComplexAttribute):
214
214
 
215
215
 
216
216
  class User(Resource):
217
- schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:User"]
217
+ schemas: Annotated[list[str], Required.true] = [
218
+ "urn:ietf:params:scim:schemas:core:2.0:User"
219
+ ]
218
220
 
219
221
  user_name: Annotated[Optional[str], Uniqueness.server, Required.true] = None
220
222
  """Unique identifier for the User, typically used by the user to directly
@@ -7,6 +7,7 @@ from pydantic import Field
7
7
  from pydantic import PlainSerializer
8
8
 
9
9
  from ..base import ComplexAttribute
10
+ from ..base import Required
10
11
  from ..utils import int_to_str
11
12
  from .message import Message
12
13
 
@@ -53,7 +54,9 @@ class BulkRequest(Message):
53
54
  The models for Bulk operations are defined, but their behavior is not implemented nor tested yet.
54
55
  """
55
56
 
56
- schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"]
57
+ schemas: Annotated[list[str], Required.true] = [
58
+ "urn:ietf:params:scim:api:messages:2.0:BulkRequest"
59
+ ]
57
60
 
58
61
  fail_on_errors: Optional[int] = None
59
62
  """An integer specifying the number of errors that the service provider
@@ -74,7 +77,9 @@ class BulkResponse(Message):
74
77
  The models for Bulk operations are defined, but their behavior is not implemented nor tested yet.
75
78
  """
76
79
 
77
- schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:BulkResponse"]
80
+ schemas: Annotated[list[str], Required.true] = [
81
+ "urn:ietf:params:scim:api:messages:2.0:BulkResponse"
82
+ ]
78
83
 
79
84
  operations: Optional[list[BulkOperation]] = Field(
80
85
  None, serialization_alias="Operations"
@@ -3,6 +3,7 @@ from typing import Optional
3
3
 
4
4
  from pydantic import PlainSerializer
5
5
 
6
+ from ..base import Required
6
7
  from ..utils import int_to_str
7
8
  from .message import Message
8
9
 
@@ -10,7 +11,9 @@ from .message import Message
10
11
  class Error(Message):
11
12
  """Representation of SCIM API errors."""
12
13
 
13
- schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:Error"]
14
+ schemas: Annotated[list[str], Required.true] = [
15
+ "urn:ietf:params:scim:api:messages:2.0:Error"
16
+ ]
14
17
 
15
18
  status: Annotated[Optional[int], PlainSerializer(int_to_str)] = None
16
19
  """The HTTP status code (see Section 6 of [RFC7231]) expressed as a JSON
@@ -18,6 +18,7 @@ from typing_extensions import Self
18
18
  from ..base import BaseModel
19
19
  from ..base import BaseModelType
20
20
  from ..base import Context
21
+ from ..base import Required
21
22
  from ..rfc7643.resource import AnyResource
22
23
  from .message import Message
23
24
 
@@ -78,7 +79,9 @@ class ListResponseMetaclass(BaseModelType):
78
79
 
79
80
 
80
81
  class ListResponse(Message, Generic[AnyResource], metaclass=ListResponseMetaclass):
81
- schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:ListResponse"]
82
+ schemas: Annotated[list[str], Required.true] = [
83
+ "urn:ietf:params:scim:api:messages:2.0:ListResponse"
84
+ ]
82
85
 
83
86
  total_results: Optional[int] = None
84
87
  """The total number of results returned by the list or query operation."""
@@ -1,7 +1,10 @@
1
+ from typing import Annotated
2
+
1
3
  from ..base import BaseModel
4
+ from ..base import Required
2
5
 
3
6
 
4
7
  class Message(BaseModel):
5
8
  """SCIM protocol messages as defined by :rfc:`RFC7644 §3.1 <7644#section-3.1>`."""
6
9
 
7
- schemas: list[str]
10
+ schemas: Annotated[list[str], Required.true]
@@ -1,4 +1,5 @@
1
1
  from enum import Enum
2
+ from typing import Annotated
2
3
  from typing import Any
3
4
  from typing import Optional
4
5
 
@@ -6,6 +7,7 @@ from pydantic import Field
6
7
  from pydantic import field_validator
7
8
 
8
9
  from ..base import ComplexAttribute
10
+ from ..base import Required
9
11
  from .message import Message
10
12
 
11
13
 
@@ -57,7 +59,9 @@ class PatchOp(Message):
57
59
  The models for Patch operations are defined, but their behavior is not implemented nor tested yet.
58
60
  """
59
61
 
60
- schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]
62
+ schemas: Annotated[list[str], Required.true] = [
63
+ "urn:ietf:params:scim:api:messages:2.0:PatchOp"
64
+ ]
61
65
 
62
66
  operations: Optional[list[PatchOperation]] = Field(
63
67
  None, serialization_alias="Operations"
@@ -1,9 +1,11 @@
1
1
  from enum import Enum
2
+ from typing import Annotated
2
3
  from typing import Optional
3
4
 
4
5
  from pydantic import field_validator
5
6
  from pydantic import model_validator
6
7
 
8
+ from ..base import Required
7
9
  from .message import Message
8
10
 
9
11
 
@@ -13,7 +15,9 @@ class SearchRequest(Message):
13
15
  https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.3
14
16
  """
15
17
 
16
- schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:SearchRequest"]
18
+ schemas: Annotated[list[str], Required.true] = [
19
+ "urn:ietf:params:scim:api:messages:2.0:SearchRequest"
20
+ ]
17
21
 
18
22
  attributes: Optional[list[str]] = None
19
23
  """A multi-valued list of strings indicating the names of resource
@@ -72,3 +76,17 @@ class SearchRequest(Message):
72
76
  )
73
77
 
74
78
  return self
79
+
80
+ @property
81
+ def start_index_0(self):
82
+ """The 0 indexed start index."""
83
+ return self.start_index - 1 if self.start_index is not None else None
84
+
85
+ @property
86
+ def stop_index_0(self):
87
+ """The 0 indexed stop index."""
88
+ return (
89
+ self.start_index_0 + self.count
90
+ if self.start_index_0 is not None and self.count is not None
91
+ else None
92
+ )
scim2_models/utils.py CHANGED
@@ -1,8 +1,14 @@
1
+ import base64
1
2
  import re
3
+ from typing import Annotated
4
+ from typing import Literal
2
5
  from typing import Optional
3
6
  from typing import Union
4
7
 
8
+ from pydantic import EncodedBytes
9
+ from pydantic import EncoderProtocol
5
10
  from pydantic.alias_generators import to_snake
11
+ from pydantic_core import PydanticCustomError
6
12
 
7
13
  try:
8
14
  from types import UnionType # type: ignore
@@ -17,6 +23,57 @@ def int_to_str(status: Optional[int]) -> Optional[str]:
17
23
  return None if status is None else str(status)
18
24
 
19
25
 
26
+ # Copied from Pydantic 2.10 repository
27
+ class Base64Encoder(EncoderProtocol): # pragma: no cover
28
+ """Standard (non-URL-safe) Base64 encoder."""
29
+
30
+ @classmethod
31
+ def decode(cls, data: bytes) -> bytes:
32
+ """Decode the data from base64 encoded bytes to original bytes data.
33
+
34
+ Args:
35
+ data: The data to decode.
36
+
37
+ Returns:
38
+ The decoded data.
39
+
40
+ """
41
+ try:
42
+ return base64.b64decode(data)
43
+ except ValueError as e:
44
+ raise PydanticCustomError(
45
+ "base64_decode", "Base64 decoding error: '{error}'", {"error": str(e)}
46
+ ) from e
47
+
48
+ @classmethod
49
+ def encode(cls, value: bytes) -> bytes:
50
+ """Encode the data from bytes to a base64 encoded bytes.
51
+
52
+ Args:
53
+ value: The data to encode.
54
+
55
+ Returns:
56
+ The encoded data.
57
+
58
+ """
59
+ return base64.b64encode(value)
60
+
61
+ @classmethod
62
+ def get_json_format(cls) -> Literal["base64"]:
63
+ """Get the JSON format for the encoded data.
64
+
65
+ Returns:
66
+ The JSON format for the encoded data.
67
+
68
+ """
69
+ return "base64"
70
+
71
+
72
+ # Compatibility with Pydantic <2.10
73
+ # https://pydantic.dev/articles/pydantic-v2-10-release#use-b64decode-and-b64encode-for-base64bytes-and-base64str-types
74
+ Base64Bytes = Annotated[bytes, EncodedBytes(encoder=Base64Encoder)]
75
+
76
+
20
77
  def to_camel(string: str) -> str:
21
78
  """Transform strings to camelCase.
22
79
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: scim2-models
3
- Version: 0.2.9
3
+ Version: 0.2.11
4
4
  Summary: SCIM2 models serialization and validation with pydantic
5
5
  Project-URL: documentation, https://scim2-models.readthedocs.io
6
6
  Project-URL: repository, https://github.com/python-scim/scim2-models
@@ -0,0 +1,24 @@
1
+ scim2_models/__init__.py,sha256=Y06vTA_51lXfv9zk_dTzyIIqEo1H2bcencvMM5KAwn8,3063
2
+ scim2_models/base.py,sha256=_RTwLpwyiyKTc7VYuB854yuPhr8gakahmrOFTKTxHFI,32268
3
+ scim2_models/constants.py,sha256=SuMGFtVNletdV5ZJRUcIq7o2CqZCRvOurnIdLE_cakE,540
4
+ scim2_models/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ scim2_models/utils.py,sha256=MzZz212-lkVWgXcpXvNwoi_u28wBTpkTwPrfYC5v92A,2771
6
+ scim2_models/rfc7643/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ scim2_models/rfc7643/enterprise_user.py,sha256=EaxdHH2dcBrwWwGpaZC6iZ9dbcaVN1NpoRLAdkTYssQ,1781
8
+ scim2_models/rfc7643/group.py,sha256=CBmSnlzd4JktCpOmg4XkfJJUd8oD6KbYFRTt0dSdixE,1302
9
+ scim2_models/rfc7643/resource.py,sha256=zMyk02Traoq6XNBcS_fSnXIOadZwxTdA9CV4vLuy38k,12648
10
+ scim2_models/rfc7643/resource_type.py,sha256=p8IKV5IakPMBX6d2Fv2MFqaQL5iTw152vmozVVSC8gU,3196
11
+ scim2_models/rfc7643/schema.py,sha256=tYKxQP791Vm58xjgaIyQmRF8WP04EPkJrP_OtzusHXA,9642
12
+ scim2_models/rfc7643/service_provider_config.py,sha256=deMNCXlqiNzuLcVRN9mdHiTUxhczDnvi-oO6k-Anj8U,5402
13
+ scim2_models/rfc7643/user.py,sha256=-wlO2IC0MxFWUJpaddEwAG5LsEcORnbHfkRwGG3fVSk,9942
14
+ scim2_models/rfc7644/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ scim2_models/rfc7644/bulk.py,sha256=I6S40kyJPwDQwPFi668wFFDVTST7z6QTe9HTL5QUViI,2577
16
+ scim2_models/rfc7644/error.py,sha256=l4-vtGQYm5u13-ParhbHSeqXEil0E09QXSO9twAT3SU,6185
17
+ scim2_models/rfc7644/list_response.py,sha256=Zh8Od2iOd3kinwLP-PHQMEbfM1tdl111Qap9DR8DHc4,4251
18
+ scim2_models/rfc7644/message.py,sha256=F4kPqbHAka3-wZzap9a45noQZw-o1vznTJypNABBF7w,253
19
+ scim2_models/rfc7644/patch_op.py,sha256=OE-ixDanTkY5zQP7EK7OAp88uE_fMk03mqmaZHxgJ-g,2210
20
+ scim2_models/rfc7644/search_request.py,sha256=X3DZQOtLu8EWYvkNfNOcFlGtx3TGNEkpL59dAdut7Sc,3006
21
+ scim2_models-0.2.11.dist-info/METADATA,sha256=mfd5sP_S-WqTDypsNmGp8e0GvJLbzNM9S8Dpi4hL4_g,16267
22
+ scim2_models-0.2.11.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
23
+ scim2_models-0.2.11.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
24
+ scim2_models-0.2.11.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- scim2_models/__init__.py,sha256=Y06vTA_51lXfv9zk_dTzyIIqEo1H2bcencvMM5KAwn8,3063
2
- scim2_models/base.py,sha256=_RTwLpwyiyKTc7VYuB854yuPhr8gakahmrOFTKTxHFI,32268
3
- scim2_models/constants.py,sha256=SuMGFtVNletdV5ZJRUcIq7o2CqZCRvOurnIdLE_cakE,540
4
- scim2_models/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- scim2_models/utils.py,sha256=2QptWn3e74nVmemMEPsm80m8bJ9IL7PUgzOTRDWoq10,1216
6
- scim2_models/rfc7643/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- scim2_models/rfc7643/enterprise_user.py,sha256=trwVpCZT0FXGFFyo57L4ME7h0gnFstYdWAm6mqFRfOM,1741
8
- scim2_models/rfc7643/group.py,sha256=5EmJsjlWdj_vnrtn4k3iwrPq3vVG0yntpHVRT7hhq5Q,1234
9
- scim2_models/rfc7643/resource.py,sha256=FlrIFPq7kZiOesBtzrM_844pH5OWiCb63VEXnVb9NuI,12596
10
- scim2_models/rfc7643/resource_type.py,sha256=dD6H1qeUM7HQMMRFHqnyruT7dzkIDLUBPXV-mPeZOoc,3156
11
- scim2_models/rfc7643/schema.py,sha256=RJiCfx4lseqIXESnqK0PqrBcjPlOgiKgsAeacPYvmjM,9318
12
- scim2_models/rfc7643/service_provider_config.py,sha256=uHSVDkLfjzGmjQJQ-8B_s34wgOvG_wd5tE37fJEeSTI,5362
13
- scim2_models/rfc7643/user.py,sha256=ctPO2ujeXI-p8yJWkFEaHKYrYnOWq4RGgMV6hY3nNB4,9903
14
- scim2_models/rfc7644/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- scim2_models/rfc7644/bulk.py,sha256=wgLBhV3AbD6z7KVP9SpvP2_sgGSXkHJ4kk9XvM40Ikg,2469
16
- scim2_models/rfc7644/error.py,sha256=_zYjAn7kK8hA4HAX6n9kcqCOMxRiPibzUuun7wah5xo,6117
17
- scim2_models/rfc7644/list_response.py,sha256=mewgLvo8UKqvb4DVoSHX16Hj5P4ijUHEczFluxV-GWU,4183
18
- scim2_models/rfc7644/message.py,sha256=Kie5G8XTKiJYAQ8WzPE17UWaMhtNDW6OYdvCkaq14XE,169
19
- scim2_models/rfc7644/patch_op.py,sha256=gxydWwvEFJwiHwXuHxf4OSnL7b-oEsU6vaFvK9mSB0A,2113
20
- scim2_models/rfc7644/search_request.py,sha256=bO_jxTNuIFo98zPjiM1eH3XnA_UXFiSQnoVISjV0FYw,2497
21
- scim2_models-0.2.9.dist-info/METADATA,sha256=gu79yS0dSzmzSbgYAmK9v66SE8Ve8fvAReNBr2OqhJw,16266
22
- scim2_models-0.2.9.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
23
- scim2_models-0.2.9.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
24
- scim2_models-0.2.9.dist-info/RECORD,,