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.
- {scim2_models-0.3.6 → scim2_models-0.4.0}/.github/workflows/tests.yaml +14 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/.pre-commit-config.yaml +4 -2
- {scim2_models-0.3.6 → scim2_models-0.4.0}/PKG-INFO +1 -1
- {scim2_models-0.3.6 → scim2_models-0.4.0}/conftest.py +1 -1
- {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/changelog.rst +21 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/tutorial.rst +45 -5
- {scim2_models-0.3.6 → scim2_models-0.4.0}/pyproject.toml +16 -5
- scim2_models-0.4.0/scim2_models/__init__.py +109 -0
- scim2_models-0.4.0/scim2_models/annotations.py +104 -0
- scim2_models-0.4.0/scim2_models/attributes.py +57 -0
- scim2_models-0.4.0/scim2_models/base.py +545 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/scim2_models/constants.py +3 -3
- scim2_models-0.4.0/scim2_models/context.py +168 -0
- {scim2_models-0.3.6/scim2_models/rfc7644 → scim2_models-0.4.0/scim2_models/messages}/bulk.py +4 -4
- {scim2_models-0.3.6/scim2_models/rfc7644 → scim2_models-0.4.0/scim2_models/messages}/error.py +13 -13
- scim2_models-0.4.0/scim2_models/messages/list_response.py +75 -0
- scim2_models-0.4.0/scim2_models/messages/message.py +119 -0
- scim2_models-0.4.0/scim2_models/messages/patch_op.py +478 -0
- {scim2_models-0.3.6/scim2_models/rfc7644 → scim2_models-0.4.0/scim2_models/messages}/search_request.py +55 -6
- scim2_models-0.4.0/scim2_models/reference.py +82 -0
- {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/enterprise_user.py +4 -4
- {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/group.py +5 -5
- scim2_models-0.4.0/scim2_models/resources/resource.py +468 -0
- {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/resource_type.py +13 -22
- {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/schema.py +51 -45
- {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/service_provider_config.py +7 -7
- {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/resources}/user.py +9 -9
- scim2_models-0.4.0/scim2_models/scim_object.py +66 -0
- scim2_models-0.4.0/scim2_models/urn.py +109 -0
- scim2_models-0.4.0/scim2_models/utils.py +201 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_dynamic_resources.py +15 -15
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_dynamic_schemas.py +30 -9
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_errors.py +1 -1
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_model_attributes.py +84 -59
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_model_serialization.py +85 -6
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_model_validation.py +6 -6
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_models.py +2 -2
- scim2_models-0.4.0/tests/test_patch_op_add.py +278 -0
- scim2_models-0.4.0/tests/test_patch_op_extensions.py +335 -0
- scim2_models-0.4.0/tests/test_patch_op_remove.py +289 -0
- scim2_models-0.4.0/tests/test_patch_op_replace.py +219 -0
- scim2_models-0.4.0/tests/test_patch_op_validation.py +464 -0
- scim2_models-0.4.0/tests/test_path_validation.py +112 -0
- scim2_models-0.4.0/tests/test_reference.py +80 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_resource_extension.py +41 -0
- scim2_models-0.4.0/tests/test_search_request.py +243 -0
- scim2_models-0.4.0/tests/test_utils.py +29 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/uv.lock +147 -147
- scim2_models-0.3.6/scim2_models/__init__.py +0 -109
- scim2_models-0.3.6/scim2_models/base.py +0 -904
- scim2_models-0.3.6/scim2_models/rfc7643/resource.py +0 -355
- scim2_models-0.3.6/scim2_models/rfc7644/list_response.py +0 -136
- scim2_models-0.3.6/scim2_models/rfc7644/message.py +0 -10
- scim2_models-0.3.6/scim2_models/rfc7644/patch_op.py +0 -70
- scim2_models-0.3.6/scim2_models/utils.py +0 -99
- scim2_models-0.3.6/tests/test_patch_op.py +0 -37
- scim2_models-0.3.6/tests/test_search_request.py +0 -79
- scim2_models-0.3.6/tests/test_utils.py +0 -15
- {scim2_models-0.3.6 → scim2_models-0.4.0}/.github/FUNDING.yml +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/.github/workflows/release.yml +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/.gitignore +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/.readthedocs.yml +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/LICENSE +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/README.md +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/__init__.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/conf.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/contributing.rst +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/index.rst +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/doc/reference.rst +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.1-user-minimal.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.2-user-full.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.3-enterprise_user.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.4-group.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.5-service_provider_configuration.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.6-resource_type-group.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.6-resource_type-user.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.1-schema-enterprise_user.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.1-schema-group.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.1-schema-user.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.2-schema-resource_type.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.2-schema-schema.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7643-8.7.2-schema-service_provider_configuration.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.12-error-bad_request.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.12-error-not_found.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.14-user-post_request.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.14-user-post_response.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.3-user-post_request.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.3-user-post_response.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.4.1-user-known-resource.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.4.2-list_response-partial_attributes.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.4.3-list_response-post_query.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.4.3-search_request.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.1-user-put_request.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.1-user-put_response.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.1-patch_op-add_emails.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.1-patch_op-add_members.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.2-patch_op-remove_all_members.json +0 -0
- {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
- {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
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.2-patch_op-remove_one_member.json +0 -0
- {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
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.3-patch_op-replace_all_members.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.5.2.3-patch_op-replace_street_address.json +0 -0
- {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
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.6-error-not_found.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.1-bulk_request-circular_conflict.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.1-list_response-circular_reference.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.2-bulk_request-enterprise_user.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.2-bulk_request-temporary_identifier.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.2-bulk_response-temporary_identifier.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-bulk_request-multiple_operations.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-bulk_response-error_invalid_syntax.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-bulk_response-multiple_errors.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-bulk_response-multiple_operations.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.3-error-invalid_syntax.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.7.4-error-payload_too_large.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-3.9-user-partial_response.json +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/samples/rfc7644-4-list_response-resource_types.json +0 -0
- {scim2_models-0.3.6/scim2_models/rfc7643 → scim2_models-0.4.0/scim2_models/messages}/__init__.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/scim2_models/py.typed +0 -0
- {scim2_models-0.3.6/scim2_models/rfc7644 → scim2_models-0.4.0/scim2_models/resources}/__init__.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/__init__.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/conftest.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_enterprise_user.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_group.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_list_response.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_resource_type.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_schema.py +0 -0
- {scim2_models-0.3.6 → scim2_models-0.4.0}/tests/test_service_provider_configuration.py +0 -0
- {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.
|
|
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.
|
|
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
|
+
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.
|
|
207
|
+
<class 'scim2_models.resources.user.User'>
|
|
208
208
|
>>> type(group)
|
|
209
|
-
<class 'scim2_models.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
+
)
|