gertvdijk/purepythonmilter

How to keep a context between multiple hooks?

Opened this issue · 3 comments

Sorry if this is obvious, but async python is new to me. How do I keep a context for a single email when I have multiple hooks? i.e. I want to check the daemon_name macro in the on_connect hook, and then check if one or more header values are present in the hook_on_header callback and only then decide to do something (i.e. add a header or reject the message).

Is there some kind of state variable or context where I can keep information / state about an email between hooks?

It's not possible currently, and it should be a new API I think. See #7 (comment)

Another API design that I like is using a context manager, as used in Kilter (https://code.kodo.org.uk/kilter/kilter.service).

But anyway, I think it just needs a whole refactor, I'm afraid. Help appreciated, as I probably am lacking time the coming months to pick that up.

the contextvars module has asyncio support and i think it could help here: https://docs.python.org/3/library/contextvars.html#asyncio-support

As @wbolster said, using contextvars seems to work well. For example:

from contextvars import ContextVar

# at the global scope:
message_store: ContextVar[str] = ContextVar("message_store", default="")
premium_domain: ContextVar[bool] = ContextVar("premium_domain", default=False)

# Then in the "on_" handlers, for example:

async def on_mail_from(cmd: MailFrom) -> None:
    _, domain = cmd.address.split("@")
    if await redis.sismember("premium_domains", domain):
        premium_domain.set(True)

async def on_body_chunk(cmd: BodyChunk) -> None:
    message_store.set(message_store.get() + cmd.data_raw.decode())

async def on_end_of_message(cmd: EndOfMessage) -> Continue:
    if premium_domain.get():
        logger.info(f"Premium user, skipping. ID={cmd.macros['i']}")
        return Continue()
    message = add_powered_by(message_store.get())
    logger.info(f"On end of message with Powered by. ID={cmd.macros['i']}")
    return Continue(manipulations=[ReplaceBodyChunk(chunk=message.encode())])

@gertvdijk am I missing anything?