jetstream-api 0.24.0__tar.gz → 0.25.0__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.
Files changed (32) hide show
  1. {jetstream_api-0.24.0/jetstream_api.egg-info → jetstream_api-0.25.0}/PKG-INFO +20 -1
  2. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/README.md +19 -0
  3. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/__init__.py +1 -1
  4. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/admin.py +81 -5
  5. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/properties.py +143 -23
  6. {jetstream_api-0.24.0 → jetstream_api-0.25.0/jetstream_api.egg-info}/PKG-INFO +20 -1
  7. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/pyproject.toml +1 -1
  8. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/LICENSE.txt +0 -0
  9. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/_codec.py +0 -0
  10. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/_consumer.py +0 -0
  11. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/_groupwire.py +0 -0
  12. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/_propv2.py +0 -0
  13. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/_qleader.py +0 -0
  14. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/_throttle.py +0 -0
  15. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/admintopic.py +0 -0
  16. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/common.py +0 -0
  17. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/connectivity.py +0 -0
  18. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/exception.py +0 -0
  19. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/msg.py +0 -0
  20. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/partitioner.py +0 -0
  21. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/pattern.py +0 -0
  22. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/producer.py +0 -0
  23. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/py.typed +0 -0
  24. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/qmgr.py +0 -0
  25. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/topic.py +0 -0
  26. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/ilink/util.py +0 -0
  27. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/jetstream_api.egg-info/SOURCES.txt +0 -0
  28. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/jetstream_api.egg-info/dependency_links.txt +0 -0
  29. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/jetstream_api.egg-info/requires.txt +0 -0
  30. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/jetstream_api.egg-info/top_level.txt +0 -0
  31. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/setup.cfg +0 -0
  32. {jetstream_api-0.24.0 → jetstream_api-0.25.0}/tests/test_example.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jetstream-api
3
- Version: 0.24.0
3
+ Version: 0.25.0
4
4
  Summary: Python client API for iLink M.O.M. (Message Oriented Middleware)
5
5
  Author: snowjeans
6
6
  License: Boost Software License - Version 1.0 - August 17th, 2003
@@ -60,6 +60,25 @@ lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치
60
60
  ## 설치 방법
61
61
  pip install jetstream-api
62
62
 
63
+ ## 0.25.0 변경 - 서비스 속성 · 로거 Property v2(JSON) · 블롭 왕복 보존 (자바 v2.21.0 와 같음, 짝 엔진 7.0.1.3624 이상)
64
+
65
+ - **서비스 속성이 엔진 7 과 JSON 으로 오간다**(엔진 P5-2). ``ILAdminService.getProperty`` 는 SERVICE GET + LOGGER LIST(시스템 ·
66
+ 클라이언트 로그 칸), ``setProperty`` 는 **바꾼 칸만** SERVICE ALTER + 바뀐 로그 카테고리마다 LOGGER ALTER 다. 6.x 엔진은 기존 블롭.
67
+ ☠ **7.0.1.3623 이하 7.x 엔진에는 서비스 속성 조회 · 변경이 거절된다.**
68
+ - 더함: ``ILAdminService.setPropertyWithResult`` (``ILPropertyResult``, 로그 키는 ``LOGGER.SYS.LEVEL`` 모양, ``IP`` 는 ``RESTART`` -
69
+ 재기동 뒤 반영, 나머지는 ``NOW``). 요청이 둘 이상이라 **원자적이지 않다** - 로거 변경이 거절되면 사유 뒤에
70
+ ``[LOGGER <카테고리> 거절 - 이미 적용: SERVICE, ...]`` 가 붙는다(이미 적용된 쪽은 되돌리지 않는다).
71
+ - **서비스 · 큐관리자 속성 블롭 왕복 보존**: 읽은 객체를 그대로 ``setProperty`` 로 되보내면 엔진이 준 바이트가 그대로 나간다.
72
+ 전에는 클라가 모르는 자리(하위호환 로그 칸 · 스레드 풀 칸 등)를 0 이나 기본값으로 덮었다.
73
+ - 문자열 상한을 엔진과 맞췄다: 큐 설명(``description``) **199바이트**(0.23.0 은 200), 서비스 IP 99 · 이름 47 · 허브 IP 23 · HOMEPATH 99
74
+ (엔진 7.0.1.3623 이상과 같다). 옛 판에서 저장된 더 긴 값은 읽기 · 되보내기에서 거절되지 않는다(읽을 때는 상한을 묻지 않는다).
75
+ - 서비스 ``aclType`` · ``relayType`` 은 0/1 만 받는다(그 밖은 엔진 7.0.1.3619 이상이 거절, 엔진 7 JSON 경로는 클라가 먼저 거절).
76
+ - 채널: 종류 · 방향을 바꿔 ``setChannelProperty`` 를 부르면 엔진 7 은 ``MIMQE_IMMUTABLE_PROPERTY (TYPE fixed at <현재 값>)`` 로 거절하고,
77
+ 6.x 블롭은 조용히 옛 값을 유지한다(문서 정정 - 옛 문구 "다르면 NOT_FOUND" 는 틀렸다). TLS 채널을 JSON 으로 만들거나 바꾸는 것은 엔진
78
+ 7.0.1.3617 이상에서 맞다(그 전 판은 기본 인증서를 채우지 않아 ``MIMQE_SSL_ERROR``).
79
+ - 세션: 엔진 7.0.1.3617 이상에서 ``destroyedTime`` 이 ``-1`` 이 아니면 끊긴 세션(보관 중)이다 - 상태는 ``EXCEPTION`` 일 수 있다.
80
+ 그 전 판은 끊긴 세션도 ``-1`` 이라 가릴 수 없다.
81
+
63
82
  ## 0.24.0 변경 - 채널 · 세션 Property v2(JSON) · IBMMQ 채널 종류 (자바 v2.20.0 와 같음, 짝 엔진 7.0.1.3615 이상)
64
83
 
65
84
  - **채널 관리가 엔진 7 과 JSON(Property v2) 으로 오간다**(엔진 P4). 함수 이름은 그대로 - ``createChannel`` · ``setChannelProperty`` ·
@@ -10,6 +10,25 @@ lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치
10
10
  ## 설치 방법
11
11
  pip install jetstream-api
12
12
 
13
+ ## 0.25.0 변경 - 서비스 속성 · 로거 Property v2(JSON) · 블롭 왕복 보존 (자바 v2.21.0 와 같음, 짝 엔진 7.0.1.3624 이상)
14
+
15
+ - **서비스 속성이 엔진 7 과 JSON 으로 오간다**(엔진 P5-2). ``ILAdminService.getProperty`` 는 SERVICE GET + LOGGER LIST(시스템 ·
16
+ 클라이언트 로그 칸), ``setProperty`` 는 **바꾼 칸만** SERVICE ALTER + 바뀐 로그 카테고리마다 LOGGER ALTER 다. 6.x 엔진은 기존 블롭.
17
+ ☠ **7.0.1.3623 이하 7.x 엔진에는 서비스 속성 조회 · 변경이 거절된다.**
18
+ - 더함: ``ILAdminService.setPropertyWithResult`` (``ILPropertyResult``, 로그 키는 ``LOGGER.SYS.LEVEL`` 모양, ``IP`` 는 ``RESTART`` -
19
+ 재기동 뒤 반영, 나머지는 ``NOW``). 요청이 둘 이상이라 **원자적이지 않다** - 로거 변경이 거절되면 사유 뒤에
20
+ ``[LOGGER <카테고리> 거절 - 이미 적용: SERVICE, ...]`` 가 붙는다(이미 적용된 쪽은 되돌리지 않는다).
21
+ - **서비스 · 큐관리자 속성 블롭 왕복 보존**: 읽은 객체를 그대로 ``setProperty`` 로 되보내면 엔진이 준 바이트가 그대로 나간다.
22
+ 전에는 클라가 모르는 자리(하위호환 로그 칸 · 스레드 풀 칸 등)를 0 이나 기본값으로 덮었다.
23
+ - 문자열 상한을 엔진과 맞췄다: 큐 설명(``description``) **199바이트**(0.23.0 은 200), 서비스 IP 99 · 이름 47 · 허브 IP 23 · HOMEPATH 99
24
+ (엔진 7.0.1.3623 이상과 같다). 옛 판에서 저장된 더 긴 값은 읽기 · 되보내기에서 거절되지 않는다(읽을 때는 상한을 묻지 않는다).
25
+ - 서비스 ``aclType`` · ``relayType`` 은 0/1 만 받는다(그 밖은 엔진 7.0.1.3619 이상이 거절, 엔진 7 JSON 경로는 클라가 먼저 거절).
26
+ - 채널: 종류 · 방향을 바꿔 ``setChannelProperty`` 를 부르면 엔진 7 은 ``MIMQE_IMMUTABLE_PROPERTY (TYPE fixed at <현재 값>)`` 로 거절하고,
27
+ 6.x 블롭은 조용히 옛 값을 유지한다(문서 정정 - 옛 문구 "다르면 NOT_FOUND" 는 틀렸다). TLS 채널을 JSON 으로 만들거나 바꾸는 것은 엔진
28
+ 7.0.1.3617 이상에서 맞다(그 전 판은 기본 인증서를 채우지 않아 ``MIMQE_SSL_ERROR``).
29
+ - 세션: 엔진 7.0.1.3617 이상에서 ``destroyedTime`` 이 ``-1`` 이 아니면 끊긴 세션(보관 중)이다 - 상태는 ``EXCEPTION`` 일 수 있다.
30
+ 그 전 판은 끊긴 세션도 ``-1`` 이라 가릴 수 없다.
31
+
13
32
  ## 0.24.0 변경 - 채널 · 세션 Property v2(JSON) · IBMMQ 채널 종류 (자바 v2.20.0 와 같음, 짝 엔진 7.0.1.3615 이상)
14
33
 
15
34
  - **채널 관리가 엔진 7 과 JSON(Property v2) 으로 오간다**(엔진 P4). 함수 이름은 그대로 - ``createChannel`` · ``setChannelProperty`` ·
@@ -1,6 +1,6 @@
1
1
  """iLink - Python client API for iLink M.O.M. (Message Oriented Middleware)."""
2
2
 
3
- __version__ = "0.24.0"
3
+ __version__ = "0.25.0"
4
4
 
5
5
  # --- Admin / Service ---
6
6
  from .admin import ILAdminService, ILAdminQmgr, ILLogBrowser, ILQueueBrowser
@@ -100,6 +100,46 @@ def _session_get_v2(service, qmgrName: str, sessionId: str) -> ILSessionProperty
100
100
  return ILSessionProperty()._pv2_from_body(_propv2.request(service, qmgrName, _propv2.PROP_GET, {"_TYPE": "SESSION", "ID": sessionId}))
