detectkit 0.8.1__tar.gz → 0.8.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.
Files changed (90) hide show
  1. {detectkit-0.8.1/detectkit.egg-info → detectkit-0.8.2}/PKG-INFO +1 -1
  2. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/__init__.py +1 -1
  3. detectkit-0.8.2/detectkit/cli/_output.py +50 -0
  4. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/cli/commands/clean.py +32 -53
  5. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/cli/commands/unlock.py +5 -14
  6. {detectkit-0.8.1 → detectkit-0.8.2/detectkit.egg-info}/PKG-INFO +1 -1
  7. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit.egg-info/SOURCES.txt +1 -0
  8. {detectkit-0.8.1 → detectkit-0.8.2}/LICENSE +0 -0
  9. {detectkit-0.8.1 → detectkit-0.8.2}/MANIFEST.in +0 -0
  10. {detectkit-0.8.1 → detectkit-0.8.2}/README.md +0 -0
  11. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/__init__.py +0 -0
  12. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/channels/__init__.py +0 -0
  13. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/channels/base.py +0 -0
  14. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/channels/email.py +0 -0
  15. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/channels/factory.py +0 -0
  16. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/channels/mattermost.py +0 -0
  17. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/channels/slack.py +0 -0
  18. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/channels/telegram.py +0 -0
  19. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/channels/webhook.py +0 -0
  20. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/orchestrator/__init__.py +0 -0
  21. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/orchestrator/_base.py +0 -0
  22. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
  23. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/orchestrator/_decision.py +0 -0
  24. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
  25. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/orchestrator/_recovery.py +0 -0
  26. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/orchestrator/_types.py +0 -0
  27. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
  28. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/cli/__init__.py +0 -0
  29. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/cli/commands/__init__.py +0 -0
  30. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/cli/commands/init.py +0 -0
  31. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/cli/commands/run.py +0 -0
  32. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/cli/commands/test_alert.py +0 -0
  33. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/cli/main.py +0 -0
  34. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/config/__init__.py +0 -0
  35. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/config/metric_config.py +0 -0
  36. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/config/profile.py +0 -0
  37. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/config/project_config.py +0 -0
  38. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/config/validator.py +0 -0
  39. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/core/__init__.py +0 -0
  40. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/core/interval.py +0 -0
  41. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/core/models.py +0 -0
  42. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/__init__.py +0 -0
  43. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/clickhouse_manager.py +0 -0
  44. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/__init__.py +0 -0
  45. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/_alert_states.py +0 -0
  46. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/_base.py +0 -0
  47. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/_datapoints.py +0 -0
  48. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/_detections.py +0 -0
  49. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/_maintenance.py +0 -0
  50. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/_metrics.py +0 -0
  51. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/_schema.py +0 -0
  52. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/_tasks.py +0 -0
  53. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/internal_tables/manager.py +0 -0
  54. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/manager.py +0 -0
  55. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/database/tables.py +0 -0
  56. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/__init__.py +0 -0
  57. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/base.py +0 -0
  58. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/factory.py +0 -0
  59. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/seasonality.py +0 -0
  60. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/statistical/__init__.py +0 -0
  61. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/statistical/_windowed.py +0 -0
  62. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/statistical/iqr.py +0 -0
  63. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/statistical/mad.py +0 -0
  64. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/statistical/manual_bounds.py +0 -0
  65. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/detectors/statistical/zscore.py +0 -0
  66. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/loaders/__init__.py +0 -0
  67. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/loaders/metric_loader.py +0 -0
  68. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/loaders/query_template.py +0 -0
  69. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/orchestration/__init__.py +0 -0
  70. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/orchestration/error_dispatch.py +0 -0
  71. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/orchestration/task_manager/__init__.py +0 -0
  72. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
  73. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/orchestration/task_manager/_base.py +0 -0
  74. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
  75. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/orchestration/task_manager/_load_step.py +0 -0
  76. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/orchestration/task_manager/_types.py +0 -0
  77. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/orchestration/task_manager/manager.py +0 -0
  78. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/utils/__init__.py +0 -0
  79. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/utils/datetime_utils.py +0 -0
  80. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/utils/env_interpolation.py +0 -0
  81. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/utils/json_utils.py +0 -0
  82. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit/utils/stats.py +0 -0
  83. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit.egg-info/dependency_links.txt +0 -0
  84. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit.egg-info/entry_points.txt +0 -0
  85. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit.egg-info/requires.txt +0 -0
  86. {detectkit-0.8.1 → detectkit-0.8.2}/detectkit.egg-info/top_level.txt +0 -0
  87. {detectkit-0.8.1 → detectkit-0.8.2}/pyproject.toml +0 -0
  88. {detectkit-0.8.1 → detectkit-0.8.2}/requirements.txt +0 -0
  89. {detectkit-0.8.1 → detectkit-0.8.2}/setup.cfg +0 -0
  90. {detectkit-0.8.1 → detectkit-0.8.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: detectkit
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Metric monitoring with automatic anomaly detection
5
5
  Author: detectkit team
6
6
  License: MIT
@@ -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.8.1"
7
+ __version__ = "0.8.2"
8
8
 
9
9
  from detectkit.core.interval import Interval
10
10
  from detectkit.core.models import ColumnDefinition, TableModel
@@ -0,0 +1,50 @@
1
+ """Shared CLI output helpers so every command renders in one house style.
2
+
3
+ Mirrors the load → detect → alert pipeline's tree look (``┌─ / │ / └─``) used by
4
+ ``dtk run`` so the maintenance commands (``dtk clean``, ``dtk unlock``) match it
5
+ instead of each inventing its own formatting.
6
+
7
+ House conventions:
8
+ - A metric *with* something to report is a tree: a cyan-bold ``┌─ <name>``
9
+ header followed by one child line per item (``│ `` for all but the last,
10
+ ``└─ `` for the last).
11
+ - A metric with *nothing* to do is a single ``•`` line.
12
+ - A per-metric error is a red ``✗`` line (to stderr).
13
+ - The final summary is a cyan-bold ``Done. …`` line.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import click
19
+
20
+
21
+ def echo_tree(name: str, children: list[str], *, warnings: list[str] | None = None) -> None:
22
+ """Print a ``┌─ name`` header with ``│``/``└─`` child lines.
23
+
24
+ ``warnings`` (if any) are rendered as yellow ``│`` continuation lines above
25
+ the children. ``children`` must be non-empty (a metric with no items should
26
+ use :func:`echo_noop` instead).
27
+ """
28
+ click.echo(click.style(f" ┌─ {name}", fg="cyan", bold=True))
29
+ for warning in warnings or []:
30
+ click.echo(click.style(f" │ ⚠ {warning}", fg="yellow", bold=True))
31
+ last = len(children) - 1
32
+ for i, child in enumerate(children):
33
+ prefix = " └─ " if i == last else " │ "
34
+ click.echo(f"{prefix}{child}")
35
+
36
+
37
+ def echo_noop(name: str, reason: str) -> None:
38
+ """A metric with nothing to do — a single ``•`` line."""
39
+ click.echo(f" • {name}: {reason}")
40
+
41
+
42
+ def echo_error(name: str, message: str) -> None:
43
+ """A per-metric failure — a red ``✗`` line on stderr."""
44
+ click.echo(click.style(f" ✗ {name}: {message}", fg="red"), err=True)
45
+
46
+
47
+ def echo_done(summary: str) -> None:
48
+ """The closing ``Done. …`` summary (cyan, bold), preceded by a blank line."""
49
+ click.echo()
50
+ click.echo(click.style(f"Done. {summary}", fg="cyan", bold=True))
@@ -27,6 +27,7 @@ from pathlib import Path
27
27
 
28
28
  import click
29
29
 
30
+ from detectkit.cli._output import echo_done, echo_error, echo_noop, echo_tree
30
31
  from detectkit.cli.commands.run import find_project_root, select_metrics
31
32
  from detectkit.config.metric_config import MetricConfig
32
33
  from detectkit.config.profile import ProfilesConfig
@@ -112,6 +113,8 @@ def _clean_drift(
112
113
  total_det_groups = 0
113
114
  total_alert_rows = 0
114
115
 
116
+ verb = "deleting" if execute else "would delete"
117
+
115
118
  for _, config in metrics:
116
119
  metric_name = config.name
117
120
  try:
@@ -120,7 +123,7 @@ def _clean_drift(
120
123
  db_detectors = internal_manager.list_detector_ids(metric_name)
121
124
  db_alerts = internal_manager.list_alert_config_ids(metric_name)
122
125
  except Exception as e:
123
- click.echo(click.style(f" ✗ {metric_name}: error inspecting: {e}", fg="red"), err=True)
126
+ echo_error(metric_name, f"error inspecting: {e}")
124
127
  continue
125
128
 
126
129
  orphan_detectors = {
@@ -129,55 +132,42 @@ def _clean_drift(
129
132
  orphan_alerts = [a for a in db_alerts if a not in valid_alerts]
130
133
 
131
134
  if not orphan_detectors and not orphan_alerts:
132
- click.echo(f" • {metric_name}: nothing stale")
135
+ echo_noop(metric_name, "nothing stale")
133
136
  continue
134
137
 
135
- click.echo(click.style(f" {metric_name}:", bold=True))
138
+ children = [
139
+ f"detector {det_id}: {verb} {count:,} detection row(s)"
140
+ for det_id, count in sorted(orphan_detectors.items())
141
+ ] + [
142
+ f"alert_config {alert_id}: {verb} stale alert state"
143
+ for alert_id in sorted(orphan_alerts)
144
+ ]
136
145
 
137
146
  # An empty valid set means EVERY stored row is "orphaned" — usually a
138
147
  # config mid-edit, not an intent to wipe the metric. Flag it loudly.
148
+ warnings = []
139
149
  if orphan_detectors and not valid_detectors:
140
- click.echo(
141
- click.style(
142
- " ⚠ config defines no detectors — ALL detections below would be removed",
143
- fg="yellow",
144
- bold=True,
145
- )
146
- )
150
+ warnings.append("config defines no detectors — ALL detections below would be removed")
147
151
  if orphan_alerts and not valid_alerts:
148
- click.echo(
149
- click.style(
150
- " ⚠ config defines no alerting — ALL alert states below would be removed",
151
- fg="yellow",
152
- bold=True,
153
- )
154
- )
152
+ warnings.append("config defines no alerting — ALL alert states below would be removed")
153
+
154
+ echo_tree(metric_name, children, warnings=warnings)
155
155
 
156
- for det_id, count in sorted(orphan_detectors.items()):
157
- total_det_groups += 1
158
- verb = "deleting" if execute else "would delete"
159
- click.echo(f" detector {det_id}: {verb} {count:,} detection row(s)")
160
- if execute:
156
+ if execute:
157
+ for det_id in orphan_detectors:
161
158
  internal_manager.delete_detections(
162
159
  metric_name=metric_name, detector_id=det_id, mutations_sync=True
163
160
  )
164
-
165
- for alert_id in sorted(orphan_alerts):
166
- total_alert_rows += 1
167
- verb = "deleting" if execute else "would delete"
168
- click.echo(f" alert_config {alert_id}: {verb} stale alert state")
169
- if execute:
161
+ for alert_id in orphan_alerts:
170
162
  internal_manager.delete_alert_state(metric_name, alert_id)
171
163
 
172
- click.echo()
173
- prefix = "Deleted" if execute else "Would delete"
174
- click.echo(
175
- click.style(
176
- f"{prefix} {total_det_groups} orphaned detector group(s) "
177
- f"and {total_alert_rows} orphaned alert-state row(s).",
178
- fg="cyan",
179
- bold=True,
180
- )
164
+ total_det_groups += len(orphan_detectors)
165
+ total_alert_rows += len(orphan_alerts)
166
+
167
+ verb_done = "Removed" if execute else "Would remove"
168
+ echo_done(
169
+ f"{verb_done} {total_det_groups} detector group(s) "
170
+ f"and {total_alert_rows} alert-state row(s)."
181
171
  )
182
172
  if not execute and (total_det_groups or total_alert_rows):
183
173
  click.echo("Re-run with --execute to apply.")
@@ -222,15 +212,10 @@ def _clean_orphaned_metrics(
222
212
  try:
223
213
  counts = internal_manager.count_metric_rows(name)
224
214
  except Exception as e:
225
- click.echo(click.style(f" ✗ {name}: error counting rows: {e}", fg="red"), err=True)
215
+ echo_error(name, f"error counting rows: {e}")
226
216
  continue
227
- total = sum(counts.values())
228
- verb = "deleting" if execute else "would delete"
229
- detail = ", ".join(f"{table}={count:,}" for table, count in counts.items() if count)
230
- click.echo(
231
- click.style(f" {name}: {verb} {total:,} row(s)", bold=True)
232
- + (f" [{detail}]" if detail else "")
233
- )
217
+ children = [f"{table}: {count:,} row(s)" for table, count in counts.items() if count]
218
+ echo_tree(name, children or ["(no rows)"])
234
219
 
235
220
  if not execute:
236
221
  click.echo()
@@ -264,16 +249,10 @@ def _clean_orphaned_metrics(
264
249
  try:
265
250
  internal_manager.purge_metric(name)
266
251
  purged += 1
267
- click.echo(click.style(f" ✓ {name}: purged", fg="green"))
268
252
  except Exception as e:
269
- click.echo(click.style(f" ✗ {name}: error purging: {e}", fg="red"), err=True)
253
+ echo_error(name, f"error purging: {e}")
270
254
 
271
- click.echo()
272
- click.echo(
273
- click.style(
274
- f"Done. Purged {purged} of {len(orphans)} orphaned metric(s).", fg="cyan", bold=True
275
- )
276
- )
255
+ echo_done(f"Purged {purged} of {len(orphans)} orphaned metric(s).")
277
256
 
278
257
 
279
258
  # ── helpers ──────────────────────────────────────────────────────────────────
@@ -8,6 +8,7 @@ auto-expires after its timeout, but this command clears it immediately.
8
8
 
9
9
  import click
10
10
 
11
+ from detectkit.cli._output import echo_done, echo_error, echo_noop, echo_tree
11
12
  from detectkit.cli.commands.run import find_project_root, select_metrics
12
13
  from detectkit.config.profile import ProfilesConfig
13
14
  from detectkit.database.internal_tables import InternalTablesManager
@@ -83,23 +84,13 @@ def run_unlock(select: str, profile: str | None):
83
84
  try:
84
85
  was_locked = internal_manager.clear_lock(metric_name)
85
86
  except Exception as e:
86
- click.echo(
87
- click.style(f" ✗ {metric_name}: error clearing lock: {e}", fg="red"),
88
- err=True,
89
- )
87
+ echo_error(metric_name, f"error clearing lock: {e}")
90
88
  continue
91
89
 
92
90
  if was_locked:
93
91
  cleared += 1
94
- click.echo(click.style(f" ✓ {metric_name}: lock cleared", fg="green"))
92
+ echo_tree(metric_name, ["lock cleared"])
95
93
  else:
96
- click.echo(f" • {metric_name}: no active lock")
94
+ echo_noop(metric_name, "no active lock")
97
95
 
98
- click.echo()
99
- click.echo(
100
- click.style(
101
- f"Done. Cleared {cleared} lock(s) of {len(metrics)} metric(s).",
102
- fg="cyan",
103
- bold=True,
104
- )
105
- )
96
+ echo_done(f"Cleared {cleared} lock(s) of {len(metrics)} metric(s).")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: detectkit
3
- Version: 0.8.1
3
+ Version: 0.8.2
4
4
  Summary: Metric monitoring with automatic anomaly detection
5
5
  Author: detectkit team
6
6
  License: MIT
@@ -29,6 +29,7 @@ detectkit/alerting/orchestrator/_recovery.py
29
29
  detectkit/alerting/orchestrator/_types.py
30
30
  detectkit/alerting/orchestrator/orchestrator.py
31
31
  detectkit/cli/__init__.py
32
+ detectkit/cli/_output.py
32
33
  detectkit/cli/main.py
33
34
  detectkit/cli/commands/__init__.py
34
35
  detectkit/cli/commands/clean.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes