uptimer-python-sdk 1.7.0__tar.gz → 1.8.0__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.
- uptimer_python_sdk-1.8.0/.cursor/rules/sdk-docs-implemented-only.mdc +47 -0
- uptimer_python_sdk-1.8.0/AGENTS.md +32 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/CHANGELOG.md +50 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/PKG-INFO +300 -10
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/README.md +299 -9
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/pyproject.toml +5 -1
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/__init__.py +3 -3
- uptimer_python_sdk-1.8.0/src/uptimer/endpoints/delivery.py +84 -0
- uptimer_python_sdk-1.8.0/src/uptimer/endpoints/notifications.py +414 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/endpoints/subjects.py +227 -21
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/endpoints/v2.py +3 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/endpoints/websites.py +40 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/__init__.py +120 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/deserialize.py +113 -0
- uptimer_python_sdk-1.8.0/src/uptimer/models/v2/notifications.py +332 -0
- uptimer_python_sdk-1.8.0/src/uptimer/models/v2/rule.py +222 -0
- uptimer_python_sdk-1.8.0/src/uptimer/models/v2/signal.py +90 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/subject.py +4 -3
- uptimer_python_sdk-1.8.0/tests/integrations/test_notifications.py +201 -0
- uptimer_python_sdk-1.8.0/tests/integrations/test_signals_rules.py +135 -0
- uptimer_python_sdk-1.8.0/tests/test_alert_delivery_endpoint.py +208 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_model_namespace.py +65 -0
- uptimer_python_sdk-1.8.0/tests/test_notifications_endpoint.py +353 -0
- uptimer_python_sdk-1.8.0/tests/test_server_contract_v2_notifications.py +150 -0
- uptimer_python_sdk-1.8.0/tests/test_signals_rules_endpoint.py +418 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/uv.lock +1 -1
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/.gitignore +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/.pre-commit-config.yaml +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/.vscode/extensions.json +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/.vscode/launch.json +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/.vscode/settings.json +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/.vscode/tasks.json +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/LICENSE +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/NOTICE +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/Taskfile.yml +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/examples/01_client_setup.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/examples/02_list_workspaces.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/examples/03_list_locations.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/examples/04_create_website_monitor.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/examples/05_open_incidents.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/examples/README.md +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/examples/__init__.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/client.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/compat.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/endpoints/__init__.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/endpoints/endpoint.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/endpoints/incidents.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/endpoints/locations.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/endpoints/v1.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/endpoints/workspaces.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/errors.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/http.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/__init__.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/errors.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/acknowledgement.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/incident.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/location.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/maintenance.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/monitor.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/observation.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/models/v2/workspace.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/src/uptimer/py.typed +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/__init__.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/conftest.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/integrations/__init__.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/integrations/conftest.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/integrations/test_acknowledgement.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/integrations/test_observations.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/integrations/test_subjects.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/integrations/test_workspaces.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_acknowledgement.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_client.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_endpoint.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_integration_v2.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_maintenance.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_observations_endpoint.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_subjects_endpoint.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_v2_endpoints.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tests/test_workspaces_endpoint.py +0 -0
- {uptimer_python_sdk-1.7.0 → uptimer_python_sdk-1.8.0}/tools/mypy-diff.sh +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: SDK docs describe only what this package implements — no gaps, no roadmap, no release process
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SDK docs describe only what this package implements
|
|
7
|
+
|
|
8
|
+
Applies to everything a user of the published package reads: `README.md` (which
|
|
9
|
+
is also the PyPI long description), docstrings under `src/`, and `examples/`.
|
|
10
|
+
|
|
11
|
+
## The rule
|
|
12
|
+
|
|
13
|
+
**Document only capabilities present in this package version.** That is all.
|
|
14
|
+
|
|
15
|
+
A README is a contract for the package somebody just installed, not a status
|
|
16
|
+
board for the work around it.
|
|
17
|
+
|
|
18
|
+
## Do not write
|
|
19
|
+
|
|
20
|
+
- "this SDK does not wrap those routes **yet**", "not implemented", "missing",
|
|
21
|
+
"unsupported" — anything that describes an absence;
|
|
22
|
+
- "not in the published package", "until X is on PyPI", "use a checkout of this
|
|
23
|
+
repository" — release sequencing and workarounds;
|
|
24
|
+
- ticket or issue numbers, internal milestones, or which release something is
|
|
25
|
+
planned for;
|
|
26
|
+
- TODOs or roadmap notes in public docstrings.
|
|
27
|
+
|
|
28
|
+
## Do instead
|
|
29
|
+
|
|
30
|
+
- **Omit it.** A capability this package does not have simply does not appear.
|
|
31
|
+
The reader loses nothing: they cannot call it either way.
|
|
32
|
+
- Where the HTTP API has something the package does not, the
|
|
33
|
+
[REST documentation](https://uptimer.myuptime.info/latest/reference/rest-api/)
|
|
34
|
+
covers it. Link that for the full reference when it helps — without saying
|
|
35
|
+
what is missing here.
|
|
36
|
+
- State the **server** a capability needs when it is not the package minimum
|
|
37
|
+
("needs an Uptimer 1.7.0+ server"). That is a fact about an implemented
|
|
38
|
+
feature, not a gap.
|
|
39
|
+
- When something ships, add it to the docs in the same change that adds the
|
|
40
|
+
code.
|
|
41
|
+
|
|
42
|
+
## Why
|
|
43
|
+
|
|
44
|
+
Users read this to decide what to call. "We have not wrapped that yet" answers a
|
|
45
|
+
question nobody asked and dates the moment it is written; a checkout instruction
|
|
46
|
+
sends somebody around a problem that a later release quietly removed. Both have
|
|
47
|
+
happened here.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# AGENTS.md — uptimer-python-sdk
|
|
2
|
+
|
|
3
|
+
The public Python client for Uptimer. Fleet-wide rules apply (see the workspace
|
|
4
|
+
`AGENTS.md`): trunk-based on `main`, no agent attribution, Commitizen subjects.
|
|
5
|
+
|
|
6
|
+
## Documentation policy
|
|
7
|
+
|
|
8
|
+
**SDK docs describe only what this package implements.** That is all.
|
|
9
|
+
|
|
10
|
+
`README.md` (the PyPI long description), the docstrings under `src/`, and
|
|
11
|
+
`examples/` document capabilities present in **this package version**. No "not
|
|
12
|
+
wrapped yet", no checkout workarounds, no ticket numbers, no release
|
|
13
|
+
sequencing. A capability this package does not have is **omitted** — the
|
|
14
|
+
[REST documentation](https://uptimer.myuptime.info/latest/reference/rest-api/)
|
|
15
|
+
covers the HTTP API in full, and may be linked for that.
|
|
16
|
+
|
|
17
|
+
The same rule, in the form Cursor reads:
|
|
18
|
+
[`.cursor/rules/sdk-docs-implemented-only.mdc`](.cursor/rules/sdk-docs-implemented-only.mdc).
|
|
19
|
+
|
|
20
|
+
## Working here
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
task cq # lint + type-check + tests (ruff, mypy, pytest)
|
|
24
|
+
task test # pytest alone
|
|
25
|
+
task build # sdist + wheel into dist/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- The package version tracks the Uptimer release it targets: 1.8.x speaks to
|
|
29
|
+
Uptimer 1.8.0 and later. `uptimer.__version__` is where the compatibility
|
|
30
|
+
minimum comes from, and `cz` bumps it with `pyproject.toml`.
|
|
31
|
+
- **Never publish a prerelease** (rc/a/b/dev) to TestPyPI or PyPI. This package
|
|
32
|
+
ships final `X.Y.Z` only; publishing is an operator action.
|
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
## 1.8.0 (2026-09-19)
|
|
2
|
+
|
|
3
|
+
Targets Uptimer **1.8.0** and later.
|
|
4
|
+
|
|
5
|
+
### Feat
|
|
6
|
+
|
|
7
|
+
- **notifications**: `client.v2.notifications.destinations` — list, create, get,
|
|
8
|
+
update, delete, plus `set_enabled`, `make_default` and `send_test`. A
|
|
9
|
+
destination is one place a workspace's alerts can go: a Slack incoming
|
|
10
|
+
webhook, or any HTTP endpoint.
|
|
11
|
+
- **notifications**: `client.v2.notifications.transformations` — list, create,
|
|
12
|
+
get, update, delete, plus `samples` (the field vocabulary a template may
|
|
13
|
+
read) and `preview` (the same judgement a save makes, storing nothing). A
|
|
14
|
+
template is stored only once it renders all three messages.
|
|
15
|
+
- **notifications**: `client.v2.notifications.deliveries.all(...)` — the
|
|
16
|
+
delivery log, filtered by destination and by undelivered. Records are kept 30
|
|
17
|
+
days.
|
|
18
|
+
- **delivery**: `client.v2.subjects(slug).delivery` and
|
|
19
|
+
`client.v2.monitoring.websites(id).delivery` — `get`, `replace` and `clear`
|
|
20
|
+
for which destinations one subject tells, and about what. The table is the
|
|
21
|
+
resource: a save replaces it rather than merging.
|
|
22
|
+
- **signals**: `client.v2.subjects(slug).signals` — list, create, get, update
|
|
23
|
+
and delete. Reporting observations to an existing signal is unchanged.
|
|
24
|
+
- **rules**: `client.v2.subjects(slug).rules` — list, create, get, update and
|
|
25
|
+
delete, with typed policy models (`RuleDocument`, `RuleInput`,
|
|
26
|
+
`RuleDecision`, `RuleWait`). A rule input citing another rule is `from_rule`
|
|
27
|
+
in Python, because `from` is a keyword.
|
|
28
|
+
|
|
29
|
+
### Fix
|
|
30
|
+
|
|
31
|
+
- **subjects**: a `workspace_id` given to `client.v2.subjects(slug, ws)` now
|
|
32
|
+
reaches the signal, rule and observation routes under it. Without it, a
|
|
33
|
+
caller holding the same subject slug in two workspaces was answered
|
|
34
|
+
`Ambiguous subject` by the server — by the one call that could have
|
|
35
|
+
disambiguated.
|
|
36
|
+
- **compat**: `uptimer.__version__` is bumped with the package, so the minimum
|
|
37
|
+
server version it derives is the version this package targets. It had
|
|
38
|
+
drifted: 1.7.0 moved `pyproject.toml` alone, and the published client went on
|
|
39
|
+
accepting a 1.6.0 server.
|
|
40
|
+
|
|
41
|
+
### Docs
|
|
42
|
+
|
|
43
|
+
- The README documents only what this package implements, with runnable
|
|
44
|
+
examples for destinations, transformations, subject delivery, the delivery
|
|
45
|
+
log, signals and rules. The complete HTTP API — including anything not
|
|
46
|
+
wrapped here — is at
|
|
47
|
+
<https://uptimer.myuptime.info/latest/reference/rest-api/>.
|
|
48
|
+
- That policy is now written down for contributors and agents in `AGENTS.md`
|
|
49
|
+
and `.cursor/rules/sdk-docs-implemented-only.mdc`.
|
|
50
|
+
|
|
1
51
|
## 1.7.0 (2026-09-14)
|
|
2
52
|
|
|
3
53
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: uptimer-python-sdk
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: A Python SDK for hosted and self-hosted Uptimer
|
|
5
5
|
Project-URL: Homepage, https://myuptime.info
|
|
6
6
|
Project-URL: Documentation, https://uptimer.myuptime.info/latest/reference/python-sdk/
|
|
@@ -41,6 +41,17 @@ or
|
|
|
41
41
|
uv add uptimer-python-sdk
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
**This package targets Uptimer 1.8.0 and later.** The SDK's major.minor tracks
|
|
45
|
+
the server release it speaks to, so install the one that matches yours; patch
|
|
46
|
+
numbers move independently. `client.ensure_compatible()` checks it for you and
|
|
47
|
+
fails with a message that names the fix.
|
|
48
|
+
|
|
49
|
+
Still on API v1? Pin `uptimer-python-sdk<1`. The server's v1 is unchanged and
|
|
50
|
+
supported, so 0.4.x keeps working — it just cannot use anything newer.
|
|
51
|
+
|
|
52
|
+
The complete REST API reference is at
|
|
53
|
+
[uptimer.myuptime.info](https://uptimer.myuptime.info/latest/reference/rest-api/).
|
|
54
|
+
|
|
44
55
|
## Usage
|
|
45
56
|
|
|
46
57
|
### Create client
|
|
@@ -208,9 +219,97 @@ locations, so it has its own call — `client.v2.monitoring.websites.create` —
|
|
|
208
219
|
and asking for `subject_kind="website"` on this route is refused with a message
|
|
209
220
|
saying so.
|
|
210
221
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
222
|
+
### Signals: what reports to a subject
|
|
223
|
+
|
|
224
|
+
A **signal** is one thing that reports. A **heartbeat** is expected to keep
|
|
225
|
+
reporting, so its silence is itself a symptom; an **event** reports only when
|
|
226
|
+
there is something to say, so its silence means nothing. The kind is fixed when
|
|
227
|
+
the signal is created — senders are already posting to it, and changing what
|
|
228
|
+
their silence means underneath them is not a rename.
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
from uptimer.models.v2 import (
|
|
232
|
+
SIGNAL_KIND_HEARTBEAT,
|
|
233
|
+
CreateSignalRequest,
|
|
234
|
+
UpdateSignalRequest,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
signals = client.v2.subjects("nightly-export", "your-workspace-id").signals
|
|
238
|
+
|
|
239
|
+
signal = signals.create(
|
|
240
|
+
CreateSignalRequest(
|
|
241
|
+
name="Worker pulse",
|
|
242
|
+
kind=SIGNAL_KIND_HEARTBEAT,
|
|
243
|
+
meta={"team": "payments"}, # stored and returned untouched
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# `id` is the slug a sender posts to. A rename never moves it.
|
|
248
|
+
signals.update(signal.id, UpdateSignalRequest(name="Worker heartbeat", meta={}))
|
|
249
|
+
|
|
250
|
+
for existing in signals.all():
|
|
251
|
+
print(existing.id, existing.signal_kind)
|
|
252
|
+
|
|
253
|
+
# Deleting a signal deletes its observations. One a rule reads is refused:
|
|
254
|
+
# retarget or remove those rules first.
|
|
255
|
+
signals.delete(signal.id)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Rules: what counts as a problem
|
|
259
|
+
|
|
260
|
+
A **rule** reads a subject's signals — or another of its rules — and decides
|
|
261
|
+
whether there is a problem. The policy is a document: what it reads, what those
|
|
262
|
+
inputs have to agree on, and how long a state must hold.
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from uptimer.models.v2 import (
|
|
266
|
+
INPUT_MODE_LATEST_VALUE,
|
|
267
|
+
INPUT_MODE_STATUS,
|
|
268
|
+
NEED_ANY,
|
|
269
|
+
CreateRuleRequest,
|
|
270
|
+
RuleDecision,
|
|
271
|
+
RuleDocument,
|
|
272
|
+
RuleInput,
|
|
273
|
+
RuleWait,
|
|
274
|
+
UpdateRuleRequest,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
rules = client.v2.subjects("nightly-export", "your-workspace-id").rules
|
|
278
|
+
|
|
279
|
+
rule = rules.create(
|
|
280
|
+
CreateRuleRequest(
|
|
281
|
+
name="Export health",
|
|
282
|
+
document=RuleDocument(
|
|
283
|
+
inputs=[
|
|
284
|
+
# The heartbeat stopped, or its last observation said "problem".
|
|
285
|
+
RuleInput(signal="worker-pulse", mode=INPUT_MODE_STATUS,
|
|
286
|
+
no_data_after="5m"),
|
|
287
|
+
# A number out of range.
|
|
288
|
+
RuleInput(signal="queue-depth", mode=INPUT_MODE_LATEST_VALUE,
|
|
289
|
+
compare=">", threshold=1000),
|
|
290
|
+
# Another rule's verdict. `from` is a Python keyword, so it is
|
|
291
|
+
# `from_rule` here and `from` on the wire.
|
|
292
|
+
RuleInput(from_rule="queue-health"),
|
|
293
|
+
],
|
|
294
|
+
decision=RuleDecision(need=NEED_ANY),
|
|
295
|
+
wait=RuleWait(confirm_after="2m", close_after="2m"),
|
|
296
|
+
),
|
|
297
|
+
),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# The policy is a REPLACEMENT, not a patch: read it, change it, send the whole
|
|
301
|
+
# thing back. A CHANGED policy increments policy_version; sending the same
|
|
302
|
+
# document back leaves it where it was. Either way the rule keeps its slug, so
|
|
303
|
+
# the incidents already pointing at it stay attached.
|
|
304
|
+
rule.document.wait.confirm_after = "5m"
|
|
305
|
+
rules.update(rule.id, UpdateRuleRequest(name=rule.name, document=rule.document))
|
|
306
|
+
|
|
307
|
+
rules.delete(rule.id)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Durations are strings — `"5m"`, `"2m0s"` — so a stored policy reads the way you
|
|
311
|
+
would write it. Every input must cite a signal or a rule **of this subject**: a
|
|
312
|
+
subject is the boundary, so add the signals first.
|
|
214
313
|
|
|
215
314
|
### Reporting your own observations
|
|
216
315
|
|
|
@@ -281,12 +380,8 @@ under its subject on v2. Neither method falls back to the other, and there is no
|
|
|
281
380
|
kind-agnostic one: acknowledging is a claim about a specific incident, and an
|
|
282
381
|
SDK that guessed which family it belonged to could claim the wrong one.
|
|
283
382
|
|
|
284
|
-
**Availability.** These methods
|
|
285
|
-
not
|
|
286
|
-
checkout of this repository. They need a matching **Uptimer 1.7.0+** server —
|
|
287
|
-
the routes do not exist before that — and on
|
|
288
|
-
[myuptime.info](https://myuptime.info) they arrive when the hosted service picks
|
|
289
|
-
up the 1.7.0 API.
|
|
383
|
+
**Availability.** These methods need an **Uptimer 1.7.0+** server — the routes
|
|
384
|
+
do not exist before that.
|
|
290
385
|
|
|
291
386
|
**Custom monitoring** — list the subject's open incidents, pick one, acknowledge
|
|
292
387
|
it by id:
|
|
@@ -403,6 +498,201 @@ out — and `muted` says what waits, in the server's own words.
|
|
|
403
498
|
A past end time, a window already running, a website subject, or a caller who is
|
|
404
499
|
not an editor raise `DefaultUptimerApiError`. Reading takes the viewer role.
|
|
405
500
|
|
|
501
|
+
### Alert destinations
|
|
502
|
+
|
|
503
|
+
A **destination** is one place a workspace's alerts can go: a Slack incoming
|
|
504
|
+
webhook, or any HTTP endpoint that accepts a POST. A workspace has as many as it
|
|
505
|
+
needs, and each subject chooses which of them it tells.
|
|
506
|
+
|
|
507
|
+
Reading a destination takes the **editor** role, not just membership: a
|
|
508
|
+
destination holds a webhook URL, and a URL is enough for anyone holding it to
|
|
509
|
+
post into your channel.
|
|
510
|
+
|
|
511
|
+
```python
|
|
512
|
+
from uptimer.models.v2 import (
|
|
513
|
+
DESTINATION_TYPE_SLACK,
|
|
514
|
+
DESTINATION_TYPE_WEBHOOK,
|
|
515
|
+
CreateDestinationRequest,
|
|
516
|
+
UpdateDestinationRequest,
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
destinations = client.v2.notifications.destinations
|
|
520
|
+
|
|
521
|
+
# The FIRST destination in a workspace becomes its default, asked for or not:
|
|
522
|
+
# a workspace whose only destination is not the default notifies nobody.
|
|
523
|
+
slack = destinations.create(
|
|
524
|
+
CreateDestinationRequest(
|
|
525
|
+
name="Acme · #incidents",
|
|
526
|
+
url="https://hooks.slack.com/services/T00/B00/xxxx",
|
|
527
|
+
destination_type=DESTINATION_TYPE_SLACK,
|
|
528
|
+
channel="#incidents", # stored without the '#'
|
|
529
|
+
),
|
|
530
|
+
workspace_id="your-workspace-id",
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
relay = destinations.create(
|
|
534
|
+
CreateDestinationRequest(
|
|
535
|
+
name="Pager relay",
|
|
536
|
+
url="https://hooks.example.com/uptimer",
|
|
537
|
+
destination_type=DESTINATION_TYPE_WEBHOOK,
|
|
538
|
+
),
|
|
539
|
+
workspace_id="your-workspace-id",
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
# Move the address, or attach a payload template (see below). The TYPE is fixed
|
|
543
|
+
# at creation: delete and recreate to change it.
|
|
544
|
+
destinations.update(
|
|
545
|
+
relay.id,
|
|
546
|
+
UpdateDestinationRequest(name=relay.name, url="https://hooks.example.com/v2"),
|
|
547
|
+
workspace_id="your-workspace-id",
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
destinations.set_enabled(relay.id, enabled=False, workspace_id="your-workspace-id")
|
|
551
|
+
destinations.make_default(slack.id, workspace_id="your-workspace-id")
|
|
552
|
+
|
|
553
|
+
# A REAL send: same render, same transport, same delivery record as an alert.
|
|
554
|
+
# A destination that refuses it raises, with the far end's own words.
|
|
555
|
+
destinations.send_test(slack.id, workspace_id="your-workspace-id")
|
|
556
|
+
|
|
557
|
+
# Deleting the default promotes nobody — check what you did.
|
|
558
|
+
answer = destinations.delete(relay.id, workspace_id="your-workspace-id")
|
|
559
|
+
assert answer.was_default is False
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
`workspace_id` is optional everywhere here. It settles an ambiguity rather than
|
|
563
|
+
being required: these resources have no slug of their own, so the server
|
|
564
|
+
searches your memberships when it is absent and says so if the answer is more
|
|
565
|
+
than one.
|
|
566
|
+
|
|
567
|
+
### Transformations: the shape a destination receives
|
|
568
|
+
|
|
569
|
+
A **transformation** is a named template for the body a destination gets — a
|
|
570
|
+
PagerDuty event, your own JSON, a line of text. A destination with none gets
|
|
571
|
+
Uptimer's built-in Slack-shaped message, which is what `transformation_id=None`
|
|
572
|
+
means.
|
|
573
|
+
|
|
574
|
+
```python
|
|
575
|
+
from uptimer.models.v2 import (
|
|
576
|
+
CreateTransformationRequest,
|
|
577
|
+
UpdateDestinationRequest,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
transformations = client.v2.notifications.transformations
|
|
581
|
+
|
|
582
|
+
# The vocabulary: three sample messages, each with every field a template may
|
|
583
|
+
# read. No workspace — these are the product's own fixtures.
|
|
584
|
+
for sample in transformations.samples():
|
|
585
|
+
print(sample.label, sorted(sample.fields))
|
|
586
|
+
|
|
587
|
+
# Ask before you write. `passed` is exactly the condition a save enforces.
|
|
588
|
+
preview = transformations.preview(
|
|
589
|
+
'{"event": "{{ kind }}", "summary": "{{ summary }}"}',
|
|
590
|
+
workspace_id="your-workspace-id",
|
|
591
|
+
)
|
|
592
|
+
assert preview.passed
|
|
593
|
+
for result in preview.results:
|
|
594
|
+
print(result.label, result.ok, result.output or result.error)
|
|
595
|
+
|
|
596
|
+
template = transformations.create(
|
|
597
|
+
CreateTransformationRequest(
|
|
598
|
+
name="PagerDuty compact",
|
|
599
|
+
template='{"event": "{{ kind }}", "summary": "{{ summary }}"}',
|
|
600
|
+
),
|
|
601
|
+
workspace_id="your-workspace-id",
|
|
602
|
+
)
|
|
603
|
+
assert template.content_type == "application/json"
|
|
604
|
+
|
|
605
|
+
# Attach it. Passing transformation_id=None puts the destination back on
|
|
606
|
+
# Uptimer's own message.
|
|
607
|
+
client.v2.notifications.destinations.update(
|
|
608
|
+
slack.id,
|
|
609
|
+
UpdateDestinationRequest(
|
|
610
|
+
name=slack.name,
|
|
611
|
+
url=slack.url,
|
|
612
|
+
channel=slack.channel,
|
|
613
|
+
transformation_id=template.id,
|
|
614
|
+
),
|
|
615
|
+
workspace_id="your-workspace-id",
|
|
616
|
+
)
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
**A template is stored only once it renders all three messages.** There is no
|
|
620
|
+
force flag: a template that breaks on one of them raises, naming the sample that
|
|
621
|
+
broke, and a refused edit leaves the stored template exactly as it was. A
|
|
622
|
+
template starting with `{` or `[` is treated as JSON — values are escaped as
|
|
623
|
+
they are substituted and the result has to parse, so a quoted error cannot break
|
|
624
|
+
the document.
|
|
625
|
+
|
|
626
|
+
### Alert delivery: which destinations a subject tells
|
|
627
|
+
|
|
628
|
+
The choice lives on the **subject**, not on the workspace: the marketing site
|
|
629
|
+
telling nobody must not stop the payments API paging the on-call.
|
|
630
|
+
|
|
631
|
+
```python
|
|
632
|
+
from uptimer.models.v2 import (
|
|
633
|
+
ALERT_KIND_NO_DATA,
|
|
634
|
+
ALERT_KIND_PROBLEM,
|
|
635
|
+
ALERT_KIND_RECOVERY,
|
|
636
|
+
DeliverySelection,
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
delivery = client.v2.subjects("nightly-export", "your-workspace-id").delivery
|
|
640
|
+
|
|
641
|
+
table = delivery.get()
|
|
642
|
+
if table.uses_workspace_default:
|
|
643
|
+
print("this subject falls back to", table.default_destination_id)
|
|
644
|
+
elif table.is_silent:
|
|
645
|
+
print("nothing is sent for this subject")
|
|
646
|
+
|
|
647
|
+
# The body is the WHOLE table: what you send is what the subject will have.
|
|
648
|
+
delivery.replace(
|
|
649
|
+
[
|
|
650
|
+
DeliverySelection(
|
|
651
|
+
destination_id=slack.id,
|
|
652
|
+
alert_kinds=[ALERT_KIND_PROBLEM, ALERT_KIND_NO_DATA, ALERT_KIND_RECOVERY],
|
|
653
|
+
),
|
|
654
|
+
DeliverySelection(destination_id=relay.id, alert_kinds=[ALERT_KIND_PROBLEM]),
|
|
655
|
+
],
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
# Back to the workspace default — or to silence, if there is no default.
|
|
659
|
+
delivery.clear()
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
A reminder about an unanswered problem rides with `problem`: a destination that
|
|
663
|
+
hears about problems hears the four-hourly reminders too, which is why there is
|
|
664
|
+
no fourth alert kind.
|
|
665
|
+
|
|
666
|
+
A **website monitor** carries the same table on its own collection, because
|
|
667
|
+
`/v2/subjects` serves Custom subjects only:
|
|
668
|
+
|
|
669
|
+
```python
|
|
670
|
+
client.v2.monitoring.websites("monitor-id").delivery.get()
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
Saving a table changes delivery **only**. Signals, rules, incidents,
|
|
674
|
+
acknowledgement and maintenance are untouched, and nothing is sent by saving.
|
|
675
|
+
|
|
676
|
+
### Delivery log: what was actually sent
|
|
677
|
+
|
|
678
|
+
```python
|
|
679
|
+
records = client.v2.notifications.deliveries.all(
|
|
680
|
+
workspace_id="your-workspace-id",
|
|
681
|
+
destination_id=slack.id, # optional: one destination
|
|
682
|
+
undelivered=True, # optional: only what did not arrive
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
for record in records:
|
|
686
|
+
print(record.at, record.destination_name, record.status)
|
|
687
|
+
if not record.delivered:
|
|
688
|
+
print(" refused:", record.error)
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
It is a read — nothing here sends or resends, and there is no retry. Each record
|
|
692
|
+
keeps the destination's **name and type as they were at the attempt**, so a
|
|
693
|
+
rename or a delete later leaves the row still saying where the message went; the
|
|
694
|
+
webhook URL is never recorded. Records are kept **30 days**.
|
|
695
|
+
|
|
406
696
|
### Incident status
|
|
407
697
|
|
|
408
698
|
`client.v2.incidents.all()` returns only **open** incidents. `status` carries the
|