Schemas and service discovery#

Models that describe resource schemas, resource types, and a service provider’s capabilities, and the registry gathering them.

class scim2_models.ScimProvider(models: Iterable[type[Resource] | type[Extension]] = (), resource_types: Iterable[ResourceType] | None = None, config: ServiceProviderConfig | None = None, policy: ScimPolicy | None = None)[source]#

A SCIM service: the resources it serves, and the capabilities it declares.

A service publishes schemas, resource_types and config on the three discovery endpoints of RFC7644 §4. A server describes itself with a provider, and a client describes the peer it queries; the class carries no notion of either role.

models is the catalog of what the service can build: bare resources and extensions, each identified by its schema URI. resource_types binds extensions to a resource and gives it an endpoint, as RFC7643 §6 describes. The provider composes the two:

>>> from scim2_models import EnterpriseUser, ResourceType, ScimProvider, User
>>> provider = ScimProvider(
...     models=[User, EnterpriseUser],
...     resource_types=[ResourceType.from_resource(User[EnterpriseUser])],
... )
>>> provider.model_for("User") is User[EnterpriseUser]
True
>>> provider.model_for(str(User.__schema__)) is User
True

Leave resource_types out for a service whose resources carry no extension: the provider derives one per resource.

A provider is immutable. It validates its whole description at construction, since nothing can change afterwards.

Parameters:
  • models – The bare resource and extension models the service builds its resources from.

  • resource_types – The bindings between a resource, its extensions and an endpoint, derived from the models when left out.

  • config – The capabilities the service declares.

Raises:

ScimProviderError – When a model declares no schema or is already parameterized, when two models share a schema, when two resource types share a name or an endpoint, or when a resource type names a schema no model describes.

property config: ServiceProviderConfig | None#

What the service publishes on /ServiceProviderConfig.

classmethod from_discovery(schemas: Iterable[Schema], resource_types: Iterable[ResourceType], config: ServiceProviderConfig | None = None, policy: ScimPolicy | None = None) → ScimProvider[source]#

Build a provider from what a service publishes about itself.

This is how a client describes the peer it queried, and how a server describes itself when its resources are configured rather than written in Python. Each schema becomes a model, a resource or an extension depending on how the resource types name it.

Parameters:
  • schemas – What /Schemas answered.

  • resource_types – What /ResourceTypes answered.

  • config – What /ServiceProviderConfig answered.

  • policy – How much the answers of the peer are allowed to depart from the specification. This is a choice, not something a service publishes about itself.

Raises:

ScimProviderError – When a resource type names a schema the service does not publish, or when a model cannot be built from a schema.

model_for(key: str | Schema | ResourceType) → type[ScimObject] | None[source]#

Return the model a key designates, or None.

The name a resource type declares, or the ResourceType itself, answers the composed model, extensions included. That name is the one meta.resourceType carries (RFC7643 §6), not the name of a Python class and not an endpoint, which model_for_endpoint() takes.

A schema URI, or a Schema, answers the catalog instead: the bare resource, or the extension the URI names. RFC7643 §3 has meta.resourceType, not schemas, tell what a resource is.

An unknown key is not a protocol error, so nothing is raised: a server answers it with a 404.

>>> from scim2_models import ScimProvider, User
>>> provider = ScimProvider(models=[User])
>>> provider.model_for("User") is User
True
>>> provider.model_for("Pet") is None
True

The derived resource type is named after the last segment of the schema URI, which is why "User" answers above.

model_for_endpoint(endpoint: str) → type[ScimObject] | None[source]#

Return the composed model an endpoint serves, or None.

>>> from scim2_models import ScimProvider, User
>>> provider = ScimProvider(models=[User])
>>> provider.model_for_endpoint("/Users") is User
True
property models: tuple[type[Resource] | type[Extension], ...]#

The bare resource and extension models the service builds from.

property policy: ScimPolicy#

How much the payloads the service reads may depart from the specification.

Unlike config, this is never None: a policy always applies, and a provider given none declares the strict reading.

property resource_types: tuple[ResourceType, ...]#

What the service publishes on /ResourceTypes.

property schemas: tuple[Schema, ...][source]#

What the service publishes on /Schemas.

Every model contributes one schema, whether a resource type references it or not: a service may describe a schema before binding it.

class scim2_models.ScimProviderError[source]#

A provider cannot describe a coherent service.

Only the code that builds a provider raises this. It stays out of the SCIMException hierarchy, where every class maps to a scimType and turns into an Error response.

