detectkit 0.16.0__tar.gz → 0.16.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {detectkit-0.16.0/detectkit.egg-info → detectkit-0.16.2}/PKG-INFO +1 -1
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/__init__.py +1 -1
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/webhook.py +25 -11
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/alerting.md +5 -3
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/cli.md +3 -2
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/test_alert.py +18 -2
- {detectkit-0.16.0 → detectkit-0.16.2/detectkit.egg-info}/PKG-INFO +1 -1
- {detectkit-0.16.0 → detectkit-0.16.2}/LICENSE +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/MANIFEST.in +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/README.md +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/_output.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/CLAUDE.section.md +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/rules/project.md +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/main.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/profile.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/project_config.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/config/validator.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/core/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/core/interval.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/core/models.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/manager.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/database/tables.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/base.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit/utils/stats.py +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/SOURCES.txt +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/pyproject.toml +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/requirements.txt +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/setup.cfg +0 -0
- {detectkit-0.16.0 → detectkit-0.16.2}/setup.py +0 -0
|
@@ -4,7 +4,7 @@ detectk - Anomaly Detection for Time-Series Metrics
|
|
|
4
4
|
A Python library for data analysts and engineers to monitor metrics with automatic anomaly detection.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.16.
|
|
7
|
+
__version__ = "0.16.2"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -293,20 +293,21 @@ class WebhookChannel(BaseAlertChannel):
|
|
|
293
293
|
err = f"{ctx['error_type']}: {ctx['error_message']}".strip(": ")
|
|
294
294
|
full("Error", code(err))
|
|
295
295
|
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
|
|
296
|
+
# Links block — kept as its own flexible field, but every entry is a
|
|
297
|
+
# compact clickable label, never a raw URL string. A Grafana dashboard
|
|
298
|
+
# URL can be a paragraph long once it carries variables; nobody should
|
|
299
|
+
# read that in an alert, so we hide it behind the label and render in the
|
|
300
|
+
# platform's link syntax. Holds dashboard + any extra links + the "how to
|
|
301
|
+
# read this alert" guide, joined by " · ".
|
|
302
|
+
link_parts = []
|
|
299
303
|
if alert_data.dashboard_url:
|
|
300
|
-
|
|
304
|
+
link_parts.append(self._link_markup(alert_data.dashboard_url, "Dashboard"))
|
|
301
305
|
for label, url in alert_data.links.items():
|
|
302
|
-
|
|
303
|
-
if link_lines:
|
|
304
|
-
full("Links", "\n".join(link_lines))
|
|
305
|
-
|
|
306
|
-
# "How to read this alert" — the last field, a bare URL (auto-linkified on
|
|
307
|
-
# both Slack and Mattermost) pointing readers at the interpretation guide.
|
|
306
|
+
link_parts.append(self._link_markup(url, label))
|
|
308
307
|
if ctx["help_url"]:
|
|
309
|
-
|
|
308
|
+
link_parts.append(self._link_markup(ctx["help_url"], ctx["help_label"]))
|
|
309
|
+
if link_parts:
|
|
310
|
+
full("Links", " · ".join(link_parts))
|
|
310
311
|
|
|
311
312
|
# A plain-text one-liner for notification previews / unsupported clients.
|
|
312
313
|
if kind == "no_data":
|
|
@@ -328,6 +329,19 @@ class WebhookChannel(BaseAlertChannel):
|
|
|
328
329
|
"mrkdwn_in": ["text", "fields"],
|
|
329
330
|
}
|
|
330
331
|
|
|
332
|
+
def _link_markup(self, url: str, label: str) -> str:
|
|
333
|
+
"""Render *label* as a clickable link in the target platform's syntax.
|
|
334
|
+
|
|
335
|
+
Slack incoming webhooks (``hooks.slack.com``) use ``<url|label>``;
|
|
336
|
+
Mattermost and other webhooks use markdown ``[label](url)``. This keeps a
|
|
337
|
+
link a short clickable phrase instead of a raw URL — important because a
|
|
338
|
+
real dashboard URL (e.g. Grafana with many variables) can be extremely
|
|
339
|
+
long, and no one should have to read it inside an alert.
|
|
340
|
+
"""
|
|
341
|
+
if "hooks.slack.com" in self.webhook_url:
|
|
342
|
+
return f"<{url}|{label}>"
|
|
343
|
+
return f"[{label}]({url})"
|
|
344
|
+
|
|
331
345
|
@staticmethod
|
|
332
346
|
def _unix_ts(ts: Any) -> int | None:
|
|
333
347
|
"""Epoch seconds (UTC) for a naive-UTC timestamp, or None on failure."""
|
|
@@ -167,9 +167,11 @@ config needed. Control it project-wide with `alert_help_url` in
|
|
|
167
167
|
Per-channel rendering (defaults only; resolved by
|
|
168
168
|
`ProjectConfig.resolve_alert_help_url`):
|
|
169
169
|
|
|
170
|
-
- **Slack / Mattermost / generic webhook** — a
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
- **Slack / Mattermost / generic webhook** — a clickable `How to read this alert`
|
|
171
|
+
label in the compact `Links` field (alongside `Dashboard` + any extra links),
|
|
172
|
+
never a raw URL. Rendered in the platform's link syntax (Slack `<url|label>`,
|
|
173
|
+
Mattermost/generic markdown links) so a long dashboard URL stays hidden behind
|
|
174
|
+
its label.
|
|
173
175
|
- **Telegram** — appended to the links line (after the optional "Open dashboard"
|
|
174
176
|
link) as an `<a>` link reading `How to read this alert`.
|
|
175
177
|
- **Email** — in the footer, after `Sent by detectkit · <project>` (and any CC),
|
|
@@ -55,8 +55,9 @@ dtk run --select <sel> [--steps load,detect,alert] [--from DATE] [--to DATE] \
|
|
|
55
55
|
|
|
56
56
|
Sends a mock alert (fake value/CI/severity) through the metric's configured
|
|
57
57
|
channels, using that alert config's own rule (`min_detectors` / `direction` /
|
|
58
|
-
`consecutive_anomalies`)
|
|
59
|
-
verify webhook URLs, channel permissions, and
|
|
58
|
+
`consecutive_anomalies`) and the project-name `[name]` prefix, so the preview
|
|
59
|
+
matches a real firing. Use it to verify webhook URLs, channel permissions, and
|
|
60
|
+
custom templates.
|
|
60
61
|
|
|
61
62
|
## `dtk unlock --select <sel>`
|
|
62
63
|
|
|
@@ -23,6 +23,7 @@ def create_mock_alert_data(
|
|
|
23
23
|
alerting_config,
|
|
24
24
|
timezone_display: str = "UTC",
|
|
25
25
|
help_url: str | None = None,
|
|
26
|
+
project_name: str | None = None,
|
|
26
27
|
) -> AlertData:
|
|
27
28
|
"""
|
|
28
29
|
Create realistic mock AlertData for testing.
|
|
@@ -36,6 +37,10 @@ def create_mock_alert_data(
|
|
|
36
37
|
timezone_display: Timezone for display
|
|
37
38
|
help_url: Resolved "how to read this alert" link to preview (the
|
|
38
39
|
project's ``alert_help_url``); ``None`` renders no help link.
|
|
40
|
+
project_name: Project name from ``detectkit_project.yml`` — stamped so
|
|
41
|
+
the preview carries the same ``[name]`` prefix a real ``dtk run``
|
|
42
|
+
renders (the run pipeline sets it in ``_alert_step.py``); ``None``
|
|
43
|
+
renders no prefix.
|
|
39
44
|
|
|
40
45
|
Returns:
|
|
41
46
|
AlertData with mock anomaly data
|
|
@@ -90,6 +95,7 @@ def create_mock_alert_data(
|
|
|
90
95
|
},
|
|
91
96
|
consecutive_count=consecutive_required,
|
|
92
97
|
mentions=mentions,
|
|
98
|
+
project_name=project_name,
|
|
93
99
|
dashboard_url=getattr(alerting_config, "dashboard_url", None),
|
|
94
100
|
links=dict(getattr(alerting_config, "links", {}) or {}),
|
|
95
101
|
help_url=help_url,
|
|
@@ -123,7 +129,13 @@ def run_test_alert(metric_name: str, profile: str | None = None):
|
|
|
123
129
|
with open(project_config_path) as f:
|
|
124
130
|
project_data = yaml.safe_load(f)
|
|
125
131
|
|
|
126
|
-
|
|
132
|
+
# Project name drives the [name] prefix a real `dtk run` stamps on every
|
|
133
|
+
# alert (see _alert_step.py); thread it so the preview matches a real firing.
|
|
134
|
+
project_name = project_data.get("name")
|
|
135
|
+
|
|
136
|
+
# Metrics dir lives under `paths.metrics` (default "metrics"); the old
|
|
137
|
+
# top-level `metrics_path` key is deprecated and ignored by ProjectConfig.
|
|
138
|
+
metrics_dir_name = (project_data.get("paths") or {}).get("metrics", "metrics")
|
|
127
139
|
|
|
128
140
|
# Resolve the "how to read this alert" link so the preview matches what real
|
|
129
141
|
# alerts would carry (brand default, a custom URL, or hidden via false).
|
|
@@ -187,7 +199,11 @@ def run_test_alert(metric_name: str, profile: str | None = None):
|
|
|
187
199
|
print(f" Channels: {', '.join(alerting_config.channels)}\n")
|
|
188
200
|
|
|
189
201
|
alert_data = create_mock_alert_data(
|
|
190
|
-
metric_config,
|
|
202
|
+
metric_config,
|
|
203
|
+
alerting_config,
|
|
204
|
+
timezone_display,
|
|
205
|
+
help_url=help_url,
|
|
206
|
+
project_name=project_name,
|
|
191
207
|
)
|
|
192
208
|
|
|
193
209
|
success_count = 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.16.0 → detectkit-0.16.2}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|