pamoja-telemetry 0.1.18__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.
@@ -0,0 +1,13 @@
1
+ # Native extensions are built per platform, not committed.
2
+ *.so
3
+ *.pyd
4
+ *.dylib
5
+
6
+ # Build and packaging output.
7
+ dist/
8
+ wheels/
9
+ target/
10
+
11
+ # Local virtual environments.
12
+ .venv/
13
+ venv/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anthony Wiedman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.5
2
+ Name: pamoja-telemetry
3
+ Version: 0.1.18
4
+ Summary: Observability that ships only what is worth the bytes as link cost rises, while counting everything.
5
+ Project-URL: Repository, https://github.com/molexxxx/pamoja
6
+ Project-URL: Documentation, https://pamoja.molex.cloud/docs/guides/telemetry.html
7
+ Author: molexxxx
8
+ License: MIT
9
+ License-File: LICENSE-MIT
10
+ Keywords: iot,pamoja,robotics,telemetry
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: pamoja-native==0.1.18
17
+ Description-Content-Type: text/markdown
18
+
19
+ # pamoja-telemetry
20
+
21
+ Observability that ships only what is worth the bytes as link cost rises, while counting everything. One capability of [pamoja](https://github.com/molexxxx/pamoja), one memory-safe Rust core with bindings for TypeScript, Python, and C#.
22
+
23
+ [![read the guide](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-guide.svg)](https://pamoja.molex.cloud/docs/guides/telemetry.html)
24
+ [![documentation](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-docs.svg)](https://pamoja.molex.cloud/docs/)
25
+ [![API reference](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-api.svg)](https://pamoja.molex.cloud/docs/reference/python/pamoja/telemetry.html)
26
+
27
+ ## Install
28
+
29
+ ```sh
30
+ pip install pamoja-telemetry
31
+ ```
32
+
33
+ ```python
34
+ from pamoja import telemetry
35
+ ```
36
+
37
+ This pulls in `pamoja-native`, the compiled engine. `pip install pamoja` is the whole framework in one package.
38
+
39
+ ## Example
40
+
41
+ The script the test suite runs, spliced here as it ran.
42
+
43
+ From [`bindings/python/guides/telemetry.py`](https://github.com/molexxxx/pamoja/blob/main/bindings/python/guides/telemetry.py):
44
+
45
+ ```python
46
+ from pamoja.telemetry import Event, Level, LinkCost, Reporter, link_cost_threshold
47
+
48
+ # The node is willing to record everything, then finds out it is reporting over a metered
49
+ # link, which puts the bar at INFO.
50
+ reporter = Reporter(Level.TRACE)
51
+ reporter.adapt_to(LinkCost.METERED)
52
+ print(f"on a metered link, nothing below {reporter.threshold.value} is sent")
53
+
54
+ # Routine detail stops going out. A reading and the warning that follows it still do, and
55
+ # a shipped event comes back with the measurement that triggered it.
56
+ tick = reporter.record(Event(Level.DEBUG, "loop.tick"))
57
+ reading = reporter.record(Event(Level.INFO, "reading.ok", 4.8))
58
+ print(f"loop.tick sent: {tick is not None}")
59
+ print(f"reading.ok sent: {reading is not None}")
60
+ warned = reporter.record(Event(Level.WARN, "battery.low", 0.18))
61
+ print(f"sent {warned.code} carrying {warned.value}")
62
+
63
+ # The node falls back to satellite, which raises the bar to WARN. The same reading is no
64
+ # longer worth its bytes; a failure still is.
65
+ reporter.adapt_to(LinkCost.EXPENSIVE)
66
+ dearer = reporter.record(Event(Level.INFO, "reading.ok", 4.9))
67
+ lost = reporter.record(Event(Level.ERROR, "link.lost"))
68
+ print(f"on satellite, reading.ok sent: {dearer is not None}")
69
+ print(f"on satellite, link.lost sent: {lost is not None}")
70
+
71
+ # Only the stream was thinned, not the counts, so every event is still accounted for and
72
+ # the snapshot is what the node ships in place of them.
73
+ counts = reporter.snapshot()
74
+ print(f"of {reporter.total} events, {counts.emitted} went out and {counts.dropped} were counted only")
75
+ ```
76
+
77
+ ## The same capability in every language
78
+
79
+ | Language | Package | Reference |
80
+ | --- | --- | --- |
81
+ | Rust | [`pamoja-telemetry`](https://crates.io/crates/pamoja-telemetry) | [reference](https://pamoja.molex.cloud/docs/reference/rust/pamoja_telemetry/index.html), [docs.rs](https://docs.rs/pamoja-telemetry), [install](https://pamoja.molex.cloud/docs/reference/rust.html#rust-telemetry) |
82
+ | TypeScript | [`@pamoja/telemetry`](https://www.npmjs.com/package/@pamoja/telemetry) | [reference](https://pamoja.molex.cloud/docs/reference/node/modules/_pamoja_telemetry.html), [install](https://pamoja.molex.cloud/docs/reference/node.html#node-telemetry) |
83
+ | Python | [`pamoja-telemetry`](https://pypi.org/project/pamoja-telemetry/) | [reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/telemetry.html), [install](https://pamoja.molex.cloud/docs/reference/python.html#python-telemetry) |
84
+ | C# | [`Pamoja.Telemetry`](https://www.nuget.org/packages/Pamoja.Telemetry) | [reference](https://pamoja.molex.cloud/docs/reference/dotnet/api/Pamoja.Telemetry.html), [install](https://pamoja.molex.cloud/docs/reference/dotnet.html#dotnet-telemetry) |
85
+
86
+ ## Documentation
87
+
88
+ - [`pamoja.telemetry` reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/telemetry.html), every class and function in this module.
89
+ - [The Telemetry guide](https://pamoja.molex.cloud/docs/guides/telemetry.html), with the same example in Rust, TypeScript, and C#.
90
+ - [Every capability](https://pamoja.molex.cloud/docs/), and the [install page](https://pamoja.molex.cloud/docs/install.html).
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,76 @@
1
+ # pamoja-telemetry
2
+
3
+ Observability that ships only what is worth the bytes as link cost rises, while counting everything. One capability of [pamoja](https://github.com/molexxxx/pamoja), one memory-safe Rust core with bindings for TypeScript, Python, and C#.
4
+
5
+ [![read the guide](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-guide.svg)](https://pamoja.molex.cloud/docs/guides/telemetry.html)
6
+ [![documentation](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-docs.svg)](https://pamoja.molex.cloud/docs/)
7
+ [![API reference](https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-api.svg)](https://pamoja.molex.cloud/docs/reference/python/pamoja/telemetry.html)
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ pip install pamoja-telemetry
13
+ ```
14
+
15
+ ```python
16
+ from pamoja import telemetry
17
+ ```
18
+
19
+ This pulls in `pamoja-native`, the compiled engine. `pip install pamoja` is the whole framework in one package.
20
+
21
+ ## Example
22
+
23
+ The script the test suite runs, spliced here as it ran.
24
+
25
+ From [`bindings/python/guides/telemetry.py`](https://github.com/molexxxx/pamoja/blob/main/bindings/python/guides/telemetry.py):
26
+
27
+ ```python
28
+ from pamoja.telemetry import Event, Level, LinkCost, Reporter, link_cost_threshold
29
+
30
+ # The node is willing to record everything, then finds out it is reporting over a metered
31
+ # link, which puts the bar at INFO.
32
+ reporter = Reporter(Level.TRACE)
33
+ reporter.adapt_to(LinkCost.METERED)
34
+ print(f"on a metered link, nothing below {reporter.threshold.value} is sent")
35
+
36
+ # Routine detail stops going out. A reading and the warning that follows it still do, and
37
+ # a shipped event comes back with the measurement that triggered it.
38
+ tick = reporter.record(Event(Level.DEBUG, "loop.tick"))
39
+ reading = reporter.record(Event(Level.INFO, "reading.ok", 4.8))
40
+ print(f"loop.tick sent: {tick is not None}")
41
+ print(f"reading.ok sent: {reading is not None}")
42
+ warned = reporter.record(Event(Level.WARN, "battery.low", 0.18))
43
+ print(f"sent {warned.code} carrying {warned.value}")
44
+
45
+ # The node falls back to satellite, which raises the bar to WARN. The same reading is no
46
+ # longer worth its bytes; a failure still is.
47
+ reporter.adapt_to(LinkCost.EXPENSIVE)
48
+ dearer = reporter.record(Event(Level.INFO, "reading.ok", 4.9))
49
+ lost = reporter.record(Event(Level.ERROR, "link.lost"))
50
+ print(f"on satellite, reading.ok sent: {dearer is not None}")
51
+ print(f"on satellite, link.lost sent: {lost is not None}")
52
+
53
+ # Only the stream was thinned, not the counts, so every event is still accounted for and
54
+ # the snapshot is what the node ships in place of them.
55
+ counts = reporter.snapshot()
56
+ print(f"of {reporter.total} events, {counts.emitted} went out and {counts.dropped} were counted only")
57
+ ```
58
+
59
+ ## The same capability in every language
60
+
61
+ | Language | Package | Reference |
62
+ | --- | --- | --- |
63
+ | Rust | [`pamoja-telemetry`](https://crates.io/crates/pamoja-telemetry) | [reference](https://pamoja.molex.cloud/docs/reference/rust/pamoja_telemetry/index.html), [docs.rs](https://docs.rs/pamoja-telemetry), [install](https://pamoja.molex.cloud/docs/reference/rust.html#rust-telemetry) |
64
+ | TypeScript | [`@pamoja/telemetry`](https://www.npmjs.com/package/@pamoja/telemetry) | [reference](https://pamoja.molex.cloud/docs/reference/node/modules/_pamoja_telemetry.html), [install](https://pamoja.molex.cloud/docs/reference/node.html#node-telemetry) |
65
+ | Python | [`pamoja-telemetry`](https://pypi.org/project/pamoja-telemetry/) | [reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/telemetry.html), [install](https://pamoja.molex.cloud/docs/reference/python.html#python-telemetry) |
66
+ | C# | [`Pamoja.Telemetry`](https://www.nuget.org/packages/Pamoja.Telemetry) | [reference](https://pamoja.molex.cloud/docs/reference/dotnet/api/Pamoja.Telemetry.html), [install](https://pamoja.molex.cloud/docs/reference/dotnet.html#dotnet-telemetry) |
67
+
68
+ ## Documentation
69
+
70
+ - [`pamoja.telemetry` reference](https://pamoja.molex.cloud/docs/reference/python/pamoja/telemetry.html), every class and function in this module.
71
+ - [The Telemetry guide](https://pamoja.molex.cloud/docs/guides/telemetry.html), with the same example in Rust, TypeScript, and C#.
72
+ - [Every capability](https://pamoja.molex.cloud/docs/), and the [install page](https://pamoja.molex.cloud/docs/install.html).
73
+
74
+ ## License
75
+
76
+ MIT
@@ -0,0 +1,149 @@
1
+ """Idiomatic telemetry facade.
2
+
3
+ A node that ships every event it produces will spend more on reporting than on
4
+ the job it was installed to do, and on a satellite link that is money. A reporter
5
+ ships what is worth its bytes, counts everything either way, and moves its own
6
+ bar as the link gets more expensive, so the aggregate picture survives even when
7
+ the detail cannot be sent.
8
+
9
+ The generated binding decides on the level alone, since that is all the core
10
+ reporter reads. The event a caller writes travels no further than this layer,
11
+ which hands it straight back when it should be shipped.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import enum
17
+ from dataclasses import dataclass
18
+
19
+ from pamoja._native import Reporter as _Reporter
20
+ from pamoja._native import Snapshot
21
+ from pamoja._native import link_cost_threshold as _link_cost_threshold
22
+
23
+ __all__ = [
24
+ "Event",
25
+ "Level",
26
+ "LinkCost",
27
+ "Reporter",
28
+ "Snapshot",
29
+ "link_cost_threshold",
30
+ ]
31
+
32
+
33
+ class Level(str, enum.Enum):
34
+ """How urgent an event is."""
35
+
36
+ #: Fine-grained detail, useful only when chasing a specific problem.
37
+ TRACE = "Trace"
38
+ #: Diagnostic detail for development.
39
+ DEBUG = "Debug"
40
+ #: A normal, noteworthy event.
41
+ INFO = "Info"
42
+ #: Something unexpected that the node recovered from.
43
+ WARN = "Warn"
44
+ #: A failure that needs attention.
45
+ ERROR = "Error"
46
+
47
+
48
+ class LinkCost(str, enum.Enum):
49
+ """What the link back to the network currently costs."""
50
+
51
+ #: Bytes are effectively free, such as on wired power and ethernet.
52
+ FREE = "Free"
53
+ #: Bytes are paid for, such as on a cellular plan.
54
+ METERED = "Metered"
55
+ #: Bytes are scarce, such as on a satellite or long-range radio link.
56
+ EXPENSIVE = "Expensive"
57
+ #: Nothing can be shipped at all.
58
+ OFFLINE = "Offline"
59
+
60
+
61
+ @dataclass(frozen=True)
62
+ class Event:
63
+ """A structured telemetry event.
64
+
65
+ The code is a stable, short label such as ``battery.low`` rather than a
66
+ free-form message, so events stay small and group cleanly into counts.
67
+ """
68
+
69
+ #: How urgent the event is.
70
+ level: Level
71
+ #: A stable, short identifier for what happened.
72
+ code: str
73
+ #: An optional measurement, such as the charge that triggered it.
74
+ value: float | None = None
75
+
76
+
77
+ def link_cost_threshold(cost: LinkCost) -> Level:
78
+ """Return the level a link cost calls for.
79
+
80
+ :param cost: What the link currently costs.
81
+ :returns: The lowest level still worth its bytes at that cost.
82
+ """
83
+ return Level(_link_cost_threshold(cost.value))
84
+
85
+
86
+ class Reporter:
87
+ """Record events, ship the ones worth their bytes, and count them all."""
88
+
89
+ def __init__(self, threshold: Level = Level.INFO) -> None:
90
+ """Create a reporter that ships events at or above ``threshold``.
91
+
92
+ :param threshold: The lowest level to ship.
93
+ """
94
+ self._inner = _Reporter(threshold.value)
95
+
96
+ @property
97
+ def threshold(self) -> Level:
98
+ """The level this reporter is currently shipping from."""
99
+ return Level(self._inner.threshold)
100
+
101
+ @threshold.setter
102
+ def threshold(self, threshold: Level) -> None:
103
+ self._inner.threshold = threshold.value
104
+
105
+ @property
106
+ def total(self) -> int:
107
+ """How many events have been seen across every level."""
108
+ return self._inner.total
109
+
110
+ @property
111
+ def emitted(self) -> int:
112
+ """How many events passed the threshold and were shipped."""
113
+ return self._inner.emitted
114
+
115
+ @property
116
+ def dropped(self) -> int:
117
+ """How many events the threshold dropped."""
118
+ return self._inner.dropped
119
+
120
+ def adapt_to(self, cost: LinkCost) -> None:
121
+ """Move the threshold to match what the link now costs.
122
+
123
+ :param cost: What the link currently costs.
124
+ """
125
+ self._inner.adapt_to(cost.value)
126
+
127
+ def record(self, event: Event) -> Event | None:
128
+ """Record an event, returning it when it should be shipped.
129
+
130
+ :param event: The event that occurred.
131
+ :returns: The same event when it passed the threshold, or ``None`` when
132
+ it was counted and dropped.
133
+ """
134
+ return event if self._inner.record(event.level.value) else None
135
+
136
+ def count(self, level: Level) -> int:
137
+ """Return how many events have been seen at a level, shipped or not.
138
+
139
+ :param level: The level to count.
140
+ :returns: The number of events recorded at that level.
141
+ """
142
+ return self._inner.count(level.value)
143
+
144
+ def snapshot(self) -> Snapshot:
145
+ """Take a snapshot of the counters to ship in place of the stream.
146
+
147
+ :returns: The per-level counts and the shipped and dropped totals.
148
+ """
149
+ return self._inner.snapshot()
File without changes
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pamoja-telemetry"
7
+ version = "0.1.18"
8
+ description = "Observability that ships only what is worth the bytes as link cost rises, while counting everything."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ license-files = ["LICENSE-MIT"]
12
+ requires-python = ">=3.10"
13
+ authors = [{ name = "molexxxx" }]
14
+ keywords = ["pamoja", "iot", "robotics", "telemetry"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ "Typing :: Typed",
20
+ ]
21
+ dependencies = [
22
+ "pamoja-native==0.1.18",
23
+ ]
24
+
25
+ [project.urls]
26
+ Repository = "https://github.com/molexxxx/pamoja"
27
+ Documentation = "https://pamoja.molex.cloud/docs/guides/telemetry.html"
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["pamoja"]