11from abc import ABC , abstractmethod
2- from collections .abc import Awaitable , Callable , MutableMapping
3- from contextlib import suppress
2+ from collections .abc import Awaitable , Callable , Iterator , MutableMapping
3+ from contextlib import contextmanager , suppress
44from dataclasses import dataclass , field
55from functools import partial
66from typing import (
@@ -53,11 +53,13 @@ def get_instance(self) -> T:
5353
5454
5555class CacheLogic [T ]:
56- __slots__ = ("__semaphore" , )
56+ __slots__ = ("__is_instantiating" , "__semaphore" )
5757
58+ __is_instantiating : bool
5859 __semaphore : AsyncContextManager [Any ]
5960
6061 def __init__ (self ) -> None :
62+ self .__is_instantiating = False
6163 self .__semaphore = AsyncSemaphore (1 )
6264
6365 async def aget_or_create [K ](
@@ -66,11 +68,14 @@ async def aget_or_create[K](
6668 key : K ,
6769 factory : Callable [..., Awaitable [T ]],
6870 ) -> T :
71+ self .__fail_if_instantiating ()
6972 async with self .__semaphore :
7073 with suppress (KeyError ):
7174 return cache [key ]
7275
73- instance = await factory ()
76+ with self .__instantiating ():
77+ instance = await factory ()
78+
7479 cache [key ] = instance
7580
7681 return instance
@@ -81,13 +86,29 @@ def get_or_create[K](
8186 key : K ,
8287 factory : Callable [..., T ],
8388 ) -> T :
89+ self .__fail_if_instantiating ()
8490 with suppress (KeyError ):
8591 return cache [key ]
8692
87- instance = factory ()
93+ with self .__instantiating ():
94+ instance = factory ()
95+
8896 cache [key ] = instance
8997 return instance
9098
99+ def __fail_if_instantiating (self ) -> None :
100+ if self .__is_instantiating :
101+ raise RecursionError ("Recursive call detected during instantiation." )
102+
103+ @contextmanager
104+ def __instantiating (self ) -> Iterator [None ]:
105+ self .__is_instantiating = True
106+
107+ try :
108+ yield
109+ finally :
110+ self .__is_instantiating = False
111+
91112
92113@dataclass (repr = False , eq = False , frozen = True , slots = True )
93114class SingletonInjectable [T ](Injectable [T ]):
0 commit comments