scim2-models 0.5.2__py3-none-any.whl → 0.6.1__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.
- scim2_models/__init__.py +32 -0
- scim2_models/attributes.py +5 -1
- scim2_models/base.py +52 -8
- scim2_models/exceptions.py +265 -0
- scim2_models/messages/bulk.py +3 -7
- scim2_models/messages/error.py +184 -15
- scim2_models/messages/list_response.py +2 -5
- scim2_models/messages/message.py +4 -4
- scim2_models/messages/patch_op.py +127 -261
- scim2_models/messages/search_request.py +9 -57
- scim2_models/path.py +733 -0
- scim2_models/reference.py +139 -48
- scim2_models/resources/enterprise_user.py +10 -8
- scim2_models/resources/group.py +9 -7
- scim2_models/resources/resource.py +116 -29
- scim2_models/resources/resource_type.py +15 -14
- scim2_models/resources/schema.py +15 -20
- scim2_models/resources/service_provider_config.py +10 -13
- scim2_models/resources/user.py +13 -10
- scim2_models/scim_object.py +84 -1
- scim2_models/utils.py +0 -140
- {scim2_models-0.5.2.dist-info → scim2_models-0.6.1.dist-info}/METADATA +2 -2
- scim2_models-0.6.1.dist-info/RECORD +30 -0
- scim2_models/urn.py +0 -126
- scim2_models-0.5.2.dist-info/RECORD +0 -29
- {scim2_models-0.5.2.dist-info → scim2_models-0.6.1.dist-info}/WHEEL +0 -0
scim2_models/__init__.py
CHANGED
|
@@ -7,6 +7,18 @@ from .attributes import ComplexAttribute
|
|
|
7
7
|
from .attributes import MultiValuedComplexAttribute
|
|
8
8
|
from .base import BaseModel
|
|
9
9
|
from .context import Context
|
|
10
|
+
from .exceptions import InvalidFilterException
|
|
11
|
+
from .exceptions import InvalidPathException
|
|
12
|
+
from .exceptions import InvalidSyntaxException
|
|
13
|
+
from .exceptions import InvalidValueException
|
|
14
|
+
from .exceptions import InvalidVersionException
|
|
15
|
+
from .exceptions import MutabilityException
|
|
16
|
+
from .exceptions import NoTargetException
|
|
17
|
+
from .exceptions import PathNotFoundException
|
|
18
|
+
from .exceptions import SCIMException
|
|
19
|
+
from .exceptions import SensitiveException
|
|
20
|
+
from .exceptions import TooManyException
|
|
21
|
+
from .exceptions import UniquenessException
|
|
10
22
|
from .messages.bulk import BulkOperation
|
|
11
23
|
from .messages.bulk import BulkRequest
|
|
12
24
|
from .messages.bulk import BulkResponse
|
|
@@ -16,6 +28,10 @@ from .messages.message import Message
|
|
|
16
28
|
from .messages.patch_op import PatchOp
|
|
17
29
|
from .messages.patch_op import PatchOperation
|
|
18
30
|
from .messages.search_request import SearchRequest
|
|
31
|
+
from .path import URN
|
|
32
|
+
from .path import Path
|
|
33
|
+
from .reference import URI
|
|
34
|
+
from .reference import External
|
|
19
35
|
from .reference import ExternalReference
|
|
20
36
|
from .reference import Reference
|
|
21
37
|
from .reference import URIReference
|
|
@@ -72,6 +88,7 @@ __all__ = [
|
|
|
72
88
|
"EnterpriseUser",
|
|
73
89
|
"Entitlement",
|
|
74
90
|
"Error",
|
|
91
|
+
"External",
|
|
75
92
|
"ExternalReference",
|
|
76
93
|
"Extension",
|
|
77
94
|
"Filter",
|
|
@@ -79,13 +96,22 @@ __all__ = [
|
|
|
79
96
|
"GroupMember",
|
|
80
97
|
"GroupMembership",
|
|
81
98
|
"Im",
|
|
99
|
+
"InvalidFilterException",
|
|
100
|
+
"InvalidPathException",
|
|
101
|
+
"InvalidSyntaxException",
|
|
102
|
+
"InvalidValueException",
|
|
103
|
+
"InvalidVersionException",
|
|
82
104
|
"ListResponse",
|
|
83
105
|
"Manager",
|
|
84
106
|
"Message",
|
|
85
107
|
"Meta",
|
|
86
108
|
"Mutability",
|
|
109
|
+
"MutabilityException",
|
|
87
110
|
"MultiValuedComplexAttribute",
|
|
88
111
|
"Name",
|
|
112
|
+
"NoTargetException",
|
|
113
|
+
"Path",
|
|
114
|
+
"PathNotFoundException",
|
|
89
115
|
"Patch",
|
|
90
116
|
"PatchOp",
|
|
91
117
|
"PatchOperation",
|
|
@@ -97,13 +123,19 @@ __all__ = [
|
|
|
97
123
|
"ResourceType",
|
|
98
124
|
"Returned",
|
|
99
125
|
"Role",
|
|
126
|
+
"SCIMException",
|
|
100
127
|
"Schema",
|
|
101
128
|
"SchemaExtension",
|
|
102
129
|
"SearchRequest",
|
|
130
|
+
"SensitiveException",
|
|
103
131
|
"ServiceProviderConfig",
|
|
104
132
|
"Sort",
|
|
133
|
+
"TooManyException",
|
|
105
134
|
"Uniqueness",
|
|
135
|
+
"UniquenessException",
|
|
136
|
+
"URI",
|
|
106
137
|
"URIReference",
|
|
138
|
+
"URN",
|
|
107
139
|
"User",
|
|
108
140
|
"X509Certificate",
|
|
109
141
|
]
|
scim2_models/attributes.py
CHANGED
|
@@ -34,7 +34,11 @@ class MultiValuedComplexAttribute(ComplexAttribute):
|
|
|
34
34
|
|
|
35
35
|
primary: bool | None = None
|
|
36
36
|
"""A Boolean value indicating the 'primary' or preferred attribute value
|
|
37
|
-
for this attribute.
|
|
37
|
+
for this attribute.
|
|
38
|
+
|
|
39
|
+
Per :rfc:`RFC 7643 §2.4 <7643#section-2.4>`, the primary attribute value
|
|
40
|
+
``True`` MUST appear no more than once in a multi-valued attribute list.
|
|
41
|
+
"""
|
|
38
42
|
|
|
39
43
|
display: Annotated[str | None, Mutability.immutable] = None
|
|
40
44
|
"""A human-readable name, primarily used for display purposes."""
|
scim2_models/base.py
CHANGED
|
@@ -364,6 +364,49 @@ class BaseModel(PydanticBaseModel):
|
|
|
364
364
|
cls._check_mutability_issues(original, obj)
|
|
365
365
|
return obj
|
|
366
366
|
|
|
367
|
+
@model_validator(mode="after")
|
|
368
|
+
def check_primary_attribute_uniqueness(self, info: ValidationInfo) -> Self:
|
|
369
|
+
"""Validate that only one attribute can be marked as primary in multi-valued lists.
|
|
370
|
+
|
|
371
|
+
Per RFC 7643 Section 2.4: The primary attribute value 'true' MUST appear no more than once.
|
|
372
|
+
"""
|
|
373
|
+
scim_context = info.context.get("scim") if info.context else None
|
|
374
|
+
if not scim_context or scim_context == Context.DEFAULT:
|
|
375
|
+
return self
|
|
376
|
+
|
|
377
|
+
for field_name in self.__class__.model_fields:
|
|
378
|
+
if not self.get_field_multiplicity(field_name):
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
field_value = getattr(self, field_name)
|
|
382
|
+
if field_value is None:
|
|
383
|
+
continue
|
|
384
|
+
|
|
385
|
+
element_type = self.get_field_root_type(field_name)
|
|
386
|
+
if (
|
|
387
|
+
element_type is None
|
|
388
|
+
or not isclass(element_type)
|
|
389
|
+
or not issubclass(element_type, PydanticBaseModel)
|
|
390
|
+
or "primary" not in element_type.model_fields
|
|
391
|
+
):
|
|
392
|
+
continue
|
|
393
|
+
|
|
394
|
+
primary_count = sum(
|
|
395
|
+
1 for item in field_value if getattr(item, "primary", None) is True
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
if primary_count > 1:
|
|
399
|
+
raise PydanticCustomError(
|
|
400
|
+
"primary_uniqueness_error",
|
|
401
|
+
"Field '{field_name}' has {count} items marked as primary, but only one is allowed per RFC 7643",
|
|
402
|
+
{
|
|
403
|
+
"field_name": field_name,
|
|
404
|
+
"count": primary_count,
|
|
405
|
+
},
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
return self
|
|
409
|
+
|
|
367
410
|
@classmethod
|
|
368
411
|
def _check_mutability_issues(
|
|
369
412
|
cls, original: "BaseModel", replacement: "BaseModel"
|
|
@@ -406,7 +449,9 @@ class BaseModel(PydanticBaseModel):
|
|
|
406
449
|
main_schema = self._attribute_urn
|
|
407
450
|
separator = "."
|
|
408
451
|
else:
|
|
409
|
-
main_schema = self.__class__
|
|
452
|
+
main_schema = getattr(self.__class__, "__schema__", None)
|
|
453
|
+
if main_schema is None:
|
|
454
|
+
return
|
|
410
455
|
separator = ":"
|
|
411
456
|
|
|
412
457
|
for field_name in self.__class__.model_fields:
|
|
@@ -540,13 +585,12 @@ class BaseModel(PydanticBaseModel):
|
|
|
540
585
|
"""
|
|
541
586
|
from scim2_models.resources.resource import Extension
|
|
542
587
|
|
|
543
|
-
main_schema = self.__class__
|
|
588
|
+
main_schema = getattr(self.__class__, "__schema__", None)
|
|
544
589
|
field = self.__class__.model_fields[field_name]
|
|
545
590
|
alias = field.serialization_alias or field_name
|
|
546
591
|
field_type = self.get_field_root_type(field_name)
|
|
547
|
-
|
|
548
|
-
alias
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
return full_urn
|
|
592
|
+
if isclass(field_type) and issubclass(field_type, Extension):
|
|
593
|
+
return alias
|
|
594
|
+
if main_schema is None:
|
|
595
|
+
return alias
|
|
596
|
+
return f"{main_schema}:{alias}"
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""SCIM exceptions corresponding to RFC 7644 error types.
|
|
2
|
+
|
|
3
|
+
This module provides a hierarchy of exceptions that map to SCIM protocol errors.
|
|
4
|
+
Each exception can be converted to a :class:`~scim2_models.Error` response object
|
|
5
|
+
or to a :class:`~pydantic_core.PydanticCustomError` for use in Pydantic validators.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic_core import PydanticCustomError
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .messages.error import Error
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SCIMException(Exception):
|
|
18
|
+
"""Base exception for SCIM protocol errors.
|
|
19
|
+
|
|
20
|
+
Each subclass corresponds to a scimType defined in :rfc:`RFC 7644 Table 9 <7644#section-3.12>`.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
status: int = 400
|
|
24
|
+
scim_type: str = ""
|
|
25
|
+
_default_detail: str = "A SCIM error occurred"
|
|
26
|
+
|
|
27
|
+
def __init__(self, *, detail: str | None = None, **context: Any):
|
|
28
|
+
self.context = context
|
|
29
|
+
self._detail = detail
|
|
30
|
+
super().__init__(detail or self._default_detail)
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def detail(self) -> str:
|
|
34
|
+
"""The error detail message."""
|
|
35
|
+
return self._detail or self._default_detail
|
|
36
|
+
|
|
37
|
+
def to_error(self) -> "Error":
|
|
38
|
+
"""Convert this exception to a SCIM Error response object."""
|
|
39
|
+
from .messages.error import Error
|
|
40
|
+
|
|
41
|
+
return Error(
|
|
42
|
+
status=self.status,
|
|
43
|
+
scim_type=self.scim_type or None,
|
|
44
|
+
detail=str(self),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def as_pydantic_error(self) -> PydanticCustomError:
|
|
48
|
+
"""Convert to PydanticCustomError for use in Pydantic validators."""
|
|
49
|
+
return PydanticCustomError(
|
|
50
|
+
f"scim_{self.scim_type}" if self.scim_type else "scim_error",
|
|
51
|
+
str(self),
|
|
52
|
+
{"scim_type": self.scim_type, "status": self.status, **self.context},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class InvalidFilterException(SCIMException):
|
|
57
|
+
"""The specified filter syntax was invalid.
|
|
58
|
+
|
|
59
|
+
Corresponds to scimType ``invalidFilter`` with HTTP status 400.
|
|
60
|
+
|
|
61
|
+
:rfc:`RFC 7644 Section 3.4.2.2 <7644#section-3.4.2.2>`
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
status = 400
|
|
65
|
+
scim_type = "invalidFilter"
|
|
66
|
+
_default_detail = (
|
|
67
|
+
"The specified filter syntax was invalid, "
|
|
68
|
+
"or the specified attribute and filter comparison combination is not supported"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def __init__(self, *, filter: str | None = None, **kw: Any):
|
|
72
|
+
self.filter = filter
|
|
73
|
+
super().__init__(**kw)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TooManyException(SCIMException):
|
|
77
|
+
"""The specified filter yields too many results.
|
|
78
|
+
|
|
79
|
+
Corresponds to scimType ``tooMany`` with HTTP status 400.
|
|
80
|
+
|
|
81
|
+
:rfc:`RFC 7644 Section 3.4.2.2 <7644#section-3.4.2.2>`
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
status = 400
|
|
85
|
+
scim_type = "tooMany"
|
|
86
|
+
_default_detail = (
|
|
87
|
+
"The specified filter yields many more results "
|
|
88
|
+
"than the server is willing to calculate or process"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class UniquenessException(SCIMException):
|
|
93
|
+
"""One or more attribute values are already in use or reserved.
|
|
94
|
+
|
|
95
|
+
Corresponds to scimType ``uniqueness`` with HTTP status 409.
|
|
96
|
+
|
|
97
|
+
:rfc:`RFC 7644 Section 3.3.1 <7644#section-3.3.1>`
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
status = 409
|
|
101
|
+
scim_type = "uniqueness"
|
|
102
|
+
_default_detail = (
|
|
103
|
+
"One or more of the attribute values are already in use or are reserved"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self, *, attribute: str | None = None, value: Any | None = None, **kw: Any
|
|
108
|
+
):
|
|
109
|
+
self.attribute = attribute
|
|
110
|
+
self.value = value
|
|
111
|
+
super().__init__(**kw)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class MutabilityException(SCIMException):
|
|
115
|
+
"""The attempted modification is not compatible with the attribute's mutability.
|
|
116
|
+
|
|
117
|
+
Corresponds to scimType ``mutability`` with HTTP status 400.
|
|
118
|
+
|
|
119
|
+
:rfc:`RFC 7644 Section 3.5.2 <7644#section-3.5.2>`
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
status = 400
|
|
123
|
+
scim_type = "mutability"
|
|
124
|
+
_default_detail = (
|
|
125
|
+
"The attempted modification is not compatible with the target attribute's "
|
|
126
|
+
"mutability or current state"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def __init__(
|
|
130
|
+
self,
|
|
131
|
+
*,
|
|
132
|
+
attribute: str | None = None,
|
|
133
|
+
mutability: str | None = None,
|
|
134
|
+
operation: str | None = None,
|
|
135
|
+
**kw: Any,
|
|
136
|
+
):
|
|
137
|
+
self.attribute = attribute
|
|
138
|
+
self.mutability = mutability
|
|
139
|
+
self.operation = operation
|
|
140
|
+
super().__init__(**kw)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class InvalidSyntaxException(SCIMException):
|
|
144
|
+
"""The request body message structure was invalid.
|
|
145
|
+
|
|
146
|
+
Corresponds to scimType ``invalidSyntax`` with HTTP status 400.
|
|
147
|
+
|
|
148
|
+
:rfc:`RFC 7644 Section 3.12 <7644#section-3.12>`
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
status = 400
|
|
152
|
+
scim_type = "invalidSyntax"
|
|
153
|
+
_default_detail = (
|
|
154
|
+
"The request body message structure was invalid "
|
|
155
|
+
"or did not conform to the request schema"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class InvalidPathException(SCIMException):
|
|
160
|
+
"""The path attribute was invalid or malformed.
|
|
161
|
+
|
|
162
|
+
Corresponds to scimType ``invalidPath`` with HTTP status 400.
|
|
163
|
+
|
|
164
|
+
:rfc:`RFC 7644 Section 3.5.2 <7644#section-3.5.2>`
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
status = 400
|
|
168
|
+
scim_type = "invalidPath"
|
|
169
|
+
_default_detail = "The path attribute was invalid or malformed"
|
|
170
|
+
|
|
171
|
+
def __init__(self, *, path: str | None = None, **kw: Any):
|
|
172
|
+
self.path = path
|
|
173
|
+
super().__init__(**kw)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class PathNotFoundException(InvalidPathException):
|
|
177
|
+
"""The path references a non-existent field.
|
|
178
|
+
|
|
179
|
+
This is a specialized form of :class:`InvalidPathException`.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
_default_detail = "The specified path references a non-existent field"
|
|
183
|
+
|
|
184
|
+
def __init__(self, *, path: str | None = None, field: str | None = None, **kw: Any):
|
|
185
|
+
self.field = field
|
|
186
|
+
super().__init__(path=path, **kw)
|
|
187
|
+
|
|
188
|
+
def __str__(self) -> str:
|
|
189
|
+
if self._detail:
|
|
190
|
+
return self._detail
|
|
191
|
+
if self.field:
|
|
192
|
+
return f"Field not found: {self.field}"
|
|
193
|
+
return self._default_detail
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class NoTargetException(SCIMException):
|
|
197
|
+
"""The specified path did not yield a target that could be operated on.
|
|
198
|
+
|
|
199
|
+
Corresponds to scimType ``noTarget`` with HTTP status 400.
|
|
200
|
+
|
|
201
|
+
:rfc:`RFC 7644 Section 3.5.2 <7644#section-3.5.2>`
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
status = 400
|
|
205
|
+
scim_type = "noTarget"
|
|
206
|
+
_default_detail = (
|
|
207
|
+
"The specified path did not yield an attribute or attribute value "
|
|
208
|
+
"that could be operated on"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def __init__(self, *, path: str | None = None, **kw: Any):
|
|
212
|
+
self.path = path
|
|
213
|
+
super().__init__(**kw)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class InvalidValueException(SCIMException):
|
|
217
|
+
"""A required value was missing or the value was not compatible.
|
|
218
|
+
|
|
219
|
+
Corresponds to scimType ``invalidValue`` with HTTP status 400.
|
|
220
|
+
|
|
221
|
+
:rfc:`RFC 7644 Section 3.12 <7644#section-3.12>`
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
status = 400
|
|
225
|
+
scim_type = "invalidValue"
|
|
226
|
+
_default_detail = (
|
|
227
|
+
"A required value was missing, or the value specified was not compatible "
|
|
228
|
+
"with the operation or attribute type, or resource schema"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def __init__(
|
|
232
|
+
self, *, attribute: str | None = None, reason: str | None = None, **kw: Any
|
|
233
|
+
):
|
|
234
|
+
self.attribute = attribute
|
|
235
|
+
self.reason = reason
|
|
236
|
+
super().__init__(**kw)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class InvalidVersionException(SCIMException):
|
|
240
|
+
"""The specified SCIM protocol version is not supported.
|
|
241
|
+
|
|
242
|
+
Corresponds to scimType ``invalidVers`` with HTTP status 400.
|
|
243
|
+
|
|
244
|
+
:rfc:`RFC 7644 Section 3.13 <7644#section-3.13>`
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
status = 400
|
|
248
|
+
scim_type = "invalidVers"
|
|
249
|
+
_default_detail = "The specified SCIM protocol version is not supported"
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class SensitiveException(SCIMException):
|
|
253
|
+
"""The request cannot be completed due to sensitive information in the URI.
|
|
254
|
+
|
|
255
|
+
Corresponds to scimType ``sensitive`` with HTTP status 400.
|
|
256
|
+
|
|
257
|
+
:rfc:`RFC 7644 Section 7.5.2 <7644#section-7.5.2>`
|
|
258
|
+
"""
|
|
259
|
+
|
|
260
|
+
status = 400
|
|
261
|
+
scim_type = "sensitive"
|
|
262
|
+
_default_detail = (
|
|
263
|
+
"The specified request cannot be completed, due to the passing of sensitive "
|
|
264
|
+
"information in a request URI"
|
|
265
|
+
)
|
scim2_models/messages/bulk.py
CHANGED
|
@@ -5,8 +5,8 @@ from typing import Any
|
|
|
5
5
|
from pydantic import Field
|
|
6
6
|
from pydantic import PlainSerializer
|
|
7
7
|
|
|
8
|
-
from ..annotations import Required
|
|
9
8
|
from ..attributes import ComplexAttribute
|
|
9
|
+
from ..path import URN
|
|
10
10
|
from ..utils import _int_to_str
|
|
11
11
|
from .message import Message
|
|
12
12
|
|
|
@@ -53,9 +53,7 @@ class BulkRequest(Message):
|
|
|
53
53
|
The models for Bulk operations are defined, but their behavior is not implemented nor tested yet.
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
"urn:ietf:params:scim:api:messages:2.0:BulkRequest"
|
|
58
|
-
]
|
|
56
|
+
__schema__ = URN("urn:ietf:params:scim:api:messages:2.0:BulkRequest")
|
|
59
57
|
|
|
60
58
|
fail_on_errors: int | None = None
|
|
61
59
|
"""An integer specifying the number of errors that the service provider
|
|
@@ -76,9 +74,7 @@ class BulkResponse(Message):
|
|
|
76
74
|
The models for Bulk operations are defined, but their behavior is not implemented nor tested yet.
|
|
77
75
|
"""
|
|
78
76
|
|
|
79
|
-
|
|
80
|
-
"urn:ietf:params:scim:api:messages:2.0:BulkResponse"
|
|
81
|
-
]
|
|
77
|
+
__schema__ = URN("urn:ietf:params:scim:api:messages:2.0:BulkResponse")
|
|
82
78
|
|
|
83
79
|
operations: list[BulkOperation] | None = Field(
|
|
84
80
|
None, serialization_alias="Operations"
|