class scim2_models.Attribute(*, name: Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>, <CaseExact.true: True>] = None, type: Type | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, multiValued: Annotated[bool | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, description: Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Required.false: False>, <CaseExact.true: True>] = None, required: Required, <Mutability.read_only: 'readOnly'>, <Required.false: False>] = Required.false, canonicalValues: Annotated[list[str] | None, <Mutability.read_only: 'readOnly'>, <CaseExact.true: True>] = None, caseExact: CaseExact, <Mutability.read_only: 'readOnly'>, <Required.false: False>] = CaseExact.false, mutability: Mutability, <Mutability.read_only: 'readOnly'>, <Required.false: False>, <CaseExact.true: True>] = Mutability.read_write, returned: Returned, <Mutability.read_only: 'readOnly'>, <Required.false: False>, <CaseExact.true: True>] = Returned.default, uniqueness: Uniqueness, <Mutability.read_only: 'readOnly'>, <Required.false: False>, <CaseExact.true: True>] = Uniqueness.none, referenceTypes: Annotated[list[str] | None, <Mutability.read_only: 'readOnly'>, <Required.false: False>, <CaseExact.true: True>] = None, subAttributes: Attribute] | None, <Mutability.read_only: 'readOnly'>] = None)[source]#
class Type(*values)[source]#
canonical_values: true: True>]#

A collection of suggested canonical values that MAY be used (e.g., “work” and “home”).

case_exact: false: False>]#

A Boolean value that specifies whether or not a string attribute is case sensitive.

description: true: True>]#

The attribute’s human-readable description.

get_attribute(attribute_name: str) → Attribute | None[source]#

Find an attribute by its name.

multi_valued: true: True>]#

A Boolean value indicating the attribute’s plurality.

mutability: true: True>]#

A single keyword indicating the circumstances under which the value of the attribute can be (re)defined.

name: true: True>]#

The attribute’s name.

reference_types: true: True>]#

A multi-valued array of JSON strings that indicate the SCIM resource types that may be referenced.

required: false: False>]#

A Boolean value that specifies whether or not the attribute is required.

returned: true: True>]#

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.

sub_attributes: read_only: 'readOnly'>]#

When an attribute is of type “complex”, “subAttributes” defines a set of sub-attributes.

type: true: True>]#

The attribute’s data type.

uniqueness: true: True>]#

A single keyword value that specifies how the service provider enforces uniqueness of attribute values.

class scim2_models.Schema(*, schemas: ~typing.Annotated[list[str], <Required.true: True>] = <factory>, id: ~typing.Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, externalId: ~typing.Annotated[str | None, <Mutability.read_write: 'readWrite'>, <Returned.default: 'default'>, <CaseExact.true: True>] = None, meta: ~typing.Annotated[~scim2_models.resources.resource.Meta | None, <Mutability.read_only: 'readOnly'>, <Returned.default: 'default'>] = None, name: ~typing.Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Returned.default: 'default'>, <Required.true: True>] = None, description: ~typing.Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Returned.default: 'default'>] = None, attributes: ~typing.Annotated[list[~scim2_models.resources.schema.Attribute] | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None)[source]#
attributes: true: True>]#

A complex type that defines service provider attributes and their qualities via the following set of sub-attributes.

description: default: 'default'>]#

The schema’s human-readable description.

get_attribute(attribute_name: str) → Attribute | None[source]#

Find an attribute by its name.

id: true: True>]#

The unique URI of the schema.

name: true: True>]#

The schema’s human-readable name.

class scim2_models.ResourceType(*, schemas: ~typing.Annotated[list[str], <Required.true: True>] = <factory>, id: ~typing.Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Returned.default: 'default'>] = None, externalId: ~typing.Annotated[str | None, <Mutability.read_write: 'readWrite'>, <Returned.default: 'default'>, <CaseExact.true: True>] = None, meta: ~typing.Annotated[~scim2_models.resources.resource.Meta | None, <Mutability.read_only: 'readOnly'>, <Returned.default: 'default'>] = None, name: ~typing.Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>, <CaseExact.true: True>, <Uniqueness.server: 'server'>] = None, description: ~typing.Annotated[str | None, <Mutability.read_only: 'readOnly'>] = None, endpoint: ~typing.Annotated[~scim2_models.reference.Reference[uri] | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>, <Uniqueness.server: 'server'>] = None, schema: ~typing.Annotated[~scim2_models.reference.Reference[uri] | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, schemaExtensions: ~typing.Annotated[list[~scim2_models.resources.resource_type.SchemaExtension] | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None)[source]#
description: read_only: 'readOnly'>]#

The resource type’s human-readable description.

When applicable, service providers MUST specify the description.

endpoint: server: 'server'>]#

The resource type’s HTTP-addressable endpoint relative to the Base URL, e.g., ‘/Users’.

classmethod from_resource(resource_model: type[Resource]) → Self[source]#

Build a naive ResourceType from a resource model.

id: default: 'default'>]#

The resource type’s server unique id.

This is often the same value as the “name” attribute.

name: server: 'server'>]#

The resource type name.

When applicable, service providers MUST specify the name, e.g., ‘User’.

schema_: true: True>]#

The resource type’s primary/base schema URI.

schema_extensions: true: True>]#

A list of URIs of the resource type’s schema extensions.

class scim2_models.SchemaExtension(*, schema: Reference[uri] | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, required: Annotated[bool | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None)[source]#
required: true: True>]#

A Boolean value that specifies whether or not the schema extension is required for the resource type.

