uns-kit 0.0.4__py3-none-any.whl → 0.0.6__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.
@@ -0,0 +1,101 @@
1
+ import asyncio
2
+ import math
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+
6
+ from uns_kit import UnsConfig, UnsMqttClient, TopicBuilder
7
+
8
+
9
+ def load_config() -> UnsConfig:
10
+ cfg_path = Path("config.json")
11
+ return UnsConfig.load(cfg_path)
12
+
13
+
14
+ def simulate_sensor_value(step: int) -> float:
15
+ base_value = 42.0
16
+ fast_cycle = math.sin(step / 5.0) * 3.0
17
+ slow_cycle = math.sin(step / 25.0) * 6.0
18
+ ripple = math.sin(step / 2.0 + math.pi / 4.0) * 0.5
19
+ value = base_value + fast_cycle + slow_cycle + ripple
20
+ return round(value, 2)
21
+
22
+
23
+ def prompt_bool(prompt: str, default_yes: bool = True) -> bool:
24
+ suffix = " (Y/n) " if default_yes else " (y/N) "
25
+ answer = input(prompt + suffix).strip().lower()
26
+ if not answer:
27
+ return default_yes
28
+ return answer in ("y", "yes")
29
+
30
+
31
+ def prompt_int(prompt: str, default: int, suffix: str = "") -> int:
32
+ answer = input(f"{prompt} (default {default}{suffix}) ").strip()
33
+ try:
34
+ return int(answer) if answer else default
35
+ except ValueError:
36
+ return default
37
+
38
+
39
+ def prompt_topic(prompt: str, default: str) -> str:
40
+ answer = input(f"{prompt} (default {default}) ").strip()
41
+ return answer or default
42
+
43
+
44
+ def log_info(message: str) -> None:
45
+ timestamp = datetime.now().isoformat(timespec="seconds")
46
+ print(f"{timestamp} [INFO] {message}")
47
+
48
+
49
+ async def main() -> None:
50
+ cfg = load_config()
51
+ host = f"{cfg.host}:{cfg.port}" if cfg.port else cfg.host
52
+
53
+ if not prompt_bool(f"Would you like to continue with load-test on {host}?"):
54
+ print("Load test aborted.")
55
+ return
56
+
57
+ count = prompt_int("How many iterations should be run?", 100, "")
58
+ delay_ms = prompt_int("What should be the delay between intervals in milliseconds?", 0, " ms")
59
+ topic = prompt_topic("Topic to publish to", "raw/data")
60
+ retain = prompt_bool("Retain each published message so late subscribers see the last value?", True)
61
+ delay_s = delay_ms / 1000.0
62
+
63
+ log_info(f"Starting load test with {count} messages and {delay_ms} ms delay...")
64
+
65
+ # Use a dedicated identity for load testing to avoid clashing with app processes.
66
+ tb = TopicBuilder(package_name="uns-loadtest", package_version="0.0.0", process_name="load-tester")
67
+ client = UnsMqttClient(
68
+ cfg.host,
69
+ port=cfg.port,
70
+ username=cfg.username or None,
71
+ password=cfg.password or None,
72
+ tls=cfg.tls,
73
+ client_id="uns-loadtest-client",
74
+ topic_builder=tb,
75
+ instance_name="py-load-test",
76
+ reconnect_interval=1,
77
+ )
78
+ await client.connect()
79
+
80
+ start = asyncio.get_event_loop().time()
81
+ for i in range(count):
82
+ now = asyncio.get_event_loop().time()
83
+ sensor_value = simulate_sensor_value(i)
84
+ raw_payload = f"{i},{int(now * 1000)},{sensor_value}"
85
+ await client.publish_raw(topic, raw_payload, retain=retain)
86
+ if delay_s:
87
+ await asyncio.sleep(delay_s)
88
+
89
+ log_info("Sleeping for 50ms.")
90
+ await asyncio.sleep(0.05)
91
+ end = asyncio.get_event_loop().time()
92
+ duration = end - start
93
+ rate = count / duration if duration > 0 else 0
94
+ log_info(f"Load test completed in {duration:.2f} seconds.")
95
+ log_info(f"Message rate: {rate:.2f} msg/s.")
96
+
97
+ await client.close()
98
+
99
+
100
+ if __name__ == "__main__":
101
+ asyncio.run(main())
uns_kit/topic_builder.py CHANGED
@@ -33,6 +33,16 @@ class TopicBuilder:
33
33
  def handover_topic(self) -> str:
