@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,163 +1,16 @@
1
- #!/usr/bin/env python3
2
- from __future__ import annotations
3
-
4
- import argparse
5
- import json
6
- import os
1
+ from gateway.client import Client, iso_now
7
2
  import time
8
- from datetime import datetime, timezone
9
- from typing import Iterable, 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 iso_from_epoch_ms(ms: int) -> str:
22
- dt = datetime.fromtimestamp(ms / 1000.0, tz=timezone.utc)
23
- return dt.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
24
-
25
-
26
- def make_channel(addr: str) -> grpc.Channel:
27
- return grpc.insecure_channel(addr)
28
-
29
-
30
- import atexit, signal, subprocess
31
- _AUTO_GATEWAY_PROC = None
32
- def _cleanup_gateway():
33
- global _AUTO_GATEWAY_PROC
34
- if _AUTO_GATEWAY_PROC and _AUTO_GATEWAY_PROC.poll() is None:
35
- try:
36
- if os.name == 'nt':
37
- _AUTO_GATEWAY_PROC.terminate()
38
- else:
39
- os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGTERM)
40
- try:
41
- _AUTO_GATEWAY_PROC.wait(timeout=3)
42
- except Exception:
43
- if os.name == 'nt':
44
- _AUTO_GATEWAY_PROC.kill()
45
- else:
46
- os.killpg(os.getpgid(_AUTO_GATEWAY_PROC.pid), signal.SIGKILL)
47
- except Exception:
48
- pass
49
- _AUTO_GATEWAY_PROC = None
50
-
51
- def _default_addr() -> str:
52
- if os.name == 'nt':
53
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
54
- s.bind(('127.0.0.1', 0))
55
- port = s.getsockname()[1]
56
- return f"127.0.0.1:{port}"
57
- else:
58
- return f"unix:/tmp/uns-gateway-table-transformer-{os.getpid()}.sock"
59
-
60
-
61
- def ensure_gateway_running(addr: str | None, *, auto: bool, timeout_s: int = 20) -> str:
62
- if not addr:
63
- addr = _default_addr()
64
- ch = make_channel(addr)
65
- try:
66
- grpc.channel_ready_future(ch).result(timeout=2)
67
- ch.close(); return addr
68
- except Exception:
69
- ch.close()
70
- if not auto:
71
- return addr
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 transform(addr: str | None, in_topic: str, out_topic: str, out_attribute: str, auto: bool) -> None:
104
- addr = ensure_gateway_running(addr, auto=auto)
105
- # Ensure both subscriber and publisher are ready
106
- try:
107
- with make_channel(addr) as ch:
108
- gw.UnsGatewayStub(ch).Ready(pb2.ReadyRequest(timeout_ms=15000, wait_input=True, wait_output=True))
109
- except Exception:
110
- pass
111
- with make_channel(addr) as ch:
112
- stub = gw.UnsGatewayStub(ch)
113
- stream = stub.Subscribe(pb2.SubscribeRequest(topics=[in_topic]))
114
- for msg in stream:
115
- if msg.topic != in_topic:
116
- continue
117
- try:
118
- obj = json.loads(msg.payload)
119
- except Exception:
120
- continue
121
- ts = obj.pop("Timestamp", None)
122
- if ts is None:
123
- continue
124
- try:
125
- ts_ms = int(ts)
126
- except Exception:
127
- continue
128
- time_iso = iso_from_epoch_ms(ts_ms if ts_ms > 10_000_000_000 else ts_ms * 1000)
129
-
130
- values: Dict[str, pb2.TableValue] = {}
131
- tv_list = []
132
- for k, v in obj.items():
133
- if isinstance(v, (int, float)):
134
- tv_list.append(pb2.TableValue(key=k, value_number=float(v)))
135
- elif v is None:
136
- tv_list.append(pb2.TableValue(key=k))
137
- else:
138
- tv_list.append(pb2.TableValue(key=k, value_string=str(v)))
139
-
140
- req = pb2.PublishRequest(
141
- topic=out_topic,
142
- attribute=out_attribute,
143
- table=pb2.Table(time=time_iso, values=tv_list, data_group="demo_table"),
144
- )
145
- res = stub.Publish(req)
146
- if not res.ok:
147
- print("publish error:", res.error)
148
-
149
-
150
- def main():
151
- parser = argparse.ArgumentParser(description="Subscribe to an upstream topic and publish UNS table packets")
152
- parser.add_argument("--addr", default=None, help="Gateway address (unix:/path.sock or host:port); defaults to unique per-script")
153
- parser.add_argument("--auto", action="store_true", help="auto-start gateway if not running")
154
- parser.add_argument("--in-topic", default="integration/raw-table")
155
- parser.add_argument("--out-topic", default="example/factory-a/line-1/")
156
- parser.add_argument("--attribute", default="table-sample")
157
- args = parser.parse_args()
158
-
159
- transform(args.addr, args.in_topic, args.out_topic, args.attribute, args.auto)
160
-
161
3
 
