Rina
08/19/2025, 8:19 AMFlow run encountered an exception: TypeError: object NoneType can't be used in 'await' expression
.
def notify_on_slack(slack_text: str, channel: str = "channel1"):
"""Send Slack notification - following the working pattern"""
async def notify(slack_text: str):
try:
if channel == "channel1" or channel == "channel2":
slack_webhook_block = await SlackWebhook.aload(f"slack-{channel}")
if slack_webhook_block is None:
raise ValueError(f"Slack webhook block 'slack-{channel}' not found")
else:
raise ValueError(f"Invalid channel: {channel}")
cleaned_text = cleandoc(slack_text)
result = await slack_webhook_block.notify(cleaned_text)
return result
except Exception as e:
print(f"Failed to send Slack notification to {channel}: {e}")
raise e
return asyncio.run(notify(slack_text))
When I remove the await before slack_webhook_block.notify
it doesn't throw any error but the message is not sent in the Slack channel either. What am I missing?Marvin
08/19/2025, 8:19 AMMarvin
08/19/2025, 8:22 AMSlackWebhook.notify(...)
is synchronous and returns None
.
- await slack_webhook_block.notify(...)
tries to await None
→ TypeError: object NoneType can't be used in 'await' expression
.
2) You’re wrapping everything in asyncio.run
- Don’t call asyncio.run
from within Prefect flows/tasks; Prefect manages the event loop. Make your wrapper either fully sync or fully async without asyncio.run
.
Here are two clean patterns that work.
Pattern A: Keep it simple (sync everywhere)
from textwrap import dedent as cleandoc
from prefect.blocks.notifications import SlackWebhook
def notify_on_slack(slack_text: str, channel: str = "channel1"):
if channel not in {"channel1", "channel2"}:
raise ValueError(f"Invalid channel: {channel}")
block = SlackWebhook.load(f"slack-{channel}") # sync load
block.notify(cleandoc(slack_text)) # sync notify
Use it from a sync flow or task without any async ceremony:
from prefect import flow
@flow
def my_flow():
notify_on_slack("Hello from Prefect!", "channel1")
Pattern B: If you’re already in an async function
- Load the block asynchronously if you like, but still call .notify
without await
.
- If you want to avoid blocking the event loop, run the sync call in a thread.
from textwrap import dedent as cleandoc
from prefect.blocks.notifications import SlackWebhook
import anyio
async def notify_on_slack_async(slack_text: str, channel: str = "channel1"):
if channel not in {"channel1", "channel2"}:
raise ValueError(f"Invalid channel: {channel}")
block = await SlackWebhook.aload(f"slack-{channel}")
# Notify is sync; run it in a worker thread so you don't block the loop
await anyio.to_thread.run_sync(block.notify, cleandoc(slack_text))
Common pitfalls if you still don’t see a message:
- The block name is wrong. Ensure a block named exactly slack-channel1
(or slack-channel2
) exists and has a valid Incoming Webhook URL.
- The webhook belongs to a different Slack workspace or channel than you’re watching.
- Your Slack app doesn’t have Incoming Webhooks enabled for that channel.
- Network egress blocked. Normally you’d see an HTTP error in logs if Slack rejected the request.
Quick sanity checks:
# 1) Will raise if the block doesn’t exist
from prefect.blocks.notifications import SlackWebhook
SlackWebhook.load("slack-channel1")
# 2) If you have the raw webhook URL, test directly:
import requests
<http://requests.post|requests.post>("<your webhook url>", json={"text": "Ping from requests"})
References
- Slack webhook block API (Prefect 3.x): https://docs.prefect.io/latest/api-ref/blocks/#prefect.blocks.notifications.SlackWebhook
- Your linked context: issue and code snippet
If you can share:
- Your Prefect version
- How you created the blocks (names, workspace)
- Where you’re calling notify_on_slack
from (sync vs async flow/task)
…I can point out the exact fix instead of playing guess-the-webhook. I live for it.Rina
08/19/2025, 8:32 AMMarvin
08/19/2025, 8:37 AMMarvin
08/19/2025, 8:38 AMimport importlib.util
print(importlib.util.find_spec("apprise"))
If that prints None:
pip install apprise
# or
pip install "prefect[notifications]"
2) The webhook itself isn’t working (or points to a different channel/workspace)
- Sanity test the exact URL outside Prefect:
import requests
url = "<your slack incoming webhook url>"
r = <http://requests.post|requests.post>(url, json={"text": "Ping from requests"})
print(r.status_code, r.text)
r.raise_for_status()
If this doesn’t post, the webhook/app is disabled or tied to a different channel/workspace.
3) Message content ends up empty
- Some formatting pipelines can strip everything. Try the most boring possible payload:
from prefect.blocks.notifications import SlackWebhook
SlackWebhook.load("slack-channel1").notify("hello world")
4) You’re expecting channel
to change the Slack destination
- The channel
argument in your function only selects which block to load. The Slack destination is determined by the webhook URL stored in that block. If `slack-channel1`’s URL points to a different channel, that’s where it will go.
5) Networking/logging hides the problem
- Try DEBUG logging to surface HTTP failures:
import logging
logging.getLogger("prefect").setLevel(logging.DEBUG)
logging.getLogger("apprise").setLevel(logging.DEBUG)
- If you’re running in a worker/container/VPC, confirm outbound HTTPS is allowed.
A minimal end-to-end check (no cleverness, just facts):
from prefect.blocks.notifications import SlackWebhook
# 1) Create a throwaway block with a fresh webhook URL
SlackWebhook(url="<https://hooks.slack.com/services/...>").save("slack-smoke-test", overwrite=True)
# 2) Send a message
SlackWebhook.load("slack-smoke-test").notify("smoke test: hello from Prefect 3.4.13")
If that still doesn’t show up:
- What’s the output of the <http://requests.post|requests.post>
test?
- Is apprise
installed?
- Are you running this in the same environment where earlier alerts worked (those may have used a different Slack integration than incoming webhooks)?
If you share:
- Whether apprise
is installed
- The HTTP status/text from the direct <http://requests.post|requests.post>
test
- Where the flow runs (local, container, worker, VPC)
…I can point you to the exact culprit. With only a minor sigh.Rina
08/19/2025, 10:30 AMMarvin
08/19/2025, 10:30 AMMarvin
08/19/2025, 10:32 AMfrom prefect.blocks.notifications import SlackWebhook
from prefect import runtime
print("workspace:", getattr(runtime, "workspace_name", "unknown"))
blk = SlackWebhook.load("slack-channel1")
# Do NOT print the URL; just confirm it exists and has a sensible length
url = blk.url.get_secret_value()
print("url length:", len(url), "endswith:", url[-8:])
2) Check apprise version and call it directly (prints whether it thinks it sent anything)
import apprise
print("apprise version:", apprise.__version__)
a = apprise.Apprise()
a.add(url)
ok = a.notify(body="hello from ECS via apprise")
print("apprise.notify returned:", ok)
3) Bypass apprise: post directly to Slack using the same URL
import requests
r = <http://requests.post|requests.post>(url, json={"text": "hello from ECS via requests"})
print("requests:", r.status_code, r.text[:200])
r.raise_for_status()
Interpretation
- If step 2 returns False but step 3 is 200/ok: the issue is Apprise-in-ECS. Pin/ensure the dependency:
- pip install "prefect[notifications]==3.4.13"
- And ensure a recent Apprise (e.g., >=1.9.4). Print it after install to be sure.
- If both 2 and 3 fail: this is ECS networking/DNS/proxy egress. You’ll usually see a non-200 or an exception in step 3.
- If both succeed, but SlackWebhook.load(...).notify("text")
still “does nothing”: we’re likely loading a different block (different workspace) or the call path swallows the apprise return. Confirm the workspace printed above matches where you created the block, and try this exact minimal call:
from prefect.blocks.notifications import SlackWebhook
SlackWebhook.load("slack-channel1").notify("hello world (minimal)")
A few more checks to avoid wild goose chases:
- Confirm the ECS image actually has the right deps:
import prefect, apprise
print("prefect:", prefect.__version__, "apprise:", apprise.__version__)
- Make sure the block you’re loading is the one you tested with automations. If you have multiple workspaces (dev/prod), the same block name can exist with different URLs.
- If you must keep moving, you can skip Apprise entirely in your flow:
from prefect.blocks.notifications import SlackWebhook
import requests
def notify_on_slack(text, block_name="slack-channel1"):
url = SlackWebhook.load(block_name).url.get_secret_value()
r = <http://requests.post|requests.post>(url, json={"text": text})
r.raise_for_status()
If you can paste back:
- The outputs of steps 1–3 (mask the last 8 chars if you like; we only need the status/booleans)
- The printed Prefect/Apprise versions
- The printed workspace name
…I’ll pinpoint the exact issue. I’m practically thrilled.Rina
08/19/2025, 11:29 AMrequest
solution and I saw a fix was already made here, as I understand it'll be available in the next Prefect version