101
101
 
102
102
 
103
+ # ── 다음 판 Property v2 SERVICE + LOGGER(엔진 P5-2) ── 서비스 레벨(큐관리자 이름 빈칸)
104
+
105
+ def _service_get_v2(service) -> ILServiceProperty:
106
+ prop = ILServiceProperty()._pv2_from_body(_propv2.request(service, "", _propv2.PROP_GET, {"_TYPE": "SERVICE"}))
107
+ items = _propv2.request(service, "", _propv2.PROP_LIST, {"_TYPE": "LOGGER"}).get("_ITEMS", []) or []
108
+ if not isinstance(items, list):
109
+ raise _propv2.PropV2Error("_ITEMS must be ARRAY of OBJ")
110
+ return prop._pv2_logger_fill(items)
111
+
112
+
113
+ def _service_set_v2(service, prop: ILServiceProperty) -> ILPropertyResult:
114
+ # SERVICE ALTER(바뀐 칸) 뒤 바뀐 카테고리마다 LOGGER ALTER. 원자성은 없다 - 실패하면 이미 적용된 쪽을 사유에 붙인다(엔진 합의)
115
+ merged = {"_APPLIED": [], "_IGNORED": [], "_NORMALIZED": []}
116
+ done = []
117
+
118
+ def merge(r: dict, prefix: str) -> None:
119
+ for k in merged:
120
+ for e in r.get(k, []) or []:
121
+ if isinstance(e, dict):
122
+ merged[k].append(dict(e, KEY=prefix + str(e.get("KEY", ""))))
123
+
124
+ body = prop._pv2_patch_body()
125
+ if body:
126
+ merge(_propv2.request(service, "", _propv2.PROP_ALTER, dict({"_TYPE": "SERVICE"}, **body)), "")
127
+ prop._pv2_clear()
128
+ done.append("SERVICE")
129
+ for lb in prop._pv2_logger_patches():
130
+ cat = lb["CATEGORY"]
131
+ try:
132
+ r = _propv2.request(service, "", _propv2.PROP_ALTER, lb)
133
+ except ILOperationException as exc:
134
+ raise ILOperationException("%s [LOGGER %s 거절%s]" % (exc, cat, (" - 이미 적용: " + ", ".join(done)) if done else ""), exc) from exc
135
+ merge(r, "LOGGER.%s." % cat)
136
+ snap = dict(prop.__dict__.get("_il_log_snap") or ILServiceProperty()._pv2_log_now())
137
+ snap[cat] = prop._pv2_log_now()[cat]
138
+ object.__setattr__(prop, "_il_log_snap", snap)
139
+ done.append("LOGGER " + cat)
140
+ return ILPropertyResult(merged)
141
+
142
+
103
143
  class ILAdminService:
104
144
  """ilink 서버를 **관리**하는 클라이언트. 큐 관리자 생성/기동/속성 변경을 맡는다.
105
145
 
@@ -381,12 +421,16 @@ class ILAdminService:
381
421
  **읽은 뒤 고쳐서 :meth:`setProperty` 로 되돌려주는 방식**이다.
382
422
  빈 객체를 새로 만들어 넣으면 지정하지 않은 항목이 기본값으로 덮인다.
383
423
 
424
+ 엔진 7 이면 JSON 으로 받는다(다음 판) - 서비스 칸은 SERVICE GET, 로그 칸(시스템 · 클라이언트)은 LOGGER LIST 다.
425
+
384
426
  Example::
385
427
 
386
428
  prop = svc.getProperty()
387
429
  print(prop.isTraceEnabled())
388
430
  """
389
431
  try:
432
+ if _propv2.peer_speaks_v2(self.version): # 다음 판(엔진 P5-2): 7 이면 SERVICE GET + LOGGER LIST(JSON), 6.x 면 아래 블롭
433
+ return _service_get_v2(self.service)
390
434
  res_msg = self.service.inquire(ILC.MIMQ_GET_SERVER_PROPERTY)
391
435
  return ILServiceProperty(res_msg.getDataMsg().getData())
392
436
  except _PASSTHROUGH_EXCEPTIONS:
@@ -398,7 +442,8 @@ class ILAdminService:
398
442
  """서버 전체(ilsvc) 속성을 변경한다.
399
443
 
400
444
  Args:
401
- vo (ILServiceProperty): 반영할 설정. **전체가 통째로 덮어써진다.**
445
+ vo (ILServiceProperty): 반영할 설정. 6.x 엔진에는 **전체가 통째로 덮어써진다.** 엔진 7 에는 바꾼 칸만 간다(다음 판,
446
+ :meth:`setPropertyWithResult` 참고 - 요청이 둘 이상이라 원자적이지 않다).
402
447
 
403
448
  Raises:
404
449
  ILOperationException: 서버가 변경을 거절했을 때.
@@ -415,6 +460,9 @@ class ILAdminService:
415
460
  svc.setProperty(prop)
416
461
  """
417
462
  try:
