feat: Grafana copy publishing, dashboard examples, and optional OTel metrics
Implements three observability improvements: #82 — publish_grafana_copy(): Uses SQLite online backup API (WAL-safe) to atomically publish a consistent read-only copy beside the target. Adds --publish-copy option to grafana-schema and snapshot CLI commands. #83 — examples/grafana/: Minimal working Grafana setup with docker-compose, provisioning datasource/dashboard YAML, and three dashboard JSON files (mt5cli-overview, mt5cli-trades, mt5cli-market). All queries use grafana_* views; no credentials or private paths included. #84 — mt5cli/telemetry.py: Optional OTel metrics behind mt5cli[otel] extra. Base install is unaffected. Adds _Mt5Metrics singleton (no-op until configure_metrics() is called), wraps update_history() and update_observability() with record_history_update / record_snapshot_update context managers, and emits account/position gauges from snapshots. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2022,6 +2022,77 @@ class TestSnapshotCommand:
|
||||
assert result.exit_code == 0, result.output
|
||||
assert updater.call_args.kwargs["with_grafana_schema"] is False
|
||||
|
||||
def test_snapshot_with_publish_copy(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""--publish-copy calls publish_grafana_copy after update_observability."""
|
||||
mocker.patch("mt5cli.cli.sdk.update_observability_with_config")
|
||||
mock_publish = mocker.patch("mt5cli.grafana.publish_grafana_copy")
|
||||
copy_path = tmp_path / "grafana.db"
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
"-o",
|
||||
str(tmp_path / "out.db"),
|
||||
"snapshot",
|
||||
"--publish-copy",
|
||||
str(copy_path),
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_publish.assert_called_once()
|
||||
|
||||
def test_snapshot_no_publish_copy_by_default(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""Snapshot does not call publish_grafana_copy without --publish-copy."""
|
||||
mocker.patch("mt5cli.cli.sdk.update_observability_with_config")
|
||||
mock_publish = mocker.patch("mt5cli.grafana.publish_grafana_copy")
|
||||
result = runner.invoke(app, ["-o", str(tmp_path / "out.db"), "snapshot"])
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_publish.assert_not_called()
|
||||
|
||||
|
||||
class TestGrafanaSchemaPublishCopy:
|
||||
"""Tests for grafana-schema --publish-copy option."""
|
||||
|
||||
def test_grafana_schema_with_publish_copy(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""grafana-schema --publish-copy calls publish_grafana_copy."""
|
||||
mock_publish = mocker.patch("mt5cli.grafana.publish_grafana_copy")
|
||||
output = tmp_path / "out.db"
|
||||
copy_path = tmp_path / "grafana.db"
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
"-o",
|
||||
str(output),
|
||||
"grafana-schema",
|
||||
"--publish-copy",
|
||||
str(copy_path),
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_publish.assert_called_once()
|
||||
|
||||
def test_grafana_schema_no_publish_copy_by_default(
|
||||
self,
|
||||
tmp_path: Path,
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""grafana-schema does not call publish_grafana_copy by default."""
|
||||
mock_publish = mocker.patch("mt5cli.grafana.publish_grafana_copy")
|
||||
result = runner.invoke(app, ["-o", str(tmp_path / "out.db"), "grafana-schema"])
|
||||
assert result.exit_code == 0, result.output
|
||||
mock_publish.assert_not_called()
|
||||
|
||||
|
||||
class TestMain:
|
||||
"""Tests for the main entry point."""
|
||||
|
||||
Reference in New Issue
Block a user