scim2-models 0.5.1__py3-none-any.whl → 0.6.0__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.
@@ -1,18 +1,24 @@
1
+ import warnings
2
+ from collections.abc import Mapping
3
+ from collections.abc import Sequence
1
4
  from typing import Annotated
5
+ from typing import Any
2
6
 
3
7
  from pydantic import PlainSerializer
8
+ from pydantic import ValidationError
4
9
 
5
- from ..annotations import Required
10
+ from ..path import URN
6
11
  from ..utils import _int_to_str
7
12
  from .message import Message
8
13
 
9
14
 
10
15
  class Error(Message):
11
- """Representation of SCIM API errors."""
16
+ """Representation of SCIM API errors.
12
17
 
13
- schemas: Annotated[list[str], Required.true] = [
14
- "urn:ietf:params:scim:api:messages:2.0:Error"
15
- ]
18
+ :rfc:`RFC 7644 Section 3.12 <7644#section-3.12>`
19
+ """
20
+
21
+ __schema__ = URN("urn:ietf:params:scim:api:messages:2.0:Error")
16
22
 
17
23
  status: Annotated[int | None, PlainSerializer(_int_to_str)] = None
18
24
  """The HTTP status code (see Section 6 of [RFC7231]) expressed as a JSON
@@ -26,7 +32,18 @@ class Error(Message):
26
32
 
27
33
  @classmethod
28
34
  def make_invalid_filter_error(cls) -> "Error":
29
- """Pre-defined error intended to be raised when the specified filter syntax was invalid (does not comply with :rfc:`Figure 1 of RFC7644 <7644#section-3.4.2.2>`), or the specified attribute and filter comparison combination is not supported."""
35
+ """Pre-defined error intended to be raised when the specified filter syntax was invalid (does not comply with :rfc:`Figure 1 of RFC7644 <7644#section-3.4.2.2>`), or the specified attribute and filter comparison combination is not supported.
36
+
37
+ .. deprecated:: 0.6.0
38
+ Use :class:`~scim2_models.InvalidFilterException` instead.
39
+ Will be removed in 0.7.0.
40
+ """
41
+ warnings.warn(
42
+ "make_invalid_filter_error is deprecated, use InvalidFilterException().to_error() instead. "
43
+ "Will be removed in 0.7.0.",
44
+ DeprecationWarning,
45
+ stacklevel=2,
46
+ )
30
47
  return Error(
31
48
  status=400,
32
49
  scim_type="invalidFilter",
@@ -35,7 +52,18 @@ class Error(Message):
35
52
 
36
53
  @classmethod
37
54
  def make_too_many_error(cls) -> "Error":
38
- """Pre-defined error intended to be raised when the specified filter yields many more results than the server is willing to calculate or process. For example, a filter such as ``(userName pr)`` by itself would return all entries with a ``userName`` and MAY not be acceptable to the service provider."""
55
+ """Pre-defined error intended to be raised when the specified filter yields many more results than the server is willing to calculate or process. For example, a filter such as ``(userName pr)`` by itself would return all entries with a ``userName`` and MAY not be acceptable to the service provider.
56
+
57
+ .. deprecated:: 0.6.0
58
+ Use :class:`~scim2_models.TooManyException` instead.
59
+ Will be removed in 0.7.0.
60
+ """
61
+ warnings.warn(
62
+ "make_too_many_error is deprecated, use TooManyException().to_error() instead. "
63
+ "Will be removed in 0.7.0.",
64
+ DeprecationWarning,
65
+ stacklevel=2,
66
+ )
39
67
  return Error(
40
68
  status=400,
41
69
  scim_type="tooMany",
@@ -44,7 +72,18 @@ class Error(Message):
44
72
 
45
73
  @classmethod
46
74
  def make_uniqueness_error(cls) -> "Error":
47
- """Pre-defined error intended to be raised when One or more of the attribute values are already in use or are reserved."""
75
+ """Pre-defined error intended to be raised when One or more of the attribute values are already in use or are reserved.
76
+
77
+ .. deprecated:: 0.6.0
78
+ Use :class:`~scim2_models.UniquenessException` instead.
79
+ Will be removed in 0.7.0.
80
+ """
81
+ warnings.warn(
82
+ "make_uniqueness_error is deprecated, use UniquenessException().to_error() instead. "
83
+ "Will be removed in 0.7.0.",
84
+ DeprecationWarning,
85
+ stacklevel=2,
86
+ )
48
87
  return Error(
49
88
  status=409,
50
89
  scim_type="uniqueness",
@@ -53,7 +92,18 @@ class Error(Message):
53
92
 
54
93
  @classmethod
55
94
  def make_mutability_error(cls) -> "Error":
56
- """Pre-defined error intended to be raised when the attempted modification is not compatible with the target attribute's mutability or current state (e.g., modification of an "immutable" attribute with an existing value)."""
95
+ """Pre-defined error intended to be raised when the attempted modification is not compatible with the target attribute's mutability or current state (e.g., modification of an "immutable" attribute with an existing value).
96
+
97
+ .. deprecated:: 0.6.0
98
+ Use :class:`~scim2_models.MutabilityException` instead.
99
+ Will be removed in 0.7.0.
100
+ """
101
+ warnings.warn(
102
+ "make_mutability_error is deprecated, use MutabilityException().to_error() instead. "
103
+ "Will be removed in 0.7.0.",
104
+ DeprecationWarning,
105
+ stacklevel=2,
106
+ )
57
107
  return Error(
58
108
  status=400,
59
109
  scim_type="mutability",
@@ -62,7 +112,18 @@ class Error(Message):
62
112
 
63
113
  @classmethod
64
114
  def make_invalid_syntax_error(cls) -> "Error":
65
- """Pre-defined error intended to be raised when the request body message structure was invalid or did not conform to the request schema."""
115
+ """Pre-defined error intended to be raised when the request body message structure was invalid or did not conform to the request schema.
116
+
117
+ .. deprecated:: 0.6.0
118
+ Use :class:`~scim2_models.InvalidSyntaxException` instead.
119
+ Will be removed in 0.7.0.
120
+ """
121
+ warnings.warn(
122
+ "make_invalid_syntax_error is deprecated, use InvalidSyntaxException().to_error() instead. "
123
+ "Will be removed in 0.7.0.",
124
+ DeprecationWarning,
125
+ stacklevel=2,
126
+ )
66
127
  return Error(
67
128
  status=400,
68
129
  scim_type="invalidSyntax",
@@ -71,7 +132,18 @@ class Error(Message):
71
132
 
72
133
  @classmethod
73
134
  def make_invalid_path_error(cls) -> "Error":
74
- """Pre-defined error intended to be raised when the "path" attribute was invalid or malformed (see :rfc:`Figure 7 of RFC7644 <7644#section-3.5.2>`)."""
135
+ """Pre-defined error intended to be raised when the "path" attribute was invalid or malformed (see :rfc:`Figure 7 of RFC7644 <7644#section-3.5.2>`).
136
+
137
+ .. deprecated:: 0.6.0
138
+ Use :class:`~scim2_models.InvalidPathException` instead.
139
+ Will be removed in 0.7.0.
140
+ """
141
+ warnings.warn(
142
+ "make_invalid_path_error is deprecated, use InvalidPathException().to_error() instead. "
143
+ "Will be removed in 0.7.0.",
144
+ DeprecationWarning,
145
+ stacklevel=2,
146
+ )
75
147
  return Error(
76
148
  status=400,
77
149
  scim_type="invalidPath",
@@ -80,7 +152,18 @@ class Error(Message):
80
152
 
81
153
  @classmethod
82
154
  def make_no_target_error(cls) -> "Error":
83
- """Pre-defined error intended to be raised when the specified "path" did not yield an attribute or attribute value that could be operated on. This occurs when the specified "path" value contains a filter that yields no match."""
155
+ """Pre-defined error intended to be raised when the specified "path" did not yield an attribute or attribute value that could be operated on. This occurs when the specified "path" value contains a filter that yields no match.
156
+
157
+ .. deprecated:: 0.6.0
158
+ Use :class:`~scim2_models.NoTargetException` instead.
159
+ Will be removed in 0.7.0.
160
+ """
161
+ warnings.warn(
162
+ "make_no_target_error is deprecated, use NoTargetException().to_error() instead. "
163
+ "Will be removed in 0.7.0.",
164
+ DeprecationWarning,
165
+ stacklevel=2,
166
+ )
84
167
  return Error(
85
168
  status=400,
86
169
  scim_type="noTarget",
@@ -89,7 +172,18 @@ class Error(Message):
89
172
 
90
173
  @classmethod
91
174
  def make_invalid_value_error(cls) -> "Error":
92
- """Pre-defined error intended to be raised when a required value was missing, or the value specified was not compatible with the operation or attribute type (see :rfc:`Section 2.2 of RFC7643 <7643#section-2.2>`), or resource schema (see :rfc:`Section 4 of RFC7643 <7643#section-4>`)."""
175
+ """Pre-defined error intended to be raised when a required value was missing, or the value specified was not compatible with the operation or attribute type (see :rfc:`Section 2.2 of RFC7643 <7643#section-2.2>`), or resource schema (see :rfc:`Section 4 of RFC7643 <7643#section-4>`).
176
+
177
+ .. deprecated:: 0.6.0
178
+ Use :class:`~scim2_models.InvalidValueException` instead.
179
+ Will be removed in 0.7.0.
180
+ """
181
+ warnings.warn(
182
+ "make_invalid_value_error is deprecated, use InvalidValueException().to_error() instead. "
183
+ "Will be removed in 0.7.0.",
184
+ DeprecationWarning,
185
+ stacklevel=2,
186
+ )
93
187
  return Error(
94
188
  status=400,
95
189
  scim_type="invalidValue",
@@ -98,7 +192,18 @@ class Error(Message):
98
192
 
99
193
  @classmethod
100
194
  def make_invalid_version_error(cls) -> "Error":
101
- """Pre-defined error intended to be raised when the specified SCIM protocol version is not supported (see :rfc:`Section 3.13 of RFC7644 <7644#section-3.13>`)."""
195
+ """Pre-defined error intended to be raised when the specified SCIM protocol version is not supported (see :rfc:`Section 3.13 of RFC7644 <7644#section-3.13>`).
196
+
197
+ .. deprecated:: 0.6.0
198
+ Use :class:`~scim2_models.InvalidVersionException` instead.
199
+ Will be removed in 0.7.0.
200
+ """
201
+ warnings.warn(
202
+ "make_invalid_version_error is deprecated, use InvalidVersionException().to_error() instead. "
203
+ "Will be removed in 0.7.0.",
204
+ DeprecationWarning,
205
+ stacklevel=2,
206
+ )
102
207
  return Error(
103
208
  status=400,
104
209
  scim_type="invalidVers",
@@ -107,9 +212,73 @@ class Error(Message):
107
212
 
108
213
  @classmethod
109
214
  def make_sensitive_error(cls) -> "Error":
110
- """Pre-defined error intended to be raised when the specified request cannot be completed, due to the passing of sensitive (e.g., personal) information in a request URI. For example, personal information SHALL NOT be transmitted over request URIs. See :rfc:`Section 7.5.2 of RFC7644 <7644#section-7.5.2>`."""
215
+ """Pre-defined error intended to be raised when the specified request cannot be completed, due to the passing of sensitive (e.g., personal) information in a request URI. For example, personal information SHALL NOT be transmitted over request URIs. See :rfc:`Section 7.5.2 of RFC7644 <7644#section-7.5.2>`.
216
+
217
+ .. deprecated:: 0.6.0
218
+ Use :class:`~scim2_models.SensitiveException` instead.
219
+ Will be removed in 0.7.0.
220
+ """
221
+ warnings.warn(
222
+ "make_sensitive_error is deprecated, use SensitiveException().to_error() instead. "
223
+ "Will be removed in 0.7.0.",
224
+ DeprecationWarning,
225
+ stacklevel=2,
226
+ )
111
227
  return Error(
112
228
  status=400,
113
229
  scim_type="sensitive",
114
230
  detail="""The specified request cannot be completed, due to the passing of sensitive (e.g., personal) information in a request URI. For example, personal information SHALL NOT be transmitted over request URIs. See Section 7.5.2. of RFC7644""",
115
231
  )
232
+
233
+ @classmethod
234
+ def from_validation_error(cls, error: Mapping[str, Any]) -> "Error":
235
+ """Convert a single Pydantic error dict to a SCIM Error.
236
+
237
+ If the error is a SCIM-specific error (raised via
238
+ :meth:`SCIMException.as_pydantic_error`), its scim_type and status
239
+ are preserved. Otherwise, a best-effort mapping is performed.
240
+
241
+ :param error: A single error dict from ``ValidationError.errors()``.
242
+ :return: A SCIM Error object.
243
+ """
244
+ if error["type"].startswith("scim_"):
245
+ ctx = error.get("ctx", {})
246
+ return cls(
247
+ status=ctx.get("status", 400),
248
+ scim_type=ctx.get("scim_type"),
249
+ detail=error["msg"],
250
+ )
251
+
252
+ loc = ", ".join(str(loc) for loc in error["loc"])
253
+ detail = f"{error['msg']}: {loc}" if loc else error["msg"]
254
+
255
+ scim_type: str | None = None
256
+ error_type = error["type"]
257
+ if error_type in ("missing", "required_error"):
258
+ scim_type = "invalidValue"
259
+ elif error_type in (
260
+ "string_type",
261
+ "int_type",
262
+ "int_parsing",
263
+ "bool_type",
264
+ "bool_parsing",
265
+ "float_type",
266
+ "float_parsing",
267
+ "json_invalid",
268
+ "value_error",
269
+ ):
270
+ scim_type = "invalidSyntax"
271
+
272
+ return cls(status=400, scim_type=scim_type, detail=detail)
273
+
274
+ @classmethod
275
+ def from_validation_errors(
276
+ cls, errors: ValidationError | Sequence[Mapping[str, Any]]
277
+ ) -> list["Error"]:
278
+ """Convert Pydantic validation errors to a list of SCIM Errors.
279
+
280
+ :param errors: A ``ValidationError`` or a list of error dicts.
281
+ :return: A list of SCIM Error objects.
282
+ """
283
+ error_list = errors.errors() if isinstance(errors, ValidationError) else errors
284
+ return [cls.from_validation_error(error) for error in error_list]
@@ -1,4 +1,3 @@
1
- from typing import Annotated
2
1
  from typing import Any
3
2
  from typing import Generic
4
3
 
@@ -9,17 +8,15 @@ from pydantic import model_validator
9
8
  from pydantic_core import PydanticCustomError
10
9
  from typing_extensions import Self
11
10
 
12
- from ..annotations import Required
13
11
  from ..context import Context
12
+ from ..path import URN
14
13
  from ..resources.resource import AnyResource
15
14
  from .message import Message
16
15
  from .message import _GenericMessageMetaclass
17
16
 
18
17
 
19
18
  class ListResponse(Message, Generic[AnyResource], metaclass=_GenericMessageMetaclass):
20
- schemas: Annotated[list[str], Required.true] = [
21
- "urn:ietf:params:scim:api:messages:2.0:ListResponse"
22
- ]
19
+ __schema__ = URN("urn:ietf:params:scim:api:messages:2.0:ListResponse")
23
20
 
24
21
  total_results: int | None = None
25
22
  """The total number of results returned by the list or query operation."""
@@ -7,11 +7,11 @@ from typing import get_origin
7
7
 
8
8
  from pydantic import Discriminator
9
9
  from pydantic import Tag
10
- from pydantic._internal._model_construction import ModelMetaclass
11
10
 
12
11
  from scim2_models.resources.resource import Resource
13
12
 
14
13
  from ..base import BaseModel
14
+ from ..scim_object import ScimMetaclass
15
15
  from ..scim_object import ScimObject
16
16
  from ..utils import UNION_TYPES
17
17
 
@@ -56,7 +56,7 @@ def _get_tag(resource_type: type[BaseModel]) -> Tag:
56
56
  :param resource_type: SCIM resource type
57
57
  :return: Pydantic Tag for discrimination
58
58
  """
59
- return Tag(resource_type.model_fields["schemas"].default[0])
59
+ return Tag(getattr(resource_type, "__schema__", None) or "")
60
60
 
61
61
 
62
62
  def _create_tagged_resource_union(resource_union: Any) -> Any:
@@ -75,7 +75,7 @@ def _create_tagged_resource_union(resource_union: Any) -> Any:
75
75
 
76
76
  # Set up schemas for the discriminator function
77
77
  resource_types_schemas = [
78
- resource_type.model_fields["schemas"].default[0]
78
+ getattr(resource_type, "__schema__", None) or ""
79
79
  for resource_type in resource_types
80
80
  ]
81
81
 
@@ -92,7 +92,7 @@ def _create_tagged_resource_union(resource_union: Any) -> Any:
92
92
  return Annotated[union, discriminator]
93
93
 
94
94
 
95
- class _GenericMessageMetaclass(ModelMetaclass):
95
+ class _GenericMessageMetaclass(ScimMetaclass):
96
96
  """Metaclass for SCIM generic types with discriminated unions."""
97
97
 
98
98
  def __new__(