rainbow-rb-utils 0.0.9.dev5__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,400 @@
1
+ import ast
2
+ import asyncio
3
+ import builtins
4
+ import inspect
5
+ import math
6
+ import operator
7
+ import re
8
+ from collections.abc import Callable, Coroutine
9
+ from concurrent.futures import Future
10
+ from typing import Any
11
+
12
+ # --- 간단 테이블 (함수/상수/연산자) -----------------------------------------
13
+ BIN_OPS = {
14
+ ast.Add: operator.add,
15
+ ast.Sub: operator.sub,
16
+ ast.Mult: operator.mul,
17
+ ast.Div: operator.truediv,
18
+ ast.FloorDiv: operator.floordiv,
19
+ ast.Pow: operator.pow,
20
+ ast.Mod: operator.mod,
21
+ }
22
+ UNARY_OPS: dict[type[ast.unaryop], Callable] = {ast.USub: operator.neg, ast.UAdd: operator.pos}
23
+ CMP_OPS: dict[type[ast.cmpop], Callable] = {
24
+ ast.Eq: operator.eq,
25
+ ast.NotEq: operator.ne,
26
+ ast.Lt: operator.lt,
27
+ ast.LtE: operator.le,
28
+ ast.Gt: operator.gt,
29
+ ast.GtE: operator.ge,
30
+ }
31
+ ALLOWED_FUNCS = {
32
+ "sin": math.sin,
33
+ "cos": math.cos,
34
+ "tan": math.tan,
35
+ "sqrt": math.sqrt,
36
+ "log": math.log,
37
+ "log10": math.log10,
38
+ "exp": math.exp,
39
+ "abs": abs,
40
+ "max": max,
41
+ "min": min,
42
+ "pow": pow,
43
+ "sum": sum,
44
+ "round": round,
45
+ "int": int,
46
+ "float": float,
47
+ "str": str,
48
+ "len": len,
49
+ "range": range,
50
+ }
51
+ ALLOWED_CALLABLES = set(ALLOWED_FUNCS.values())
52
+ ALLOWED_CONSTS = {
53
+ "pi": math.pi,
54
+ "e": math.e,
55
+ "tau": getattr(math, "tau", 2 * math.pi),
56
+ "inf": math.inf,
57
+ "nan": math.nan,
58
+ }
59
+
60
+ EXPLICIT_EXPR_PATTERN = re.compile(r"\$[A-Za-z_]\w*")
61
+ RB_GLOBAL_PATTERN = re.compile(r"\bRB_[A-Za-z_0-9]*\b")
62
+
63
+
64
+ def safe_eval_expr(
65
+ expr: Any,
66
+ variables: dict[str, dict[str, Any]] | None = None,
67
+ get_global_variable: Callable[[str], Any] | None = None,
68
+ ) -> Any:
69
+ if not isinstance(expr, str):
70
+ return expr
71
+
72
+ vars_ = variables or {"local": {}}
73
+ s = expr.strip()
74
+ if not s:
75
+ return ""
76
+
77
+ is_explicit_expression = bool(EXPLICIT_EXPR_PATTERN.search(s) or RB_GLOBAL_PATTERN.search(s))
78
+ normalized = EXPLICIT_EXPR_PATTERN.sub(lambda match: match.group()[1:], s)
79
+
80
+ # --- 증감(식 전체일 때만 허용) ---------------------------------------------
81
+ inc_post = re.match(r"^([A-Za-z_]\w*)\s*\+\+$", s) # a++
82
+ dec_post = re.match(r"^([A-Za-z_]\w*)\s*--$", s) # a--
83
+ inc_pre = re.match(r"^\+\+\s*([A-Za-z_]\w*)$", s) # ++a
84
+ dec_pre = re.match(r"^--\s*([A-Za-z_]\w*)$", s) # --a
85
+ match = inc_post or dec_post or inc_pre or dec_pre
86
+ if match:
87
+ name = match.group(1)
88
+ exists = name in vars_.get("local", {}) or name in vars_.get("global", {})
89
+ cur: int | None = (
90
+ vars_.get("local", {}).get(name)
91
+ if name in vars_.get("local", {})
92
+ else vars_.get("global", {}).get(name, 0)
93
+ )
94
+
95
+ if cur is None:
96
+ raise ValueError(f"variable {name} not found")
97
+
98
+ if inc_post:
99
+ vars_.setdefault("local", {})[name] = cur + 1 if exists else 1
100
+ return cur
101
+ if dec_post:
102
+ vars_.setdefault("local", {})[name] = cur - 1 if exists else -1
103
+ return cur
104
+ if inc_pre:
105
+ v = cur + 1 if exists else 1
106
+ vars_.setdefault("local", {})[name] = v
107
+ return v
108
+ if dec_pre:
109
+ v = cur - 1 if exists else -1
110
+ vars_.setdefault("local", {})[name] = v
111
+ return v
112
+
113
+ # --- 내부 평가기 ------------------------------------------------------------
114
+ def _eval(node: ast.AST) -> Any:
115
+ if isinstance(node, ast.Constant):
116
+ return node.value
117
+
118
+ if isinstance(node, ast.Name):
119
+ name = node.id
120
+ if name == "true":
121
+ return True
122
+ if name == "false":
123
+ return False
124
+ if name == "null":
125
+ return None
126
+ if name in ALLOWED_FUNCS: # 함수 이름
127
+ return ALLOWED_FUNCS[name]
128
+ if name in ALLOWED_CONSTS: # 수학 상수
129
+ return ALLOWED_CONSTS[name]
130
+ if name == "math": # math.* 전용
131
+ return "math"
132
+ if name in vars_.get("local", {}):
133
+ return vars_["local"].get(name)
134
+
135
+ if name.startswith("RB_"):
136
+ global_val = get_global_variable(name) if get_global_variable is not None else None
137
+ if global_val is not None:
138
+ return global_val
139
+ raise ValueError(f"global variable {name} not found")
140
+
141
+ return name # 모르는 식별자는 문자열로 취급(원하면 여기서 NameError)
142
+
143
+ if isinstance(node, ast.Attribute):
144
+ base = _eval(node.value)
145
+ attr = node.attr
146
+ if isinstance(base, dict) and attr in base:
147
+ return base[attr]
148
+ if base == "math":
149
+ if attr in ALLOWED_CONSTS:
150
+ return ALLOWED_CONSTS[attr]
151
+ if attr in ALLOWED_FUNCS and hasattr(math, attr):
152
+ return getattr(math, attr)
153
+ raise ValueError(f"unknown math attribute: {attr}")
154
+ raise ValueError(f"attribute access not allowed: {type(base).__name__}.{attr}")
155
+
156
+ if isinstance(node, ast.Subscript):
157
+ seq = _eval(node.value)
158
+ sl = node.slice
159
+ if isinstance(sl, ast.Slice):
160
+ lo = _eval(sl.lower) if sl.lower else None
161
+ up = _eval(sl.upper) if sl.upper else None
162
+ st = _eval(sl.step) if sl.step else None
163
+ return seq[slice(lo, up, st)]
164
+ return seq[_eval(sl)]
165
+
166
+ if isinstance(node, ast.BinOp):
167
+ try:
168
+ bin_fn = BIN_OPS[type(node.op)] # get() 쓰지 말고 [] 사용 → Optional 제거
169
+ except KeyError:
170
+ raise ValueError(f"unsupported operator: {type(node.op)}") from None
171
+ return bin_fn(_eval(node.left), _eval(node.right))
172
+
173
+ if isinstance(node, ast.UnaryOp):
174
+ try:
175
+ un_fn = UNARY_OPS[type(node.op)] # 변수명 분리(= 타입 분리)
176
+ except KeyError:
177
+ raise ValueError(f"unsupported unary operator: {type(node.op)}") from None
178
+ return un_fn(_eval(node.operand))
179
+
180
+ if isinstance(node, ast.Compare):
181
+ left = _eval(node.left)
182
+ for op, comp in zip(node.ops, node.comparators, strict=False): # strict 인자 빼
183
+ try:
184
+ cmp_fn = CMP_OPS[type(op)] # Optional 제거
185
+ except KeyError:
186
+ raise ValueError(f"unsupported comparison: {type(op)}") from None
187
+ right = _eval(comp)
188
+ if not cmp_fn(left, right):
189
+ return False
190
+ left = right
191
+ return True
192
+
193
+ if isinstance(node, ast.BoolOp):
194
+ if isinstance(node.op, ast.And):
195
+ return all(_eval(v) for v in node.values)
196
+ if isinstance(node.op, ast.Or):
197
+ return any(_eval(v) for v in node.values)
198
+
199
+ if isinstance(node, ast.Call):
200
+ fn = _eval(node.func)
201
+ if fn not in ALLOWED_CALLABLES:
202
+ raise ValueError(f"function not allowed: {fn}")
203
+ if node.keywords:
204
+ raise ValueError("keyword arguments not allowed")
205
+ args = [_eval(a) for a in node.args]
206
+ return fn(*args)
207
+
208
+ if isinstance(node, ast.List):
209
+ return [_eval(e) for e in node.elts]
210
+ if isinstance(node, ast.Tuple):
211
+ return tuple(_eval(e) for e in node.elts)
212
+ if isinstance(node, ast.Dict):
213
+ return {
214
+ (_eval(k) if k is not None else None): _eval(v)
215
+ for k, v in zip(node.keys, node.values, strict=False)
216
+ }
217
+
218
+ if isinstance(node, ast.IfExp):
219
+ return _eval(node.body) if _eval(node.test) else _eval(node.orelse)
220
+
221
+ raise ValueError(f"unsupported expression: {ast.dump(node)}")
222
+
223
+ # --- 단순 대입: a = <expr> (한 문장만 허용) ---------------------------------
224
+ try:
225
+ mod = ast.parse(normalized, mode="exec")
226
+ if len(mod.body) == 1:
227
+ stmt = mod.body[0]
228
+
229
+ # 1) 단순 대입: a = <expr>
230
+ if isinstance(stmt, ast.Assign):
231
+ tgt = stmt.targets[0]
232
+ if not isinstance(tgt, ast.Name):
233
+ raise ValueError("only simple assignment like `a = <expr>` is allowed")
234
+ val = _eval(stmt.value)
235
+ vars_.setdefault("local", {})[tgt.id] = val
236
+ return val
237
+
238
+ # 2) 복합 대입: a *= <expr> (+=, -=, /= ... 포함)
239
+ if isinstance(stmt, ast.AugAssign):
240
+ if not isinstance(stmt.target, ast.Name):
241
+ raise ValueError("only simple aug assignment like `a *= <expr>` is allowed")
242
+
243
+ name = stmt.target.id
244
+
245
+ # 현재값 가져오기 (local 우선, 없으면 global, 그래도 없으면 0)
246
+ if name in vars_.get("local", {}):
247
+ cur = vars_["local"][name]
248
+ elif name in vars_.get("global", {}):
249
+ cur = vars_["global"][name]
250
+ else:
251
+ cur = 0
252
+
253
+ try:
254
+ bin_fn = BIN_OPS[type(stmt.op)]
255
+ except KeyError:
256
+ raise ValueError(
257
+ f"unsupported operator in aug-assign: {type(stmt.op)}"
258
+ ) from None
259
+
260
+ rhs = _eval(stmt.value)
261
+ new_val = bin_fn(cur, rhs)
262
+
263
+ # 로컬에 기록(네 증감 로직이랑 일관되게)
264
+ vars_.setdefault("local", {})[name] = new_val
265
+ return new_val
266
+ except SyntaxError:
267
+ pass # 표현식일 수 있으니 아래로 진행
268
+ except Exception as e:
269
+ raise e
270
+
271
+ # --- 일반 표현식 ------------------------------------------------------------
272
+ try:
273
+ tree = ast.parse(normalized, mode="eval")
274
+ return _eval(tree.body)
275
+ except SyntaxError:
276
+ if is_explicit_expression:
277
+ raise ValueError(f"failed to evaluate expression: {expr}") from None
278
+ return s
279
+ except Exception:
280
+ if is_explicit_expression:
281
+ raise ValueError(f"failed to evaluate expression: {expr}") from None
282
+ return s
283
+
284
+
285
+ def _pick_target_loop(func, provided: dict) -> asyncio.AbstractEventLoop | None:
286
+ bound_self = getattr(func, "__self__", None)
287
+ if bound_self is not None:
288
+ loop = getattr(bound_self, "loop", None)
289
+ if isinstance(loop, asyncio.AbstractEventLoop):
290
+ return loop
291
+
292
+ loop = provided.get("loop")
293
+ if isinstance(loop, asyncio.AbstractEventLoop):
294
+ return loop
295
+
296
+ return None
297
+
298
+
299
+ def call_with_matching_args(func, **provided):
300
+ """func의 시그니처를 보고 필요한 인자만 추려서 호출 (동기/비동기 모두 지원, 루프 안전)"""
301
+ sig = inspect.signature(func)
302
+ call_kwargs = {}
303
+
304
+ for name, param in sig.parameters.items():
305
+ if name in provided:
306
+ call_kwargs[name] = provided[name]
307
+ elif param.default is inspect.Parameter.empty and param.kind not in (
308
+ inspect.Parameter.VAR_POSITIONAL,
309
+ inspect.Parameter.VAR_KEYWORD,
310
+ ):
311
+ raise TypeError(f"Missing required parameter: {name}")
312
+
313
+ result: Any = func(**call_kwargs)
314
+
315
+ async def _await_awaitable(a):
316
+ return await a
317
+
318
+ if inspect.isawaitable(result):
319
+ target_loop = _pick_target_loop(func, provided)
320
+
321
+ if target_loop is not None:
322
+ coro: Coroutine[Any, Any, Any] = (
323
+ result if inspect.iscoroutine(result) else _await_awaitable(result)
324
+ )
325
+ future: Future[Any] = asyncio.run_coroutine_threadsafe(coro, target_loop)
326
+ return future.result()
327
+
328
+ try:
329
+ running = asyncio.get_running_loop()
330
+ except RuntimeError:
331
+ return asyncio.run(_await_awaitable(result))
332
+
333
+ task: asyncio.Task = running.create_task(_await_awaitable(result))
334
+ return task
335
+
336
+ # 3) 동기 함수면 결과 그대로 반환
337
+ return result
338
+
339
+
340
+ def eval_value(
341
+ value: Any,
342
+ variables: dict[str, dict[str, Any]] | None = None,
343
+ get_global_variable: Callable[[str], Any] | None = None,
344
+ ) -> Any:
345
+ # 1) 문자열이면 → 기존 safe_eval_expr 호출
346
+ if isinstance(value, str):
347
+ return safe_eval_expr(
348
+ value,
349
+ variables=variables,
350
+ get_global_variable=get_global_variable,
351
+ )
352
+
353
+ # 2) 리스트면 → 각 요소를 재귀적으로 평가
354
+ if isinstance(value, list):
355
+ return [
356
+ eval_value(v, variables=variables, get_global_variable=get_global_variable)
357
+ for v in value
358
+ ]
359
+
360
+ # 3) 튜플도 동일
361
+ if isinstance(value, tuple):
362
+ return tuple(
363
+ eval_value(v, variables=variables, get_global_variable=get_global_variable)
364
+ for v in value
365
+ )
366
+
367
+ # 4) 딕셔너리면 → value만 재귀적으로 평가 (key는 그대로)
368
+ if isinstance(value, dict):
369
+ return {
370
+ k: eval_value(v, variables=variables, get_global_variable=get_global_variable)
371
+ for k, v in value.items()
372
+ }
373
+
374
+ # 5) 나머지(int/float/bool/None 등)는 그대로
375
+ return value
376
+
377
+
378
+ DENY_BUILTINS = {
379
+ "__import__", # import 막기 (이거 열리면 끝)
380
+ "open", # 파일 접근
381
+ "input", # 서버에서 대기 위험
382
+ "eval",
383
+ "exec",
384
+ "compile", # 자기 실행 계열
385
+ "globals",
386
+ "locals",
387
+ "vars", # 탐색/우회 보조
388
+ "breakpoint", # 디버거
389
+ }
390
+
391
+
392
+ def make_builtins_allow_most():
393
+ """위험하지 않은 모든 내장 함수를 허용하는 딕셔너리를 생성"""
394
+ safe = dict(builtins.__dict__)
395
+
396
+ for k in list(safe.keys()):
397
+ if k in DENY_BUILTINS:
398
+ safe.pop(k, None)
399
+
400
+ return safe
@@ -0,0 +1,28 @@
1
+ import asyncio
2
+ import socket
3
+ from typing import Any
4
+
5
+ from .parser import t_to_dict
6
+
7
+
8
+ def ManipulateZenohResHelper(obj: Any):
9
+ dict_obj = t_to_dict(obj)
10
+
11
+ if dict_obj.hasAttr("returnValue"):
12
+ dict_obj.set("return_value", dict_obj.get("returnValue"))
13
+ del dict_obj["returnValue"]
14
+
15
+ raise ValueError("obj is not a dict")
16
+
17
+
18
+ async def get_current_ip(timeout: float = 0.5) -> str:
19
+ hostname = socket.gethostname()
20
+ loop = asyncio.get_running_loop()
21
+ try:
22
+ return await asyncio.wait_for(
23
+ loop.run_in_executor(None, socket.gethostbyname, hostname),
24
+ timeout=timeout,
25
+ )
26
+ except Exception:
27
+ # DNS가 막히면 이전 IP를 쓰거나, 로컬 IP로 폴백
28
+ return "127.0.0.1"
@@ -0,0 +1,202 @@
1
+ import inspect
2
+
3
+ from rainbow.rb_flat_buffers.IPC.State_Core import State_Core
4
+ from rainbow.rb_log.log import rb_log
5
+ from rainbow.rb_schemas.sdk import FlowManagerArgs
6
+ from rainbow.rb_zenoh.client import QueryAllResult, QueryResult, T
7
+
8
+
9
+ def snake_to_title(s: str) -> str:
10
+ return s.replace("_", " ").title()
11
+
12
+
13
+ def validate_payload(results: list[QueryAllResult], /, fm_args: FlowManagerArgs | None = None):
14
+ """
15
+ 반환된 객체 리스트에서 obj_payload 필드가 None인 경우 예외를 던진다.
16
+
17
+ Args:
18
+ results: 반환된 객체 리스트 (obj_payload 필드가 있는지 확인)
19
+ """
20
+
21
+ if fm_args is None:
22
+ return
23
+
24
+ call_function = inspect.stack()[1].function
25
+
26
+ for result in results:
27
+ obj_payload = result.get("obj_payload", None)
28
+ key = result.get("key", "unknown_key")
29
+ if obj_payload is None:
30
+ raise RuntimeError(
31
+ f"{snake_to_title(call_function)} failed[{key}]: obj_payload is None"
32
+ )
33
+
34
+
35
+ def validate_multi_return_value(
36
+ results: list[QueryAllResult],
37
+ /,
38
+ title: str | None = None,
39
+ fm_args: FlowManagerArgs | None = None,
40
+ ):
41
+ """
42
+ PyFM이 실행할 경우 obj_payload에 returnValue 필드가 있고, 0이 아니면 에러로 간주해서 로그 남기고 예외를 던진다.
43
+
44
+ Args:
45
+ obj_payload: 반환된 객체 (returnValue 필드가 있는지 확인)
46
+ key: 키 값 (로그용)
47
+ """
48
+ if fm_args is None:
49
+ return
50
+
51
+ call_function = inspect.stack()[1].function
52
+
53
+ for result in results:
54
+ obj_payload = result.get("obj_payload", None)
55
+ key = result.get("key", "unknown_key")
56
+ if obj_payload is None:
57
+ raise RuntimeError(
58
+ f"{title if title is not None else snake_to_title(call_function)} failed[{key}]: obj_payload is None"
59
+ )
60
+
61
+ if hasattr(obj_payload, "returnValue") and obj_payload.returnValue != 0:
62
+ error_msg = f"[{call_function}] return value is {obj_payload.returnValue}"
63
+ if key is not None:
64
+ error_msg = f"[{call_function}] [{key}] return value is {obj_payload.returnValue}"
65
+ rb_log.error(error_msg)
66
+ raise RuntimeError(error_msg)
67
+
68
+
69
+ def validate_single_return_value(res: QueryResult[T], fm_args: FlowManagerArgs | None = None) -> T:
70
+ """
71
+ PyFM이 실행할 경우 obj_payload에 returnValue 필드가 있고, 0이 아니면 에러로 간주해서 로그 남기고 예외를 던진다.
72
+
73
+ Args:
74
+ res: query_one 결과 객체
75
+ key: 키 값 (로그용)
76
+ """
77
+
78
+ call_function = inspect.stack()[1].function
79
+
80
+ obj_payload = res.get("obj_payload", None)
81
+ if obj_payload is None:
82
+ raise RuntimeError(f"{snake_to_title(call_function)} failed: obj_payload is None")
83
+
84
+ if hasattr(obj_payload, "returnValue") and obj_payload.returnValue != 0:
85
+ error_msg = f"[{call_function}] return value is {obj_payload.returnValue}"
86
+ raise RuntimeError(error_msg)
87
+
88
+ if fm_args is not None:
89
+ fm_args.done()
90
+
91
+ return obj_payload
92
+
93
+
94
+ def parse_state_core_mv_to_dict(mv: memoryview) -> dict[str, object]:
95
+ """
96
+ State_Core 메모리뷰를 딕셔너리로 변환
97
+ Args:
98
+ mv: State_Core 메모리뷰
99
+
100
+ Returns:
101
+ dict[str, object]: State_Core 메모리뷰를 딕셔너리로 변환
102
+ """
103
+
104
+ def _numpy_or_list_to_list(values) -> list[float | int]:
105
+ return values.tolist() if hasattr(values, "tolist") else list(values)
106
+
107
+ def _parse_string(value) -> str | None:
108
+ if value is None:
109
+ return None
110
+ if isinstance(value, memoryview):
111
+ value = bytes(value)
112
+ if isinstance(value, bytes | bytearray):
113
+ return bytes(value).decode("utf-8", "ignore")
114
+ return str(value)
115
+
116
+ def _parse_float_array_struct(obj: object) -> dict | None:
117
+ if obj is None:
118
+ return None
119
+ values = obj.FAsNumpy() if hasattr(obj, "FAsNumpy") else obj.F()
120
+ return {"f": _numpy_or_list_to_list(values)}
121
+
122
+ def _parse_uint_array_struct(obj: object) -> dict | None:
123
+ if obj is None:
124
+ return None
125
+ values = obj.UAsNumpy() if hasattr(obj, "UAsNumpy") else obj.U()
126
+ return {"u": _numpy_or_list_to_list(values)}
127
+
128
+ obj = State_Core.GetRootAs(mv, 0)
129
+
130
+ return {
131
+ "heartBeat": obj.HeartBeat(),
132
+ "timestampUsecMono": obj.TimestampUsecMono(),
133
+ "jointQRef": _parse_float_array_struct(obj.JointQRef()),
134
+ "jointQEnc": _parse_float_array_struct(obj.JointQEnc()),
135
+ "jointTEsti": _parse_float_array_struct(obj.JointTEsti()),
136
+ "jointTMeas": _parse_float_array_struct(obj.JointTMeas()),
137
+ "jointTemper": _parse_float_array_struct(obj.JointTemper()),
138
+ "carteXRef": _parse_float_array_struct(obj.CarteXRef()),
139
+ "carteXEnc": _parse_float_array_struct(obj.CarteXEnc()),
140
+ "carteSpeedRef": _parse_float_array_struct(obj.CarteSpeedRef()),
141
+ "carteSpeedEnc": _parse_float_array_struct(obj.CarteSpeedEnc()),
142
+ "userfSelectionNo": obj.UserfSelectionNo(),
143
+ "userfXRef": _parse_float_array_struct(obj.UserfXRef()),
144
+ "toolSelectionNo": obj.ToolSelectionNo(),
145
+ "toolName": _parse_string(obj.ToolName()),
146
+ "toolTcpX": obj.ToolTcpX(),
147
+ "toolTcpY": obj.ToolTcpY(),
148
+ "toolTcpZ": obj.ToolTcpZ(),
149
+ "toolTcpRx": obj.ToolTcpRx(),
150
+ "toolTcpRy": obj.ToolTcpRy(),
151
+ "toolTcpRz": obj.ToolTcpRz(),
152
+ "toolComM": obj.ToolComM(),
153
+ "toolComX": obj.ToolComX(),
154
+ "toolComY": obj.ToolComY(),
155
+ "toolComZ": obj.ToolComZ(),
156
+ "cboxDigitalInput": _parse_uint_array_struct(obj.CboxDigitalInput()),
157
+ "cboxDigitalOutput": _parse_uint_array_struct(obj.CboxDigitalOutput()),
158
+ "cboxAnalogInput": _parse_float_array_struct(obj.CboxAnalogInput()),
159
+ "cboxAnalogOutput": _parse_float_array_struct(obj.CboxAnalogOutput()),
160
+ "exDigitalInput": _parse_uint_array_struct(obj.ExDigitalInput()),
161
+ "exDigitalOutput": _parse_uint_array_struct(obj.ExDigitalOutput()),
162
+ "exAnalogInput": _parse_float_array_struct(obj.ExAnalogInput()),
163
+ "exAnalogOutput": _parse_float_array_struct(obj.ExAnalogOutput()),
164
+ "toolDigitalInput": _parse_uint_array_struct(obj.ToolDigitalInput()),
165
+ "toolDigitalOutput": _parse_uint_array_struct(obj.ToolDigitalOutput()),
166
+ "toolAnalogInput": _parse_float_array_struct(obj.ToolAnalogInput()),
167
+ "toolAnalogOutput": _parse_float_array_struct(obj.ToolAnalogOutput()),
168
+ "toolVoltageOutput": obj.ToolVoltageOutput(),
169
+ "motionMode": obj.MotionMode(),
170
+ "motionExecutionResult": obj.MotionExecutionResult(),
171
+ "motionSpeedBar": obj.MotionSpeedBar(),
172
+ "motionIsPause": obj.MotionIsPause(),
173
+ "statusLan2can": obj.StatusLan2can(),
174
+ "statusSwitchEmg": obj.StatusSwitchEmg(),
175
+ "statusPowerOut": obj.StatusPowerOut(),
176
+ "statusServoNum": obj.StatusServoNum(),
177
+ "statusIsRefon": obj.StatusIsRefon(),
178
+ "statusOutColl": obj.StatusOutColl(),
179
+ "statusSelfColl": obj.StatusSelfColl(),
180
+ "statusDtMode": obj.StatusDtMode(),
181
+ "statusPyfmState": obj.StatusPyfmState(),
182
+ "forcetorqueExtX": obj.ForcetorqueExtX(),
183
+ "forcetorqueExtY": obj.ForcetorqueExtY(),
184
+ "forcetorqueExtZ": obj.ForcetorqueExtZ(),
185
+ "forcetorqueExtRx": obj.ForcetorqueExtRx(),
186
+ "forcetorqueExtRy": obj.ForcetorqueExtRy(),
187
+ "forcetorqueExtRz": obj.ForcetorqueExtRz(),
188
+ "forcetorqueIntX": obj.ForcetorqueIntX(),
189
+ "forcetorqueIntY": obj.ForcetorqueIntY(),
190
+ "forcetorqueIntZ": obj.ForcetorqueIntZ(),
191
+ "forcetorqueIntRx": obj.ForcetorqueIntRx(),
192
+ "forcetorqueIntRy": obj.ForcetorqueIntRy(),
193
+ "forcetorqueIntRz": obj.ForcetorqueIntRz(),
194
+ "weldInfoTxChunk": obj.WeldInfoTxChunk(),
195
+ "weldInfoTxCurrent": obj.WeldInfoTxCurrent(),
196
+ "weldInfoTxVoltage": obj.WeldInfoTxVoltage(),
197
+ "weldInfoTxFeedrate": obj.WeldInfoTxFeedrate(),
198
+ "weldInfoRxChunk": obj.WeldInfoRxChunk(),
199
+ "weldInfoRxCurrent": obj.WeldInfoRxCurrent(),
200
+ "weldInfoRxVoltage": obj.WeldInfoRxVoltage(),
201
+ "weldInfoRxFeedrate": obj.WeldInfoRxFeedrate(),
202
+ }
@@ -0,0 +1,38 @@
1
+ """
2
+ [페이지네이션 응답 타입]
3
+ """
4
+ from typing import Literal
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class RequestPagination(BaseModel):
10
+ pageNo: int = Field(1, example=1)
11
+ pageSize: int = Field(10, example=10)
12
+ searchText: str = Field("", example="")
13
+ sortOption: str = Field("createdAt", example="createdAt")
14
+ sortDirection: Literal["ASC", "DESC"] | None = Field(None, example="ASC")
15
+
16
+ def get_offset(self) -> int:
17
+ # if self.pageNo is None or self.pageNo < 1:
18
+ # self.pageNo = 1
19
+ # if self.pageSize is None or self.pageSize < 1:
20
+ # self.pageSize = 10
21
+ return (self.pageNo - 1) * self.pageSize
22
+
23
+ def get_limit(self) -> int:
24
+ # if self.pageSize is None or self.pageSize < 1:
25
+ # self.pageSize = 10
26
+ return self.pageSize
27
+
28
+ class ResponsePagination(RequestPagination):
29
+ totalSize: int
30
+ totalPage: int
31
+
32
+ class ResponsePaginationPD(BaseModel):
33
+ """
34
+ - items: 아이템 목록
35
+ - pageInfo: 페이지 정보
36
+ """
37
+ items: list[dict]
38
+ pageInfo: ResponsePagination