scim2-models 0.6.1__tar.gz → 0.6.3__tar.gz
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-0.6.1 → scim2_models-0.6.3}/PKG-INFO +12 -1
- {scim2_models-0.6.1 → scim2_models-0.6.3}/README.md +11 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/pyproject.toml +1 -1
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/exceptions.py +29 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/path.py +10 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/reference.py +10 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/resources/enterprise_user.py +2 -1
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/resources/group.py +2 -1
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/resources/resource.py +18 -16
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/resources/resource_type.py +11 -4
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/resources/user.py +3 -4
- {scim2_models-0.6.1 → scim2_models-0.6.3}/LICENSE +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/__init__.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/annotations.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/attributes.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/base.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/constants.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/context.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/messages/__init__.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/messages/bulk.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/messages/error.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/messages/list_response.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/messages/message.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/messages/patch_op.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/messages/search_request.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/py.typed +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/resources/__init__.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/resources/schema.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/resources/service_provider_config.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/scim_object.py +0 -0
- {scim2_models-0.6.1 → scim2_models-0.6.3}/scim2_models/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: scim2-models
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: SCIM2 models serialization and validation with pydantic
|
|
5
5
|
Keywords: scim,scim2,provisioning,pydantic,rfc7643,rfc7644
|
|
6
6
|
Author: Yaal Coop
|
|
@@ -240,6 +240,17 @@ Provisioning is the action of managing a set of resources across different servi
|
|
|
240
240
|
SCIM is often used between Identity Providers and applications in completion of standards like OAuth2 and OpenID Connect.
|
|
241
241
|
It allows users and groups creations, modifications and deletions to be synchronized between applications.
|
|
242
242
|
|
|
243
|
+
## Features
|
|
244
|
+
|
|
245
|
+
- **SCIM Resources**: Native Python classes for `User`, `Group`, `EnterpriseUser`, `ServiceProviderConfig`, `ResourceType` and `Schema`
|
|
246
|
+
- **Pydantic Integration**: Full Pydantic v2 support with validation, serialization and IDE autocompletion
|
|
247
|
+
- **RFC Compliance**: Implements [RFC7643](https://datatracker.ietf.org/doc/html/rfc7643) (schema) and [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644) (protocol) specifications
|
|
248
|
+
- **Attribute Metadata**: Support for `Mutability`, `Returned`, `Uniqueness` and `Required` field annotations
|
|
249
|
+
- **Context-Aware Validation**: Automatic field inclusion/exclusion based on HTTP context (creation, query, replacement, etc.)
|
|
250
|
+
- **Schema Extensions**: Dynamic schema extensions and custom resource types, dynamic conversion between schemas and Python models.
|
|
251
|
+
- **SCIM Messages**: `ListResponse`, `SearchRequest`, `PatchOp`, and `Error` messages
|
|
252
|
+
- **Error Handling**: SCIM-compliant exceptions with automatic conversion to `Error` responses
|
|
253
|
+
|
|
243
254
|
## Installation
|
|
244
255
|
|
|
245
256
|
```shell
|
|
@@ -12,6 +12,17 @@ Provisioning is the action of managing a set of resources across different servi
|
|
|
12
12
|
SCIM is often used between Identity Providers and applications in completion of standards like OAuth2 and OpenID Connect.
|
|
13
13
|
It allows users and groups creations, modifications and deletions to be synchronized between applications.
|
|
14
14
|
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **SCIM Resources**: Native Python classes for `User`, `Group`, `EnterpriseUser`, `ServiceProviderConfig`, `ResourceType` and `Schema`
|
|
18
|
+
- **Pydantic Integration**: Full Pydantic v2 support with validation, serialization and IDE autocompletion
|
|
19
|
+
- **RFC Compliance**: Implements [RFC7643](https://datatracker.ietf.org/doc/html/rfc7643) (schema) and [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644) (protocol) specifications
|
|
20
|
+
- **Attribute Metadata**: Support for `Mutability`, `Returned`, `Uniqueness` and `Required` field annotations
|
|
21
|
+
- **Context-Aware Validation**: Automatic field inclusion/exclusion based on HTTP context (creation, query, replacement, etc.)
|
|
22
|
+
- **Schema Extensions**: Dynamic schema extensions and custom resource types, dynamic conversion between schemas and Python models.
|
|
23
|
+
- **SCIM Messages**: `ListResponse`, `SearchRequest`, `PatchOp`, and `Error` messages
|
|
24
|
+
- **Error Handling**: SCIM-compliant exceptions with automatic conversion to `Error` responses
|
|
25
|
+
|
|
15
26
|
## Installation
|
|
16
27
|
|
|
17
28
|
```shell
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scim2-models"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.3"
|
|
8
8
|
description = "SCIM2 models serialization and validation with pydantic"
|
|
9
9
|
authors = [{name="Yaal Coop", email="contact@yaal.coop"}]
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -52,6 +52,21 @@ class SCIMException(Exception):
|
|
|
52
52
|
{"scim_type": self.scim_type, "status": self.status, **self.context},
|
|
53
53
|
)
|
|
54
54
|
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_error(cls, error: "Error") -> "SCIMException":
|
|
57
|
+
"""Create an exception from a SCIM Error object.
|
|
58
|
+
|
|
59
|
+
:param error: The SCIM Error object to convert.
|
|
60
|
+
:return: The appropriate SCIMException subclass instance.
|
|
61
|
+
"""
|
|
62
|
+
from .messages.error import Error
|
|
63
|
+
|
|
64
|
+
if not isinstance(error, Error):
|
|
65
|
+
raise TypeError(f"Expected Error, got {type(error).__name__}")
|
|
66
|
+
|
|
67
|
+
exception_class = _SCIM_TYPE_TO_EXCEPTION.get(error.scim_type or "", cls)
|
|
68
|
+
return exception_class(detail=error.detail)
|
|
69
|
+
|
|
55
70
|
|
|
56
71
|
class InvalidFilterException(SCIMException):
|
|
57
72
|
"""The specified filter syntax was invalid.
|
|
@@ -263,3 +278,17 @@ class SensitiveException(SCIMException):
|
|
|
263
278
|
"The specified request cannot be completed, due to the passing of sensitive "
|
|
264
279
|
"information in a request URI"
|
|
265
280
|
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
_SCIM_TYPE_TO_EXCEPTION: dict[str, type[SCIMException]] = {
|
|
284
|
+
"invalidFilter": InvalidFilterException,
|
|
285
|
+
"tooMany": TooManyException,
|
|
286
|
+
"uniqueness": UniquenessException,
|
|
287
|
+
"mutability": MutabilityException,
|
|
288
|
+
"invalidSyntax": InvalidSyntaxException,
|
|
289
|
+
"invalidPath": InvalidPathException,
|
|
290
|
+
"noTarget": NoTargetException,
|
|
291
|
+
"invalidValue": InvalidValueException,
|
|
292
|
+
"invalidVers": InvalidVersionException,
|
|
293
|
+
"sensitive": SensitiveException,
|
|
294
|
+
}
|
|
@@ -9,6 +9,8 @@ from typing import NamedTuple
|
|
|
9
9
|
from typing import TypeVar
|
|
10
10
|
|
|
11
11
|
from pydantic import GetCoreSchemaHandler
|
|
12
|
+
from pydantic import GetJsonSchemaHandler
|
|
13
|
+
from pydantic.json_schema import JsonSchemaValue
|
|
12
14
|
from pydantic_core import core_schema
|
|
13
15
|
|
|
14
16
|
from .base import BaseModel
|
|
@@ -129,6 +131,14 @@ class Path(UserString, Generic[ResourceT]):
|
|
|
129
131
|
serialization=core_schema.plain_serializer_function_ser_schema(str),
|
|
130
132
|
)
|
|
131
133
|
|
|
134
|
+
@classmethod
|
|
135
|
+
def __get_pydantic_json_schema__(
|
|
136
|
+
cls,
|
|
137
|
+
_core_schema: core_schema.CoreSchema,
|
|
138
|
+
_handler: GetJsonSchemaHandler,
|
|
139
|
+
) -> JsonSchemaValue:
|
|
140
|
+
return {"type": "string"}
|
|
141
|
+
|
|
132
142
|
def __init__(self, path: "str | Path[Any]"):
|
|
133
143
|
if isinstance(path, Path):
|
|
134
144
|
path = str(path)
|
|
@@ -7,6 +7,8 @@ from typing import get_args
|
|
|
7
7
|
from typing import get_origin
|
|
8
8
|
|
|
9
9
|
from pydantic import GetCoreSchemaHandler
|
|
10
|
+
from pydantic import GetJsonSchemaHandler
|
|
11
|
+
from pydantic.json_schema import JsonSchemaValue
|
|
10
12
|
from pydantic_core import Url
|
|
11
13
|
from pydantic_core import ValidationError
|
|
12
14
|
from pydantic_core import core_schema
|
|
@@ -112,6 +114,14 @@ class Reference(str, Generic[ReferenceTypes]):
|
|
|
112
114
|
serialization=core_schema.plain_serializer_function_ser_schema(str),
|
|
113
115
|
)
|
|
114
116
|
|
|
117
|
+
@classmethod
|
|
118
|
+
def __get_pydantic_json_schema__(
|
|
119
|
+
cls,
|
|
120
|
+
_core_schema: core_schema.CoreSchema,
|
|
121
|
+
_handler: GetJsonSchemaHandler,
|
|
122
|
+
) -> JsonSchemaValue:
|
|
123
|
+
return {"type": "string", "format": "uri"}
|
|
124
|
+
|
|
115
125
|
@classmethod
|
|
116
126
|
def get_scim_reference_types(cls) -> list[str]:
|
|
117
127
|
"""Return referenceTypes for SCIM schema generation."""
|
|
@@ -3,6 +3,7 @@ from typing import Annotated
|
|
|
3
3
|
|
|
4
4
|
from pydantic import Field
|
|
5
5
|
|
|
6
|
+
from ..annotations import CaseExact
|
|
6
7
|
from ..annotations import Mutability
|
|
7
8
|
from ..annotations import Required
|
|
8
9
|
from ..attributes import ComplexAttribute
|
|
@@ -15,7 +16,7 @@ if TYPE_CHECKING:
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class Manager(ComplexAttribute):
|
|
18
|
-
value: Annotated[str | None, Required.true] = None
|
|
19
|
+
value: Annotated[str | None, Required.true, CaseExact.true] = None
|
|
19
20
|
"""The id of the SCIM resource representing the User's manager."""
|
|
20
21
|
|
|
21
22
|
ref: Annotated[ # type: ignore[type-arg]
|
|
@@ -7,6 +7,7 @@ from typing import Union
|
|
|
7
7
|
from pydantic import Field
|
|
8
8
|
|
|
9
9
|
from ..annotations import Mutability
|
|
10
|
+
from ..annotations import Required
|
|
10
11
|
from ..attributes import ComplexAttribute
|
|
11
12
|
from ..path import URN
|
|
12
13
|
from ..reference import Reference
|
|
@@ -38,7 +39,7 @@ class GroupMember(ComplexAttribute):
|
|
|
38
39
|
class Group(Resource[Any]):
|
|
39
40
|
__schema__ = URN("urn:ietf:params:scim:schemas:core:2.0:Group")
|
|
40
41
|
|
|
41
|
-
display_name: str | None = None
|
|
42
|
+
display_name: Annotated[str | None, Required.true] = None
|
|
42
43
|
"""A human-readable name for the Group."""
|
|
43
44
|
|
|
44
45
|
members: list[GroupMember] | None = None
|
|
@@ -532,19 +532,21 @@ def _model_attribute_to_scim_attribute(
|
|
|
532
532
|
else None
|
|
533
533
|
)
|
|
534
534
|
|
|
535
|
-
|
|
536
|
-
name
|
|
537
|
-
type
|
|
538
|
-
multi_valued
|
|
539
|
-
description
|
|
540
|
-
canonical_values
|
|
541
|
-
required
|
|
542
|
-
case_exact
|
|
543
|
-
mutability
|
|
544
|
-
returned
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
535
|
+
kwargs: dict[str, Any] = {
|
|
536
|
+
"name": field_info.serialization_alias or attribute_name,
|
|
537
|
+
"type": Attribute.Type(attribute_type),
|
|
538
|
+
"multi_valued": model.get_field_multiplicity(attribute_name),
|
|
539
|
+
"description": field_info.description,
|
|
540
|
+
"canonical_values": field_info.examples,
|
|
541
|
+
"required": model.get_field_annotation(attribute_name, Required),
|
|
542
|
+
"case_exact": model.get_field_annotation(attribute_name, CaseExact),
|
|
543
|
+
"mutability": model.get_field_annotation(attribute_name, Mutability),
|
|
544
|
+
"returned": model.get_field_annotation(attribute_name, Returned),
|
|
545
|
+
"sub_attributes": sub_attributes,
|
|
546
|
+
}
|
|
547
|
+
if attribute_type != Attribute.Type.complex:
|
|
548
|
+
kwargs["uniqueness"] = model.get_field_annotation(attribute_name, Uniqueness)
|
|
549
|
+
if attribute_type == Attribute.Type.reference:
|
|
550
|
+
kwargs["reference_types"] = root_type.get_scim_reference_types() # type: ignore[attr-defined]
|
|
551
|
+
|
|
552
|
+
return Attribute(**kwargs)
|
|
@@ -8,6 +8,7 @@ from ..annotations import CaseExact
|
|
|
8
8
|
from ..annotations import Mutability
|
|
9
9
|
from ..annotations import Required
|
|
10
10
|
from ..annotations import Returned
|
|
11
|
+
from ..annotations import Uniqueness
|
|
11
12
|
from ..attributes import ComplexAttribute
|
|
12
13
|
from ..path import URN
|
|
13
14
|
from ..reference import URI
|
|
@@ -38,7 +39,13 @@ class SchemaExtension(ComplexAttribute):
|
|
|
38
39
|
class ResourceType(Resource[Any]):
|
|
39
40
|
__schema__ = URN("urn:ietf:params:scim:schemas:core:2.0:ResourceType")
|
|
40
41
|
|
|
41
|
-
name: Annotated[
|
|
42
|
+
name: Annotated[
|
|
43
|
+
str | None,
|
|
44
|
+
Mutability.read_only,
|
|
45
|
+
Required.true,
|
|
46
|
+
CaseExact.true,
|
|
47
|
+
Uniqueness.server,
|
|
48
|
+
] = None
|
|
42
49
|
"""The resource type name.
|
|
43
50
|
|
|
44
51
|
When applicable, service providers MUST specify the name, e.g.,
|
|
@@ -57,9 +64,9 @@ class ResourceType(Resource[Any]):
|
|
|
57
64
|
This is often the same value as the "name" attribute.
|
|
58
65
|
"""
|
|
59
66
|
|
|
60
|
-
endpoint: Annotated[
|
|
61
|
-
None
|
|
62
|
-
|
|
67
|
+
endpoint: Annotated[
|
|
68
|
+
Reference[URI] | None, Mutability.read_only, Required.true, Uniqueness.server
|
|
69
|
+
] = None
|
|
63
70
|
"""The resource type's HTTP-addressable endpoint relative to the Base URL,
|
|
64
71
|
e.g., '/Users'."""
|
|
65
72
|
|
|
@@ -2,7 +2,6 @@ from enum import Enum
|
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
from typing import Annotated
|
|
4
4
|
from typing import ClassVar
|
|
5
|
-
from typing import Union
|
|
6
5
|
|
|
7
6
|
from pydantic import Base64Bytes
|
|
8
7
|
from pydantic import EmailStr
|
|
@@ -180,7 +179,7 @@ class Address(ComplexAttribute):
|
|
|
180
179
|
|
|
181
180
|
primary: bool | None = None
|
|
182
181
|
"""A Boolean value indicating the 'primary' or preferred attribute value
|
|
183
|
-
for this attribute, e.g., the preferred
|
|
182
|
+
for this attribute, e.g., the preferred address."""
|
|
184
183
|
|
|
185
184
|
|
|
186
185
|
class Entitlement(ComplexAttribute):
|
|
@@ -202,8 +201,8 @@ class GroupMembership(ComplexAttribute):
|
|
|
202
201
|
value: Annotated[str | None, Mutability.read_only] = None
|
|
203
202
|
"""The identifier of the User's group."""
|
|
204
203
|
|
|
205
|
-
ref: Annotated[
|
|
206
|
-
Reference[
|
|
204
|
+
ref: Annotated[
|
|
205
|
+
Reference["Group"] | None,
|
|
207
206
|
Mutability.read_only,
|
|
208
207
|
] = Field(None, serialization_alias="$ref")
|
|
209
208
|
"""The reference URI of a target resource, if the attribute is a
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|