If true, a resource of this type MUST include this schema extension and also include any attributes declared as required in this schema extension. If false, a resource of this type MAY omit this schema extension.

schema_: true: True>]#

The URI of a schema extension.

class scim2_models.ServiceProviderConfig(*, schemas: ~typing.Annotated[list[str], <Required.true: True>] = <factory>, id: ~typing.Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Returned.default: 'default'>, <Uniqueness.global_: 'global'>] = None, externalId: ~typing.Annotated[str | None, <Mutability.read_write: 'readWrite'>, <Returned.default: 'default'>, <CaseExact.true: True>] = None, meta: ~typing.Annotated[~scim2_models.resources.resource.Meta | None, <Mutability.read_only: 'readOnly'>, <Returned.default: 'default'>] = None, documentationUri: ~typing.Annotated[~scim2_models.reference.Reference[external] | None, <Mutability.read_only: 'readOnly'>] = None, patch: ~typing.Annotated[~scim2_models.resources.service_provider_config.Patch | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, bulk: ~typing.Annotated[~scim2_models.resources.service_provider_config.Bulk | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, filter: ~typing.Annotated[~scim2_models.resources.service_provider_config.Filter | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, changePassword: ~typing.Annotated[~scim2_models.resources.service_provider_config.ChangePassword | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, sort: ~typing.Annotated[~scim2_models.resources.service_provider_config.Sort | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, etag: ~typing.Annotated[~scim2_models.resources.service_provider_config.ETag | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, authenticationSchemes: ~typing.Annotated[list[~scim2_models.resources.service_provider_config.AuthenticationScheme] | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, pagination: ~typing.Annotated[~scim2_models.resources.service_provider_config.Pagination | None, <Mutability.read_only: 'readOnly'>] = None)[source]#
authentication_schemes: true: True>]#

A complex type that specifies supported authentication scheme properties.

bulk: true: True>]#

A complex type that specifies bulk configuration options.

change_password: true: True>]#

A complex type that specifies configuration options related to changing a password.

documentation_uri: read_only: 'readOnly'>]#

An HTTP-addressable URL pointing to the service provider’s human- consumable help documentation.

etag: true: True>]#

A complex type that specifies ETag configuration options.

filter: true: True>]#

A complex type that specifies FILTER options.

id: global_: 'global'>]#

A unique identifier for a SCIM resource as defined by the service provider.

pagination: read_only: 'readOnly'>]#

A complex type that specifies pagination configuration options.

patch: true: True>]#

A complex type that specifies PATCH configuration options.

sort: true: True>]#

A complex type that specifies sort result options.

class scim2_models.AuthenticationScheme(*, type: Type | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, name: Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, description: Annotated[str | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, specUri: Reference[external] | None, <Mutability.read_only: 'readOnly'>] = None, documentationUri: Reference[external] | None, <Mutability.read_only: 'readOnly'>] = None, primary: Annotated[bool | None, <Mutability.read_only: 'readOnly'>] = None)[source]#
class Type(*values)[source]#
description: true: True>]#

A description of the authentication scheme.

documentation_uri: read_only: 'readOnly'>]#

An HTTP-addressable URL pointing to the authentication scheme’s usage documentation.

name: true: True>]#

The common authentication scheme name, e.g., HTTP Basic.

primary: read_only: 'readOnly'>]#

A Boolean value indicating the ‘primary’ or preferred attribute value for this attribute, e.g., the preferred mailing address or primary email address.

spec_uri: read_only: 'readOnly'>]#

An HTTP-addressable URL pointing to the authentication scheme’s specification.

type: true: True>]#

The authentication scheme.

class scim2_models.Patch(*, supported: Annotated[bool | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None)[source]#
supported: true: True>]#

A Boolean value specifying whether or not the operation is supported.

class scim2_models.Bulk(*, supported: Annotated[bool | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, maxOperations: Annotated[int | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, maxPayloadSize: Annotated[int | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None)[source]#
max_operations: true: True>]#

An integer value specifying the maximum number of operations.

max_payload_size: true: True>]#

An integer value specifying the maximum payload size in bytes.

supported: true: True>]#

A Boolean value specifying whether or not the operation is supported.

class scim2_models.Filter(*, supported: Annotated[bool | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None, maxResults: Annotated[int | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None)[source]#
max_results: true: True>]#

An integer value specifying the maximum number of resources returned in a response.

supported: true: True>]#

A Boolean value specifying whether or not the operation is supported.

class scim2_models.ChangePassword(*, supported: Annotated[bool | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None)[source]#
supported: true: True>]#

A Boolean value specifying whether or not the operation is supported.

class scim2_models.Sort(*, supported: Annotated[bool | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None)[source]#
supported: true: True>]#

A Boolean value specifying whether or not the operation is supported.

class scim2_models.ETag(*, supported: Annotated[bool | None, <Mutability.read_only: 'readOnly'>, <Required.true: True>] = None)[source]#
supported: true: True>]#

A Boolean value specifying whether or not the operation is supported.