@uns-kit/cli 0.0.36 → 0.0.37

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 (46) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +128 -128
  3. package/dist/index.js +11 -5
  4. package/package.json +8 -2
  5. package/templates/api/src/examples/api-example.ts +62 -62
  6. package/templates/azure-pipelines.yml +21 -21
  7. package/templates/codegen/codegen.ts +15 -15
  8. package/templates/codegen/src/uns/uns-tags.ts +1 -1
  9. package/templates/codegen/src/uns/uns-topics.ts +1 -1
  10. package/templates/config-files/config-docker.json +26 -26
  11. package/templates/config-files/config-localhost.json +26 -26
  12. package/templates/cron/src/examples/cron-example.ts +46 -46
  13. package/templates/default/README.md +32 -30
  14. package/templates/default/config.json +27 -27
  15. package/templates/default/gitignore +51 -51
  16. package/templates/default/package.json +38 -19
  17. package/templates/default/src/config/project.config.extension.example +23 -23
  18. package/templates/default/src/config/project.config.extension.ts +6 -6
  19. package/templates/default/src/examples/data-example.ts +68 -68
  20. package/templates/default/src/examples/load-test-data.ts +108 -108
  21. package/templates/default/src/examples/table-example.ts +66 -66
  22. package/templates/default/src/examples/uns-gateway-cli.ts +7 -7
  23. package/templates/default/src/index.ts +15 -15
  24. package/templates/default/src/uns/uns-tags.ts +2 -2
  25. package/templates/default/src/uns/uns-topics.ts +2 -2
  26. package/templates/default/tsconfig.json +29 -16
  27. package/templates/python/app/README.md +8 -8
  28. package/templates/python/examples/README.md +134 -134
  29. package/templates/python/examples/api_handler.py +28 -28
  30. package/templates/python/examples/data_publish.py +11 -11
  31. package/templates/python/examples/data_subscribe.py +8 -8
  32. package/templates/python/examples/data_transformer.py +17 -17
  33. package/templates/python/examples/table_transformer.py +15 -15
  34. package/templates/python/gateway/cli.py +75 -75
  35. package/templates/python/gateway/client.py +155 -155
  36. package/templates/python/gateway/manager.py +97 -97
  37. package/templates/python/proto/uns-gateway.proto +102 -102
  38. package/templates/python/pyproject.toml +4 -4
  39. package/templates/python/scripts/setup.sh +87 -87
  40. package/templates/temporal/src/examples/temporal-example.ts +35 -35
  41. package/templates/vscode/.vscode/launch.json +164 -164
  42. package/templates/vscode/.vscode/settings.json +9 -9
  43. package/templates/vscode/uns-kit.code-workspace +13 -13
  44. package/templates/python/gen/__init__.py +0 -1
  45. package/templates/python/gen/uns_gateway_pb2.py +0 -70
  46. package/templates/python/gen/uns_gateway_pb2_grpc.py +0 -312
