rainbow-rb-utils 0.0.9.dev45__tar.gz → 0.0.73__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.
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/PKG-INFO +2 -2
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/pyproject.toml +2 -2
- rainbow_rb_utils-0.0.73/src/rainbow/rb_utils/flatbuffer.py +145 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/flow_manager.py +20 -5
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/helper.py +24 -10
- rainbow_rb_utils-0.0.73/src/rainbow/rb_utils/program.py +135 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow_rb_utils.egg-info/PKG-INFO +2 -2
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow_rb_utils.egg-info/SOURCES.txt +1 -0
- rainbow_rb_utils-0.0.73/src/rainbow_rb_utils.egg-info/requires.txt +2 -0
- rainbow_rb_utils-0.0.9.dev45/src/rainbow/rb_utils/program.py +0 -12
- rainbow_rb_utils-0.0.9.dev45/src/rainbow_rb_utils.egg-info/requires.txt +0 -2
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/README.md +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/setup.cfg +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/__init__.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/asyncio_helper.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/date.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/env.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/file.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/manipulate.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/pagination.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/parser.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/path_utils.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/py.typed +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/service_exception.py +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow_rb_utils.egg-info/dependency_links.txt +0 -0
- {rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow_rb_utils.egg-info/top_level.txt +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rainbow-rb-utils
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.73
|
|
4
4
|
Summary: Rainbow Python Utilities
|
|
5
5
|
Author-email: Derek <dfd1123@rainbow-robotics.com>
|
|
6
6
|
Requires-Python: >=3.11
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Requires-Dist: psutil<8.0.0,>=7.0.0
|
|
9
|
-
Requires-Dist: rainbow-rb-log==0.0.
|
|
9
|
+
Requires-Dist: rainbow-rb-log==0.0.73
|
|
10
10
|
|
|
11
11
|
# rb_utils
|
|
12
12
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rainbow-rb-utils"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.73"
|
|
8
8
|
requires-python = ">=3.11"
|
|
9
9
|
description = "Rainbow Python Utilities"
|
|
10
10
|
authors = [
|
|
@@ -14,7 +14,7 @@ readme = "README.md"
|
|
|
14
14
|
|
|
15
15
|
dependencies = [
|
|
16
16
|
"psutil>=7.0.0,<8.0.0",
|
|
17
|
-
"rainbow-rb-log==0.0.
|
|
17
|
+
"rainbow-rb-log==0.0.73",
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
[tool.setuptools]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""서비스 dict를 생성된 FlatBuffer ``*T`` 객체로 변환하는 유틸리티."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import importlib
|
|
7
|
+
import importlib.util
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from functools import lru_cache
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from rainbow.rb_zenoh.fb_codec import FbTypeError, resolve_fb_type
|
|
14
|
+
|
|
15
|
+
from rainbow.rb_utils.parser import camel_to_snake
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@lru_cache(maxsize=256)
|
|
19
|
+
def _field_annotations(t_cls: type) -> dict[str, str]:
|
|
20
|
+
"""flatc가 생성한 sibling ``.pyi``에서 T 객체의 필드 타입을 읽는다."""
|
|
21
|
+
spec = importlib.util.find_spec(t_cls.__module__)
|
|
22
|
+
if spec is None or not spec.origin or not spec.origin.endswith(".py"):
|
|
23
|
+
return {}
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
with open(spec.origin[:-3] + ".pyi", encoding="utf-8") as file:
|
|
27
|
+
tree = ast.parse(file.read())
|
|
28
|
+
except (OSError, SyntaxError):
|
|
29
|
+
return {}
|
|
30
|
+
|
|
31
|
+
for node in ast.walk(tree):
|
|
32
|
+
if isinstance(node, ast.ClassDef) and node.name == t_cls.__name__:
|
|
33
|
+
return {
|
|
34
|
+
statement.target.id: ast.unparse(statement.annotation)
|
|
35
|
+
for statement in node.body
|
|
36
|
+
if isinstance(statement, ast.AnnAssign)
|
|
37
|
+
and isinstance(statement.target, ast.Name)
|
|
38
|
+
}
|
|
39
|
+
return {}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _strip_optional(annotation: str) -> str:
|
|
43
|
+
annotation = annotation.strip()
|
|
44
|
+
for prefix in ("typing.Optional[", "Optional["):
|
|
45
|
+
if annotation.startswith(prefix) and annotation.endswith("]"):
|
|
46
|
+
return annotation[len(prefix):-1].strip()
|
|
47
|
+
union_parts = [part.strip() for part in annotation.split("|")]
|
|
48
|
+
if len(union_parts) == 2 and "None" in union_parts:
|
|
49
|
+
return next(part for part in union_parts if part != "None")
|
|
50
|
+
return annotation
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _list_element_type(annotation: str) -> str | None:
|
|
54
|
+
annotation = _strip_optional(annotation)
|
|
55
|
+
for prefix in ("typing.List[", "List[", "list["):
|
|
56
|
+
if annotation.startswith(prefix) and annotation.endswith("]"):
|
|
57
|
+
return annotation[len(prefix):-1].strip().strip("'\"")
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _coerce_literal(annotation: str, value: Any, owner_module: str) -> Any:
|
|
62
|
+
if not annotation.startswith(("typing.Literal[", "Literal[")):
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
if isinstance(value, Enum):
|
|
66
|
+
value = value.value
|
|
67
|
+
if not isinstance(value, str):
|
|
68
|
+
return value
|
|
69
|
+
|
|
70
|
+
for member in annotation[annotation.index("[") + 1:-1].split(","):
|
|
71
|
+
enum_name, separator, member_name = member.strip().rpartition(".")
|
|
72
|
+
if not separator or member_name != value:
|
|
73
|
+
continue
|
|
74
|
+
enum_module = importlib.import_module(
|
|
75
|
+
f"{owner_module.rpartition('.')[0]}.{enum_name}"
|
|
76
|
+
)
|
|
77
|
+
enum_cls = getattr(enum_module, enum_name)
|
|
78
|
+
return getattr(enum_cls, member_name)
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _coerce_scalar(annotation: str, value: Any, owner_module: str) -> Any:
|
|
83
|
+
annotation = _strip_optional(annotation)
|
|
84
|
+
value = _coerce_literal(annotation, value, owner_module)
|
|
85
|
+
|
|
86
|
+
if annotation == "int":
|
|
87
|
+
if isinstance(value, datetime):
|
|
88
|
+
return int(value.timestamp() * 1000)
|
|
89
|
+
try:
|
|
90
|
+
return int(value)
|
|
91
|
+
except (TypeError, ValueError):
|
|
92
|
+
return int(datetime.fromisoformat(value).timestamp() * 1000)
|
|
93
|
+
if annotation == "float":
|
|
94
|
+
return float(value)
|
|
95
|
+
if annotation == "bool":
|
|
96
|
+
if isinstance(value, str):
|
|
97
|
+
return value.lower() in {"1", "true", "yes"}
|
|
98
|
+
return bool(value)
|
|
99
|
+
if annotation == "str":
|
|
100
|
+
return str(value)
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _build_value(annotation: str | None, value: Any, owner_module: str) -> Any:
|
|
105
|
+
if value is None or annotation is None:
|
|
106
|
+
return value
|
|
107
|
+
|
|
108
|
+
element_type = _list_element_type(annotation)
|
|
109
|
+
if element_type is not None:
|
|
110
|
+
values = value if isinstance(value, list | tuple) else [value]
|
|
111
|
+
return [_build_value(element_type, item, owner_module) for item in values]
|
|
112
|
+
|
|
113
|
+
nested_type = _strip_optional(annotation).strip("'\"")
|
|
114
|
+
if isinstance(value, dict):
|
|
115
|
+
try:
|
|
116
|
+
nested_cls = resolve_fb_type(nested_type)
|
|
117
|
+
except FbTypeError:
|
|
118
|
+
return value
|
|
119
|
+
return dict_to_flatbuffer(nested_cls, value)
|
|
120
|
+
|
|
121
|
+
return _coerce_scalar(nested_type, value, owner_module)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def dict_to_flatbuffer(t_cls: type, data: dict[str, Any]) -> Any:
|
|
125
|
+
"""dict를 ``t_cls``와 중첩된 FlatBuffer T 객체로 재귀 변환한다."""
|
|
126
|
+
if not isinstance(data, dict):
|
|
127
|
+
raise TypeError(f"{t_cls.__name__}: dict required, got {type(data).__name__}")
|
|
128
|
+
|
|
129
|
+
obj = t_cls()
|
|
130
|
+
annotations = _field_annotations(t_cls)
|
|
131
|
+
fields = {key: value for key, value in vars(obj).items() if not key.startswith("_")}
|
|
132
|
+
normalized_data = {camel_to_snake(key): value for key, value in data.items()}
|
|
133
|
+
|
|
134
|
+
for field, default in fields.items():
|
|
135
|
+
normalized_field = camel_to_snake(field)
|
|
136
|
+
if normalized_field not in normalized_data:
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
value = normalized_data[normalized_field]
|
|
140
|
+
annotation = annotations.get(field)
|
|
141
|
+
if value is None and default is not None and annotation is not None:
|
|
142
|
+
continue
|
|
143
|
+
setattr(obj, field, _build_value(annotation, value, t_cls.__module__))
|
|
144
|
+
|
|
145
|
+
return obj
|
{rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/flow_manager.py
RENAMED
|
@@ -88,7 +88,9 @@ def safe_eval_expr(
|
|
|
88
88
|
if not s:
|
|
89
89
|
return ""
|
|
90
90
|
|
|
91
|
-
is_explicit_expression = bool(
|
|
91
|
+
is_explicit_expression = bool(
|
|
92
|
+
EXPLICIT_EXPR_PATTERN.search(s) or RB_GLOBAL_PATTERN.search(s) or GET_VAR_PATTERN.search(s)
|
|
93
|
+
)
|
|
92
94
|
normalized = EXPLICIT_EXPR_PATTERN.sub(lambda match: match.group()[1:], s)
|
|
93
95
|
|
|
94
96
|
# --- 내부 평가기 ------------------------------------------------------------
|
|
@@ -118,8 +120,13 @@ def safe_eval_expr(
|
|
|
118
120
|
if name in vars_.get("local", {}):
|
|
119
121
|
return vars_["local"].get(name)
|
|
120
122
|
|
|
123
|
+
if name in vars_.get("global", {}):
|
|
124
|
+
return vars_["global"].get(name)
|
|
125
|
+
|
|
121
126
|
if name.startswith("RB_"):
|
|
127
|
+
print("eval_value : RB_ started", name)
|
|
122
128
|
global_val = get_global_variable(name) if get_global_variable is not None else None
|
|
129
|
+
print("eval_value : ", global_val, name)
|
|
123
130
|
if global_val is not None:
|
|
124
131
|
return global_val
|
|
125
132
|
raise ValueError(f"global variable {name} not found")
|
|
@@ -187,7 +194,9 @@ def safe_eval_expr(
|
|
|
187
194
|
if get_var is None:
|
|
188
195
|
raise ValueError("get_var is not available in this context")
|
|
189
196
|
if len(node.args) not in (2, 3):
|
|
190
|
-
raise ValueError(
|
|
197
|
+
raise ValueError(
|
|
198
|
+
"get_var requires 2 or 3 arguments: get_var(pid_or_model, var_name[, default])"
|
|
199
|
+
)
|
|
191
200
|
pid_or_model = _eval(node.args[0])
|
|
192
201
|
var_name = _eval(node.args[1])
|
|
193
202
|
if len(node.args) == 3:
|
|
@@ -421,21 +430,27 @@ def eval_value(
|
|
|
421
430
|
# 2) 리스트면 → 각 요소를 재귀적으로 평가
|
|
422
431
|
if isinstance(value, list):
|
|
423
432
|
return [
|
|
424
|
-
eval_value(
|
|
433
|
+
eval_value(
|
|
434
|
+
v, variables=variables, get_global_variable=get_global_variable, get_var=get_var
|
|
435
|
+
)
|
|
425
436
|
for v in value
|
|
426
437
|
]
|
|
427
438
|
|
|
428
439
|
# 3) 튜플도 동일
|
|
429
440
|
if isinstance(value, tuple):
|
|
430
441
|
return tuple(
|
|
431
|
-
eval_value(
|
|
442
|
+
eval_value(
|
|
443
|
+
v, variables=variables, get_global_variable=get_global_variable, get_var=get_var
|
|
444
|
+
)
|
|
432
445
|
for v in value
|
|
433
446
|
)
|
|
434
447
|
|
|
435
448
|
# 4) 딕셔너리면 → value만 재귀적으로 평가 (key는 그대로)
|
|
436
449
|
if isinstance(value, dict):
|
|
437
450
|
return {
|
|
438
|
-
k: eval_value(
|
|
451
|
+
k: eval_value(
|
|
452
|
+
v, variables=variables, get_global_variable=get_global_variable, get_var=get_var
|
|
453
|
+
)
|
|
439
454
|
for k, v in value.items()
|
|
440
455
|
}
|
|
441
456
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import math
|
|
3
3
|
import socket
|
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
6
7
|
from .parser import t_to_dict
|
|
@@ -18,23 +19,36 @@ def ManipulateZenohResHelper(obj: Any):
|
|
|
18
19
|
raise ValueError("obj is not a dict")
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
# 기본 executor 는 쓰지 않는다. wait_for 는 시작된 스레드를 못 죽여서, 여기가
|
|
23
|
+
# 막히면 이 함수를 넘어 MuscatRouter 핸들러까지 같이 굶는다.
|
|
24
|
+
_dns_fallback_pool = ThreadPoolExecutor(max_workers=1, thread_name_prefix="rb-ip-dns")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _route_lookup_ip() -> str | None:
|
|
28
|
+
"""기본 경로로 나가는 인터페이스 IP. 패킷 안 나가고 라우팅 테이블만 본다 (µs)."""
|
|
29
|
+
try:
|
|
30
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
|
31
|
+
sock.connect(("8.8.8.8", 80))
|
|
32
|
+
return sock.getsockname()[0]
|
|
33
|
+
except OSError:
|
|
34
|
+
return None # 기본 경로 없음
|
|
35
|
+
|
|
36
|
+
|
|
21
37
|
async def get_current_ip(timeout: float = 0.5) -> str:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
sock.connect(("8.8.8.8", 80))
|
|
26
|
-
return sock.getsockname()[0]
|
|
27
|
-
except Exception:
|
|
28
|
-
return socket.gethostbyname(socket.gethostname())
|
|
38
|
+
ip = _route_lookup_ip()
|
|
39
|
+
if ip is not None:
|
|
40
|
+
return ip
|
|
29
41
|
|
|
42
|
+
# 기본 경로가 없을 때만. 호스트명 해석은 DNS 를 탈 수 있다.
|
|
30
43
|
loop = asyncio.get_running_loop()
|
|
31
44
|
try:
|
|
32
45
|
return await asyncio.wait_for(
|
|
33
|
-
loop.run_in_executor(
|
|
46
|
+
loop.run_in_executor(
|
|
47
|
+
_dns_fallback_pool, lambda: socket.gethostbyname(socket.gethostname())
|
|
48
|
+
),
|
|
34
49
|
timeout=timeout,
|
|
35
50
|
)
|
|
36
|
-
except
|
|
37
|
-
# DNS가 막히면 이전 IP를 쓰거나, 로컬 IP로 폴백
|
|
51
|
+
except (TimeoutError, OSError):
|
|
38
52
|
return "127.0.0.1"
|
|
39
53
|
|
|
40
54
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import ipaddress
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_task_id(args: Any | None) -> str:
|
|
6
|
+
if not args:
|
|
7
|
+
return "unknown_module"
|
|
8
|
+
|
|
9
|
+
# FlowManagerArgs 객체와 dict 입력을 모두 허용한다.
|
|
10
|
+
ctx = args.get("ctx") if isinstance(args, dict) else getattr(args, "ctx", None)
|
|
11
|
+
|
|
12
|
+
process_id = getattr(ctx, "process_id", None)
|
|
13
|
+
return str(process_id) if process_id else "unknown_module"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_MISSING = object()
|
|
17
|
+
|
|
18
|
+
_RESOLVE_OPTIONS = ("SKIP", "STOP", "ASK")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InterfaceResult:
|
|
22
|
+
def __init__(self, success: bool, error: str | None = None):
|
|
23
|
+
self.success = success
|
|
24
|
+
self.error = error
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
if self.error:
|
|
28
|
+
return self.error
|
|
29
|
+
return "success" if self.success else "The request failed."
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def to_int(
|
|
33
|
+
data_or_value,
|
|
34
|
+
field_name: str | None = None,
|
|
35
|
+
default: int | object = _MISSING,
|
|
36
|
+
error_prefix: str | None = None,
|
|
37
|
+
) -> int:
|
|
38
|
+
"""값을 int로 변환하고, default가 있을 때만 None/빈 문자열을 대체한다."""
|
|
39
|
+
|
|
40
|
+
def _raise(msg: str) -> None:
|
|
41
|
+
if error_prefix:
|
|
42
|
+
raise ValueError(f"{error_prefix}: {msg}")
|
|
43
|
+
raise ValueError(msg)
|
|
44
|
+
|
|
45
|
+
if field_name is not None and hasattr(data_or_value, "get"):
|
|
46
|
+
value = data_or_value.get(field_name)
|
|
47
|
+
name = field_name
|
|
48
|
+
else:
|
|
49
|
+
value = data_or_value
|
|
50
|
+
name = field_name or "value"
|
|
51
|
+
|
|
52
|
+
if value is None or value == "":
|
|
53
|
+
if default is _MISSING:
|
|
54
|
+
_raise(f"failed: {name} is required")
|
|
55
|
+
return int(default)
|
|
56
|
+
try:
|
|
57
|
+
return int(value)
|
|
58
|
+
except (TypeError, ValueError) as exc:
|
|
59
|
+
if error_prefix:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"{error_prefix}: failed: int conversion failed {name}={value}"
|
|
62
|
+
) from exc
|
|
63
|
+
raise ValueError(f"failed: int conversion failed {name}={value}") from exc
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def validate_server_ip(data, error_prefix: str) -> str:
|
|
67
|
+
server_ip = data.get("server_ip")
|
|
68
|
+
|
|
69
|
+
if server_ip in (None, "", Ellipsis):
|
|
70
|
+
raise ValueError(f"{error_prefix}: server_ip is required")
|
|
71
|
+
|
|
72
|
+
if not isinstance(server_ip, str):
|
|
73
|
+
raise ValueError(f"{error_prefix}: server_ip must be a string")
|
|
74
|
+
|
|
75
|
+
server_ip = server_ip.strip()
|
|
76
|
+
if not server_ip:
|
|
77
|
+
raise ValueError(f"{error_prefix}: server_ip is required")
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
ipaddress.ip_address(server_ip)
|
|
81
|
+
except ValueError as exc:
|
|
82
|
+
raise ValueError(f"{error_prefix}: invalid server_ip={server_ip}") from exc
|
|
83
|
+
|
|
84
|
+
return server_ip
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_resolve_option(data, error_prefix: str) -> str:
|
|
88
|
+
value = data.get("resolve_option", "SKIP")
|
|
89
|
+
if value not in _RESOLVE_OPTIONS:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"{error_prefix}: invalid resolve_option={value!r} (allowed: {_RESOLVE_OPTIONS})"
|
|
92
|
+
)
|
|
93
|
+
return value
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_timeout_sec(data, error_prefix: str, default: float | None = None) -> int:
|
|
97
|
+
value = data.get("timeout_sec")
|
|
98
|
+
if value is None or value == "":
|
|
99
|
+
if default is None:
|
|
100
|
+
raise ValueError(f"{error_prefix}: timeout_sec is required")
|
|
101
|
+
return int(default * 1000)
|
|
102
|
+
try:
|
|
103
|
+
return int(float(value) * 1000)
|
|
104
|
+
except (TypeError, ValueError) as exc:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
f"{error_prefix}: failed: timeout_sec conversion failed value={value}"
|
|
107
|
+
) from exc
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_required_field(data, field_name: str, error_prefix: str):
|
|
111
|
+
value = data.get(field_name)
|
|
112
|
+
if value in (None, "", Ellipsis):
|
|
113
|
+
raise ValueError(f"{error_prefix}: {field_name} is required")
|
|
114
|
+
return value
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def resolve_interface_flow(
|
|
118
|
+
result,
|
|
119
|
+
resolve_option: Literal["SKIP", "STOP", "ASK"],
|
|
120
|
+
flow_manager_args,
|
|
121
|
+
alarm_fn=None,
|
|
122
|
+
error_prefix: str = "Interface",
|
|
123
|
+
):
|
|
124
|
+
err_msg = getattr(result, "error", None)
|
|
125
|
+
if err_msg in (None, ""):
|
|
126
|
+
err_msg = str(result) if result not in (None, False) else "The request failed."
|
|
127
|
+
if resolve_option == "ASK" and alarm_fn is not None:
|
|
128
|
+
alarm_fn(title="Interface Debug", content=f"{error_prefix}.(Err:{err_msg})")
|
|
129
|
+
flow_manager_args.ctx.pause_all()
|
|
130
|
+
flow_manager_args.ctx.pause()
|
|
131
|
+
flow_manager_args.ctx.check_stop()
|
|
132
|
+
elif resolve_option == "STOP":
|
|
133
|
+
raise RuntimeError(f"{error_prefix}: result={err_msg}")
|
|
134
|
+
else:
|
|
135
|
+
pass # SKIP: do nothing and continue
|
{rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow_rb_utils.egg-info/PKG-INFO
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rainbow-rb-utils
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.73
|
|
4
4
|
Summary: Rainbow Python Utilities
|
|
5
5
|
Author-email: Derek <dfd1123@rainbow-robotics.com>
|
|
6
6
|
Requires-Python: >=3.11
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
Requires-Dist: psutil<8.0.0,>=7.0.0
|
|
9
|
-
Requires-Dist: rainbow-rb-log==0.0.
|
|
9
|
+
Requires-Dist: rainbow-rb-log==0.0.73
|
|
10
10
|
|
|
11
11
|
# rb_utils
|
|
12
12
|
|
{rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow_rb_utils.egg-info/SOURCES.txt
RENAMED
|
@@ -5,6 +5,7 @@ src/rainbow/rb_utils/asyncio_helper.py
|
|
|
5
5
|
src/rainbow/rb_utils/date.py
|
|
6
6
|
src/rainbow/rb_utils/env.py
|
|
7
7
|
src/rainbow/rb_utils/file.py
|
|
8
|
+
src/rainbow/rb_utils/flatbuffer.py
|
|
8
9
|
src/rainbow/rb_utils/flow_manager.py
|
|
9
10
|
src/rainbow/rb_utils/helper.py
|
|
10
11
|
src/rainbow/rb_utils/manipulate.py
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def get_task_id(args: Any | None) -> str:
|
|
5
|
-
if not args:
|
|
6
|
-
return "unknown_module"
|
|
7
|
-
|
|
8
|
-
# FlowManagerArgs 객체와 dict 입력을 모두 허용한다.
|
|
9
|
-
ctx = args.get("ctx") if isinstance(args, dict) else getattr(args, "ctx", None)
|
|
10
|
-
|
|
11
|
-
process_id = getattr(ctx, "process_id", None)
|
|
12
|
-
return str(process_id) if process_id else "unknown_module"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/asyncio_helper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow/rb_utils/service_exception.py
RENAMED
|
File without changes
|
|
File without changes
|
{rainbow_rb_utils-0.0.9.dev45 → rainbow_rb_utils-0.0.73}/src/rainbow_rb_utils.egg-info/top_level.txt
RENAMED
|
File without changes
|