1 import inspect 2 3 from .sync import iscoroutinefunction 4 5 6 def is_double_callable(application): 7 """ 8 Tests to see if an application is a legacy-style (double-callable) application. 9 """ 10 # Look for a hint on the object first 11 if getattr(application, "_asgi_single_callable", False): 12 return False 13 if getattr(application, "_asgi_double_callable", False): 14 return True 15 # Uninstanted classes are double-callable 16 if inspect.isclass(application): 17 return True 18 # Instanted classes depend on their __call__ 19 if hasattr(application, "__call__"): 20 # We only check to see if its __call__ is a coroutine function - 21 # if it's not, it still might be a coroutine function itself. 22 if iscoroutinefunction(application.__call__): 23 return False 24 # Non-classes we just check directly 25 return not iscoroutinefunction(application) 26 27 28 def double_to_single_callable(application): 29 """ 30 Transforms a double-callable ASGI application into a single-callable one. 31 """ 32 33 async def new_application(scope, receive, send): 34 instance = application(scope) 35 return await instance(receive, send) 36 37 return new_application 38 39 40 def guarantee_single_callable(application): 41 """ 42 Takes either a single- or double-callable application and always returns it 43 in single-callable style. Use this to add backwards compatibility for ASGI 44 2.0 applications to your server/test harness/etc. 45 """ 46 if is_double_callable(application): 47 application = double_to_single_callable(application) 48 return application 49