You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
7.4 KiB
218 lines
7.4 KiB
# |
|
# Licensed to the Apache Software Foundation (ASF) under one |
|
# or more contributor license agreements. See the NOTICE file |
|
# distributed with this work for additional information |
|
# regarding copyright ownership. The ASF licenses this file |
|
# to you under the Apache License, Version 2.0 (the |
|
# "License"); you may not use this file except in compliance |
|
# with the License. You may obtain a copy of the License at |
|
# |
|
# http://www.apache.org/licenses/LICENSE-2.0 |
|
# |
|
# Unless required by applicable law or agreed to in writing, |
|
# software distributed under the License is distributed on an |
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|
# KIND, either express or implied. See the License for the |
|
# specific language governing permissions and limitations |
|
# under the License. |
|
|
|
import json |
|
from typing import Any, Dict, List, Optional |
|
|
|
from airflow.models import BaseOperator |
|
from airflow.providers.slack.hooks.slack import SlackHook |
|
from airflow.utils.decorators import apply_defaults |
|
|
|
|
|
class SlackAPIOperator(BaseOperator): |
|
""" |
|
Base Slack Operator |
|
The SlackAPIPostOperator is derived from this operator. |
|
In the future additional Slack API Operators will be derived from this class as well |
|
Only one of `slack_conn_id` and `token` is required. |
|
|
|
:param slack_conn_id: Slack connection ID which its password is Slack API token. Optional |
|
:type slack_conn_id: str |
|
:param token: Slack API token (https://api.slack.com/web). Optional |
|
:type token: str |
|
:param method: The Slack API Method to Call (https://api.slack.com/methods). Optional |
|
:type method: str |
|
:param api_params: API Method call parameters (https://api.slack.com/methods). Optional |
|
:type api_params: dict |
|
:param client_args: Slack Hook parameters. Optional. Check airflow.providers.slack.hooks.SlackHook |
|
:type api_params: dict |
|
""" |
|
|
|
@apply_defaults |
|
def __init__( |
|
self, |
|
*, |
|
slack_conn_id: Optional[str] = None, |
|
token: Optional[str] = None, |
|
method: Optional[str] = None, |
|
api_params: Optional[Dict] = None, |
|
**kwargs, |
|
) -> None: |
|
super().__init__(**kwargs) |
|
|
|
self.token = token # type: Optional[str] |
|
self.slack_conn_id = slack_conn_id # type: Optional[str] |
|
|
|
self.method = method |
|
self.api_params = api_params |
|
|
|
def construct_api_call_params(self) -> Any: |
|
""" |
|
Used by the execute function. Allows templating on the source fields |
|
of the api_call_params dict before construction |
|
|
|
Override in child classes. |
|
Each SlackAPIOperator child class is responsible for |
|
having a construct_api_call_params function |
|
which sets self.api_call_params with a dict of |
|
API call parameters (https://api.slack.com/methods) |
|
""" |
|
raise NotImplementedError( |
|
"SlackAPIOperator should not be used directly. Chose one of the subclasses instead" |
|
) |
|
|
|
def execute(self, **kwargs): # noqa: D403 |
|
""" |
|
SlackAPIOperator calls will not fail even if the call is not unsuccessful. |
|
It should not prevent a DAG from completing in success |
|
""" |
|
if not self.api_params: |
|
self.construct_api_call_params() |
|
slack = SlackHook(token=self.token, slack_conn_id=self.slack_conn_id) |
|
slack.call(self.method, json=self.api_params) |
|
|
|
|
|
class SlackAPIPostOperator(SlackAPIOperator): |
|
""" |
|
Posts messages to a slack channel |
|
Examples: |
|
|
|
.. code-block:: python |
|
|
|
slack = SlackAPIPostOperator( |
|
task_id="post_hello", |
|
dag=dag, |
|
token="XXX", |
|
text="hello there!", |
|
channel="#random", |
|
) |
|
|
|
:param channel: channel in which to post message on slack name (#general) or |
|
ID (C12318391). (templated) |
|
:type channel: str |
|
:param username: Username that airflow will be posting to Slack as. (templated) |
|
:type username: str |
|
:param text: message to send to slack. (templated) |
|
:type text: str |
|
:param icon_url: url to icon used for this message |
|
:type icon_url: str |
|
:param attachments: extra formatting details. (templated) |
|
- see https://api.slack.com/docs/attachments. |
|
:type attachments: list of hashes |
|
:param blocks: extra block layouts. (templated) |
|
- see https://api.slack.com/reference/block-kit/blocks. |
|
:type blocks: list of hashes |
|
""" |
|
|
|
template_fields = ("username", "text", "attachments", "blocks", "channel") |
|
ui_color = "#FFBA40" |
|
|
|
@apply_defaults |
|
def __init__( |
|
self, |
|
channel: str = "#general", |
|
username: str = "Airflow", |
|
text: str = "No message has been set.\n" |
|
"Here is a cat video instead\n" |
|
"https://www.youtube.com/watch?v=J---aiyznGQ", |
|
icon_url: str = "https://raw.githubusercontent.com/apache/" |
|
"airflow/master/airflow/www/static/pin_100.png", |
|
attachments: Optional[List] = None, |
|
blocks: Optional[List] = None, |
|
**kwargs, |
|
) -> None: |
|
self.method = "chat.postMessage" |
|
self.channel = channel |
|
self.username = username |
|
self.text = text |
|
self.icon_url = icon_url |
|
self.attachments = attachments or [] |
|
self.blocks = blocks or [] |
|
super().__init__(method=self.method, **kwargs) |
|
|
|
def construct_api_call_params(self) -> Any: |
|
self.api_params = { |
|
"channel": self.channel, |
|
"username": self.username, |
|
"text": self.text, |
|
"icon_url": self.icon_url, |
|
"attachments": json.dumps(self.attachments), |
|
"blocks": json.dumps(self.blocks), |
|
} |
|
|
|
|
|
class SlackAPIFileOperator(SlackAPIOperator): |
|
""" |
|
Send a file to a slack channel |
|
Examples: |
|
|
|
.. code-block:: python |
|
|
|
slack = SlackAPIFileOperator( |
|
task_id="slack_file_upload", |
|
dag=dag, |
|
slack_conn_id="slack", |
|
channel="#general", |
|
initial_comment="Hello World!", |
|
filename="hello_world.csv", |
|
filetype="csv", |
|
content="hello,world,csv,file", |
|
) |
|
|
|
:param channel: channel in which to sent file on slack name (templated) |
|
:type channel: str |
|
:param initial_comment: message to send to slack. (templated) |
|
:type initial_comment: str |
|
:param filename: name of the file (templated) |
|
:type filename: str |
|
:param filetype: slack filetype. (templated) |
|
- see https://api.slack.com/types/file |
|
:type filetype: str |
|
:param content: file content. (templated) |
|
:type content: str |
|
""" |
|
|
|
template_fields = ("channel", "initial_comment", "filename", "filetype", "content") |
|
ui_color = "#44BEDF" |
|
|
|
@apply_defaults |
|
def __init__( |
|
self, |
|
channel: str = "#general", |
|
initial_comment: str = "No message has been set!", |
|
filename: str = "default_name.csv", |
|
filetype: str = "csv", |
|
content: str = "default,content,csv,file", |
|
**kwargs, |
|
) -> None: |
|
self.method = "files.upload" |
|
self.channel = channel |
|
self.initial_comment = initial_comment |
|
self.filename = filename |
|
self.filetype = filetype |
|
self.content = content |
|
super().__init__(method=self.method, **kwargs) |
|
|
|
def construct_api_call_params(self) -> Any: |
|
self.api_params = { |
|
"channels": self.channel, |
|
"content": self.content, |
|
"filename": self.filename, |
|
"filetype": self.filetype, |
|
"initial_comment": self.initial_comment, |
|
}
|
|
|