scim2-models 0.3.5__tar.gz → 0.3.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. {scim2_models-0.3.5 → scim2_models-0.3.6}/PKG-INFO +1 -1
  2. {scim2_models-0.3.5 → scim2_models-0.3.6}/doc/changelog.rst +8 -0
  3. {scim2_models-0.3.5 → scim2_models-0.3.6}/pyproject.toml +1 -1
  4. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7643/resource.py +5 -4
  5. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7643/resource_type.py +13 -1
  6. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7644/list_response.py +2 -1
  7. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_resource_type.py +34 -0
  8. {scim2_models-0.3.5 → scim2_models-0.3.6}/uv.lock +165 -164
  9. {scim2_models-0.3.5 → scim2_models-0.3.6}/.github/FUNDING.yml +0 -0
  10. {scim2_models-0.3.5 → scim2_models-0.3.6}/.github/workflows/release.yml +0 -0
  11. {scim2_models-0.3.5 → scim2_models-0.3.6}/.github/workflows/tests.yaml +0 -0
  12. {scim2_models-0.3.5 → scim2_models-0.3.6}/.gitignore +0 -0
  13. {scim2_models-0.3.5 → scim2_models-0.3.6}/.pre-commit-config.yaml +0 -0
  14. {scim2_models-0.3.5 → scim2_models-0.3.6}/.readthedocs.yml +0 -0
  15. {scim2_models-0.3.5 → scim2_models-0.3.6}/LICENSE +0 -0
  16. {scim2_models-0.3.5 → scim2_models-0.3.6}/README.md +0 -0
  17. {scim2_models-0.3.5 → scim2_models-0.3.6}/conftest.py +0 -0
  18. {scim2_models-0.3.5 → scim2_models-0.3.6}/doc/__init__.py +0 -0
  19. {scim2_models-0.3.5 → scim2_models-0.3.6}/doc/conf.py +0 -0
  20. {scim2_models-0.3.5 → scim2_models-0.3.6}/doc/contributing.rst +0 -0
  21. {scim2_models-0.3.5 → scim2_models-0.3.6}/doc/index.rst +0 -0
  22. {scim2_models-0.3.5 → scim2_models-0.3.6}/doc/reference.rst +0 -0
  23. {scim2_models-0.3.5 → scim2_models-0.3.6}/doc/tutorial.rst +0 -0
  24. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.1-user-minimal.json +0 -0
  25. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.2-user-full.json +0 -0
  26. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.3-enterprise_user.json +0 -0
  27. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.4-group.json +0 -0
  28. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.5-service_provider_configuration.json +0 -0
  29. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.6-resource_type-group.json +0 -0
  30. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.6-resource_type-user.json +0 -0
  31. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.7.1-schema-enterprise_user.json +0 -0
  32. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.7.1-schema-group.json +0 -0
  33. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.7.1-schema-user.json +0 -0
  34. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.7.2-schema-resource_type.json +0 -0
  35. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.7.2-schema-schema.json +0 -0
  36. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7643-8.7.2-schema-service_provider_configuration.json +0 -0
  37. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.12-error-bad_request.json +0 -0
  38. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.12-error-not_found.json +0 -0
  39. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.14-user-post_request.json +0 -0
  40. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.14-user-post_response.json +0 -0
  41. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.3-user-post_request.json +0 -0
  42. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.3-user-post_response.json +0 -0
  43. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.4.1-user-known-resource.json +0 -0
  44. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.4.2-list_response-partial_attributes.json +0 -0
  45. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.4.3-list_response-post_query.json +0 -0
  46. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.4.3-search_request.json +0 -0
  47. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.1-user-put_request.json +0 -0
  48. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.1-user-put_response.json +0 -0
  49. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.1-patch_op-add_emails.json +0 -0
  50. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.1-patch_op-add_members.json +0 -0
  51. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.2-patch_op-remove_all_members.json +0 -0
  52. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.2-patch_op-remove_and_add_one_member.json +0 -0
  53. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.2-patch_op-remove_multi_complex_value.json +0 -0
  54. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.2-patch_op-remove_one_member.json +0 -0
  55. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.3-patch_op-replace_all_email_values.json +0 -0
  56. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.3-patch_op-replace_all_members.json +0 -0
  57. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.3-patch_op-replace_street_address.json +0 -0
  58. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.5.2.3-patch_op-replace_user_work_address.json +0 -0
  59. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.6-error-not_found.json +0 -0
  60. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.1-bulk_request-circular_conflict.json +0 -0
  61. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.1-list_response-circular_reference.json +0 -0
  62. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.2-bulk_request-enterprise_user.json +0 -0
  63. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.2-bulk_request-temporary_identifier.json +0 -0
  64. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.2-bulk_response-temporary_identifier.json +0 -0
  65. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.3-bulk_request-multiple_operations.json +0 -0
  66. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.3-bulk_response-error_invalid_syntax.json +0 -0
  67. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.3-bulk_response-multiple_errors.json +0 -0
  68. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.3-bulk_response-multiple_operations.json +0 -0
  69. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.3-error-invalid_syntax.json +0 -0
  70. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.7.4-error-payload_too_large.json +0 -0
  71. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-3.9-user-partial_response.json +0 -0
  72. {scim2_models-0.3.5 → scim2_models-0.3.6}/samples/rfc7644-4-list_response-resource_types.json +0 -0
  73. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/__init__.py +0 -0
  74. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/base.py +0 -0
  75. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/constants.py +0 -0
  76. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/py.typed +0 -0
  77. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7643/__init__.py +0 -0
  78. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7643/enterprise_user.py +0 -0
  79. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7643/group.py +0 -0
  80. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7643/schema.py +0 -0
  81. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7643/service_provider_config.py +0 -0
  82. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7643/user.py +0 -0
  83. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7644/__init__.py +0 -0
  84. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7644/bulk.py +0 -0
  85. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7644/error.py +0 -0
  86. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7644/message.py +0 -0
  87. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7644/patch_op.py +0 -0
  88. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/rfc7644/search_request.py +0 -0
  89. {scim2_models-0.3.5 → scim2_models-0.3.6}/scim2_models/utils.py +0 -0
  90. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/__init__.py +0 -0
  91. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/conftest.py +0 -0
  92. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_dynamic_resources.py +0 -0
  93. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_dynamic_schemas.py +0 -0
  94. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_enterprise_user.py +0 -0
  95. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_errors.py +0 -0
  96. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_group.py +0 -0
  97. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_list_response.py +0 -0
  98. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_model_attributes.py +0 -0
  99. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_model_serialization.py +0 -0
  100. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_model_validation.py +0 -0
  101. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_models.py +0 -0
  102. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_patch_op.py +0 -0
  103. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_resource_extension.py +0 -0
  104. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_schema.py +0 -0
  105. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_search_request.py +0 -0
  106. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_service_provider_configuration.py +0 -0
  107. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_user.py +0 -0
  108. {scim2_models-0.3.5 → scim2_models-0.3.6}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scim2-models
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: SCIM2 models serialization and validation with pydantic
5
5
  Project-URL: documentation, https://scim2-models.readthedocs.io
