scim2-server 0.6.1__tar.gz → 0.6.2__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 (29) hide show
  1. {scim2_server-0.6.1 → scim2_server-0.6.2}/PKG-INFO +2 -2
  2. {scim2_server-0.6.1 → scim2_server-0.6.2}/pyproject.toml +2 -2
  3. {scim2_server-0.6.1 → scim2_server-0.6.2}/pyproject.toml.orig +2 -2
  4. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/service.py +57 -5
  5. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/storage.py +3 -1
  6. {scim2_server-0.6.1 → scim2_server-0.6.2}/LICENSE +0 -0
  7. {scim2_server-0.6.1 → scim2_server-0.6.2}/README.md +0 -0
  8. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/__init__.py +0 -0
  9. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/applications/__init__.py +0 -0
  10. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/applications/asgi.py +0 -0
  11. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/applications/base.py +0 -0
  12. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/applications/wsgi.py +0 -0
  13. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/bulk.py +0 -0
  14. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/conditions.py +0 -0
  15. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/errors.py +0 -0
  16. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/handler.py +0 -0
  17. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/memory.py +0 -0
  18. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/py.typed +0 -0
  19. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/requests.py +0 -0
  20. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/resources/default-resource-types.json +0 -0
  21. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/resources/default-schemas.json +0 -0
  22. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/resources/default-service-provider-config.json +0 -0
  23. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/responses.py +0 -0
  24. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/routing.py +0 -0
  25. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/testing.py +0 -0
  26. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/testserver/__init__.py +0 -0
  27. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/testserver/application.py +0 -0
  28. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/testserver/cli.py +0 -0
  29. {scim2_server-0.6.1 → scim2_server-0.6.2}/scim2_server/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: scim2-server
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: SCIM server core for any storage and web framework
5
5
  Keywords: scim,scim2,provisioning,rfc7643,rfc7644
6
6
  Author: Yaal Coop, Christian Friedrich Coors
@@ -217,7 +217,7 @@ Classifier: License :: OSI Approved :: Apache Software License
217
217
  Classifier: Environment :: Web Environment
218
218
  Classifier: Programming Language :: Python
219
219
  Classifier: Operating System :: OS Independent
220
- Requires-Dist: scim2-models>=0.10.3
220
+ Requires-Dist: scim2-models>=0.10.6
221
221
  Requires-Dist: pytest>=9.1.1 ; extra == 'testing'
222
222
  Requires-Python: >=3.11
223
223
  Project-URL: repository, https://github.com/python-scim/scim2-server
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "scim2-server"
7
- version = "0.6.1"
7
+ version = "0.6.2"
8
8
  description = "SCIM server core for any storage and web framework"
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -28,7 +28,7 @@ classifiers = [
28
28
  "Operating System :: OS Independent",
29
29
  ]
30
30
  requires-python = ">= 3.11"
31
- dependencies = ["scim2-models>=0.10.3"]
31
+ dependencies = ["scim2-models>=0.10.6"]
32
32
 
33
33
  [[project.authors]]
34
34
  name = "Yaal Coop"
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "scim2-server"
7
- version = "0.6.1"
7
+ version = "0.6.2"
8
8
  description = "SCIM server core for any storage and web framework"
