uptimer-python-sdk 1.7.0__py3-none-any.whl → 1.8.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 +3 -3
- uptimer/endpoints/delivery.py +84 -0
- uptimer/endpoints/notifications.py +414 -0
- uptimer/endpoints/subjects.py +227 -21
- uptimer/endpoints/v2.py +3 -0
- uptimer/endpoints/websites.py +40 -0
- uptimer/models/v2/__init__.py +120 -0
- uptimer/models/v2/deserialize.py +113 -0
- uptimer/models/v2/notifications.py +332 -0
- uptimer/models/v2/rule.py +222 -0
- uptimer/models/v2/signal.py +90 -0
- uptimer/models/v2/subject.py +4 -3
- {uptimer_python_sdk-1.7.0.dist-info → uptimer_python_sdk-1.8.0.dist-info}/METADATA +300 -10
- {uptimer_python_sdk-1.7.0.dist-info → uptimer_python_sdk-1.8.0.dist-info}/RECORD +17 -12
- {uptimer_python_sdk-1.7.0.dist-info → uptimer_python_sdk-1.8.0.dist-info}/WHEEL +0 -0
- {uptimer_python_sdk-1.7.0.dist-info → uptimer_python_sdk-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {uptimer_python_sdk-1.7.0.dist-info → uptimer_python_sdk-1.8.0.dist-info}/licenses/NOTICE +0 -0
uptimer/__init__.py
CHANGED
|
@@ -4,9 +4,9 @@ Uptimer Python SDK.
|
|
|
4
4
|
Targets Uptimer API v2 only. Code written against 0.4.x keeps working against
|
|
5
5
|
the server — API v1 is unchanged and supported — but must stay on the 0.4.x SDK.
|
|
6
6
|
|
|
7
|
-
The version tracks the uptimer release this SDK targets: 1.
|
|
8
|
-
uptimer 1.
|
|
7
|
+
The version tracks the uptimer release this SDK targets: 1.8.x speaks to
|
|
8
|
+
uptimer 1.8.0 and later. Patch numbers are independent, so an SDK fix can ship
|
|
9
9
|
without a server release. See product Decision 0013.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
__version__ = "1.
|
|
12
|
+
__version__ = "1.8.0"
|
|
@@ -0,0 +1,84 @@
|
|
|
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_subject_delivery
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from uptimer.http import UptimerHttpLib
|
|
10
|
+
from uptimer.models.v2 import DeliverySelection, SubjectAlertDelivery
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AlertDeliveryEndpoint(BaseEndpoint):
|
|
14
|
+
"""
|
|
15
|
+
Which destinations ONE subject tells, and about what (uptimer 1.8.0).
|
|
16
|
+
|
|
17
|
+
The choice lives on the subject rather than on the workspace, and that is
|
|
18
|
+
the whole point of it: the marketing site telling nobody must not stop the
|
|
19
|
+
payments API paging the on-call.
|
|
20
|
+
|
|
21
|
+
Three operations: read the table, replace it, clear it. There is no "add one
|
|
22
|
+
row" — the resource IS the table, so a save says what the subject will have.
|
|
23
|
+
A merge would make a removed row reappear, which is the bug an operator
|
|
24
|
+
reports as "it keeps notifying the channel I deleted".
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
http: UptimerHttpLib,
|
|
30
|
+
parent_segments: str | list[str] | None = None,
|
|
31
|
+
workspace_id: str | None = None,
|
|
32
|
+
):
|
|
33
|
+
super().__init__(http, "delivery", parent_segments)
|
|
34
|
+
self._workspace_id = workspace_id
|
|
35
|
+
|
|
36
|
+
def _params(self) -> dict | None:
|
|
37
|
+
return {"workspace_id": self._workspace_id} if self._workspace_id else None
|
|
38
|
+
|
|
39
|
+
def get(self) -> SubjectAlertDelivery:
|
|
40
|
+
"""
|
|
41
|
+
Return the whole table, and what an empty one means right now.
|
|
42
|
+
|
|
43
|
+
`fallback` reads "workspace_default" or "silence", so a caller knows
|
|
44
|
+
what happens next without modelling the fallback itself.
|
|
45
|
+
"""
|
|
46
|
+
response = self.http.client.get(self.url, params=self._params())
|
|
47
|
+
result = self.http.parse_response(response=response)
|
|
48
|
+
return from_api_subject_delivery(result)
|
|
49
|
+
|
|
50
|
+
def replace(self, selections: list[DeliverySelection]) -> SubjectAlertDelivery:
|
|
51
|
+
"""
|
|
52
|
+
Replace the table with these rows.
|
|
53
|
+
|
|
54
|
+
Each row needs at least one alert kind — problem, no_data, recovery — a
|
|
55
|
+
destination of this workspace, and no duplicate destination. An empty
|
|
56
|
+
list is a real instruction ("use the workspace default, or send nothing
|
|
57
|
+
if there is none") and does the same as `clear()`.
|
|
58
|
+
|
|
59
|
+
Saving changes delivery ONLY: signals, rules, incidents, acknowledgement
|
|
60
|
+
and maintenance are untouched, and nothing is sent by saving.
|
|
61
|
+
"""
|
|
62
|
+
body = {
|
|
63
|
+
"selections": [
|
|
64
|
+
{
|
|
65
|
+
"destination_id": row.destination_id,
|
|
66
|
+
"alert_kinds": list(row.alert_kinds),
|
|
67
|
+
}
|
|
68
|
+
for row in selections
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
response = self.http.client.post(self.url, params=self._params(), json=body)
|
|
72
|
+
result = self.http.parse_response(response=response)
|
|
73
|
+
return from_api_subject_delivery(result)
|
|
74
|
+
|
|
75
|
+
def clear(self) -> SubjectAlertDelivery:
|
|
76
|
+
"""
|
|
77
|
+
Empty the table.
|
|
78
|
+
|
|
79
|
+
The workspace default then speaks for this subject — or, with no
|
|
80
|
+
default, nothing does.
|
|
81
|
+
"""
|
|
82
|
+
response = self.http.client.delete(self.url, params=self._params())
|
|
83
|
+
result = self.http.parse_response(response=response)
|
|
84
|
+
return from_api_subject_delivery(result)
|
|
@@ -0,0 +1,414 @@
|
|
|
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 (
|
|
8
|
+
DeleteDestinationResponse,
|
|
9
|
+
DeleteTransformationResponse,
|
|
10
|
+
TestDeliveryResponse,
|
|
11
|
+
from_api_delivery_record,
|
|
12
|
+
from_api_destination,
|
|
13
|
+
from_api_transformation,
|
|
14
|
+
from_api_transformation_preview,
|
|
15
|
+
samples_from_api,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from uptimer.http import UptimerHttpLib
|
|
20
|
+
from uptimer.models.v2 import (
|
|
21
|
+
CreateDestinationRequest,
|
|
22
|
+
CreateTransformationRequest,
|
|
23
|
+
DeliveryRecord,
|
|
24
|
+
Destination,
|
|
25
|
+
Transformation,
|
|
26
|
+
TransformationPreview,
|
|
27
|
+
TransformationSample,
|
|
28
|
+
UpdateDestinationRequest,
|
|
29
|
+
UpdateTransformationRequest,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _params(workspace_id: str | None, **extra: str | int | None) -> dict | None:
|
|
34
|
+
"""
|
|
35
|
+
Build the query for a notifications call.
|
|
36
|
+
|
|
37
|
+
`workspace_id` is optional and settles an ambiguity rather than being
|
|
38
|
+
required: these resources have no slug of their own, so the server searches
|
|
39
|
+
your memberships when it is absent and says so if the answer is more than
|
|
40
|
+
one.
|
|
41
|
+
"""
|
|
42
|
+
params: dict[str, str | int] = {"workspace_id": workspace_id} if workspace_id else {}
|
|
43
|
+
params.update({key: value for key, value in extra.items() if value is not None})
|
|
44
|
+
return params or None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DestinationsEndpoint(BaseEndpoint):
|
|
48
|
+
"""
|
|
49
|
+
The workspace's alert destinations (uptimer 1.8.0).
|
|
50
|
+
|
|
51
|
+
A destination is one place alerts can go: a Slack incoming webhook or any
|
|
52
|
+
HTTP endpoint. Which SUBJECT sends to which of them is decided elsewhere —
|
|
53
|
+
`client.v2.subjects(slug).delivery` — because that choice belongs to the
|
|
54
|
+
subject, not to the workspace.
|
|
55
|
+
|
|
56
|
+
Reading these needs the EDITOR role, not just membership: a destination
|
|
57
|
+
holds a webhook URL, and a URL is enough for anyone holding it to post into
|
|
58
|
+
your channel.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
http: UptimerHttpLib,
|
|
64
|
+
parent_segments: str | list[str] | None = None,
|
|
65
|
+
):
|
|
66
|
+
super().__init__(http, "destinations", parent_segments)
|
|
67
|
+
|
|
68
|
+
def all(self, workspace_id: str | None = None) -> list[Destination]:
|
|
69
|
+
"""Every destination in the workspace, by name."""
|
|
70
|
+
response = self.http.client.get(self.url, params=_params(workspace_id))
|
|
71
|
+
result = self.http.parse_response(response=response)
|
|
72
|
+
return [from_api_destination(item) for item in result]
|
|
73
|
+
|
|
74
|
+
def get(self, destination_id: int, workspace_id: str | None = None) -> Destination:
|
|
75
|
+
"""One destination by id."""
|
|
76
|
+
response = self.http.client.get(
|
|
77
|
+
f"{self.url}/{destination_id}",
|
|
78
|
+
params=_params(workspace_id),
|
|
79
|
+
)
|
|
80
|
+
result = self.http.parse_response(response=response)
|
|
81
|
+
return from_api_destination(result)
|
|
82
|
+
|
|
83
|
+
def create(
|
|
84
|
+
self,
|
|
85
|
+
destination: CreateDestinationRequest,
|
|
86
|
+
workspace_id: str | None = None,
|
|
87
|
+
) -> Destination:
|
|
88
|
+
"""
|
|
89
|
+
Create one, enabled.
|
|
90
|
+
|
|
91
|
+
The FIRST destination in a workspace becomes its default whether or not
|
|
92
|
+
it asks: a workspace whose only destination is not the default notifies
|
|
93
|
+
nobody.
|
|
94
|
+
|
|
95
|
+
Raises DefaultUptimerApiError on a name that is empty or already used, an
|
|
96
|
+
address that is not http(s), a type that is neither slack nor webhook,
|
|
97
|
+
and a transformation that is not in this workspace.
|
|
98
|
+
"""
|
|
99
|
+
response = self.http.client.post(
|
|
100
|
+
self.url,
|
|
101
|
+
params=_params(workspace_id),
|
|
102
|
+
json=asdict(destination),
|
|
103
|
+
)
|
|
104
|
+
result = self.http.parse_response(response=response)
|
|
105
|
+
return from_api_destination(result)
|
|
106
|
+
|
|
107
|
+
def update(
|
|
108
|
+
self,
|
|
109
|
+
destination_id: int,
|
|
110
|
+
destination: UpdateDestinationRequest,
|
|
111
|
+
workspace_id: str | None = None,
|
|
112
|
+
) -> Destination:
|
|
113
|
+
"""
|
|
114
|
+
Replace name, channel, URL, default and transformation.
|
|
115
|
+
|
|
116
|
+
The TYPE cannot be changed: a Slack hook and a plain webhook carry
|
|
117
|
+
different payloads, and converting one silently is how a channel goes
|
|
118
|
+
quiet. Delete and recreate instead.
|
|
119
|
+
"""
|
|
120
|
+
response = self.http.client.post(
|
|
121
|
+
f"{self.url}/{destination_id}",
|
|
122
|
+
params=_params(workspace_id),
|
|
123
|
+
json=asdict(destination),
|
|
124
|
+
)
|
|
125
|
+
result = self.http.parse_response(response=response)
|
|
126
|
+
return from_api_destination(result)
|
|
127
|
+
|
|
128
|
+
def set_enabled(
|
|
129
|
+
self,
|
|
130
|
+
destination_id: int,
|
|
131
|
+
enabled: bool, # noqa: FBT001
|
|
132
|
+
workspace_id: str | None = None,
|
|
133
|
+
) -> Destination:
|
|
134
|
+
"""
|
|
135
|
+
Switch one on or off.
|
|
136
|
+
|
|
137
|
+
A disabled destination keeps its name, its address and every subject
|
|
138
|
+
selection pointing at it, and sends nothing until it is switched back
|
|
139
|
+
on.
|
|
140
|
+
"""
|
|
141
|
+
response = self.http.client.post(
|
|
142
|
+
f"{self.url}/{destination_id}/enabled",
|
|
143
|
+
params=_params(workspace_id),
|
|
144
|
+
json={"enabled": enabled},
|
|
145
|
+
)
|
|
146
|
+
result = self.http.parse_response(response=response)
|
|
147
|
+
return from_api_destination(result)
|
|
148
|
+
|
|
149
|
+
def make_default(
|
|
150
|
+
self,
|
|
151
|
+
destination_id: int,
|
|
152
|
+
workspace_id: str | None = None,
|
|
153
|
+
) -> Destination:
|
|
154
|
+
"""
|
|
155
|
+
Make this the workspace fallback.
|
|
156
|
+
|
|
157
|
+
It is what a subject sends to when it has chosen nothing of its own.
|
|
158
|
+
Moving the default clears it from whichever destination held it. A
|
|
159
|
+
DISABLED destination is refused: a fallback that cannot receive anything
|
|
160
|
+
is silence wearing a label.
|
|
161
|
+
"""
|
|
162
|
+
response = self.http.client.post(
|
|
163
|
+
f"{self.url}/{destination_id}/default",
|
|
164
|
+
params=_params(workspace_id),
|
|
165
|
+
)
|
|
166
|
+
result = self.http.parse_response(response=response)
|
|
167
|
+
return from_api_destination(result)
|
|
168
|
+
|
|
169
|
+
def send_test(
|
|
170
|
+
self,
|
|
171
|
+
destination_id: int,
|
|
172
|
+
workspace_id: str | None = None,
|
|
173
|
+
) -> TestDeliveryResponse:
|
|
174
|
+
"""
|
|
175
|
+
Send one real test message.
|
|
176
|
+
|
|
177
|
+
It is a REAL send: the same render, the same transport and the same
|
|
178
|
+
delivery record an alert produces, so a template that works here works
|
|
179
|
+
during an outage.
|
|
180
|
+
|
|
181
|
+
A destination that refuses the message raises DefaultUptimerApiError
|
|
182
|
+
carrying the far end's own words — the request was fine, the destination
|
|
183
|
+
was not — and the attempt is recorded either way.
|
|
184
|
+
"""
|
|
185
|
+
response = self.http.client.post(
|
|
186
|
+
f"{self.url}/{destination_id}/test",
|
|
187
|
+
params=_params(workspace_id),
|
|
188
|
+
)
|
|
189
|
+
result = self.http.parse_response(response=response)
|
|
190
|
+
return TestDeliveryResponse(
|
|
191
|
+
message=result["message"],
|
|
192
|
+
destination_id=result["destination_id"],
|
|
193
|
+
workspace_id=result["workspace_id"],
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def delete(
|
|
197
|
+
self,
|
|
198
|
+
destination_id: int,
|
|
199
|
+
workspace_id: str | None = None,
|
|
200
|
+
) -> DeleteDestinationResponse:
|
|
201
|
+
"""
|
|
202
|
+
Delete one, and every subject selection naming it.
|
|
203
|
+
|
|
204
|
+
The records it already wrote stay.
|
|
205
|
+
|
|
206
|
+
Deleting the default PROMOTES NOBODY: a workspace is allowed to have
|
|
207
|
+
none, and handing the role to whichever destination is next would start
|
|
208
|
+
sending a channel alerts it never asked for. Check `was_default` on the
|
|
209
|
+
answer. Delivery records stay — they record what was sent, and that
|
|
210
|
+
remains true after the destination is gone.
|
|
211
|
+
"""
|
|
212
|
+
response = self.http.client.delete(
|
|
213
|
+
f"{self.url}/{destination_id}",
|
|
214
|
+
params=_params(workspace_id),
|
|
215
|
+
)
|
|
216
|
+
result = self.http.parse_response(response=response)
|
|
217
|
+
return DeleteDestinationResponse(
|
|
218
|
+
message=result["message"],
|
|
219
|
+
destination_id=result["destination_id"],
|
|
220
|
+
workspace_id=result["workspace_id"],
|
|
221
|
+
was_default=result.get("was_default", False),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class TransformationsEndpoint(BaseEndpoint):
|
|
226
|
+
"""
|
|
227
|
+
The workspace's outbound payload templates (uptimer 1.8.0).
|
|
228
|
+
|
|
229
|
+
A transformation is a named template for what a destination receives — a
|
|
230
|
+
PagerDuty event, your own JSON, a line of text. A destination with none gets
|
|
231
|
+
Uptimer's built-in Slack-shaped message.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
def __init__(
|
|
235
|
+
self,
|
|
236
|
+
http: UptimerHttpLib,
|
|
237
|
+
parent_segments: str | list[str] | None = None,
|
|
238
|
+
):
|
|
239
|
+
super().__init__(http, "transformations", parent_segments)
|
|
240
|
+
|
|
241
|
+
def all(self, workspace_id: str | None = None) -> list[Transformation]:
|
|
242
|
+
"""Every transformation in the workspace, by name."""
|
|
243
|
+
response = self.http.client.get(self.url, params=_params(workspace_id))
|
|
244
|
+
result = self.http.parse_response(response=response)
|
|
245
|
+
return [from_api_transformation(item) for item in result]
|
|
246
|
+
|
|
247
|
+
def samples(self) -> list[TransformationSample]:
|
|
248
|
+
"""
|
|
249
|
+
Return the three messages every template is judged against.
|
|
250
|
+
|
|
251
|
+
Each carries every field it may read. No workspace: the samples are the product's, not a tenant's. They are
|
|
252
|
+
the same fixtures the editor shows and the same ones a save is judged
|
|
253
|
+
against, so you can render locally and get the answer this API would
|
|
254
|
+
give.
|
|
255
|
+
"""
|
|
256
|
+
response = self.http.client.get(f"{self.url}/samples")
|
|
257
|
+
result = self.http.parse_response(response=response)
|
|
258
|
+
return samples_from_api(result)
|
|
259
|
+
|
|
260
|
+
def preview(
|
|
261
|
+
self,
|
|
262
|
+
template: str,
|
|
263
|
+
workspace_id: str | None = None,
|
|
264
|
+
) -> TransformationPreview:
|
|
265
|
+
"""
|
|
266
|
+
Render a template against every sample, storing nothing.
|
|
267
|
+
|
|
268
|
+
`passed` is exactly the condition a create or an update enforces, so
|
|
269
|
+
this answers "will a write be accepted" before attempting one — and
|
|
270
|
+
says, per sample, what is wrong when it will not.
|
|
271
|
+
"""
|
|
272
|
+
response = self.http.client.post(
|
|
273
|
+
f"{self.url}/preview",
|
|
274
|
+
params=_params(workspace_id),
|
|
275
|
+
json={"template": template},
|
|
276
|
+
)
|
|
277
|
+
result = self.http.parse_response(response=response)
|
|
278
|
+
return from_api_transformation_preview(result)
|
|
279
|
+
|
|
280
|
+
def get(
|
|
281
|
+
self,
|
|
282
|
+
transformation_id: int,
|
|
283
|
+
workspace_id: str | None = None,
|
|
284
|
+
) -> Transformation:
|
|
285
|
+
"""One transformation by id."""
|
|
286
|
+
response = self.http.client.get(
|
|
287
|
+
f"{self.url}/{transformation_id}",
|
|
288
|
+
params=_params(workspace_id),
|
|
289
|
+
)
|
|
290
|
+
result = self.http.parse_response(response=response)
|
|
291
|
+
return from_api_transformation(result)
|
|
292
|
+
|
|
293
|
+
def create(
|
|
294
|
+
self,
|
|
295
|
+
transformation: CreateTransformationRequest,
|
|
296
|
+
workspace_id: str | None = None,
|
|
297
|
+
) -> Transformation:
|
|
298
|
+
"""
|
|
299
|
+
Store one, if it renders every sample.
|
|
300
|
+
|
|
301
|
+
There is no force flag. A template that breaks one message raises
|
|
302
|
+
DefaultUptimerApiError naming the sample that broke, because a template
|
|
303
|
+
that cannot render a recovery would fail at the moment it mattered.
|
|
304
|
+
"""
|
|
305
|
+
response = self.http.client.post(
|
|
306
|
+
self.url,
|
|
307
|
+
params=_params(workspace_id),
|
|
308
|
+
json=asdict(transformation),
|
|
309
|
+
)
|
|
310
|
+
result = self.http.parse_response(response=response)
|
|
311
|
+
return from_api_transformation(result)
|
|
312
|
+
|
|
313
|
+
def update(
|
|
314
|
+
self,
|
|
315
|
+
transformation_id: int,
|
|
316
|
+
transformation: UpdateTransformationRequest,
|
|
317
|
+
workspace_id: str | None = None,
|
|
318
|
+
) -> Transformation:
|
|
319
|
+
"""
|
|
320
|
+
Replace its name and template, judged by the same rule.
|
|
321
|
+
|
|
322
|
+
A refused edit leaves the STORED template exactly as it was.
|
|
323
|
+
"""
|
|
324
|
+
response = self.http.client.post(
|
|
325
|
+
f"{self.url}/{transformation_id}",
|
|
326
|
+
params=_params(workspace_id),
|
|
327
|
+
json=asdict(transformation),
|
|
328
|
+
)
|
|
329
|
+
result = self.http.parse_response(response=response)
|
|
330
|
+
return from_api_transformation(result)
|
|
331
|
+
|
|
332
|
+
def delete(
|
|
333
|
+
self,
|
|
334
|
+
transformation_id: int,
|
|
335
|
+
workspace_id: str | None = None,
|
|
336
|
+
) -> DeleteTransformationResponse:
|
|
337
|
+
"""Delete one. Destinations using it fall back to the built-in message."""
|
|
338
|
+
response = self.http.client.delete(
|
|
339
|
+
f"{self.url}/{transformation_id}",
|
|
340
|
+
params=_params(workspace_id),
|
|
341
|
+
)
|
|
342
|
+
result = self.http.parse_response(response=response)
|
|
343
|
+
return DeleteTransformationResponse(
|
|
344
|
+
message=result["message"],
|
|
345
|
+
transformation_id=result["transformation_id"],
|
|
346
|
+
workspace_id=result["workspace_id"],
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class DeliveriesEndpoint(BaseEndpoint):
|
|
351
|
+
"""
|
|
352
|
+
The delivery log (uptimer 1.8.0).
|
|
353
|
+
|
|
354
|
+
What was actually sent, and what the far end said. It is a READ. Nothing here sends, resends or changes a destination — Uptimer
|
|
355
|
+
sends once, in the background, and this is the record. Attempts are kept 30
|
|
356
|
+
days.
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
def __init__(
|
|
360
|
+
self,
|
|
361
|
+
http: UptimerHttpLib,
|
|
362
|
+
parent_segments: str | list[str] | None = None,
|
|
363
|
+
):
|
|
364
|
+
super().__init__(http, "deliveries", parent_segments)
|
|
365
|
+
|
|
366
|
+
def all(
|
|
367
|
+
self,
|
|
368
|
+
workspace_id: str | None = None,
|
|
369
|
+
destination_id: int | None = None,
|
|
370
|
+
undelivered: bool = False, # noqa: FBT001, FBT002
|
|
371
|
+
) -> list[DeliveryRecord]:
|
|
372
|
+
"""
|
|
373
|
+
Return recorded attempts, newest first.
|
|
374
|
+
|
|
375
|
+
`destination_id` narrows the log to one destination, and `undelivered`
|
|
376
|
+
to the attempts that were not accepted — the two filters the Delivery
|
|
377
|
+
page has.
|
|
378
|
+
"""
|
|
379
|
+
params = _params(
|
|
380
|
+
workspace_id,
|
|
381
|
+
destination_id=destination_id,
|
|
382
|
+
undelivered="true" if undelivered else None,
|
|
383
|
+
)
|
|
384
|
+
response = self.http.client.get(self.url, params=params)
|
|
385
|
+
result = self.http.parse_response(response=response)
|
|
386
|
+
return [from_api_delivery_record(item) for item in result]
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class NotificationsEndpoint(BaseEndpoint):
|
|
390
|
+
"""
|
|
391
|
+
Where a workspace's alerts can go, and what they look like (uptimer 1.8.0).
|
|
392
|
+
|
|
393
|
+
Three collections, and one thing that is NOT here: which destinations a
|
|
394
|
+
subject tells. That belongs to the subject —
|
|
395
|
+
`client.v2.subjects(slug).delivery` and
|
|
396
|
+
`client.v2.monitoring.websites(id).delivery` — because a workspace-wide
|
|
397
|
+
route says one thing for everything a team watches, and the point of this
|
|
398
|
+
release is that it no longer has to.
|
|
399
|
+
"""
|
|
400
|
+
|
|
401
|
+
destinations: DestinationsEndpoint
|
|
402
|
+
transformations: TransformationsEndpoint
|
|
403
|
+
deliveries: DeliveriesEndpoint
|
|
404
|
+
|
|
405
|
+
def __init__(
|
|
406
|
+
self,
|
|
407
|
+
http: UptimerHttpLib,
|
|
408
|
+
parent_segments: str | list[str] | None = None,
|
|
409
|
+
):
|
|
410
|
+
super().__init__(http, "notifications", parent_segments)
|
|
411
|
+
mine = [*self._parent_segments, self.segment]
|
|
412
|
+
self.destinations = DestinationsEndpoint(http, mine)
|
|
413
|
+
self.transformations = TransformationsEndpoint(http, mine)
|
|
414
|
+
self.deliveries = DeliveriesEndpoint(http, mine)
|