6
6
  Project-URL: repository, https://github.com/python-scim/scim2-models
@@ -1,6 +1,14 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ [0.3.6] - 2025-07-02
5
+ --------------------
6
+
7
+ Added
8
+ ^^^^^
9
+ - Fix :meth:`ResourceType.from_resource <scim2_models.ResourceType.from_resource>`
10
+ usage for resources with several extensions. :pr:`95`
11
+
4
12
  [0.3.5] - 2025-06-05
5
13
  --------------------
6
14
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "scim2-models"
7
- version = "0.3.5"
7
+ version = "0.3.6"
8
8
  description = "SCIM2 models serialization and validation with pydantic"
9
9
  authors = [{name="Yaal Coop", email="contact@yaal.coop"}]
10
10
  license = {file = "LICENSE"}
@@ -4,7 +4,6 @@ from typing import Any
4
4
  from typing import Generic
5
5
  from typing import Optional
6
6
  from typing import TypeVar
7
- from typing import Union
8
7
  from typing import get_args
9
8
  from typing import get_origin
10
9
 
@@ -24,6 +23,7 @@ from ..base import Returned
24
23
  from ..base import Uniqueness
25
24
  from ..base import URIReference
26
25
  from ..base import is_complex_attribute
26
+ from ..utils import UNION_TYPES
27
27
  from ..utils import normalize_attribute_name
28
28
 
29
29
 
@@ -117,7 +117,7 @@ class ResourceMetaclass(BaseModelType):
117
117
  extensions = kwargs["__pydantic_generic_metadata__"]["args"][0]
118
118
  extensions = (
119
119
  get_args(extensions)
120
- if get_origin(extensions) == Union
120
+ if get_origin(extensions) in UNION_TYPES
121
121
  else [extensions]
122
122
  )
123
123
  for extension in extensions:
@@ -183,7 +183,8 @@ class Resource(BaseModel, Generic[AnyExtension], metaclass=ResourceMetaclass):
183
183
  extension_models = cls.__pydantic_generic_metadata__.get("args", [])