34
34
  return f"{self._base}handover"
35
35
 
36
+ def wildcard_active_topic(self) -> str:
37
+ parts = self._base.strip("/").split("/")
38
+ if len(parts) < 2:
39
+ raise ValueError("processStatusTopic must follow 'uns-infra/<package>/<version>/<process>/'")
40
+ return "/".join(parts[:2]) + "/+/+/active"
41
+
42
+ def instance_status_topic(self, instance_name: str) -> str:
43
+ sanitized = self.sanitize_topic_part(instance_name)
44
+ return f"{self._base}{sanitized}/"
45
+
36
46
  @staticmethod
37
47
  def extract_base_topic(full_topic: str) -> str:
38
48
  parts = full_topic.split("/")
@@ -0,0 +1,237 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime, timezone
5
+ from enum import Enum
6
+ from typing import Any, Dict, Optional
7
+
8
+ from .client import UnsMqttClient
9
+ from .packet import UnsPacket, isoformat
10
+ from .proxy import UnsProxy
11
+ from .topic_builder import TopicBuilder
12
+
13
+
14
+ class MessageMode(str, Enum):
15
+ RAW = "raw"
16
+ DELTA = "delta"
17
+ BOTH = "both"
18
+
19
+
20
+ @dataclass
21
+ class LastValueEntry:
22
+ value: Any
23
+ uom: Optional[str]
24
+ timestamp: datetime
25
+
26
+
27
+ class UnsMqttProxy(UnsProxy):
28
+ def __init__(
29
+ self,
30
+ host: str,
31
+ *,
32
+ process_name: str,
33
+ instance_name: str,
34
+ package_name: str = "uns-kit",
35
+ package_version: str = "0.0.1",
36
+ port: Optional[int] = None,
37
+ username: Optional[str] = None,
38
+ password: Optional[str] = None,
39
+ tls: bool = False,
40
+ client_id: Optional[str] = None,
41
+ keepalive: int = 60,
42
+ clean_session: bool = True,
43
+ ) -> None:
44
+ self.topic_builder = TopicBuilder(package_name, package_version, process_name)
45
+ self.instance_status_topic = self.topic_builder.instance_status_topic(instance_name)
46
+ self.client = UnsMqttClient(
47
+ host,
48
+ port=port,
49
+ username=username,
50
+ password=password,
51
+ tls=tls,
52
+ client_id=client_id,
53
+ keepalive=keepalive,
54
+ clean_session=clean_session,
55
+ topic_builder=self.topic_builder,
56
+ instance_name=instance_name,
57
+ publisher_active=True,
58
+ subscriber_active=True,
59
+ )
60
+ super().__init__(self.client, self.instance_status_topic, instance_name)
61
+ self._last_values: Dict[str, LastValueEntry] = {}
62
+ self._sequence_ids: Dict[str, int] = {}
63
+
64
+ async def connect(self) -> None:
65
+ await self.client.connect()
66
+ await self.start()
67
+
68
+ async def close(self) -> None:
69
+ await self.stop()
70
+ await self.client.close()
71
+
72
+ async def publish_message(self, topic: str, payload: str | bytes) -> None:
73
+ await self.client.publish_raw(topic, payload)
74
+
75
+ async def publish_packet(self, topic: str, packet: Dict[str, Any]) -> None:
76
+ await self.client.publish_packet(topic, packet)
77
+
78
+ async def publish_mqtt_message(self, mqtt_message: Dict[str, Any], mode: MessageMode = MessageMode.RAW) -> None:
79
+ attrs = mqtt_message.get("attributes")
80
+ if attrs is None:
81
+ raise ValueError("mqtt_message must include attributes")
82
+ if not isinstance(attrs, list):
83
+ attrs = [attrs]
84
+
85
+ base_topic = mqtt_message.get("topic", "")
86
+ asset = mqtt_message.get("asset")
87
+ asset_description = mqtt_message.get("assetDescription")
88
+ object_type = mqtt_message.get("objectType")
89
+ object_type_description = mqtt_message.get("objectTypeDescription")
90
+ object_id = mqtt_message.get("objectId")
91
+
92
+ for attr in attrs:
93
+ attribute = attr.get("attribute")
94
+ if attribute is None:
95
+ raise ValueError("attribute is required")
96
+ description = attr.get("description")
97
+ tags = attr.get("tags")
98
+ attribute_needs_persistence = attr.get("attributeNeedsPersistence")
99
+
100
+ message = attr.get("message")
101
+ if message is None:
102
+ if "data" in attr:
103
+ message = {
104
+ "data": attr["data"],
105
+ **({"createdAt": attr["createdAt"]} if attr.get("createdAt") else {}),
106
+ **({"expiresAt": attr["expiresAt"]} if attr.get("expiresAt") else {}),
107
+ }
108
+ elif "table" in attr:
109
+ message = {
110
+ "table": attr["table"],
111
+ **({"createdAt": attr["createdAt"]} if attr.get("createdAt") else {}),
112
+ **({"expiresAt": attr["expiresAt"]} if attr.get("expiresAt") else {}),
113
+ }
114
+ else:
115
+ raise ValueError("Attribute entry must include exactly one of data/table/message")
116
+
117
+ packet = UnsPacket.from_message(message)
118
+
119
+ msg = {
120
+ "topic": base_topic,
121
+ "asset": asset,
122
+ "assetDescription": asset_description,
123
+ "objectType": object_type,
124
+ "objectTypeDescription": object_type_description,
125
+ "objectId": object_id,
126
+ "attribute": attribute,
127
+ "description": description or attribute,
128
+ "tags": tags,
129
+ "attributeNeedsPersistence": attribute_needs_persistence,
130
+ "packet": packet,
131
+ }
132
+
133
+ if mode == MessageMode.RAW:
134
+ await self._process_and_publish(msg, value_is_cumulative=False)
135
+ elif mode == MessageMode.DELTA:
136
+ delta_msg = dict(msg)
137
+ delta_msg["attribute"] = f"{attribute}-delta"
138
+ delta_msg["description"] = f"{msg['description']} (delta)"
139
+ await self._process_and_publish(delta_msg, value_is_cumulative=True)
140
+ elif mode == MessageMode.BOTH:
141
+ await self._process_and_publish(msg, value_is_cumulative=False)
142
+ delta_msg = dict(msg)
143
+ delta_msg["attribute"] = f"{attribute}-delta"
144
+ delta_msg["description"] = f"{msg['description']} (delta)"
145
+ await self._process_and_publish(delta_msg, value_is_cumulative=True)
146
+
147
+ def _resolve_object_identity(self, msg: Dict[str, Any]) -> None:
148
+ topic = msg.get("topic", "")
149
+ provided_type = msg.get("objectType")
150
+ provided_id = msg.get("objectId")
151
+ provided_asset = msg.get("asset")
152
+
153
+ parts = [p for p in topic.split("/") if p]
154
+ parsed_type = parts[-2] if len(parts) >= 2 else None
155
+ parsed_id = parts[-1] if len(parts) >= 1 else None
156
+ parsed_asset = parts[-3] if len(parts) >= 3 else None
157
+
158
+ msg["objectType"] = provided_type or parsed_type
159
+ msg["objectId"] = provided_id or parsed_id or "main"
160
+ msg["asset"] = provided_asset or parsed_asset
161
+
162
+ def _normalize_topic(self, topic: str) -> str:
163
+ return topic if topic.endswith("/") else f"{topic}/"
164
+
165
+ async def _process_and_publish(self, msg: Dict[str, Any], *, value_is_cumulative: bool) -> None:
166
+ self._resolve_object_identity(msg)
167
+ base_topic = self._normalize_topic(msg.get("topic", ""))
168
+
169
+ packet = msg["packet"]
170
+ message = packet.get("message", {})
171
+ data = message.get("data")
172
+ table = message.get("table")
173
+
174
+ attribute_type = "Data" if data is not None else "Table" if table is not None else None
175
+ data_group = ""
176
+ if isinstance(data, dict):
177
+ data_group = data.get("dataGroup") or ""
178
+ if isinstance(table, dict):
179
+ data_group = table.get("dataGroup") or ""
180
+
181
+ await self.register_unique_topic(
182
+ {
183
+ "timestamp": isoformat(datetime.now(timezone.utc)),
184
+ "topic": base_topic,
185
+ "asset": msg.get("asset"),
186
+ "assetDescription": msg.get("assetDescription"),
187
+ "objectType": msg.get("objectType"),
188
+ "objectTypeDescription": msg.get("objectTypeDescription"),
189
+ "objectId": msg.get("objectId"),
190
+ "attribute": msg.get("attribute"),
191
+ "attributeType": attribute_type,
192
+ "description": msg.get("description"),
193
+ "tags": msg.get("tags"),
194
+ "attributeNeedsPersistence": msg.get("attributeNeedsPersistence"),
195
+ "dataGroup": data_group,
196
+ }
197
+ )
198
+
199
+ publish_topic = (
200
+ f"{base_topic}"
201
+ f"{msg.get('asset') + '/' if msg.get('asset') else ''}"
202
+ f"{msg.get('objectType') + '/' if msg.get('objectType') else ''}"
203
+ f"{msg.get('objectId') + '/' if msg.get('objectId') else ''}"
204
+ f"{msg.get('attribute')}"
205
+ )
206
+
207
+ seq_id = self._sequence_ids.get(base_topic, 0)
208
+ self._sequence_ids[base_topic] = seq_id + 1
209
+ packet["sequenceId"] = seq_id
210
+
211
+ if isinstance(data, dict):
212
+ time_value = data.get("time")
213
+ if not time_value:
214
+ time_value = UnsPacket.data(value=0)["message"]["data"]["time"]
215
+ data["time"] = time_value
216
+ current_time = datetime.fromisoformat(time_value.replace("Z", "+00:00"))
217
+ new_value = data.get("value")
218
+ new_uom = data.get("uom")
219
+ last = self._last_values.get(publish_topic)
220
+ if last:
221
+ interval_ms = int((current_time - last.timestamp).total_seconds() * 1000)
222
+ packet["interval"] = interval_ms
223
+ if value_is_cumulative and isinstance(new_value, (int, float)) and isinstance(last.value, (int, float)):
224
+ delta = new_value - last.value
225
+ data["value"] = delta
226
+ data["time"] = isoformat(current_time)
227
+ self._last_values[publish_topic] = LastValueEntry(new_value, new_uom, current_time)
228
+ await self.client.publish_raw(publish_topic, UnsPacket.to_json(packet))
229
+ else:
230
+ self._last_values[publish_topic] = LastValueEntry(new_value, new_uom, current_time)
231
+ # For delta mode with no previous value, skip to avoid bogus delta; otherwise publish.
232
+ if not value_is_cumulative:
233
+ await self.client.publish_raw(publish_topic, UnsPacket.to_json(packet))
234
+ elif isinstance(table, dict):
235
+ await self.client.publish_raw(publish_topic, UnsPacket.to_json(packet))
236
+ else:
237
+ raise ValueError("packet.message must include data or table")
uns_kit/version.py ADDED
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ try: # pragma: no cover - best-effort metadata fetch
4
+ from importlib.metadata import version
5
+
6
+ __version__ = version("uns-kit")
7
+ except Exception: # fallback for editable/local
8
+ __version__ = "0.0.0"
9
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uns-kit
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: Lightweight Python UNS MQTT client (pub/sub + infra topics)
5
5
  License: MIT
