Question: I'm developing a library that should support sync and async users while minimizing code duplication inside the library. The ideal case would be implementing the library async (since the library does remote API calls) and adding support for sync with a wrapper or so. Consider the following function as part of the library. # Library code async def internal_function(): """Internal part of my library doing HTTP call""" pass  My idea was to provide a wrapper that detects if the users of the library use async or not. # Library code def api_call(): """Public api of my library""" if asyncio.get_event_loop().is_running(): # Async caller return internal_function() # This returns a coroutine, so the caller needs to await it # Sync caller, so we need to run the coroutine in a blocking way return asyncio.run(internal_function())  At first, this seemed to be the solution. With this I can support  users that call the function from an async function, users that call the function from a notebook (that's also async), and users that call the function from a plain python script (here it falls back to the blocking asyncio.run).  However, there are cases when the function is called from within an event loop, but the direct caller is a sync function. # Code from users of the library async def entrypoint(): """This entrypoint is called in an event loop, e.g. within fastlib""" return legacy_sync_code() # Call to sync function def legacy_sync_code(): """Legacy code that does not support async""" # Call to library code from sync function, # expect value not coroutine, could not use await api_response = api_call() return api_response.json() # Needs to be value, not coroutine  In the last line, the call of json() fails. api_call() wrongly inferrs that the caller can await the response, and returns the coroutine. In order to support these kind of users, I would need to  identify if the direct calling function is sync and expects a value instead of a corouting, have a way to await the result of internal_function() in a blocking way. Using asyncio.run does only work in plain python scripts and fails if the code was called form an event loop higher up in the stack trace.  The first point could be mitigated if my library provided two functions, e.g., api_call_async() and api_call_sync(). I hope I make my point clear enough. I don't see why this should be fundamentally impossible, however, I can accept if the design of Python does not allow me to support sync and async users in a completely transparent way.

Answer: Do not try to be too flexible or solve problems that yet not occurred. That would make your life harder and potentially your code more complex to develop and maintain and in result not really that flexible as you'd have thought. It's nothing wrong to not babysit fellow developers and let them take some bit of responsibility. Let them make own choice if they want to go sync or async. Also note, that what can appear benefit for you (in terms of implementation details etc, like detecting sync/async), may be a deal breaker for others as you might be doing something you weren't asked for in the first place. It's easy to outsmart yourself for no real benefit and then waste lot of time fixing problems you just happily created. I personally would go for separate api_call_sync() and api_call_async() approach but also ensure my library documentation is dumb-clear and discuss all the edge cases you right now think of as being potentially problematic, so my users got full picture of the battlefield to take the best shot they can.