Skip to content
Open
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
12 changes: 12 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ include = [
"/pythonkuma",
]

[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.14", "3.13", "3.12"]

[tool.hatch.envs.default]
dependencies = [
"ruff==0.15.0",
Expand All @@ -45,6 +48,10 @@ dependencies = [
"mashumaro==3.20",
"mkdocs-material==9.7.1",
"mkdocstrings[python]==1.0.3",
"pytest-asyncio==1.3.0",
"pytest==9.0.2",
"pytest-cov==7.0.0",
"syrupy==5.1.0",
]

[tool.hatch.envs.hatch-static-analysis]
Expand All @@ -63,6 +70,8 @@ pythonpath = ["pythonkuma"]
[tool.hatch.envs.hatch-test]
extra-dependencies = [
"pytest-cov==7.0.0",
"pytest-asyncio==1.3.0",
"syrupy==5.1.0",
]

[tool.hatch.envs.default.scripts]
Expand All @@ -83,6 +92,9 @@ indent-style = "space"
select = ["ALL"]
ignore = ["TRY003", "COM812", "N818", "C901"]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101", "TC002", "TC003"]

[lint.per-file-ignores]
"**/scripts/*" = [
"INP001",
Expand Down
2 changes: 1 addition & 1 deletion pythonkuma/uptimekuma.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ async def metrics(self) -> dict[str | int, UptimeKumaMonitor]:
raise UptimeKumaConnectionException from e

try:
metrics = set(text_string_to_metric_families(await request.text()))
metrics = list(text_string_to_metric_families(await request.text()))
except ValueError as e:
raise UptimeKumaParseException from e

Expand Down
77 changes: 77 additions & 0 deletions tests/__snapshots__/test_metrics.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# serializer version: 1
# name: test_metrics
dict({
1: dict({
'monitor_cert_days_remaining': 80,
'monitor_cert_is_valid': True,
'monitor_hostname': None,
'monitor_id': 1,
'monitor_name': 'Home Assistant',
'monitor_port': None,
'monitor_response_time': 85,
'monitor_response_time_seconds_1d': 0.10396079958463136,
'monitor_response_time_seconds_30d': 0.10284582478851578,
'monitor_response_time_seconds_365d': 0.10957428212662089,
'monitor_status': 1,
'monitor_type': 'http',
'monitor_uptime_ratio_1d': 1.0,
'monitor_uptime_ratio_30d': 0.999247554552295,
'monitor_uptime_ratio_365d': 0.9944324016912971,
'monitor_url': 'https://home.example.com:8123',
}),
2: dict({
'monitor_cert_days_remaining': 80,
'monitor_cert_is_valid': True,
'monitor_hostname': None,
'monitor_id': 2,
'monitor_name': 'FritzBox',
'monitor_port': None,
'monitor_response_time': 2725,
'monitor_response_time_seconds_1d': 2.339521038961039,
'monitor_response_time_seconds_30d': 2.3636583723629956,
'monitor_response_time_seconds_365d': 2.3783335690116663,
'monitor_status': 1,
'monitor_type': 'http',
'monitor_uptime_ratio_1d': 0.9992213859330392,
'monitor_uptime_ratio_30d': 0.9998319004850872,
'monitor_uptime_ratio_365d': 0.9947252084798553,
'monitor_url': 'https://home.example.com',
}),
3: dict({
'monitor_cert_days_remaining': 46,
'monitor_cert_is_valid': True,
'monitor_hostname': None,
'monitor_id': 3,
'monitor_name': 'Jellyfin',
'monitor_port': None,
'monitor_response_time': 85,
'monitor_response_time_seconds_1d': 0.10102960288808664,
'monitor_response_time_seconds_30d': 0.09908259629443207,
'monitor_response_time_seconds_365d': 0.10429958790526786,
'monitor_status': 1,
'monitor_type': 'keyword',
'monitor_uptime_ratio_1d': 1.0,
'monitor_uptime_ratio_30d': 0.9993293252532994,
'monitor_uptime_ratio_365d': 0.9941631600380073,
'monitor_url': 'https://home.example.com:8920/health',
}),
8: dict({
'monitor_cert_days_remaining': 81,
'monitor_cert_is_valid': True,
'monitor_hostname': None,
'monitor_id': 8,
'monitor_name': 'Nextcloud',
'monitor_port': None,
'monitor_response_time': 150,
'monitor_response_time_seconds_1d': 0.16155477855477854,
'monitor_response_time_seconds_30d': 0.3391915450984161,
'monitor_response_time_seconds_365d': 0.34379255863250385,
'monitor_status': 1,
'monitor_type': 'json-query',
'monitor_uptime_ratio_1d': 1.0,
'monitor_uptime_ratio_30d': 0.9991115593334294,
'monitor_uptime_ratio_365d': 0.9994703389830508,
'monitor_url': 'https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json',
}),
})
# ---
31 changes: 31 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Fixtures for pythonkuma."""

from collections.abc import Generator
from functools import lru_cache
import pathlib
from unittest.mock import AsyncMock

from aiohttp import ClientResponse
import pytest


@lru_cache
def load_fixture(filename: str) -> str:
"""Load a fixture."""
return (
pathlib.Path(__file__)
.parent.joinpath("fixtures", filename)
.read_text(encoding="utf-8")
)


@pytest.fixture
def mock_session() -> Generator[AsyncMock]:
"""Mock aiohttp ClientSession."""
mock_session = AsyncMock()
mock_response = AsyncMock(spec=ClientResponse, status=200)
mock_response.text.return_value = load_fixture("metrics.txt")

mock_session.get.return_value = mock_response

return mock_session
61 changes: 61 additions & 0 deletions tests/fixtures/metrics.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# HELP monitor_cert_days_remaining The number of days remaining until the certificate expires
# TYPE monitor_cert_days_remaining gauge
monitor_cert_days_remaining{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null"} 80
monitor_cert_days_remaining{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null"} 80
monitor_cert_days_remaining{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null"} 46
monitor_cert_days_remaining{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null"} 81

# HELP monitor_cert_is_valid Is the certificate still valid? (1 = Yes, 0= No)
# TYPE monitor_cert_is_valid gauge
monitor_cert_is_valid{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null"} 1
monitor_cert_is_valid{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null"} 1
monitor_cert_is_valid{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null"} 1
monitor_cert_is_valid{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null"} 1

# HELP monitor_uptime_ratio Uptime ratio calculated over sliding window specified by the 'window' label. (0.0 - 1.0)
# TYPE monitor_uptime_ratio gauge
monitor_uptime_ratio{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null",window="1d"} 1
monitor_uptime_ratio{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null",window="30d"} 0.999247554552295
monitor_uptime_ratio{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null",window="365d"} 0.9944324016912971
monitor_uptime_ratio{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null",window="1d"} 0.9992213859330392
monitor_uptime_ratio{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null",window="30d"} 0.9998319004850872
monitor_uptime_ratio{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null",window="365d"} 0.9947252084798553
monitor_uptime_ratio{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null",window="1d"} 1
monitor_uptime_ratio{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null",window="30d"} 0.9993293252532994
monitor_uptime_ratio{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null",window="365d"} 0.9941631600380073
monitor_uptime_ratio{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null",window="1d"} 1
monitor_uptime_ratio{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null",window="30d"} 0.9991115593334294
monitor_uptime_ratio{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null",window="365d"} 0.9994703389830508

# HELP monitor_response_time_seconds Average response time in seconds calculated over sliding window specified by the 'window' label
# TYPE monitor_response_time_seconds gauge
monitor_response_time_seconds{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null",window="1d"} 0.10396079958463136
monitor_response_time_seconds{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null",window="30d"} 0.10284582478851578
monitor_response_time_seconds{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null",window="365d"} 0.10957428212662089
monitor_response_time_seconds{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null",window="1d"} 2.339521038961039
monitor_response_time_seconds{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null",window="30d"} 2.3636583723629956
monitor_response_time_seconds{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null",window="365d"} 2.3783335690116663
monitor_response_time_seconds{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null",window="1d"} 0.10102960288808664
monitor_response_time_seconds{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null",window="30d"} 0.09908259629443207
monitor_response_time_seconds{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null",window="365d"} 0.10429958790526786
monitor_response_time_seconds{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null",window="1d"} 0.16155477855477854
monitor_response_time_seconds{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null",window="30d"} 0.3391915450984161
monitor_response_time_seconds{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null",window="365d"} 0.34379255863250385

# HELP monitor_response_time Monitor Response Time (ms)
# TYPE monitor_response_time gauge
monitor_response_time{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null"} 85
monitor_response_time{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null"} 2725
monitor_response_time{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null"} 85
monitor_response_time{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null"} 150

# HELP monitor_status Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)
# TYPE monitor_status gauge
monitor_status{monitor_id="1",monitor_name="Home Assistant",monitor_type="http",monitor_url="https://home.example.com:8123",monitor_hostname="null",monitor_port="null"} 1
monitor_status{monitor_id="2",monitor_name="FritzBox",monitor_type="http",monitor_url="https://home.example.com",monitor_hostname="null",monitor_port="null"} 1
monitor_status{Test="",Zuhause="",monitor_id="3",monitor_name="Jellyfin",monitor_type="keyword",monitor_url="https://home.example.com:8920/health",monitor_hostname="null",monitor_port="null"} 1
monitor_status{monitor_id="8",monitor_name="Nextcloud",monitor_type="json-query",monitor_url="https://cloud.example.com/ocs/v2.php/apps/serverinfo/api/v1/info?format=json",monitor_hostname="null",monitor_port="null"} 1

# HELP app_version The service version by package.json
# TYPE app_version gauge
app_version{version="2.1.0",major="2",minor="1",patch="0"} 1
16 changes: 16 additions & 0 deletions tests/test_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Tests for pythonkuma."""

from unittest.mock import AsyncMock

from syrupy.assertion import SnapshotAssertion

from pythonkuma import UptimeKuma


async def test_metrics(mock_session: AsyncMock, snapshot: SnapshotAssertion) -> None:
"""Test metrics."""
uptime_kuma = UptimeKuma(mock_session, "http://uptime.example.com", "test-apikey")

response = await uptime_kuma.metrics()

assert {k: v.to_dict() for k, v in response.items()} == snapshot
Loading