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,165 @@
1
+ """
2
+ [파서 유틸리티]
3
+ """
4
+
5
+ import json
6
+ from collections.abc import (
7
+ Mapping,
8
+ )
9
+ from datetime import datetime
10
+ from typing import Any
11
+
12
+ import numpy as np
13
+
14
+ try:
15
+ import orjson
16
+ except ImportError:
17
+ orjson = None
18
+
19
+
20
+ def bytes_to_str(b: bytes, encoding: str = "utf-8", errors: str = "strict") -> str:
21
+ return b.decode(encoding, errors)
22
+
23
+
24
+ def _orjson_default(obj: Any) -> Any:
25
+ if np is not None and isinstance(obj, np.generic):
26
+ return obj.item()
27
+ if isinstance(obj, bytes | bytearray | memoryview):
28
+ if isinstance(obj, memoryview):
29
+ obj = bytes(obj)
30
+ try:
31
+ return obj.decode("utf-8")
32
+ except UnicodeDecodeError:
33
+ return list(obj)
34
+ if isinstance(obj, set):
35
+ return list(obj)
36
+
37
+ to_list = getattr(obj, "tolist", None)
38
+ if callable(to_list):
39
+ return to_list()
40
+
41
+ obj_dict = getattr(obj, "__dict__", None)
42
+ if obj_dict is not None:
43
+ return obj_dict
44
+
45
+ slots = getattr(type(obj), "__slots__", None)
46
+ if slots:
47
+ return {key: getattr(obj, key, None) for key in slots}
48
+
49
+ raise TypeError(f"Object of type {type(obj)!r} is not JSON serializable")
50
+
51
+
52
+ def _t_to_dict_via_orjson(obj: Any) -> Any:
53
+ if orjson is None:
54
+ raise TypeError("orjson unavailable")
55
+
56
+ return orjson.loads(
57
+ orjson.dumps(
58
+ obj,
59
+ default=_orjson_default,
60
+ option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY,
61
+ )
62
+ )
63
+
64
+
65
+ def snake_to_camel(s: str) -> str:
66
+ """
67
+ - s: 스네이크 케이스 문자열
68
+ - 스네이크 케이스 문자열을 카멜 케이스 문자열로 변환
69
+ """
70
+ parts = s.split("_")
71
+ return "".join(p[:1].upper() + p[1:] for p in parts if p)
72
+
73
+
74
+ def camel_to_snake(s: str) -> str:
75
+ """
76
+ - s: 카멜 케이스 문자열
77
+ - 카멜 케이스 문자열을 스네이크 케이스 문자열로 변환
78
+ """
79
+ return "".join("_" + word.lower() if word.isupper() else word for word in s).lstrip("_")
80
+
81
+
82
+ def t_to_dict(obj: Any) -> Any:
83
+ """
84
+ - obj: 객체
85
+ - 객체를 딕셔너리로 변환
86
+ """
87
+ # numpy scalar (np.float32, np.int64 등) -> Python scalar
88
+ if np is not None and isinstance(obj, np.generic):
89
+ return obj.item()
90
+ # 원시형
91
+ if obj is None or isinstance(obj, bool | int | float | str):
92
+ return obj
93
+ # numpy 배열
94
+ if np is not None and isinstance(obj, np.ndarray):
95
+ return obj.tolist()
96
+ # bytes류 → string 디코딩
97
+ if isinstance(obj, bytes | bytearray | memoryview):
98
+ if isinstance(obj, memoryview):
99
+ obj = bytes(obj)
100
+ try:
101
+ return obj.decode("utf-8")
102
+ except UnicodeDecodeError:
103
+ return list(obj)
104
+ # dict류
105
+ if isinstance(obj, Mapping):
106
+ return {k: t_to_dict(v) for k, v in obj.items()}
107
+ # 시퀀스
108
+ if isinstance(obj, list | tuple | set):
109
+ return [t_to_dict(v) for v in obj]
110
+ # 큰 dict-backed/slotted 객체는 C serializer 경로를 우선 시도
111
+ if orjson is not None and (
112
+ getattr(obj, "__dict__", None) is not None or getattr(type(obj), "__slots__", None)
113
+ ):
114
+ try:
115
+ return _t_to_dict_via_orjson(obj)
116
+ except (TypeError, orjson.JSONEncodeError, orjson.JSONDecodeError):
117
+ pass
118
+ # 일반/T-object
119
+ if hasattr(obj, "__dict__"):
120
+ return {k: t_to_dict(v) for k, v in vars(obj).items()}
121
+ # 마지막 안전핀
122
+ return str(obj)
123
+
124
+
125
+ def _normalize_filter(filter_: Any) -> dict:
126
+ """
127
+ filter_가 None, dict, JSON string 모두 OK.
128
+ 빈 문자열/공백/'null'/'None'이면 {} 반환.
129
+ """
130
+ if filter_ is None:
131
+ return {}
132
+ if isinstance(filter_, Mapping):
133
+ return dict(filter_)
134
+ if isinstance(filter_, str):
135
+ s = filter_.strip()
136
+ print("s : ", s)
137
+ if s == "" or s.lower() in ("null", "none"):
138
+ return {}
139
+ try:
140
+ data = json.loads(s)
141
+ except Exception as e:
142
+ raise TypeError(f"filter_ JSON 파싱 실패: {e}") from e
143
+ if not isinstance(data, dict):
144
+ raise TypeError("filter_ 문자열은 JSON object여야 합니다.")
145
+ return data
146
+ raise TypeError(f"filter_ 타입이 잘못되었습니다: {type(filter_)}")
147
+
148
+
149
+ def to_json(obj: Any) -> str:
150
+ """
151
+ - obj: 객체
152
+ - 객체를 JSON 문자열로 변환
153
+ """
154
+ return json.dumps(t_to_dict(obj), ensure_ascii=False)
155
+
156
+
157
+ def to_iso(val: str | datetime):
158
+ """
159
+ - val: 문자열 또는 datetime
160
+ - 문자열 또는 datetime를 ISO 형식의 문자열로 변환
161
+ """
162
+ if isinstance(val, datetime):
163
+ return val.isoformat()
164
+
165
+ return val
File without changes
@@ -0,0 +1,37 @@
1
+ """
2
+ [서비스 예외 클래스]
3
+ """
4
+ from __future__ import (
5
+ annotations,
6
+ )
7
+
8
+ from dataclasses import (
9
+ dataclass,
10
+ )
11
+ from typing import Any
12
+
13
+
14
+ @dataclass(slots=True)
15
+ class ServiceException(Exception):
16
+ """
17
+ - message: 예외 메시지
18
+ - status_code: HTTP 상태 코드 (기본: None)
19
+ """
20
+ message: str
21
+ status_code: int | None = None # HTTP 등
22
+
23
+ def __str__(self) -> str:
24
+ base = self.message
25
+ if self.status_code is not None:
26
+ base += f" (status={self.status_code})"
27
+ return base
28
+
29
+ def to_dict(self) -> dict[str, Any]:
30
+ """
31
+ - message: 예외 메시지
32
+ - status_code: HTTP 상태 코드
33
+ """
34
+ return {
35
+ "message": self.message,
36
+ "status_code": self.status_code,
37
+ }
@@ -0,0 +1,139 @@
1
+ Metadata-Version: 2.4
2
+ Name: rainbow-rb-utils
3
+ Version: 0.0.9.dev5
4
+ Summary: Rainbow Python Utilities
5
+ Author-email: Derek <dfd1123@rainbow-robotics.com>
6
+ Requires-Python: <3.13,>=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: psutil<8.0.0,>=7.0.0
9
+ Requires-Dist: rainbow-rb-log==0.0.9.dev5
10
+
11
+ # rb_utils
12
+
13
+ `rb_utils`는 백엔드 전반에서 재사용하는 유틸리티 모음 패키지입니다.
14
+
15
+ ## 모듈/함수 상세
16
+
17
+ ## 1) `rb_utils.parser`
18
+
19
+ - `snake_to_camel(s: str) -> str`: snake_case 문자열을 CamelCase 형태로 변환
20
+ - `camel_to_snake(s: str) -> str`: CamelCase 문자열을 snake_case로 변환
21
+ - `t_to_dict(obj: Any) -> Any`: 중첩 객체(FlatBuffer T, dict/list/tuple/set, bytes, numpy 배열)를 Python 기본 타입으로 재귀 변환
22
+ - `_normalize_filter(filter_: Any) -> dict`: `None/dict/JSON 문자열`을 공통 dict로 정규화
23
+ - `to_json(obj: Any) -> str`: `t_to_dict` 결과를 JSON 문자열로 직렬화
24
+ - `to_iso(val: str | datetime) -> str`: datetime이면 ISO 문자열, 문자열이면 그대로 반환
25
+
26
+ 예시:
27
+
28
+ ```python
29
+ from rb_utils.parser import t_to_dict, to_json
30
+
31
+ payload_dict = t_to_dict(obj_payload)
32
+ payload_json = to_json(obj_payload)
33
+ ```
34
+
35
+ ## 2) `rb_utils.flow_manager`
36
+
37
+ - `safe_eval_expr(expr, variables=None, get_global_variable=None) -> Any`: 제한된 AST 규칙으로 안전 평가
38
+ - `_pick_target_loop(func, provided) -> asyncio.AbstractEventLoop | None`: callable 실행 대상 loop 선택
39
+ - `call_with_matching_args(func, **provided) -> Any`: 함수 시그니처에 맞춰 인자만 골라 호출 (sync/async 공통)
40
+ - `eval_value(value, variables=None, get_global_variable=None) -> Any`: 문자열/리스트/튜플/딕셔너리를 재귀 평가
41
+ - `make_builtins_allow_most() -> dict`: 위험 builtins 제외한 builtins dict 생성
42
+
43
+ 예시:
44
+
45
+ ```python
46
+ from rb_utils.flow_manager import eval_value
47
+
48
+ value = eval_value("a + 10", variables={"local": {"a": 5}, "global": {}})
49
+ # value == 15
50
+ ```
51
+
52
+ ## 3) `rb_utils.asyncio_helper`
53
+
54
+ - `spawn(coro, *, name=None, log_ex=True)`: 현재 loop에 task 생성 후 완료/예외 로깅
55
+ - `_ensure_private_loop() -> asyncio.AbstractEventLoop`: 전용 백그라운드 loop 확보
56
+ - `fire_and_log(coro, *, name=None, loop=None)`: 현재 loop/외부 loop/전용 loop에 안전하게 실행
57
+ - `_attach_done_logging(fut_or_task, *, name=None)`: Future/Task 완료 시 예외 로깅 callback 연결
58
+
59
+ 예시:
60
+
61
+ ```python
62
+ from rb_utils.asyncio_helper import fire_and_log
63
+
64
+ fire_and_log(socket_client.emit("muscat/program/log", {"msg": "hello"}), name="program-log")
65
+ ```
66
+
67
+ ## 4) `rb_utils.service_exception`
68
+
69
+ - `class ServiceException(Exception)`:
70
+ - 필드: `message: str`, `status_code: int | None`
71
+ - `__str__()`: `message (status=...)` 형태 출력
72
+ - `to_dict()`: `{message, status_code}` 반환
73
+
74
+ 예시:
75
+
76
+ ```python
77
+ from rb_utils.service_exception import ServiceException
78
+
79
+ raise ServiceException("invalid request", status_code=400)
80
+ ```
81
+
82
+ ## 5) `rb_utils.date`
83
+
84
+ - `is_valid_date(d)`: `date/str/int(timestamp)` 유효성 검사
85
+ - `timestamp_ms_to_seconds(timestamp, tz=UTC)`: ms/seconds timestamp를 seconds로 정규화
86
+ - `is_has_time(s: str)`: 문자열에 시간 정보 포함 여부 판단
87
+ - `parse_date_with_time(mode, d, tz=UTC)`: 날짜/문자열/타임스탬프를 timezone-aware datetime으로 변환
88
+ - `ensure_datetime(dt, tz)`: `date | datetime`을 지정 tz의 datetime으로 보정
89
+ - `_tz(tz_name)`: timezone 캐시 조회
90
+ - `convert_dt(local, in_tz, out_tz)`: 입력 tz 기준 datetime/date를 출력 tz로 변환
91
+ - `get_current_dt_yyyymmddhhmmss(tz)`: 현재 시각을 `YYYYMMDDHHMMSS` 문자열 반환
92
+
93
+ 예시:
94
+
95
+ ```python
96
+ from rb_utils.date import parse_date_with_time
97
+ from datetime import UTC
98
+
99
+ dt = parse_date_with_time("start", "2026-02-25", tz=UTC)
100
+ ```
101
+
102
+ ## 6) `rb_utils.file`
103
+
104
+ - `content_disposition_header(filename) -> str`: 다운로드용 `Content-Disposition` 헤더 생성
105
+ - `sanitize_filename(name, default="export.csv", max_name_len=100) -> str`: OS 금지문자 제거/길이 제한
106
+ - `get_env_path() -> Path | None`: 실행 위치 기준으로 `.env` 파일 탐색
107
+
108
+ 예시:
109
+
110
+ ```python
111
+ from rb_utils.file import sanitize_filename
112
+
113
+ safe_name = sanitize_filename("../../report?.csv")
114
+ ```
115
+
116
+ ## 7) `rb_utils.pagination`
117
+
118
+ - `class PageInfo(TypedDict)`: 페이지 응답 메타 타입 (`mode/page/pages/limit/total/sort/order`)
119
+ - `class LogsResponse(TypedDict)`: 로그 목록 응답 타입 (`items`, `pageInfo`)
120
+
121
+ ## 8) `rb_utils.helper`
122
+
123
+ - `ManipulateZenohResHelper(obj)`: Zenoh 응답 객체 보정 유틸 의도 함수
124
+
125
+ 주의:
126
+ - 현재 구현은 dict 처리가 아닌 `.hasAttr/.set` 접근과 즉시 `ValueError`를 포함하므로, 사용 전 동작 검증이 필요합니다.
127
+
128
+ ## 실무 사용 패턴
129
+
130
+ - FlatBuffer/SDK 응답 평탄화: `t_to_dict`
131
+ - Flow step 동적 인자 평가: `eval_value`, `safe_eval_expr`
132
+ - 비동기 fire-and-forget + 예외 로깅: `fire_and_log`
133
+ - 서비스 레이어 공통 예외: `ServiceException`
134
+ - 파일 다운로드/이름 정리: `content_disposition_header`, `sanitize_filename`
135
+
136
+ ## 공통 주의사항
137
+
138
+ - `flow_manager` 평가기는 허용된 연산/함수만 실행합니다.
139
+ - timezone 변환 함수는 `Asia/Seoul` 같은 IANA tz 문자열을 명시해야 합니다.
@@ -0,0 +1,15 @@
1
+ rainbow/rb_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ rainbow/rb_utils/asyncio_helper.py,sha256=NhKx63RDOf5Q-fjrKuxYa901eLo1jtaaOL1DWYd-Mzw,4866
3
+ rainbow/rb_utils/date.py,sha256=jw9LyTbNmvWerKK5APqhJi7ScElBla_V0Ez7Sk-eh7M,6396
4
+ rainbow/rb_utils/file.py,sha256=rrQbfRFqemC3fNAT1th4bd75_HSJ6CHhQ2BuJYz_ZAU,1369
5
+ rainbow/rb_utils/flow_manager.py,sha256=Gk67dCQ8_4CvO5qdW7JwtuNh4TKV2eERaPHLwB-Ptpc,13834
6
+ rainbow/rb_utils/helper.py,sha256=0ZfbhbrdA44qak5yNckBuYEbi575Gti0O_ZD1GyBloY,751
7
+ rainbow/rb_utils/manipulate.py,sha256=hnYGa7GhFhku1tfCE0EQKtpFNQkMnunmY9t9QY3_bAM,8254
8
+ rainbow/rb_utils/pagination.py,sha256=5x8JVRj48bqrYVernpBlAfDxMpAkDg5GyqfHESIZMK4,1077
9
+ rainbow/rb_utils/parser.py,sha256=CADqHJkeTU_i3UczUt9e5jv6zzPNc_8mPBMc9uezbeg,4664
10
+ rainbow/rb_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ rainbow/rb_utils/service_exception.py,sha256=kXVzY_6eFUm8vEcWALGvOGeekcVK4hCqMFVk4MVrbHM,799
12
+ rainbow_rb_utils-0.0.9.dev5.dist-info/METADATA,sha256=9_WkB1OKKNbSSyc93G4x-bsOUhM9k5KOSh6CAYYzQiY,5228
13
+ rainbow_rb_utils-0.0.9.dev5.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
14
+ rainbow_rb_utils-0.0.9.dev5.dist-info/top_level.txt,sha256=sFePQxnQ07jm2rW5WMQ-MhaAdxyvnPKeXldV_kQjJ_A,8
15
+ rainbow_rb_utils-0.0.9.dev5.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ rainbow