This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new ed6a4fd116 Remove deprecated parts from Slack provider (#33557)
ed6a4fd116 is described below
commit ed6a4fd1162407a2007d949765eab5749af1c3ac
Author: Andrey Anshin <[email protected]>
AuthorDate: Thu Aug 24 13:51:34 2023 +0400
Remove deprecated parts from Slack provider (#33557)
---
airflow/providers/slack/CHANGELOG.rst | 38 ++++
airflow/providers/slack/hooks/slack.py | 93 ++-------
airflow/providers/slack/hooks/slack_webhook.py | 232 +++------------------
airflow/providers/slack/operators/slack.py | 10 +-
airflow/providers/slack/operators/slack_webhook.py | 70 +------
airflow/providers/slack/provider.yaml | 1 +
airflow/providers/slack/transfers/sql_to_slack.py | 15 +-
.../operators/slack_operator_howto_guide.rst | 2 +-
tests/providers/slack/hooks/test_slack.py | 61 +-----
tests/providers/slack/hooks/test_slack_webhook.py | 169 ++-------------
tests/providers/slack/operators/test_slack.py | 53 ++---
.../slack/operators/test_slack_webhook.py | 71 +------
.../providers/slack/transfers/test_sql_to_slack.py | 20 +-
13 files changed, 155 insertions(+), 680 deletions(-)
diff --git a/airflow/providers/slack/CHANGELOG.rst
b/airflow/providers/slack/CHANGELOG.rst
index c11dbfb333..58f87bbde4 100644
--- a/airflow/providers/slack/CHANGELOG.rst
+++ b/airflow/providers/slack/CHANGELOG.rst
@@ -27,6 +27,44 @@
Changelog
---------
+8.0.0
+.....
+
+Breaking changes
+~~~~~~~~~~~~~~~~
+
+.. warning::
+ ``SlackHook`` and ``SlackWebhookHook`` constructor expected keyword-only
arguments.
+
+ Removed deprecated parameter ``token`` from the ``SlackHook`` and dependent
operators.
+ Required create ``Slack API Connection`` and provide connection id to
``slack_conn_id`` operators / hook,
+ and the behavior should stay the same.
+
+ Parsing Slack Incoming Webhook Token from the Connection ``hostname`` is
removed, ``password`` should be filled.
+
+ Removed deprecated parameter ``webhook_token`` from the ``SlackWebhookHook``
and dependent operators
+ Required create ``Slack Incoming Webhook Connection`` and provide connection
id to ``slack_webhook_conn_id``
+ operators / hook, and the behavior should stay the same.
+
+ Removed deprecated method ``execute`` from the ``SlackWebhookHook``. Use
``send``, ``send_text`` or ``send_dict`` instead.
+
+ Removed deprecated parameters ``attachments``, ``blocks``, ``channel``,
``username``, ``username``,
+ ``icon_emoji`` from the ``SlackWebhookHook``. Provide them directly to
``SlackWebhookHook.send`` method,
+ and the behavior should stay the same.
+
+ Removed deprecated parameter ``message`` from the ``SlackWebhookHook``.
+ Provide ``text`` directly to ``SlackWebhookHook.send`` method, and the
behavior should stay the same.
+
+ Removed deprecated parameter ``link_names`` from the ``SlackWebhookHook``
and dependent operators.
+ This parameter has no affect in the past, you should not provide it.
+ If you want to mention user see: `Slack Documentation
<https://api.slack.com/reference/surfaces/formatting#mentioning-users>`__.
+
+ Removed deprecated parameters ``endpoint``, ``method``, ``data``,
``headers``, ``response_check``,
+ ``response_filter``, ``extra_options``, ``log_response``, ``auth_type``,
``tcp_keep_alive``,
+ ``tcp_keep_alive_idle``, ``tcp_keep_alive_idle``, ``tcp_keep_alive_count``,
``tcp_keep_alive_interval``
+ from the ``SlackWebhookOperator``. Those parameters has no affect in the
past, you should not provide it.
+
+
7.3.2
.....
diff --git a/airflow/providers/slack/hooks/slack.py
b/airflow/providers/slack/hooks/slack.py
index bd8ee74b34..aa868d34b4 100644
--- a/airflow/providers/slack/hooks/slack.py
+++ b/airflow/providers/slack/hooks/slack.py
@@ -26,11 +26,10 @@ from typing import TYPE_CHECKING, Any, Sequence
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
-from airflow.exceptions import AirflowException, AirflowNotFoundException,
AirflowProviderDeprecationWarning
+from airflow.exceptions import AirflowNotFoundException
from airflow.hooks.base import BaseHook
from airflow.providers.slack.utils import ConnectionExtraConfig
from airflow.utils.helpers import exactly_one
-from airflow.utils.log.secrets_masker import mask_secret
if TYPE_CHECKING:
from slack_sdk.http_retry import RetryHandler
@@ -103,7 +102,6 @@ class SlackHook(BaseHook):
If not set than default WebClient BASE_URL will use
(``https://www.slack.com/api/``).
:param proxy: Proxy to make the Slack API call.
:param retry_handlers: List of handlers to customize retry logic in
``slack_sdk.WebClient``.
- :param token: (deprecated) Slack API Token.
"""
conn_name_attr = "slack_conn_id"
@@ -113,42 +111,31 @@ class SlackHook(BaseHook):
def __init__(
self,
- token: str | None = None,
- slack_conn_id: str | None = None,
+ *,
+ slack_conn_id: str,
base_url: str | None = None,
timeout: int | None = None,
proxy: str | None = None,
retry_handlers: list[RetryHandler] | None = None,
**extra_client_args: Any,
) -> None:
- if not token and not slack_conn_id:
- raise AirflowException("Either `slack_conn_id` or `token` should
be provided.")
- if token:
- mask_secret(token)
- warnings.warn(
- "Provide token as hook argument deprecated by security reason
and will be removed "
- "in a future releases. Please specify token in `Slack API`
connection.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
- if not slack_conn_id:
- warnings.warn(
- "You have not set parameter `slack_conn_id`. Currently `Slack
API` connection id optional "
- "but in a future release it will mandatory.",
- FutureWarning,
- stacklevel=2,
- )
-
super().__init__()
- self._token = token
self.slack_conn_id = slack_conn_id
self.base_url = base_url
self.timeout = timeout
self.proxy = proxy
self.retry_handlers = retry_handlers
+ if "token" in extra_client_args:
+ warnings.warn(
+ f"Provide `token` as part of {type(self).__name__!r}
parameters is disallowed, "
+ f"please use Airflow Connection.",
+ UserWarning,
+ stacklevel=2,
+ )
+ extra_client_args.pop("token")
+ if "logger" not in extra_client_args:
+ extra_client_args["logger"] = self.log
self.extra_client_args = extra_client_args
- if self.extra_client_args.pop("use_session", None) is not None:
- warnings.warn("`use_session` has no affect in
slack_sdk.WebClient.", UserWarning, stacklevel=2)
@cached_property
def client(self) -> WebClient:
@@ -161,24 +148,15 @@ class SlackHook(BaseHook):
def _get_conn_params(self) -> dict[str, Any]:
"""Fetch connection params as a dict and merge it with hook
parameters."""
- conn = self.get_connection(self.slack_conn_id) if self.slack_conn_id
else None
- conn_params: dict[str, Any] = {"retry_handlers": self.retry_handlers}
-
- if self._token:
- conn_params["token"] = self._token
- elif conn:
- if not conn.password:
- raise AirflowNotFoundException(
- f"Connection ID {self.slack_conn_id!r} does not contain
password (Slack API Token)."
- )
- conn_params["token"] = conn.password
-
+ conn = self.get_connection(self.slack_conn_id)
+ if not conn.password:
+ raise AirflowNotFoundException(
+ f"Connection ID {self.slack_conn_id!r} does not contain
password (Slack API Token)."
+ )
+ conn_params: dict[str, Any] = {"token": conn.password,
"retry_handlers": self.retry_handlers}
extra_config = ConnectionExtraConfig(
- conn_type=self.conn_type,
- conn_id=conn.conn_id if conn else None,
- extra=conn.extra_dejson if conn else {},
+ conn_type=self.conn_type, conn_id=conn.conn_id,
extra=conn.extra_dejson
)
-
# Merge Hook parameters with Connection config
conn_params.update(
{
@@ -187,41 +165,10 @@ class SlackHook(BaseHook):
"proxy": self.proxy or extra_config.get("proxy", default=None),
}
)
-
# Add additional client args
conn_params.update(self.extra_client_args)
- if "logger" not in conn_params:
- conn_params["logger"] = self.log
-
return {k: v for k, v in conn_params.items() if v is not None}
- @cached_property
- def token(self) -> str:
- warnings.warn(
- "`SlackHook.token` property deprecated and will be removed in a
future releases.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
- return self._get_conn_params()["token"]
-
- def __get_token(self, token: Any, slack_conn_id: Any) -> str:
- warnings.warn(
- "`SlackHook.__get_token` method deprecated and will be removed in
a future releases.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
- if token is not None:
- return token
-
- if slack_conn_id is not None:
- conn = self.get_connection(slack_conn_id)
-
- if not getattr(conn, "password", None):
- raise AirflowException("Missing token(password) in Slack
connection")
- return conn.password
-
- raise AirflowException("Cannot get token: No valid Slack token nor
slack_conn_id supplied.")
-
def call(self, api_method: str, **kwargs) -> SlackResponse:
"""
Calls Slack WebClient `WebClient.api_call` with given arguments.
diff --git a/airflow/providers/slack/hooks/slack_webhook.py
b/airflow/providers/slack/hooks/slack_webhook.py
index 82b21f175e..bbaf271082 100644
--- a/airflow/providers/slack/hooks/slack_webhook.py
+++ b/airflow/providers/slack/hooks/slack_webhook.py
@@ -21,20 +21,16 @@ import json
import warnings
from functools import cached_property, wraps
from typing import TYPE_CHECKING, Any, Callable
-from urllib.parse import urlsplit
from slack_sdk import WebhookClient
-from airflow.exceptions import AirflowException,
AirflowProviderDeprecationWarning
+from airflow.exceptions import AirflowException, AirflowNotFoundException
from airflow.hooks.base import BaseHook
-from airflow.models import Connection
from airflow.providers.slack.utils import ConnectionExtraConfig
-from airflow.utils.log.secrets_masker import mask_secret
if TYPE_CHECKING:
from slack_sdk.http_retry import RetryHandler
-DEFAULT_SLACK_WEBHOOK_ENDPOINT = "https://hooks.slack.com/services"
LEGACY_INTEGRATION_PARAMS = ("channel", "username", "icon_emoji", "icon_url")
@@ -125,8 +121,6 @@ class SlackWebhookHook(BaseHook):
and receive a response from Slack. If not set than default
WebhookClient value will use.
:param proxy: Proxy to make the Slack Incoming Webhook call.
:param retry_handlers: List of handlers to customize retry logic in
``slack_sdk.WebhookClient``.
- :param webhook_token: (deprecated) Slack Incoming Webhook token.
- Use instead Slack Incoming Webhook connection password field.
"""
conn_name_attr = "slack_webhook_conn_id"
@@ -136,87 +130,29 @@ class SlackWebhookHook(BaseHook):
def __init__(
self,
- slack_webhook_conn_id: str | None = None,
- webhook_token: str | None = None,
+ *,
+ slack_webhook_conn_id: str,
timeout: int | None = None,
proxy: str | None = None,
retry_handlers: list[RetryHandler] | None = None,
- **kwargs,
+ **extra_client_args: Any,
):
super().__init__()
-
- http_conn_id = kwargs.pop("http_conn_id", None)
- if http_conn_id:
- warnings.warn(
- "Parameter `http_conn_id` is deprecated. Please use
`slack_webhook_conn_id` instead.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
- if slack_webhook_conn_id:
- raise AirflowException("You cannot provide both
`slack_webhook_conn_id` and `http_conn_id`.")
- slack_webhook_conn_id = http_conn_id
-
- if not slack_webhook_conn_id and not webhook_token:
- raise AirflowException("Either `slack_webhook_conn_id` or
`webhook_token` should be provided.")
- if webhook_token:
- mask_secret(webhook_token)
- warnings.warn(
- "Provide `webhook_token` as hook argument deprecated by
security reason and will be removed "
- "in a future releases. Please specify it in `Slack Webhook`
connection.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
- if not slack_webhook_conn_id:
- warnings.warn(
- "You have not set parameter `slack_webhook_conn_id`. Currently
`Slack Incoming Webhook` "
- "connection id optional but in a future release it will
mandatory.",
- FutureWarning,
- stacklevel=2,
- )
-
self.slack_webhook_conn_id = slack_webhook_conn_id
self.timeout = timeout
self.proxy = proxy
self.retry_handlers = retry_handlers
- self._webhook_token = webhook_token
-
- # Compatibility with previous version of SlackWebhookHook
- deprecated_class_attrs = []
- for deprecated_attr in (
- "message",
- "attachments",
- "blocks",
- "channel",
- "username",
- "icon_emoji",
- "icon_url",
- "link_names",
- ):
- if deprecated_attr in kwargs:
- deprecated_class_attrs.append(deprecated_attr)
- setattr(self, deprecated_attr, kwargs.pop(deprecated_attr))
- if deprecated_attr == "message":
- # Slack WebHook Post Request not expected `message` as
field,
- # so we also set "text" attribute which will check by
SlackWebhookHook._resolve_argument
- self.text = getattr(self, deprecated_attr)
- elif deprecated_attr == "link_names":
- warnings.warn(
- "`link_names` has no affect, if you want to mention
user see: "
-
"https://api.slack.com/reference/surfaces/formatting#mentioning-users",
- UserWarning,
- stacklevel=2,
- )
-
- if deprecated_class_attrs:
+ if "webhook_token" in extra_client_args:
warnings.warn(
- f"Provide {','.join(repr(a) for a in deprecated_class_attrs)}
as hook argument(s) "
- f"is deprecated and will be removed in a future releases. "
- f"Please specify attributes in
`{self.__class__.__name__}.send` method instead.",
- AirflowProviderDeprecationWarning,
+ f"Provide `webhook_token` as part of {type(self).__name__!r}
parameters is disallowed, "
+ f"please use Airflow Connection.",
+ UserWarning,
stacklevel=2,
)
-
- self.extra_client_args = kwargs
+ extra_client_args.pop("webhook_token")
+ if "logger" not in extra_client_args:
+ extra_client_args["logger"] = self.log
+ self.extra_client_args = extra_client_args
@cached_property
def client(self) -> WebhookClient:
@@ -227,81 +163,30 @@ class SlackWebhookHook(BaseHook):
"""Get the underlying slack_sdk.webhook.WebhookClient (cached)."""
return self.client
- @cached_property
- def webhook_token(self) -> str:
- """Return Slack Webhook Token URL."""
- warnings.warn(
- "`SlackHook.webhook_token` property deprecated and will be removed
in a future releases.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
- return self._get_conn_params()["url"]
-
def _get_conn_params(self) -> dict[str, Any]:
"""Fetch connection params as a dict and merge it with hook
parameters."""
- default_schema, _, default_host =
DEFAULT_SLACK_WEBHOOK_ENDPOINT.partition("://")
- if self.slack_webhook_conn_id:
- conn = self.get_connection(self.slack_webhook_conn_id)
- else:
- # If slack_webhook_conn_id not specified, then use connection with
default schema and host
- conn = Connection(
- conn_id=None, conn_type=self.conn_type, host=default_schema,
password=default_host
+ conn = self.get_connection(self.slack_webhook_conn_id)
+ if not conn.password or not conn.password.strip():
+ raise AirflowNotFoundException(
+ f"Connection ID {self.slack_webhook_conn_id!r} does not
contain password "
+ f"(Slack Webhook Token)."
)
- extra_config = ConnectionExtraConfig(
- conn_type=self.conn_type,
- conn_id=conn.conn_id,
- extra=conn.extra_dejson,
- )
- conn_params: dict[str, Any] = {"retry_handlers": self.retry_handlers}
-
- webhook_token = None
- if self._webhook_token:
- self.log.debug("Retrieving Slack Webhook Token from hook
attribute.")
- webhook_token = self._webhook_token
- elif conn.conn_id:
- if conn.password:
- self.log.debug(
- "Retrieving Slack Webhook Token from Connection ID %r
password.",
- self.slack_webhook_conn_id,
- )
- webhook_token = conn.password
-
- webhook_token = webhook_token or ""
- if not webhook_token and not conn.host:
- raise AirflowException("Cannot get token: No valid Slack token nor
valid Connection ID supplied.")
- elif webhook_token and "://" in webhook_token:
+
+ webhook_token = conn.password.strip()
+ if webhook_token and "://" in conn.password:
self.log.debug("Retrieving Slack Webhook Token URL from webhook
token.")
url = webhook_token
else:
self.log.debug("Constructing Slack Webhook Token URL.")
- if conn.host and "://" in conn.host:
- base_url = conn.host
- else:
- schema = conn.schema if conn.schema else default_schema
- host = conn.host if conn.host else default_host
- base_url = f"{schema}://{host}"
-
- base_url = base_url.rstrip("/")
- if not webhook_token:
- parsed_token = (urlsplit(base_url).path or "").strip("/")
- if base_url == DEFAULT_SLACK_WEBHOOK_ENDPOINT or not
parsed_token:
- # Raise an error in case of password not specified and
- # 1. Result of constructing base_url equal
https://hooks.slack.com/services
- # 2. Empty url path, e.g. if base_url =
https://hooks.slack.com
- raise AirflowException(
- "Cannot get token: No valid Slack token nor valid
Connection ID supplied."
- )
- mask_secret(parsed_token)
- warnings.warn(
- f"Found Slack Webhook Token URL in Connection
{conn.conn_id!r} `host` "
- "and `password` field is empty. This behaviour deprecated "
- "and could expose you token in the UI and will be removed
in a future releases.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
+ base_url = conn.host
+ if not base_url or "://" not in base_url:
+ base_url = f"{conn.schema or 'https'}://{conn.host or
'hooks.slack.com/services'}"
url = (base_url.rstrip("/") + "/" +
webhook_token.lstrip("/")).rstrip("/")
- conn_params["url"] = url
+ conn_params: dict[str, Any] = {"url": url, "retry_handlers":
self.retry_handlers}
+ extra_config = ConnectionExtraConfig(
+ conn_type=self.conn_type, conn_id=conn.conn_id,
extra=conn.extra_dejson
+ )
# Merge Hook parameters with Connection config
conn_params.update(
{
@@ -311,34 +196,8 @@ class SlackWebhookHook(BaseHook):
)
# Add additional client args
conn_params.update(self.extra_client_args)
- if "logger" not in conn_params:
- conn_params["logger"] = self.log
-
return {k: v for k, v in conn_params.items() if v is not None}
- def _resolve_argument(self, name: str, value):
- """
- Resolve message parameters.
-
- .. note::
- This method exist for compatibility and merge instance class
attributes with
- method attributes and not be required when assign class attributes
to message
- would completely remove.
- """
- if value is None and name in (
- "text",
- "attachments",
- "blocks",
- "channel",
- "username",
- "icon_emoji",
- "icon_url",
- "link_names",
- ):
- return getattr(self, name, None)
-
- return value
-
@check_webhook_response
def send_dict(self, body: dict[str, Any] | str, *, headers: dict[str, str]
| None = None):
"""
@@ -376,7 +235,6 @@ class SlackWebhookHook(BaseHook):
self,
*,
text: str | None = None,
- attachments: list[dict[str, Any]] | None = None,
blocks: list[dict[str, Any]] | None = None,
response_type: str | None = None,
replace_original: bool | None = None,
@@ -384,6 +242,7 @@ class SlackWebhookHook(BaseHook):
unfurl_links: bool | None = None,
unfurl_media: bool | None = None,
headers: dict[str, str] | None = None,
+ attachments: list[dict[str, Any]] | None = None,
**kwargs,
):
"""
@@ -391,7 +250,6 @@ class SlackWebhookHook(BaseHook):
:param text: The text message
(even when having blocks, setting this as well is recommended as
it works as fallback).
- :param attachments: A collection of attachments.
:param blocks: A collection of Block Kit UI components.
:param response_type: The type of message (either 'in_channel' or
'ephemeral').
:param replace_original: True if you use this option for response_url
requests.
@@ -399,26 +257,20 @@ class SlackWebhookHook(BaseHook):
:param unfurl_links: Option to indicate whether text url should unfurl.
:param unfurl_media: Option to indicate whether media url should
unfurl.
:param headers: Request headers for this request.
+ :param attachments: A collection of attachments.
"""
body = {
- "text": self._resolve_argument("text", text),
- "attachments": self._resolve_argument("attachments", attachments),
- "blocks": self._resolve_argument("blocks", blocks),
+ "text": text,
+ "attachments": attachments,
+ "blocks": blocks,
"response_type": response_type,
"replace_original": replace_original,
"delete_original": delete_original,
"unfurl_links": unfurl_links,
"unfurl_media": unfurl_media,
# Legacy Integration Parameters
- **{lip: self._resolve_argument(lip, kwargs.pop(lip, None)) for lip
in LEGACY_INTEGRATION_PARAMS},
+ **kwargs,
}
- if kwargs:
- warnings.warn(
- f"Found unexpected keyword-argument(s) {', '.join(repr(k) for
k in kwargs)} "
- "in `send` method. This argument(s) have no effect.",
- UserWarning,
- stacklevel=2,
- )
body = {k: v for k, v in body.items() if v is not None}
return self.send_dict(body=body, headers=headers)
@@ -481,21 +333,3 @@ class SlackWebhookHook(BaseHook):
"proxy": "http://localhost:9000",
},
}
-
- def execute(self) -> None:
- """
- Remote Popen (actually execute the slack webhook call).
-
- .. note::
- This method exist for compatibility with previous version of
operator
- and expected that Slack Incoming Webhook message constructing from
class attributes rather than
- pass as method arguments.
- """
- warnings.warn(
- "`SlackWebhookHook.execute` method deprecated and will be removed
in a future releases. "
- "Please use `SlackWebhookHook.send` or
`SlackWebhookHook.send_dict` or "
- "`SlackWebhookHook.send_text` methods instead.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
- self.send()
diff --git a/airflow/providers/slack/operators/slack.py
b/airflow/providers/slack/operators/slack.py
index 6d2f27fbb5..d19dbafe31 100644
--- a/airflow/providers/slack/operators/slack.py
+++ b/airflow/providers/slack/operators/slack.py
@@ -25,7 +25,6 @@ from typing import Any, Sequence
from airflow.exceptions import AirflowProviderDeprecationWarning
from airflow.models import BaseOperator
from airflow.providers.slack.hooks.slack import SlackHook
-from airflow.utils.log.secrets_masker import mask_secret
class SlackAPIOperator(BaseOperator):
@@ -44,25 +43,20 @@ class SlackAPIOperator(BaseOperator):
def __init__(
self,
*,
- slack_conn_id: str | None = None,
- token: str | None = None,
+ slack_conn_id: str,
method: str | None = None,
api_params: dict | None = None,
**kwargs,
) -> None:
super().__init__(**kwargs)
- if token:
- mask_secret(token)
- self.token = token
self.slack_conn_id = slack_conn_id
-
self.method = method
self.api_params = api_params
@cached_property
def hook(self) -> SlackHook:
"""Slack Hook."""
- return SlackHook(token=self.token, slack_conn_id=self.slack_conn_id)
+ return SlackHook(slack_conn_id=self.slack_conn_id)
def construct_api_call_params(self) -> Any:
"""API call parameters used by the execute function.
diff --git a/airflow/providers/slack/operators/slack_webhook.py
b/airflow/providers/slack/operators/slack_webhook.py
index 84399b7748..f386adf6b8 100644
--- a/airflow/providers/slack/operators/slack_webhook.py
+++ b/airflow/providers/slack/operators/slack_webhook.py
@@ -17,11 +17,9 @@
# under the License.
from __future__ import annotations
-import warnings
from functools import cached_property
from typing import TYPE_CHECKING, Sequence
-from airflow.exceptions import AirflowException,
AirflowProviderDeprecationWarning
from airflow.models import BaseOperator
from airflow.providers.slack.hooks.slack_webhook import SlackWebhookHook
@@ -41,12 +39,6 @@ class SlackWebhookOperator(BaseOperator):
It is possible to change this values only in `Legacy Slack Integration
Incoming Webhook
<https://api.slack.com/legacy/custom-integrations/messaging/webhooks#legacy-customizations>`_.
- .. warning::
- This operator could take Slack Webhook Token from ``webhook_token``
- as well as from :ref:`Slack Incoming Webhook connection
<howto/connection:slack-incoming-webhook>`.
- However, provide ``webhook_token`` it is not secure and this attribute
- will be removed in the future version of provider.
-
:param slack_webhook_conn_id: :ref:`Slack Incoming Webhook
<howto/connection:slack>`
connection id that has Incoming Webhook token in the password field.
:param message: The formatted text of the message to be published.
@@ -59,15 +51,10 @@ class SlackWebhookOperator(BaseOperator):
:param username: The username to post to slack with
:param icon_emoji: The emoji to use as icon for the user posting to Slack
:param icon_url: The icon image URL string to use in place of the default
icon.
- :param link_names: Whether or not to find and link channel and usernames
in your
- message
:param proxy: Proxy to use to make the Slack webhook call
- :param webhook_token: (deprecated) Slack Incoming Webhook token.
- Please use ``slack_webhook_conn_id`` instead.
"""
template_fields: Sequence[str] = (
- "webhook_token",
"message",
"attachments",
"blocks",
@@ -79,8 +66,7 @@ class SlackWebhookOperator(BaseOperator):
def __init__(
self,
*,
- slack_webhook_conn_id: str | None = None,
- webhook_token: str | None = None,
+ slack_webhook_conn_id,
message: str = "",
attachments: list | None = None,
blocks: list | None = None,
@@ -88,55 +74,11 @@ class SlackWebhookOperator(BaseOperator):
username: str | None = None,
icon_emoji: str | None = None,
icon_url: str | None = None,
- link_names: bool = False,
proxy: str | None = None,
**kwargs,
) -> None:
- http_conn_id = kwargs.pop("http_conn_id", None)
- if http_conn_id:
- warnings.warn(
- "Parameter `http_conn_id` is deprecated. Please use
`slack_webhook_conn_id` instead.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
- if slack_webhook_conn_id:
- raise AirflowException("You cannot provide both
`slack_webhook_conn_id` and `http_conn_id`.")
- slack_webhook_conn_id = http_conn_id
-
- # Compatibility with previous version of operator which based on
SimpleHttpOperator.
- # Users might pass these arguments previously, however its never pass
to SlackWebhookHook.
- # We remove this arguments if found in ``kwargs`` and notify users if
found any.
- deprecated_class_attrs = []
- for deprecated_attr in (
- "endpoint",
- "method",
- "data",
- "headers",
- "response_check",
- "response_filter",
- "extra_options",
- "log_response",
- "auth_type",
- "tcp_keep_alive",
- "tcp_keep_alive_idle",
- "tcp_keep_alive_count",
- "tcp_keep_alive_interval",
- ):
- if deprecated_attr in kwargs:
- deprecated_class_attrs.append(deprecated_attr)
- kwargs.pop(deprecated_attr)
- if deprecated_class_attrs:
- warnings.warn(
- f"Provide {','.join(repr(a) for a in deprecated_class_attrs)}
is deprecated "
- f"and as has no affect, please remove it from
{self.__class__.__name__} "
- "constructor attributes otherwise in future version of
provider it might cause an issue.",
- AirflowProviderDeprecationWarning,
- stacklevel=2,
- )
-
super().__init__(**kwargs)
self.slack_webhook_conn_id = slack_webhook_conn_id
- self.webhook_token = webhook_token
self.proxy = proxy
self.message = message
self.attachments = attachments
@@ -145,17 +87,11 @@ class SlackWebhookOperator(BaseOperator):
self.username = username
self.icon_emoji = icon_emoji
self.icon_url = icon_url
- self.link_names = link_names
@cached_property
def hook(self) -> SlackWebhookHook:
"""Create and return an SlackWebhookHook (cached)."""
- return SlackWebhookHook(
- slack_webhook_conn_id=self.slack_webhook_conn_id,
- proxy=self.proxy,
- # Deprecated. SlackWebhookHook will notify user if user provide
non-empty ``webhook_token``.
- webhook_token=self.webhook_token,
- )
+ return
SlackWebhookHook(slack_webhook_conn_id=self.slack_webhook_conn_id,
proxy=self.proxy)
def execute(self, context: Context) -> None:
"""Call the SlackWebhookHook to post the provided Slack message."""
@@ -169,6 +105,4 @@ class SlackWebhookOperator(BaseOperator):
username=self.username,
icon_emoji=self.icon_emoji,
icon_url=self.icon_url,
- # Unused Parameters, if not None than warn user
- link_names=self.link_names,
)
diff --git a/airflow/providers/slack/provider.yaml
b/airflow/providers/slack/provider.yaml
index 9f46d284d4..e751b2e0f6 100644
--- a/airflow/providers/slack/provider.yaml
+++ b/airflow/providers/slack/provider.yaml
@@ -23,6 +23,7 @@ description: |
suspended: false
versions:
+ - 8.0.0
- 7.3.2
- 7.3.1
- 7.3.0
diff --git a/airflow/providers/slack/transfers/sql_to_slack.py
b/airflow/providers/slack/transfers/sql_to_slack.py
index b04adcab09..8a1d4fa478 100644
--- a/airflow/providers/slack/transfers/sql_to_slack.py
+++ b/airflow/providers/slack/transfers/sql_to_slack.py
@@ -103,8 +103,6 @@ class SqlToSlackOperator(BaseSqlToSlackOperator):
:param sql_hook_params: Extra config params to be passed to the underlying
hook.
Should match the desired hook constructor params.
:param slack_conn_id: The connection id for Slack.
- :param slack_webhook_token: The token to use to authenticate to Slack. If
this is not provided, the
- 'slack_conn_id' attribute needs to be specified in the 'password'
field.
:param slack_channel: The channel to send message. Override default from
Slack connection.
:param results_df_name: The name of the JINJA template's dataframe
variable, default is 'results_df'
:param parameters: The parameters to pass to the SQL query
@@ -120,9 +118,8 @@ class SqlToSlackOperator(BaseSqlToSlackOperator):
*,
sql: str,
sql_conn_id: str,
+ slack_conn_id: str,
sql_hook_params: dict | None = None,
- slack_conn_id: str | None = None,
- slack_webhook_token: str | None = None,
slack_channel: str | None = None,
slack_message: str,
results_df_name: str = "results_df",
@@ -135,17 +132,11 @@ class SqlToSlackOperator(BaseSqlToSlackOperator):
)
self.slack_conn_id = slack_conn_id
- self.slack_webhook_token = slack_webhook_token
self.slack_channel = slack_channel
self.slack_message = slack_message
self.results_df_name = results_df_name
self.kwargs = kwargs
- if not self.slack_conn_id and not self.slack_webhook_token:
- raise AirflowException(
- "SqlToSlackOperator requires either a `slack_conn_id` or a
`slack_webhook_token` argument"
- )
-
def _render_and_send_slack_message(self, context, df) -> None:
# Put the dataframe into the context and render the JINJA template
fields
context[self.results_df_name] = df
@@ -156,9 +147,7 @@ class SqlToSlackOperator(BaseSqlToSlackOperator):
slack_hook.send(text=self.slack_message, channel=self.slack_channel)
def _get_slack_hook(self) -> SlackWebhookHook:
- return SlackWebhookHook(
- slack_webhook_conn_id=self.slack_conn_id,
webhook_token=self.slack_webhook_token
- )
+ return SlackWebhookHook(slack_webhook_conn_id=self.slack_conn_id)
def render_template_fields(self, context, jinja_env=None) -> None:
# If this is the first render of the template fields, exclude
slack_message from rendering since
diff --git
a/docs/apache-airflow-providers-slack/operators/slack_operator_howto_guide.rst
b/docs/apache-airflow-providers-slack/operators/slack_operator_howto_guide.rst
index 46f66659fd..66d57a46df 100644
---
a/docs/apache-airflow-providers-slack/operators/slack_operator_howto_guide.rst
+++
b/docs/apache-airflow-providers-slack/operators/slack_operator_howto_guide.rst
@@ -23,7 +23,7 @@ Introduction
Slack operators can send text messages
(:class:`~airflow.providers.slack.operators.slack.SlackAPIFileOperator`)
or files
(:class:`~airflow.providers.slack.operators.slack.SlackAPIPostOperator`) to
specified Slack channels.
-Provide either ``slack_conn_id`` or ``token`` for the connection, and specify
``channel`` (name or ID).
+Provide ``slack_conn_id`` for the connection, and specify ``channel`` (name or
ID).
Example Code for Sending Files
------------------------------
diff --git a/tests/providers/slack/hooks/test_slack.py
b/tests/providers/slack/hooks/test_slack.py
index c13e6c267f..43a03f40f9 100644
--- a/tests/providers/slack/hooks/test_slack.py
+++ b/tests/providers/slack/hooks/test_slack.py
@@ -24,12 +24,12 @@ from unittest import mock
from unittest.mock import patch
import pytest
-from pytest import param
+from pytest import MonkeyPatch, param
from slack_sdk.errors import SlackApiError
from slack_sdk.http_retry.builtin_handlers import ConnectionErrorRetryHandler,
RateLimitErrorRetryHandler
from slack_sdk.web.slack_response import SlackResponse
-from airflow.exceptions import AirflowException, AirflowNotFoundException,
AirflowProviderDeprecationWarning
+from airflow.exceptions import AirflowNotFoundException
from airflow.models.connection import Connection
from airflow.providers.slack.hooks.slack import SlackHook
from tests.test_utils.providers import get_provider_min_airflow_version,
object_exists
@@ -69,50 +69,13 @@ def slack_api_connections():
),
]
- conn_uris = {f"AIRFLOW_CONN_{c.conn_id.upper()}": c.get_uri() for c in
connections}
-
- with mock.patch.dict("os.environ", values=conn_uris):
+ with MonkeyPatch.context() as mp:
+ for conn in connections:
+ mp.setenv(f"AIRFLOW_CONN_{conn.conn_id.upper()}", conn.get_uri())
yield
class TestSlackHook:
- def test_token_arg_deprecated(self):
- """Test deprecation warning if token provided as hook argument."""
- warning_message = (
- "Provide token as hook argument deprecated by security reason and
will be removed "
- r"in a future releases. Please specify token in `Slack API`
connection\."
- )
- with pytest.warns(AirflowProviderDeprecationWarning,
match=warning_message):
- SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID, token="foo-bar")
-
- def test_token_property_deprecated(self):
- """Test deprecation warning if access to ``SlackHook.token``
property."""
- hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
- warning_message = r"`SlackHook.token` property deprecated and will be
removed in a future releases\."
- with pytest.warns(AirflowProviderDeprecationWarning,
match=warning_message):
- assert hook.token == MOCK_SLACK_API_TOKEN
-
- def test_optional_conn_id_deprecated(self):
- """Test deprecation warning if not set connection ID."""
- warning_message = (
- r"You have not set parameter `slack_conn_id`\. Currently `Slack
API` connection id optional "
- r"but in a future release it will mandatory\."
- )
- with pytest.warns(FutureWarning, match=warning_message):
- SlackHook(token="foo-bar")
-
- def test_use_session_has_no_affect(self):
- """Test that specified previously in docstring `use_session` take no
affect."""
- warning_message = r"`use_session` has no affect in
slack_sdk\.WebClient\."
- with pytest.warns(UserWarning, match=warning_message):
- hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID,
use_session="foo-bar")
- assert "use_session" not in hook.extra_client_args
-
- def test_get_token_with_token_only(self):
- """Test retrieve token when only hook arg provided without Slack API
Connection ID."""
- test_hook_arg_token = "xapp-1-arg-token"
- assert SlackHook(test_hook_arg_token,
None)._get_conn_params()["token"] == test_hook_arg_token
-
@pytest.mark.parametrize(
"conn_id",
[
@@ -126,15 +89,11 @@ class TestSlackHook:
assert hook._get_conn_params()["token"] == MOCK_SLACK_API_TOKEN
def test_resolve_token(self):
- """Test retrieve token when both hook arg and Slack API Connection ID
provided."""
- test_hook_arg_token = "xapp-1-arg-token"
- hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID,
token=test_hook_arg_token)
- assert hook._get_conn_params()["token"] == test_hook_arg_token
-
- def test_nor_token_and_nor_conn_id_provided(self):
- """Test neither hook arg and Slack API Connection ID provided."""
- with pytest.raises(AirflowException, match=r"Either `slack_conn_id` or
`token` should be provided\."):
- SlackHook(slack_conn_id=None, token=None)
+ """Test that we only use token from Slack API Connection ID."""
+ with pytest.warns(UserWarning, match="Provide `token` as part of .*
parameters is disallowed"):
+ hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID,
token="foo-bar")
+ assert "token" not in hook.extra_client_args
+ assert hook._get_conn_params()["token"] == MOCK_SLACK_API_TOKEN
def test_empty_password(self):
"""Test password field defined in the connection."""
diff --git a/tests/providers/slack/hooks/test_slack_webhook.py
b/tests/providers/slack/hooks/test_slack_webhook.py
index 3e89c5dfbd..9b3ff8e093 100644
--- a/tests/providers/slack/hooks/test_slack_webhook.py
+++ b/tests/providers/slack/hooks/test_slack_webhook.py
@@ -26,11 +26,11 @@ from unittest import mock
from unittest.mock import patch
import pytest
-from pytest import param
+from pytest import MonkeyPatch, param
from slack_sdk.http_retry.builtin_handlers import ConnectionErrorRetryHandler,
RateLimitErrorRetryHandler
from slack_sdk.webhook.webhook_response import WebhookResponse
-from airflow.exceptions import AirflowException,
AirflowProviderDeprecationWarning
+from airflow.exceptions import AirflowException, AirflowNotFoundException
from airflow.models.connection import Connection
from airflow.providers.slack.hooks.slack_webhook import SlackWebhookHook,
check_webhook_response
from tests.test_utils.providers import get_provider_min_airflow_version,
object_exists
@@ -92,13 +92,6 @@ def slack_webhook_connections():
host="https://hooks.slack.com/services/",
extra={"webhook_token": TEST_TOKEN},
),
- Connection(conn_id="conn_token_in_host_1", conn_type=CONN_TYPE,
host=TEST_WEBHOOK_URL),
- Connection(
- conn_id="conn_token_in_host_2",
- conn_type=CONN_TYPE,
- schema="https",
-
host="hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
- ),
Connection(
conn_id="conn_custom_endpoint_1",
conn_type=CONN_TYPE,
@@ -133,10 +126,9 @@ def slack_webhook_connections():
host="some.netloc",
),
]
-
- conn_uris = {f"AIRFLOW_CONN_{c.conn_id.upper()}": c.get_uri() for c in
connections}
-
- with mock.patch.dict("os.environ", values=conn_uris):
+ with MonkeyPatch.context() as mp:
+ for conn in connections:
+ mp.setenv(f"AIRFLOW_CONN_{conn.conn_id.upper()}", conn.get_uri())
yield
@@ -175,40 +167,6 @@ class TestCheckWebhookResponseDecorator:
class TestSlackWebhookHook:
- def test_no_credentials(self):
- """Test missing credentials."""
- error_message = r"Either `slack_webhook_conn_id` or `webhook_token`
should be provided\."
- with pytest.raises(AirflowException, match=error_message):
- SlackWebhookHook(slack_webhook_conn_id=None, webhook_token=None)
-
- @mock.patch("airflow.providers.slack.hooks.slack_webhook.mask_secret")
- def test_webhook_token(self, mock_mask_secret):
- webhook_token = "test-value"
- warning_message = (
- r"Provide `webhook_token` as hook argument deprecated by security
reason and will be removed "
- r"in a future releases. Please specify it in `Slack Webhook`
connection\."
- )
- with pytest.warns(AirflowProviderDeprecationWarning,
match=warning_message):
- SlackWebhookHook(webhook_token=webhook_token)
- mock_mask_secret.assert_called_once_with(webhook_token)
-
- def test_conn_id(self):
- """Different conn_id arguments and options."""
- hook =
SlackWebhookHook(slack_webhook_conn_id=SlackWebhookHook.default_conn_name,
http_conn_id=None)
- assert hook.slack_webhook_conn_id == SlackWebhookHook.default_conn_name
- assert not hasattr(hook, "http_conn_id")
-
- hook = SlackWebhookHook(slack_webhook_conn_id=None,
http_conn_id=SlackWebhookHook.default_conn_name)
- assert hook.slack_webhook_conn_id == SlackWebhookHook.default_conn_name
- assert not hasattr(hook, "http_conn_id")
-
- error_message = "You cannot provide both `slack_webhook_conn_id` and
`http_conn_id`."
- with pytest.raises(AirflowException, match=error_message):
- SlackWebhookHook(
- slack_webhook_conn_id=SlackWebhookHook.default_conn_name,
- http_conn_id=SlackWebhookHook.default_conn_name,
- )
-
@pytest.mark.parametrize(
"conn_id",
[
@@ -218,30 +176,32 @@ class TestSlackWebhookHook:
"conn_host_with_schema",
"conn_host_without_schema",
"conn_parts",
- "conn_token_in_host_1",
- "conn_token_in_host_2",
],
)
def test_construct_webhook_url(self, conn_id):
"""Test valid connections."""
hook = SlackWebhookHook(slack_webhook_conn_id=conn_id)
conn_params = hook._get_conn_params()
+ assert not hasattr(hook, "http_conn_id")
+ assert not hasattr(hook, "webhook_token")
assert "url" in conn_params
assert conn_params["url"] == TEST_WEBHOOK_URL
- @mock.patch("airflow.providers.slack.hooks.slack_webhook.mask_secret")
+ def test_ignore_webhook_token(self):
+ """Test that we only use token from Slack API Connection ID."""
+ with pytest.warns(
+ UserWarning, match="Provide `webhook_token` as part of .*
parameters is disallowed"
+ ):
+ hook = SlackWebhookHook(slack_webhook_conn_id=TEST_CONN_ID,
webhook_token="foo-bar")
+ assert "webhook_token" not in hook.extra_client_args
+ assert hook._get_conn_params()["url"] == TEST_WEBHOOK_URL
+
@pytest.mark.parametrize("conn_id", ["conn_token_in_host_1",
"conn_token_in_host_2"])
- def test_construct_webhook_url_deprecated_full_url_in_host(self,
mock_mask_secret, conn_id):
- """Test deprecated option with full URL in host/schema and empty
password."""
+ def test_wrong_connections(self, conn_id):
+ """Test previously valid connections, but now it is dropped."""
hook = SlackWebhookHook(slack_webhook_conn_id=conn_id)
- warning_message = (
- r"Found Slack Webhook Token URL in Connection .* `host` and
`password` field is empty\."
- )
- with pytest.warns(AirflowProviderDeprecationWarning,
match=warning_message):
- conn_params = hook._get_conn_params()
- mock_mask_secret.assert_called_once_with(mock.ANY)
- assert "url" in conn_params
- assert conn_params["url"] == TEST_WEBHOOK_URL
+ with pytest.raises(AirflowNotFoundException):
+ hook._get_conn_params()
@pytest.mark.parametrize(
"conn_id", ["conn_custom_endpoint_1", "conn_custom_endpoint_2",
"conn_custom_endpoint_3"]
@@ -264,19 +224,10 @@ class TestSlackWebhookHook:
def test_no_password_in_connection_field(self, conn_id):
"""Test connection which missing password field in connection."""
hook = SlackWebhookHook(slack_webhook_conn_id=conn_id)
- error_message = r"Cannot get token\: No valid Slack token nor valid
Connection ID supplied\."
- with pytest.raises(AirflowException, match=error_message):
+ error_message = r"Connection ID .* does not contain password \(Slack
Webhook Token\)"
+ with pytest.raises(AirflowNotFoundException, match=error_message):
hook._get_conn_params()
- @pytest.mark.parametrize("conn_id", [None, "conn_empty"])
- @pytest.mark.parametrize("token", [TEST_TOKEN, TEST_WEBHOOK_URL,
f"/{TEST_TOKEN}"])
- def test_empty_connection_field_with_token(self, conn_id, token):
- """Test connections which is empty or not set and valid webhook_token
specified."""
- hook = SlackWebhookHook(slack_webhook_conn_id="conn_empty",
webhook_token=token)
- conn_params = hook._get_conn_params()
- assert "url" in conn_params
- assert conn_params["url"] == TEST_WEBHOOK_URL
-
@pytest.mark.parametrize(
"hook_config,conn_extra,expected",
[
@@ -484,82 +435,6 @@ class TestSlackWebhookHook:
hook.send(**send_params, headers=headers)
mock_hook_send_dict.assert_called_once_with(body=send_params,
headers=headers)
- @pytest.mark.parametrize(
- "deprecated_hook_attr",
- [
- "message",
- "attachments",
- "blocks",
- "channel",
- "username",
- "icon_emoji",
- "icon_url",
- ],
- )
-
@mock.patch("airflow.providers.slack.hooks.slack_webhook.SlackWebhookHook.send_dict")
- def test_hook_send_by_hook_attributes(self, mock_hook_send_dict,
deprecated_hook_attr):
- """Test `SlackWebhookHook.send` with parameters set in hook
attributes."""
- send_params = {deprecated_hook_attr: "test-value"}
- expected_body = {deprecated_hook_attr if deprecated_hook_attr !=
"message" else "text": "test-value"}
- warning_message = (
- r"Provide .* as hook argument\(s\) is deprecated and will be
removed in a future releases\. "
- r"Please specify attributes in `SlackWebhookHook\.send` method
instead\."
- )
- with pytest.warns(AirflowProviderDeprecationWarning,
match=warning_message):
- hook = SlackWebhookHook(slack_webhook_conn_id=TEST_CONN_ID,
**send_params)
- assert getattr(hook, deprecated_hook_attr) == "test-value"
- if deprecated_hook_attr == "message":
- assert getattr(hook, "text") == "test-value"
- # Test ``.send()`` method
- hook.send()
- mock_hook_send_dict.assert_called_once_with(body=expected_body,
headers=None)
-
- # Test deprecated ``.execute()`` method
- mock_hook_send_dict.reset_mock()
- warning_message = (
- "`SlackWebhookHook.execute` method deprecated and will be removed
in a future releases. "
- "Please use `SlackWebhookHook.send` or
`SlackWebhookHook.send_dict` or "
- "`SlackWebhookHook.send_text` methods instead."
- )
- with pytest.warns(AirflowProviderDeprecationWarning,
match=warning_message):
- hook.execute()
- mock_hook_send_dict.assert_called_once_with(body=expected_body,
headers=None)
-
- @mock.patch("airflow.providers.slack.hooks.slack_webhook.WebhookClient")
- def test_hook_ignored_attributes(self, mock_webhook_client_cls):
- """Test hook constructor warn users about ignored attributes."""
- mock_webhook_client = mock_webhook_client_cls.return_value
- mock_webhook_client_send_dict = mock_webhook_client.send_dict
- mock_webhook_client_send_dict.return_value = MOCK_WEBHOOK_RESPONSE
- with pytest.warns(UserWarning) as recwarn:
- hook = SlackWebhookHook(slack_webhook_conn_id=TEST_CONN_ID,
link_names="test-value")
- assert len(recwarn) == 2
- assert str(recwarn.pop(UserWarning).message).startswith(
- "`link_names` has no affect, if you want to mention user see:"
- )
- assert
str(recwarn.pop(AirflowProviderDeprecationWarning).message).startswith(
- "Provide 'link_names' as hook argument(s) is deprecated and will
be removed in a future releases."
- )
- hook.send()
- mock_webhook_client_send_dict.assert_called_once_with({}, headers=None)
-
- @mock.patch("airflow.providers.slack.hooks.slack_webhook.WebhookClient")
- def test_hook_send_unexpected_arguments(self, mock_webhook_client_cls,
recwarn):
- """Test `SlackWebhookHook.send` unexpected attributes."""
- mock_webhook_client = mock_webhook_client_cls.return_value
- mock_webhook_client_send_dict = mock_webhook_client.send_dict
- mock_webhook_client_send_dict.return_value = MOCK_WEBHOOK_RESPONSE
-
- hook = SlackWebhookHook(slack_webhook_conn_id=TEST_CONN_ID)
- warning_message = (
- r"Found unexpected keyword-argument\(s\) 'link_names', 'as_user' "
- r"in `send` method\. This argument\(s\) have no effect\."
- )
- with pytest.warns(UserWarning, match=warning_message):
- hook.send(link_names="foo-bar", as_user="root", text="Awesome!")
-
- mock_webhook_client_send_dict.assert_called_once_with({"text":
"Awesome!"}, headers=None)
-
@pytest.mark.parametrize("headers", [None, {"User-Agent": "Airflow"}])
@pytest.mark.parametrize("unfurl_links", [None, False, True])
@pytest.mark.parametrize("unfurl_media", [None, False, True])
diff --git a/tests/providers/slack/operators/test_slack.py
b/tests/providers/slack/operators/test_slack.py
index 3e34386247..8dfebc9b2d 100644
--- a/tests/providers/slack/operators/test_slack.py
+++ b/tests/providers/slack/operators/test_slack.py
@@ -34,27 +34,14 @@ SLACK_API_TEST_CONNECTION_ID = "test_slack_conn_id"
class TestSlackAPIOperator:
- @mock.patch("airflow.providers.slack.operators.slack.mask_secret")
- def test_mask_token(self, mock_mask_secret):
- SlackAPIOperator(task_id="test-mask-token", token="super-secret-token")
- mock_mask_secret.assert_called_once_with("super-secret-token")
-
@mock.patch("airflow.providers.slack.operators.slack.SlackHook")
- @pytest.mark.parametrize(
- "token,conn_id",
- [
- ("token", SLACK_API_TEST_CONNECTION_ID),
- ("token", None),
- (None, SLACK_API_TEST_CONNECTION_ID),
- ],
- )
- def test_hook(self, mock_slack_hook_cls, token, conn_id):
+ def test_hook(self, mock_slack_hook_cls):
mock_slack_hook = mock_slack_hook_cls.return_value
- op = SlackAPIOperator(task_id="test-mask-token", token=token,
slack_conn_id=conn_id)
+ op = SlackAPIOperator(task_id="test-mask-token",
slack_conn_id=SLACK_API_TEST_CONNECTION_ID)
hook = op.hook
assert hook == mock_slack_hook
assert hook is op.hook
- mock_slack_hook_cls.assert_called_once_with(token=token,
slack_conn_id=conn_id)
+
mock_slack_hook_cls.assert_called_once_with(slack_conn_id=SLACK_API_TEST_CONNECTION_ID)
class TestSlackAPIPostOperator:
@@ -109,11 +96,10 @@ class TestSlackAPIPostOperator:
"blocks": self.test_blocks_in_json,
}
- def __construct_operator(self, test_token, test_slack_conn_id,
test_api_params=None):
+ def __construct_operator(self, test_slack_conn_id, test_api_params=None):
return SlackAPIPostOperator(
task_id="slack",
username=self.test_username,
- token=test_token,
slack_conn_id=test_slack_conn_id,
channel=self.test_channel,
text=self.test_text,
@@ -124,11 +110,10 @@ class TestSlackAPIPostOperator:
)
def test_init_with_valid_params(self):
- test_token = "test_token"
-
- slack_api_post_operator = self.__construct_operator(test_token, None,
self.test_api_params)
- assert slack_api_post_operator.token == test_token
- assert slack_api_post_operator.slack_conn_id is None
+ slack_api_post_operator = self.__construct_operator(
+ SLACK_API_TEST_CONNECTION_ID, self.test_api_params
+ )
+ assert slack_api_post_operator.slack_conn_id ==
SLACK_API_TEST_CONNECTION_ID
assert slack_api_post_operator.method == self.expected_method
assert slack_api_post_operator.text == self.test_text
assert slack_api_post_operator.channel == self.test_channel
@@ -137,10 +122,7 @@ class TestSlackAPIPostOperator:
assert slack_api_post_operator.icon_url == self.test_icon_url
assert slack_api_post_operator.attachments == self.test_attachments
assert slack_api_post_operator.blocks == self.test_blocks
-
- slack_api_post_operator = self.__construct_operator(None,
SLACK_API_TEST_CONNECTION_ID)
- assert slack_api_post_operator.token is None
- assert slack_api_post_operator.slack_conn_id ==
SLACK_API_TEST_CONNECTION_ID
+ assert not hasattr(slack_api_post_operator, "token")
@mock.patch("airflow.providers.slack.operators.slack.SlackHook")
def test_api_call_params_with_default_args(self, mock_hook):
@@ -177,10 +159,9 @@ class TestSlackAPIFileOperator:
self.test_api_params = {"key": "value"}
self.expected_method = "files.upload"
- def __construct_operator(self, test_token, test_slack_conn_id,
test_api_params=None):
+ def __construct_operator(self, test_slack_conn_id, test_api_params=None):
return SlackAPIFileOperator(
task_id="slack",
- token=test_token,
slack_conn_id=test_slack_conn_id,
channels=self.test_channel,
initial_comment=self.test_initial_comment,
@@ -191,11 +172,10 @@ class TestSlackAPIFileOperator:
)
def test_init_with_valid_params(self):
- test_token = "test_token"
-
- slack_api_post_operator = self.__construct_operator(test_token, None,
self.test_api_params)
- assert slack_api_post_operator.token == test_token
- assert slack_api_post_operator.slack_conn_id is None
+ slack_api_post_operator = self.__construct_operator(
+ SLACK_API_TEST_CONNECTION_ID, self.test_api_params
+ )
+ assert slack_api_post_operator.slack_conn_id ==
SLACK_API_TEST_CONNECTION_ID
assert slack_api_post_operator.method == self.expected_method
assert slack_api_post_operator.initial_comment ==
self.test_initial_comment
assert slack_api_post_operator.channels == self.test_channel
@@ -203,10 +183,7 @@ class TestSlackAPIFileOperator:
assert slack_api_post_operator.filename == self.filename
assert slack_api_post_operator.filetype == self.test_filetype
assert slack_api_post_operator.content == self.test_content
-
- slack_api_post_operator = self.__construct_operator(None,
SLACK_API_TEST_CONNECTION_ID)
- assert slack_api_post_operator.token is None
- assert slack_api_post_operator.slack_conn_id ==
SLACK_API_TEST_CONNECTION_ID
+ assert not hasattr(slack_api_post_operator, "token")
@mock.patch("airflow.providers.slack.operators.slack.SlackHook.send_file")
@pytest.mark.parametrize("initial_comment", [None, "foo-bar"])
diff --git a/tests/providers/slack/operators/test_slack_webhook.py
b/tests/providers/slack/operators/test_slack_webhook.py
index 59bcf40c85..abc8900c82 100644
--- a/tests/providers/slack/operators/test_slack_webhook.py
+++ b/tests/providers/slack/operators/test_slack_webhook.py
@@ -21,94 +21,32 @@ from unittest import mock
import pytest
-from airflow.exceptions import AirflowException,
AirflowProviderDeprecationWarning
from airflow.providers.slack.operators.slack_webhook import
SlackWebhookOperator
class TestSlackWebhookOperator:
def setup_method(self):
self.default_op_kwargs = {
+ "slack_webhook_conn_id": "test_conn_id",
"channel": None,
"username": None,
"icon_emoji": None,
"icon_url": None,
}
- @pytest.mark.parametrize(
- "simple_http_op_attr",
- [
- "endpoint",
- "method",
- "data",
- "headers",
- "response_check",
- "response_filter",
- "extra_options",
- "log_response",
- "auth_type",
- "tcp_keep_alive",
- "tcp_keep_alive_idle",
- "tcp_keep_alive_count",
- "tcp_keep_alive_interval",
- ],
- )
- def test_unused_deprecated_http_operator_kwargs(self, simple_http_op_attr):
- """
- Test remove deprecated (and unused) SimpleHttpOperator keyword
arguments.
- No error should happen if provide any of attribute, unless operator
allow to provide this attributes.
- """
- kw = {simple_http_op_attr: "foo-bar"}
- warning_message = rf"Provide '{simple_http_op_attr}' is deprecated and
as has no affect"
- with pytest.warns(AirflowProviderDeprecationWarning,
match=warning_message):
- SlackWebhookOperator(task_id="test_unused_args", **kw)
-
- def test_deprecated_http_conn_id(self):
- """Test resolve deprecated http_conn_id."""
- warning_message = (
- r"Parameter `http_conn_id` is deprecated. Please use
`slack_webhook_conn_id` instead."
- )
- with pytest.warns(AirflowProviderDeprecationWarning,
match=warning_message):
- op = SlackWebhookOperator(
- task_id="test_deprecated_http_conn_id",
slack_webhook_conn_id=None, http_conn_id="http_conn"
- )
- assert op.slack_webhook_conn_id == "http_conn"
-
- error_message = "You cannot provide both `slack_webhook_conn_id` and
`http_conn_id`."
- with pytest.raises(AirflowException, match=error_message):
- with pytest.warns(AirflowProviderDeprecationWarning,
match=warning_message):
- SlackWebhookOperator(
- task_id="test_both_conn_ids",
- slack_webhook_conn_id="slack_webhook_conn_id",
- http_conn_id="http_conn",
- )
-
- @pytest.mark.parametrize(
- "slack_webhook_conn_id,webhook_token",
- [
- ("test_conn_id", None),
- (None, "https://hooks.slack.com/services/T000/B000/XXX"),
- ("test_conn_id", "https://hooks.slack.com/services/T000/B000/XXX"),
- ],
- )
@pytest.mark.parametrize("proxy", [None, "https://localhost:9999"])
@mock.patch("airflow.providers.slack.operators.slack_webhook.SlackWebhookHook")
- def test_hook(self, mock_slackwebhook_cls, slack_webhook_conn_id,
webhook_token, proxy):
+ def test_hook(self, mock_slackwebhook_cls, proxy):
"""Test get cached ``SlackWebhookHook`` hook."""
- op_kw = {
- "slack_webhook_conn_id": slack_webhook_conn_id,
- "proxy": proxy,
- "webhook_token": webhook_token,
- }
- op = SlackWebhookOperator(task_id="test_hook", **op_kw)
+ op = SlackWebhookOperator(task_id="test_hook",
slack_webhook_conn_id="test_conn_id", proxy=proxy)
hook = op.hook
assert hook is op.hook, "Expected cached hook"
- mock_slackwebhook_cls.assert_called_once_with(**op_kw)
+
mock_slackwebhook_cls.assert_called_once_with(slack_webhook_conn_id="test_conn_id",
proxy=proxy)
def test_assert_templated_fields(self):
"""Test expected templated fields."""
operator =
SlackWebhookOperator(task_id="test_assert_templated_fields",
**self.default_op_kwargs)
template_fields = (
- "webhook_token",
"message",
"attachments",
"blocks",
@@ -163,5 +101,4 @@ class TestSlackWebhookOperator:
username=username,
icon_emoji=icon_emoji,
icon_url=icon_url,
- link_names=mock.ANY,
)
diff --git a/tests/providers/slack/transfers/test_sql_to_slack.py
b/tests/providers/slack/transfers/test_sql_to_slack.py
index 23efa895a2..07e6e87aba 100644
--- a/tests/providers/slack/transfers/test_sql_to_slack.py
+++ b/tests/providers/slack/transfers/test_sql_to_slack.py
@@ -138,10 +138,7 @@ class TestSqlToSlackOperator:
sql_to_slack_operator.run(start_date=DEFAULT_DATE,
end_date=DEFAULT_DATE, ignore_ti_state=True)
# Test that the Slack hook is instantiated with the right parameters
- mock_slack_hook_class.assert_called_once_with(
- slack_webhook_conn_id="slack_connection",
- webhook_token=None,
- )
+
mock_slack_hook_class.assert_called_once_with(slack_webhook_conn_id="slack_connection")
# Test that the `SlackWebhookHook.send` method gets run once
slack_webhook_hook.send.assert_called_once_with(
@@ -160,7 +157,6 @@ class TestSqlToSlackOperator:
operator_args = {
"sql_conn_id": "snowflake_connection",
"slack_conn_id": "slack_connection",
- "slack_webhook_token": "test_token",
"slack_message": "message: {{ ds }}, {{ results_df }}",
"slack_channel": "#test",
"sql": "sql {{ ds }}",
@@ -173,10 +169,7 @@ class TestSqlToSlackOperator:
sql_to_slack_operator.run(start_date=DEFAULT_DATE,
end_date=DEFAULT_DATE, ignore_ti_state=True)
# Test that the Slack hook is instantiated with the right parameters
- mock_slack_hook_class.assert_called_once_with(
- slack_webhook_conn_id="slack_connection",
- webhook_token="test_token",
- )
+
mock_slack_hook_class.assert_called_once_with(slack_webhook_conn_id="slack_connection")
# Test that the `SlackWebhookHook.send` method gets run once
slack_webhook_hook.send.assert_called_once_with(
@@ -217,10 +210,7 @@ class TestSqlToSlackOperator:
sql_to_slack_operator.run(start_date=DEFAULT_DATE,
end_date=DEFAULT_DATE, ignore_ti_state=True)
# Test that the Slack hook is instantiated with the right parameters
- mock_slack_hook_class.assert_called_once_with(
- slack_webhook_conn_id="slack_connection",
- webhook_token=None,
- )
+
mock_slack_hook_class.assert_called_once_with(slack_webhook_conn_id="slack_connection")
# Test that the `SlackWebhookHook.send` method gets run once
slack_webhook_hook.send.assert_called_once_with(
@@ -242,9 +232,9 @@ class TestSqlToSlackOperator:
"sql": "sql {{ ds }}",
"results_df_name": "xxxx",
"sql_hook_params": hook_params,
+ "slack_conn_id": "slack_connection",
"parameters": ["1", "2", "3"],
"slack_message": "message: {{ ds }}, {{ xxxx }}",
- "slack_webhook_token": "test_token",
"dag": self.example_dag,
}
sql_to_slack_operator = SqlToSlackOperator(task_id=TEST_TASK_ID,
**operator_args)
@@ -257,7 +247,7 @@ class TestSqlToSlackOperator:
op = SqlToSlackOperator(
task_id="sql_hook_params",
sql_conn_id="postgres_test",
- slack_webhook_token="slack_token",
+ slack_conn_id="slack_connection",
sql="SELECT 1",
slack_message="message: {{ ds }}, {{ xxxx }}",
sql_hook_params={