scim2-models 0.2.9__tar.gz → 0.2.11__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.2.9 → scim2_models-0.2.11}/PKG-INFO +1 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/doc/changelog.rst +19 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/pyproject.toml +1 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.2-user-full.json +1 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.3-enterprise_user.json +1 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7643/enterprise_user.py +3 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7643/group.py +4 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7643/resource.py +2 -2
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7643/resource_type.py +3 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7643/schema.py +12 -3
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7643/service_provider_config.py +3 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7643/user.py +4 -2
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7644/bulk.py +7 -2
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7644/error.py +4 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7644/list_response.py +4 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7644/message.py +4 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7644/patch_op.py +5 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7644/search_request.py +19 -1
- scim2_models-0.2.11/scim2_models/utils.py +99 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_dynamic_resources.py +1 -2
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_list_response.py +3 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_model_attributes.py +13 -11
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_model_serialization.py +3 -2
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_model_validation.py +3 -3
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_resource_extension.py +3 -1
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_schema.py +13 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_search_request.py +6 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/uv.lock +93 -93
- scim2_models-0.2.9/scim2_models/utils.py +0 -42
- {scim2_models-0.2.9 → scim2_models-0.2.11}/.github/FUNDING.yml +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/.github/workflows/release.yml +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/.github/workflows/tests.yaml +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/.gitignore +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/.pre-commit-config.yaml +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/.readthedocs.yml +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/LICENSE +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/README.md +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/conftest.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/doc/__init__.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/doc/conf.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/doc/contributing.rst +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/doc/index.rst +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/doc/reference.rst +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/doc/tutorial.rst +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.1-user-minimal.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.4-group.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.5-service_provider_configuration.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.6-resource_type-group.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.6-resource_type-user.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.7.1-schema-enterprise_user.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.7.1-schema-group.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.7.1-schema-user.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.7.2-schema-resource_type.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.7.2-schema-schema.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7643-8.7.2-schema-service_provider_configuration.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.12-error-bad_request.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.12-error-not_found.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.14-user-post_request.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.14-user-post_response.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.3-user-post_request.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.3-user-post_response.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.4.1-user-known-resource.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.4.2-list_response-partial_attributes.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.4.3-list_response-post_query.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.4.3-search_request.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.1-user-put_request.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.1-user-put_response.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.1-patch_op-add_emails.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.1-patch_op-add_members.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.2-patch_op-remove_all_members.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.2-patch_op-remove_and_add_one_member.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.2-patch_op-remove_multi_complex_value.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.2-patch_op-remove_one_member.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.3-patch_op-replace_all_email_values.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.3-patch_op-replace_all_members.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.3-patch_op-replace_street_address.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.5.2.3-patch_op-replace_user_work_address.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.6-error-not_found.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.1-bulk_request-circular_conflict.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.1-list_response-circular_reference.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.2-bulk_request-enterprise_user.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.2-bulk_request-temporary_identifier.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.2-bulk_response-temporary_identifier.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.3-bulk_request-multiple_operations.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.3-bulk_response-error_invalid_syntax.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.3-bulk_response-multiple_errors.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.3-bulk_response-multiple_operations.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.3-error-invalid_syntax.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.7.4-error-payload_too_large.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-3.9-user-partial_response.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/samples/rfc7644-4-list_response-resource_types.json +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/__init__.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/base.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/constants.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/py.typed +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7643/__init__.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/scim2_models/rfc7644/__init__.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/__init__.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/conftest.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_dynamic_schemas.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_enterprise_user.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_errors.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_group.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_models.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_patch_op.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_resource_type.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_service_provider_configuration.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_user.py +0 -0
- {scim2_models-0.2.9 → scim2_models-0.2.11}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: scim2-models
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.11
|
|
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,25 @@
|
|
|
1
1
|
Changelog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
[0.2.11] - 2024-12-08
|
|
5
|
+
---------------------
|
|
6
|
+
Added
|
|
7
|
+
^^^^^
|
|
8
|
+
- Implement :meth:`Schema.get_attribute <scim2_models.Schema.get_attribute>`.
|
|
9
|
+
- Implement :meth:`SearchRequest.start_index_0 <scim2_models.SearchRequest.start_index_0>`
|
|
10
|
+
and :meth:`SearchRequest.start_index_1 <scim2_models.SearchRequest.start_index_1>`.
|
|
11
|
+
|
|
12
|
+
[0.2.10] - 2024-12-02
|
|
13
|
+
---------------------
|
|
14
|
+
|
|
15
|
+
Changed
|
|
16
|
+
^^^^^^^
|
|
17
|
+
- The ``schema`` attribute is annotated with :attr:`~scim2_models.Required.true`.
|
|
18
|
+
|
|
19
|
+
Fixed
|
|
20
|
+
^^^^^
|
|
21
|
+
- ``Base64Bytes`` compatibility between pydantic 2.10+ and <2.10
|
|
22
|
+
|
|
4
23
|
[0.2.9] - 2024-12-02
|
|
5
24
|
--------------------
|
|
6
25
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scim2-models"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.11"
|
|
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"}
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
],
|
|
101
101
|
"x509Certificates": [
|
|
102
102
|
{
|
|
103
|
-
"value": "
|
|
103
|
+
"value": "MIIDQzCCAqygAwIBAgICEAAwDQYJKoZIhvcNAQEFBQAwTjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFDASBgNVBAoMC2V4YW1wbGUuY29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xMTEwMjIwNjI0MzFaFw0xMjEwMDQwNjI0MzFaMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtleGFtcGxlLmNvbTEhMB8GA1UEAwwYTXMuIEJhcmJhcmEgSiBKZW5zZW4gSUlJMSIwIAYJKoZIhvcNAQkBFhNiamVuc2VuQGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7Kr+Dcds/JQ5GwejJFcBIP682X3xpjis56AK02bc1FLgzdLI8auoR+cC9/Vrh5t66HkQIOdA4unHh0AaZ4xL5PhVbXIPMB5vAPKpzz5iPSi8xO8SL7I7SDhcBVJhqVqr3HgllEG6UClDdHO7nkLuwXq8HcISKkbT5WFTVfFZzidPl8HZ7DhXkZIRtJwBweq4bvm3hM1Os7UQH05ZS6cVDgweKNwdLLrT51ikSQG3DYrl+ft781UQRIqxgwqCfXEuDiinPh0kkvIi5jivVu1Z9QiwlYEdRbLJ4zJQBmDrSGTMYn4lRc2HgHO4DqB/bnMVorHB0CC6AV1QoFK4GPe1LwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU8pD0U0vsZIsaA16lL8En8bx0F/gwHwYDVR0jBBgwFoAUdGeKitcaF7gnzsNwDx708kqaVt0wDQYJKoZIhvcNAQEFBQADgYEAA81SsFnOdYJtNg5Tcq+/ByEDrBgnusx0jloUhByPMEVkoMZ3J7j1ZgI8rAbOkNngX8+pKfTiDz1RC4+dx8oU6Za+4NJXUjlL5CvV6BEYb1+QAEJwitTVvxB/A67g42/vzgAtoRUeDov1+GFiBZ+GNF/cAYKcMtGcrs2i97ZkJMo="
|
|
104
104
|
}
|
|
105
105
|
],
|
|
106
106
|
"meta": {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
],
|
|
102
102
|
"x509Certificates": [
|
|
103
103
|
{
|
|
104
|
-
"value": "
|
|
104
|
+
"value": "MIIDQzCCAqygAwIBAgICEAAwDQYJKoZIhvcNAQEFBQAwTjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFDASBgNVBAoMC2V4YW1wbGUuY29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xMTEwMjIwNjI0MzFaFw0xMjEwMDQwNjI0MzFaMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtleGFtcGxlLmNvbTEhMB8GA1UEAwwYTXMuIEJhcmJhcmEgSiBKZW5zZW4gSUlJMSIwIAYJKoZIhvcNAQkBFhNiamVuc2VuQGV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7Kr+Dcds/JQ5GwejJFcBIP682X3xpjis56AK02bc1FLgzdLI8auoR+cC9/Vrh5t66HkQIOdA4unHh0AaZ4xL5PhVbXIPMB5vAPKpzz5iPSi8xO8SL7I7SDhcBVJhqVqr3HgllEG6UClDdHO7nkLuwXq8HcISKkbT5WFTVfFZzidPl8HZ7DhXkZIRtJwBweq4bvm3hM1Os7UQH05ZS6cVDgweKNwdLLrT51ikSQG3DYrl+ft781UQRIqxgwqCfXEuDiinPh0kkvIi5jivVu1Z9QiwlYEdRbLJ4zJQBmDrSGTMYn4lRc2HgHO4DqB/bnMVorHB0CC6AV1QoFK4GPe1LwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU8pD0U0vsZIsaA16lL8En8bx0F/gwHwYDVR0jBBgwFoAUdGeKitcaF7gnzsNwDx708kqaVt0wDQYJKoZIhvcNAQEFBQADgYEAA81SsFnOdYJtNg5Tcq+/ByEDrBgnusx0jloUhByPMEVkoMZ3J7j1ZgI8rAbOkNngX8+pKfTiDz1RC4+dx8oU6Za+4NJXUjlL5CvV6BEYb1+QAEJwitTVvxB/A67g42/vzgAtoRUeDov1+GFiBZ+GNF/cAYKcMtGcrs2i97ZkJMo="
|
|
105
105
|
}
|
|
106
106
|
],
|
|
107
107
|
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
|
|
@@ -26,7 +26,9 @@ class Manager(ComplexAttribute):
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class EnterpriseUser(Extension):
|
|
29
|
-
schemas: list[str] = [
|
|
29
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
30
|
+
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
|
|
31
|
+
]
|
|
30
32
|
|
|
31
33
|
employee_number: Optional[str] = None
|
|
32
34
|
"""Numeric or alphanumeric identifier assigned to a person, typically based
|
|
@@ -8,6 +8,7 @@ from pydantic import Field
|
|
|
8
8
|
from ..base import MultiValuedComplexAttribute
|
|
9
9
|
from ..base import Mutability
|
|
10
10
|
from ..base import Reference
|
|
11
|
+
from ..base import Required
|
|
11
12
|
from .resource import Resource
|
|
12
13
|
|
|
13
14
|
|
|
@@ -31,7 +32,9 @@ class GroupMember(MultiValuedComplexAttribute):
|
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class Group(Resource):
|
|
34
|
-
schemas: list[str] = [
|
|
35
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
36
|
+
"urn:ietf:params:scim:schemas:core:2.0:Group"
|
|
37
|
+
]
|
|
35
38
|
|
|
36
39
|
display_name: Optional[str] = None
|
|
37
40
|
"""A human-readable name for the Group."""
|
|
@@ -136,7 +136,7 @@ class ResourceMetaclass(BaseModelType):
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
class Resource(BaseModel, Generic[AnyExtension], metaclass=ResourceMetaclass):
|
|
139
|
-
schemas: list[str]
|
|
139
|
+
schemas: Annotated[list[str], Required.true]
|
|
140
140
|
"""The "schemas" attribute is a REQUIRED attribute and is an array of
|
|
141
141
|
Strings containing URIs that are used to indicate the namespaces of the
|
|
142
142
|
SCIM schemas that define the attributes present in the current JSON
|
|
@@ -229,7 +229,7 @@ class Resource(BaseModel, Generic[AnyExtension], metaclass=ResourceMetaclass):
|
|
|
229
229
|
return Resource.get_by_schema(resource_types, schema, **kwargs)
|
|
230
230
|
|
|
231
231
|
@field_serializer("schemas")
|
|
232
|
-
def set_extension_schemas(self, schemas: list[str]):
|
|
232
|
+
def set_extension_schemas(self, schemas: Annotated[list[str], Required.true]):
|
|
233
233
|
"""Add model extension ids to the 'schemas' attribute."""
|
|
234
234
|
extension_schemas = self.get_extension_models().keys()
|
|
235
235
|
schemas = self.schemas + [
|
|
@@ -35,7 +35,9 @@ class SchemaExtension(ComplexAttribute):
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class ResourceType(Resource):
|
|
38
|
-
schemas: list[str] = [
|
|
38
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
39
|
+
"urn:ietf:params:scim:schemas:core:2.0:ResourceType"
|
|
40
|
+
]
|
|
39
41
|
|
|
40
42
|
name: Annotated[Optional[str], Mutability.read_only, Required.true] = None
|
|
41
43
|
"""The resource type name.
|
|
@@ -9,7 +9,6 @@ from typing import Optional
|
|
|
9
9
|
from typing import Union
|
|
10
10
|
from typing import get_origin
|
|
11
11
|
|
|
12
|
-
from pydantic import Base64Bytes
|
|
13
12
|
from pydantic import Field
|
|
14
13
|
from pydantic import create_model
|
|
15
14
|
from pydantic import field_validator
|
|
@@ -30,6 +29,7 @@ from ..base import Uniqueness
|
|
|
30
29
|
from ..base import URIReference
|
|
31
30
|
from ..base import is_complex_attribute
|
|
32
31
|
from ..constants import RESERVED_WORDS
|
|
32
|
+
from ..utils import Base64Bytes
|
|
33
33
|
from ..utils import normalize_attribute_name
|
|
34
34
|
from .resource import Extension
|
|
35
35
|
from .resource import Resource
|
|
@@ -65,7 +65,7 @@ def make_python_model(
|
|
|
65
65
|
if attr.name
|
|
66
66
|
}
|
|
67
67
|
pydantic_attributes["schemas"] = (
|
|
68
|
-
|
|
68
|
+
Annotated[list[str], Required.true],
|
|
69
69
|
Field(default=[obj.id]),
|
|
70
70
|
)
|
|
71
71
|
|
|
@@ -240,7 +240,9 @@ class Attribute(ComplexAttribute):
|
|
|
240
240
|
|
|
241
241
|
|
|
242
242
|
class Schema(Resource):
|
|
243
|
-
schemas: list[str] = [
|
|
243
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
244
|
+
"urn:ietf:params:scim:schemas:core:2.0:Schema"
|
|
245
|
+
]
|
|
244
246
|
|
|
245
247
|
id: Annotated[Optional[str], Mutability.read_only, Required.true] = None
|
|
246
248
|
"""The unique URI of the schema."""
|
|
@@ -264,3 +266,10 @@ class Schema(Resource):
|
|
|
264
266
|
def urn_id(cls, value: str) -> str:
|
|
265
267
|
"""Ensure that schema ids are URI, as defined in RFC7643 §7."""
|
|
266
268
|
return str(Url(value))
|
|
269
|
+
|
|
270
|
+
def get_attribute(self, attribute_name: str) -> Optional[Attribute]:
|
|
271
|
+
"""Find an attribute by its name."""
|
|
272
|
+
for attribute in self.attributes or []:
|
|
273
|
+
if attribute.name == attribute_name:
|
|
274
|
+
return attribute
|
|
275
|
+
return None
|
|
@@ -94,7 +94,9 @@ class AuthenticationScheme(ComplexAttribute):
|
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
class ServiceProviderConfig(Resource):
|
|
97
|
-
schemas: list[str] = [
|
|
97
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
98
|
+
"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
|
|
99
|
+
]
|
|
98
100
|
|
|
99
101
|
id: Annotated[
|
|
100
102
|
Optional[str], Mutability.read_only, Returned.default, Uniqueness.global_
|
|
@@ -4,7 +4,6 @@ from typing import Literal
|
|
|
4
4
|
from typing import Optional
|
|
5
5
|
from typing import Union
|
|
6
6
|
|
|
7
|
-
from pydantic import Base64Bytes
|
|
8
7
|
from pydantic import EmailStr
|
|
9
8
|
from pydantic import Field
|
|
10
9
|
|
|
@@ -17,6 +16,7 @@ from ..base import Reference
|
|
|
17
16
|
from ..base import Required
|
|
18
17
|
from ..base import Returned
|
|
19
18
|
from ..base import Uniqueness
|
|
19
|
+
from ..utils import Base64Bytes
|
|
20
20
|
from .resource import Resource
|
|
21
21
|
|
|
22
22
|
|
|
@@ -214,7 +214,9 @@ class X509Certificate(MultiValuedComplexAttribute):
|
|
|
214
214
|
|
|
215
215
|
|
|
216
216
|
class User(Resource):
|
|
217
|
-
schemas: list[str] = [
|
|
217
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
218
|
+
"urn:ietf:params:scim:schemas:core:2.0:User"
|
|
219
|
+
]
|
|
218
220
|
|
|
219
221
|
user_name: Annotated[Optional[str], Uniqueness.server, Required.true] = None
|
|
220
222
|
"""Unique identifier for the User, typically used by the user to directly
|
|
@@ -7,6 +7,7 @@ from pydantic import Field
|
|
|
7
7
|
from pydantic import PlainSerializer
|
|
8
8
|
|
|
9
9
|
from ..base import ComplexAttribute
|
|
10
|
+
from ..base import Required
|
|
10
11
|
from ..utils import int_to_str
|
|
11
12
|
from .message import Message
|
|
12
13
|
|
|
@@ -53,7 +54,9 @@ class BulkRequest(Message):
|
|
|
53
54
|
The models for Bulk operations are defined, but their behavior is not implemented nor tested yet.
|
|
54
55
|
"""
|
|
55
56
|
|
|
56
|
-
schemas: list[str] = [
|
|
57
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
58
|
+
"urn:ietf:params:scim:api:messages:2.0:BulkRequest"
|
|
59
|
+
]
|
|
57
60
|
|
|
58
61
|
fail_on_errors: Optional[int] = None
|
|
59
62
|
"""An integer specifying the number of errors that the service provider
|
|
@@ -74,7 +77,9 @@ class BulkResponse(Message):
|
|
|
74
77
|
The models for Bulk operations are defined, but their behavior is not implemented nor tested yet.
|
|
75
78
|
"""
|
|
76
79
|
|
|
77
|
-
schemas: list[str] = [
|
|
80
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
81
|
+
"urn:ietf:params:scim:api:messages:2.0:BulkResponse"
|
|
82
|
+
]
|
|
78
83
|
|
|
79
84
|
operations: Optional[list[BulkOperation]] = Field(
|
|
80
85
|
None, serialization_alias="Operations"
|
|
@@ -3,6 +3,7 @@ from typing import Optional
|
|
|
3
3
|
|
|
4
4
|
from pydantic import PlainSerializer
|
|
5
5
|
|
|
6
|
+
from ..base import Required
|
|
6
7
|
from ..utils import int_to_str
|
|
7
8
|
from .message import Message
|
|
8
9
|
|
|
@@ -10,7 +11,9 @@ from .message import Message
|
|
|
10
11
|
class Error(Message):
|
|
11
12
|
"""Representation of SCIM API errors."""
|
|
12
13
|
|
|
13
|
-
schemas: list[str] = [
|
|
14
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
15
|
+
"urn:ietf:params:scim:api:messages:2.0:Error"
|
|
16
|
+
]
|
|
14
17
|
|
|
15
18
|
status: Annotated[Optional[int], PlainSerializer(int_to_str)] = None
|
|
16
19
|
"""The HTTP status code (see Section 6 of [RFC7231]) expressed as a JSON
|
|
@@ -18,6 +18,7 @@ from typing_extensions import Self
|
|
|
18
18
|
from ..base import BaseModel
|
|
19
19
|
from ..base import BaseModelType
|
|
20
20
|
from ..base import Context
|
|
21
|
+
from ..base import Required
|
|
21
22
|
from ..rfc7643.resource import AnyResource
|
|
22
23
|
from .message import Message
|
|
23
24
|
|
|
@@ -78,7 +79,9 @@ class ListResponseMetaclass(BaseModelType):
|
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
class ListResponse(Message, Generic[AnyResource], metaclass=ListResponseMetaclass):
|
|
81
|
-
schemas: list[str] = [
|
|
82
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
83
|
+
"urn:ietf:params:scim:api:messages:2.0:ListResponse"
|
|
84
|
+
]
|
|
82
85
|
|
|
83
86
|
total_results: Optional[int] = None
|
|
84
87
|
"""The total number of results returned by the list or query operation."""
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
1
3
|
from ..base import BaseModel
|
|
4
|
+
from ..base import Required
|
|
2
5
|
|
|
3
6
|
|
|
4
7
|
class Message(BaseModel):
|
|
5
8
|
"""SCIM protocol messages as defined by :rfc:`RFC7644 §3.1 <7644#section-3.1>`."""
|
|
6
9
|
|
|
7
|
-
schemas: list[str]
|
|
10
|
+
schemas: Annotated[list[str], Required.true]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
+
from typing import Annotated
|
|
2
3
|
from typing import Any
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
@@ -6,6 +7,7 @@ from pydantic import Field
|
|
|
6
7
|
from pydantic import field_validator
|
|
7
8
|
|
|
8
9
|
from ..base import ComplexAttribute
|
|
10
|
+
from ..base import Required
|
|
9
11
|
from .message import Message
|
|
10
12
|
|
|
11
13
|
|
|
@@ -57,7 +59,9 @@ class PatchOp(Message):
|
|
|
57
59
|
The models for Patch operations are defined, but their behavior is not implemented nor tested yet.
|
|
58
60
|
"""
|
|
59
61
|
|
|
60
|
-
schemas: list[str] = [
|
|
62
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
63
|
+
"urn:ietf:params:scim:api:messages:2.0:PatchOp"
|
|
64
|
+
]
|
|
61
65
|
|
|
62
66
|
operations: Optional[list[PatchOperation]] = Field(
|
|
63
67
|
None, serialization_alias="Operations"
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
+
from typing import Annotated
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
4
5
|
from pydantic import field_validator
|
|
5
6
|
from pydantic import model_validator
|
|
6
7
|
|
|
8
|
+
from ..base import Required
|
|
7
9
|
from .message import Message
|
|
8
10
|
|
|
9
11
|
|
|
@@ -13,7 +15,9 @@ class SearchRequest(Message):
|
|
|
13
15
|
https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.3
|
|
14
16
|
"""
|
|
15
17
|
|
|
16
|
-
schemas: list[str] = [
|
|
18
|
+
schemas: Annotated[list[str], Required.true] = [
|
|
19
|
+
"urn:ietf:params:scim:api:messages:2.0:SearchRequest"
|
|
20
|
+
]
|
|
17
21
|
|
|
18
22
|
attributes: Optional[list[str]] = None
|
|
19
23
|
"""A multi-valued list of strings indicating the names of resource
|
|
@@ -72,3 +76,17 @@ class SearchRequest(Message):
|
|
|
72
76
|
)
|
|
73
77
|
|
|
74
78
|
return self
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def start_index_0(self):
|
|
82
|
+
"""The 0 indexed start index."""
|
|
83
|
+
return self.start_index - 1 if self.start_index is not None else None
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def stop_index_0(self):
|
|
87
|
+
"""The 0 indexed stop index."""
|
|
88
|
+
return (
|
|
89
|
+
self.start_index_0 + self.count
|
|
90
|
+
if self.start_index_0 is not None and self.count is not None
|
|
91
|
+
else None
|
|
92
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import re
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
from typing import Literal
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
from pydantic import EncodedBytes
|
|
9
|
+
from pydantic import EncoderProtocol
|
|
10
|
+
from pydantic.alias_generators import to_snake
|
|
11
|
+
from pydantic_core import PydanticCustomError
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from types import UnionType # type: ignore
|
|
15
|
+
|
|
16
|
+
UNION_TYPES = [Union, UnionType]
|
|
17
|
+
except ImportError:
|
|
18
|
+
# Python 3.9 has no UnionType
|
|
19
|
+
UNION_TYPES = [Union]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def int_to_str(status: Optional[int]) -> Optional[str]:
|
|
23
|
+
return None if status is None else str(status)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Copied from Pydantic 2.10 repository
|
|
27
|
+
class Base64Encoder(EncoderProtocol): # pragma: no cover
|
|
28
|
+
"""Standard (non-URL-safe) Base64 encoder."""
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def decode(cls, data: bytes) -> bytes:
|
|
32
|
+
"""Decode the data from base64 encoded bytes to original bytes data.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
data: The data to decode.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
The decoded data.
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
return base64.b64decode(data)
|
|
43
|
+
except ValueError as e:
|
|
44
|
+
raise PydanticCustomError(
|
|
45
|
+
"base64_decode", "Base64 decoding error: '{error}'", {"error": str(e)}
|
|
46
|
+
) from e
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def encode(cls, value: bytes) -> bytes:
|
|
50
|
+
"""Encode the data from bytes to a base64 encoded bytes.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
value: The data to encode.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The encoded data.
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
return base64.b64encode(value)
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def get_json_format(cls) -> Literal["base64"]:
|
|
63
|
+
"""Get the JSON format for the encoded data.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The JSON format for the encoded data.
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
return "base64"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Compatibility with Pydantic <2.10
|
|
73
|
+
# https://pydantic.dev/articles/pydantic-v2-10-release#use-b64decode-and-b64encode-for-base64bytes-and-base64str-types
|
|
74
|
+
Base64Bytes = Annotated[bytes, EncodedBytes(encoder=Base64Encoder)]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def to_camel(string: str) -> str:
|
|
78
|
+
"""Transform strings to camelCase.
|
|
79
|
+
|
|
80
|
+
This method is used for attribute name serialization. This is more
|
|
81
|
+
or less the pydantic implementation, but it does not add uppercase
|
|
82
|
+
on alphanumerical characters after specials characters. For instance
|
|
83
|
+
'$ref' stays '$ref'.
|
|
84
|
+
"""
|
|
85
|
+
snake = to_snake(string)
|
|
86
|
+
camel = re.sub(r"_+([0-9A-Za-z]+)", lambda m: m.group(1).title(), snake)
|
|
87
|
+
return camel
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def normalize_attribute_name(attribute_name: str) -> str:
|
|
91
|
+
"""Remove all non-alphabetical characters and lowerise a string.
|
|
92
|
+
|
|
93
|
+
This method is used for attribute name validation.
|
|
94
|
+
"""
|
|
95
|
+
is_extension_attribute = ":" in attribute_name
|
|
96
|
+
if not is_extension_attribute:
|
|
97
|
+
attribute_name = re.sub(r"[\W_]+", "", attribute_name)
|
|
98
|
+
|
|
99
|
+
return attribute_name.lower()
|
|
@@ -2,8 +2,6 @@ import datetime
|
|
|
2
2
|
from typing import Literal
|
|
3
3
|
from typing import Union
|
|
4
4
|
|
|
5
|
-
from pydantic import Base64Bytes
|
|
6
|
-
|
|
7
5
|
from scim2_models.base import CaseExact
|
|
8
6
|
from scim2_models.base import ComplexAttribute
|
|
9
7
|
from scim2_models.base import ExternalReference
|
|
@@ -18,6 +16,7 @@ from scim2_models.rfc7643.resource import Extension
|
|
|
18
16
|
from scim2_models.rfc7643.resource import Resource
|
|
19
17
|
from scim2_models.rfc7643.schema import Attribute
|
|
20
18
|
from scim2_models.rfc7643.schema import Schema
|
|
19
|
+
from scim2_models.utils import Base64Bytes
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
def test_make_group_model_from_schema(load_sample):
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from typing import Annotated
|
|
1
2
|
from typing import Union
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
@@ -7,6 +8,7 @@ from scim2_models import Context
|
|
|
7
8
|
from scim2_models import EnterpriseUser
|
|
8
9
|
from scim2_models import Group
|
|
9
10
|
from scim2_models import ListResponse
|
|
11
|
+
from scim2_models import Required
|
|
10
12
|
from scim2_models import Resource
|
|
11
13
|
from scim2_models import ResourceType
|
|
12
14
|
from scim2_models import ServiceProviderConfig
|
|
@@ -107,7 +109,7 @@ def test_mixed_types(load_sample):
|
|
|
107
109
|
|
|
108
110
|
|
|
109
111
|
class Foobar(Resource):
|
|
110
|
-
schemas: list[str] = ["foobarschema"]
|
|
112
|
+
schemas: Annotated[list[str], Required.true] = ["foobarschema"]
|
|
111
113
|
|
|
112
114
|
|
|
113
115
|
def test_mixed_types_type_missing(load_sample):
|
|
@@ -7,9 +7,11 @@ import pytest
|
|
|
7
7
|
from scim2_models.base import BaseModel
|
|
8
8
|
from scim2_models.base import ComplexAttribute
|
|
9
9
|
from scim2_models.base import Context
|
|
10
|
+
from scim2_models.base import Required
|
|
10
11
|
from scim2_models.base import Returned
|
|
11
12
|
from scim2_models.base import validate_attribute_urn
|
|
12
13
|
from scim2_models.rfc7643.enterprise_user import EnterpriseUser
|
|
14
|
+
from scim2_models.rfc7643.resource import Extension
|
|
13
15
|
from scim2_models.rfc7643.resource import Meta
|
|
14
16
|
from scim2_models.rfc7643.resource import Resource
|
|
15
17
|
from scim2_models.rfc7643.user import User
|
|
@@ -20,7 +22,7 @@ class Sub(ComplexAttribute):
|
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class Sup(Resource):
|
|
23
|
-
schemas: list[str] = ["urn:example:2.0:Sup"]
|
|
25
|
+
schemas: Annotated[list[str], Required.true] = ["urn:example:2.0:Sup"]
|
|
24
26
|
dummy: str
|
|
25
27
|
sub: Sub
|
|
26
28
|
subs: list[Sub]
|
|
@@ -44,7 +46,7 @@ class Baz(ComplexAttribute):
|
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
class Foo(Resource):
|
|
47
|
-
schemas: list[str] = ["urn:example:2.0:Foo"]
|
|
49
|
+
schemas: Annotated[list[str], Required.true] = ["urn:example:2.0:Foo"]
|
|
48
50
|
sub: Annotated[ReturnedModel, Returned.default]
|
|
49
51
|
bar: str
|
|
50
52
|
snake_case: str
|
|
@@ -52,15 +54,15 @@ class Foo(Resource):
|
|
|
52
54
|
|
|
53
55
|
|
|
54
56
|
class Bar(Resource):
|
|
55
|
-
schemas: list[str] = ["urn:example:2.0:Bar"]
|
|
57
|
+
schemas: Annotated[list[str], Required.true] = ["urn:example:2.0:Bar"]
|
|
56
58
|
sub: Annotated[ReturnedModel, Returned.default]
|
|
57
59
|
bar: str
|
|
58
60
|
snake_case: str
|
|
59
61
|
baz: Optional[Baz] = None
|
|
60
62
|
|
|
61
63
|
|
|
62
|
-
class Extension
|
|
63
|
-
schemas: list[str] = ["urn:example:2.0:
|
|
64
|
+
class MyExtension(Extension):
|
|
65
|
+
schemas: Annotated[list[str], Required.true] = ["urn:example:2.0:MyExtension"]
|
|
64
66
|
baz: str
|
|
65
67
|
|
|
66
68
|
|
|
@@ -105,14 +107,14 @@ def test_validate_attribute_urn():
|
|
|
105
107
|
)
|
|
106
108
|
|
|
107
109
|
assert (
|
|
108
|
-
validate_attribute_urn("urn:example:2.0:
|
|
109
|
-
== "urn:example:2.0:
|
|
110
|
+
validate_attribute_urn("urn:example:2.0:MyExtension:baz", Foo[MyExtension])
|
|
111
|
+
== "urn:example:2.0:MyExtension:baz"
|
|
110
112
|
)
|
|
111
113
|
assert (
|
|
112
114
|
validate_attribute_urn(
|
|
113
|
-
"urn:example:2.0:
|
|
115
|
+
"urn:example:2.0:MyExtension:baz", resource_types=[Foo[MyExtension]]
|
|
114
116
|
)
|
|
115
|
-
== "urn:example:2.0:
|
|
117
|
+
== "urn:example:2.0:MyExtension:baz"
|
|
116
118
|
)
|
|
117
119
|
|
|
118
120
|
with pytest.raises(ValueError, match="No default schema and relative URN"):
|
|
@@ -299,7 +301,7 @@ def test_dump_after_assignment():
|
|
|
299
301
|
|
|
300
302
|
def test_binary_attributes():
|
|
301
303
|
decoded = b"This is a very long line with a lot of characters, enough to create newlines when encoded."
|
|
302
|
-
encoded = "
|
|
304
|
+
encoded = "VGhpcyBpcyBhIHZlcnkgbG9uZyBsaW5lIHdpdGggYSBsb3Qgb2YgY2hhcmFjdGVycywgZW5vdWdoIHRvIGNyZWF0ZSBuZXdsaW5lcyB3aGVuIGVuY29kZWQu"
|
|
303
305
|
|
|
304
306
|
user = User.model_validate(
|
|
305
307
|
{"userName": "foobar", "x509Certificates": [{"value": encoded}]}
|
|
@@ -317,7 +319,7 @@ def test_binary_attributes():
|
|
|
317
319
|
assert user.x509_certificates[0].value == decoded
|
|
318
320
|
assert user.model_dump()["x509Certificates"][0]["value"] == encoded
|
|
319
321
|
|
|
320
|
-
encoded_with_padding = "
|
|
322
|
+
encoded_with_padding = "VGhpcyBpcyBhIHZlcnkgbG9uZyBsaW5lIHdpdGggYSBsb3Qgb2YgY2hhcmFjdGVycywgZW5vdWdoIHRvIGNyZWF0ZSBuZXdsaW5lcyB3aGVuIGVuY29kZWQu=================="
|
|
321
323
|
user = User.model_validate(
|
|
322
324
|
{"userName": "foobar", "x509Certificates": [{"value": encoded_with_padding}]}
|
|
323
325
|
)
|
|
@@ -6,6 +6,7 @@ import pytest
|
|
|
6
6
|
from scim2_models.base import ComplexAttribute
|
|
7
7
|
from scim2_models.base import Context
|
|
8
8
|
from scim2_models.base import Mutability
|
|
9
|
+
from scim2_models.base import Required
|
|
9
10
|
from scim2_models.base import Returned
|
|
10
11
|
from scim2_models.rfc7643.resource import Resource
|
|
11
12
|
|
|
@@ -18,7 +19,7 @@ class SubRetModel(ComplexAttribute):
|
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class SupRetResource(Resource):
|
|
21
|
-
schemas: list[str] = ["org:example:SupRetResource"]
|
|
22
|
+
schemas: Annotated[list[str], Required.true] = ["org:example:SupRetResource"]
|
|
22
23
|
|
|
23
24
|
always_returned: Annotated[Optional[str], Returned.always] = None
|
|
24
25
|
never_returned: Annotated[Optional[str], Returned.never] = None
|
|
@@ -29,7 +30,7 @@ class SupRetResource(Resource):
|
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
class MutResource(Resource):
|
|
32
|
-
schemas: list[str] = ["org:example:MutResource"]
|
|
33
|
+
schemas: Annotated[list[str], Required.true] = ["org:example:MutResource"]
|
|
33
34
|
|
|
34
35
|
read_only: Annotated[Optional[str], Mutability.read_only] = None
|
|
35
36
|
read_write: Annotated[Optional[str], Mutability.read_write] = None
|
|
@@ -12,7 +12,7 @@ from scim2_models.rfc7643.resource import Resource
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class RetResource(Resource):
|
|
15
|
-
schemas: list[str] = ["org:example:RetResource"]
|
|
15
|
+
schemas: Annotated[list[str], Required.true] = ["org:example:RetResource"]
|
|
16
16
|
|
|
17
17
|
always_returned: Annotated[Optional[str], Returned.always] = None
|
|
18
18
|
never_returned: Annotated[Optional[str], Returned.never] = None
|
|
@@ -21,7 +21,7 @@ class RetResource(Resource):
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class MutResource(Resource):
|
|
24
|
-
schemas: list[str] = ["org:example:MutResource"]
|
|
24
|
+
schemas: Annotated[list[str], Required.true] = ["org:example:MutResource"]
|
|
25
25
|
|
|
26
26
|
read_only: Annotated[Optional[str], Mutability.read_only] = None
|
|
27
27
|
read_write: Annotated[Optional[str], Mutability.read_write] = None
|
|
@@ -30,7 +30,7 @@ class MutResource(Resource):
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class ReqResource(Resource):
|
|
33
|
-
schemas: list[str] = ["org:example:ReqResource"]
|
|
33
|
+
schemas: Annotated[list[str], Required.true] = ["org:example:ReqResource"]
|
|
34
34
|
|
|
35
35
|
required: Annotated[Optional[str], Required.true] = None
|
|
36
36
|
optional: Annotated[Optional[str], Required.false] = None
|