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

1from typing import List 

2 

3from opal_common.schemas.scopes import Scope 

4from opal_server.redis_utils import RedisDB 

5 

6 

7class ScopeNotFoundError(Exception): 

8 def __init__(self, id: str): 

9 self._id = id 

10 

11 def __str__(self) -> str: 

12 return f"Scope {self._id} not found" 

13 

14 

15class ScopeRepository: 

16 def __init__(self, redis_db: RedisDB): 

17 self._redis_db = redis_db 

18 self._prefix = "permit.io/Scope" 

19 

20 @property 

21 def db(self) -> RedisDB: 

22 return self._redis_db 

23 

24 async def all(self) -> List[Scope]: 

25 scopes = [] 

26 

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) 

44 

45 return scopes 

46 

47 async def get(self, scope_id: str) -> Scope: 

48 key = self._redis_key(scope_id) 

49 value = await self._redis_db.get(key) 

50 

51 if value: 

52 return Scope.parse_raw(value) 

53 else: 

54 raise ScopeNotFoundError(scope_id) 

55 

56 async def put(self, scope: Scope): 

57 key = self._redis_key(scope.scope_id) 

58 await self._redis_db.set(key, scope) 

59 

60 async def delete(self, scope_id: str): 

61 key = self._redis_key(scope_id) 

62 await self._redis_db.delete(key) 

63 

64 def _redis_key(self, scope_id: str): 

65 return f"{self._prefix}:{scope_id}"