184
184
  extension_models = (
185
185
  get_args(extension_models[0])
186
- if len(extension_models) == 1 and get_origin(extension_models[0]) == Union
186
+ if len(extension_models) == 1
187
+ and get_origin(extension_models[0]) in UNION_TYPES
187
188
  else extension_models
188
189
  )
189
190
 
@@ -301,7 +302,7 @@ def model_to_schema(model: type[BaseModel]):
301
302
 
302
303
  def get_reference_types(type) -> list[str]:
303
304
  first_arg = get_args(type)[0]
304
- types = get_args(first_arg) if get_origin(first_arg) == Union else [first_arg]
305
+ types = get_args(first_arg) if get_origin(first_arg) in UNION_TYPES else [first_arg]
305
306
 
306
307
  def serialize_ref_type(ref_type):
307
308
  if ref_type == URIReference:
@@ -1,5 +1,7 @@
1
1
  from typing import Annotated
2
2
  from typing import Optional
3
+ from typing import get_args
4
+ from typing import get_origin
3
5
 
4
6
  from pydantic import Field
5
7
  from typing_extensions import Self
@@ -11,6 +13,7 @@ from ..base import Reference
11
13
  from ..base import Required
12
14
  from ..base import Returned
13
15
  from ..base import URIReference
16
+ from ..utils import UNION_TYPES
14
17
  from .resource import Resource
15
18
 
16
19
 
@@ -82,7 +85,16 @@ class ResourceType(Resource):
82
85
  """Build a naive ResourceType from a resource model."""
83
86
  schema = resource_model.model_fields["schemas"].default[0]
84
87
  name = schema.split(":")[-1]
85
- extensions = resource_model.__pydantic_generic_metadata__["args"]
88
+ if resource_model.__pydantic_generic_metadata__["args"]:
89
+ extensions = resource_model.__pydantic_generic_metadata__["args"][0]
90
+ extensions = (
91
+ get_args(extensions)
92
+ if get_origin(extensions) in UNION_TYPES
93
+ else [extensions]
94
+ )
95
+ else:
96
+ extensions = []
97
+
86
98
  return ResourceType(
87
99
  id=name,
88
100
  name=name,
@@ -20,6 +20,7 @@ from ..base import BaseModelType
20
20
  from ..base import Context
21
21
  from ..base import Required
22
22
  from ..rfc7643.resource import AnyResource
23
+ from ..utils import UNION_TYPES
23
24
  from .message import Message
24
25
 
25
26
 
@@ -29,7 +30,7 @@ class ListResponseMetaclass(BaseModelType):
29
30
 
30
31
  https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions
31
32
  """
32
- if not get_origin(resource_union) == Union:
33
+ if get_origin(resource_union) not in UNION_TYPES:
33
34
  return resource_union
34
35
 
35
36
  resource_types = get_args(resource_union)
@@ -1,5 +1,10 @@
1
+ from typing import Annotated
2
+ from typing import Union
3
+
1
4
  from scim2_models import EnterpriseUser
5
+ from scim2_models import Extension
2
6
  from scim2_models import Reference
7
+ from scim2_models import Required
3
8
  from scim2_models import ResourceType
4
9
  from scim2_models import User
5
10
 
@@ -61,3 +66,32 @@ def test_from_resource_with_extensions():
61
66
  == "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
62
67
  )
63
68
  assert not enterprise_user_rt.schema_extensions[0].required
69
+
70
+
71
+ def test_from_resource_with_mulitple_extensions():
72
+ class TestExtension(Extension):
73
+ schemas: Annotated[list[str], Required.true] = [
74
+ "urn:ietf:params:scim:schemas:extension:Test:1.0:User"
75
+ ]
76
+
77
+ test: Union[str, None] = None
78
+ test2: Union[list[str], None] = None
79
+
80
+ enterprise_user_rt = ResourceType.from_resource(
81
+ User[Union[EnterpriseUser, TestExtension]]
82
+ )
83
+ assert enterprise_user_rt.id == "User"
84
+ assert enterprise_user_rt.name == "User"
85
+ assert enterprise_user_rt.description == "User"
86
+ assert enterprise_user_rt.endpoint == "/Users"
87
+ assert enterprise_user_rt.schema_ == "urn:ietf:params:scim:schemas:core:2.0:User"
88
+ assert (
89
+ enterprise_user_rt.schema_extensions[0].schema_
90
+ == "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
91
+ )
92
+ assert not enterprise_user_rt.schema_extensions[0].required
93
+ assert (
94
+ enterprise_user_rt.schema_extensions[1].schema_
95
+ == "urn:ietf:params:scim:schemas:extension:Test:1.0:User"
96
+ )
97
+ assert not enterprise_user_rt.schema_extensions[1].required