Storages#

The contract between the server and the data of an application, and the storages that keep the resources in memory.

class scim2_server.storage.ScimStorage[source]#

Where a SCIM server reads and writes its resources.

Subclass it to connect the server to a data source, such as a SQL database or a directory. The server handles the SCIM protocol, and calls these methods to read, search, create, update and delete resources. Every method receives the ResourceType it applies to, so a single storage can serve several resource types.

A storage follows these rules, which ScimStorageContract checks:

  • Every resource it returns is a copy. Changing it does not change the stored resource, and the resources it receives are not changed either.

  • It fills id, meta.resourceType, meta.created, meta.lastModified and meta.version. It leaves meta.location to the server, which knows the URLs. For the same reason, a reference to another resource, such as members.$ref, can be relative to the SCIM root, such as Users/2819c223. The server returns its URL.

  • The version changes whenever the resource changes. An update that changes nothing may keep it.

  • A resource that does not exist raises NotFoundException.

  • A value already taken by an attribute whose uniqueness is server or global raises UniquenessException.

  • It supports what the ServiceProviderConfig of the server announces, such as sorting. The server refuses the rest before calling the storage.

supports_cursors: bool = False#

Whether search() pages the resources with cursors that give stable pages.

Set it to True once search() follows the rules of the cursors. The server refuses to start when its configuration announces cursor pagination and the storage does not support it.

abstractmethod get(resource_type: ResourceType, resource_id: str, *, response_parameters: ResponseParameters[Any] | None = None) → Resource[source]#

Return a resource.

The server passes response_parameters when it returns the resource to the client, for a GET request. The storage may then leave out the attributes the response does not keep, for instance to avoid loading the members of a group. It must return every attribute the response keeps. Path.iter_paths gives them. The server applies the parameters to the response anyway. Without response_parameters, the storage returns the whole resource.

A get method without response_parameters is deprecated, and raises a DeprecationWarning. The server then does not pass the parameters. scim2-server 0.9 will require it.

Raises:

NotFoundException – When no resource of this type has this identifier.

abstractmethod search(resource_types: list[ResourceType], search_request: SearchRequest[Any], *, position: Any = None) → SearchPage[source]#

Return one page of the matching resources, and their number.

The search request is already validated, and its count is already bounded by the maxResults of the server. The storage filters, sorts and pages the resources of every given resource type as a single collection. Several resource types mean a search at the server root.

An attribute that a resource type does not declare matches none of its resources (RFC 7644 §3.4.2.1).

A start_index pages the resources by index. A cursor that is not None pages them with a cursor (RFC 9865), when the storage supports_cursors. The storage then returns the next and previous positions of the page, as values that JSON can hold. The server hides them in the cursors it gives the client, and passes them back as position. position is None on the first page.

The cursors give stable pages. A position holds the sort value and the identifier of the last resource of the page, or of the first one for the previous page. A resource that exists during the whole paging, and whose sort value does not change, is returned exactly once. A resource created or deleted meanwhile is returned or not, depending on where it sorts.

A search method without position is deprecated, and raises a DeprecationWarning. It returns the number of matching resources and the page, and pages by index only. scim2-server 0.9 will require position.

Raises:
abstractmethod create(resource_type: ResourceType, resource: Resource) → Resource[source]#

Store a new resource, and return the stored resource.

Raises:

UniquenessException – When a unique value is taken.

abstractmethod update(resource_type: ResourceType, resource: Resource, *, expected_version: str | None = None) → Resource[source]#

Replace the stored resource that has the identifier of resource, and return the stored resource.

The server calls it for PUT and PATCH requests. It applies the request to the stored resource first, so resource is the whole new state of the resource.

Parameters:

expected_version – The version the stored resource must still have, when given.

Raises:
abstractmethod delete(resource_type: ResourceType, resource_id: str, *, expected_version: str | None = None) → None[source]#

Delete a resource.

Parameters:

expected_version – The version the stored resource must still have, when given.

Raises:
operation() → AbstractContextManager[None][source]#

Enclose one SCIM operation: a single request, or one operation of a bulk request.

It does nothing by default. A SQL storage can open a savepoint here, so that a failed operation does not prevent the next ones of a bulk request (RFC 7644 §3.7). Committing the request is left to the application.

class scim2_server.storage.AsyncScimStorage[source]#

The asynchronous variant of ScimStorage.

Its methods are coroutines, and follow the rules of ScimStorage, which AsyncScimStorageContract checks.

supports_cursors: bool = False#

Whether search() pages the resources with cursors that give stable pages. See ScimStorage.supports_cursors.

abstractmethod async get(resource_type: ResourceType, resource_id: str, *, response_parameters: ResponseParameters[Any] | None = None) → Resource[source]#

Return a resource. See ScimStorage.get().

abstractmethod async search(resource_types: list[ResourceType], search_request: SearchRequest[Any], *, position: Any = None) → SearchPage[source]#

Return one page of the matching resources, and their number. See ScimStorage.search().

abstractmethod async create(resource_type: ResourceType, resource: Resource) → Resource[source]#

Store a new resource. See ScimStorage.create().

abstractmethod async update(resource_type: ResourceType, resource: Resource, *, expected_version: str | None = None) → Resource[source]#

Replace a stored resource. See ScimStorage.update().

abstractmethod async delete(resource_type: ResourceType, resource_id: str, *, expected_version: str | None = None) → None[source]#

Delete a resource. See ScimStorage.delete().

operation() → AbstractAsyncContextManager[None][source]#

Enclose one SCIM operation. See ScimStorage.operation().

class scim2_server.storage.SearchPage(total: int | None, resources: list[Resource], next: Any = None, previous: Any = None)[source]#

A page of found resources, as ScimStorage.search() returns it.

total: int | None#

The number of matching resources, on every page.

It may be None when the resources are paged with a cursor (RFC 9865 §2).

resources: list[Resource]#

The resources of the page.

next: Any = None#

The position of the next page, or None on the last page.

previous: Any = None#

The position of the previous page, or None on the first page.

class scim2_server.memory.InMemoryStorage(clock: Callable[[], ~datetime.datetime]=<function utcnow>)[source]#

A storage keeping the resources in memory, for tests, debugging and demos.

It is not optimized for performance: a search walks every resource. A lock serializes every access, so it can serve a threaded server.

Parameters:

clock – Return the date of a write, for meta.created and meta.lastModified. Pass a fixed clock to get predictable dates.

generate_id(resource_type: ResourceType, resource: Resource) → str[source]#

Return the identifier of a new resource.

Override this method to get predictable identifiers.

next_version() → str[source]#

Return the version of a new write.

Versions come from a counter, so two writes never share a version, even within the same microsecond.

operation() → Generator[None][source]#

Hold the lock for the whole operation.

class scim2_server.memory.AsyncInMemoryStorage(storage: InMemoryStorage | None = None)[source]#

The asynchronous variant of InMemoryStorage.

It serves the resources of an InMemoryStorage. Every call runs without awaiting anything, so a call is never interrupted by another coroutine. operation() takes no lock: holding the lock of the storage across an await would block the event loop. Two concurrent updates of a resource are still told apart by expected_version.

Parameters:

storage – The storage to serve. Pass a subclass of InMemoryStorage to change how identifiers are generated.

property resources: list[Resource]#

The stored resources.