463
+ if _propv2.peer_speaks_v2(self.version): # 다음 판(엔진 P5-2): 7 이면 바꾼 칸만 JSON, 6.x 면 아래 블롭(전체)
464
+ self.setPropertyWithResult(vo)
465
+ return
418
466
  self.service.apply(
419
467
  ILC.MIMQ_SET_SERVER_PROPERTY,
420
468
  vo.get_serialized(),
@@ -425,6 +473,34 @@ class ILAdminService:
425
473
  except Exception as exc:
426
474
  raise ILException(exc) from exc
427
475
 
476
+ def setPropertyWithResult(self, vo: ILServiceProperty) -> ILPropertyResult:
477
+ """:meth:`setProperty` 와 같고, 엔진이 무엇을 적용 · 무시했는지 돌려준다(다음 판).
478
+
479
+ 엔진 7 에는 **바꾼 칸만** 보낸다 - 서비스 칸은 SERVICE ALTER, 로그 칸(시스템 · 클라이언트)은 바뀐 카테고리마다 LOGGER ALTER 다.
480
+ 결과의 로그 키는 ``LOGGER.SYS.LEVEL`` 모양이다. ``IP`` 는 ``"RESTART"`` (재기동 뒤 반영, :meth:`ILPropertyResult.needs_restart`),
481
+ 나머지는 ``"NOW"`` 다. 바꾼 것이 없으면 보내지 않고 빈 결과를 돌려준다.
482
+
483
+ Args:
484
+ vo (ILServiceProperty): 바꿀 설정. :meth:`getProperty` 로 읽어 고친 객체를 넘긴다.
485
+
486
+ Returns:
487
+ ILPropertyResult: 적용 · 무시 목록. **6.x 엔진이면 빈 결과**다(블롭으로 전체를 보낸다).
488
+
489
+ Raises:
490
+ ILOperationException: 엔진이 거절했을 때(사유 그대로). 요청이 둘 이상이라 **원자적이지 않다** - 로거 변경이 거절되면
491
+ 사유 뒤에 ``[LOGGER <카테고리> 거절 - 이미 적용: SERVICE, ...]`` 를 붙인다(이미 적용된 쪽은 되돌리지 않는다).
492
+ ILException: 통신 실패.
493
+ """
494
+ try:
495
+ if not _propv2.peer_speaks_v2(self.version):
496
+ self.setProperty(vo)
497
+ return ILPropertyResult()
498
+ return _service_set_v2(self.service, vo)
499
+ except _PASSTHROUGH_EXCEPTIONS:
500
+ raise
501
+ except Exception as exc:
502
+ raise ILException(exc) from exc
503
+
428
504
  def setSystemTrace(self, enabled: bool) -> None:
429
505
  """서버 시스템 트레이스를 켜거나 끈다.
430
506
 
@@ -5909,10 +5985,10 @@ class ILAdminQmgr:
5909
5985
  전체가 통째로 덮어써진다.
5910
5986
 
5911
5987
  Warning:
5912
- **종류(type)와 방향(direction)은 바꿀 수 없다.** 기존 채널과 다르면
5913
- 서버가 ``MIMQE_OBJECT_NOT_FOUND`` 로 답한다 - "그런 채널 없음"처럼
5914
- 보이지만 실제로는 **종류/방향이 달라서** 못 찾은 것이다.
5915
- 바꾸려면 지우고 다시 만들어야 한다.
5988
+ **종류(type)와 방향(direction)은 바꿀 수 없다** - 바꾸려면 지우고 다시 만들어야 한다(이 메서드는 제거 · 재생성을 하지 않는다).
5989
+ 엔진 7(JSON)은 바꿔 보내면 ``MIMQE_IMMUTABLE_PROPERTY (TYPE fixed at <현재 값>)`` 로 **거절**한다(같은 값이면 통과).
5990
+ ☠ 6.x 블롭 경로는 거절하지 않고 **조용히 옛 값을 유지한다**(성공을 돌려준다, 엔진 6.3.4 동작).
5991
+ ``MIMQE_OBJECT_NOT_FOUND`` 는 채널 이름이 없을 때다.
5916
5992
 
5917
5993
  Note:
5918
5994
  **클러스터 팔로워에서는 거부된다.** 구성 변경(생성/변경/삭제)은 리더에서만
@@ -40,6 +40,19 @@ def _format_millis(value: int, fmt: str) -> str:
40
40
  LEGACY_FORCE_RELOAD_MIN = 1048576
41
41
 
42
42
 
43
+ def _patch_blob(raw: bytes, ref: bytes, built: bytes, fields) -> bytes:
44
+ """서버 원본 ``raw`` 위에, 클라 표현이 달라진 칸만 ``built`` 에서 옮긴다(왕복 보존, 다음 판).
45
+
46
+ ``ref`` 는 원본을 읽은 직후 클라가 짠 바이트다. 칸 ``(오프셋, 폭)`` 마다 ``built`` 와 ``ref`` 가 다르면(= 그 뒤 바꿨으면)
47
+ 그 칸을 덮고, 같으면 원본 바이트를 둔다. 표에 없는 자리(클라가 모르는 칸 · 예약)는 늘 원본 그대로다.
48
+ """
49
+ out = bytearray(raw)
50
+ for off, w in fields:
51
+ if built[off:off + w] != ref[off:off + w]:
52
+ out[off:off + w] = built[off:off + w]
53
+ return bytes(out)
54
+
55
+
43
56
  def _has_value(raw) -> bool:
44
57
  """서버가 준 원본이 쓸 만한지 본다.
45
58
 
@@ -753,7 +766,7 @@ class ILQueueProperty(_AutoAccessor):
753
766
  스펙: 기본 LOCAL · 범위 LOCAL | REMOTE | XMIT · 변경 불가.
754
767
  description (str): 큐 설명(다음 판, 엔진 7). 기본 ``""``. **생성할 때만** 지정한다 - 변경(ALTER)으로 보내면 엔진이 거절한다.
755
768
  ☠ **엔진 7(JSON)에서만 오간다** - 6.x 엔진과의 고정폭 블롭에는 싣지 않는다.
756
- 스펙: 기본 "" · 범위 200바이트 이하 문자열(블롭 칸 폭) · 생성 전용.
769
+ 스펙: 기본 "" · 범위 199바이트 이하 문자열(칸 폭 200 − 1, 엔진 7.0.1.3620 571 과 같다) · 생성 전용.
757
770
  depth (int): **읽기 전용** 현재 깊이. 기본 0.
758
771
  스펙: 기본 0 · 범위 0~2147483647 · 단위 건 · 변경 불가.
759
772
  maxDepth (int): 최대 깊이. 넘으면 put 이 거부된다. 기본 99999999.
@@ -832,7 +845,7 @@ class ILQueueProperty(_AutoAccessor):
832
845
  # 엔진 QueueProperty::Validate() 와 같은 규칙.
833
846
  _SPEC = {
834
847
  "name": _spec_maxlen(47),
835
- "description": _spec_maxlen(200),
848
+ "description": _spec_maxlen(199),
836
849
  "triggerProcess": _spec_maxlen(999),
837
850
  "remoteQmgrName": _spec_maxlen(47),
838
851
  "remoteQueueName": _spec_maxlen(47),
@@ -3389,9 +3402,22 @@ class ILQmgrProperty(_AutoAccessor):
3389
3402
  if len(ba) > offset and ba[offset] != 0x00:
3390
3403
  self.subGroupTimeoutSec = ba_pick_int(ba, offset, 4)
3391
3404
  offset += 4
3405
+ # 왕복 보존(다음 판): 원본과, 원본을 클라가 다시 짠 바이트
3406
+ # ☠ 조회 경로를 실패시키지 않는다 - 다시 짜기가 실패하면(폭 검사 등) 원본 보관을 포기하고 옛 직렬화로 간다
3407
+ try:
3408
+ self._blobBuilt = self._build_blob()
3409
+ self._blobRaw = bytes(ba[:ILC.MIMQ_VO_QMGR_LEN])
3410
+ except Exception: # noqa: BLE001
3411
+ self._blobRaw = None
3392
3412
  except Exception as exc:
3393
3413
  raise ILException(exc) from exc
3394
3414
 
3415
+ #: 클라가 아는 칸 ``(오프셋, 폭)``. 이 밖의 자리(SSL 포트 272 · 하위호환 로그 278 · 321 · 344 · 396 · 풀 446 · 세션 610)는 원본 그대로 간다.
3416
+ _BLOB_FIELDS = ((0, 6), (6, 60), (66, 100), (166, 100), (266, 6), (301, 20), (322, 20), (342, 2), (346, 10), (356, 10), (366, 20),
3417
+ (386, 10), (406, 10), (416, 10), (426, 10), (436, 10), (466, 10), (476, 20), (496, 2), (498, 13), (511, 5),
3418
+ (516, 2), (518, 15), (533, 5), (538, 2), (540, 15), (555, 5), (560, 2), (562, 15), (577, 5), (582, 2), (584, 15),
3419
+ (599, 5), (604, 2), (606, 2), (608, 2), (620, 32), (652, 10), (662, 4))
3420
+
3395
3421
  def get_serialized(self, with_topic_logger: bool = True) -> bytes:
3396
3422
  """이 객체를 서버 규격의 **고정폭 바이트**로 직렬화한다.
3397
3423
 
@@ -3401,7 +3427,32 @@ class ILQmgrProperty(_AutoAccessor):
3401
3427
  Note:
3402
3428
  라이브러리가 요청을 보낼 때 내부적으로 부른다. 응용이 직접 부를
3403
3429
  일은 거의 없다.
3430
+
3431
+ **서버가 준 바이트로 만든 객체는 그 원본 위에 바뀐 칸만 덮는다** - 읽은 객체를 그대로 되보내면 같은 바이트가 나간다
3432
+ (다음 판). 클라가 모르는 자리(하위호환 로그 · 스레드 풀 · 세션 칸 등)를 0 이나 기본값으로 덮지 않는다.
3404
3433
  """
3434
+ built = self._build_blob()
3435
+ raw = self.__dict__.get("_blobRaw")
3436
+ ba = bytearray(built if raw is None else _patch_blob(raw, self._blobBuilt, built, type(self)._BLOB_FIELDS))
3437
+ topic_offset, session_offset = 496, 610
3438
+ if not with_topic_logger:
3439
+ for i in range(20):
3440
+ ba[topic_offset + i] = 0
3441
+ raw = getattr(self, "_volumeTailRaw", None)
3442
+ if raw:
3443
+ ba[topic_offset:topic_offset + 20] = raw
3444
+ else:
3445
+ ba_merge(max(self.volumeMonitorSizeLimit, LEGACY_FORCE_RELOAD_MIN),
3446
+ ba, topic_offset, 20)
3447
+ raw2 = getattr(self, "_sessionPerThreadRaw", None)
3448
+ if raw2:
3449
+ ba[session_offset:session_offset + 10] = raw2
3450
+ else:
3451
+ ba_merge(LEGACY_SESSION_PER_THREAD, ba, session_offset, 10)
3452
+ return bytes(ba)
3453
+
3454
+ def _build_blob(self) -> bytes:
3455
+ # 필드만으로 짠 바이트(원본 없음 기준). 서버 원본이 있으면 get_serialized 가 원본 위에 바뀐 칸만 옮긴다
3405
3456
  ba = bytearray(ILC.MIMQ_VO_QMGR_LEN)
3406
3457
  offset = 0
3407
3458
  ba_merge(self.status, ba, offset, 6)
@@ -3461,7 +3512,6 @@ class ILQmgrProperty(_AutoAccessor):
3461
3512
  # 이 자리는 구엔진의 volumeMonitorForceReloadSize 다. 값을 실어 보내면
3462
3513
  # 구엔진이 자기 필드로 읽어 imqlsr 가 SIGFPE 로 죽는다(6.3.1.2813 실측).
3463
3514
  # 비워 보내려면 get_serialized(with_topic_logger=False) 를 쓴다.
3464
- topic_offset = offset
3465
3515
  ba_merge(self.logLevelTopic, ba, offset, 2)
3466
3516
  offset += 2
3467
3517
  ba_merge(self.logFileSizeTopic, ba, offset, 13)
@@ -3499,7 +3549,6 @@ class ILQmgrProperty(_AutoAccessor):
3499
3549
  ba_merge(self.aclType, ba, offset, 2)
3500
3550
  offset += 2
3501
3551
  # 구엔진에만 있는 sessionPerThread 자리 - 0 을 보내면 큐관리자가 죽는다
3502
- session_offset = offset
3503
3552
  offset += 10
3504
3553
  ba_merge(self.deadLetterQueueName, ba, offset, 32)
3505
3554
  offset += 32
@@ -3507,25 +3556,6 @@ class ILQmgrProperty(_AutoAccessor):
3507
3556
  offset += 10
3508
3557
  ba_merge(self.subGroupTimeoutSec, ba, offset, 4)
3509
3558
  offset += 4
3510
- if not with_topic_logger:
3511
- for i in range(20):
3512
- ba[topic_offset + i] = 0
3513
- raw = getattr(self, "_volumeTailRaw", None)
3514
- if raw:
3515
- # 서버가 준 값을 그대로 되돌려 준다
3516
- ba[topic_offset:topic_offset + 20] = raw
3517
- else:
3518
- # 받은 값이 없다 - 엔진 생성자와 같이 sizeLimit 을 따라간다
3519
- # (6.3 ManagerProperty.hpp:143 이 mForceReloadSize = mSizeLimit).
3520
- # 엔진 세터가 1048576 미만을 올려 잡으므로 그 아래로 내리지 않는다.
3521
- ba_merge(max(self.volumeMonitorSizeLimit, LEGACY_FORCE_RELOAD_MIN),
3522
- ba, topic_offset, 20)
3523
- # 구엔진에만 있는 sessionPerThread 도 되돌려 준다
3524
- raw2 = getattr(self, "_sessionPerThreadRaw", None)
3525
- if raw2:
3526
- ba[session_offset:session_offset + 10] = raw2
3527
- else:
3528
- ba_merge(LEGACY_SESSION_PER_THREAD, ba, session_offset, 10)
3529
3559
  return bytes(ba)
3530
3560
 
3531
3561
  def get_status_string(self) -> str:
@@ -4440,6 +4470,76 @@ class ILServiceProperty(_AutoAccessor):
4440
4470
  out[key] = back[getattr(self, field)]
4441
4471
  return {k: out[k] for _, (k, _) in cls._PV2_KEYS.items() if k in out} # 표 순서
4442
4472
 
4473
+ #: 다음 판 Property v2 LOGGER(엔진 P5-2): 서비스 로거 카테고리 -> (레벨 · 회전 크기 · 보관 개수 필드). 서비스는 SYS · CLI 만.
4474
+ _PV2_LOGGERS = {"SYS": ("logLevelSystem", "logFileSizeSystem", "logFileCountSystem"),
4475
+ "CLI": ("logLevelClient", "logFileSizeClient", "logFileCountClient")}
4476
+ #: LEVEL 와이어 글자(코드 0~7 순서, :meth:`ILC.get_log_level_string` 과 같다 - 4 는 ``"WARNING"``). ``"WARN"`` 은 옛 XML 별칭으로 받기만 한다.
4477
+ _PV2_LOG_LEVELS = ("TRACE", "DEBUG", "MSG", "INFO", "WARNING", "ERROR", "FATAL", "SYSTEM")
4478
+
4479
+ def _pv2_log_now(self) -> dict:
4480
+ # 카테고리 -> (레벨, 크기, 개수) 지금 값
4481
+ return {c: tuple(getattr(self, f) for f in fs) for c, fs in type(self)._PV2_LOGGERS.items()}
4482
+
4483
+ def _pv2_logger_fill(self, items: list) -> "ILServiceProperty":
4484
+ """LOGGER LIST 원소(JSON)로 로그 칸을 채운다. **라이브러리 내부용이다.** 서비스가 모르는 카테고리는 버린다.
4485
+
4486
+ 채운 값을 비교 기준으로 둔다 - :meth:`_pv2_logger_patches` 는 이 기준과 달라진 칸만 싣는다.
4487
+
4488
+ Raises:
4489
+ PropV2Error: 값 종류가 다르거나 LEVEL 글자를 모를 때(조용히 오독하지 않는다).
4490
+ """
4491
+ from . import _propv2
4492
+ cls = type(self)
4493
+ for e in items:
4494
+ if not isinstance(e, dict):
4495
+ raise _propv2.PropV2Error("_ITEMS must be ARRAY of OBJ")
4496
+ fs = cls._PV2_LOGGERS.get(_propv2.take(e, "CATEGORY", "STR"))
4497
+ if fs is None:
4498
+ continue
4499
+ lv = _propv2.take(e, "LEVEL", "STR")
4500
+ if lv is not None:
4501
+ k = "WARNING" if lv == "WARN" else lv
4502
+ if k not in cls._PV2_LOG_LEVELS:
4503
+ raise _propv2.PropV2Error("LEVEL: unknown value %r" % (lv,))
4504
+ object.__setattr__(self, fs[0], cls._PV2_LOG_LEVELS.index(k))
4505
+ for f, key in ((fs[1], "FILESIZE"), (fs[2], "FILECOUNT")):
4506
+ v = _propv2.take(e, key, "INT")
4507
+ if v is not None:
4508
+ object.__setattr__(self, f, v)
4509
+ self._pv2_logger_sent()
4510
+ return self
4511
+
4512
+ def _pv2_logger_patches(self) -> list:
4513
+ """바뀐 카테고리마다 LOGGER ALTER 본문(``_TYPE`` · ``CATEGORY`` + 바뀐 키만)을 만든다. **라이브러리 내부용이다.**
4514
+
4515
+ 기준은 마지막으로 읽은(또는 보낸) 값이고, 읽은 적이 없으면 기본값이다.
4516
+
4517
+ Raises:
4518
+ PropV2Error: 레벨 칸에 0~7 밖의 코드가 있을 때.
4519
+ """
4520
+ from . import _propv2
4521
+ cls = type(self)
4522
+ base = self.__dict__.get("_il_log_snap") or cls()._pv2_log_now()
4523
+ out = []
4524
+ for cat, now in self._pv2_log_now().items():
4525
+ was = base[cat]
4526
+ body = {}
4527
+ if now[0] != was[0]:
4528
+ if not (isinstance(now[0], int) and 0 <= now[0] < len(cls._PV2_LOG_LEVELS)):
4529
+ raise _propv2.PropV2Error("LEVEL: unknown code %r" % (now[0],))
4530
+ body["LEVEL"] = cls._PV2_LOG_LEVELS[now[0]]
4531
+ if now[1] != was[1]:
4532
+ body["FILESIZE"] = int(now[1])
4533
+ if now[2] != was[2]:
4534
+ body["FILECOUNT"] = int(now[2])
4535
+ if body:
4536
+ out.append(dict({"_TYPE": "LOGGER", "CATEGORY": cat}, **body))
4537
+ return out
4538
+
4539
+ def _pv2_logger_sent(self) -> None:
4540
+ # 지금 로그 칸 값을 비교 기준으로 둔다(읽은 직후 · 보낸 직후)
4541
+ object.__setattr__(self, "_il_log_snap", self._pv2_log_now())
4542
+
4443
4543
  def __init__(self, ba: bytes | bytearray | None = None) -> None:
4444
4544
  """서비스 속성 객체를 만든다.
4445
4545
 
@@ -4526,6 +4626,17 @@ class ILServiceProperty(_AutoAccessor):
4526
4626
  offset += 24
4527
4627
  self.hubPort = ba_pick_int(ba, offset, 10)
4528
4628
  offset += 10
4629
+ # 왕복 보존: 원본과, 원본을 클라가 다시 짠 바이트. 직렬화는 원본 위에 클라 표현이 달라진 칸만 덮는다
4630
+ # ☠ 조회 경로를 실패시키지 않는다 - 다시 짜기가 실패하면(폭 검사 등) 원본 보관을 포기하고 옛 직렬화로 간다
4631
+ try:
4632
+ self._blobBuilt = self._build_blob()
4633
+ self._blobRaw = bytes(ba[:ILC.MIMQ_VO_SERVER_LEN])
4634
+ except Exception: # noqa: BLE001
4635
+ self._blobRaw = None
4636
+
4637
+ #: 클라가 아는 칸 ``(오프셋, 폭)``. 이 밖의 자리(하위호환 로그 106 · 풀 168 · 로그 3~4번째 232 · 예약 402)는 원본 그대로 간다.
4638
+ _BLOB_FIELDS = ((0, 100), (100, 6), (128, 20), (148, 10), (158, 10), (188, 2), (190, 15), (205, 5), (210, 2), (212, 15),
4639
+ (227, 5), (276, 2), (278, 2), (280, 2), (282, 2), (284, 36), (320, 48), (368, 24), (392, 10))
4529
4640
 
4530
4641
  def get_serialized(self) -> bytes:
4531
4642
  """이 객체를 서버 규격의 **고정폭 바이트**로 직렬화한다.
@@ -4536,7 +4647,16 @@ class ILServiceProperty(_AutoAccessor):
4536
4647
  Note:
4537
4648
  라이브러리가 요청을 보낼 때 내부적으로 부른다. 응용이 직접 부를
4538
4649
  일은 거의 없다.
4650
+
4651
+ **서버가 준 바이트로 만든 객체는 그 원본 위에 바뀐 칸만 덮는다** - 읽은 객체를 그대로 되보내면 같은 바이트가 나간다
4652
+ (다음 판). 클라가 모르는 자리(하위호환 로그 · 스레드 풀 · 로그 3~4번째 카테고리 · 예약)를 0 이나 기본값으로 덮지 않는다.
4539
4653
  """
4654
+ built = self._build_blob()
4655
+ raw = self.__dict__.get("_blobRaw")
4656
+ return built if raw is None else _patch_blob(raw, self._blobBuilt, built, type(self)._BLOB_FIELDS)
4657
+
4658
+ def _build_blob(self) -> bytes:
4659
+ # 필드만으로 짠 바이트(원본 없음 기준). 서버 원본이 있으면 get_serialized 가 원본 위에 바뀐 칸만 옮긴다
4540
4660
  ba = bytearray(ILC.MIMQ_VO_SERVER_LEN)
4541
4661
  offset = 0
4542
4662
  ba_merge(self.ip, ba, offset, 100)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jetstream-api
3
- Version: 0.24.0
3
+ Version: 0.25.0
4
4
  Summary: Python client API for iLink M.O.M. (Message Oriented Middleware)
5
5
  Author: snowjeans
6
6
  License: Boost Software License - Version 1.0 - August 17th, 2003
@@ -60,6 +60,25 @@ lz4 배치 압축을 쓰려면 `pip install "jetstream-api[lz4]"`, zstd 배치
60
60
  ## 설치 방법
61
61
  pip install jetstream-api
62
62
 
63
+ ## 0.25.0 변경 - 서비스 속성 · 로거 Property v2(JSON) · 블롭 왕복 보존 (자바 v2.21.0 와 같음, 짝 엔진 7.0.1.3624 이상)
64
+
65
+ - **서비스 속성이 엔진 7 과 JSON 으로 오간다**(엔진 P5-2). ``ILAdminService.getProperty`` 는 SERVICE GET + LOGGER LIST(시스템 ·
66
+ 클라이언트 로그 칸), ``setProperty`` 는 **바꾼 칸만** SERVICE ALTER + 바뀐 로그 카테고리마다 LOGGER ALTER 다. 6.x 엔진은 기존 블롭.
67
+ ☠ **7.0.1.3623 이하 7.x 엔진에는 서비스 속성 조회 · 변경이 거절된다.**
68
+ - 더함: ``ILAdminService.setPropertyWithResult`` (``ILPropertyResult``, 로그 키는 ``LOGGER.SYS.LEVEL`` 모양, ``IP`` 는 ``RESTART`` -
69
+ 재기동 뒤 반영, 나머지는 ``NOW``). 요청이 둘 이상이라 **원자적이지 않다** - 로거 변경이 거절되면 사유 뒤에
70
+ ``[LOGGER <카테고리> 거절 - 이미 적용: SERVICE, ...]`` 가 붙는다(이미 적용된 쪽은 되돌리지 않는다).
71
+ - **서비스 · 큐관리자 속성 블롭 왕복 보존**: 읽은 객체를 그대로 ``setProperty`` 로 되보내면 엔진이 준 바이트가 그대로 나간다.
72
+ 전에는 클라가 모르는 자리(하위호환 로그 칸 · 스레드 풀 칸 등)를 0 이나 기본값으로 덮었다.
73
+ - 문자열 상한을 엔진과 맞췄다: 큐 설명(``description``) **199바이트**(0.23.0 은 200), 서비스 IP 99 · 이름 47 · 허브 IP 23 · HOMEPATH 99
74
+ (엔진 7.0.1.3623 이상과 같다). 옛 판에서 저장된 더 긴 값은 읽기 · 되보내기에서 거절되지 않는다(읽을 때는 상한을 묻지 않는다).
75
+ - 서비스 ``aclType`` · ``relayType`` 은 0/1 만 받는다(그 밖은 엔진 7.0.1.3619 이상이 거절, 엔진 7 JSON 경로는 클라가 먼저 거절).
76
+ - 채널: 종류 · 방향을 바꿔 ``setChannelProperty`` 를 부르면 엔진 7 은 ``MIMQE_IMMUTABLE_PROPERTY (TYPE fixed at <현재 값>)`` 로 거절하고,
77
+ 6.x 블롭은 조용히 옛 값을 유지한다(문서 정정 - 옛 문구 "다르면 NOT_FOUND" 는 틀렸다). TLS 채널을 JSON 으로 만들거나 바꾸는 것은 엔진
78
+ 7.0.1.3617 이상에서 맞다(그 전 판은 기본 인증서를 채우지 않아 ``MIMQE_SSL_ERROR``).
79
+ - 세션: 엔진 7.0.1.3617 이상에서 ``destroyedTime`` 이 ``-1`` 이 아니면 끊긴 세션(보관 중)이다 - 상태는 ``EXCEPTION`` 일 수 있다.
80
+ 그 전 판은 끊긴 세션도 ``-1`` 이라 가릴 수 없다.
81
+
63
82
  ## 0.24.0 변경 - 채널 · 세션 Property v2(JSON) · IBMMQ 채널 종류 (자바 v2.20.0 와 같음, 짝 엔진 7.0.1.3615 이상)
64
83
 
65
84
  - **채널 관리가 엔진 7 과 JSON(Property v2) 으로 오간다**(엔진 P4). 함수 이름은 그대로 - ``createChannel`` · ``setChannelProperty`` ·
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jetstream-api"
7
- version = "0.24.0"
7
+ version = "0.25.0"
8
8
  description = "Python client API for iLink M.O.M. (Message Oriented Middleware)"
9
9
  authors = [{name = "snowjeans"}]
10
10
  readme = "README.md"
File without changes