From 5aba7b85ddd4028d432bd0254dcf873735472023 Mon Sep 17 00:00:00 2001 From: Meow-Knight Date: Mon, 2 Jun 2025 16:49:41 +0700 Subject: [PATCH] feat: Adjust the PatchOp model --- scim2_models/base.py | 28 ++++++++++++++++++++++++++++ scim2_models/rfc7644/patch_op.py | 14 +++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/scim2_models/base.py b/scim2_models/base.py index ddab376..7c8c17f 100644 --- a/scim2_models/base.py +++ b/scim2_models/base.py @@ -278,6 +278,32 @@ class Context(Enum): - not dump attributes annotated with :attr:`~scim2_models.Returned.request` unless they are explicitly included. """ + RESOURCE_PATCH_REQUEST = auto() + """The resource patch request context. + + Should be used for clients building a payload for a PATCH request, + and servers validating PATCH request payloads. + + - When used for serialization, it will not dump attributes annotated with :attr:`~scim2_models.Mutability.read_only`. + - When used for validation, it will raise a :class:`~pydantic.ValidationError`: + - when finding attributes annotated with :attr:`~scim2_models.Mutability.read_only`, + - when attributes annotated with :attr:`Required.true ` are missing or null. + """ + + RESOURCE_PATCH_RESPONSE = auto() + """The resource patch response context. + + Should be used for servers building a payload for a PATCH response, + and clients validating patch response payloads. + + - When used for validation, it will raise a :class:`~pydantic.ValidationError` when finding attributes annotated with :attr:`~scim2_models.Returned.never` or when attributes annotated with :attr:`~scim2_models.Returned.always` are missing or :data:`None`; + - When used for serialization, it will: + - always dump attributes annotated with :attr:`~scim2_models.Returned.always`; + - never dump attributes annotated with :attr:`~scim2_models.Returned.never`; + - dump attributes annotated with :attr:`~scim2_models.Returned.default` unless they are explicitly excluded; + - not dump attributes annotated with :attr:`~scim2_models.Returned.request` unless they are explicitly included. + """ + @classmethod def is_request(cls, ctx: "Context") -> bool: return ctx in ( @@ -285,6 +311,7 @@ def is_request(cls, ctx: "Context") -> bool: cls.RESOURCE_QUERY_REQUEST, cls.RESOURCE_REPLACEMENT_REQUEST, cls.SEARCH_REQUEST, + cls.RESOURCE_PATCH_REQUEST, ) @classmethod @@ -294,6 +321,7 @@ def is_response(cls, ctx: "Context") -> bool: cls.RESOURCE_QUERY_RESPONSE, cls.RESOURCE_REPLACEMENT_RESPONSE, cls.SEARCH_RESPONSE, + cls.RESOURCE_PATCH_RESPONSE, ) diff --git a/scim2_models/rfc7644/patch_op.py b/scim2_models/rfc7644/patch_op.py index 2a8128f..43f756b 100644 --- a/scim2_models/rfc7644/patch_op.py +++ b/scim2_models/rfc7644/patch_op.py @@ -17,7 +17,7 @@ class Op(str, Enum): remove = "remove" add = "add" - op: Optional[Optional[Op]] = None + op: Op """Each PATCH operation object MUST have exactly one "op" member, whose value indicates the operation to perform and MAY be one of "add", "remove", or "replace". @@ -63,8 +63,16 @@ class PatchOp(Message): "urn:ietf:params:scim:api:messages:2.0:PatchOp" ] - operations: Optional[list[PatchOperation]] = Field( - None, serialization_alias="Operations" + operations: Annotated[Optional[list[PatchOperation]], Required.true] = Field( + None, serialization_alias="Operations", min_length=1 ) """The body of an HTTP PATCH request MUST contain the attribute "Operations", whose value is an array of one or more PATCH operations.""" + + @field_validator("schemas") + @classmethod + def validate_schemas(cls, value): + expected = ["urn:ietf:params:scim:api:messages:2.0:PatchOp"] + if value != expected: + raise ValueError(f"`schemas` must be exactly {expected}") + return value