Lee-W commented on code in PR #56911:
URL: https://github.com/apache/airflow/pull/56911#discussion_r2498199826
##########
providers/discord/src/airflow/providers/discord/hooks/discord_webhook.py:
##########
@@ -19,10 +19,74 @@
import json
import re
-from typing import Any
+from typing import TYPE_CHECKING, Any
-from airflow.exceptions import AirflowException
-from airflow.providers.http.hooks.http import HttpHook
+import aiohttp
+
+from airflow.providers.common.compat.connection import get_async_connection
+from airflow.providers.http.hooks.http import HttpAsyncHook, HttpHook
+
+if TYPE_CHECKING:
+ from airflow.providers.common.compat.sdk import Connection
+
+
+class DiscordCommonHandler:
+ """Contains the common functionality."""
+
+ def get_webhook_endpoint(self, conn: Connection | None, webhook_endpoint:
str | None) -> str:
+ """
+ Return the default webhook endpoint or override if a webhook_endpoint
is manually supplied.
+
+ :param conn: Airflow Discord connection
+ :param webhook_endpoint: The manually provided webhook endpoint
+ :return: Webhook endpoint (str) to use
+ """
+ if webhook_endpoint:
+ endpoint = webhook_endpoint
+ elif conn:
+ extra = conn.extra_dejson
+ endpoint = extra.get("webhook_endpoint", "")
+ else:
+ raise ValueError(
+ "Cannot get webhook endpoint: No valid Discord webhook
endpoint or http_conn_id supplied."
+ )
+
+ # make sure endpoint matches the expected Discord webhook format
+ if not re.fullmatch("webhooks/[0-9]+/[a-zA-Z0-9_-]+", endpoint):
+ raise ValueError(
+ 'Expected Discord webhook endpoint in the form of
"webhooks/{webhook.id}/{webhook.token}".'
+ )
+
+ return endpoint
+
+ def build_discord_payload(
+ self, tts: bool, message: str, username: str | None, avatar_url: str |
None
+ ) -> str:
+ """
+ Build a valid Discord JSON payload.
+
+ :param tts: Is a text-to-speech message
+ :param message: The message you want to send to your Discord channel
+ (max 2000 characters)
+ :param username: Override the default username of the webhook
+ :param avatar_url: Override the default avatar of the webhook
+ :return: Discord payload (str) to send
+ """
+ payload: dict[str, Any] = {}
+
+ if username:
+ payload["username"] = username
+ if avatar_url:
+ payload["avatar_url"] = avatar_url
+
+ payload["tts"] = tts
+
+ if len(message) <= 2000:
+ payload["content"] = message
+ else:
+ raise ValueError("Discord message length must be 2000 or fewer
characters.")
Review Comment:
```suggestion
if len(message) > 200:
raise ValueError("Discord message length must be 2000 or fewer
characters.")
payload: dict[str, Any] = {
"content": message,
"tts": tts,
}
if username:
payload["username"] = username
if avatar_url:
payload["avatar_url"] = avatar_url
```
##########
providers/discord/src/airflow/providers/discord/hooks/discord_webhook.py:
##########
@@ -19,10 +19,74 @@
import json
import re
-from typing import Any
+from typing import TYPE_CHECKING, Any
-from airflow.exceptions import AirflowException
-from airflow.providers.http.hooks.http import HttpHook
+import aiohttp
+
+from airflow.providers.common.compat.connection import get_async_connection
+from airflow.providers.http.hooks.http import HttpAsyncHook, HttpHook
+
+if TYPE_CHECKING:
+ from airflow.providers.common.compat.sdk import Connection
+
+
+class DiscordCommonHandler:
+ """Contains the common functionality."""
+
+ def get_webhook_endpoint(self, conn: Connection | None, webhook_endpoint:
str | None) -> str:
+ """
+ Return the default webhook endpoint or override if a webhook_endpoint
is manually supplied.
+
+ :param conn: Airflow Discord connection
+ :param webhook_endpoint: The manually provided webhook endpoint
+ :return: Webhook endpoint (str) to use
+ """
+ if webhook_endpoint:
+ endpoint = webhook_endpoint
+ elif conn:
+ extra = conn.extra_dejson
+ endpoint = extra.get("webhook_endpoint", "")
+ else:
+ raise ValueError(
+ "Cannot get webhook endpoint: No valid Discord webhook
endpoint or http_conn_id supplied."
+ )
+
+ # make sure endpoint matches the expected Discord webhook format
+ if not re.fullmatch("webhooks/[0-9]+/[a-zA-Z0-9_-]+", endpoint):
+ raise ValueError(
+ 'Expected Discord webhook endpoint in the form of
"webhooks/{webhook.id}/{webhook.token}".'
+ )
+
+ return endpoint
+
+ def build_discord_payload(
+ self, tts: bool, message: str, username: str | None, avatar_url: str |
None
Review Comment:
let's make it keyword only, the order of these parameters does not matter or
mean something
```suggestion
self, *, tts: bool, message: str, username: str | None, avatar_url:
str | None
```
##########
providers/discord/src/airflow/providers/discord/hooks/discord_webhook.py:
##########
@@ -19,10 +19,74 @@
import json
import re
-from typing import Any
+from typing import TYPE_CHECKING, Any
-from airflow.exceptions import AirflowException
-from airflow.providers.http.hooks.http import HttpHook
+import aiohttp
+
+from airflow.providers.common.compat.connection import get_async_connection
+from airflow.providers.http.hooks.http import HttpAsyncHook, HttpHook
+
+if TYPE_CHECKING:
+ from airflow.providers.common.compat.sdk import Connection
+
+
+class DiscordCommonHandler:
+ """Contains the common functionality."""
+
+ def get_webhook_endpoint(self, conn: Connection | None, webhook_endpoint:
str | None) -> str:
+ """
+ Return the default webhook endpoint or override if a webhook_endpoint
is manually supplied.
+
+ :param conn: Airflow Discord connection
+ :param webhook_endpoint: The manually provided webhook endpoint
+ :return: Webhook endpoint (str) to use
+ """
+ if webhook_endpoint:
+ endpoint = webhook_endpoint
+ elif conn:
+ extra = conn.extra_dejson
+ endpoint = extra.get("webhook_endpoint", "")
+ else:
+ raise ValueError(
+ "Cannot get webhook endpoint: No valid Discord webhook
endpoint or http_conn_id supplied."
+ )
+
+ # make sure endpoint matches the expected Discord webhook format
+ if not re.fullmatch("webhooks/[0-9]+/[a-zA-Z0-9_-]+", endpoint):
+ raise ValueError(
+ 'Expected Discord webhook endpoint in the form of
"webhooks/{webhook.id}/{webhook.token}".'
+ )
+
+ return endpoint
+
+ def build_discord_payload(
+ self, tts: bool, message: str, username: str | None, avatar_url: str |
None
Review Comment:
do we want to rename `message` as `content` for consistency?
##########
providers/discord/src/airflow/providers/discord/hooks/discord_webhook.py:
##########
@@ -148,11 +177,100 @@ def execute(self) -> None:
# we only need https proxy for Discord
proxies = {"https": self.proxy}
- discord_payload = self._build_discord_payload()
+ discord_payload = self.handler.build_discord_payload(
+ tts=self.tts, message=self.message, username=self.username,
avatar_url=self.avatar_url
+ )
self.run(
endpoint=self.webhook_endpoint,
data=discord_payload,
headers={"Content-type": "application/json"},
extra_options={"proxies": proxies},
)
+
+
+class DiscordWebhookAsyncHook(HttpAsyncHook):
+ """
+ This hook allows you to post messages to Discord using incoming webhooks
using async HTTP.
+
+ Takes a Discord connection ID with a default relative webhook endpoint. The
+ default endpoint can be overridden using the webhook_endpoint parameter
+ (https://discordapp.com/developers/docs/resources/webhook).
+
+ Each Discord webhook can be pre-configured to use a specific username and
+ avatar_url. You can override these defaults in this hook.
+
+ :param http_conn_id: Http connection ID with host as
"https://discord.com/api/" and
+ default webhook endpoint in the extra field in the
form of
+ {"webhook_endpoint":
"webhooks/{webhook.id}/{webhook.token}"}
+ :param webhook_endpoint: Discord webhook endpoint in the form of
+ "webhooks/{webhook.id}/{webhook.token}"
+ :param message: The message you want to send to your Discord channel
+ (max 2000 characters)
+ :param username: Override the default username of the webhook
+ :param avatar_url: Override the default avatar of the webhook
+ :param tts: Is a text-to-speech message
+ :param proxy: Proxy to use to make the Discord webhook call
+ """
+
+ default_headers = {
+ "Content-Type": "application/json",
+ }
+ conn_name_attr = "http_conn_id"
+ default_conn_name = "discord_default"
+ conn_type = "discord"
+ hook_name = "Async Discord"
+
+ def __init__(
+ self,
+ http_conn_id: str = "",
Review Comment:
```suggestion
*,
http_conn_id: str = "",
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]