Saturday, November 4, 2023

Python Asynchronous Context Manager PlayGround

class AsyncContextManager:
    global correlationMap
    global requestId
    async def __aenter__(self) -> Literal['Waited']: # enter the async context manager
        print('>Entering the async context manager') # report a message
        await asyncio.sleep(0.05) # block for a moment
        return 'Waited'
    async def __aexit__(self, exc_type, exc, tb) -> Literal['Exited']: # exit the async context manager
        print('>Exiting the async context manager') # send a message
        await asyncio.sleep(0.05) # block for a moment
        return 'Exited'
    async def getresult(self):
        await asyncio.sleep(0.05)
        # print('Running inside the context manager')
        return correlationMap[requestId]
    async def dummyasync() -> Literal['Dummy function']:
        print(f"Dummy function started")
        await asyncio.sleep(0.1)
        return 'Dummy function' 
 
async with AsyncContextManager():
    app.add_background_task(callback)
    app.add_background_task(checkResult)
    while responsefirstcall.text == 'Waiting...':
        correlationMap[requestId] = await AsyncContextManager().getresult()
        print('correlationMap before loop: ' + str(correlationMap[requestId]))
            while correlationMap[requestId] == 'Waiting...':
                await asyncio.sleep(0.2)
                print('checkResult inside while loop: ' + str(response.text[:-1]))
                correlationMap[requestId] = await AsyncContextManager().getresult()
                print('Check result inside while loop: ' + str(correlationMap[requestId][:-1]) + ' ' + str(time.strftime("%A %d:%m:%Y %H:%M:%S UTC%z", time.localtime())))
try:
                            # response: httpx.Response = await client.get(f'http://{externalip}:5000/checkResult')
                            if correlationMap[requestId] != 'Waiting...':
                            # if response.text != 'Waiting...': # print('Not Waiting...')
                                # etransdomain = scripts.etrans.etrans(sourcelang, [targetlang], 'SPD', searchformterm).domain
                                # rows.append('E-Translate domain: ' + etransdomain)
                                rows.append('E-Translate')
                                rows.append(correlationMap[requestId].replace('\n', ''))
                                # rows.append(response.text.replace('\n', ''))
                                print(rows)
                                break
                            # else:
                            #     continue
                        except Exception as exc:
                            print(exc)
                            await asyncio.sleep(0.1)
                            rows.append('E-Translate')
                            rows.append('Error')
                            break
 
if rows: print("list is not empty")

Future

Future is simply an abstraction of value that may be not computed yet and will be available eventually. It's a simple container that only does one thing - whenever the value is set, fire all registered callbacks.

If you want to obtain that value, you register a callback via add_done_callback() method.

But unlike in Promise, the actual computation is done externally - and that external code has to call set_result() method to resolve the future.

Coroutine

Coroutine is the object very similar to Generator.

A generator is typically iterated within for loop. It yields values and, starting from PEP342 acceptance, it receives values.

A coroutine is typically iterated within the event loop in depths of asyncio library. A coroutine yields Future instances. When you are iterating over a coroutine and it yields a future, you shall wait until this future is resolved. After that you shall send the value of future into the coroutine, then you receive another future, and so on.

An await expression is practically identical to yield from expression, so by awaiting other coroutine, you stop until that coroutine has all its futures resolved, and get coroutine's return value. The Future is one-tick iterable and its iterator returns actual Future - that roughly means that await future equals yield from future equals yield future.

Task

Task is Future which has been actually started to compute and is attached to event loop. So it's special kind of Future (class Task is derived from class Future), which is associated with some event loop, and it has some coroutine, which serves as Task executor.

Task is usually created by event loop object: you give a coroutine to the loop, it creates Task object and starts to iterate over that coroutine in manner described above. Once the coroutine is finished, Task's Future is resolved by coroutine's return value.

You see, the task is quite similar to JS Promise - it encapsulates background job and its result.

Coroutine Function and Async Function

Coroutine func is a factory of coroutines, like generator function to generators. Notice the difference between Python's coroutine function and Javascript's async function - JS async function, when called, creates a Promise and its internal generator immediately starts being iterated, while Python's coroutine does nothing, until Task is created upon it.