scim2-models 0.3.6__tar.gz → 0.4.0__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 (130) hide show
  1. {scim2_models-0.3.6 → scim2_models-0.4.0}/.github/workflows/tests.yaml +14 -0
  2. {scim2_models-0.3.6 → scim2_models-0.4.0}/.pre-commit-config.yaml +4 -2
  3. {scim2_models-0.3.6 → scim2_models-0.4.0}/PKG-INFO +1 -1
  4. {scim2_models-0.3.6 → scim2_models-0.4.0}/conftest.py +1 -1
  5. {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/changelog.rst +21 -0
  6. {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/tutorial.rst +45 -5
  7. {scim2_models-0.3.6 → scim2_models-0.4.0}/pyproject.toml +16 -5
  8. scim2_models-0.4.0/scim2_models/__init__.py +109 -0
  9. scim2_models-0.4.0/scim2_models/annotations.py +104 -0
  10. scim2_models-0.4.0/scim2_models/attributes.py +57 -0
  11. scim2_models-0.4.0/scim2_models/base.py +545 -0
  12. {scim2_models-0.3.6 → scim2_models-0.4.0}/scim2_models/constants.py +3 -3
  13. scim2_models-0.4.0/scim2_models/context.py +168 -0
  14. {scim2_models-0.3.6/scim2_models/rfc7644 → scim2_models-0.4.0/scim2_models/messages}/bulk.py +4 -4
  15. {scim2_models-0.3.6/scim2_models/rfc7644 → scim2_models-0.4.0/scim2_models/messages}/error.py +13 -13
  16. scim2_models-0.4.0/scim2_models/messages/list_response.py +75 -0
  17. scim2_models-0.4.0/scim2_models/messages/message.py +119 -0
  18. scim2_models-0.4.0/scim2_models/messages/patch_op.py +478 -0
  19. {scim2_models-0.3.6/scim2_models/rfc7644 → scim2_models-0.4.0/scim2_models/messages}/search_request.py +55 -6
  20. scim2_models-0.4.0/scim2_models/reference.py +82 -0
  21. {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/enterprise_user.py +4 -4
  22. {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/group.py +5 -5
  23. scim2_models-0.4.0/scim2_models/resources/resource.py +468 -0
  24. {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/resource_type.py +13 -22
  25. {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/schema.py +51 -45
  26. {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/service_provider_config.py +7 -7
  27. {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/user.py +9 -9
  28. scim2_models-0.4.0/scim2_models/scim_object.py +66 -0
  29. scim2_models-0.4.0/scim2_models/urn.py +109 -0
  30. scim2_models-0.4.0/scim2_models/utils.py +201 -0
  31. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_dynamic_resources.py +15 -15
  32. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_dynamic_schemas.py +30 -9
  33. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_errors.py +1 -1
  34. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_model_attributes.py +84 -59
  35. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_model_serialization.py +85 -6
  36. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_model_validation.py +6 -6
  37. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_models.py +2 -2
  38. scim2_models-0.4.0/tests/test_patch_op_add.py +278 -0
  39. scim2_models-0.4.0/tests/test_patch_op_extensions.py +335 -0
  40. scim2_models-0.4.0/tests/test_patch_op_remove.py +289 -0
  41. scim2_models-0.4.0/tests/test_patch_op_replace.py +219 -0
  42. scim2_models-0.4.0/tests/test_patch_op_validation.py +464 -0
  43. scim2_models-0.4.0/tests/test_path_validation.py +112 -0
  44. scim2_models-0.4.0/tests/test_reference.py +80 -0
  45. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_resource_extension.py +41 -0
  46. scim2_models-0.4.0/tests/test_search_request.py +243 -0
  47. scim2_models-0.4.0/tests/test_utils.py +29 -0
  48. {scim2_models-0.3.6 → scim2_models-0.4.0}/uv.lock +147 -147
  49. scim2_models-0.3.6/scim2_models/__init__.py +0 -109
  50. scim2_models-0.3.6/scim2_models/base.py +0 -904
  51. scim2_models-0.3.6/scim2_models/rfc7643/resource.py +0 -355
  52. scim2_models-0.3.6/scim2_models/rfc7644/list_response.py +0 -136
  53. scim2_models-0.3.6/scim2_models/rfc7644/message.py +0 -10
  54. scim2_models-0.3.6/scim2_models/rfc7644/patch_op.py +0 -70
  55. scim2_models-0.3.6/scim2_models/utils.py +0 -99
  56. scim2_models-0.3.6/tests/test_patch_op.py +0 -37
  57. scim2_models-0.3.6/tests/test_search_request.py +0 -79
  58. scim2_models-0.3.6/tests/test_utils.py +0 -15
  59. {scim2_models-0.3.6 → scim2_models-0.4.0}/.github/FUNDING.yml +0 -0
  60. {scim2_models-0.3.6 → scim2_models-0.4.0}/.github/workflows/release.yml +0 -0
  61. {scim2_models-0.3.6 → scim2_models-0.4.0}/.gitignore +0 -0
  62. {scim2_models-0.3.6 → scim2_models-0.4.0}/.readthedocs.yml +0 -0
  63. {scim2_models-0.3.6 → scim2_models-0.4.0}/LICENSE +0 -0
  64. {scim2_models-0.3.6 → scim2_models-0.4.0}/README.md +0 -0
  65. {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/__init__.py +0 -0
  66. {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/conf.py +0 -0
  67. {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/contributing.rst +0 -0
  68. {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/index.rst +0 -0
  69. {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/reference.rst +0 -0
  70. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.1-user-minimal.json +0 -0
  71. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.2-user-full.json +0 -0
  72. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.3-enterprise_user.json +0 -0
  73. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.4-group.json +0 -0
  74. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.5-service_provider_configuration.json +0 -0
  75. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.6-resource_type-group.json +0 -0
  76. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.6-resource_type-user.json +0 -0
  77. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.1-schema-enterprise_user.json +0 -0
  78. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.1-schema-group.json +0 -0
  79. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.1-schema-user.json +0 -0
  80. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.2-schema-resource_type.json +0 -0
  81. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.2-schema-schema.json +0 -0
  82. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.2-schema-service_provider_configuration.json +0 -0
  83. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.12-error-bad_request.json +0 -0
  84. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.12-error-not_found.json +0 -0
  85. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.14-user-post_request.json +0 -0
  86. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.14-user-post_response.json +0 -0
  87. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.3-user-post_request.json +0 -0
  88. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.3-user-post_response.json +0 -0
  89. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.4.1-user-known-resource.json +0 -0
  90. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.4.2-list_response-partial_attributes.json +0 -0
  91. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.4.3-list_response-post_query.json +0 -0
  92. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.4.3-search_request.json +0 -0
  93. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.1-user-put_request.json +0 -0
  94. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.1-user-put_response.json +0 -0
  95. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.1-patch_op-add_emails.json +0 -0
  96. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.1-patch_op-add_members.json +0 -0
  97. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.2-patch_op-remove_all_members.json +0 -0
  98. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.2-patch_op-remove_and_add_one_member.json +0 -0
  99. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.2-patch_op-remove_multi_complex_value.json +0 -0
  100. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.2-patch_op-remove_one_member.json +0 -0
  101. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.3-patch_op-replace_all_email_values.json +0 -0
  102. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.3-patch_op-replace_all_members.json +0 -0
  103. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.3-patch_op-replace_street_address.json +0 -0
  104. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.3-patch_op-replace_user_work_address.json +0 -0
  105. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.6-error-not_found.json +0 -0
  106. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.1-bulk_request-circular_conflict.json +0 -0
  107. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.1-list_response-circular_reference.json +0 -0
  108. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.2-bulk_request-enterprise_user.json +0 -0
  109. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.2-bulk_request-temporary_identifier.json +0 -0
  110. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.2-bulk_response-temporary_identifier.json +0 -0
  111. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-bulk_request-multiple_operations.json +0 -0
  112. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-bulk_response-error_invalid_syntax.json +0 -0
  113. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-bulk_response-multiple_errors.json +0 -0
  114. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-bulk_response-multiple_operations.json +0 -0
  115. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-error-invalid_syntax.json +0 -0
  116. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.4-error-payload_too_large.json +0 -0
  117. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.9-user-partial_response.json +0 -0
  118. {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-4-list_response-resource_types.json +0 -0
  119. {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/messages}/__init__.py +0 -0
  120. {scim2_models-0.3.6 → scim2_models-0.4.0}/scim2_models/py.typed +0 -0
  121. {scim2_models-0.3.6/scim2_models/rfc7644 → scim2_models-0.4.0/scim2_models/resources}/__init__.py +0 -0
  122. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/__init__.py +0 -0
  123. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/conftest.py +0 -0
  124. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_enterprise_user.py +0 -0
  125. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_group.py +0 -0
  126. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_list_response.py +0 -0
  127. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_resource_type.py +0 -0
  128. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_schema.py +0 -0
  129. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_service_provider_configuration.py +0 -0
  130. {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_user.py +0 -0
@@ -34,6 +34,20 @@ jobs:
34
34
  - name: Run tests
35
35
  run: uv run pytest --showlocals
36
36
 
37
+ coverage:
38
+ name: py${{ matrix.python }} unit tests
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+ - name: Install uv
43
+ uses: astral-sh/setup-uv@v3
44
+ with:
45
+ enable-cache: true
46
+ - name: Install Python ${{ matrix.python }}
47
+ run: uv python install ${{ matrix.python }}
48
+ - name: Run tests
49
+ run: uv run pytest --cov --cov-fail-under=100
50
+
37
51
  downstream-tests:
38
52
  name: py${{ matrix.python }} ${{ matrix.downstream }} downstream unit tests
39
53
  runs-on: ubuntu-latest
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  repos:
3
3
  - repo: https://github.com/astral-sh/ruff-pre-commit
4
- rev: 'v0.11.10'
4
+ rev: 'v0.12.4'
5
5
  hooks:
6
6
  - id: ruff
7
7
  args: [--fix, --exit-non-zero-on-fix]
@@ -16,9 +16,11 @@ repos:
16
16
  exclude: "\\.svg$|\\.map$|\\.min\\.css$|\\.min\\.js$|\\.po$|\\.pot$"
17
17
  - id: check-toml
18
18
  - repo: https://github.com/pre-commit/mirrors-mypy
19
- rev: v1.15.0
19
+ rev: v1.17.0
20
20
  hooks:
21
21
  - id: mypy
22
+ additional_dependencies:
23
+ - pydantic[email]>=2.7.0
22
24
  - repo: https://github.com/codespell-project/codespell
23
25
  rev: v2.4.1
24
26
  hooks:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scim2-models
3
- Version: 0.3.6
3
+ Version: 0.4.0
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
@@ -5,7 +5,7 @@ import scim2_models
5
5
 
6
6
 
7
7
  @pytest.fixture(autouse=True)
8
- def add_doctest_namespace(doctest_namespace):
8
+ def add_doctest_namespace(doctest_namespace: dict) -> dict:
9
9
  doctest_namespace["pydantic"] = pydantic
10
10
  imports = {item: getattr(scim2_models, item) for item in scim2_models.__all__}
11
11
  doctest_namespace.update(imports)
@@ -1,6 +1,27 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ [0.4.0] - 2025-07-23
5
+ --------------------
6
+
7
+ Added
8
+ ^^^^^
9
+ - Proper path validation for :attr:`~scim2_models.SearchRequest.attributes`, :attr:`~scim2_models.SearchRequest.excluded_attributes` and :attr:`~scim2_models.SearchRequest.sort_by`.
10
+ - Implement :meth:`~scim2_models.PatchOp.patch`
11
+
12
+ Fixed
13
+ ^^^^^
14
+ - When using ``model_dump``, ignore invalid ``attributes`` and ``excluded_attributes``
15
+ as suggested by RFC7644.
16
+ - Don't normalize attributes typed with :data:`Any`. :issue:`20`
17
+
18
+ [0.3.7] - 2025-07-17
19
+ --------------------
20
+
21
+ Fixed
22
+ ^^^^^
23
+ - All non strict mypy type annotations are fixed.
24
+
4
25
  [0.3.6] - 2025-07-02
5
26
  --------------------
6
27
 
@@ -204,9 +204,9 @@ If a response resource type cannot be found, a ``pydantic.ValidationError`` will
204
204
  >>> response = ListResponse[Union[User, Group]].model_validate(payload)
205
205
  >>> user, group = response.resources
206
206
  >>> type(user)
207
- <class 'scim2_models.rfc7643.user.User'>
207
+ <class 'scim2_models.resources.user.User'>
208
208
  >>> type(group)
209
- <class 'scim2_models.rfc7643.group.Group'>
209
+ <class 'scim2_models.resources.group.Group'>
210
210
 
211
211
 
212
212
  Schema extensions
@@ -403,9 +403,49 @@ This can be used by client applications that intends to dynamically discover ser
403
403
  :language: json
404
404
  :caption: schema-group.json
405
405
 
406
- Bulk and Patch operations
407
- =========================
406
+ Patch operations
407
+ ================
408
+
409
+ :class:`~scim2_models.PatchOp` allows you to apply patch operations to modify SCIM resources.
410
+ The :meth:`~scim2_models.PatchOp.patch` method applies operations in sequence and returns whether the resource was modified. The return code is a boolean indicating whether the object have been modified by the operations.
411
+
412
+ .. note::
413
+ :class:`~scim2_models.PatchOp` takes a type parameter that should be the class of the resource
414
+ that is expected to be patched.
415
+
416
+ .. code-block:: python
417
+
418
+ >>> from scim2_models import User, PatchOp, PatchOperation
419
+ >>> user = User(user_name="john.doe", nick_name="Johnny")
420
+
421
+ >>> payload = {
422
+ ... "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
423
+ ... "Operations": [
424
+ ... {"op": "replace", "path": "nickName", "value": "John" },
425
+ ... {"op": "add", "path": "emails", "value": [{"value": "john@example.com"}]},
426
+ ... ]
427
+ ... }
428
+ >>> patch = PatchOp[User].model_validate(
429
+ ... payload, scim_ctx=Context.RESOURCE_PATCH_REQUEST
430
+ ... )
431
+
432
+ >>> modified = patch.patch(user)
433
+ >>> print(modified)
434
+ True
435
+ >>> print(user.nick_name)
436
+ John
437
+ >>> print(user.emails[0].value)
438
+ john@example.com
439
+
440
+ .. warning::
441
+
442
+ Patch operations are validated in the :attr:`~scim2_models.Context.RESOURCE_PATCH_REQUEST`
443
+ context. Make sure to validate patch operations with the correct context to
444
+ ensure proper validation of mutability and required constraints.
445
+
446
+ Bulk operations
447
+ ===============
408
448
 
409
449
  .. todo::
410
450
 
411
- Bulk and Patch operations are not implemented yet, but any help is welcome!
451
+ Bulk operations are not implemented yet, but any help is welcome!
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "scim2-models"
7
- version = "0.3.6"
7
+ version = "0.4.0"
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"}
@@ -68,6 +68,7 @@ exclude_lines = [
68
68
  "pragma: no cover",
69
69
  "raise NotImplementedError",
70
70
  "except ImportError",
71
+ "if TYPE_CHECKING:",
71
72
  ]
72
73
 
73
74
  [tool.ruff.lint]
@@ -104,10 +105,20 @@ docstring-code-format = true
104
105
  addopts = "--doctest-modules --doctest-glob='*.rst'"
105
106
  doctest_optionflags= "ALLOW_UNICODE IGNORE_EXCEPTION_DETAIL ELLIPSIS"
106
107
 
107
- # [tool.mypy]
108
- # plugins = [
109
- # "pydantic.mypy"
110
- # ]
108
+ [tool.mypy]
109
+ plugins = [
110
+ "pydantic.mypy"
111
+ ]
112
+ warn_unused_ignores = true
113
+ exclude = [
114
+ "tests/",
115
+ "conftest.py",
116
+ ]
117
+
118
+ [tool.pydantic-mypy]
119
+ init_forbid_extra = true
120
+ init_typed = true
121
+ warn_required_dynamic_aliases = true
111
122
 
112
123
  [tool.tox]
113
124
  requires = ["tox>=4.19"]
@@ -0,0 +1,109 @@
1
+ from .annotations import CaseExact
2
+ from .annotations import Mutability
3
+ from .annotations import Required
4
+ from .annotations import Returned
5
+ from .annotations import Uniqueness
6
+ from .attributes import ComplexAttribute
7
+ from .attributes import MultiValuedComplexAttribute
8
+ from .base import BaseModel
9
+ from .context import Context
10
+ from .messages.bulk import BulkOperation
11
+ from .messages.bulk import BulkRequest
12
+ from .messages.bulk import BulkResponse
13
+ from .messages.error import Error
14
+ from .messages.list_response import ListResponse
15
+ from .messages.message import Message
16
+ from .messages.patch_op import PatchOp
17
+ from .messages.patch_op import PatchOperation
18
+ from .messages.search_request import SearchRequest
19
+ from .reference import ExternalReference
20
+ from .reference import Reference
21
+ from .reference import URIReference
22
+ from .resources.enterprise_user import EnterpriseUser
23
+ from .resources.enterprise_user import Manager
24
+ from .resources.group import Group
25
+ from .resources.group import GroupMember
26
+ from .resources.resource import AnyExtension
27
+ from .resources.resource import AnyResource
28
+ from .resources.resource import Extension
29
+ from .resources.resource import Meta
30
+ from .resources.resource import Resource
31
+ from .resources.resource_type import ResourceType
32
+ from .resources.resource_type import SchemaExtension
33
+ from .resources.schema import Attribute
34
+ from .resources.schema import Schema
35
+ from .resources.service_provider_config import AuthenticationScheme
36
+ from .resources.service_provider_config import Bulk
37
+ from .resources.service_provider_config import ChangePassword
38
+ from .resources.service_provider_config import ETag
39
+ from .resources.service_provider_config import Filter
40
+ from .resources.service_provider_config import Patch
41
+ from .resources.service_provider_config import ServiceProviderConfig
42
+ from .resources.service_provider_config import Sort
43
+ from .resources.user import Address
44
+ from .resources.user import Email
45
+ from .resources.user import Entitlement
46
+ from .resources.user import GroupMembership
47
+ from .resources.user import Im
48
+ from .resources.user import Name
49
+ from .resources.user import PhoneNumber
50
+ from .resources.user import Photo
51
+ from .resources.user import Role
52
+ from .resources.user import User
53
+ from .resources.user import X509Certificate
54
+
55
+ __all__ = [
56
+ "Address",
57
+ "AnyResource",
58
+ "AnyExtension",
59
+ "Attribute",
60
+ "AuthenticationScheme",
61
+ "BaseModel",
62
+ "Bulk",
63
+ "BulkOperation",
64
+ "BulkRequest",
65
+ "BulkResponse",
66
+ "CaseExact",
67
+ "ChangePassword",
68
+ "ComplexAttribute",
69
+ "Context",
70
+ "ETag",
71
+ "Email",
72
+ "EnterpriseUser",
73
+ "Entitlement",
74
+ "Error",
75
+ "ExternalReference",
76
+ "Extension",
77
+ "Filter",
78
+ "Group",
79
+ "GroupMember",
80
+ "GroupMembership",
81
+ "Im",
82
+ "ListResponse",
83
+ "Manager",
84
+ "Message",
85
+ "Meta",
86
+ "Mutability",
87
+ "MultiValuedComplexAttribute",
88
+ "Name",
89
+ "Patch",
90
+ "PatchOp",
91
+ "PatchOperation",
92
+ "PhoneNumber",
93
+ "Photo",
94
+ "Reference",
95
+ "Required",
96
+ "Resource",
97
+ "ResourceType",
98
+ "Returned",
99
+ "Role",
100
+ "Schema",
101
+ "SchemaExtension",
102
+ "SearchRequest",
103
+ "ServiceProviderConfig",
104
+ "Sort",
105
+ "Uniqueness",
106
+ "URIReference",
107
+ "User",
108
+ "X509Certificate",
109
+ ]
@@ -0,0 +1,104 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Mutability(str, Enum):
5
+ """A single keyword indicating the circumstances under which the value of the attribute can be (re)defined."""
6
+
7
+ read_only = "readOnly"
8
+ """The attribute SHALL NOT be modified."""
9
+
10
+ read_write = "readWrite"
11
+ """The attribute MAY be updated and read at any time."""
12
+
13
+ immutable = "immutable"
14
+ """The attribute MAY be defined at resource creation (e.g., POST) or at
15
+ record replacement via a request (e.g., a PUT).
16
+
17
+ The attribute SHALL NOT be updated.
18
+ """
19
+
20
+ write_only = "writeOnly"
21
+ """The attribute MAY be updated at any time.
22
+
23
+ Attribute values SHALL NOT be returned (e.g., because the value is a
24
+ stored hash). Note: An attribute with a mutability of "writeOnly"
25
+ usually also has a returned setting of "never".
26
+ """
27
+
28
+ _default = read_write
29
+
30
+
31
+ class Returned(str, Enum):
32
+ """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."""
33
+
34
+ always = "always" # cannot be excluded
35
+ """The attribute is always returned, regardless of the contents of the
36
+ "attributes" parameter.
37
+
38
+ For example, "id" is always returned to identify a SCIM resource.
39
+ """
40
+
41
+ never = "never" # always excluded
42
+ """The attribute is never returned, regardless of the contents of the
43
+ "attributes" parameter."""
44
+
45
+ default = "default" # included by default but can be excluded
46
+ """The attribute is returned by default in all SCIM operation responses
47
+ where attribute values are returned, unless it is explicitly excluded."""
48
+
49
+ request = "request" # excluded by default but can be included
50
+ """The attribute is returned in response to any PUT, POST, or PATCH
51
+ operations if specified in the "attributes" parameter."""
52
+
53
+ _default = default
54
+
55
+
56
+ class Uniqueness(str, Enum):
57
+ """A single keyword value that specifies how the service provider enforces uniqueness of attribute values."""
58
+
59
+ none = "none"
60
+ """The values are not intended to be unique in any way."""
61
+
62
+ server = "server"
63
+ """The value SHOULD be unique within the context of the current SCIM
64
+ endpoint (or tenancy) and MAY be globally unique (e.g., a "username", email
65
+ address, or other server-generated key or counter).
66
+
67
+ No two resources on the same server SHOULD possess the same value.
68
+ """
69
+
70
+ global_ = "global"
71
+ """The value SHOULD be globally unique (e.g., an email address, a GUID, or
72
+ other value).
73
+
74
+ No two resources on any server SHOULD possess the same value.
75
+ """
76
+
77
+ _default = none
78
+
79
+
80
+ class Required(Enum):
81
+ """A Boolean value that specifies whether the attribute is required or not.
82
+
83
+ Missing required attributes raise a :class:`~pydantic.ValidationError` on :attr:`~scim2_models.Context.RESOURCE_CREATION_REQUEST` and :attr:`~scim2_models.Context.RESOURCE_REPLACEMENT_REQUEST` validations.
84
+ """
85
+
86
+ true = True
87
+ false = False
88
+
89
+ _default = false
90
+
91
+ def __bool__(self) -> bool:
92
+ return self.value
93
+
94
+
95
+ class CaseExact(Enum):
96
+ """A Boolean value that specifies whether a string attribute is case- sensitive or not."""
97
+
98
+ true = True
99
+ false = False
100
+
101
+ _default = false
102
+
103
+ def __bool__(self) -> bool:
104
+ return self.value
@@ -0,0 +1,57 @@
1
+ from inspect import isclass
2
+ from typing import Annotated
3
+ from typing import Any
4
+ from typing import Optional
5
+ from typing import get_origin
6
+
7
+ from pydantic import Field
8
+
9
+ from .annotations import Mutability
10
+
11
+ # This import will work because we'll import this module after BaseModel is defined
12
+ from .base import BaseModel
13
+ from .reference import Reference
14
+
15
+
16
+ class ComplexAttribute(BaseModel):
17
+ """A complex attribute as defined in :rfc:`RFC7643 §2.3.8 <7643#section-2.3.8>`."""
18
+
19
+ _attribute_urn: Optional[str] = None
20
+
21
+ def get_attribute_urn(self, field_name: str) -> str:
22
+ """Build the full URN of the attribute.
23
+
24
+ See :rfc:`RFC7644 §3.10 <7644#section-3.10>`.
25
+ """
26
+ alias = (
27
+ self.__class__.model_fields[field_name].serialization_alias or field_name
28
+ )
29
+ return f"{self._attribute_urn}.{alias}"
30
+
31
+
32
+ class MultiValuedComplexAttribute(ComplexAttribute):
33
+ type: Optional[str] = None
34
+ """A label indicating the attribute's function."""
35
+
36
+ primary: Optional[bool] = None
37
+ """A Boolean value indicating the 'primary' or preferred attribute value
38
+ for this attribute."""
39
+
40
+ display: Annotated[Optional[str], Mutability.immutable] = None
41
+ """A human-readable name, primarily used for display purposes."""
42
+
43
+ value: Optional[Any] = None
44
+ """The value of an entitlement."""
45
+
46
+ ref: Optional[Reference] = Field(None, serialization_alias="$ref")
47
+ """The reference URI of a target resource, if the attribute is a
48
+ reference."""
49
+
50
+
51
+ def is_complex_attribute(type_: type) -> bool:
52
+ # issubclass raise a TypeError with 'Reference' on python < 3.11
53
+ return (
54
+ get_origin(type_) != Reference
55
+ and isclass(type_)
56
+ and issubclass(type_, (ComplexAttribute, MultiValuedComplexAttribute))
57
+ )