web-dev-qa-db-fra.com

Gestionnaire de contexte asynchrone

J'ai un API asynchrone que j'utilise pour me connecter et envoyer du courrier à un serveur SMTP qui a une configuration et un démontage. Il s'intègre donc parfaitement dans l'utilisation d'un contextmanager de Python 3's contextlib.

Cependant, je ne sais pas si c'est possible d'écrire car ils utilisent tous les deux la syntaxe du générateur pour écrire.

Cela pourrait démontrer le problème (contient un mélange de syntaxe de base de rendement et d'attente asynchrone pour démontrer la différence entre les appels asynchrones et les rendements au gestionnaire de contexte).

@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()

Est-ce que ce genre de chose est possible dans python actuellement? Et comment pourrais-je utiliser une instruction withas si elle l'est? Sinon, existe-t-il une autre façon pourrait y parvenir - peut-être en utilisant l'ancien gestionnaire de contexte de style?

28
freebie

Merci à @jonrsharpe a pu créer un gestionnaire de contexte asynchrone.

Voici à quoi ressemblait le mien pour tous ceux qui veulent un exemple de code:

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()

usage:

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

N'hésitez pas à signaler si j'ai fait quelque chose de stupide.

18
freebie

Dans Python 3.7, vous pourrez écrire:

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()

Jusqu'à la sortie de la version 3.7, vous pouvez utiliser le async_generator package pour cela. Sur 3.6, vous pouvez écrire:

# 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()

Et si vous souhaitez revenir à la version 3.5, vous pouvez écrire:

# 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()
37
Nathaniel J. Smith

Le paquet asyncio_extras a une belle solution pour cela:

import asyncio_extras

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

Pour Python <3.6, vous auriez également besoin du paquet async_generator et remplacer yield client Par await yield_(client).

9
Bart Robinson