6
6
  Author: Aljoša Vister
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Programming Language :: Python :: 3.14
16
16
  Requires-Dist: asyncio-mqtt (>=0.16.1,<0.17.0)
17
17
  Requires-Dist: click (>=8.1.7,<9.0.0)
18
+ Requires-Dist: paho-mqtt (<2)
18
19
  Description-Content-Type: text/markdown
19
20
 
20
21
  # uns-kit (Python)
@@ -22,8 +23,8 @@ Description-Content-Type: text/markdown
22
23
  Lightweight UNS MQTT client for Python. Provides:
23
24
  - Topic builder compatible with UNS infra topics (`uns-infra/<package>/<version>/<process>/`).
24
25
  - Async publish/subscribe via MQTT v5 (using `asyncio-mqtt`).
25
- - Infra status topics (`alive`, `uptime`) with MQTT will.
26
- - Minimal UNS packet builder/parser (data/table).
26
+ - Process + instance status topics (active/heap/uptime/alive + stats).
27
+ - Minimal UNS packet builder/parser (data/table) aligned with TS core.
27
28
 
28
29
  ## Install (editable)
29
30
  ```bash
@@ -42,20 +43,21 @@ poetry run uns-kit-py write-config --path config.json
42
43
  ## Quick start
43
44
  ```python
