Coverage for /usr/local/lib/python3.10/site-packages/opal_server-0.0.0-py3.10.egg/opal_server/scopes/scope_repository.py: 93%
37 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
1from typing import List
3from opal_common.schemas.scopes import Scope
4from opal_server.redis_utils import RedisDB
7class ScopeNotFoundError(Exception):
8 def __init__(self, id: str):
9 self._id = id
11 def __str__(self) -> str:
12 return f"Scope {self._id} not found"
15class ScopeRepository:
16 def __init__(self, redis_db: RedisDB):
17 self._redis_db = redis_db
18 self._prefix = "permit.io/Scope"
20 @property
21 def db(self) -> RedisDB:
22 return self._redis_db
24 async def all(self) -> List[Scope]:
25 scopes = []
27 async for value in self._redis_db.scan(f"{self._prefix}:*"):
28 if not value: 28 ↛ 41line 28 didn't jump to line 41 because the condition on line 28 was never true
29 # The scan lists keys and then reads each one, so a scope
30 # deleted in between comes back as None. Passing that to
31 # parse_raw raises ValidationError and kills the WHOLE scan —
32 # one concurrently-deleted scope would abort an entire
33 # sync_scopes pass, and poison the sibling check a delete's
34 # purge depends on (observed under the bed's churn: two
35 # deletes' clone dirs stranded because their own delete raced
36 # the scan). A key that no longer exists is simply not a scope.
37 #
38 # Deliberately narrow: a record that IS present but does not
39 # parse still raises, because that is corruption and should not
40 # be silently skipped.
41 continue
42 scope = Scope.parse_raw(value)
43 scopes.append(scope)
45 return scopes
47 async def get(self, scope_id: str) -> Scope:
48 key = self._redis_key(scope_id)
49 value = await self._redis_db.get(key)
51 if value:
52 return Scope.parse_raw(value)
53 else:
54 raise ScopeNotFoundError(scope_id)
56 async def put(self, scope: Scope):
57 key = self._redis_key(scope.scope_id)
58 await self._redis_db.set(key, scope)
60 async def delete(self, scope_id: str):
61 key = self._redis_key(scope_id)
62 await self._redis_db.delete(key)
64 def _redis_key(self, scope_id: str):
65 return f"{self._prefix}:{scope_id}"