From d082e9d3c2d2443afa348cb50f8d05762ad38ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Mon, 2 Dec 2024 08:22:16 +0100 Subject: [PATCH] Revert "refactor: Resources must define a 'scim_schema' attribute" This reverts commit 7514c8b318b35c0a0070d985dcdde851be8e7d08. This was not compatible with Pydantic 2.10 --- doc/changelog.rst | 22 ------------- doc/tutorial.rst | 9 ++---- scim2_models/base.py | 21 ++++++++---- scim2_models/rfc7643/enterprise_user.py | 5 +-- scim2_models/rfc7643/group.py | 3 +- scim2_models/rfc7643/resource.py | 29 ++++------------- scim2_models/rfc7643/resource_type.py | 9 +++--- scim2_models/rfc7643/schema.py | 8 +++-- .../rfc7643/service_provider_config.py | 5 +-- scim2_models/rfc7643/user.py | 3 +- scim2_models/rfc7644/bulk.py | 5 ++- scim2_models/rfc7644/error.py | 3 +- scim2_models/rfc7644/list_response.py | 8 ++--- scim2_models/rfc7644/message.py | 12 ------- scim2_models/rfc7644/patch_op.py | 3 +- scim2_models/rfc7644/search_request.py | 3 +- tests/test_dynamic_resources.py | 32 +++++++++++-------- tests/test_list_response.py | 3 +- tests/test_model_attributes.py | 9 +++--- tests/test_model_serialization.py | 5 ++- tests/test_model_validation.py | 7 ++-- tests/test_models.py | 19 ----------- tests/test_resource_extension.py | 5 +-- 23 files changed, 75 insertions(+), 153 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 7d42c0d..0916964 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,28 +1,6 @@ Changelog ========= -[0.3.0] - Unreleased --------------------- - -.. warning:: - - This version comes with breaking changes: - - - :class:`~scim2_models.Resource`, :class:`~scim2_models.Extension` and :class:`~scim2_models.Message` must define a ``scim_schema`` attribute. - - .. code-block:: python - :caption: Before - - class MyResource(Resource): - schemas : list[str] = ["example:schema:MyResource"] - - .. code-block:: python - :caption: After - - class MyResource(Resource): - scim_schema: ClassVar[str] = "example:schema:MyResource" - - [0.2.7] - 2024-11-30 -------------------- diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 76c4c06..8efcaa2 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -272,12 +272,11 @@ Custom models You can write your own model and use it the same way than the other scim2-models models. Just inherit from :class:`~scim2_models.Resource` for your main resource, or :class:`~scim2_models.Extension` for extensions. -Then you need to define a ``scim_schema`` attribute, that is a class variable detailing the schema identifier of your model. Use :class:`~scim2_models.ComplexAttribute` as base class for complex attributes: .. code-block:: python - >>> from typing import Annotated, ClassVar, Optional, List + >>> from typing import Annotated, Optional, List >>> from scim2_models import Resource, Returned, Mutability, ComplexAttribute >>> from enum import Enum @@ -289,7 +288,7 @@ Use :class:`~scim2_models.ComplexAttribute` as base class for complex attributes ... """The pet color.""" >>> class Pet(Resource): - ... scim_schema : ClassVar[str] = "example:schemas:Pet" + ... schemas: List[str] = ["example:schemas:Pet"] ... ... name: Annotated[Optional[str], Mutability.immutable, Returned.always] ... """The name of the pet.""" @@ -310,8 +309,6 @@ that can take type parameters to represent :rfc:`RFC7643 §7 'referenceTypes'<7 >>> from typing import Literal >>> class PetOwner(Resource): - ... scim_schema : ClassVar[str] = "examples:schema.PetOwner" - ... ... pet: Reference[Literal["Pet"]] :class:`~scim2_models.Reference` has two special type parameters :data:`~scim2_models.ExternalReference` and :data:`~scim2_models.URIReference` that matches :rfc:`RFC7643 §7 <7643#section-7>` external and URI reference types. @@ -328,7 +325,7 @@ This is useful for server implementations, so custom models or models provided b >>> class MyCustomResource(Resource): ... """My awesome custom schema.""" ... - ... scim_schema: ClassVar[str] = "example:schemas:MyCustomResource" + ... schemas: List[str] = ["example:schemas:MyCustomResource"] ... ... foobar: Optional[str] ... diff --git a/scim2_models/base.py b/scim2_models/base.py index f33db4c..426484b 100644 --- a/scim2_models/base.py +++ b/scim2_models/base.py @@ -92,7 +92,11 @@ def validate_attribute_urn( if default_resource and default_resource not in resource_types: resource_types.append(default_resource) - default_schema = default_resource.scim_schema if default_resource else None + default_schema = ( + default_resource.model_fields["schemas"].default[0] + if default_resource + else None + ) schema: Optional[Any] schema, attribute_base = extract_schema_and_attribute_base(attribute_name) @@ -609,7 +613,10 @@ def mark_with_schema(self): if not is_complex_attribute(attr_type): continue - main_schema = getattr(self, "_scim_schema", None) or self.scim_schema + main_schema = ( + getattr(self, "_schema", None) + or self.model_fields["schemas"].default[0] + ) separator = ":" if isinstance(self, Resource) else "." schema = f"{main_schema}{separator}{field_name}" @@ -617,9 +624,9 @@ def mark_with_schema(self): if attr_value := getattr(self, field_name): if isinstance(attr_value, list): for item in attr_value: - item._scim_schema = schema + item._schema = schema else: - attr_value._scim_schema = schema + attr_value._schema = schema @field_serializer("*", mode="wrap") def scim_serializer( @@ -786,7 +793,7 @@ def get_attribute_urn(self, field_name: str) -> str: See :rfc:`RFC7644 §3.10 <7644#section-3.10>`. """ - main_schema = self.scim_schema + main_schema = self.model_fields["schemas"].default[0] alias = self.model_fields[field_name].serialization_alias or field_name # if alias contains a ':' this is an extension urn @@ -797,7 +804,7 @@ def get_attribute_urn(self, field_name: str) -> str: class ComplexAttribute(BaseModel): """A complex attribute as defined in :rfc:`RFC7643 §2.3.8 <7643#section-2.3.8>`.""" - _scim_schema: Optional[str] = None + _schema: Optional[str] = None def get_attribute_urn(self, field_name: str) -> str: """Build the full URN of the attribute. @@ -805,7 +812,7 @@ def get_attribute_urn(self, field_name: str) -> str: See :rfc:`RFC7644 §3.10 <7644#section-3.10>`. """ alias = self.model_fields[field_name].serialization_alias or field_name - return f"{self._scim_schema}.{alias}" + return f"{self._schema}.{alias}" class MultiValuedComplexAttribute(ComplexAttribute): diff --git a/scim2_models/rfc7643/enterprise_user.py b/scim2_models/rfc7643/enterprise_user.py index 4df9faa..9c9020d 100644 --- a/scim2_models/rfc7643/enterprise_user.py +++ b/scim2_models/rfc7643/enterprise_user.py @@ -1,5 +1,4 @@ from typing import Annotated -from typing import ClassVar from typing import Literal from typing import Optional @@ -27,9 +26,7 @@ class Manager(ComplexAttribute): class EnterpriseUser(Extension): - scim_schema: ClassVar[str] = ( - "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" - ) + schemas: list[str] = ["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"] employee_number: Optional[str] = None """Numeric or alphanumeric identifier assigned to a person, typically based diff --git a/scim2_models/rfc7643/group.py b/scim2_models/rfc7643/group.py index 5083674..877b007 100644 --- a/scim2_models/rfc7643/group.py +++ b/scim2_models/rfc7643/group.py @@ -1,5 +1,4 @@ from typing import Annotated -from typing import ClassVar from typing import Literal from typing import Optional from typing import Union @@ -32,7 +31,7 @@ class GroupMember(MultiValuedComplexAttribute): class Group(Resource): - scim_schema: ClassVar[str] = "urn:ietf:params:scim:schemas:core:2.0:Group" + schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:Group"] display_name: Optional[str] = None """A human-readable name for the Group.""" diff --git a/scim2_models/rfc7643/resource.py b/scim2_models/rfc7643/resource.py index ece511d..69af464 100644 --- a/scim2_models/rfc7643/resource.py +++ b/scim2_models/rfc7643/resource.py @@ -78,13 +78,6 @@ class Meta(ComplexAttribute): class Extension(BaseModel): - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - if not hasattr(cls, "scim_schema"): - raise AttributeError( - f"{cls.__name__} did not define a scim_schema attribute" - ) - @classmethod def to_schema(cls): """Build a :class:`~scim2_models.Schema` from the current extension class.""" @@ -127,7 +120,7 @@ def __new__(cls, name, bases, attrs, **kwargs): else [extensions] ) for extension in extensions: - schema = extension.scim_schema + schema = extension.model_fields["schemas"].default[0] attrs.setdefault("__annotations__", {})[extension.__name__] = Annotated[ Optional[extension], WrapSerializer(extension_serializer), @@ -143,18 +136,6 @@ def __new__(cls, name, bases, attrs, **kwargs): class Resource(BaseModel, Generic[AnyExtension], metaclass=ResourceMetaclass): - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - if not hasattr(cls, "scim_schema"): - raise AttributeError( - f"{cls.__name__} did not define a scim_schema attribute" - ) - - def init_schemas(): - return [cls.scim_schema] - - cls.model_fields["schemas"].default_factory = init_schemas - schemas: list[str] """The "schemas" attribute is a REQUIRED attribute and is an array of Strings containing URIs that are used to indicate the namespaces of the @@ -205,7 +186,9 @@ def get_extension_models(cls) -> dict[str, type]: else extension_models ) - by_schema = {ext.scim_schema: ext for ext in extension_models} + by_schema = { + ext.model_fields["schemas"].default[0]: ext for ext in extension_models + } return by_schema @staticmethod @@ -214,7 +197,7 @@ def get_by_schema( ) -> Optional[type]: """Given a resource type list and a schema, find the matching resource type.""" by_schema = { - resource_type.scim_schema.lower(): resource_type + resource_type.model_fields["schemas"].default[0].lower(): resource_type for resource_type in (resource_types or []) } if with_extensions: @@ -291,7 +274,7 @@ def compare_field_infos(fi1, fi2): def model_to_schema(model: type[BaseModel]): from scim2_models.rfc7643.schema import Schema - schema_urn = model.scim_schema + schema_urn = model.model_fields["schemas"].default[0] field_infos = dedicated_attributes(model) attributes = [ model_attribute_to_attribute(model, attribute_name) diff --git a/scim2_models/rfc7643/resource_type.py b/scim2_models/rfc7643/resource_type.py index 612b462..84dfcf8 100644 --- a/scim2_models/rfc7643/resource_type.py +++ b/scim2_models/rfc7643/resource_type.py @@ -1,5 +1,4 @@ from typing import Annotated -from typing import ClassVar from typing import Optional from pydantic import Field @@ -36,7 +35,7 @@ class SchemaExtension(ComplexAttribute): class ResourceType(Resource): - scim_schema: ClassVar[str] = "urn:ietf:params:scim:schemas:core:2.0:ResourceType" + schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"] name: Annotated[Optional[str], Mutability.read_only, Required.true] = None """The resource type name. @@ -79,7 +78,7 @@ class ResourceType(Resource): @classmethod def from_resource(cls, resource_model: type[Resource]) -> Self: """Build a naive ResourceType from a resource model.""" - schema = resource_model.scim_schema + schema = resource_model.model_fields["schemas"].default[0] name = schema.split(":")[-1] extensions = resource_model.__pydantic_generic_metadata__["args"] return ResourceType( @@ -89,7 +88,9 @@ def from_resource(cls, resource_model: type[Resource]) -> Self: endpoint=f"/{name}s", schema_=schema, schema_extensions=[ - SchemaExtension(schema_=extension.scim_schema, required=False) + SchemaExtension( + schema_=extension.model_fields["schemas"].default[0], required=False + ) for extension in extensions ], ) diff --git a/scim2_models/rfc7643/schema.py b/scim2_models/rfc7643/schema.py index 2c8c8e0..1a33463 100644 --- a/scim2_models/rfc7643/schema.py +++ b/scim2_models/rfc7643/schema.py @@ -3,7 +3,6 @@ from enum import Enum from typing import Annotated from typing import Any -from typing import ClassVar from typing import List # noqa : UP005 from typing import Literal from typing import Optional @@ -65,7 +64,10 @@ def make_python_model( for attr in (obj.attributes or []) if attr.name } - pydantic_attributes["scim_schema"] = (ClassVar[str], obj.id) + pydantic_attributes["schemas"] = ( + Optional[list[str]], + Field(default=[obj.id]), + ) model_name = to_pascal(to_snake(obj.name)) model = create_model(model_name, __base__=base, **pydantic_attributes) @@ -238,7 +240,7 @@ def to_python(self) -> Optional[tuple[Any, Field]]: class Schema(Resource): - scim_schema: ClassVar[str] = "urn:ietf:params:scim:schemas:core:2.0:Schema" + schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:Schema"] id: Annotated[Optional[str], Mutability.read_only, Required.true] = None """The unique URI of the schema.""" diff --git a/scim2_models/rfc7643/service_provider_config.py b/scim2_models/rfc7643/service_provider_config.py index 5f1cbba..784ffd8 100644 --- a/scim2_models/rfc7643/service_provider_config.py +++ b/scim2_models/rfc7643/service_provider_config.py @@ -1,6 +1,5 @@ from enum import Enum from typing import Annotated -from typing import ClassVar from typing import Optional from pydantic import Field @@ -95,9 +94,7 @@ class Type(str, Enum): class ServiceProviderConfig(Resource): - scim_schema: ClassVar[str] = ( - "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig" - ) + schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"] id: Annotated[ Optional[str], Mutability.read_only, Returned.default, Uniqueness.global_ diff --git a/scim2_models/rfc7643/user.py b/scim2_models/rfc7643/user.py index 034b330..44ea9ea 100644 --- a/scim2_models/rfc7643/user.py +++ b/scim2_models/rfc7643/user.py @@ -1,6 +1,5 @@ from enum import Enum from typing import Annotated -from typing import ClassVar from typing import Literal from typing import Optional from typing import Union @@ -215,7 +214,7 @@ class X509Certificate(MultiValuedComplexAttribute): class User(Resource): - scim_schema: ClassVar[str] = "urn:ietf:params:scim:schemas:core:2.0:User" + schemas: list[str] = ["urn:ietf:params:scim:schemas:core:2.0:User"] user_name: Annotated[Optional[str], Uniqueness.server, Required.true] = None """Unique identifier for the User, typically used by the user to directly diff --git a/scim2_models/rfc7644/bulk.py b/scim2_models/rfc7644/bulk.py index 3585273..d2adc45 100644 --- a/scim2_models/rfc7644/bulk.py +++ b/scim2_models/rfc7644/bulk.py @@ -1,7 +1,6 @@ from enum import Enum from typing import Annotated from typing import Any -from typing import ClassVar from typing import Optional from pydantic import Field @@ -54,7 +53,7 @@ class BulkRequest(Message): The models for Bulk operations are defined, but their behavior is not implemented nor tested yet. """ - scim_schema: ClassVar[str] = "urn:ietf:params:scim:api:messages:2.0:BulkRequest" + schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"] fail_on_errors: Optional[int] = None """An integer specifying the number of errors that the service provider @@ -75,7 +74,7 @@ class BulkResponse(Message): The models for Bulk operations are defined, but their behavior is not implemented nor tested yet. """ - scim_schema: ClassVar[str] = "urn:ietf:params:scim:api:messages:2.0:BulkResponse" + schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:BulkResponse"] operations: Optional[list[BulkOperation]] = Field( None, serialization_alias="Operations" diff --git a/scim2_models/rfc7644/error.py b/scim2_models/rfc7644/error.py index ebde80c..93ba783 100644 --- a/scim2_models/rfc7644/error.py +++ b/scim2_models/rfc7644/error.py @@ -1,5 +1,4 @@ from typing import Annotated -from typing import ClassVar from typing import Optional from pydantic import PlainSerializer @@ -11,7 +10,7 @@ class Error(Message): """Representation of SCIM API errors.""" - scim_schema: ClassVar[str] = "urn:ietf:params:scim:api:messages:2.0:Error" + schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:Error"] status: Annotated[Optional[int], PlainSerializer(int_to_str)] = None """The HTTP status code (see Section 6 of [RFC7231]) expressed as a JSON diff --git a/scim2_models/rfc7644/list_response.py b/scim2_models/rfc7644/list_response.py index b25f720..3241e9e 100644 --- a/scim2_models/rfc7644/list_response.py +++ b/scim2_models/rfc7644/list_response.py @@ -1,6 +1,5 @@ from typing import Annotated from typing import Any -from typing import ClassVar from typing import Generic from typing import Optional from typing import Union @@ -45,7 +44,8 @@ def get_schema_from_payload(payload: Any) -> Optional[str]: ) resource_types_schemas = [ - resource_type.scim_schema for resource_type in resource_types + resource_type.model_fields["schemas"].default[0] + for resource_type in resource_types ] common_schemas = [ schema for schema in payload_schemas if schema in resource_types_schemas @@ -55,7 +55,7 @@ def get_schema_from_payload(payload: Any) -> Optional[str]: discriminator = Discriminator(get_schema_from_payload) def get_tag(resource_type: type[BaseModel]) -> Tag: - return Tag(resource_type.scim_schema) + return Tag(resource_type.model_fields["schemas"].default[0]) tagged_resources = [ Annotated[resource_type, get_tag(resource_type)] @@ -78,7 +78,7 @@ def __new__(cls, name, bases, attrs, **kwargs): class ListResponse(Message, Generic[AnyResource], metaclass=ListResponseMetaclass): - scim_schema: ClassVar[str] = "urn:ietf:params:scim:api:messages:2.0:ListResponse" + schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:ListResponse"] total_results: Optional[int] = None """The total number of results returned by the list or query operation.""" diff --git a/scim2_models/rfc7644/message.py b/scim2_models/rfc7644/message.py index ca4f8a1..8304f58 100644 --- a/scim2_models/rfc7644/message.py +++ b/scim2_models/rfc7644/message.py @@ -5,15 +5,3 @@ class Message(BaseModel): """SCIM protocol messages as defined by :rfc:`RFC7644 §3.1 <7644#section-3.1>`.""" schemas: list[str] - - def __init_subclass__(cls, **kwargs): - super().__init_subclass__(**kwargs) - if not hasattr(cls, "scim_schema"): - raise AttributeError( - f"{cls.__name__} did not define a scim_schema attribute" - ) - - def init_schemas(): - return [cls.scim_schema] - - cls.model_fields["schemas"].default_factory = init_schemas diff --git a/scim2_models/rfc7644/patch_op.py b/scim2_models/rfc7644/patch_op.py index c7fb0a2..6c83928 100644 --- a/scim2_models/rfc7644/patch_op.py +++ b/scim2_models/rfc7644/patch_op.py @@ -1,6 +1,5 @@ from enum import Enum from typing import Any -from typing import ClassVar from typing import Optional from pydantic import Field @@ -58,7 +57,7 @@ class PatchOp(Message): The models for Patch operations are defined, but their behavior is not implemented nor tested yet. """ - scim_schema: ClassVar[str] = "urn:ietf:params:scim:api:messages:2.0:PatchOp" + schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:PatchOp"] operations: Optional[list[PatchOperation]] = Field( None, serialization_alias="Operations" diff --git a/scim2_models/rfc7644/search_request.py b/scim2_models/rfc7644/search_request.py index b4f3122..8f61b65 100644 --- a/scim2_models/rfc7644/search_request.py +++ b/scim2_models/rfc7644/search_request.py @@ -1,5 +1,4 @@ from enum import Enum -from typing import ClassVar from typing import Optional from pydantic import field_validator @@ -14,7 +13,7 @@ class SearchRequest(Message): https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.3 """ - scim_schema: ClassVar[str] = "urn:ietf:params:scim:api:messages:2.0:SearchRequest" + schemas: list[str] = ["urn:ietf:params:scim:api:messages:2.0:SearchRequest"] attributes: Optional[list[str]] = None """A multi-valued list of strings indicating the names of resource diff --git a/tests/test_dynamic_resources.py b/tests/test_dynamic_resources.py index 51890db..c817f06 100644 --- a/tests/test_dynamic_resources.py +++ b/tests/test_dynamic_resources.py @@ -25,7 +25,9 @@ def test_make_group_model_from_schema(load_sample): schema = Schema.model_validate(payload) Group = Resource.from_schema(schema) - assert Group.scim_schema == "urn:ietf:params:scim:schemas:core:2.0:Group" + assert Group.model_fields["schemas"].default == [ + "urn:ietf:params:scim:schemas:core:2.0:Group" + ] # displayName assert Group.get_field_root_type("display_name") is str @@ -151,7 +153,9 @@ def test_make_user_model_from_schema(load_sample): schema = Schema.model_validate(payload) User = Resource.from_schema(schema) - assert User.scim_schema == "urn:ietf:params:scim:schemas:core:2.0:User" + assert User.model_fields["schemas"].default == [ + "urn:ietf:params:scim:schemas:core:2.0:User" + ] # user_name assert User.get_field_root_type("user_name") is str @@ -1241,10 +1245,9 @@ def test_make_enterprise_user_model_from_schema(load_sample): schema = Schema.model_validate(payload) EnterpriseUser = Extension.from_schema(schema) - assert ( - EnterpriseUser.scim_schema - == "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" - ) + assert EnterpriseUser.model_fields["schemas"].default == [ + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" + ] # employee_number assert EnterpriseUser.get_field_root_type("employee_number") is str @@ -1431,9 +1434,9 @@ def test_make_resource_type_model_from_schema(load_sample): schema = Schema.model_validate(payload) ResourceType = Resource.from_schema(schema) - assert ( - ResourceType.scim_schema == "urn:ietf:params:scim:schemas:core:2.0:ResourceType" - ) + assert ResourceType.model_fields["schemas"].default == [ + "urn:ietf:params:scim:schemas:core:2.0:ResourceType" + ] # id assert ResourceType.get_field_root_type("id") is str @@ -1624,10 +1627,9 @@ def test_make_service_provider_config_model_from_schema(load_sample): schema = Schema.model_validate(payload) ServiceProviderConfig = Resource.from_schema(schema) - assert ( - ServiceProviderConfig.scim_schema - == "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig" - ) + assert ServiceProviderConfig.model_fields["schemas"].default == [ + "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig" + ] # documentation_uri assert ( @@ -2161,7 +2163,9 @@ def test_make_schema_model_from_schema(load_sample): schema = Schema.model_validate(payload) Schema_ = Resource.from_schema(schema) - assert Schema_.scim_schema == "urn:ietf:params:scim:schemas:core:2.0:Schema" + assert Schema_.model_fields["schemas"].default == [ + "urn:ietf:params:scim:schemas:core:2.0:Schema" + ] # id assert Schema_.get_field_root_type("id") is str diff --git a/tests/test_list_response.py b/tests/test_list_response.py index 938566a..9c410e5 100644 --- a/tests/test_list_response.py +++ b/tests/test_list_response.py @@ -1,4 +1,3 @@ -from typing import ClassVar from typing import Union import pytest @@ -108,7 +107,7 @@ def test_mixed_types(load_sample): class Foobar(Resource): - scim_schema: ClassVar[str] = "foobarschema" + schemas: list[str] = ["foobarschema"] def test_mixed_types_type_missing(load_sample): diff --git a/tests/test_model_attributes.py b/tests/test_model_attributes.py index 8bef1df..087d0e7 100644 --- a/tests/test_model_attributes.py +++ b/tests/test_model_attributes.py @@ -1,6 +1,5 @@ import uuid from typing import Annotated -from typing import ClassVar from typing import Optional import pytest @@ -21,7 +20,7 @@ class Sub(ComplexAttribute): class Sup(Resource): - scim_schema: ClassVar[str] = "urn:example:2.0:Sup" + schemas: list[str] = ["urn:example:2.0:Sup"] dummy: str sub: Sub subs: list[Sub] @@ -45,7 +44,7 @@ class Baz(ComplexAttribute): class Foo(Resource): - scim_schema: ClassVar[str] = "urn:example:2.0:Foo" + schemas: list[str] = ["urn:example:2.0:Foo"] sub: Annotated[ReturnedModel, Returned.default] bar: str snake_case: str @@ -53,7 +52,7 @@ class Foo(Resource): class Bar(Resource): - scim_schema: ClassVar[str] = "urn:example:2.0:Bar" + schemas: list[str] = ["urn:example:2.0:Bar"] sub: Annotated[ReturnedModel, Returned.default] bar: str snake_case: str @@ -61,7 +60,7 @@ class Bar(Resource): class Extension(Resource): - scim_schema: ClassVar[str] = "urn:example:2.0:Extension" + schemas: list[str] = ["urn:example:2.0:Extension"] baz: str diff --git a/tests/test_model_serialization.py b/tests/test_model_serialization.py index e0cc596..b8e644d 100644 --- a/tests/test_model_serialization.py +++ b/tests/test_model_serialization.py @@ -1,5 +1,4 @@ from typing import Annotated -from typing import ClassVar from typing import Optional import pytest @@ -19,7 +18,7 @@ class SubRetModel(ComplexAttribute): class SupRetResource(Resource): - scim_schema: ClassVar[str] = "org:example:SupRetResource" + schemas: list[str] = ["org:example:SupRetResource"] always_returned: Annotated[Optional[str], Returned.always] = None never_returned: Annotated[Optional[str], Returned.never] = None @@ -30,7 +29,7 @@ class SupRetResource(Resource): class MutResource(Resource): - scim_schema: ClassVar[str] = "org:example:MutResource" + schemas: list[str] = ["org:example:MutResource"] read_only: Annotated[Optional[str], Mutability.read_only] = None read_write: Annotated[Optional[str], Mutability.read_write] = None diff --git a/tests/test_model_validation.py b/tests/test_model_validation.py index 5161819..35e4282 100644 --- a/tests/test_model_validation.py +++ b/tests/test_model_validation.py @@ -1,5 +1,4 @@ from typing import Annotated -from typing import ClassVar from typing import Optional import pytest @@ -13,7 +12,7 @@ class RetResource(Resource): - scim_schema: ClassVar[str] = "org:example:RetResource" + schemas: list[str] = ["org:example:RetResource"] always_returned: Annotated[Optional[str], Returned.always] = None never_returned: Annotated[Optional[str], Returned.never] = None @@ -22,7 +21,7 @@ class RetResource(Resource): class MutResource(Resource): - scim_schema: ClassVar[str] = "org:example:MutResource" + schemas: list[str] = ["org:example:MutResource"] read_only: Annotated[Optional[str], Mutability.read_only] = None read_write: Annotated[Optional[str], Mutability.read_write] = None @@ -31,7 +30,7 @@ class MutResource(Resource): class ReqResource(Resource): - scim_schema: ClassVar[str] = "org:example:ReqResource" + schemas: list[str] = ["org:example:ReqResource"] required: Annotated[Optional[str], Required.true] = None optional: Annotated[Optional[str], Required.false] = None diff --git a/tests/test_models.py b/tests/test_models.py index f853b49..88d2b11 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,16 +1,12 @@ import os from typing import Union -import pytest - from scim2_models import BulkRequest from scim2_models import BulkResponse from scim2_models import EnterpriseUser from scim2_models import Error -from scim2_models import Extension from scim2_models import Group from scim2_models import ListResponse -from scim2_models import Message from scim2_models import PatchOp from scim2_models import Resource from scim2_models import ResourceType @@ -161,18 +157,3 @@ def test_everything_is_optional(): ] for model in models: model() - - -def test_scim_schema_is_mandatory(): - """Check that the scim_schema attribute is needed to define new resources.""" - with pytest.raises(AttributeError): - - class InvalidResource(Resource): ... - - with pytest.raises(AttributeError): - - class InvalidExtension(Extension): ... - - with pytest.raises(AttributeError): - - class InvalidMessage(Message): ... diff --git a/tests/test_resource_extension.py b/tests/test_resource_extension.py index 70a5a7e..950a9f0 100644 --- a/tests/test_resource_extension.py +++ b/tests/test_resource_extension.py @@ -1,5 +1,4 @@ import datetime -from typing import ClassVar from typing import Optional from typing import Union @@ -202,9 +201,7 @@ def test_invalid_setitem(): class SuperHero(Extension): - scim_schema: ClassVar[str] = ( - "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" - ) + schemas: list[str] = ["urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"] superpower: Optional[str] = None """The superhero superpower."""