pythonkuma 0.0.0rc1__tar.gz → 0.0.0rc3__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.
- {pythonkuma-0.0.0rc1 → pythonkuma-0.0.0rc3}/PKG-INFO +6 -7
- {pythonkuma-0.0.0rc1 → pythonkuma-0.0.0rc3}/README.md +5 -6
- {pythonkuma-0.0.0rc1 → pythonkuma-0.0.0rc3}/pythonkuma/__init__.py +3 -3
- pythonkuma-0.0.0rc3/pythonkuma/models.py +77 -0
- pythonkuma-0.0.0rc3/pythonkuma/uptimekuma.py +76 -0
- pythonkuma-0.0.0rc1/pythonkuma/const.py +0 -5
- pythonkuma-0.0.0rc1/pythonkuma/decorator.py +0 -71
- pythonkuma-0.0.0rc1/pythonkuma/models.py +0 -101
- pythonkuma-0.0.0rc1/pythonkuma/uptimekuma.py +0 -23
- {pythonkuma-0.0.0rc1 → pythonkuma-0.0.0rc3}/.gitignore +0 -0
- {pythonkuma-0.0.0rc1 → pythonkuma-0.0.0rc3}/LICENSE +0 -0
- {pythonkuma-0.0.0rc1 → pythonkuma-0.0.0rc3}/pyproject.toml +0 -0
- {pythonkuma-0.0.0rc1 → pythonkuma-0.0.0rc3}/pythonkuma/exceptions.py +0 -0
- {pythonkuma-0.0.0rc1 → pythonkuma-0.0.0rc3}/pythonkuma/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonkuma
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.0rc3
|
|
4
4
|
Summary: Simple Python wrapper for Uptime Kuma
|
|
5
5
|
Project-URL: Source, https://github.com/tr4nt0r/pythonkuma
|
|
6
6
|
Author-email: Manfred Dennerlein Rodelo <manfred@dennerlein.name>, Jayakorn Karikan <jayakornk@gmail.com>
|
|
@@ -37,17 +37,16 @@ import aiohttp
|
|
|
37
37
|
|
|
38
38
|
from pythonkuma import UptimeKuma
|
|
39
39
|
|
|
40
|
-
URL = ""
|
|
41
|
-
|
|
42
|
-
PASSWORD = ""
|
|
40
|
+
URL = "https://uptime.exampe.com"
|
|
41
|
+
API_KEY = "api_key"
|
|
43
42
|
|
|
44
43
|
|
|
45
44
|
async def main():
|
|
46
45
|
|
|
47
46
|
async with aiohttp.ClientSession() as session:
|
|
48
|
-
uptime_kuma = UptimeKuma(session, URL,
|
|
49
|
-
response = await uptime_kuma.
|
|
50
|
-
print(response
|
|
47
|
+
uptime_kuma = UptimeKuma(session, URL, API_KEY)
|
|
48
|
+
response = await uptime_kuma.metrics()
|
|
49
|
+
print(response)
|
|
51
50
|
|
|
52
51
|
|
|
53
52
|
asyncio.run(main())
|
|
@@ -16,17 +16,16 @@ import aiohttp
|
|
|
16
16
|
|
|
17
17
|
from pythonkuma import UptimeKuma
|
|
18
18
|
|
|
19
|
-
URL = ""
|
|
20
|
-
|
|
21
|
-
PASSWORD = ""
|
|
19
|
+
URL = "https://uptime.exampe.com"
|
|
20
|
+
API_KEY = "api_key"
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
async def main():
|
|
25
24
|
|
|
26
25
|
async with aiohttp.ClientSession() as session:
|
|
27
|
-
uptime_kuma = UptimeKuma(session, URL,
|
|
28
|
-
response = await uptime_kuma.
|
|
29
|
-
print(response
|
|
26
|
+
uptime_kuma = UptimeKuma(session, URL, API_KEY)
|
|
27
|
+
response = await uptime_kuma.metrics()
|
|
28
|
+
print(response)
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
asyncio.run(main())
|
|
@@ -5,15 +5,15 @@ from .exceptions import (
|
|
|
5
5
|
UptimeKumaConnectionException,
|
|
6
6
|
UptimeKumaException,
|
|
7
7
|
)
|
|
8
|
-
from .models import
|
|
8
|
+
from .models import MonitorStatus, MonitorType, UptimeKumaMonitor
|
|
9
9
|
from .uptimekuma import UptimeKuma
|
|
10
10
|
|
|
11
|
-
__version__ = "0.0.
|
|
11
|
+
__version__ = "0.0.0rc3"
|
|
12
12
|
|
|
13
13
|
__all__ = [
|
|
14
|
+
"MonitorStatus",
|
|
14
15
|
"MonitorType",
|
|
15
16
|
"UptimeKuma",
|
|
16
|
-
"UptimeKumaApiResponse",
|
|
17
17
|
"UptimeKumaAuthenticationException",
|
|
18
18
|
"UptimeKumaConnectionException",
|
|
19
19
|
"UptimeKumaException",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Uptime Kuma models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import IntEnum, StrEnum
|
|
7
|
+
from typing import Self
|
|
8
|
+
|
|
9
|
+
from mashumaro import DataClassDictMixin
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MonitorStatus(IntEnum):
|
|
13
|
+
"""Monitor states."""
|
|
14
|
+
|
|
15
|
+
DOWN = 0
|
|
16
|
+
UP = 1
|
|
17
|
+
PENDING = 2
|
|
18
|
+
MAINTENANCE = 3
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MonitorType(StrEnum):
|
|
22
|
+
"""Monitors type."""
|
|
23
|
+
|
|
24
|
+
HTTP = "http"
|
|
25
|
+
PORT = "port"
|
|
26
|
+
PING = "ping"
|
|
27
|
+
KEYWORD = "keyword"
|
|
28
|
+
DNS = "dns"
|
|
29
|
+
PUSH = "push"
|
|
30
|
+
STEAM = "steam"
|
|
31
|
+
MQTT = "mqtt"
|
|
32
|
+
SQL = "sqlserver"
|
|
33
|
+
JSON_QUERY = "json-query"
|
|
34
|
+
GROUP = "group"
|
|
35
|
+
DOCKER = "docker"
|
|
36
|
+
GRPC_KEYWORD = "grpc-keyword"
|
|
37
|
+
REAL_BROWSER = "real-browser"
|
|
38
|
+
GAMEDIG = "gamedig"
|
|
39
|
+
KAFKA_PRODUCER = "kafka-producer"
|
|
40
|
+
POSTGRES = "postgres"
|
|
41
|
+
MYSQL = "mysql"
|
|
42
|
+
MONGODB = "mongodb"
|
|
43
|
+
RADIUS = "radius"
|
|
44
|
+
REDIS = "redis"
|
|
45
|
+
TAILSCALE_PING = "tailscale-ping"
|
|
46
|
+
UNKNOWN = "unknown"
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def _missing_(cls, _: object) -> Self:
|
|
50
|
+
"""Handle new and unknown monitor types."""
|
|
51
|
+
return cls.UNKNOWN
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class UptimeKumaBaseModel(DataClassDictMixin):
|
|
56
|
+
"""UptimeKumaBaseModel."""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(kw_only=True)
|
|
60
|
+
class UptimeKumaMonitor(UptimeKumaBaseModel):
|
|
61
|
+
"""Monitor model for Uptime Kuma."""
|
|
62
|
+
|
|
63
|
+
monitor_cert_days_remaining: int
|
|
64
|
+
monitor_cert_is_valid: bool
|
|
65
|
+
monitor_hostname: str | None = field(
|
|
66
|
+
metadata={"deserialize": lambda v: None if v == "null" else v}
|
|
67
|
+
)
|
|
68
|
+
monitor_name: str
|
|
69
|
+
monitor_port: str | None = field(
|
|
70
|
+
metadata={"deserialize": lambda v: None if v == "null" else v}
|
|
71
|
+
)
|
|
72
|
+
monitor_response_time: int = 0
|
|
73
|
+
monitor_status: MonitorStatus
|
|
74
|
+
monitor_type: MonitorType = MonitorType.HTTP
|
|
75
|
+
monitor_url: str | None = field(
|
|
76
|
+
metadata={"deserialize": lambda v: None if v == "null" else v}
|
|
77
|
+
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Uptime Kuma client."""
|
|
2
|
+
|
|
3
|
+
from http import HTTPStatus
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from aiohttp import (
|
|
7
|
+
BasicAuth,
|
|
8
|
+
ClientError,
|
|
9
|
+
ClientResponseError,
|
|
10
|
+
ClientSession,
|
|
11
|
+
ClientTimeout,
|
|
12
|
+
)
|
|
13
|
+
from prometheus_client.parser import text_string_to_metric_families
|
|
14
|
+
from yarl import URL
|
|
15
|
+
|
|
16
|
+
from .exceptions import UptimeKumaAuthenticationException, UptimeKumaConnectionException
|
|
17
|
+
from .models import UptimeKumaMonitor
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class UptimeKuma:
|
|
21
|
+
"""Uptime Kuma client."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
session: ClientSession,
|
|
26
|
+
base_url: URL | str,
|
|
27
|
+
api_key: str | None = None,
|
|
28
|
+
timeout: float | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Initialize the Uptime Kuma client."""
|
|
31
|
+
self._base_url = base_url if isinstance(base_url, URL) else URL(base_url)
|
|
32
|
+
|
|
33
|
+
self._auth = BasicAuth("", api_key) if api_key else None
|
|
34
|
+
|
|
35
|
+
self._timeout = ClientTimeout(total=timeout or 10)
|
|
36
|
+
self._session = session
|
|
37
|
+
|
|
38
|
+
async def metrics(self) -> list[UptimeKumaMonitor]:
|
|
39
|
+
"""Retrieve metrics from Uptime Kuma."""
|
|
40
|
+
url = self._base_url / "metrics"
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
request = await self._session.get(
|
|
44
|
+
url, auth=self._auth, timeout=self._timeout
|
|
45
|
+
)
|
|
46
|
+
request.raise_for_status()
|
|
47
|
+
except ClientResponseError as e:
|
|
48
|
+
if e.status is HTTPStatus.UNAUTHORIZED:
|
|
49
|
+
msg = "Authentication failed for %s"
|
|
50
|
+
raise UptimeKumaAuthenticationException(msg, str(url)) from e
|
|
51
|
+
msg = "Request for %s failed with status code %s"
|
|
52
|
+
raise UptimeKumaConnectionException(msg, str(url), e.status) from e
|
|
53
|
+
except TimeoutError as e:
|
|
54
|
+
msg = "Request timeout for %s"
|
|
55
|
+
raise UptimeKumaConnectionException(msg, str(url)) from e
|
|
56
|
+
except ClientError as e:
|
|
57
|
+
raise UptimeKumaConnectionException from e
|
|
58
|
+
else:
|
|
59
|
+
parsed = text_string_to_metric_families(await request.text())
|
|
60
|
+
|
|
61
|
+
monitors: dict[str, dict[str, Any]] = {}
|
|
62
|
+
for metric in parsed:
|
|
63
|
+
if not metric.name.startswith("monitor"):
|
|
64
|
+
continue
|
|
65
|
+
for sample in metric.samples:
|
|
66
|
+
if not (monitor_name := sample.labels.get("monitor_name")):
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
monitors.setdefault(monitor_name, sample.labels).update(
|
|
70
|
+
{sample.name: sample.value}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
key: UptimeKumaMonitor.from_dict(value)
|
|
75
|
+
for key, value in monitors.items()
|
|
76
|
+
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"""Decorator for Uptime Kuma"""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
|
|
7
|
-
import aiohttp
|
|
8
|
-
|
|
9
|
-
from . import exceptions
|
|
10
|
-
from .const import LOGGER
|
|
11
|
-
from .models import UptimeKumaApiResponse
|
|
12
|
-
|
|
13
|
-
if TYPE_CHECKING:
|
|
14
|
-
from .uptimekuma import UptimeKuma
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def api_request(api_path: str, method: str = "GET"):
|
|
18
|
-
"""Decorator for Uptime Kuma API request"""
|
|
19
|
-
|
|
20
|
-
def decorator(func):
|
|
21
|
-
"""Decorator"""
|
|
22
|
-
|
|
23
|
-
async def wrapper(*args, **kwargs):
|
|
24
|
-
"""Wrapper"""
|
|
25
|
-
client: UptimeKuma = args[0]
|
|
26
|
-
url = client._base_url / api_path
|
|
27
|
-
LOGGER.debug("Requesting %s", url.human_repr())
|
|
28
|
-
try:
|
|
29
|
-
request = await client._session.request(
|
|
30
|
-
method=method,
|
|
31
|
-
url=url,
|
|
32
|
-
timeout=aiohttp.ClientTimeout(total=10),
|
|
33
|
-
auth=aiohttp.BasicAuth(client._username, client._password),
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
if request.status != 200:
|
|
37
|
-
raise exceptions.UptimeKumaConnectionException(
|
|
38
|
-
f"Request for '{url}' failed with status code '{request.status}'"
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
result = await request.text()
|
|
42
|
-
except aiohttp.ClientError as exception:
|
|
43
|
-
raise exceptions.UptimeKumaConnectionException(
|
|
44
|
-
f"Request exception for '{url}' with - {exception}"
|
|
45
|
-
) from exception
|
|
46
|
-
|
|
47
|
-
except TimeoutError:
|
|
48
|
-
raise exceptions.UptimeKumaConnectionException(f"Request timeout for '{url}'") from None
|
|
49
|
-
|
|
50
|
-
except exceptions.UptimeKumaConnectionException as exception:
|
|
51
|
-
raise exceptions.UptimeKumaConnectionException(exception) from exception
|
|
52
|
-
|
|
53
|
-
except exceptions.UptimeKumaException as exception:
|
|
54
|
-
raise exceptions.UptimeKumaException(exception) from exception
|
|
55
|
-
|
|
56
|
-
except (Exception, BaseException) as exception:
|
|
57
|
-
raise exceptions.UptimeKumaException(
|
|
58
|
-
f"Unexpected exception for '{url}' with - {exception}"
|
|
59
|
-
) from exception
|
|
60
|
-
|
|
61
|
-
LOGGER.debug("Requesting %s returned %s", url, result)
|
|
62
|
-
|
|
63
|
-
response = UptimeKumaApiResponse.from_prometheus(
|
|
64
|
-
{"monitors": result, "_api_path": api_path, "_method": method}
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
return response
|
|
68
|
-
|
|
69
|
-
return wrapper
|
|
70
|
-
|
|
71
|
-
return decorator
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
"""Uptime Kuma models"""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from enum import StrEnum
|
|
7
|
-
from typing import Any
|
|
8
|
-
|
|
9
|
-
from prometheus_client.parser import text_string_to_metric_families as parser
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class MonitorType(StrEnum):
|
|
13
|
-
"""Monitors type."""
|
|
14
|
-
|
|
15
|
-
HTTP = "http"
|
|
16
|
-
PORT = "port"
|
|
17
|
-
PING = "ping"
|
|
18
|
-
KEYWORD = "keyword"
|
|
19
|
-
DNS = "dns"
|
|
20
|
-
PUSH = "push"
|
|
21
|
-
STEAM = "steam"
|
|
22
|
-
MQTT = "mqtt"
|
|
23
|
-
SQL = "sqlserver"
|
|
24
|
-
JSON_QUERY = "json-query"
|
|
25
|
-
GROUP = "group"
|
|
26
|
-
DOCKER = "docker"
|
|
27
|
-
GRPC_KEYWORD = "grpc-keyword"
|
|
28
|
-
REAL_BROWSER = "real-browser"
|
|
29
|
-
GAMEDIG = "gamedig"
|
|
30
|
-
KAFKA_PRODUCER = "kafka-producer"
|
|
31
|
-
POSTGRES = "postgres"
|
|
32
|
-
MYSQL = "mysql"
|
|
33
|
-
MONGODB = "mongodb"
|
|
34
|
-
RADIUS = "radius"
|
|
35
|
-
REDIS = "redis"
|
|
36
|
-
TAILSCALE_PING = "tailscale-ping"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class UptimeKumaBaseModel:
|
|
40
|
-
"""UptimeKumaBaseModel."""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@dataclass
|
|
44
|
-
class UptimeKumaMonitor(UptimeKumaBaseModel):
|
|
45
|
-
"""Monitor model for Uptime Kuma."""
|
|
46
|
-
|
|
47
|
-
monitor_cert_days_remaining: float = 0
|
|
48
|
-
monitor_cert_is_valid: float = 0
|
|
49
|
-
monitor_hostname: str = ""
|
|
50
|
-
monitor_name: str = ""
|
|
51
|
-
monitor_port: str = ""
|
|
52
|
-
monitor_response_time: float = 0
|
|
53
|
-
monitor_status: float = 0
|
|
54
|
-
monitor_type: MonitorType = MonitorType.HTTP
|
|
55
|
-
monitor_url: str = ""
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def from_dict(data: dict[str, Any]) -> UptimeKumaMonitor:
|
|
59
|
-
"""Generate object from json."""
|
|
60
|
-
obj: dict[str, Any] = {}
|
|
61
|
-
for key, value in data.items():
|
|
62
|
-
if hasattr(UptimeKumaMonitor, key):
|
|
63
|
-
obj[key] = MonitorType(value) if key == "monitor_type" else value
|
|
64
|
-
|
|
65
|
-
return UptimeKumaMonitor(**obj)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
@dataclass
|
|
69
|
-
class UptimeKumaApiResponse(UptimeKumaBaseModel):
|
|
70
|
-
"""API response model for Uptime Kuma."""
|
|
71
|
-
|
|
72
|
-
_method: str | None = None
|
|
73
|
-
_api_path: str | None = None
|
|
74
|
-
data: list[UptimeKumaMonitor] | None = None
|
|
75
|
-
|
|
76
|
-
@staticmethod
|
|
77
|
-
def from_prometheus(data: dict[str, Any]) -> UptimeKumaApiResponse:
|
|
78
|
-
"""Generate object from json."""
|
|
79
|
-
obj: dict[str, Any] = {}
|
|
80
|
-
monitors = []
|
|
81
|
-
|
|
82
|
-
for key, value in data.items():
|
|
83
|
-
if hasattr(UptimeKumaApiResponse, key):
|
|
84
|
-
obj[key] = value
|
|
85
|
-
|
|
86
|
-
parsed = parser(data["monitors"])
|
|
87
|
-
for family in parsed:
|
|
88
|
-
for sample in family.samples:
|
|
89
|
-
if sample.name.startswith("monitor"):
|
|
90
|
-
existed = next(
|
|
91
|
-
(i for i, x in enumerate(monitors) if x["monitor_name"] == sample.labels["monitor_name"]),
|
|
92
|
-
None,
|
|
93
|
-
)
|
|
94
|
-
if existed is None:
|
|
95
|
-
temp = {**sample.labels, sample.name: sample.value}
|
|
96
|
-
monitors.append(temp)
|
|
97
|
-
else:
|
|
98
|
-
monitors[existed][sample.name] = sample.value
|
|
99
|
-
obj["data"] = [UptimeKumaMonitor.from_dict(monitor) for monitor in monitors]
|
|
100
|
-
|
|
101
|
-
return UptimeKumaApiResponse(**obj)
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""Uptime Kuma client."""
|
|
2
|
-
|
|
3
|
-
from aiohttp import ClientSession
|
|
4
|
-
from yarl import URL
|
|
5
|
-
|
|
6
|
-
from .decorator import api_request
|
|
7
|
-
from .models import UptimeKumaApiResponse
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class UptimeKuma:
|
|
11
|
-
"""This class is used to get information from Uptime Kuma."""
|
|
12
|
-
|
|
13
|
-
def __init__(self, session: ClientSession, base_url: URL | str, username: str, password: str) -> None:
|
|
14
|
-
"""Initialize"""
|
|
15
|
-
self.monitors = []
|
|
16
|
-
self._base_url = base_url if isinstance(base_url, URL) else URL(base_url)
|
|
17
|
-
self._username = username
|
|
18
|
-
self._password = password
|
|
19
|
-
self._session: ClientSession = session
|
|
20
|
-
|
|
21
|
-
@api_request("metrics")
|
|
22
|
-
async def async_get_monitors(self, **kwargs) -> UptimeKumaApiResponse:
|
|
23
|
-
"""Get monitors from API."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|