@@ -1,155 +1,155 @@
1
- from typing import Iterable, Dict, Callable, List
2
- import queue
3
- import json
4
- import threading
5
- from datetime import datetime, timezone
6
- import grpc
7
- from .manager import GatewayManager
8
-
9
- from gen import uns_gateway_pb2 as pb2
10
- from gen import uns_gateway_pb2_grpc as gw
11
-
12
-
13
- def iso_now() -> str:
14
- """Return current UTC time in ISO format with milliseconds."""
15
- dt = datetime.now(timezone.utc)
16
- return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
17
-
18
-
19
- def make_channel(addr: str) -> grpc.Channel:
20
- """Create a gRPC insecure channel."""
21
- return grpc.insecure_channel(addr)
22
-
23
-
24
- class Client:
25
- """
26
- High-level client for UNS gateway.
27
- Automatically manages gateway lifecycle and keeps a persistent stub.
28
- """
29
-
30
- def __init__(self, addr: str | None = None, auto: bool = True, timeout_s: int = 20):
31
- self.manager = GatewayManager(addr=addr, auto=auto, timeout_s=timeout_s)
32
- self.addr = self.manager.ensure_running()
33
- self._ch = make_channel(self.addr)
34
- self._stub = gw.UnsGatewayStub(self._ch)
35
-
36
- # --- Publish ---
37
- def publish_data(self, topic: str, attribute: str, value: float,
38
- time_iso: str | None = None, uom: str = "",
39
- data_group: str = "", cumulative: bool = False):
40
- time_iso = time_iso or iso_now()
41
- req = pb2.PublishRequest(
42
- topic=topic,
43
- attribute=attribute,
44
- data=pb2.Data(
45
- time=time_iso,
46
- value_number=value,
47
- uom=uom,
48
- data_group=data_group
49
- ),
50
- value_is_cumulative=cumulative
51
- )
52
- res = self._stub.Publish(req)
53
- if not res.ok:
54
- raise RuntimeError(res.error)
55
-
56
- def publish_table(self, topic: str, attribute: str, obj: Dict[str, any],
57
- time_iso: str | None = None, data_group: str = "default"):
58
- """
59
- Publish a dictionary as a Table.
60
- Numeric values -> value_number
61
- None -> empty
62
- Other -> value_string
63
- """
64
- time_iso = time_iso or iso_now()
65
- tv_list = []
66
- for k, v in obj.items():
67
- if isinstance(v, (int, float)):
68
- tv_list.append(pb2.TableValue(key=k, value_number=float(v)))
69
- elif v is None:
70
- tv_list.append(pb2.TableValue(key=k))
71
- else:
72
- tv_list.append(pb2.TableValue(key=k, value_string=str(v)))
73
-
74
- req = pb2.PublishRequest(
75
- topic=topic,
76
- attribute=attribute,
77
- table=pb2.Table(time=time_iso, values=tv_list, data_group=data_group)
78
- )
79
- res = self._stub.Publish(req)
80
- if not res.ok:
81
- raise RuntimeError(res.error)
82
- # --- Subscribe ---
83
- def subscribe(self, topics: Iterable[str], callback: Callable[[any], None] | None = None):
84
- """
85
- Subscribe to topics and call the provided callback on each message.
86
- """
87
- stub = self._stub
88
- try:
89
- stub.Ready(pb2.ReadyRequest(timeout_ms=15000, wait_input=True))
90
- except Exception:
91
- pass
92
-
93
- stream = stub.Subscribe(pb2.SubscribeRequest(topics=list(topics)))
94
- try:
95
- for msg in stream:
96
- if callback:
97
- callback(msg)
98
- else:
99
- print(f"{msg.topic}: {msg.payload}")
100
- except KeyboardInterrupt:
101
- pass
102
-
103
- # --- API Register ---
104
- def register_api(self, topic: str, attribute: str, desc: str = "",
105
- tags: list[str] | None = None,
106
- query_params: List[pb2.ApiQueryParam] | None = None):
107
- tags = tags or []
108
- query_params = query_params or []
109
-
110
- res = self._stub.RegisterApiGet(
111
- pb2.RegisterApiGetRequest(
112
- topic=topic,
113
- attribute=attribute,
114
- api_description=desc,
115
- tags=tags,
116
- query_params=query_params
117
- )
118
- )
119
- if not res.ok:
120
- raise RuntimeError(f"Register failed: {res.error}")
121
-
122
- def unregister_api(self, topic: str, attribute: str):
123
- res = self._stub.UnregisterApiGet(
124
- pb2.UnregisterApiGetRequest(topic=topic, attribute=attribute)
125
- )
126
- if not res.ok:
127
- raise RuntimeError(f"Unregister failed: {res.error}")
128
-
129
- # --- API Stream ---
130
- def api_stream(self, echo: bool = False, handle_event: Callable | None = None):
131
- q: "queue.Queue[pb2.ApiEventResponse|None]" = queue.Queue()
132
-
133
- def req_iter():
134
- while True:
135
- item = q.get()
136
- if item is None:
137
- break
138
- yield item
139
-
140
- stream = self._stub.ApiEventStream(req_iter())
141
- try:
142
- for ev in stream:
143
- if handle_event:
144
- resp = handle_event(ev)
145
- if resp:
146
- q.put(resp)
147
- continue
148
- if echo:
149
- q.put(pb2.ApiEventResponse(id=ev.id, status=200, body="OK"))
150
- continue
151
- body = {"status": "OK", "endpoint": ev.path, "query": dict(ev.query)}
152
- q.put(pb2.ApiEventResponse(id=ev.id, status=200, headers={"Content-Type": "application/json"},
153
- body=json.dumps(body)))
154
- except KeyboardInterrupt:
155
- q.put(None)
1
+ from typing import Iterable, Dict, Callable, List
2
+ import queue
3
+ import json
4
+ import threading
5
+ from datetime import datetime, timezone
6
+ import grpc
7
+ from .manager import GatewayManager
8
+
9
+ from gen import uns_gateway_pb2 as pb2
10
+ from gen import uns_gateway_pb2_grpc as gw
11
+
12
+
13
+ def iso_now() -> str:
14
+ """Return current UTC time in ISO format with milliseconds."""
15
+ dt = datetime.now(timezone.utc)
16
+ return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
17
+
18
+
19
+ def make_channel(addr: str) -> grpc.Channel:
20
+ """Create a gRPC insecure channel."""
21
+ return grpc.insecure_channel(addr)
22
+
23
+
24
+ class Client:
25
+ """
26
+ High-level client for UNS gateway.
27
+ Automatically manages gateway lifecycle and keeps a persistent stub.
28
+ """
29
+
30
+ def __init__(self, addr: str | None = None, auto: bool = True, timeout_s: int = 20):
31
+ self.manager = GatewayManager(addr=addr, auto=auto, timeout_s=timeout_s)
32
+ self.addr = self.manager.ensure_running()
33
+ self._ch = make_channel(self.addr)
34
+ self._stub = gw.UnsGatewayStub(self._ch)
35
+
36
+ # --- Publish ---
37
+ def publish_data(self, topic: str, attribute: str, value: float,
38
+ time_iso: str | None = None, uom: str = "",
39
+ data_group: str = "", cumulative: bool = False):
40
+ time_iso = time_iso or iso_now()
41
+ req = pb2.PublishRequest(
42
+ topic=topic,
43
+ attribute=attribute,
44
+ data=pb2.Data(
45
+ time=time_iso,
46
+ value_number=value,
47
+ uom=uom,
48
+ data_group=data_group
49
+ ),
50
+ value_is_cumulative=cumulative
51
+ )
52
+ res = self._stub.Publish(req)
53
+ if not res.ok:
54
+ raise RuntimeError(res.error)
55
+
56
+ def publish_table(self, topic: str, attribute: str, obj: Dict[str, any],
57
+ time_iso: str | None = None, data_group: str = "default"):
58
+ """
59
+ Publish a dictionary as a Table.
60
+ Numeric values -> value_number
61
+ None -> empty
62
+ Other -> value_string
63
+ """
64
+ time_iso = time_iso or iso_now()
65
+ tv_list = []
66
+ for k, v in obj.items():
67
+ if isinstance(v, (int, float)):
68
+ tv_list.append(pb2.TableValue(key=k, value_number=float(v)))
69
+ elif v is None:
70
+ tv_list.append(pb2.TableValue(key=k))
71
+ else:
72
+ tv_list.append(pb2.TableValue(key=k, value_string=str(v)))
73
+
74
+ req = pb2.PublishRequest(
75
+ topic=topic,
76
+ attribute=attribute,
77
+ table=pb2.Table(time=time_iso, values=tv_list, data_group=data_group)
78
+ )
79
+ res = self._stub.Publish(req)
80
+ if not res.ok:
81
+ raise RuntimeError(res.error)
82
+ # --- Subscribe ---
83
+ def subscribe(self, topics: Iterable[str], callback: Callable[[any], None] | None = None):
84
+ """
85
+ Subscribe to topics and call the provided callback on each message.
86
+ """
87
+ stub = self._stub
88
+ try:
89
+ stub.Ready(pb2.ReadyRequest(timeout_ms=15000, wait_input=True))
90
+ except Exception:
91
+ pass
92
+
93
+ stream = stub.Subscribe(pb2.SubscribeRequest(topics=list(topics)))
94
+ try:
95
+ for msg in stream:
96
+ if callback:
97
+ callback(msg)
98
+ else:
99
+ print(f"{msg.topic}: {msg.payload}")
100
+ except KeyboardInterrupt:
101
+ pass
102
+
103
+ # --- API Register ---
104
+ def register_api(self, topic: str, attribute: str, desc: str = "",
105
+ tags: list[str] | None = None,
106
+ query_params: List[pb2.ApiQueryParam] | None = None):
107
+ tags = tags or []
108
+ query_params = query_params or []
109
+
110
+ res = self._stub.RegisterApiGet(
111
+ pb2.RegisterApiGetRequest(
112
+ topic=topic,
113
+ attribute=attribute,
114
+ api_description=desc,
115
+ tags=tags,
116
+ query_params=query_params
117
+ )
118
+ )
119
+ if not res.ok:
120
+ raise RuntimeError(f"Register failed: {res.error}")
121
+
122
+ def unregister_api(self, topic: str, attribute: str):
123
+ res = self._stub.UnregisterApiGet(
124
+ pb2.UnregisterApiGetRequest(topic=topic, attribute=attribute)
125
+ )
126
+ if not res.ok:
127
+ raise RuntimeError(f"Unregister failed: {res.error}")
128
+
129
+ # --- API Stream ---
130
+ def api_stream(self, echo: bool = False, handle_event: Callable | None = None):
131
+ q: "queue.Queue[pb2.ApiEventResponse|None]" = queue.Queue()
132
+
133
+ def req_iter():
134
+ while True:
135
+ item = q.get()
136
+ if item is None:
137
+ break
138
+ yield item
139
+
140
+ stream = self._stub.ApiEventStream(req_iter())
141
+ try:
142
+ for ev in stream:
143
+ if handle_event:
144
+ resp = handle_event(ev)
145
+ if resp:
146
+ q.put(resp)
147
+ continue
148
+ if echo:
149
+ q.put(pb2.ApiEventResponse(id=ev.id, status=200, body="OK"))
150
+ continue
151
+ body = {"status": "OK", "endpoint": ev.path, "query": dict(ev.query)}
152
+ q.put(pb2.ApiEventResponse(id=ev.id, status=200, headers={"Content-Type": "application/json"},
153
+ body=json.dumps(body)))
154
+ except KeyboardInterrupt:
155
+ q.put(None)
@@ -1,97 +1,97 @@
1
- import os
2
- import sys
3
- import time
4
- import socket
5
- import subprocess
6
- import atexit
7
- import signal
8
- import grpc
9
-
10
- class GatewayManager:
11
- """
12
- Ensures a Node.js UNS gateway is running.
13
- Can start it automatically if needed.
14
- """
15
- def __init__(self, addr: str | None = None, auto: bool = True, timeout_s: int = 20):
16
- self.addr = addr
17
- self.auto = auto
18
- self.timeout_s = timeout_s
19
- self._proc: subprocess.Popen | None = None
20
-
21
- def _default_addr(self) -> str:
22
- if os.name == "nt":
23
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
24
- s.bind(("127.0.0.1", 0))
25
- port = s.getsockname()[1]
26
- return f"127.0.0.1:{port}"
27
- else:
28
- script = os.path.basename(sys.argv[0]).replace(".py", "")
29
- return f"unix:/tmp/uns-gateway-{script}-{os.getpid()}.sock"
30
-
31
- def _cleanup(self):
32
- if not self._proc:
33
- return
34
- try:
35
- if self._proc.poll() is None:
36
- if os.name == "nt":
37
- self._proc.terminate()
38
- else:
39
- os.killpg(os.getpgid(self._proc.pid), signal.SIGTERM)
40
- try:
41
- self._proc.wait(timeout=3)
42
- except Exception:
43
- if os.name == "nt":
44
- self._proc.kill()
45
- else:
46
- os.killpg(os.getpgid(self._proc.pid), signal.SIGKILL)
47
- except Exception:
48
- pass
49
- self._proc = None
50
-
51
- def ensure_running(self) -> str:
52
- addr = self.addr or self._default_addr()
53
- ch = grpc.insecure_channel(addr)
54
- try:
55
- grpc.channel_ready_future(ch).result(timeout=2)
56
- ch.close()
57
- return addr
58
- except Exception:
59
- ch.close()
60
- if not self.auto:
61
- return addr
62
-
63
- # Spawn Node.js gateway
64
- repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
65
- cli_path = os.path.join(repo_root, "node_modules", "@uns-kit", "core", "dist", "uns-grpc", "uns-gateway-cli")
66
- popen_kwargs = {}
67
- creationflags = 0
68
- if os.name != "nt":
69
- popen_kwargs["preexec_fn"] = os.setsid
70
- else:
71
- creationflags = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
72
-
73
- suffix = f"py-{os.path.basename(sys.argv[0]).replace('.py','')}-{os.getpid()}"
74
- self._proc = subprocess.Popen(
75
- ["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"],
76
- cwd=repo_root,
77
- creationflags=creationflags,
78
- **popen_kwargs,
79
- )
80
- atexit.register(self._cleanup)
81
-
82
- # Wait for channel ready
83
- start = time.time()
84
- while time.time() - start < self.timeout_s:
85
- ch2 = grpc.insecure_channel(addr)
86
- try:
87
- grpc.channel_ready_future(ch2).result(timeout=2)
88
- ch2.close()
89
- wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
90
- if wait_s > 0:
91
- time.sleep(wait_s)
92
- return addr
93
- except Exception:
94
- ch2.close()
95
- time.sleep(0.5)
96
-
97
- raise RuntimeError("Gateway did not become ready in time")
1
+ import os
2
+ import sys
3
+ import time
4
+ import socket
5
+ import subprocess
6
+ import atexit
7
+ import signal
8
+ import grpc
9
+
10
+ class GatewayManager:
11
+ """
12
+ Ensures a Node.js UNS gateway is running.
13
+ Can start it automatically if needed.
14
+ """
15
+ def __init__(self, addr: str | None = None, auto: bool = True, timeout_s: int = 20):
16
+ self.addr = addr
17
+ self.auto = auto
18
+ self.timeout_s = timeout_s
19
+ self._proc: subprocess.Popen | None = None
20
+
21
+ def _default_addr(self) -> str:
22
+ if os.name == "nt":
23
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
24
+ s.bind(("127.0.0.1", 0))
25
+ port = s.getsockname()[1]
26
+ return f"127.0.0.1:{port}"
27
+ else:
28
+ script = os.path.basename(sys.argv[0]).replace(".py", "")
29
+ return f"unix:/tmp/uns-gateway-{script}-{os.getpid()}.sock"
30
+
31
+ def _cleanup(self):
32
+ if not self._proc:
33
+ return
34
+ try:
35
+ if self._proc.poll() is None:
36
+ if os.name == "nt":
37
+ self._proc.terminate()
38
+ else:
39
+ os.killpg(os.getpgid(self._proc.pid), signal.SIGTERM)
40
+ try:
41
+ self._proc.wait(timeout=3)
42
+ except Exception:
43
+ if os.name == "nt":
44
+ self._proc.kill()
45
+ else:
46
+ os.killpg(os.getpgid(self._proc.pid), signal.SIGKILL)
47
+ except Exception:
48
+ pass
49
+ self._proc = None
50
+
51
+ def ensure_running(self) -> str:
52
+ addr = self.addr or self._default_addr()
53
+ ch = grpc.insecure_channel(addr)
54
+ try:
55
+ grpc.channel_ready_future(ch).result(timeout=2)
56
+ ch.close()
57
+ return addr
58
+ except Exception:
59
+ ch.close()
60
+ if not self.auto:
61
+ return addr
62
+
63
+ # Spawn Node.js gateway
64
+ repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
65
+ cli_path = os.path.join(repo_root, "node_modules", "@uns-kit", "core", "dist", "uns-grpc", "uns-gateway-cli")
66
+ popen_kwargs = {}
67
+ creationflags = 0
68
+ if os.name != "nt":
69
+ popen_kwargs["preexec_fn"] = os.setsid
70
+ else:
71
+ creationflags = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
72
+
73
+ suffix = f"py-{os.path.basename(sys.argv[0]).replace('.py','')}-{os.getpid()}"
74
+ self._proc = subprocess.Popen(
75
+ ["node", cli_path, "--addr", addr, "--instanceSuffix", suffix, "--instanceMode", "force"],
76
+ cwd=repo_root,
77
+ creationflags=creationflags,
78
+ **popen_kwargs,
79
+ )
80
+ atexit.register(self._cleanup)
81
+
82
+ # Wait for channel ready
83
+ start = time.time()
84
+ while time.time() - start < self.timeout_s:
85
+ ch2 = grpc.insecure_channel(addr)
86
+ try:
87
+ grpc.channel_ready_future(ch2).result(timeout=2)
88
+ ch2.close()
89
+ wait_s = int(os.environ.get("UNS_GATEWAY_HANDOVER_WAIT", "11"))
90
+ if wait_s > 0:
91
+ time.sleep(wait_s)
92
+ return addr
93
+ except Exception:
94
+ ch2.close()
95
+ time.sleep(0.5)
96
+
97
+ raise RuntimeError("Gateway did not become ready in time")