9
9
  authors = [
10
10
  {name="Yaal Coop", email="contact@yaal.coop"},
@@ -29,7 +29,7 @@ classifiers = [
29
29
 
30
30
  requires-python = ">= 3.11"
31
31
  dependencies = [
32
- "scim2-models>=0.10.3",
32
+ "scim2-models>=0.10.6",
33
33
  ]
34
34
 
35
35
  [project.optional-dependencies]
@@ -5,6 +5,7 @@ from typing import Any
5
5
  from typing import TypeVar
6
6
  from typing import Union
7
7
  from typing import cast
8
+ from urllib.parse import urlsplit
8
9
 
9
10
  from pydantic import ValidationError
10
11
  from pydantic_core import from_json
@@ -28,7 +29,9 @@ from scim2_models import NotFoundException
28
29
  from scim2_models import NotImplementedException
29
30
  from scim2_models import Patch
30
31
  from scim2_models import PatchOp
32
+ from scim2_models import Path
31
33
  from scim2_models import PayloadTooLargeException
34
+ from scim2_models import Reference
32
35
  from scim2_models import Resource
33
36
  from scim2_models import ResourceType
34
37
  from scim2_models import ResponseParameters
@@ -512,11 +515,28 @@ class ScimService:
512
515
  f"{base_url.rstrip('/')}/{resource_type.endpoint.strip('/')}/{resource_id}"
513
516
  )
514
517
 
518
+ def reference_location(self, base_url: str, reference: str) -> str:
519
+ """Return the URL of a reference to a resource.
520
+
521
+ A reference relative to the SCIM root, such as ``Users/2819c223``,
522
+ becomes the location of the resource
523
+ (:rfc:`RFC 7643 §2.3.7 <7643#section-2.3.7>`). Absolute references
524
+ are kept.
525
+ """
526
+ if urlsplit(reference).scheme or reference.startswith("/"):
527
+ return reference
528
+ endpoint, _, resource_id = reference.rpartition("/")
529
+ resource_type = self.get_resource_type_by_endpoint(endpoint)
530
+ if resource_type is None or not resource_id:
531
+ return f"{base_url.rstrip('/')}/{reference}"
532
+ return self.resource_location(base_url, resource_type, resource_id)
533
+
515
534
  def publish(self, base_url: str, resource: Resource[Any]) -> Resource[Any]:
516
535
  """Return a copy of a stored resource in the form sent to the client.
517
536
 
518
- Its location is set, and its version is left out when the service does
519
- not support ETags.
537
+ Its location is set, its references to other resources become URLs
538
+ with :meth:`reference_location`, and its version is left out when the
539
+ service does not support ETags.
520
540
  """
521
541
  assert resource.meta is not None
522
542
  assert resource.id is not None
@@ -527,9 +547,41 @@ class ScimService:
527
547
  }
528
548
  if not self.etag_supported:
529
549
  update["version"] = None
530
- return resource.model_copy(
531
- update={"meta": resource.meta.model_copy(update=update)}
532
- )
550
+ published = resource.model_copy(deep=True)
551
+ published.meta = resource.meta.model_copy(update=update)
552
+ self._publish_references(base_url, published)
553
+ return published
554
+
555
+ def _publish_references(self, base_url: str, resource: Resource[Any]) -> None:
556
+ """Replace in place the references of a resource to other resources by their URLs."""
557
+
558
+ def locate(reference: Any) -> Any:
559
+ if isinstance(reference, list):
560
+ return [locate(item) for item in reference]
561
+ if reference is None:
562
+ return None
563
+ return self.reference_location(base_url, reference)
564
+
565
+ for path in parametrize(Path, type(resource)).iter_paths(
566
+ target_type=[Reference]
567
+ ):
568
+ binding = path.resolve()
569
+ assert binding is not None
570
+ target_type = cast(type[Reference[Any]], binding.target_type)
571
+ reference_types = set(target_type.__reference_types__)
572
+ if not reference_types or reference_types & {"external", "uri"}:
573
+ continue
574
+
575
+ if binding.sub_field_name is None:
576
+ path.set(resource, locate(path.get(resource)), strict=False)
577
+ continue
578
+
579
+ head = f"{path.schema}:{path.parts[0]}" if path.schema else path.parts[0]
580
+ entries = Path(head).get(resource)
581
+ for entry in entries if isinstance(entries, list) else [entries]:
582
+ if entry is not None:
583
+ reference = getattr(entry, binding.sub_field_name)
584
+ setattr(entry, binding.sub_field_name, locate(reference))
533
585
 
534
586
  @staticmethod
535
587
  def resource_response(
@@ -26,7 +26,9 @@ class ScimStorage(ABC):
26
26
  stored resource, and the resources it receives are not changed either.
27
27
  - It fills ``id``, ``meta.resourceType``, ``meta.created``,
28
28
  ``meta.lastModified`` and ``meta.version``. It leaves ``meta.location``
29
- to the server, which knows the URLs.
29
+ to the server, which knows the URLs. For the same reason, a reference
30
+ to another resource, such as ``members.$ref``, can be relative to the
31
+ SCIM root, such as ``Users/2819c223``. The server returns its URL.
30
32
  - The version changes whenever the resource changes. An update that
31
33
  changes nothing may keep it.
32
34
  - A resource that does not exist raises
File without changes
File without changes