2016-05-25 19 views
9

Ho un asynchronous API che sto usando per connettere e inviare posta a un server SMTP che ha un po 'di configurazione e di abbattere. Quindi si adatta perfettamente all'uso di un contextmanager da Python 3 contextlib.Gestore di contesto asincrono

Tuttavia, non so se è possibile scrivere perché entrambi usano la sintassi del generatore per scrivere.

Questo potrebbe dimostrare il problema (contiene una combinazione di sintassi di rendimento e asincrono per dimostrare la differenza tra chiamate asincrone e rese al gestore di contesto).

@contextmanager 
async def smtp_connection(): 
    client = SMTPAsync() 
    ... 

    try: 
     await client.connect(smtp_url, smtp_port) 
     await client.starttls() 
     await client.login(smtp_username, smtp_password) 
     yield client 
    finally: 
     await client.quit() 

Attualmente questo tipo di cose è possibile all'interno di python? e come dovrei usare una dichiarazione withas se lo è? Se no, esiste un modo alternativo per ottenerlo, magari usando il gestore di contesto vecchio stile?

+0

'asyncio' ha introdotto anche un' asincrona with' contesto asincrono protocollo di gestione, vedi: https://www.python.org/dev/peps/pep-0492/#asynchronous-context-managers-and-async-with – jonrsharpe

+0

Questo sembra esattamente quello che voglio. Darà un colpo implementandolo quando avrò la possibilità. – freebie

+1

A partire da 3.7 (rilascio da qualche parte nel 2018), contextlib avrà '@ asynccontextmanager' –

risposta

9

Grazie a @jonrsharpe è stato possibile creare un gestore di contesto asincrono.

Ecco cosa mia ha finito per assomigliare a tutti coloro che vogliono qualche codice di esempio:

class SMTPConnection(): 
    def __init__(self, url, port, username, password): 
     self.client = SMTPAsync() 
     self.url  = url 
     self.port  = port 
     self.username = username 
     self.password = password 

    async def __aenter__(self): 
     await self.client.connect(self.url, self.port) 
     await self.client.starttls() 
     await self.client.login(self.username, self.password) 

     return self.client 

    async def __aexit__(self, exc_type, exc, tb): 
     await self.client.quit() 

utilizzo:

async with SMTPConnection(url, port, username, password) as client: 
    await client.sendmail(...) 

Sentitevi liberi di precisare se ho fatto qualcosa di stupido.

+0

Il problema è che se lo si utilizza due volte contemporaneamente, il client del secondo invio sovrascriverà il primo e l'uscita sul primo uscirà dal secondo pure. – iScrE4m

+0

@ iScrE4m Ah sì, non avevo previsto che venisse usato più volte, creando solo un'istanza, su richiesta, per un uso singolo. Potrebbe forse fare una classe wrapper a questa che deleghi è '__aenter__' e' __aexit__' a nuove istanze. – freebie

4

Il pacchetto asyncio_extras ha una bella soluzione per questo:

import asyncio_extras 

@asyncio_extras.async_contextmanager 
async def smtp_connection(): 
    client = SMTPAsync() 
    ... 

Per Python < 3.6, si sarebbe anche bisogno il pacchetto async_generator e sostituirlo con yield clientawait yield_(client).

1

In Python 3.7, sarete in grado di scrivere:

from contextlib import asynccontextmanager 

@asynccontextmanager 
async def smtp_connection(): 
    client = SMTPAsync() 
    ... 

    try: 
     await client.connect(smtp_url, smtp_port) 
     await client.starttls() 
     await client.login(smtp_username, smtp_password) 
     yield client 
    finally: 
     await client.quit() 

Fino 3,7 viene fuori, è possibile utilizzare il pacchetto async_generator per questo. Sul 3.6, è possibile scrivere:

# This import changed, everything else is the same 
from async_generator import asynccontextmanager 

@asynccontextmanager 
async def smtp_connection(): 
    client = SMTPAsync() 
    ... 

    try: 
     await client.connect(smtp_url, smtp_port) 
     await client.starttls() 
     await client.login(smtp_username, smtp_password) 
     yield client 
    finally: 
     await client.quit() 

E se si desidera lavorare tutta la strada fino a 3,5, si può scrivere:

# This import changed again: 
from async_generator import asynccontextmanager, async_generator, yield_ 

@asynccontextmanager 
@async_generator  # <-- added this 
async def smtp_connection(): 
    client = SMTPAsync() 
    ... 

    try: 
     await client.connect(smtp_url, smtp_port) 
     await client.starttls() 
     await client.login(smtp_username, smtp_password) 
     await yield_(client) # <-- this line changed 
    finally: 
     await client.quit()