How to keep a context between multiple hooks?
Sofahamster 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?