uptimer-python-sdk 0.3.0__py3-none-any.whl → 1.5.0__py3-none-any.whl
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.
- uptimer/__init__.py +11 -2
- uptimer/client.py +47 -4
- uptimer/compat.py +58 -0
- uptimer/endpoints/incidents.py +37 -0
- uptimer/endpoints/{regions.py → locations.py} +7 -6
- uptimer/endpoints/v2.py +38 -0
- uptimer/endpoints/websites.py +116 -0
- uptimer/endpoints/workspaces.py +4 -3
- uptimer/errors.py +18 -0
- uptimer/http.py +12 -1
- uptimer/models/__init__.py +12 -30
- uptimer/models/v2/__init__.py +70 -0
- uptimer/models/v2/deserialize.py +119 -0
- uptimer/models/v2/incident.py +39 -0
- uptimer/models/v2/location.py +16 -0
- uptimer/models/v2/monitor.py +88 -0
- uptimer_python_sdk-1.5.0.dist-info/METADATA +287 -0
- uptimer_python_sdk-1.5.0.dist-info/RECORD +26 -0
- {uptimer_python_sdk-0.3.0.dist-info → uptimer_python_sdk-1.5.0.dist-info}/WHEEL +1 -1
- uptimer/endpoints/rules.py +0 -75
- uptimer/endpoints/v1.py +0 -17
- uptimer/models/deserialize.py +0 -121
- uptimer/models/region.py +0 -9
- uptimer/models/rule.py +0 -57
- uptimer_python_sdk-0.3.0.dist-info/METADATA +0 -203
- uptimer_python_sdk-0.3.0.dist-info/RECORD +0 -22
- /uptimer/models/{workspace.py → v2/workspace.py} +0 -0
- {uptimer_python_sdk-0.3.0.dist-info → uptimer_python_sdk-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {uptimer_python_sdk-0.3.0.dist-info → uptimer_python_sdk-1.5.0.dist-info}/licenses/NOTICE +0 -0
uptimer/__init__.py
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Uptimer Python SDK.
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
Targets Uptimer API v2 only. Code written against 0.4.x keeps working against
|
|
5
|
+
the server — API v1 is unchanged and supported — but must stay on the 0.4.x SDK.
|
|
6
|
+
|
|
7
|
+
The version tracks the uptimer release this SDK targets: 1.5.x speaks to
|
|
8
|
+
uptimer 1.5.0 and later. Patch numbers are independent, so an SDK fix can ship
|
|
9
|
+
without a server release. See product Decision 0013.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "1.5.0"
|
uptimer/client.py
CHANGED
|
@@ -2,25 +2,68 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import cast
|
|
4
4
|
|
|
5
|
-
from uptimer.
|
|
5
|
+
from uptimer.compat import ensure_v2_supported
|
|
6
|
+
from uptimer.endpoints.v2 import V2Endpoint
|
|
6
7
|
from uptimer.http import UptimerHttpLib
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class UptimerClient:
|
|
10
|
-
|
|
11
|
+
"""
|
|
12
|
+
The Uptimer API client.
|
|
13
|
+
|
|
14
|
+
Resources are reached through the API version that serves them:
|
|
15
|
+
`client.v2.workspaces`, `client.v2.locations`, `client.v2.incidents` and
|
|
16
|
+
`client.v2.monitoring.websites`. This SDK does not speak API v1 — see the
|
|
17
|
+
migration note in the README if you are coming from 0.4.x.
|
|
18
|
+
|
|
19
|
+
`version()` and the compatibility helpers stay here rather than under a
|
|
20
|
+
version namespace, because `/version` is shared and unversioned.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
v2: V2Endpoint
|
|
11
24
|
|
|
12
25
|
def __init__(self, api_key: str, base_url: str):
|
|
13
26
|
self._http_lib = UptimerHttpLib(api_key, base_url)
|
|
14
|
-
self.
|
|
27
|
+
self._checked_compat = False
|
|
28
|
+
self._wire()
|
|
29
|
+
|
|
30
|
+
def _wire(self) -> None:
|
|
31
|
+
self.v2 = V2Endpoint(self._http_lib)
|
|
15
32
|
|
|
16
33
|
def version(self) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Return the server version.
|
|
36
|
+
|
|
37
|
+
`/version` is a shared global endpoint, not a versioned one, so this
|
|
38
|
+
works against any server — including one too old for the rest of this
|
|
39
|
+
SDK.
|
|
40
|
+
"""
|
|
17
41
|
response = self._http_lib.client.get(self._http_lib.build_url("version"))
|
|
18
42
|
return cast("str", self._http_lib.parse_response(response=response))
|
|
19
43
|
|
|
44
|
+
def check_compatibility(self) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Verify the server provides API v2, and return its version.
|
|
47
|
+
|
|
48
|
+
Raises IncompatibleServerError if it does not. Called once per client
|
|
49
|
+
by ensure_compatible(); call it directly to fail fast at startup.
|
|
50
|
+
"""
|
|
51
|
+
version = self.version()
|
|
52
|
+
ensure_v2_supported(version)
|
|
53
|
+
self._checked_compat = True
|
|
54
|
+
return version
|
|
55
|
+
|
|
56
|
+
def ensure_compatible(self) -> None:
|
|
57
|
+
"""Run the compatibility check once, then never again."""
|
|
58
|
+
if not self._checked_compat:
|
|
59
|
+
self.check_compatibility()
|
|
60
|
+
|
|
20
61
|
def set_uptimer_http_lib(self, http_lib: UptimerHttpLib) -> None:
|
|
21
62
|
self._http_lib = http_lib
|
|
63
|
+
self._checked_compat = False
|
|
64
|
+
self._wire()
|
|
22
65
|
|
|
23
66
|
|
|
24
67
|
class UptimerCloudClient(UptimerClient):
|
|
25
68
|
def __init__(self, api_key: str):
|
|
26
|
-
super().__init__(api_key, "https://
|
|
69
|
+
super().__init__(api_key, "https://myuptime.info/api")
|
uptimer/compat.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Server compatibility.
|
|
3
|
+
|
|
4
|
+
This SDK targets API v2 only. A server that predates v2 answers /v2 with 404,
|
|
5
|
+
which on its own tells a user nothing — so the version is checked first and the
|
|
6
|
+
404 is translated as a fallback.
|
|
7
|
+
|
|
8
|
+
Why both: `uptimer` and `myuptime.info` version independently over the same
|
|
9
|
+
shared API (1.5.x and 15.x). A numeric minimum catches the self-hosted case
|
|
10
|
+
cleanly, but cannot express "hosted 14.0.3 has no v2" — 14 is greater than 1.
|
|
11
|
+
The 404 path catches what the number cannot.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
|
|
18
|
+
from uptimer import __version__
|
|
19
|
+
from uptimer.errors import IncompatibleServerError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _minimum_from_own_version() -> tuple[int, int, int]:
|
|
23
|
+
"""
|
|
24
|
+
Return the oldest server version this SDK speaks to.
|
|
25
|
+
|
|
26
|
+
The SDK's major.minor tracks the uptimer release it targets (Decision
|
|
27
|
+
0013), so 1.5.x requires a 1.5.0 server. Deriving it here rather than
|
|
28
|
+
writing the number down twice means the two cannot drift apart.
|
|
29
|
+
"""
|
|
30
|
+
parts = __version__.split(".")
|
|
31
|
+
return (int(parts[0]), int(parts[1]), 0)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
MINIMUM_UPTIMER_VERSION = _minimum_from_own_version()
|
|
35
|
+
|
|
36
|
+
_VERSION_RE = re.compile(r"^v?(\d+)\.(\d+)\.(\d+)")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_version(version: str) -> tuple[int, int, int] | None:
|
|
40
|
+
"""
|
|
41
|
+
Parse a server version, or None if it is not a release number.
|
|
42
|
+
|
|
43
|
+
"dev" and anything unparseable return None and are treated as usable: a
|
|
44
|
+
developer running from source must not be locked out by a version string.
|
|
45
|
+
"""
|
|
46
|
+
match = _VERSION_RE.match(version.strip())
|
|
47
|
+
if not match:
|
|
48
|
+
return None
|
|
49
|
+
return (int(match.group(1)), int(match.group(2)), int(match.group(3)))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def ensure_v2_supported(version: str) -> None:
|
|
53
|
+
"""Raise IncompatibleServerError if this server is too old for API v2."""
|
|
54
|
+
parsed = parse_version(version)
|
|
55
|
+
if parsed is None:
|
|
56
|
+
return
|
|
57
|
+
if parsed < MINIMUM_UPTIMER_VERSION:
|
|
58
|
+
raise IncompatibleServerError(version)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from uptimer.endpoints.endpoint import BaseEndpoint
|
|
6
|
+
from uptimer.models.v2 import from_api_incident
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from uptimer.http import UptimerHttpLib
|
|
10
|
+
from uptimer.models.v2 import Incident
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IncidentsEndpoint(BaseEndpoint):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
http: UptimerHttpLib,
|
|
17
|
+
parent_segments: str | list[str] | None = None,
|
|
18
|
+
):
|
|
19
|
+
super().__init__(http, "incidents", parent_segments)
|
|
20
|
+
|
|
21
|
+
def all(
|
|
22
|
+
self,
|
|
23
|
+
workspace_id: str,
|
|
24
|
+
monitor_id: str | None = None,
|
|
25
|
+
) -> list[Incident]:
|
|
26
|
+
"""
|
|
27
|
+
Open incidents in a workspace, newest trouble first.
|
|
28
|
+
|
|
29
|
+
Only open ones: this answers "what is wrong now". Pass monitor_id to
|
|
30
|
+
narrow it to a single monitor.
|
|
31
|
+
"""
|
|
32
|
+
params = {"workspace_id": workspace_id}
|
|
33
|
+
if monitor_id:
|
|
34
|
+
params["monitor_id"] = monitor_id
|
|
35
|
+
response = self.http.client.get(self.url, params=params)
|
|
36
|
+
result = self.http.parse_response(response=response)
|
|
37
|
+
return [from_api_incident(item) for item in result]
|
|
@@ -3,22 +3,23 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from uptimer.endpoints.endpoint import BaseEndpoint
|
|
6
|
-
from uptimer.models import
|
|
6
|
+
from uptimer.models.v2 import from_api_location
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from uptimer.http import UptimerHttpLib
|
|
10
|
-
from uptimer.models.
|
|
10
|
+
from uptimer.models.v2 import Location
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
class
|
|
13
|
+
class LocationsEndpoint(BaseEndpoint):
|
|
14
14
|
def __init__(
|
|
15
15
|
self,
|
|
16
16
|
http: UptimerHttpLib,
|
|
17
17
|
parent_segments: str | list[str] | None = None,
|
|
18
18
|
):
|
|
19
|
-
super().__init__(http, "
|
|
19
|
+
super().__init__(http, "locations", parent_segments)
|
|
20
20
|
|
|
21
|
-
def all(self) -> list[
|
|
21
|
+
def all(self) -> list[Location]:
|
|
22
|
+
"""Every location checks can run from."""
|
|
22
23
|
response = self.http.client.get(self.url)
|
|
23
24
|
result = self.http.parse_response(response=response)
|
|
24
|
-
return [
|
|
25
|
+
return [from_api_location(item) for item in result]
|
uptimer/endpoints/v2.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from uptimer.endpoints.endpoint import BaseEndpoint
|
|
6
|
+
from uptimer.endpoints.incidents import IncidentsEndpoint
|
|
7
|
+
from uptimer.endpoints.locations import LocationsEndpoint
|
|
8
|
+
from uptimer.endpoints.websites import MonitoringEndpoint
|
|
9
|
+
from uptimer.endpoints.workspaces import WorkspacesEndpoint
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from uptimer.http import UptimerHttpLib
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class V2Endpoint(BaseEndpoint):
|
|
16
|
+
"""
|
|
17
|
+
API v2, the whole of it: `client.v2`.
|
|
18
|
+
|
|
19
|
+
The API is versioned by path, so the SDK keeps that version visible rather
|
|
20
|
+
than hiding it behind bare attributes (Decision 0012). Every resource here
|
|
21
|
+
builds its URL from this namespace's segment, which is why `/v2` appears in
|
|
22
|
+
exactly one place.
|
|
23
|
+
|
|
24
|
+
`/version` is shared and unversioned, so it stays on the client itself.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
workspaces: WorkspacesEndpoint
|
|
28
|
+
locations: LocationsEndpoint
|
|
29
|
+
incidents: IncidentsEndpoint
|
|
30
|
+
monitoring: MonitoringEndpoint
|
|
31
|
+
|
|
32
|
+
def __init__(self, http: UptimerHttpLib):
|
|
33
|
+
super().__init__(http, "v2")
|
|
34
|
+
parent = [self.segment]
|
|
35
|
+
self.workspaces = WorkspacesEndpoint(http, parent)
|
|
36
|
+
self.locations = LocationsEndpoint(http, parent)
|
|
37
|
+
self.incidents = IncidentsEndpoint(http, parent)
|
|
38
|
+
self.monitoring = MonitoringEndpoint(http, parent)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from uptimer.endpoints.endpoint import BaseEndpoint
|
|
7
|
+
from uptimer.models.v2 import DeleteWebsiteMonitorResponse, from_api_website_monitor
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from uptimer.http import UptimerHttpLib
|
|
11
|
+
from uptimer.models.v2 import (
|
|
12
|
+
CreateWebsiteMonitorRequest,
|
|
13
|
+
UpdateWebsiteMonitorRequest,
|
|
14
|
+
WebsiteMonitor,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _strip_kinds(value: object) -> object:
|
|
19
|
+
"""
|
|
20
|
+
Drop every `kind` from a request body, at any depth.
|
|
21
|
+
|
|
22
|
+
`kind` is what the server tells a client an object is; it is not something a
|
|
23
|
+
client sets. Echoing it back suggests otherwise, and the server ignores it
|
|
24
|
+
either way.
|
|
25
|
+
"""
|
|
26
|
+
if isinstance(value, dict):
|
|
27
|
+
return {k: _strip_kinds(v) for k, v in value.items() if k != "kind"}
|
|
28
|
+
if isinstance(value, list):
|
|
29
|
+
return [_strip_kinds(v) for v in value]
|
|
30
|
+
return value
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _payload(data: object) -> dict:
|
|
34
|
+
"""
|
|
35
|
+
Serialize a request dataclass, dropping fields the server fills in.
|
|
36
|
+
|
|
37
|
+
An empty agreement means "leave it alone", which the server expresses by
|
|
38
|
+
omission, so it is stripped rather than sent as "" — those are different
|
|
39
|
+
requests.
|
|
40
|
+
"""
|
|
41
|
+
body = asdict(data) # type: ignore[call-overload]
|
|
42
|
+
body.pop("id", None)
|
|
43
|
+
if not body.get("agreement"):
|
|
44
|
+
body.pop("agreement", None)
|
|
45
|
+
return _strip_kinds(body) # type: ignore[return-value]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class WebsitesEndpoint(BaseEndpoint):
|
|
49
|
+
"""
|
|
50
|
+
Website monitoring: the built-in template that watches a URL.
|
|
51
|
+
|
|
52
|
+
Nested under /v2/monitoring because it is one template among the several
|
|
53
|
+
coming later, not the general monitor model.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
http: UptimerHttpLib,
|
|
59
|
+
parent_segments: str | list[str] | None = None,
|
|
60
|
+
):
|
|
61
|
+
super().__init__(http, "websites", parent_segments)
|
|
62
|
+
|
|
63
|
+
def all(self, workspace_id: str) -> list[WebsiteMonitor]:
|
|
64
|
+
"""Every website monitor in a workspace."""
|
|
65
|
+
params = {"workspace_id": workspace_id}
|
|
66
|
+
response = self.http.client.get(self.url, params=params)
|
|
67
|
+
result = self.http.parse_response(response=response)
|
|
68
|
+
return [from_api_website_monitor(item) for item in result]
|
|
69
|
+
|
|
70
|
+
def get(self, monitor_id: str) -> WebsiteMonitor:
|
|
71
|
+
"""One website monitor by id."""
|
|
72
|
+
response = self.http.client.get(f"{self.url}/{monitor_id}")
|
|
73
|
+
result = self.http.parse_response(response=response)
|
|
74
|
+
return from_api_website_monitor(result)
|
|
75
|
+
|
|
76
|
+
def create(self, monitor: CreateWebsiteMonitorRequest) -> WebsiteMonitor:
|
|
77
|
+
"""Create a website monitor, with its signal and rule."""
|
|
78
|
+
response = self.http.client.post(self.url, json=_payload(monitor))
|
|
79
|
+
result = self.http.parse_response(response=response)
|
|
80
|
+
return from_api_website_monitor(result)
|
|
81
|
+
|
|
82
|
+
def update(
|
|
83
|
+
self,
|
|
84
|
+
monitor_id: str,
|
|
85
|
+
monitor: UpdateWebsiteMonitorRequest,
|
|
86
|
+
) -> WebsiteMonitor:
|
|
87
|
+
"""Replace a website monitor's configuration."""
|
|
88
|
+
response = self.http.client.post(
|
|
89
|
+
f"{self.url}/{monitor_id}",
|
|
90
|
+
json=_payload(monitor),
|
|
91
|
+
)
|
|
92
|
+
result = self.http.parse_response(response=response)
|
|
93
|
+
return from_api_website_monitor(result)
|
|
94
|
+
|
|
95
|
+
def delete(self, monitor_id: str) -> DeleteWebsiteMonitorResponse:
|
|
96
|
+
"""Delete a website monitor and everything under it."""
|
|
97
|
+
response = self.http.client.delete(f"{self.url}/{monitor_id}")
|
|
98
|
+
result = self.http.parse_response(response=response)
|
|
99
|
+
return DeleteWebsiteMonitorResponse(
|
|
100
|
+
message=result["message"],
|
|
101
|
+
monitor_id=result["monitor_id"],
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class MonitoringEndpoint(BaseEndpoint):
|
|
106
|
+
"""The monitoring templates. Today there is one: websites."""
|
|
107
|
+
|
|
108
|
+
websites: WebsitesEndpoint
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
http: UptimerHttpLib,
|
|
113
|
+
parent_segments: str | list[str] | None = None,
|
|
114
|
+
):
|
|
115
|
+
super().__init__(http, "monitoring", parent_segments)
|
|
116
|
+
self.websites = WebsitesEndpoint(http, [*self._parent_segments, "monitoring"])
|
uptimer/endpoints/workspaces.py
CHANGED
|
@@ -3,11 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from uptimer.endpoints.endpoint import BaseEndpoint
|
|
6
|
-
from uptimer.models import from_api_workspace
|
|
6
|
+
from uptimer.models.v2 import from_api_workspace
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from uptimer.http import UptimerHttpLib
|
|
10
|
-
from uptimer.models.
|
|
10
|
+
from uptimer.models.v2 import Workspace
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class WorkspacesEndpoint(BaseEndpoint):
|
|
@@ -19,6 +19,7 @@ class WorkspacesEndpoint(BaseEndpoint):
|
|
|
19
19
|
super().__init__(http, "workspaces", parent_segments)
|
|
20
20
|
|
|
21
21
|
def all(self) -> list[Workspace]:
|
|
22
|
+
"""Every workspace this API key can reach."""
|
|
22
23
|
response = self.http.client.get(self.url)
|
|
23
24
|
result = self.http.parse_response(response=response)
|
|
24
|
-
return [from_api_workspace(
|
|
25
|
+
return [from_api_workspace(item) for item in result]
|
uptimer/errors.py
CHANGED
|
@@ -23,3 +23,21 @@ class DefaultUptimerApiError(UptimerError):
|
|
|
23
23
|
self.message = error.get("message", "")
|
|
24
24
|
self.details = error.get("details", "")
|
|
25
25
|
super().__init__(f"API error: {self.code} {self.message}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class IncompatibleServerError(UptimerError):
|
|
29
|
+
"""
|
|
30
|
+
The server does not provide API v2, which this SDK requires.
|
|
31
|
+
|
|
32
|
+
Carries the server's own version so the message names the situation rather
|
|
33
|
+
than surfacing a bare 404.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, server_version: str):
|
|
37
|
+
self.server_version = server_version
|
|
38
|
+
super().__init__(
|
|
39
|
+
f"This server reports version {server_version}, which does not "
|
|
40
|
+
"provide API v2. uptimer-python-sdk 1.5.x requires API v2 "
|
|
41
|
+
"(uptimer 1.5.0+ or myuptime.info 15.1.0+). For API v1, use "
|
|
42
|
+
"uptimer-python-sdk 0.4.x.",
|
|
43
|
+
)
|
uptimer/http.py
CHANGED
|
@@ -5,7 +5,11 @@ from typing import Any
|
|
|
5
5
|
import httpx
|
|
6
6
|
|
|
7
7
|
from uptimer.endpoints.endpoint import BaseEndpoint
|
|
8
|
-
from uptimer.errors import
|
|
8
|
+
from uptimer.errors import (
|
|
9
|
+
DefaultUptimerApiError,
|
|
10
|
+
IncompatibleServerError,
|
|
11
|
+
UptimerInvalidHttpCodeError,
|
|
12
|
+
)
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class WorkspacesEndpoint(BaseEndpoint):
|
|
@@ -38,6 +42,13 @@ class UptimerHttpLib:
|
|
|
38
42
|
def parse_response(
|
|
39
43
|
response: httpx.Response,
|
|
40
44
|
) -> Any: # noqa: ANN401
|
|
45
|
+
if response.status_code == 404 and "/v2/" in str(response.request.url):
|
|
46
|
+
# A server without API v2 has no /v2 routes at all. Saying "404"
|
|
47
|
+
# here would leave the user guessing; the version gate in
|
|
48
|
+
# client.check_compatibility() catches this earlier when the
|
|
49
|
+
# version number can express it, and this catches the rest.
|
|
50
|
+
unknown = "unknown (no /v2 routes)"
|
|
51
|
+
raise IncompatibleServerError(unknown)
|
|
41
52
|
if response.status_code != 200:
|
|
42
53
|
raise UptimerInvalidHttpCodeError(
|
|
43
54
|
response.request.url,
|
uptimer/models/__init__.py
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
"""
|
|
2
|
+
Model exceptions, and the versioned model namespaces beneath this one.
|
|
3
|
+
|
|
4
|
+
**No API type is exported here.** v2's types live in `uptimer.models.v2`:
|
|
5
|
+
|
|
6
|
+
from uptimer.models.v2 import CreateWebsiteMonitorRequest, Location
|
|
7
|
+
|
|
8
|
+
Only the deserialization exceptions sit at this level, because they are
|
|
9
|
+
version-independent — the same `TypeMismatchError` is raised whichever API
|
|
10
|
+
version produced the payload.
|
|
11
|
+
"""
|
|
12
|
+
|
|
7
13
|
from .errors import (
|
|
8
14
|
DeserializationError,
|
|
9
15
|
InvalidDataTypeError,
|
|
@@ -12,36 +18,12 @@ from .errors import (
|
|
|
12
18
|
TypeMismatchError,
|
|
13
19
|
UnknownKindError,
|
|
14
20
|
)
|
|
15
|
-
from .region import Region
|
|
16
|
-
from .rule import (
|
|
17
|
-
BaseRule,
|
|
18
|
-
CreateRuleRequest,
|
|
19
|
-
DeleteRuleResponse,
|
|
20
|
-
Rule,
|
|
21
|
-
RuleRequest,
|
|
22
|
-
RuleResponse,
|
|
23
|
-
RuleResponseBody,
|
|
24
|
-
)
|
|
25
|
-
from .workspace import Workspace
|
|
26
21
|
|
|
27
22
|
__all__ = [
|
|
28
|
-
"BaseRule",
|
|
29
|
-
"CreateRuleRequest",
|
|
30
|
-
"DeleteRuleResponse",
|
|
31
23
|
"DeserializationError",
|
|
32
24
|
"InvalidDataTypeError",
|
|
33
25
|
"MissingKindError",
|
|
34
26
|
"ModelError",
|
|
35
|
-
"Region",
|
|
36
|
-
"Rule",
|
|
37
|
-
"RuleRequest",
|
|
38
|
-
"RuleResponse",
|
|
39
|
-
"RuleResponseBody",
|
|
40
27
|
"TypeMismatchError",
|
|
41
28
|
"UnknownKindError",
|
|
42
|
-
"Workspace",
|
|
43
|
-
"from_api",
|
|
44
|
-
"from_api_region",
|
|
45
|
-
"from_api_rule",
|
|
46
|
-
"from_api_workspace",
|
|
47
29
|
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Types for API v2.
|
|
3
|
+
|
|
4
|
+
The API is versioned by path, so its types are versioned too: import them from
|
|
5
|
+
`uptimer.models.v2`, the way resources are reached through `client.v2`. Nothing
|
|
6
|
+
here is re-exported from `uptimer.models` — a v2 name is only ever a v2 import.
|
|
7
|
+
|
|
8
|
+
Version-independent deserialization exceptions live in `uptimer.models.errors`,
|
|
9
|
+
because they say nothing about which API version raised them.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .deserialize import (
|
|
13
|
+
from_api,
|
|
14
|
+
from_api_incident,
|
|
15
|
+
from_api_location,
|
|
16
|
+
from_api_website_monitor,
|
|
17
|
+
from_api_workspace,
|
|
18
|
+
)
|
|
19
|
+
from .incident import (
|
|
20
|
+
STATUS_NO_DATA,
|
|
21
|
+
STATUS_OK,
|
|
22
|
+
STATUS_PENDING,
|
|
23
|
+
STATUS_PROBLEM,
|
|
24
|
+
STATUS_RECOVERING,
|
|
25
|
+
Incident,
|
|
26
|
+
IncidentLocations,
|
|
27
|
+
)
|
|
28
|
+
from .location import Location
|
|
29
|
+
from .monitor import (
|
|
30
|
+
AGREEMENT_ALL,
|
|
31
|
+
AGREEMENT_ANY,
|
|
32
|
+
AGREEMENT_MAJORITY,
|
|
33
|
+
BaseWebsiteMonitor,
|
|
34
|
+
CreateWebsiteMonitorRequest,
|
|
35
|
+
DeleteWebsiteMonitorResponse,
|
|
36
|
+
UpdateWebsiteMonitorRequest,
|
|
37
|
+
WebsiteMonitor,
|
|
38
|
+
WebsiteMonitorRequest,
|
|
39
|
+
WebsiteMonitorResponse,
|
|
40
|
+
WebsiteMonitorResponseBody,
|
|
41
|
+
)
|
|
42
|
+
from .workspace import Workspace
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"AGREEMENT_ALL",
|
|
46
|
+
"AGREEMENT_ANY",
|
|
47
|
+
"AGREEMENT_MAJORITY",
|
|
48
|
+
"STATUS_NO_DATA",
|
|
49
|
+
"STATUS_OK",
|
|
50
|
+
"STATUS_PENDING",
|
|
51
|
+
"STATUS_PROBLEM",
|
|
52
|
+
"STATUS_RECOVERING",
|
|
53
|
+
"BaseWebsiteMonitor",
|
|
54
|
+
"CreateWebsiteMonitorRequest",
|
|
55
|
+
"DeleteWebsiteMonitorResponse",
|
|
56
|
+
"Incident",
|
|
57
|
+
"IncidentLocations",
|
|
58
|
+
"Location",
|
|
59
|
+
"UpdateWebsiteMonitorRequest",
|
|
60
|
+
"WebsiteMonitor",
|
|
61
|
+
"WebsiteMonitorRequest",
|
|
62
|
+
"WebsiteMonitorResponse",
|
|
63
|
+
"WebsiteMonitorResponseBody",
|
|
64
|
+
"Workspace",
|
|
65
|
+
"from_api",
|
|
66
|
+
"from_api_incident",
|
|
67
|
+
"from_api_location",
|
|
68
|
+
"from_api_website_monitor",
|
|
69
|
+
"from_api_workspace",
|
|
70
|
+
]
|