diff --git a/pydantic_scim2/__init__.py b/pydantic_scim2/__init__.py index 1b6f0ec..3c4ebd7 100644 --- a/pydantic_scim2/__init__.py +++ b/pydantic_scim2/__init__.py @@ -11,6 +11,11 @@ from .responses import PatchOperation from .responses import PatchRequest from .responses import SCIMError +from .schema import AttributeKind +from .schema import Mutability +from .schema import Returned +from .schema import Schema +from .schema import Uniqueness from .service_provider import AuthenticationScheme from .service_provider import AuthenticationSchemeKind from .service_provider import Bulk @@ -74,4 +79,9 @@ "Resource", "Meta", "ETag", + "Schema", + "AttributeKind", + "Mutability", + "Returned", + "Uniqueness", ] diff --git a/pydantic_scim2/schema.py b/pydantic_scim2/schema.py new file mode 100644 index 0000000..b770875 --- /dev/null +++ b/pydantic_scim2/schema.py @@ -0,0 +1,103 @@ +from enum import Enum +from typing import List +from typing import Optional + +from pydantic import BaseModel + +from .resource import Meta + + +class AttributeKind(Enum): + string = "string" + boolean = "boolean" + decimal = "decimal" + integer = "integer" + dateTime = "dateTime" + reference = "reference" + binary = "binary" + complex = "complex" + + +class Mutability(Enum): + readOnly = "readOnly" + readWrite = "readWrite" + immutable = "immutable" + writeOnly = "writeOnly" + + +class Returned(Enum): + always = "always" + never = "never" + default = "default" + request = "request" + + +class Uniqueness(Enum): + none = "none" + server = "server" + global_ = "global" + + +class Attribute(BaseModel): + name: str + """The attribute's name.""" + + type: AttributeKind + """The attribute's data type.""" + + subAttributes: Optional[List["Attribute"]] = None + """When an attribute is of type "complex", "subAttributes" defines a set of + sub-attributes.""" + + multiValued: bool + """A Boolean value indicating the attribute's plurality.""" + + description: str + """The attribute's human-readable description.""" + + required: bool + """A Boolean value that specifies whether or not the attribute is + required.""" + + canonicalValues: Optional[List[str]] = None + """A collection of suggested canonical values that MAY be used (e.g., + "work" and "home").""" + + caseExact: bool = True + """A Boolean value that specifies whether or not a string attribute is case + sensitive.""" + + mutability: Mutability = Mutability.readWrite + """A single keyword indicating the circumstances under which the value of + the attribute can be (re)defined.""" + + returned: Returned = Returned.default + """A single keyword that indicates when an attribute and associated values + are returned in response to a GET request or in response to a PUT, POST, or + PATCH request.""" + + uniqueness: Uniqueness = Uniqueness.none + """A single keyword value that specifies how the service provider enforces + uniqueness of attribute values.""" + + referenceTypes: Optional[List[str]] = None + """A multi-valued array of JSON strings that indicate the SCIM resource + types that may be referenced.""" + + +class Schema(BaseModel): + id: str + """The unique URI of the schema.""" + + name: Optional[str] = None + """The schema's human-readable name.""" + + description: Optional[str] = None + """The schema's human-readable description.""" + + attributes: List[Attribute] + """A complex type that defines service provider attributes and their + qualities via the following set of sub-attributes.""" + + meta: Meta + """A complex attribute containing resource metadata.""" diff --git a/tests/test_models.py b/tests/test_models.py index 67d7a4f..4c32c99 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -4,14 +4,19 @@ from pydantic import AnyUrl from pydantic_scim2 import AddressKind +from pydantic_scim2 import AttributeKind from pydantic_scim2 import AuthenticationSchemeKind from pydantic_scim2 import EmailKind from pydantic_scim2 import Group from pydantic_scim2 import ImKind +from pydantic_scim2 import Mutability from pydantic_scim2 import PhoneNumberKind from pydantic_scim2 import PhotoKind from pydantic_scim2 import ResourceType +from pydantic_scim2 import Returned +from pydantic_scim2 import Schema from pydantic_scim2 import ServiceProviderConfiguration +from pydantic_scim2 import Uniqueness from pydantic_scim2 import User @@ -261,3 +266,71 @@ def test_resource_type(resource_type_payload): assert obj.schema_ == AnyUrl("urn:ietf:params:scim:schemas:core:2.0:Group") assert obj.meta.location == "https://example.com/v2/ResourceTypes/Group" assert obj.meta.resourceType == "ResourceType" + + +def test_schema(resource_schema_payload): + obj = Schema.model_validate(resource_schema_payload[0]) + obj = Schema.model_validate(resource_schema_payload[1]) + + assert obj.id == "urn:ietf:params:scim:schemas:core:2.0:Group" + assert obj.name == "Group" + assert obj.description == "Group" + assert obj.attributes[0].name == "displayName" + assert obj.attributes[0].type == AttributeKind.string + assert obj.attributes[0].multiValued is False + assert obj.attributes[0].description == ( + "A human-readable name for the Group. " "REQUIRED." + ) + assert obj.attributes[0].required is False + assert obj.attributes[0].caseExact is False + assert obj.attributes[0].mutability == Mutability.readWrite + assert obj.attributes[0].returned == Returned.default + assert obj.attributes[0].uniqueness == Uniqueness.none + assert obj.attributes[1].name == "members" + assert obj.attributes[1].type == AttributeKind.complex + assert obj.attributes[1].multiValued is True + assert obj.attributes[1].description == "A list of members of the Group." + assert obj.attributes[1].required is False + assert obj.attributes[1].subAttributes[0].name == "value" + assert obj.attributes[1].subAttributes[0].type == AttributeKind.string + assert obj.attributes[1].subAttributes[0].multiValued is False + assert ( + obj.attributes[1].subAttributes[0].description + == "Identifier of the member of this Group." + ) + assert obj.attributes[1].subAttributes[0].required is False + assert obj.attributes[1].subAttributes[0].caseExact is False + assert obj.attributes[1].subAttributes[0].mutability == Mutability.immutable + assert obj.attributes[1].subAttributes[0].returned == Returned.default + assert obj.attributes[1].subAttributes[0].uniqueness == Uniqueness.none + assert obj.attributes[1].subAttributes[1].name == "$ref" + assert obj.attributes[1].subAttributes[1].type == AttributeKind.reference + assert obj.attributes[1].subAttributes[1].referenceTypes == ["User", "Group"] + assert obj.attributes[1].subAttributes[1].multiValued is False + assert obj.attributes[1].subAttributes[1].description == ( + "The URI corresponding to a SCIM resource " "that is a member of this Group." + ) + assert obj.attributes[1].subAttributes[1].required is False + assert obj.attributes[1].subAttributes[1].caseExact is False + assert obj.attributes[1].subAttributes[1].mutability == Mutability.immutable + assert obj.attributes[1].subAttributes[1].returned == Returned.default + assert obj.attributes[1].subAttributes[1].uniqueness == Uniqueness.none + assert obj.attributes[1].subAttributes[2].name == "type" + assert obj.attributes[1].subAttributes[2].type == AttributeKind.string + assert obj.attributes[1].subAttributes[2].multiValued is False + assert obj.attributes[1].subAttributes[2].description == ( + "A label indicating the type of resource, " "e.g., 'User' or 'Group'." + ) + assert obj.attributes[1].subAttributes[2].required is False + assert obj.attributes[1].subAttributes[2].caseExact is False + assert obj.attributes[1].subAttributes[2].canonicalValues == ["User", "Group"] + assert obj.attributes[1].subAttributes[2].mutability == Mutability.immutable + assert obj.attributes[1].subAttributes[2].returned == Returned.default + assert obj.attributes[1].subAttributes[2].uniqueness == Uniqueness.none + assert obj.attributes[1].mutability == Mutability.readWrite + assert obj.attributes[1].returned == Returned.default + assert obj.meta.resourceType == "Schema" + assert ( + obj.meta.location == "/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:Group" + ) + obj = Schema.model_validate(resource_schema_payload[2])