It's normal to develop a simple implementation first. So, for example, we might start with a non-concurrent program, and then add concurrency. I'd like to be able to switch smoothly back and forth.
For example, single-threaded (pseudocode):
results=[]
for url in urls:
# This then calls other functions that call yet others
# in a call hierarchy, down to a requests.request() call.
get_result_and_store_in_database(url)
Async (pseudocode):
# The following calls other functions that call yet others
# in a call hierarchy, down to an asyncio ClientSession().get() call.
# It runs HTTP requests and store the results in a database.
# The multiple URLs are processed concurrently.
asyncio.run(get_results_in_parallel_and_store_in_db(urls))
With Python async/await, usually you wrap the run with asyncio.run() (compared to the loop you use in ordinary programs); then at the bottom of the call hierarchy, you use an IO action like aiohttp.ClientSession().get(url) (compared to the ordinary requests.request().)
But, in the async version, all functions in the call hierarchy between those two must be written as async/await. So, I need to write two copies of what is basically the same call-hierarchy, differing mostly in whether they have the async/await keywords.
That is a lot of code duplication.
How can I make a switchably non-concurrent/async program?