Skip to content

Commit e12dcdf

Browse files
committed
refactor: remove the clock parameter of the storages
The storages date every write with the current time in UTC. The clock parameter came from the in-memory storage of scim2-server, and is not part of the storage contract. A test now checks that a change of links only, such as a new member, gives the resource a new version.
1 parent 7660c0c commit e12dcdf

3 files changed

Lines changed: 23 additions & 14 deletions

File tree

‎doc/how-to/map-a-data-model.rst‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ types. Use a UUID, with the :class:`~sqlalchemy.types.Uuid` type, or a sequence
3535
table shares.
3636

3737
The primary key may be an integer, a string or a UUID, and the storage returns it as a string.
38-
The storage does not fill the key: give its column a default, such as ``default=uuid.uuid4``, a
39-
server default, or an autoincrement. The storage reads the key the database gives.
38+
Give its column a default, such as ``default=uuid.uuid4``, a server default, or an
39+
autoincrement, so that the database fills the key of each new resource.
4040

4141
Map a column
4242
------------

‎src/scim2_sqlalchemy/storage.py‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,9 @@ class _StorageBase:
7272
def __init__(
7373
self,
7474
mappings: Mapping[str, ResourceMapping],
75-
clock: Callable[[], datetime.datetime] | None,
7675
provider: ScimProvider | None,
7776
) -> None:
7877
self.mappings = dict(mappings)
79-
self.clock = clock or _utcnow
8078
self.provider = provider
8179
self._policy = provider.policy if provider else ScimPolicy()
8280
self._comparators: dict[str, _Comparator] = {}
@@ -198,7 +196,7 @@ def _new_record(self, resource_type: ResourceType, resource: Resource[Any]) -> A
198196
"""
199197
mapping = self._mapping(resource_type)
200198
record = mapping.record()
201-
now = self.clock()
199+
now = _utcnow()
202200
setattr(record, mapping._created.key, now)
203201
setattr(record, mapping._last_modified.key, now)
204202
return record
@@ -210,8 +208,13 @@ def _write_record(
210208
record: Any,
211209
links: dict[_Collection, list[Any]],
212210
) -> None:
211+
"""Write a resource in its record, and date the write.
212+
213+
The date changes the row even when only links change, such as the
214+
members of a group, so that the version changes too.
215+
"""
213216
_from_scim(mapping, resource, record, links, creating=False)
214-
setattr(record, mapping._last_modified.key, self.clock())
217+
setattr(record, mapping._last_modified.key, _utcnow())
215218

216219

217220
class SqlAlchemyStorage(_StorageBase, ScimStorage):
@@ -230,8 +233,6 @@ class SqlAlchemyStorage(_StorageBase, ScimStorage):
230233
:param mappings: The mapping of each resource type, by resource type name.
231234
:param session: Return the current :class:`~sqlalchemy.orm.Session`, such
232235
as a :class:`~sqlalchemy.orm.scoped_session`.
233-
:param clock: Return the date of a write, for ``meta.created`` and
234-
``meta.lastModified``. The current date in UTC by default.
235236
:param provider: The provider of the server. The storage then returns
236237
instances of its models, rather than of the models of the mappings,
237238
and gives each link a ``$ref`` relative to the SCIM root, from the
@@ -243,10 +244,9 @@ def __init__(
243244
self,
244245
mappings: Mapping[str, ResourceMapping],
245246
session: Callable[[], Session],
246-
clock: Callable[[], datetime.datetime] | None = None,
247247
provider: ScimProvider | None = None,
248248
) -> None:
249-
super().__init__(mappings, clock, provider)
249+
super().__init__(mappings, provider)
250250
self.session = session
251251

252252
@contextmanager
@@ -418,8 +418,6 @@ class AsyncSqlAlchemyStorage(_StorageBase, AsyncScimStorage):
418418
:param session: Return the current
419419
:class:`~sqlalchemy.ext.asyncio.AsyncSession`, such as an
420420
:class:`~sqlalchemy.ext.asyncio.async_scoped_session`.
421-
:param clock: Return the date of a write, for ``meta.created`` and
422-
``meta.lastModified``. The current date in UTC by default.
423421
:param provider: The provider of the server. The storage then returns
424422
instances of its models, rather than of the models of the mappings.
425423
Strings are compared with the
@@ -430,10 +428,9 @@ def __init__(
430428
self,
431429
mappings: Mapping[str, ResourceMapping],
432430
session: Callable[[], AsyncSession],
433-
clock: Callable[[], datetime.datetime] | None = None,
434431
provider: ScimProvider | None = None,
435432
) -> None:
436-
super().__init__(mappings, clock, provider)
433+
super().__init__(mappings, provider)
437434
self.session = session
438435

439436
@asynccontextmanager

‎tests/test_storage.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,18 @@ def test_a_uuid_identifier_matches_its_own_text(storage, user_type, scim_filter,
538538
assert total == (1 if found else 0)
539539

540540

541+
def test_a_change_of_links_only_changes_the_version(storage, user_type, group_type):
542+
"""Adding a member only writes the association table, yet the group gets a new version."""
543+
bob = storage.create(user_type, User(user_name="bob"))
544+
group = storage.create(group_type, Group(display_name="Admins"))
545+
group.members = [{"value": bob.id}]
546+
547+
updated = storage.update(group_type, group)
548+
549+
assert updated.meta.version != group.meta.version
550+
assert updated.meta.last_modified > group.meta.last_modified
551+
552+
541553
class ChangedMeanwhile:
542554
"""Make the record look loaded at another version, as when another transaction changes it right before the write."""
543555

0 commit comments

Comments
 (0)