@uns-kit/cli 0.0.32 → 0.0.35

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.
Files changed (29) hide show
  1. package/package.json +2 -2
  2. package/templates/api/src/examples/api-example.ts +1 -0
  3. package/templates/cron/src/examples/cron-example.ts +1 -0
  4. package/templates/default/config.json +3 -1
  5. package/templates/default/src/examples/table-example.ts +2 -1
  6. package/templates/python/app/README.md +8 -0
  7. package/templates/python/app/__init__.py +0 -0
  8. package/templates/python/examples/README.md +90 -40
  9. package/templates/python/examples/__init__.py +0 -0
  10. package/templates/python/examples/api_handler.py +26 -99
  11. package/templates/python/examples/data_publish.py +11 -0
  12. package/templates/python/examples/data_subscribe.py +6 -122
  13. package/templates/python/examples/data_transformer.py +13 -143
  14. package/templates/python/examples/table_transformer.py +14 -161
  15. package/templates/python/gateway/__init__.py +0 -0
  16. package/templates/python/gateway/cli.py +75 -0
  17. package/templates/python/gateway/client.py +155 -0
  18. package/templates/python/gateway/manager.py +97 -0
  19. package/templates/python/main.py +1 -0
  20. package/templates/python/pyproject.toml +5 -0
  21. package/templates/python/scripts/setup.sh +37 -14
  22. package/templates/temporal/src/examples/temporal-example.ts +1 -0
  23. package/templates/python/examples/api_register_and_serve.py +0 -159
  24. package/templates/python/examples/data_publish_once.py +0 -140
  25. package/templates/python/examples/data_publisher_loop.py +0 -142
  26. package/templates/python/gateway_client.py +0 -242
  27. package/templates/python/local/README.md +0 -8
  28. package/templates/python/local/__init__.py +0 -2
  29. package/templates/python/requirements.txt +0 -3
@@ -1,159 +0,0 @@
1
- #!/usr/bin/env python3
2
- from __future__ import annotations
3
-
4
- import argparse
5
- import json
6
- import os
7
- import queue
8
- import time
9
- from typing import Dict
10
- import socket
11
-
12
- import grpc
13
-
14
- import sys, os
15
- # Ensure generated stubs (python/gen) are importable as top-level modules
16
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'gen')))
17
- import uns_gateway_pb2 as pb2
18
- import uns_gateway_pb2_grpc as gw
19
-
20
-
21
- def make_channel(addr: str) -> grpc.Channel:
22
- return grpc.insecure_channel(addr)
23
-
24
-
25
- import atexit, signal, subprocess
26
- _AUTO_GATEWAY_PROC = None
27
- def _cleanup_gateway():
28
- global _AUTO_GATEWAY_PROC
29
- if _AUTO_GATEWAY_PROC and _AUTO_GATEWAY_PROC.poll() is None:
30
- try:
31
- if os.name == 'nt':
32
- _AUTO_GATEWAY_PROC.terminate()
33
- else:
34
- os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
35
- try:
36
- _AUTO_GATEWAY_PROC.wait(timeout=3)
37
- except Exception:
38
- if os.name == 'nt':
39
- _AUTO_GATEWAY_PROC.kill()
40
- else:
41
- os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
42
- except Exception:
43
- pass
44
- _AUTO_GATEWAY_PROC = None
45
-
46
- def _default_addr() -> str:
47
- if os.name == 'nt':
48
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
49
- s.bind(('127.0.0.1', 0))
50
- port = s.getsockname()[1]
51
- return f"127.0.0.1:{port}"
52
- else:
53
- return f"unix:/tmp/uns-gateway-api-register-and-serve-{os.getpid()}.sock"
54
-
55
-
56
- def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
57
- if not addr:
58
- addr = _default_addr()
59
- ch = make_channel(addr)
60
- try:
61
- grpc.channel_ready_future(ch).result(timeout=2)
62
- ch.close(); return addr
63
- except Exception:
64
- ch.close()
65
- if not auto:
66
- return addr
67
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
68
- cli_path = os.path.join(repo_root, "dist", "uns-grpc", "uns-gateway-cli")
69
- popen_kwargs = {}
70
- creationflags = 0
71
- if os.name != 'nt':
72
- popen_kwargs['preexec_fn'] = os.setsid
73
- else:
74
- creationflags = getattr(subprocess, 'CREATE_NEW_PROCESS_GROUP', 0)
75
- suffix = f"py-{os.path.basename(__file__).replace('.py','')}-{os.getpid()}"
76
- proc = subprocess.Popen(["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"], cwd=repo_root, creationflags=creationflags, **popen_kwargs)
77
- global _AUTO_GATEWAY_PROC
78
- _AUTO_GATEWAY_PROC = proc
79
- atexit.register(_cleanup_gateway)
80
- start = time.time()
81
- while time.time() - start < timeout_s:
82
- ch2 = make_channel(addr)
83
- try:
84
- grpc.channel_ready_future(ch2).result(timeout=2)
85
- ch2.close();
86
- try:
87
- wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
88
- except Exception:
89
- wait_s = 11
90
- if wait_s > 0:
91
- time.sleep(wait_s)
92
- return addr
93
- except Exception:
94
- ch2.close(); time.sleep(0.5)
95
- raise RuntimeError("Gateway did not become ready in time")
96
-
97
-
98
- def register(stub: gw.UnsGatewayStub) -> None:
99
- qp = [
100
- pb2.ApiQueryParam(name="filter", type="string", required=True, description="Filter za podatke"),
101
- pb2.ApiQueryParam(name="limit", type="number", required=False, description="Koliko podatkov želiš"),
102
- ]
103
- for idx, tag in [(1, "Tag1"), (2, "Tag2")]:
104
- res = stub.RegisterApiGet(pb2.RegisterApiGetRequest(
105
- topic="example/",
106
- attribute=f"summary-{idx}",
107
- api_description=f"Test API endpoint {idx}",
108
- tags=[tag],
109
- query_params=qp,
110
- ))
111
- if not res.ok:
112
- raise RuntimeError(f"Register failed: {res.error}")
113
-
114
-
115
- def serve(stub: gw.UnsGatewayStub, echo: bool) -> None:
116
- q: "queue.Queue[pb2.ApiEventResponse|None]" = queue.Queue()
117
-
118
- def req_iter():
119
- while True:
120
- item = q.get()
121
- if item is None:
122
- break
123
- yield item
124
-
125
- stream = stub.ApiEventStream(req_iter())
126
- try:
127
- for ev in stream:
128
- if echo:
129
- q.put(pb2.ApiEventResponse(id=ev.id, status=200, body="OK"))
130
- continue
131
- # Build JSON response
132
- path = ev.path
133
- query: Dict[str, str] = dict(ev.query)
134
- body = {
135
- "status": "OK",
136
- "endpoint": path,
137
- "query": query,
138
- }
139
- q.put(pb2.ApiEventResponse(id=ev.id, status=200, headers={"Content-Type": "application/json"}, body=json.dumps(body)))
140
- except KeyboardInterrupt:
141
- q.put(None)
142
-
143
-
144
- def main():
145
- parser = argparse.ArgumentParser(description="Register and serve API endpoints via gateway")
146
- parser.add_argument("--addr", default=None)
147
- parser.add_argument("--auto", action="store_true")
148
- parser.add_argument("--echo", action="store_true")
149
- args = parser.parse_args()
150
-
151
- addr = ensure_gateway_running(args.addr, auto=args.auto)
152
- with make_channel(addr) as ch:
153
- stub = gw.UnsGatewayStub(ch)
154
- register(stub)
155
- serve(stub, args.echo)
156
-
157
-
158
- if __name__ == "__main__":
159
- main()
@@ -1,140 +0,0 @@
1
- #!/usr/bin/env python3
2
- from __future__ import annotations
3
-
4
- import argparse
5
- import os
6
- import time
7
- from datetime import datetime, timezone
8
- import socket
9
-
10
- import grpc
11
-
12
- import sys, os
13
- # Ensure generated stubs (python/gen) are importable as top-level modules
14
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'gen')))
15
- import uns_gateway_pb2 as pb2
16
- import uns_gateway_pb2_grpc as gw
17
-
18
-
19
- def iso_now() -> str:
20
- dt = datetime.now(timezone.utc)
21
- return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
22
-
23
-
24
- def make_channel(addr: str) -> grpc.Channel:
25
- return grpc.insecure_channel(addr)
26
-
27
-
28
- import atexit, signal, subprocess
29
- _AUTO_GATEWAY_PROC = None
30
-
31
- def _cleanup_gateway():
32
- global _AUTO_GATEWAY_PROC
33
- if _AUTO_GATEWAY_PROC and _AUTO_GATEWAY_PROC.poll() is None:
34
- try:
35
- if os.name == 'nt':
36
- _AUTO_GATEWAY_PROC.terminate()
37
- else:
38
- os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
39
- try:
40
- _AUTO_GATEWAY_PROC.wait(timeout=3)
41
- except Exception:
42
- if os.name == 'nt':
43
- _AUTO_GATEWAY_PROC.kill()
44
- else:
45
- os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
46
- except Exception:
47
- pass
48
- _AUTO_GATEWAY_PROC = None
49
-
50
- def _default_addr() -> str:
51
- if os.name == 'nt':
52
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
53
- s.bind(('127.0.0.1', 0))
54
- port = s.getsockname()[1]
55
- return f"127.0.0.1:{port}"
56
- else:
57
- return f"unix:/tmp/uns-gateway-data-publish-once-{os.getpid()}.sock"
58
-
59
-
60
- def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
61
- if not addr:
62
- addr = _default_addr()
63
- ch = make_channel(addr)
64
- try:
65
- grpc.channel_ready_future(ch).result(timeout=2)
66
- ch.close(); return addr
67
- except Exception:
68
- ch.close()
69
- if not auto:
70
- return addr
71
- # spawn gateway directly to get a handle we can clean up
72
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
73
- cli_path = os.path.join(repo_root, "dist", "uns-grpc", "uns-gateway-cli")
74
- popen_kwargs = {}
75
- creationflags = 0
76
- if os.name != 'nt':
77
- popen_kwargs['preexec_fn'] = os.setsid
78
- else:
79
- creationflags = getattr(subprocess, 'CREATE_NEW_PROCESS_GROUP', 0)
80
- suffix = f"py-{os.path.basename(__file__).replace('.py','')}-{os.getpid()}"
81
- proc = subprocess.Popen(["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"], cwd=repo_root, creationflags=creationflags, **popen_kwargs)
82
- global _AUTO_GATEWAY_PROC
83
- _AUTO_GATEWAY_PROC = proc
84
- atexit.register(_cleanup_gateway)
85
- start = time.time()
86
- while time.time() - start < timeout_s:
87
- ch2 = make_channel(addr)
88
- try:
89
- grpc.channel_ready_future(ch2).result(timeout=2)
90
- ch2.close();
91
- try:
92
- wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
93
- except Exception:
94
- wait_s = 11
95
- if wait_s > 0:
96
- time.sleep(wait_s)
97
- return addr
98
- except Exception:
99
- ch2.close(); time.sleep(0.5)
100
- raise RuntimeError("Gateway did not become ready in time")
101
-
102
-
103
- def publish_once(addr: str | None, out_topic: str, attribute: str, value: float, uom: str, group: str, auto: bool) -> None:
104
- addr = ensure_gateway_running(addr, auto=auto)
105
- # Ensure publisher readiness via gRPC
106
- try:
107
- with make_channel(addr) as ch:
108
- gw.UnsGatewayStub(ch).Ready(pb2.ReadyRequest(timeout_ms=15000, wait_output=True))
109
- except Exception:
110
- pass
111
- with make_channel(addr) as ch:
112
- stub = gw.UnsGatewayStub(ch)
113
- req = pb2.PublishRequest(
114
- topic=out_topic,
115
- attribute=attribute,
116
- data=pb2.Data(time=iso_now(), value_number=float(value), uom=uom, data_group=group),
117
- )
118
- res = stub.Publish(req)
119
- if not res.ok:
120
- print("error:", res.error)
121
- else:
122
- print("OK")
123
-
124
-
125
- def main():
126
- parser = argparse.ArgumentParser(description="Publish one UNS data packet")
127
- parser.add_argument("--addr", default=None)
128
- parser.add_argument("--auto", action="store_true")
129
- parser.add_argument("--out-topic", default="example/")
130
- parser.add_argument("--attribute", default="data-number")
131
- parser.add_argument("--value", type=float, default=42.0)
132
- parser.add_argument("--uom", default="mV")
133
- parser.add_argument("--group", default="electricity")
134
- args = parser.parse_args()
135
-
136
- publish_once(args.addr, args.out_topic, args.attribute, args.value, args.uom, args.group, args.auto)
137
-
138
-
139
- if __name__ == "__main__":
140
- main()
@@ -1,142 +0,0 @@
1
- #!/usr/bin/env python3
2
- from __future__ import annotations
3
-
4
- import argparse
5
- import os
6
- import time
7
- from datetime import datetime, timezone
8
- import socket
9
-
10
- import grpc
11
-
12
- import sys, os
13
- # Ensure generated stubs (python/gen) are importable as top-level modules
14
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'gen')))
15
- import uns_gateway_pb2 as pb2
16
- import uns_gateway_pb2_grpc as gw
17
-
18
-
19
- def iso_now() -> str:
20
- dt = datetime.now(timezone.utc)
21
- return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
22
-
23
-
24
- def make_channel(addr: str) -> grpc.Channel:
25
- return grpc.insecure_channel(addr)
26
-
27
-
28
- import atexit, signal, subprocess
29
- _AUTO_GATEWAY_PROC = None
30
- def _cleanup_gateway():
31
- global _AUTO_GATEWAY_PROC
32
- if _AUTO_GATEWAY_PROC and _AUTO_GATEWAY_PROC.poll() is None:
33
- try:
34
- if os.name == 'nt':
35
- _AUTO_GATEWAY_PROC.terminate()
36
- else:
37
- os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
38
- try:
39
- _AUTO_GATEWAY_PROC.wait(timeout=3)
40
- except Exception:
41
- if os.name == 'nt':
42
- _AUTO_GATEWAY_PROC.kill()
43
- else:
44
- os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
45
- except Exception:
46
- pass
47
- _AUTO_GATEWAY_PROC = None
48
-
49
- def _default_addr() -> str:
50
- if os.name == 'nt':
51
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
52
- s.bind(('127.0.0.1', 0))
53
- port = s.getsockname()[1]
54
- return f"127.0.0.1:{port}"
55
- else:
56
- return f"unix:/tmp/uns-gateway-data-publisher-loop-{os.getpid()}.sock"
57
-
58
-
59
- def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
60
- if not addr:
61
- addr = _default_addr()
62
- ch = make_channel(addr)
63
- try:
64
- grpc.channel_ready_future(ch).result(timeout=2)
65
- ch.close(); return addr
66
- except Exception:
67
- ch.close()
68
- if not auto:
69
- return addr
70
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
71
- cli_path = os.path.join(repo_root, "dist", "uns-grpc", "uns-gateway-cli")
72
- popen_kwargs = {}
73
- creationflags = 0
74
- if os.name != 'nt':
75
- popen_kwargs['preexec_fn'] = os.setsid
76
- else:
77
- creationflags = getattr(subprocess, 'CREATE_NEW_PROCESS_GROUP', 0)
78
- suffix = f"py-{os.path.basename(__file__).replace('.py','')}-{os.getpid()}"
79
- proc = subprocess.Popen(["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"], cwd=repo_root, creationflags=creationflags, **popen_kwargs)
80
- global _AUTO_GATEWAY_PROC
81
- _AUTO_GATEWAY_PROC = proc
82
- atexit.register(_cleanup_gateway)
83
- start = time.time()
84
- while time.time() - start < timeout_s:
85
- ch2 = make_channel(addr)
86
- try:
87
- grpc.channel_ready_future(ch2).result(timeout=2)
88
- ch2.close();
89
- try:
90
- wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
91
- except Exception:
92
- wait_s = 11
93
- if wait_s > 0:
94
- time.sleep(wait_s)
95
- return addr
96
- except Exception:
97
- ch2.close(); time.sleep(0.5)
98
- raise RuntimeError("Gateway did not become ready in time")
99
-
100
-
101
- def loop_publish(addr: str | None, out_topic: str, attribute: str, uom: str, period_ms: int, auto: bool) -> None:
102
- addr = ensure_gateway_running(addr, auto=auto)
103
- # Ensure publisher readiness via gRPC
104
- try:
105
- with make_channel(addr) as ch:
106
- gw.UnsGatewayStub(ch).Ready(pb2.ReadyRequest(timeout_ms=15000, wait_output=True))
107
- except Exception:
108
- pass
109
- with make_channel(addr) as ch:
110
- stub = gw.UnsGatewayStub(ch)
111
- i = 0
112
- try:
113
- while True:
114
- i += 1
115
- req = pb2.PublishRequest(
116
- topic=out_topic,
117
- attribute=attribute,
118
- data=pb2.Data(time=iso_now(), value_number=float(i % 100), uom=uom),
119
- )
120
- res = stub.Publish(req)
121
- if not res.ok:
122
- print("publish error:", res.error)
123
- time.sleep(max(0.0, period_ms / 1000.0))
124
- except KeyboardInterrupt:
125
- pass
126
-
127
-
128
- def main():
129
- parser = argparse.ArgumentParser(description="Publish UNS data packets periodically (cron-like)")
130
- parser.add_argument("--addr", default=None)
131
- parser.add_argument("--auto", action="store_true")
132
- parser.add_argument("--out-topic", default="example/")
133
- parser.add_argument("--attribute", default="data-number")
134
- parser.add_argument("--uom", default="mV")
135
- parser.add_argument("--period-ms", type=int, default=1000)
136
- args = parser.parse_args()
137
-
138
- loop_publish(args.addr, args.out_topic, args.attribute, args.uom, args.period_ms, args.auto)
139
-
140
-
141
- if __name__ == "__main__":
142
- main()
@@ -1,242 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import argparse
4
- import asyncio
5
- import os
6
- import time
7
- from typing import Iterable
8
- import subprocess
9
- import socket
10
-
11
- import grpc
12
-
13
- import sys, os
14
- # Ensure generated stubs (python/gen) are importable as top-level modules
15
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'gen')))
16
- import uns_gateway_pb2 as pb2
17
- import uns_gateway_pb2_grpc as gw
18
-
19
-
20
- def make_channel(addr: str) -> grpc.Channel:
21
- # addr: e.g. unix:/tmp/xyz.sock or host:port
22
- if addr.startswith("unix:"):
23
- return grpc.insecure_channel(addr)
24
- return grpc.insecure_channel(addr)
25
-
26
-
27
- import atexit
28
- import signal
29
-
30
- AUTO_GATEWAY_PROC = None
31
-
32
-
33
- def _cleanup_gateway():
34
- global AUTO_GATEWAY_PROC
35
- if AUTO_GATEWAY_PROC is None:
36
- return
37
- try:
38
- if AUTO_GATEWAY_PROC.poll() is None:
39
- if os.name == "nt":
40
- AUTO_GATEWAY_PROC.terminate()
41
- else:
42
- os.killpg(os.getpgid(AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
43
- try:
44
- AUTO_GATEWAY_PROC.wait(timeout=3)
45
- except Exception:
46
- if os.name == "nt":
47
- AUTO_GATEWAY_PROC.kill()
48
- else:
49
- os.killpg(os.getpgid(AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
50
- except Exception:
51
- pass
52
- AUTO_GATEWAY_PROC = None
53
-
54
-
55
- def _default_addr() -> str:
56
- if os.name == "nt":
57
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
58
- s.bind(("127.0.0.1", 0))
59
- port = s.getsockname()[1]
60
- return f"127.0.0.1:{port}"
61
- else:
62
- script = os.path.basename(sys.argv[0]).replace(".py", "")
63
- return f"unix:/tmp/uns-gateway-{script}-{os.getpid()}.sock"
64
-
65
-
66
- def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
67
- if not addr:
68
- addr = _default_addr()
69
- ch = make_channel(addr)
70
- try:
71
- grpc.channel_ready_future(ch).result(timeout=2)
72
- ch.close()
73
- return addr
74
- except Exception:
75
- ch.close()
76
- if not auto:
77
- return addr
78
- # Try to spawn Node gateway directly (avoid npm wrapping) with fixed address
79
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
80
- cli_path = os.path.join(repo_root, "dist", "uns-grpc", "uns-gateway-cli")
81
- creationflags = 0
82
- popen_kwargs = {}
83
- if os.name != "nt":
84
- popen_kwargs["preexec_fn"] = os.setsid # new process group
85
- else:
86
- creationflags = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
87
- # unique suffix for instances, avoid handover by default in per-script mode
88
- suffix = f"py-{os.path.basename(sys.argv[0]).replace('.py','')}-{os.getpid()}"
89
- proc = subprocess.Popen(["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"], cwd=repo_root, creationflags=creationflags, **popen_kwargs)
90
- # remember and register cleanup
91
- global AUTO_GATEWAY_PROC
92
- AUTO_GATEWAY_PROC = proc
93
- atexit.register(_cleanup_gateway)
94
-
95
- # Wait until channel is ready
96
- start = time.time()
97
- while time.time() - start < timeout_s:
98
- ch2 = make_channel(addr)
99
- try:
100
- grpc.channel_ready_future(ch2).result(timeout=2)
101
- ch2.close()
102
- # Optional handover wait for active publisher/subscriber (default 11s)
103
- try:
104
- wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
105
- except Exception:
106
- wait_s = 11
107
- if wait_s > 0:
108
- time.sleep(wait_s)
109
- return addr
110
- except Exception:
111
- ch2.close()
112
- time.sleep(0.5)
113
- raise RuntimeError("Gateway did not become ready in time")
114
-
115
-
116
- def publish_data(addr: str, *, topic: str, attribute: str, time_iso: str, value_number: float, uom: str = "", data_group: str = "", value_is_cumulative: bool = False):
117
- with make_channel(addr) as ch:
118
- stub = gw.UnsGatewayStub(ch)
119
- req = pb2.PublishRequest(
120
- topic=topic,
121
- attribute=attribute,
122
- data=pb2.Data(time=time_iso, value_number=value_number, uom=uom, data_group=data_group),
123
- value_is_cumulative=value_is_cumulative,
124
- )
125
- res = stub.Publish(req)
126
- if not res.ok:
127
- raise RuntimeError(res.error)
128
-
129
-
130
- def subscribe(addr: str, topics: Iterable[str]):
131
- with make_channel(addr) as ch:
132
- stub = gw.UnsGatewayStub(ch)
133
- # Ensure subscriber readiness if gateway supports it
134
- try:
135
- stub.Ready(pb2.ReadyRequest(timeout_ms=15000, wait_input=True))
136
- except Exception:
137
- pass
138
- stream = stub.Subscribe(pb2.SubscribeRequest(topics=list(topics)))
139
- try:
140
- for msg in stream:
141
- print(f"{msg.topic}: {msg.payload}")
142
- except KeyboardInterrupt:
143
- pass
144
-
145
-
146
- def main():
147
- parser = argparse.ArgumentParser()
148
- parser.add_argument("--addr", default=None, help="Gateway address, e.g. unix:/tmp/uns-gateway.sock or 127.0.0.1:50051 (defaults to unique per-script)")
149
- sub = parser.add_subparsers(dest="cmd", required=True)
150
-
151
- p_pub = sub.add_parser("pub")
152
- p_pub.add_argument("topic")
153
- p_pub.add_argument("attribute")
154
- p_pub.add_argument("time_iso")
155
- p_pub.add_argument("value", type=float)
156
- p_pub.add_argument("--uom", default="")
157
- p_pub.add_argument("--group", default="")
158
- p_pub.add_argument("--cumulative", action="store_true")
159
- p_pub.add_argument("--auto", action="store_true", help="auto start gateway if not running")
160
-
161
- p_sub = sub.add_parser("sub")
162
- p_sub.add_argument("topics", nargs="+")
163
- p_sub.add_argument("--auto", action="store_true", help="auto start gateway if not running")
164
-
165
- p_reg = sub.add_parser("regapi")
166
- p_reg.add_argument("topic")
167
- p_reg.add_argument("attribute")
168
- p_reg.add_argument("--desc", default="")
169
- p_reg.add_argument("--tag", action="append", default=[])
170
- p_reg.add_argument("--param", action="append", default=[], help="query param as name:type:required:desc (e.g., filter:string:true:Filter za podatke)")
171
- p_reg.add_argument("--auto", action="store_true", help="auto start gateway if not running")
172
-
173
- p_unreg = sub.add_parser("unregapi")
174
- p_unreg.add_argument("topic")
175
- p_unreg.add_argument("attribute")
176
- p_unreg.add_argument("--auto", action="store_true", help="auto start gateway if not running")
177
-
178
- p_stream = sub.add_parser("apistream")
179
- p_stream.add_argument("--echo", action="store_true", help="auto 200 OK echo response")
180
- p_stream.add_argument("--auto", action="store_true", help="auto start gateway if not running")
181
-
182
- args = parser.parse_args()
183
- if args.cmd == "pub":
184
- addr = ensure_gateway_running(args.addr, auto=args.auto)
185
- # Ensure publisher readiness
186
- try:
187
- with make_channel(addr) as ch:
188
- gw.UnsGatewayStub(ch).Ready(pb2.ReadyRequest(timeout_ms=15000, wait_output=True))
189
- except Exception:
190
- pass
191
- publish_data(addr, topic=args.topic, attribute=args.attribute, time_iso=args.time_iso, value_number=args.value, uom=args.uom, data_group=args.group, value_is_cumulative=args.cumulative)
192
- elif args.cmd == "sub":
193
- addr = ensure_gateway_running(args.addr, auto=args.auto)
194
- subscribe(addr, args.topics)
195
- elif args.cmd == "regapi":
196
- addr = ensure_gateway_running(args.addr, auto=args.auto)
197
- with make_channel(addr) as ch:
198
- stub = gw.UnsGatewayStub(ch)
199
- params = []
200
- for s in args.param:
201
- try:
202
- name, typ, req, desc = s.split(":", 3)
203
- except ValueError:
204
- name, typ = s.split(":", 1)
205
- req, desc = "false", ""
206
- params.append(pb2.ApiQueryParam(name=name, type=typ, required=(req.lower() in ("true","1")), description=desc))
207
- req = pb2.RegisterApiGetRequest(topic=args.topic, attribute=args.attribute, api_description=args.desc, tags=args.tag, query_params=params)
208
- res = stub.RegisterApiGet(req)
209
- print("registered" if res.ok else f"error: {res.error}")
210
- elif args.cmd == "unregapi":
211
- addr = ensure_gateway_running(args.addr, auto=args.auto)
212
- with make_channel(addr) as ch:
213
- stub = gw.UnsGatewayStub(ch)
214
- res = stub.UnregisterApiGet(pb2.UnregisterApiGetRequest(topic=args.topic, attribute=args.attribute))
215
- print("unregistered" if res.ok else f"error: {res.error}")
216
- elif args.cmd == "apistream":
217
- addr = ensure_gateway_running(args.addr, auto=args.auto)
218
- import queue
219
- import threading
220
- with make_channel(addr) as ch:
221
- stub = gw.UnsGatewayStub(ch)
222
- q: "queue.Queue[pb2.ApiEventResponse|None]" = queue.Queue()
223
-
224
- def req_iter():
225
- while True:
226
- item = q.get()
227
- if item is None:
228
- break
229
- yield item
230
-
231
- stream = stub.ApiEventStream(req_iter())
232
- try:
233
- for ev in stream:
234
- print(f"API {ev.method} {ev.path} query={dict(ev.query)}")
235
- if args.echo:
236
- q.put(pb2.ApiEventResponse(id=ev.id, status=200, body="OK"))
237
- except KeyboardInterrupt:
238
- q.put(None)
239
-
240
-
241
- if __name__ == "__main__":
242
- main()
@@ -1,8 +0,0 @@
1
- This folder is reserved for your project-specific Python code.
2
-
3
- The update tools will NOT overwrite `python/local/**` (and also preserve legacy `python/rtt/**`, `python/venv`, `python/.venv`, and `python/__pycache__`).
4
-
5
- Suggested layout:
6
- - `python/local/` your packages, modules, or CLI scripts
7
- - Reference the gateway client in `python/gateway_client.py` if you interact with the UNS gateway from Python
8
-
@@ -1,2 +0,0 @@
1
- # Project-local Python package placeholder (kept across template updates)
2
-