162
- if __name__ == "__main__":
163
- main()
4
+ client = Client()
5
+ client.publish_table(
6
+ topic="sensors/summary/",
7
+ attribute="room1",
8
+ obj={
9
+ "temperature": 22.5,
10
+ "humidity": 55.2,
11
+ "status": "ok",
12
+ "error": None
13
+ },
14
+ data_group="room_stats"
15
+ )
16
+ time.sleep(10)
File without changes
@@ -0,0 +1,75 @@
1
+ import argparse
2
+ from .manager import GatewayManager
3
+ from . import client
4
+ import uns_gateway_pb2_grpc as gw
5
+ from .client import make_channel
6
+
7
+ def main():
8
+ parser = argparse.ArgumentParser()
9
+ parser.add_argument("--addr", default=None)
10
+ sub = parser.add_subparsers(dest="cmd", required=True)
11
+
12
+ # --- Publish ---
13
+ p_pub = sub.add_parser("pub")
14
+ p_pub.add_argument("topic")
15
+ p_pub.add_argument("attribute")
16
+ p_pub.add_argument("time_iso")
17
+ p_pub.add_argument("value", type=float)
18
+ p_pub.add_argument("--uom", default="")
19
+ p_pub.add_argument("--group", default="")
20
+ p_pub.add_argument("--cumulative", action="store_true")
21
+ p_pub.add_argument("--auto", action="store_true")
22
+
23
+ # --- Subscribe ---
24
+ p_sub = sub.add_parser("sub")
25
+ p_sub.add_argument("topics", nargs="+")
26
+ p_sub.add_argument("--auto", action="store_true")
27
+
28
+ # --- Register API ---
29
+ p_reg = sub.add_parser("regapi")
30
+ p_reg.add_argument("topic")
31
+ p_reg.add_argument("attribute")
32
+ p_reg.add_argument("--desc", default="")
33
+ p_reg.add_argument("--tag", action="append", default=[])
34
+ p_reg.add_argument("--param", action="append", default=[])
35
+ p_reg.add_argument("--auto", action="store_true")
36
+
37
+ # --- Unregister API ---
38
+ p_unreg = sub.add_parser("unregapi")
39
+ p_unreg.add_argument("topic")
40
+ p_unreg.add_argument("attribute")
41
+ p_unreg.add_argument("--auto", action="store_true")
42
+
43
+ # --- API Stream ---
44
+ p_stream = sub.add_parser("apistream")
45
+ p_stream.add_argument("--echo", action="store_true")
46
+ p_stream.add_argument("--auto", action="store_true")
47
+
48
+ args = parser.parse_args()
49
+ mgr = GatewayManager(args.addr, auto=getattr(args, "auto", False))
50
+ addr = mgr.ensure_running()
51
+
52
+ if args.cmd == "pub":
53
+ client.publish_data(
54
+ addr, topic=args.topic, attribute=args.attribute,
55
+ time_iso=args.time_iso, value_number=args.value,
56
+ uom=args.uom, data_group=args.group,
57
+ value_is_cumulative=args.cumulative
58
+ )
59
+ elif args.cmd == "sub":
60
+ client.subscribe(addr, args.topics)
61
+ elif args.cmd == "regapi":
62
+ with make_channel(addr) as ch:
63
+ stub = gw.UnsGatewayStub(ch)
64
+ client.register_api(stub, args.topic, args.attribute, args.desc, args.tag, args.param)
65
+ print("registered")
66
+ elif args.cmd == "unregapi":
67
+ with make_channel(addr) as ch:
68
+ stub = gw.UnsGatewayStub(ch)
69
+ client.unregister_api(stub, args.topic, args.attribute)
70
+ print("unregistered")
71
+ elif args.cmd == "apistream":
72
+ client.api_stream(addr, echo=args.echo)
73
+
74
+ if __name__ == "__main__":
75
+ main()
@@ -0,0 +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)
@@ -0,0 +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")
@@ -0,0 +1 @@
1
+ "Main Logic"
@@ -0,0 +1,5 @@
1
+ [project]
2
+ name = "uns-example"
3
+ version = "0.1.0"
4
+ description = "A new gRPC project"
5
+ dependencies = ["grpcio", "grpcio-tools", "httpx>=0.27.2"]
@@ -5,7 +5,7 @@ set -euo pipefail
5
5
  # Navigate to the project root