44
45
  import asyncio
45
- from uns_kit import UnsMqttClient, TopicBuilder, UnsPacket
46
+ from uns_kit import UnsConfig, UnsPacket, UnsProxyProcess
46
47
 
47
48
  async def main():
48
- tb = TopicBuilder(package_name="uns-kit", package_version="0.0.1", process_name="py-demo")
49
- client = UnsMqttClient(host="mqtt-broker", topic_builder=tb, reconnect_interval=1)
50
- await client.connect()
49
+ process = UnsProxyProcess("mqtt-broker", config=UnsConfig(host="mqtt-broker"))
50
+ await process.start()
51
+ mqtt = await process.create_mqtt_proxy("py")
51
52
 
52
53
  # Subscribe
53
- async with client.messages("uns-infra/#") as messages:
54
- await client.publish_packet("raw/data/", UnsPacket.data(value=1, uom="count"))
54
+ async with mqtt.client.messages("uns-infra/#") as messages:
55
+ await mqtt.publish_packet("raw/data/", UnsPacket.data(value=1, uom="count"))
55
56
  msg = await messages.__anext__()
56
57
  print(msg.topic, msg.payload.decode())
57
58
 
58
- await client.close()
59
+ await mqtt.close()
60
+ await process.stop()
59
61
 
60
62
  asyncio.run(main())
