detectkit 0.59.0__tar.gz → 0.60.0__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.59.0/detectkit.egg-info → detectkit-0.60.0}/PKG-INFO +6 -2
- {detectkit-0.59.0 → detectkit-0.60.0}/README.md +1 -1
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/__init__.py +1 -1
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/CLAUDE.section.md +1 -1
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/rules/project.md +24 -3
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +6 -2
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/init.py +13 -1
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/config/profile.py +86 -19
- detectkit-0.60.0/detectkit/database/duckdb_manager.py +329 -0
- {detectkit-0.59.0 → detectkit-0.60.0/detectkit.egg-info}/PKG-INFO +6 -2
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit.egg-info/SOURCES.txt +1 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit.egg-info/requires.txt +5 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/pyproject.toml +8 -1
- {detectkit-0.59.0 → detectkit-0.60.0}/LICENSE +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/MANIFEST.in +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/base.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/discord.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/email.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/googlechat.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/ntfy.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/teams.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/telegram.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/channels/webhook.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/orchestrator/_base.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/orchestrator/_decision.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/orchestrator/_recovery.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/orchestrator/_replay.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/_base.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/_types.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/autotuner.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/axis_spec.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/config_emitter.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/crossval.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/detector_select.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/distribution.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/grid_search.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/labels.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/result.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/runner.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/scoring.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/seasonality_search.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/settings.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/autotune/window_select.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/_output.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/rules/alerting.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/rules/autotune.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/rules/cli.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/skills/dtk-autotune/SKILL.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/skills/dtk-tune/SKILL.md +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/autotune.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/init_claude.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/osi.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/tune.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/ui.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/main.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/config/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/config/metric_io.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/config/project_config.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/config/validator.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/core/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/core/interval.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/core/models.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/_autotune_runs.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/manager.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/database/tables.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/base.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/statistical/autoreg.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/orchestration/task_manager/_alert_step.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/reporting/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/reporting/assets/report.js +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/reporting/builder.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/reporting/html_report.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/semantic/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/semantic/errors.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/semantic/exporter.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/semantic/importer.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/semantic/osi_model.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/semantic/query_gen.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/tuning/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/tuning/assets/tune.js +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/tuning/config_writer.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/tuning/html.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/tuning/payload.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/tuning/server.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/ui/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/ui/assets/ui.js +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/ui/html.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/ui/jobs.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/ui/metric_files.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/ui/overview.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/ui/server.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit/utils/stats.py +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/requirements.txt +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/setup.cfg +0 -0
- {detectkit-0.59.0 → detectkit-0.60.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: detectkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.60.0
|
|
4
4
|
Summary: Metric monitoring with automatic anomaly detection
|
|
5
5
|
Author: detectkit team
|
|
6
6
|
License: MIT
|
|
@@ -38,10 +38,13 @@ Provides-Extra: mysql
|
|
|
38
38
|
Requires-Dist: pymysql>=1.0.0; extra == "mysql"
|
|
39
39
|
Provides-Extra: mariadb
|
|
40
40
|
Requires-Dist: pymysql>=1.0.0; extra == "mariadb"
|
|
41
|
+
Provides-Extra: duckdb
|
|
42
|
+
Requires-Dist: duckdb>=1.1; extra == "duckdb"
|
|
41
43
|
Provides-Extra: all-db
|
|
42
44
|
Requires-Dist: clickhouse-driver>=0.2.0; extra == "all-db"
|
|
43
45
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == "all-db"
|
|
44
46
|
Requires-Dist: pymysql>=1.0.0; extra == "all-db"
|
|
47
|
+
Requires-Dist: duckdb>=1.1; extra == "all-db"
|
|
45
48
|
Provides-Extra: prophet
|
|
46
49
|
Requires-Dist: prophet>=1.1.0; extra == "prophet"
|
|
47
50
|
Provides-Extra: timesfm
|
|
@@ -55,6 +58,7 @@ Provides-Extra: all
|
|
|
55
58
|
Requires-Dist: clickhouse-driver>=0.2.0; extra == "all"
|
|
56
59
|
Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
|
|
57
60
|
Requires-Dist: pymysql>=1.0.0; extra == "all"
|
|
61
|
+
Requires-Dist: duckdb>=1.1; extra == "all"
|
|
58
62
|
Requires-Dist: prophet>=1.1.0; extra == "all"
|
|
59
63
|
Requires-Dist: timesfm>=0.1.0; extra == "all"
|
|
60
64
|
Requires-Dist: sqlglot>=20.0; extra == "all"
|
|
@@ -99,7 +103,7 @@ Dynamic: license-file
|
|
|
99
103
|
- **@mentions** — tag users/groups in alerts, each channel formats natively
|
|
100
104
|
- **Alert lifecycle** — consecutive anomalies, fraction-of-window rule (`anomaly_window` + `min_anomaly_share`), cooldown, recovery notifications, no-data alerts
|
|
101
105
|
- **Project-level error alerts** — catch DB outages and pipeline crashes once per run
|
|
102
|
-
- **Database agnostic** — ClickHouse, PostgreSQL, MySQL
|
|
106
|
+
- **Database agnostic** — ClickHouse, PostgreSQL, MySQL/MariaDB, DuckDB (no server needed)
|
|
103
107
|
- **Idempotent** — resume from interruptions, no duplicate processing
|
|
104
108
|
- **CLI** — `dtk init`, `dtk run --select`, `dtk unlock`, `dtk clean`, tag-based selectors
|
|
105
109
|
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules + three skills) so an assistant can scaffold metrics, configure databases, and file feedback upstream
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- **@mentions** — tag users/groups in alerts, each channel formats natively
|
|
25
25
|
- **Alert lifecycle** — consecutive anomalies, fraction-of-window rule (`anomaly_window` + `min_anomaly_share`), cooldown, recovery notifications, no-data alerts
|
|
26
26
|
- **Project-level error alerts** — catch DB outages and pipeline crashes once per run
|
|
27
|
-
- **Database agnostic** — ClickHouse, PostgreSQL, MySQL
|
|
27
|
+
- **Database agnostic** — ClickHouse, PostgreSQL, MySQL/MariaDB, DuckDB (no server needed)
|
|
28
28
|
- **Idempotent** — resume from interruptions, no duplicate processing
|
|
29
29
|
- **CLI** — `dtk init`, `dtk run --select`, `dtk unlock`, `dtk clean`, tag-based selectors
|
|
30
30
|
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules + three skills) so an assistant can scaffold metrics, configure databases, and file feedback upstream
|
|
@@ -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.
|
|
7
|
+
__version__ = "0.60.0"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -14,7 +14,7 @@ conventions.
|
|
|
14
14
|
**Database access for _you_ (recommended, not required).** detectkit itself
|
|
15
15
|
connects to the database directly via its drivers — it **never** needs an MCP to
|
|
16
16
|
run. But you assist far better with **read access to the same database** (e.g. a
|
|
17
|
-
database MCP for the project's ClickHouse / PostgreSQL / MySQL / MariaDB): you can inspect
|
|
17
|
+
database MCP for the project's ClickHouse / PostgreSQL / MySQL / MariaDB / DuckDB): you can inspect
|
|
18
18
|
a metric's series, find real incidents to label for `dtk autotune`, sanity-check
|
|
19
19
|
a metric query before running it, and confirm detections — instead of asking the
|
|
20
20
|
user to run every query by hand. Without it, fall back to marking incidents
|
|
@@ -116,10 +116,12 @@ alert_channels:
|
|
|
116
116
|
|
|
117
117
|
### Database profiles
|
|
118
118
|
|
|
119
|
-
> ClickHouse, PostgreSQL, MySQL and
|
|
119
|
+
> ClickHouse, PostgreSQL, MySQL, MariaDB and DuckDB are all supported.
|
|
120
120
|
> ClickHouse/MySQL/MariaDB use two *databases*; PostgreSQL connects to one
|
|
121
|
-
> `database` and uses two *schemas
|
|
122
|
-
>
|
|
121
|
+
> `database` and uses two *schemas*; DuckDB is a single *file* (or `:memory:`
|
|
122
|
+
> for tests) holding two *schemas*.
|
|
123
|
+
> `dtk init --db-type {clickhouse,postgres,mysql,mariadb}` scaffolds the right
|
|
124
|
+
> shape — DuckDB profiles are written by hand (not yet a `--db-type` choice).
|
|
123
125
|
|
|
124
126
|
**ClickHouse**:
|
|
125
127
|
```yaml
|
|
@@ -173,6 +175,25 @@ auto-detected at connect (`SELECT VERSION()`), so MariaDB gets its own
|
|
|
173
175
|
`VALUES()`-form upsert instead of MySQL 8.0.19's row-alias form.
|
|
174
176
|
`pip install 'detectkit[mariadb]'`.
|
|
175
177
|
|
|
178
|
+
**DuckDB** (embedded, single file; two schemas like PostgreSQL):
|
|
179
|
+
```yaml
|
|
180
|
+
profiles:
|
|
181
|
+
prod:
|
|
182
|
+
type: duckdb
|
|
183
|
+
path: /var/lib/detectkit/warehouse.duckdb # required — file path, or ":memory:"
|
|
184
|
+
internal_schema: detectkit # optional — _dtk_* tables (default: "detectkit")
|
|
185
|
+
data_schema: main # optional — data queries (default: "main")
|
|
186
|
+
read_only: false # optional (default: false)
|
|
187
|
+
settings: {} # optional — extra duckdb.connect() config options
|
|
188
|
+
```
|
|
189
|
+
> Single-writer: DuckDB allows only **one** read-write connection to the file
|
|
190
|
+
> at a time — a `dtk ui`/`dtk tune` session left open will clash with a
|
|
191
|
+
> separately spawned `dtk run` against the same file. Point a read-only
|
|
192
|
+
> consumer at it with `read_only: true`.
|
|
193
|
+
> `path: ":memory:"` has no on-disk state, so resume/idempotency breaks across
|
|
194
|
+
> process restarts — use it for tests/scratch only, never a real project.
|
|
195
|
+
> `pip install 'detectkit[duckdb]'`.
|
|
196
|
+
|
|
176
197
|
### Alert channels
|
|
177
198
|
|
|
178
199
|
Defined once in `profiles.yml`, referenced by name in each metric's
|
{detectkit-0.59.0 → detectkit-0.60.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md
RENAMED
|
@@ -50,13 +50,17 @@ side by side, ask which one to set up.
|
|
|
50
50
|
|
|
51
51
|
## Step 1 — Pick the database backend
|
|
52
52
|
|
|
53
|
-
**ClickHouse, PostgreSQL, MySQL and
|
|
53
|
+
**ClickHouse, PostgreSQL, MySQL, MariaDB and DuckDB are all supported.** Ask
|
|
54
54
|
which one the project uses (default to ClickHouse if unsure). `dtk init
|
|
55
55
|
--db-type {clickhouse,postgres,mysql,mariadb}` scaffolds `profiles.yml` for the
|
|
56
|
-
chosen backend
|
|
56
|
+
chosen backend (DuckDB isn't a `--db-type` choice yet — write its profile by
|
|
57
|
+
hand; see `project.md`). The location fields differ:
|
|
57
58
|
- **ClickHouse** / **MySQL** / **MariaDB** — two *databases*: `internal_database` / `data_database`.
|
|
58
59
|
- **PostgreSQL** — connect to a `database` (must already exist), then two
|
|
59
60
|
*schemas*: `internal_schema` / `data_schema`.
|
|
61
|
+
- **DuckDB** — a single **file** (`path`, or `":memory:"` for tests only),
|
|
62
|
+
then two *schemas*: `internal_schema` / `data_schema`. No host/port/user/
|
|
63
|
+
password — it's an embedded, single-writer database (see `project.md`).
|
|
60
64
|
|
|
61
65
|
`type: mariadb` is an identical profile alias to `type: mysql` (same location
|
|
62
66
|
fields; the vendor is auto-detected at connect). The metric query SQL dialect
|
|
@@ -123,6 +123,15 @@ _COMMENTED_EXAMPLES = {
|
|
|
123
123
|
# password: ""
|
|
124
124
|
# internal_database: detectkit
|
|
125
125
|
# data_database: analytics
|
|
126
|
+
""",
|
|
127
|
+
"duckdb": """ # Example DuckDB profile (in-process, single-file; no host/port/user/password)
|
|
128
|
+
# duckdb_dev:
|
|
129
|
+
# type: duckdb
|
|
130
|
+
# path: ./detectkit.duckdb # or ':memory:' for a transient, tests-only DB
|
|
131
|
+
# internal_schema: detectkit
|
|
132
|
+
# data_schema: main
|
|
133
|
+
# # Only ONE read-write connection at a time — don't point a scheduled
|
|
134
|
+
# # `dtk run` and a long-lived `dtk ui` at the same file simultaneously.
|
|
126
135
|
""",
|
|
127
136
|
}
|
|
128
137
|
|
|
@@ -351,7 +360,10 @@ timeouts:
|
|
|
351
360
|
# live under a top-level 'profiles:' mapping). The active dev/prod profiles
|
|
352
361
|
# are scaffolded for the chosen --db-type; the other backends are included
|
|
353
362
|
# as commented examples. See the per-database docs for connection details.
|
|
354
|
-
|
|
363
|
+
# "duckdb" is not a `--db-type` choice (there is no active/prod scaffold
|
|
364
|
+
# for it — an in-process file DB has no separate dev/prod endpoints), so
|
|
365
|
+
# it always shows up as a commented example, unlike the other backends.
|
|
366
|
+
other_backends = [t for t in ("clickhouse", "postgres", "mysql", "duckdb") if t != db_type]
|
|
355
367
|
commented = "\n".join(_COMMENTED_EXAMPLES[t] for t in other_backends)
|
|
356
368
|
profiles_config = (
|
|
357
369
|
"# Database connection profiles\n\n"
|
|
@@ -8,7 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
import yaml
|
|
11
|
-
from pydantic import BaseModel, Field, field_validator
|
|
11
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
12
12
|
|
|
13
13
|
from detectkit.database.clickhouse_manager import ClickHouseDatabaseManager
|
|
14
14
|
from detectkit.database.manager import BaseDatabaseManager
|
|
@@ -23,44 +23,68 @@ class ProfileConfig(BaseModel):
|
|
|
23
23
|
environment (dev, prod, etc.).
|
|
24
24
|
|
|
25
25
|
Attributes:
|
|
26
|
-
type: Database type ("clickhouse", "postgres", "mysql", "mariadb")
|
|
27
|
-
host: Database host
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
type: Database type ("clickhouse", "postgres", "mysql", "mariadb", "duckdb")
|
|
27
|
+
host: Database host (unused for DuckDB — it is an in-process, file-backed
|
|
28
|
+
database with no network endpoint)
|
|
29
|
+
port: Database port (unused for DuckDB; required for every other backend)
|
|
30
|
+
user: Database user (unused for DuckDB)
|
|
31
|
+
password: Database password (unused for DuckDB)
|
|
32
|
+
database: Connection-target database (PostgreSQL/MySQL/MariaDB; unused
|
|
33
|
+
for DuckDB — use `path` instead)
|
|
34
|
+
path: Path to the DuckDB database file, or ":memory:" for a transient
|
|
35
|
+
in-process database (DuckDB only)
|
|
31
36
|
internal_database: Database/schema for internal tables
|
|
32
|
-
internal_schema: Schema for internal tables (PostgreSQL only)
|
|
37
|
+
internal_schema: Schema for internal tables (PostgreSQL/DuckDB only)
|
|
33
38
|
data_database: Database for user data tables
|
|
34
|
-
data_schema: Schema for user data (PostgreSQL only)
|
|
39
|
+
data_schema: Schema for user data (PostgreSQL/DuckDB only)
|
|
35
40
|
settings: Additional database-specific settings
|
|
36
41
|
"""
|
|
37
42
|
|
|
38
43
|
type: str = Field(..., description="Database type")
|
|
39
|
-
host: str = Field(default="localhost", description="Database host")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
host: str = Field(default="localhost", description="Database host (unused for DuckDB)")
|
|
45
|
+
# No default: required for every backend except DuckDB (enforced below by
|
|
46
|
+
# `_validate_port_required`, since DuckDB has no network port at all).
|
|
47
|
+
port: int | None = Field(default=None, description="Database port (unused for DuckDB)")
|
|
48
|
+
user: str = Field(default="default", description="Database user (unused for DuckDB)")
|
|
49
|
+
password: str = Field(default="", description="Database password (unused for DuckDB)")
|
|
43
50
|
|
|
44
51
|
# Connection-target database. Required for PostgreSQL (the database to
|
|
45
52
|
# connect to, inside which internal_schema/data_schema live); optional for
|
|
46
|
-
# MySQL/MariaDB; unused for ClickHouse.
|
|
53
|
+
# MySQL/MariaDB; unused for ClickHouse and DuckDB.
|
|
47
54
|
database: str | None = Field(
|
|
48
55
|
default=None, description="Database to connect to (PostgreSQL/MySQL/MariaDB)"
|
|
49
56
|
)
|
|
50
57
|
|
|
58
|
+
# DuckDB-only: the database file path (or ":memory:"). host/port/user/
|
|
59
|
+
# password/database above are simply ignored for this backend rather than
|
|
60
|
+
# rejected, since e.g. `host` always carries its "localhost" default.
|
|
61
|
+
path: str | None = Field(
|
|
62
|
+
default=None,
|
|
63
|
+
description="Database file path, or ':memory:' for a transient in-process database (DuckDB only)",
|
|
64
|
+
)
|
|
65
|
+
read_only: bool = Field(
|
|
66
|
+
default=False,
|
|
67
|
+
description=(
|
|
68
|
+
"Open the database read-only (DuckDB only) — lets a reader profile "
|
|
69
|
+
"coexist with the one process holding the file read-write"
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
51
73
|
# Internal location for _dtk_* tables
|
|
52
74
|
internal_database: str | None = Field(
|
|
53
75
|
default=None, description="Database for internal tables (ClickHouse/MySQL/MariaDB)"
|
|
54
76
|
)
|
|
55
77
|
internal_schema: str | None = Field(
|
|
56
|
-
default=None, description="Schema for internal tables (PostgreSQL)"
|
|
78
|
+
default=None, description="Schema for internal tables (PostgreSQL/DuckDB)"
|
|
57
79
|
)
|
|
58
80
|
|
|
59
81
|
# Data location for user tables
|
|
60
82
|
data_database: str | None = Field(
|
|
61
83
|
default=None, description="Database for user data tables (ClickHouse/MySQL/MariaDB)"
|
|
62
84
|
)
|
|
63
|
-
data_schema: str | None = Field(
|
|
85
|
+
data_schema: str | None = Field(
|
|
86
|
+
default=None, description="Schema for user data (PostgreSQL/DuckDB)"
|
|
87
|
+
)
|
|
64
88
|
|
|
65
89
|
settings: dict[str, Any] = Field(
|
|
66
90
|
default_factory=dict, description="Additional database settings"
|
|
@@ -70,7 +94,7 @@ class ProfileConfig(BaseModel):
|
|
|
70
94
|
@classmethod
|
|
71
95
|
def validate_type(cls, v: str) -> str:
|
|
72
96
|
"""Validate database type."""
|
|
73
|
-
allowed_types = {"clickhouse", "postgres", "mysql", "mariadb"}
|
|
97
|
+
allowed_types = {"clickhouse", "postgres", "mysql", "mariadb", "duckdb"}
|
|
74
98
|
if v not in allowed_types:
|
|
75
99
|
raise ValueError(
|
|
76
100
|
f"Invalid database type: {v}. " f"Allowed types: {', '.join(allowed_types)}"
|
|
@@ -79,12 +103,21 @@ class ProfileConfig(BaseModel):
|
|
|
79
103
|
|
|
80
104
|
@field_validator("port")
|
|
81
105
|
@classmethod
|
|
82
|
-
def validate_port(cls, v: int) -> int:
|
|
83
|
-
"""Validate port number."""
|
|
106
|
+
def validate_port(cls, v: int | None) -> int | None:
|
|
107
|
+
"""Validate port number (when set — DuckDB profiles may omit it)."""
|
|
108
|
+
if v is None:
|
|
109
|
+
return v
|
|
84
110
|
if not (1 <= v <= 65535):
|
|
85
111
|
raise ValueError(f"Port must be between 1 and 65535, got {v}")
|
|
86
112
|
return v
|
|
87
113
|
|
|
114
|
+
@model_validator(mode="after")
|
|
115
|
+
def _validate_port_required(self) -> "ProfileConfig":
|
|
116
|
+
"""Every backend but DuckDB needs a port; DuckDB has no network endpoint."""
|
|
117
|
+
if self.type != "duckdb" and self.port is None:
|
|
118
|
+
raise ValueError(f"port is required for database type '{self.type}'")
|
|
119
|
+
return self
|
|
120
|
+
|
|
88
121
|
def get_internal_location(self) -> str:
|
|
89
122
|
"""
|
|
90
123
|
Get internal location (database or schema).
|
|
@@ -107,6 +140,10 @@ class ProfileConfig(BaseModel):
|
|
|
107
140
|
if not self.internal_database:
|
|
108
141
|
raise ValueError("internal_database must be set for MySQL/MariaDB")
|
|
109
142
|
return self.internal_database
|
|
143
|
+
elif self.type == "duckdb":
|
|
144
|
+
# Mirrors DuckDBDatabaseManager's own default (unlike PostgreSQL,
|
|
145
|
+
# an unset internal_schema doesn't need to be an error here).
|
|
146
|
+
return self.internal_schema or "detectkit"
|
|
110
147
|
else:
|
|
111
148
|
raise ValueError(f"Unsupported database type: {self.type}")
|
|
112
149
|
|
|
@@ -132,6 +169,10 @@ class ProfileConfig(BaseModel):
|
|
|
132
169
|
if not self.data_database:
|
|
133
170
|
raise ValueError("data_database must be set for MySQL/MariaDB")
|
|
134
171
|
return self.data_database
|
|
172
|
+
elif self.type == "duckdb":
|
|
173
|
+
# Mirrors DuckDBDatabaseManager's own default data_schema ("main",
|
|
174
|
+
# DuckDB's always-present default schema).
|
|
175
|
+
return self.data_schema or "main"
|
|
135
176
|
else:
|
|
136
177
|
raise ValueError(f"Unsupported database type: {self.type}")
|
|
137
178
|
|
|
@@ -144,10 +185,14 @@ class ProfileConfig(BaseModel):
|
|
|
144
185
|
|
|
145
186
|
Raises:
|
|
146
187
|
ValueError: If the database type is unsupported, or required
|
|
147
|
-
connection fields (e.g. PostgreSQL ``database
|
|
188
|
+
connection fields (e.g. PostgreSQL ``database``, DuckDB
|
|
189
|
+
``path``) are missing
|
|
148
190
|
ImportError: If the backend's driver is not installed
|
|
149
191
|
"""
|
|
150
192
|
if self.type == "clickhouse":
|
|
193
|
+
# `port` is guaranteed non-None here by `_validate_port_required`
|
|
194
|
+
# (every type but "duckdb" requires it at construction time).
|
|
195
|
+
assert self.port is not None
|
|
151
196
|
return ClickHouseDatabaseManager(
|
|
152
197
|
host=self.host,
|
|
153
198
|
port=self.port,
|
|
@@ -165,6 +210,7 @@ class ProfileConfig(BaseModel):
|
|
|
165
210
|
"PostgreSQL profiles must set 'database' (the database to "
|
|
166
211
|
"connect to, inside which internal_schema/data_schema live)"
|
|
167
212
|
)
|
|
213
|
+
assert self.port is not None
|
|
168
214
|
return PostgresDatabaseManager(
|
|
169
215
|
host=self.host,
|
|
170
216
|
port=self.port,
|
|
@@ -180,6 +226,7 @@ class ProfileConfig(BaseModel):
|
|
|
180
226
|
# connect time and adjusts the upsert SQL it generates accordingly.
|
|
181
227
|
from detectkit.database.mysql_manager import MySQLDatabaseManager
|
|
182
228
|
|
|
229
|
+
assert self.port is not None
|
|
183
230
|
return MySQLDatabaseManager(
|
|
184
231
|
host=self.host,
|
|
185
232
|
port=self.port,
|
|
@@ -190,6 +237,26 @@ class ProfileConfig(BaseModel):
|
|
|
190
237
|
data_database=self.get_data_location(),
|
|
191
238
|
settings=self.settings,
|
|
192
239
|
)
|
|
240
|
+
elif self.type == "duckdb":
|
|
241
|
+
# In-process, file-backed database: host/port/user/password/database
|
|
242
|
+
# are meaningless for it and are simply not read here (rather than
|
|
243
|
+
# rejected — `host`/`user`/`password` always carry non-empty
|
|
244
|
+
# defaults, so rejecting them would punish every duckdb profile).
|
|
245
|
+
from detectkit.database.duckdb_manager import DuckDBDatabaseManager
|
|
246
|
+
|
|
247
|
+
if not self.path:
|
|
248
|
+
raise ValueError(
|
|
249
|
+
"DuckDB profiles must set 'path' (the database file path, "
|
|
250
|
+
"or ':memory:' for a transient, tests/preview-only "
|
|
251
|
+
"in-process database)"
|
|
252
|
+
)
|
|
253
|
+
return DuckDBDatabaseManager(
|
|
254
|
+
path=self.path,
|
|
255
|
+
internal_schema=self.get_internal_location(),
|
|
256
|
+
data_schema=self.get_data_location(),
|
|
257
|
+
read_only=self.read_only,
|
|
258
|
+
settings=self.settings,
|
|
259
|
+
)
|
|
193
260
|
else:
|
|
194
261
|
raise ValueError(f"Unsupported database type: {self.type}")
|
|
195
262
|
|