6
6
  cd "$(dirname "$0")/.."
7
7
 
8
- # Detect OS and set python & venv paths
8
+ # Detect OS and set paths
9
9
  PLATFORM="$(uname)"
10
10
  if [[ "$PLATFORM" == "Linux" || "$PLATFORM" == "Darwin" ]]; then
11
11
  PYTHON_EXEC="python3"
@@ -20,16 +20,23 @@ else
20
20
  exit 1
21
21
  fi
22
22
 
23
- # Check if Python is available
24
- if ! command -v "$PYTHON_EXEC" &> /dev/null; then
25
- echo "[setup] Error: $PYTHON_EXEC not found in PATH."
26
- exit 1
23
+ if [[ "$PLATFORM" =~ MINGW.*|MSYS.*|CYGWIN.*|Windows_NT ]]; then
24
+ UV_CMD="$HOME/.local/bin/uv.exe"
25
+ else
26
+ UV_CMD="$HOME/.cargo/bin/uv"
27
+ fi
28
+
29
+ if [ ! -f "$UV_CMD" ]; then
30
+ echo "[setup] uv not found. Installing it..."
31
+ curl -LsSf https://astral.sh/uv/install.sh | sh
32
+ else
33
+ echo "[setup] uv already installed at $UV_CMD"
27
34
  fi
28
35
 
29
- # Create virtual environment if it doesn't exist
36
+ # Create virtual environment using uv
30
37
  if [ ! -d "$VENV_DIR" ]; then
31
- echo "[setup] Creating virtual environment in $VENV_DIR..."
32
- "$PYTHON_EXEC" -m venv "$VENV_DIR"
38
+ echo "[setup] Creating virtual environment in $VENV_DIR using uv..."
39
+ "$UV_CMD" venv "$VENV_DIR"
33
40
  fi
34
41
 
35
42
  # Create gen directory if it doesn't exist
@@ -46,14 +53,19 @@ fi
46
53
 
47
54
  # Activate the virtual environment
48
55
  echo "[setup] Activating virtual environment..."
49
- source "$VENV_ACTIVATE"
56
+ if [[ "$PLATFORM" =~ MINGW.*|MSYS.*|CYGWIN.* ]]; then
57
+ # Windows Git Bash / MSYS
58
+ . "$VENV_ACTIVATE"
59
+ else
60
+ # Linux / macOS
61
+ source "$VENV_ACTIVATE"
62
+ fi
50
63
 
51
- # Install Python dependencies using pip from venv
52
- echo "[setup] Installing requirements using pip from venv..."
53
- "$PYTHON_EXEC" -m pip install --upgrade pip
54
- "$PYTHON_EXEC" -m pip install -r requirements.txt
64
+ # Install dependencies using uv (much faster than pip)
65
+ echo "[setup] Installing requirements using uv..."
66
+ "$UV_CMD" sync --active
55
67
 
56
- # Generate Python gRPC code from the proto file
68
+ # Generate Python gRPC code from proto
57
69
  echo "[setup] Generating gRPC Python code..."
58
70
  "$PYTHON_EXEC" -m grpc_tools.protoc \
59
71
  -I=proto \
@@ -61,4 +73,15 @@ echo "[setup] Generating gRPC Python code..."
61
73
  --grpc_python_out=gen \
62
74
  proto/uns-gateway.proto
63
75
 
76
+ # Patch the generated _pb2_grpc.py to use relative imports
77
+ GRPC_FILE="gen/uns_gateway_pb2_grpc.py"
78
+ if [[ -f "$GRPC_FILE" ]]; then
79
+ echo "[setup] Patching $GRPC_FILE to use relative imports..."
80
+ if [[ "$PLATFORM" == "Linux" || "$PLATFORM" == "Darwin" ]]; then
81
+ sed -i 's/^import \(.*_pb2\) as/from . import \1 as/' "$GRPC_FILE"
82
+ elif [[ "$PLATFORM" =~ MINGW.*|MSYS.*|CYGWIN.* ]]; then
83
+ sed -i'' 's/^import \(.*_pb2\) as/from . import \1 as/' "$GRPC_FILE"
84
+ fi
85
+ fi
86
+
64
87
  echo "[setup] Setup complete!"
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { UnsProxyProcess, ConfigFile, logger } from "@uns-kit/core";
5
5
  import { ITemporalTopic } from "@uns-kit/temporal";
6
+ import "@uns-kit/temporal";
6
7
  import { type UnsProxyProcessWithTemporal } from "@uns-kit/temporal";
7
8
  import { UnsAttributeType } from "@uns-kit/core/graphql/schema.js";
8
9