61
63
  ```
@@ -69,6 +71,7 @@ async for msg in client.resilient_messages("uns-infra/#"):
69
71
  ### Examples
70
72
  - `examples/publish.py` — publish 5 data packets.
71
73
  - `examples/subscribe.py` — resilient subscription with auto-reconnect.
74
+ - `examples/load_test.py` — interactive publish burst.
72
75
 
73
76
  ### Create a new project
74
77
  ```bash
@@ -78,8 +81,22 @@ poetry install
78
81
  poetry run python src/main.py
79
82
  ```
80
83
 
84
+ ### Create a sandbox app in this repo
85
+ From the monorepo root:
86
+ ```bash
87
+ pnpm run py:sandbox
88
+ ```
89
+ This creates `sandbox-app-py/` using the default Python template.
90
+
81
91
  ## Notes
82
- - Default QoS is 0; will message is retained on `<statusTopic>alive`.
83
- - Uptime is published every 10 seconds on `<statusTopic>uptime`.
84
- - Packet shape mirrors the TypeScript core: `{"version":1,"message":{"data":{...}},"sequenceId":0}`.
92
+ - Default QoS is 0.
93
+ - Instance status topics are published every 10 seconds; stats every 60 seconds.
94
+ - Packet shape mirrors the TypeScript core: `{"version":"1.3.0","message":{"data":{...}},"sequenceId":0}`.
95
+
96
+ ## TODO (parity with TS core)
97
+ - Handover manager (cross-version active detection + handover_* messages).
98
+ - Publish throttling/queue.
99
+ - Status parity (publisher/subscriber active flags everywhere, richer metrics).
100
+ - API endpoints registry (to mirror @uns-kit/api produced endpoints).
101
+ - Optional: dictionary/measurement helpers + CLI wrapper.
85
102
 
@@ -0,0 +1,19 @@
1
+ uns_kit/__init__.py,sha256=oLhYBOJkoYXBJGN7VDtJpMXAb1v5qFVphun5QgFwthw,564
2
+ uns_kit/cli.py,sha256=J6TKYeBC0WB_OsLboehMBkedb16Y0w3cuDXhk3a6QUY,5734
3
+ uns_kit/client.py,sha256=GjlGOTPuaxeLlgkfcJWpxMId6nLeZNsZTNBfOYAe8YY,14892
4
+ uns_kit/config.py,sha256=K9FIvFB_zk59I_9XW5PA7sDRJOMl-5-3VCHbpyLgxyM,2899
5
+ uns_kit/packet.py,sha256=M-JJ0--FVe-iFPGnBUFynx_0iKB9F3NFwvETJ7yKibc,5874
6
+ uns_kit/proxy.py,sha256=H1Afs71rdb7DTHYp_8qeoM8uN5vOvAzYK50u9JszXQQ,2338
7
+ uns_kit/proxy_process.py,sha256=If8HXd_YAf1xYjzy8BA73Jo0DRh0-3WpHHIoVcIbS-Q,3906
8
+ uns_kit/status_monitor.py,sha256=8XXBaUHh8SW7gX4d3EtChnEkgQ7rnQ14tEHb0jDB25I,3022
9
+ uns_kit/templates/default/README.md,sha256=5_o_xowRuzHtn3NzibO-Ysq6iTTAuYbvtTFn27dH-yU,1251
10
+ uns_kit/templates/default/pyproject.toml,sha256=JT2gYvmofwBuS7FI7U0i_5U3TzyiXrwU6w-bz-cTvKU,540
11
+ uns_kit/templates/default/src/data_example.py,sha256=8D1CyGVsbeKzZV79l8zaX-hqz_gxrA_ps2TA-GwzLQI,4381
12
+ uns_kit/templates/default/src/load_test.py,sha256=SjhycCOTz2Aw2si50iYGO8wHeQ9F_tFCMqLBALuEyJA,3290
13
+ uns_kit/topic_builder.py,sha256=UYC2SS9ptHopeyQ3ud1HEg1IHr5RJ7X2DEkfZC1CM5Y,1938
14
+ uns_kit/uns_mqtt_proxy.py,sha256=QzG0E42r7n33Z4Ri70-DxTlVoM-wj0_wAtnbMyaet08,9831
15
+ uns_kit/version.py,sha256=vmk-z_o25XOVgX5lSUrECllqau8NL7B_pkKsSmwhmX0,247
16
+ uns_kit-0.0.6.dist-info/METADATA,sha256=HCwSvEURWhcRDB7noKwtNt72AL639ZAUlJK5r8XPX0c,3306
17
+ uns_kit-0.0.6.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
18
+ uns_kit-0.0.6.dist-info/entry_points.txt,sha256=sLvTioiJQfGczUD-ODVx2xwwtHcGKjlIOn8t_Lt87Pg,47
19
+ uns_kit-0.0.6.dist-info/RECORD,,
@@ -1,27 +0,0 @@
1
- import asyncio
2
- import json
3
- from pathlib import Path
4
- from uns_kit import UnsMqttClient, TopicBuilder, UnsPacket
5
-
6
-
7
- async def main():
8
- cfg = json.loads(Path("config.json").read_text())
9
- tb = TopicBuilder(cfg["packageName"], cfg["packageVersion"], cfg["processName"])
10
- client = UnsMqttClient(
11
- cfg["host"],
12
- port=cfg.get("port"),
13
- username=cfg.get("username") or None,
14
- password=cfg.get("password") or None,
15
- tls=cfg.get("tls", False),
16
- client_id=cfg.get("clientId"),
17
- topic_builder=tb,
18
- reconnect_interval=1,
19
- )
20
- await client.connect()
21
- await client.publish_packet("raw/data/", UnsPacket.data(value=1, uom="count"))
22
- async for msg in client.resilient_messages("uns-infra/#"):
23
- print(msg.topic, msg.payload.decode())
24
-
25
-
26
- if __name__ == "__main__":
27
- asyncio.run(main())
@@ -1,13 +0,0 @@
1
- uns_kit/__init__.py,sha256=-4R72u-uzg2ib2Z1SFQiSX5KSNYY6oMqVvGoVPI3eQs,290
2
- uns_kit/cli.py,sha256=USq7bHl6sL75s--Y_nV2j7v1JScZWehZp92GyqYgjyw,5552
3
- uns_kit/client.py,sha256=bFXAKmuR06mSlO8tsAoeB7jv9RQgw1a_AIbXQNpNFTw,5815
4
- uns_kit/config.py,sha256=mRMiuxGV6CEIBDVZUJQqYIeGxKzfBhlXCcGNXnVtBj4,869
5
- uns_kit/packet.py,sha256=qd6BrCv_JE8fxh-iaJBAVhUZvY28lhiDr9F1At8CXjw,1675
6
- uns_kit/templates/default/README.md,sha256=IQ1zvCSzigE3foKBLkDv9UZnZEScuS6V19oCSCJPvXw,149
7
- uns_kit/templates/default/pyproject.toml,sha256=dxSghe6RiljsXfz4Gt_YXB1g-P_W2jiYUJk6ug4TytE,362
8
- uns_kit/templates/default/src/main.py,sha256=0mxq9Oa00UwqlEiJIBnlDiulBPECJ4YSojqjv6tonTk,850
9
- uns_kit/topic_builder.py,sha256=JGYdYynRWWSwzXaCaRLjap0-du13eSc6qe1V0GrD_ak,1492
10
- uns_kit-0.0.4.dist-info/METADATA,sha256=CjegZQzS1ZMvRyMEXB7p21RaejS7cidggXYvKgZ_QMs,2736
11
- uns_kit-0.0.4.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
12
- uns_kit-0.0.4.dist-info/entry_points.txt,sha256=sLvTioiJQfGczUD-ODVx2xwwtHcGKjlIOn8t_Lt87Pg,47
13
- uns_kit-0.0.4.dist-info/RECORD,,