Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 5 additions & 15 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,10 @@ repos:
# args: ["--branch", "main"]
- id: trailing-whitespace
args: ["--markdown-linebreak-ext=md"]
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
args:
- "--profile"
- "black"
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.241
# isort removed - ruff handles import sorting now
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks:
- id: ruff
args:
- "--fix"
- "--exclude"
- "metrics/defaults/python/prompt.py"
- "--exclude"
- "anomstack/llm/completion.py"
args: ["--fix"]
- id: ruff-format
78 changes: 19 additions & 59 deletions anomstack/alerts/asciiart.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ def __init__(
else:
self.graphsymbol = graphsymbol
if self._len_noansi(self.graphsymbol) != 1:
raise Exception(
"Bad graphsymbol length, must be 1", self._len_noansi(self.graphsymbol)
)
raise Exception("Bad graphsymbol length, must be 1", self._len_noansi(self.graphsymbol))
self.multivalue = multivalue
self.hsymbols = [
self._u(""),
Expand Down Expand Up @@ -181,9 +179,7 @@ def _get_thresholds(self, data):
if self.multivalue:
totalvalue_len += len("," + self._trans_hr(ivalue))
else:
totalvalue_len = max(
totalvalue_len, len(self._trans_hr(ivalue))
)
totalvalue_len = max(totalvalue_len, len(self._trans_hr(ivalue)))

if self.multivalue:
# remove one comma if multivalues
Expand Down Expand Up @@ -214,17 +210,13 @@ def _gen_graph_string(
):
"""Generate the bar + its paddings (left and right)"""

def _gen_graph_string_part(
value, max_value, min_neg_value, graph_length, color
):
def _gen_graph_string_part(value, max_value, min_neg_value, graph_length, color):
all_width = max_value + abs(min_neg_value)

if all_width == 0:
bar_width = 0
else:
bar_width = int(
abs(float(value)) * float(graph_length) / float(all_width)
)
bar_width = int(abs(float(value)) * float(graph_length) / float(all_width))

return (
Pyasciigraph._color_string(self.graphsymbol * bar_width, color),
Expand All @@ -236,9 +228,7 @@ def _gen_graph_string_part(
if all_width == 0:
neg_width = 0
else:
neg_width = int(
abs(float(min_neg_value)) * float(graph_length) / float(all_width)
)
neg_width = int(abs(float(min_neg_value)) * float(graph_length) / float(all_width))
int(abs(max_value) * graph_length / all_width)

if isinstance(value, Iterable):
Expand All @@ -264,9 +254,7 @@ def _gen_graph_string_part(
accuvalue += scaled_value

# left padding
totalstring = (
Pyasciigraph._u(" ") * (neg_width - abs(totalsquares)) + totalstring
)
totalstring = Pyasciigraph._u(" ") * (neg_width - abs(totalsquares)) + totalstring

# reset some counters
accuvalue = 0
Expand All @@ -285,9 +273,7 @@ def _gen_graph_string_part(
accuvalue += scaled_value

# right padding
totalstring += Pyasciigraph._u(" ") * (
start_value_pos - neg_width - abs(totalsquares)
)
totalstring += Pyasciigraph._u(" ") * (start_value_pos - neg_width - abs(totalsquares))
return totalstring
else:
# handling for single value item
Expand All @@ -312,9 +298,7 @@ def _gen_info_string(self, info, start_info_pos, line_length):
number_of_space = line_length - start_info_pos - self._len_noansi(info)
return info + Pyasciigraph._u(" ") * number_of_space

def _gen_value_string(
self, value, min_neg_value, color, start_value_pos, start_info_pos
):
def _gen_value_string(self, value, min_neg_value, color, start_value_pos, start_info_pos):
"""Generate the value string + padding"""
icount = 0
if isinstance(value, Iterable) and self.multivalue:
Expand All @@ -324,14 +308,10 @@ def _gen_value_string(
# with the len() function even when they are not printed to
# the screen.
totalvalue_len = len(self._trans_hr(ivalue))
totalvalue = Pyasciigraph._color_string(
self._trans_hr(ivalue), icolor
)
totalvalue = Pyasciigraph._color_string(self._trans_hr(ivalue), icolor)
else:
totalvalue_len += len("," + self._trans_hr(ivalue))
totalvalue += "," + Pyasciigraph._color_string(
self._trans_hr(ivalue), icolor
)
totalvalue += "," + Pyasciigraph._color_string(self._trans_hr(ivalue), icolor)
icount += 1
elif isinstance(value, Iterable):
max_value = min_neg_value
Expand All @@ -347,9 +327,7 @@ def _gen_value_string(
totalvalue_len = len(self._trans_hr(value))
totalvalue = Pyasciigraph._color_string(self._trans_hr(value), color)

number_space = (
start_info_pos - start_value_pos - totalvalue_len - self.separator_length
)
number_space = start_info_pos - start_value_pos - totalvalue_len - self.separator_length

# This must not be negitive, this happens when the string length is
# larger than the separator length
Expand Down Expand Up @@ -457,18 +435,14 @@ def graph(self, label=None, data=[]):
# calcul of where to start info
start_info_pos = self.line_length - all_thre["info_max_length"]
# calcul of where to start value
start_value_pos = (
start_info_pos - self.separator_length - all_thre["value_max_length"]
)
start_value_pos = start_info_pos - self.separator_length - all_thre["value_max_length"]
# calcul of where to end graph
graph_length = start_value_pos - self.separator_length
else:
# calcul of where to start value
start_value_pos = self.min_graph_length + self.separator_length
# calcul of where to start info
start_info_pos = (
start_value_pos + all_thre["value_max_length"] + self.separator_length
)
start_info_pos = start_value_pos + all_thre["value_max_length"] + self.separator_length
# calcul of where to end graph
graph_length = start_value_pos - self.separator_length
# calcul of the real line length
Expand Down Expand Up @@ -514,32 +488,18 @@ def make_alert_message(
score_col="metric_score_smooth",
ascii_graph=False,
):
df_alert_metric = df_alert_metric.sort_values(
by="metric_timestamp", ascending=False
).dropna()
df_alert_metric["metric_timestamp"] = pd.to_datetime(
df_alert_metric["metric_timestamp"]
)
df_alert_metric = df_alert_metric.sort_values(by="metric_timestamp", ascending=False).dropna()
df_alert_metric["metric_timestamp"] = pd.to_datetime(df_alert_metric["metric_timestamp"])
x = df_alert_metric["metric_value"].round(2).values.tolist()
metric_name = df_alert_metric["metric_name"].unique()[0]
metric_timestamp_from = (
df_alert_metric["metric_timestamp"].min().strftime("%Y-%m-%d %H:%M")
)
metric_timestamp_to = (
df_alert_metric["metric_timestamp"].max().strftime("%Y-%m-%d %H:%M")
)
metric_timestamp_from = df_alert_metric["metric_timestamp"].min().strftime("%Y-%m-%d %H:%M")
metric_timestamp_to = df_alert_metric["metric_timestamp"].max().strftime("%Y-%m-%d %H:%M")
graph_title = f"{metric_name} ({metric_timestamp_from} to {metric_timestamp_to})"
message = ""
if ascii_graph:
labels = (
np.where(
df_alert_metric["metric_alert"] == 1,
anomaly_symbol,
normal_symbol
)
+ (df_alert_metric[score_col].round(2) * 100)
.astype("int")
.astype("str")
np.where(df_alert_metric["metric_alert"] == 1, anomaly_symbol, normal_symbol)
+ (df_alert_metric[score_col].round(2) * 100).astype("int").astype("str")
+ "% "
)
data = zip(labels, x)
Expand Down
26 changes: 9 additions & 17 deletions anomstack/alerts/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
Helper functions for sending alerts via email.
"""

import os
import smtplib
import ssl
import tempfile
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import os
import smtplib
import ssl
import tempfile

from dagster import get_dagster_logger

Expand Down Expand Up @@ -55,12 +55,8 @@ def send_email_with_plot(
host = os.getenv("ANOMSTACK_ALERT_EMAIL_SMTP_HOST")
port = os.getenv("ANOMSTACK_ALERT_EMAIL_SMTP_PORT")

with tempfile.NamedTemporaryFile(
prefix=attachment_name, suffix=".png", delete=False
) as temp:
fig = make_alert_plot(
df, metric_name, threshold, score_col, score_title, tags=tags
)
with tempfile.NamedTemporaryFile(prefix=attachment_name, suffix=".png", delete=False) as temp:
fig = make_alert_plot(df, metric_name, threshold, score_col, score_title, tags=tags)
fig.savefig(temp.name)

msg = MIMEMultipart()
Expand All @@ -70,14 +66,10 @@ def send_email_with_plot(

msg.attach(MIMEText(body, "html"))
binary_file = open(temp.name, "rb")
payload = MIMEBase(
"application", "octate-stream", Name=f"{attachment_name}.png"
)
payload = MIMEBase("application", "octate-stream", Name=f"{attachment_name}.png")
payload.set_payload((binary_file).read())
encoders.encode_base64(payload)
payload.add_header(
"Content-Decomposition", "attachment", filename=f"{attachment_name}.png"
)
payload.add_header("Content-Decomposition", "attachment", filename=f"{attachment_name}.png")
msg.attach(payload)

context = ssl.create_default_context()
Expand Down Expand Up @@ -126,7 +118,7 @@ def send_email(
with smtplib.SMTP(host, port) as server:
server.connect(host, port)
server.starttls(context=context)
server.login(sender, password) # type: ignore
server.login(sender, password) # type: ignore
text = msg.as_string()
server.sendmail(sender, to, text)
server.quit()
Expand Down
6 changes: 2 additions & 4 deletions anomstack/alerts/send.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
Helper functions to send alerts.
"""

import pandas as pd
from dagster import get_dagster_logger
import pandas as pd

from anomstack.alerts.asciiart import make_alert_message
from anomstack.alerts.email import send_email, send_email_with_plot
Expand Down Expand Up @@ -46,9 +46,7 @@ def send_alert(
"""
logger = get_dagster_logger()
logger.debug(f"alerts to send: \n{df}")
message = make_alert_message(
df, description=description, tags=tags, score_col=score_col
)
message = make_alert_message(df, description=description, tags=tags, score_col=score_col)
if "slack" in alert_methods:
send_alert_slack_with_plot(
df=df,
Expand Down
19 changes: 5 additions & 14 deletions anomstack/alerts/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
import os
import tempfile

import matplotlib.pyplot as plt
from dagster import get_dagster_logger
import matplotlib.pyplot as plt
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

Expand Down Expand Up @@ -73,10 +73,7 @@ def send_alert_slack(
slack_token = os.environ.get("ANOMSTACK_SLACK_BOT_TOKEN")
if not slack_token:
raise ValueError(
(
"Slack bot token not found in environment variable "
"ANOMSTACK_SLACK_BOT_TOKEN"
)
("Slack bot token not found in environment variable " "ANOMSTACK_SLACK_BOT_TOKEN")
)

client = WebClient(token=slack_token)
Expand Down Expand Up @@ -119,9 +116,7 @@ def send_alert_slack(
filename=os.path.basename(image_file_path),
)
else:
response = client.chat_postMessage(
channel=channel_id, text=f"*{title}*\n{message}"
)
response = client.chat_postMessage(channel=channel_id, text=f"*{title}*\n{message}")
except SlackApiError as e:
logger.error(f"Error sending message to Slack channel {channel_name, channel_id}: {e}")

Expand All @@ -146,13 +141,9 @@ def send_alert_slack_with_plot(
channel_name = os.environ.get("ANOMSTACK_SLACK_CHANNEL")

with tempfile.NamedTemporaryFile(
prefix=f"{metric_name}_{metric_timestamp}_",
suffix=".png",
delete=False
prefix=f"{metric_name}_{metric_timestamp}_", suffix=".png", delete=False
) as temp:
fig = make_alert_plot(
df, metric_name, threshold, score_col, score_title, tags=tags
)
fig = make_alert_plot(df, metric_name, threshold, score_col, score_title, tags=tags)
fig.savefig(temp.name)
plt.close(fig)

Expand Down
Loading