jetstream-api 0.17.0__tar.gz → 0.18.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 (27) hide show
  1. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/PKG-INFO +1 -1
  2. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/__init__.py +104 -104
  3. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/admin.py +19 -10
  4. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/common.py +40 -0
  5. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/connectivity.py +8 -1
  6. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/partitioner.py +85 -78
  7. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/producer.py +10 -7
  8. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/properties.py +97 -9
  9. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/qmgr.py +6 -4
  10. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/jetstream_api.egg-info/PKG-INFO +1 -1
  11. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/pyproject.toml +1 -1
  12. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/LICENSE.txt +0 -0
  13. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/README.md +0 -0
  14. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/_codec.py +0 -0
  15. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/admintopic.py +0 -0
  16. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/exception.py +0 -0
  17. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/msg.py +0 -0
  18. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/pattern.py +0 -0
  19. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/py.typed +0 -0
  20. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/topic.py +0 -0
  21. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/ilink/util.py +0 -0
  22. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/jetstream_api.egg-info/SOURCES.txt +0 -0
  23. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/jetstream_api.egg-info/dependency_links.txt +0 -0
  24. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/jetstream_api.egg-info/requires.txt +0 -0
  25. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/jetstream_api.egg-info/top_level.txt +0 -0
  26. {jetstream_api-0.17.0 → jetstream_api-0.18.0}/setup.cfg +0 -0
  27. {jetstream_api-0.17.0 → jetstream_api-0.18.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.17.0
3
+ Version: 0.18.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
@@ -1,104 +1,104 @@
1
- """iLink - Python client API for iLink M.O.M. (Message Oriented Middleware)."""
2
-
3
- __version__ = "0.17.0"
4
-
5
- # --- Admin / Service ---
6
- from .admin import ILAdminService, ILAdminQmgr, ILLogBrowser, ILQueueBrowser
7
-
8
- # --- Queue Manager & Queue ---
9
- from .qmgr import ILQmgr, ILQueue
10
-
11
- # --- Message ---
12
- from .msg import ILMsg, ILMsgUtil
13
-
14
- # --- Constants & Enums ---
15
- from .common import ILC, ILCC, ILRC, MatchOption
16
-
17
- # --- Properties ---
18
- from .properties import (
19
- DestItem,
20
- PartitionItem,
21
- SegmentItem,
22
- ILQueueProperty,
23
- ILChannelProperty,
24
- ILChannelType,
25
- ILChannelStatus,
26
- ILChannelDirection,
27
- ILChannelSecurity,
28
- ILChannelBalanceMode,
29
- ILClusterNode,
30
- ILClusterControlResult,
31
- ILClusterProperty,
32
- ILQmgrProperty,
33
- ILSessionProperty,
34
- ILServiceProperty,
35
- ILSpokeProperty,
36
- ILAccessRuleProperty,
37
- ILSecureFileProperty,
38
- ILQueueRealTimeInfo,
39
- ILQueueFlow,
40
- ILQmgrDiag,
41
- ILClusterDefDiag,
42
- ILClusterPeerDef,
43
- )
44
-
45
- # --- Exceptions ---
46
- from .exception import (
47
- ILException,
48
- ILNoMsgException,
49
- ILTimeoutException,
50
- ILOperationException,
51
- ILSessionException,
52
- ILMsgTypeException,
53
- )
54
-
55
- __all__ = [
56
- # Admin
57
- "ILAdminService",
58
- "ILAdminQmgr",
59
- "ILLogBrowser",
60
- "ILQueueBrowser",
61
- # QMgr / Queue
62
- "ILQmgr",
63
- "ILQueue",
64
- # Message
65
- "ILMsg",
66
- "ILMsgUtil",
67
- # Constants
68
- "ILC",
69
- "ILCC",
70
- "ILRC",
71
- "MatchOption",
72
- # Properties
73
- "DestItem",
74
- "PartitionItem",
75
- "SegmentItem",
76
- "ILQueueProperty",
77
- "ILChannelProperty",
78
- "ILChannelType",
79
- "ILChannelStatus",
80
- "ILChannelDirection",
81
- "ILChannelSecurity",
82
- "ILChannelBalanceMode",
83
- "ILClusterNode",
84
- "ILClusterControlResult",
85
- "ILClusterProperty",
86
- "ILQmgrProperty",
87
- "ILSessionProperty",
88
- "ILServiceProperty",
89
- "ILSpokeProperty",
90
- "ILAccessRuleProperty",
91
- "ILSecureFileProperty",
92
- "ILQueueRealTimeInfo",
93
- "ILQueueFlow",
94
- "ILQmgrDiag",
95
- "ILClusterDefDiag",
96
- "ILClusterPeerDef",
97
- # Exceptions
98
- "ILException",
99
- "ILNoMsgException",
100
- "ILTimeoutException",
101
- "ILOperationException",
102
- "ILSessionException",
103
- "ILMsgTypeException",
104
- ]
1
+ """iLink - Python client API for iLink M.O.M. (Message Oriented Middleware)."""
2
+
3
+ __version__ = "0.18.0"
4
+
5
+ # --- Admin / Service ---
6
+ from .admin import ILAdminService, ILAdminQmgr, ILLogBrowser, ILQueueBrowser
7
+
8
+ # --- Queue Manager & Queue ---
9
+ from .qmgr import ILQmgr, ILQueue
10
+
11
+ # --- Message ---
12
+ from .msg import ILMsg, ILMsgUtil
13
+
14
+ # --- Constants & Enums ---
15
+ from .common import ILC, ILCC, ILRC, MatchOption
16
+
17
+ # --- Properties ---
18
+ from .properties import (
19
+ DestItem,
20
+ PartitionItem,
21
+ SegmentItem,
22
+ ILQueueProperty,
23
+ ILChannelProperty,
24
+ ILChannelType,
25
+ ILChannelStatus,
26
+ ILChannelDirection,
27
+ ILChannelSecurity,
28
+ ILChannelBalanceMode,
29
+ ILClusterNode,
30
+ ILClusterControlResult,
31
+ ILClusterProperty,
32
+ ILQmgrProperty,
33
+ ILSessionProperty,
34
+ ILServiceProperty,
35
+ ILSpokeProperty,
36
+ ILAccessRuleProperty,
37
+ ILSecureFileProperty,
38
+ ILQueueRealTimeInfo,
39
+ ILQueueFlow,
40
+ ILQmgrDiag,
41
+ ILClusterDefDiag,
42
+ ILClusterPeerDef,
43
+ )
44
+
45
+ # --- Exceptions ---
46
+ from .exception import (
47
+ ILException,
48
+ ILNoMsgException,
49
+ ILTimeoutException,
50
+ ILOperationException,
51
+ ILSessionException,
52
+ ILMsgTypeException,
53
+ )
54
+
55
+ __all__ = [
56
+ # Admin
57
+ "ILAdminService",
58
+ "ILAdminQmgr",
59
+ "ILLogBrowser",
60
+ "ILQueueBrowser",
61
+ # QMgr / Queue
62
+ "ILQmgr",
63
+ "ILQueue",
64
+ # Message
65
+ "ILMsg",
66
+ "ILMsgUtil",
67
+ # Constants
68
+ "ILC",
69
+ "ILCC",
70
+ "ILRC",
71
+ "MatchOption",
72
+ # Properties
73
+ "DestItem",
74
+ "PartitionItem",
75
+ "SegmentItem",
76
+ "ILQueueProperty",
77
+ "ILChannelProperty",
78
+ "ILChannelType",
79
+ "ILChannelStatus",
80
+ "ILChannelDirection",
81
+ "ILChannelSecurity",
82
+ "ILChannelBalanceMode",
83
+ "ILClusterNode",
84
+ "ILClusterControlResult",
85
+ "ILClusterProperty",
86
+ "ILQmgrProperty",
87
+ "ILSessionProperty",
88
+ "ILServiceProperty",
89
+ "ILSpokeProperty",
90
+ "ILAccessRuleProperty",
91
+ "ILSecureFileProperty",
92
+ "ILQueueRealTimeInfo",
93
+ "ILQueueFlow",
94
+ "ILQmgrDiag",
95
+ "ILClusterDefDiag",
96
+ "ILClusterPeerDef",
97
+ # Exceptions
98
+ "ILException",
99
+ "ILNoMsgException",
100
+ "ILTimeoutException",
101
+ "ILOperationException",
102
+ "ILSessionException",
103
+ "ILMsgTypeException",
104
+ ]
@@ -119,7 +119,7 @@ class ILAdminService:
119
119
  args (list[str]): 명령행 인자. ``["--version"]`` 이면 버전을 찍는다.
120
120
  """
121
121
  if len(args) != 1:
122
- print("available argrments.")
122
+ print("available arguments.")
123
123
  print(" --version : displays the library version.")
124
124
  ILC.print_version(args[0] if args else "")
125
125
 
@@ -170,9 +170,11 @@ class ILAdminService:
170
170
  self.service.connect(host, port)
171
171
  if not clientName:
172
172
  clientName = caller_module_name() # 0.11.0: inspect.stack()[-1] 과 같은 값(프레임 순회 - 소스 줄을 읽지 않는다)
173
- conn_res_msg = self.service.request(
174
- ILC.MIMQ_NEW_CONNECTION, "0", clientName, "USR"
175
- )
173
+ # 0.18.0: 능력 선언을 **확장**에 싣는다. ☠ arg1 은 자동커밋 플래그 자리다.
174
+ _caps = ILC.client_caps_extension()
175
+ _cargs = ((ILC.MIMQ_NEW_CONNECTION,) + ((_caps,) if _caps else ())
176
+ + ("0", clientName, "USR"))
177
+ conn_res_msg = self.service.request(*_cargs)
176
178
  if conn_res_msg is None:
177
179
  raise ILOperationException("MIMQE_INTERNAL_ERROR")
178
180
  if conn_res_msg.getCode() != ILC.MIMQ_SUCCESSFUL_CONNECTION:
@@ -1035,6 +1037,7 @@ class ILAdminService:
1035
1037
  if not isinstance(cluster, ILClusterProperty):
1036
1038
  raise ILOperationException(
1037
1039
  "MIMQE_INVALID_ARGUMENT (createCluster takes an ILClusterProperty)")
1040
+ cluster._validate_addresses() # 왕복 전에 막는다 - 사유가 빠르다
1038
1041
  self.service.apply_for_report(
1039
1042
  ILC.MIMQ_CREATE_CLUSTER,
1040
1043
  cluster.get_serialized(),
@@ -1178,6 +1181,11 @@ class ILAdminService:
1178
1181
  Note:
1179
1182
  **노드 목록은 바꿀 수 없다** - 다르면 거절된다(조용히 무시되지 않는다).
1180
1183
 
1184
+ Note:
1185
+ ★ **컨트롤러는 서비스마다 하나다.** 컨트롤러를 셋 두려면 관리 서비스가
1186
+ 셋 있어야 한다 - 한 서비스에 몰면 나머지는 **닿지 않는 피어**가 되어
1187
+ 과반을 못 채우고 ☠ **거절이 아니라 끝나지 않는다.**
1188
+
1181
1189
  Example::
1182
1190
 
1183
1191
  p = judge_svc.getClusterProperty("C1")
@@ -1186,6 +1194,7 @@ class ILAdminService:
1186
1194
  """
1187
1195
  try:
1188
1196
  self._checkClusterSupported()
1197
+ cluster._validate_addresses()
1189
1198
  self.service.apply_for_report(
1190
1199
  ILC.MIMQ_SET_CLUSTER_PROPERTY,
1191
1200
  cluster.get_serialized(),
@@ -2865,7 +2874,7 @@ class ILAdminQmgr:
2865
2874
  args (list[str]): 명령행 인자. ``["--version"]`` 이면 버전을 찍는다.
2866
2875
  """
2867
2876
  if len(args) != 1:
2868
- print("available argrments.")
2877
+ print("available arguments.")
2869
2878
  print(" --version : displays the library version.")
2870
2879
  ILC.print_version(args[0] if args else "")
2871
2880
 
@@ -5666,12 +5675,12 @@ class ILAdminQmgr:
5666
5675
  전부다). 그래서 **클라이언트가 막는다** - 안 막으면 ``"JSON"``
5667
5676
  이나 오타를 보낸 쪽이 **요청한 것과 다른 모양을 조용히 받는다.**
5668
5677
 
5669
- Raises:
5670
- ValueError: ``format`` 이 ``"TEXT"`` / ``"LINE"`` 이 아닐 때.
5671
-
5672
5678
  Returns:
5673
5679
  리포트 문자열. 클러스터 미구성이면 본문에 ``STANDALONE (not clustered)``.
5674
5680
 
5681
+ Raises:
5682
+ ValueError: ``format`` 이 ``"TEXT"`` / ``"LINE"`` 이 아닐 때.
5683
+
5675
5684
  Example::
5676
5685
 
5677
5686
  aq = svc.accessAdminQmgr("NODE_A")
@@ -6134,8 +6143,8 @@ class ILAdminQmgr:
6134
6143
 
6135
6144
  Args:
6136
6145
  topicName (str): 토픽 이름. 엔진 **rev 3393 이상**은 ``A-Z a-z 0-9 . _ -``
6137
- 만 1~47바이트로 받고 ``.`` / ``..`` 는 거절한다(Kafka 토픽 이름 규칙과
6138
- 같다). 그 이전 엔진은 47바이트 상한만 검사한다. 엔진은 **생성할 때만**
6146
+ 만 1~47바이트로 받고 ``.`` / ``..`` 는 거절한다.
6147
+ 그 이전 엔진은 47바이트 상한만 검사한다. 엔진은 **생성할 때만**
6139
6148
  검사하므로 이미 있는 토픽은 이름과 상관없이 계속 쓸 수 있다.
6140
6149
  options (str): ``key=value`` 를 ``;`` 로 이은 문자열. 기본 ``""`` 는
6141
6150
  엔진 기본값이다. 예 ``"partitions=3"``, ``"retentionms=86400000"``.
@@ -895,6 +895,17 @@ class ILC:
895
895
  MIMQ_CLIENT_NAME_ILSC = "ILSC"
896
896
  MIMQ_CLIENT_NAME_ILSVC = "ILSVC"
897
897
  MIMQ_CLIENT_NAME_ILWC = "ILWC"
898
+
899
+ #: 접속 확장에 싣는 **능력 선언**의 접두(0.18.0, 엔진 CAPDECL-2 짝).
900
+ #: ☠ ``arg1`` 에 실으면 안 된다 - 엔진이 그 자리를 **자동커밋 플래그**로 읽는다
901
+ #: (``atoi("0.18.0") == 0`` 이라 모든 세션이 조용히 트랜잭션 모드가 된다).
902
+ MIMQ_CLIENT_CAPS_PREFIX = "CAP="
903
+ #: 능력 선언의 최대 길이(접두 포함). 넘으면 **자르지 않고 통째로 버린다** -
904
+ #: 자르면 ``clsblob=2`` 가 ``clsblob=`` 가 되어 **읽히는데 뜻이 다른** 값이 된다.
905
+ MIMQ_CLIENT_CAPS_MAX = 63
906
+ #: 이 클라가 읽고 쓰는 클러스터 블롭 판. :data:`ilink.properties.CLUSTER_LAYOUT_MARK`
907
+ #: 와 능력 선언이 **모두 이 값에서 나온다** - 판 번호를 두 곳에 적지 않는다.
908
+ MIMQ_CLUSTER_BLOB_VERSION = 2
898
909
  MIMQ_SESSION_STATUS_DEAD = 0
899
910
  MIMQ_SESSION_STATUS_IDLE = 1
900
911
  MIMQ_SESSION_STATUS_RECEIVING = 2
@@ -1027,6 +1038,35 @@ class ILC:
1027
1038
  return False
1028
1039
  return True
1029
1040
 
1041
+ @staticmethod
1042
+ def client_caps_extension():
1043
+ """접속 메시지에 실을 **능력 선언** 확장을 만든다(0.18.0).
1044
+
1045
+ Returns:
1046
+ bytes | None: ``b"CAP=clsblob=2,ver=0.18.0"`` 꼴. 길이가
1047
+ :data:`MIMQ_CLIENT_CAPS_MAX` 를 넘으면 **None**(안 싣는다).
1048
+
1049
+ Note:
1050
+ ★ 엔진은 이 값을 **기록만** 한다 - 아무것도 거부하지 않는다. 이득은
1051
+ **다음 판 변경 때부터**다: 그때 엔진이 "못 읽는 클라" 에게 사유 있는
1052
+ 오류를 줄 수 있다. 지금 배포된 판(0.17.0 이하)은 구할 수 없다.
1053
+
1054
+ ☠ **admin / qmgr 유형에만 싣는다.** ``CHL`` / ``REL`` 은 확장을 자기
1055
+ 용도로 쓰고 옛 엔진이 그 크기로 분기한다.
1056
+
1057
+ 판정은 ``clsblob`` 으로 한다. ``ver`` 은 사람이 로그에서 읽는 값이고
1058
+ **판정에 쓰지 않는다** - 판 문자열로 능력을 유도하면 엔진이 표를 들고
1059
+ 있어야 하고 그 표는 반드시 낡는다.
1060
+ """
1061
+ import ilink # 판 문자열은 한 곳에만 있다
1062
+ text = "%sclsblob=%d,ver=%s" % (ILC.MIMQ_CLIENT_CAPS_PREFIX,
1063
+ ILC.MIMQ_CLUSTER_BLOB_VERSION,
1064
+ getattr(ilink, "__version__", "?"))
1065
+ raw = bytes(c for c in text.encode("utf-8", "replace") if 0x20 <= c <= 0x7E)
1066
+ if len(raw) > ILC.MIMQ_CLIENT_CAPS_MAX:
1067
+ return None # 자르지 않는다 - 뜻이 달라진다
1068
+ return raw
1069
+
1030
1070
  @staticmethod
1031
1071
  def get_api_version_info() -> str:
1032
1072
  """이 라이브러리의 버전 정보 문자열.
@@ -42,7 +42,14 @@ def _ba_pick_int(ba: bytes, offset: int, length: int) -> int:
42
42
 
43
43
 
44
44
  def clamp_max_frame_length(n: int) -> int:
45
- """프레임 길이 상한 설정값을 엔진 설정 범위(1,048,576 ~ 999,999,999)로 맞춘다."""
45
+ """프레임 길이 상한 설정값을 엔진 설정 범위(1,048,576 ~ 999,999,999)로 맞춘다.
46
+
47
+ Args:
48
+ n (int): 맞출 값(**바이트**).
49
+
50
+ Returns:
51
+ int: 범위 안으로 보정한 값(**바이트**).
52
+ """
46
53
  return min(max(ILC.MIMQ_MIN_MAX_FRAME_LENGTH, int(n)), ILC.MIMQ_MAX_FRAME_LENGTH)
47
54
 
48
55
 
@@ -1,78 +1,85 @@
1
- """토픽 파티셔너 - 키로 파티션 번호를 정한다(Kafka ``DefaultPartitioner`` 와 같은 규칙).
2
-
3
- 엔진은 발행 레코드의 파티션을 고르지 않는다(IDEMP 설계 v0.4, 사용자 결정). 발행 경로
4
- (producer, 관리 API 단건 발행)가 **이 모듈 하나로** 파티션을 정해 명시 파티션으로 싣는다.
5
- 그래서 같은 키는 어느 경로로 보내도 같은 파티션으로 간다.
6
-
7
- - 해시는 Kafka ``org.apache.kafka.common.utils.Utils.murmur2`` 와 비트 단위로 같다
8
- (seed ``0x9747b28c``). 골든 벡터 ``tests/data/murmur2_golden.txt`` 는 Kafka 3.9 jar 에서 뽑았다.
9
- - 키는 **UTF-8 바이트**로 해시한다. 자바 클라이언트도 같은 바이트를 쓴다.
10
- - 파티션 = ``to_positive(murmur2(UTF-8 키)) % 파티션 수``.
11
-
12
- **응용이 직접 부를 일은 없다** - 라이브러리 내부에서 쓴다.
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- _M = 0x5BD1E995
18
- _SEED = 0x9747B28C
19
- _MASK = 0xFFFFFFFF
20
-
21
-
22
- def murmur2(data: bytes) -> int:
23
- """Kafka ``Utils.murmur2`` 와 같은 32비트 해시(부호 있는 정수)를 돌려준다.
24
-
25
- Args:
26
- data (bytes): 해시할 바이트.
27
-
28
- Returns:
29
- int: -2^31 ~ 2^31-1 범위의 해시(자바 int 와 같은 값).
30
- """
31
- length = len(data)
32
- h = (_SEED ^ length) & _MASK
33
- n4 = length // 4
34
- for i in range(n4):
35
- i4 = i * 4
36
- k = data[i4] | (data[i4 + 1] << 8) | (data[i4 + 2] << 16) | (data[i4 + 3] << 24)
37
- k = (k * _M) & _MASK
38
- k ^= k >> 24
39
- k = (k * _M) & _MASK
40
- h = (h * _M) & _MASK
41
- h ^= k
42
- rem = length % 4
43
- base = length & ~3
44
- if rem >= 3:
45
- h ^= data[base + 2] << 16
46
- if rem >= 2:
47
- h ^= data[base + 1] << 8
48
- if rem >= 1:
49
- h ^= data[base]
50
- h = (h * _M) & _MASK
51
- h ^= h >> 13
52
- h = (h * _M) & _MASK
53
- h ^= h >> 15
54
- return h - (1 << 32) if h & 0x80000000 else h
55
-
56
-
57
- def to_positive(value: int) -> int:
58
- """Kafka ``Utils.toPositive`` - 부호 비트를 떼어 0 이상으로 만든다."""
59
- return value & 0x7FFFFFFF
60
-
61
-
62
- def partition_for_key(key: str | bytes, num_partitions: int) -> int:
63
- """키가 있는 레코드의 파티션 번호.
64
-
65
- Args:
66
- key (str | bytes): 레코드 키. 문자열이면 UTF-8 로 바꿔 해시한다.
67
- num_partitions (int): 토픽의 파티션 수(1 이상).
68
-
69
- Returns:
70
- int: 0 ~ num_partitions-1.
71
-
72
- Raises:
73
- ValueError: 파티션 수가 1 미만일 때.
74
- """
75
- if num_partitions < 1:
76
- raise ValueError("num_partitions must be >= 1: %r" % (num_partitions,))
77
- raw = key.encode("utf-8") if isinstance(key, str) else bytes(key)
78
- return to_positive(murmur2(raw)) % num_partitions
1
+ """토픽 파티셔너 - 키로 파티션 번호를 정한다(Kafka ``DefaultPartitioner`` 와 같은 규칙).
2
+
3
+ 엔진은 발행 레코드의 파티션을 고르지 않는다(IDEMP 설계 v0.4, 사용자 결정). 발행 경로
4
+ (producer, 관리 API 단건 발행)가 **이 모듈 하나로** 파티션을 정해 명시 파티션으로 싣는다.
5
+ 그래서 같은 키는 어느 경로로 보내도 같은 파티션으로 간다.
6
+
7
+ - 해시는 Kafka ``org.apache.kafka.common.utils.Utils.murmur2`` 와 비트 단위로 같다
8
+ (seed ``0x9747b28c``). 골든 벡터 ``tests/data/murmur2_golden.txt`` 는 Kafka 3.9 jar 에서 뽑았다.
9
+ - 키는 **UTF-8 바이트**로 해시한다. 자바 클라이언트도 같은 바이트를 쓴다.
10
+ - 파티션 = ``to_positive(murmur2(UTF-8 키)) % 파티션 수``.
11
+
12
+ **응용이 직접 부를 일은 없다** - 라이브러리 내부에서 쓴다.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ _M = 0x5BD1E995
18
+ _SEED = 0x9747B28C
19
+ _MASK = 0xFFFFFFFF
20
+
21
+
22
+ def murmur2(data: bytes) -> int:
23
+ """Kafka ``Utils.murmur2`` 와 같은 32비트 해시(부호 있는 정수)를 돌려준다.
24
+
25
+ Args:
26
+ data (bytes): 해시할 바이트.
27
+
28
+ Returns:
29
+ int: -2^31 ~ 2^31-1 범위의 해시(자바 int 와 같은 값).
30
+ """
31
+ length = len(data)
32
+ h = (_SEED ^ length) & _MASK
33
+ n4 = length // 4
34
+ for i in range(n4):
35
+ i4 = i * 4
36
+ k = data[i4] | (data[i4 + 1] << 8) | (data[i4 + 2] << 16) | (data[i4 + 3] << 24)
37
+ k = (k * _M) & _MASK
38
+ k ^= k >> 24
39
+ k = (k * _M) & _MASK
40
+ h = (h * _M) & _MASK
41
+ h ^= k
42
+ rem = length % 4
43
+ base = length & ~3
44
+ if rem >= 3:
45
+ h ^= data[base + 2] << 16
46
+ if rem >= 2:
47
+ h ^= data[base + 1] << 8
48
+ if rem >= 1:
49
+ h ^= data[base]
50
+ h = (h * _M) & _MASK
51
+ h ^= h >> 13
52
+ h = (h * _M) & _MASK
53
+ h ^= h >> 15
54
+ return h - (1 << 32) if h & 0x80000000 else h
55
+
56
+
57
+ def to_positive(value: int) -> int:
58
+ """Kafka ``Utils.toPositive`` - 부호 비트를 떼어 0 이상으로 만든다.
59
+
60
+ Args:
61
+ value (int): 부호 있는 32비트 값.
62
+
63
+ Returns:
64
+ int: 0 이상으로 만든 값.
65
+ """
66
+ return value & 0x7FFFFFFF
67
+
68
+
69
+ def partition_for_key(key: str | bytes, num_partitions: int) -> int:
70
+ """키가 있는 레코드의 파티션 번호.
71
+
72
+ Args:
73
+ key (str | bytes): 레코드 키. 문자열이면 UTF-8 로 바꿔 해시한다.
74
+ num_partitions (int): 토픽의 파티션 수(1 이상).
75
+
76
+ Returns:
77
+ int: 0 ~ num_partitions-1.
78
+
79
+ Raises:
80
+ ValueError: 파티션 수가 1 미만일 때.
81
+ """
82
+ if num_partitions < 1:
83
+ raise ValueError("num_partitions must be >= 1: %r" % (num_partitions,))
84
+ raw = key.encode("utf-8") if isinstance(key, str) else bytes(key)
85
+ return to_positive(murmur2(raw)) % num_partitions
@@ -303,7 +303,7 @@ class ILProducerConfig:
303
303
 
304
304
  def maxRequestSize(self, n: int) -> "ILProducerConfig":
305
305
  """**봉투 하나의 본문 크기 상한**이자 레코드 크기 상한(바이트).
306
- (기본 1048576 = 1MB, Kafka ``max.request.size`` 와 같은 뜻)
306
+ (기본 1048576 = 1MB)
307
307
 
308
308
  Args:
309
309
  n (int): 프레임 크기 상한(**바이트**). **1024 미만은 1024 로, 999,999,974 초과는
@@ -417,7 +417,7 @@ class ILProducerConfig:
417
417
  """lz4 압축 레벨을 정한다. (기본 ``9`` = 빠른 압축기)
418
418
 
419
419
  Args:
420
- level (int): ``1`` ~ ``17``. Kafka ``compression.lz4.level`` 과 뜻이 같다 - ``9`` (기본)는 빠른 압축기이고,
420
+ level (int): ``1`` ~ ``17``. ``9`` (기본)는 빠른 압축기이고,
421
421
  ``9`` 가 아닌 값은 그 레벨의 LZ4HC(훨씬 느리고 조금 더 준다)다. 레벨 12 이상은 결과가 같다.
422
422
 
423
423
  Returns:
@@ -689,8 +689,8 @@ class ILProducerConfig:
689
689
  # 0.7.0: 엔진은 (pid, 파티션)마다 최근 배치 5개로 중복을 가린다 - 그보다 많이 겹쳐 보내면 재전송이 중복으로 가려지지 않는다
690
690
  if self._idempotenceSet and self._enableIdempotence and self._maxInFlight > 5:
691
691
  raise ValueError(
692
- "enableIdempotence requires maxInFlight <= 5 (the engine remembers the last 5 batches"
693
- " per partition; Kafka has the same limit)"
692
+ "enableIdempotence requires maxInFlight <= 5"
693
+ " (the engine remembers the last 5 batches per partition)"
694
694
  )
695
695
  # 0.8.0: 직접 대입(cfg.compressionType = "gzip")은 빌더의 검사를 거치지 않는다 - 여기서 다시 본다
696
696
  self._compressionType = _codec.check_type(self._compressionType)
@@ -1492,7 +1492,9 @@ class ILTopicProducer:
1492
1492
  conn.max_frame_length = self.config.get_max_frame_length()
1493
1493
  conn.connect(host, port, self.config.get_request_timeout_ms())
1494
1494
  try:
1495
- rep = self._request(conn, ILC.MIMQ_NEW_CONNECTION, "1", self.clientName, "USR")
1495
+ # 0.18.0: 능력 선언. ☠ arg1("1")은 자동커밋 플래그라 그대로 둔다.
1496
+ rep = self._request(conn, ILC.MIMQ_NEW_CONNECTION, "1", self.clientName, "USR",
1497
+ ext=ILC.client_caps_extension())
1496
1498
  if rep is None or rep.getCode() != ILC.MIMQ_SUCCESSFUL_CONNECTION:
1497
1499
  raise ILOperationException((rep.getArg2() if rep is not None else "")
1498
1500
  or "MIMQE_INVALID_MSG_FORMAT (connection)")
@@ -1534,9 +1536,10 @@ class ILTopicProducer:
1534
1536
  self._connected = True
1535
1537
  self._reconnect_attempts = 0
1536
1538
 
1537
- def _request(self, conn: TCPConnector, code: int, *args: str):
1539
+ def _request(self, conn: TCPConnector, code: int, *args: str, ext: bytes = None):
1538
1540
  with _io_lock_of(conn): # 0.7.3: 부트스트랩 전용 연결이지만 요청-응답 짝 규칙은 같게
1539
- conn.send(ILMsgUtil.create_request_msg(code, *args))
1541
+ conn.send(ILMsgUtil.create_request_msg_with_ext(code, ext, *args)
1542
+ if ext is not None else ILMsgUtil.create_request_msg(code, *args))
1540
1543
  res = conn.recv(self.config.get_request_timeout_ms())
1541
1544
  if res.getType() == ILC.MIMQ_REPORT_MESSAGE:
1542
1545
  return res.getReportMsg()
@@ -200,6 +200,17 @@ class _AutoAccessor:
200
200
  field = name[2].lower() + name[3:]
201
201
  if hasattr(self, field):
202
202
  return lambda: bool(getattr(self, field))
203
+ # 0.18.0: 개명 안내를 **게터 호출에도** 붙인다. 전에는 대입 경로에만 있어서
204
+ # `p.getClusterType()` 이 맨 AttributeError 로 떨어졌다 - 정작 시험과 예제가
205
+ # 부딪히는 것은 게터 쪽이다.
206
+ for _pfx, _cut in (("get", 3), ("set", 3), ("is", 2)):
207
+ if name.startswith(_pfx) and len(name) > _cut:
208
+ moved = type(self)._RENAMED.get(name[_cut].lower() + name[_cut + 1:])
209
+ if moved is not None:
210
+ raise AttributeError(
211
+ f"{self.__class__.__name__} has no attribute {name}"
212
+ f" - {moved[1]} 에서 {moved[0]} 로 바뀌었다"
213
+ )
203
214
  raise AttributeError(f"{self.__class__.__name__} has no attribute {name}")
204
215
 
205
216
  _SPEC: dict = {}
@@ -3267,8 +3278,7 @@ class ILQmgrProperty(_AutoAccessor):
3267
3278
  스펙: 기본 10485760 · 범위 1~1073741824000 · 단위 byte · 변경 가능/재기동 불필요.
3268
3279
  subExpiryMinutes (int): 구독 만료(분). 기본 10080(7일).
3269
3280
  스펙: 기본 10080 · 범위 0~2147483647 (0=비활성) · 단위 분 · 변경 가능/재기동 불필요.
3270
- memberExpiryMinutes (int): 컨슈머 그룹 멤버 유휴 만료(분). 기본 5
3271
- (Kafka ``max.poll.interval.ms`` 기본값에 대응).
3281
+ memberExpiryMinutes (int): 컨슈머 그룹 멤버 유휴 만료(분). 기본 5.
3272
3282
  구독 옵션 ``expiryMs`` 가 이 값을 덮는다.
3273
3283
  스펙: 기본 5 · 범위 1~9999 · 단위 분 · 변경 가능/재기동 불필요.
3274
3284
  deadSessionKeepDuration (int): 끊긴 세션 정보 보관(초). 기본 600.
@@ -5292,11 +5302,11 @@ class ILSpokeProperty(_AutoAccessor):
5292
5302
  #: 왼쪽 정렬 십진수로 썼고 ``MIMQ_RUNNING`` 이 **2** 라, 판 표시를 ``"2"`` 로 두면
5293
5303
  #: 옛 블롭이 **"판 2 와 정확히 일치" 로 읽힌다**(거절도 아니고 "낮은 판" 도 아니다).
5294
5304
  #: 글자로 시작하면 옛 블롭이 만들 수 없는 값이 되어 두 방향이 모두 막힌다.
5295
- CLUSTER_LAYOUT_MARK = "V2"
5305
+ CLUSTER_LAYOUT_MARK = "V%d" % ILC.MIMQ_CLUSTER_BLOB_VERSION
5296
5306
 
5297
5307
  #: :data:`CLUSTER_LAYOUT_MARK` 의 숫자 부분. 비교는 이쪽으로 한다 - 문자열 비교는
5298
5308
  #: ``"V10" < "V2"`` 로 갈린다.
5299
- _CLUSTER_LAYOUT_NUMBER = 2
5309
+ _CLUSTER_LAYOUT_NUMBER = ILC.MIMQ_CLUSTER_BLOB_VERSION
5300
5310
 
5301
5311
  #: :class:`ILClusterNode` 와이어 레이아웃 ``(속성 이름, 폭)``. **오프셋은 순서에서 계산한다** -
5302
5312
  #: 폭이 조정돼도 이 표 한 줄만 고치면 파싱·직렬화·보존이 같이 따라간다.
@@ -5380,6 +5390,13 @@ class ILClusterNode(_AutoAccessor):
5380
5390
  ctrlPort (int): 그 노드의 **컨트롤러 포트**. 문법에서 주소 뒤 ``/9000`` 자리다.
5381
5391
  스펙: 범위 1025~65535 · 변경 불가.
5382
5392
  coordinator (bool | None): 이 노드가 **컨트롤러 멤버**인가(문법의 ``COORDS``).
5393
+ 엔진 rev 3463 이상이면 보낸 값이 **정의에 보존된다**(되읽으면 온다).
5394
+ ☠ 다만 **동작에 쓰이는 것은 컨트롤러 정족수가 켜진 뒤**다 - 그전에는
5395
+ 저장만 되고 아무 일도 일어나지 않는다.
5396
+ ☠ 문법의 ``COORDS(1)`` 은 *"1번이 컨트롤러고 **나머지는 아니다**"* 라는
5397
+ 완결된 진술이다. 블롭으로 같은 말을 하려면 나머지 노드를 **명시적으로
5398
+ ``False``** 로 둬야 한다 - 빈칸으로 두면 *"모른다"* 가 되어 뜻이 다르다.
5399
+ 컨트롤러 노드에는 **``ctrl_port`` 가 반드시** 있어야 한다.
5383
5400
  ★ **``None`` 은 "모름"** 이고 ``False``("아니다")와 **다르다**. 서버가 안
5384
5401
  채웠거나 이 판이 모르는 값일 때 ``None`` 이다. 참/거짓으로 판정하지 말고
5385
5402
  ``is None`` 으로 먼저 가른다.
@@ -5660,7 +5677,11 @@ _CLUSTER_FIXED_FIELDS = (
5660
5677
  ("replicas", 5), ("minSync", 5),
5661
5678
  ("secretFile", 256),
5662
5679
  ("controllerLeader", 6), ("defTerm", 10), ("defVersion", 10),
5663
- ("signature", 20), ("statusReason", 96), (None, 185),
5680
+ ("signature", 20), ("statusReason", 96),
5681
+ # 엔진 r3473 이 예약 앞 12바이트에 이름을 줬다. **판 번호는 안 올랐다**(V2 그대로) -
5682
+ # 이 클라는 모르는 자리를 원본째 되보내므로 이름이 붙기 전에도 아무 문제가 없었다.
5683
+ # ☠ 둘 다 **관측 전용**이다: 엔진이 나갈 때만 채우고 들어올 때는 지운다.
5684
+ ("actingCoordinator", 6), ("localCtrlPort", 6), (None, 173),
5664
5685
  )
5665
5686
 
5666
5687
  _CLUSTER_OFF, _CLUSTER_LEN = _field_offsets(_CLUSTER_FIXED_FIELDS)
@@ -5765,6 +5786,11 @@ class ILClusterProperty(_AutoAccessor):
5765
5786
  defVersion (int | None): **읽기 전용.** 이 정의의 **판 번호**.
5766
5787
  ★ ``defTerm`` 과 **쌍**이다. **0 은 정상값**이고 없음은 오직 ``None`` 이다.
5767
5788
  signature (str): **읽기 전용.** 엔진이 정의에 붙이는 표식.
5789
+ actingCoordinator (int | None): **읽기 전용.** 승격을 집행하는 조정자의
5790
+ 노드 번호. 모르면 ``None`` - **0 은 노드 번호가 아니다.**
5791
+ localCtrlPort (int | None): **읽기 전용.** 이 응답을 준 **서비스가 실제로
5792
+ 듣고 있는** ilcc 포트. 정의에 적힌 값이 아니라 **관측값**이다.
5793
+ ★ 둘 다 엔진이 **나갈 때만** 채운다 - 실어 보내도 무시된다.
5768
5794
  nodes (list[ILClusterNode]): 클러스터를 이루는 노드 전부.
5769
5795
 
5770
5796
  Note:
@@ -5806,11 +5832,15 @@ class ILClusterProperty(_AutoAccessor):
5806
5832
  "defTerm": "엔진이 채우는 값이다",
5807
5833
  "defVersion": "엔진이 채우는 값이다",
5808
5834
  "signature": "엔진이 채우는 값이다",
5835
+ "actingCoordinator": "관측값이다 - 엔진이 나갈 때만 채운다",
5836
+ "localCtrlPort": "관측값이다 - 답한 서비스가 실제로 듣는 포트다",
5809
5837
  }
5810
5838
 
5811
5839
  _RENAMED = {
5812
5840
  "clusterType": ("노드마다의 역할(ILClusterNode.is_broker / coordinator)", "0.17.0"),
5813
5841
  "ilccPort": ("노드마다의 ILClusterNode.ctrlPort", "0.17.0"),
5842
+ "judge": ("노드마다의 ILClusterNode.is_coordinator", "0.17.0"),
5843
+ "coordinator": ("노드마다의 ILClusterNode.is_coordinator", "0.17.0"),
5814
5844
  "authorityHolder": ("controllerLeader (노드 번호다)", "0.17.0"),
5815
5845
  }
5816
5846
 
@@ -5826,15 +5856,15 @@ class ILClusterProperty(_AutoAccessor):
5826
5856
  ba (bytes | bytearray | None): 서버가 보낸 바이트(키워드로도 줄 수 있다).
5827
5857
  주면 나머지 인자는 무시된다.
5828
5858
 
5859
+ Raises:
5860
+ ILDeserializeException: 바이트 길이가 규격보다 짧거나, **판 표시가 없거나
5861
+ (레거시) 이 클라보다 높을 때.**
5862
+
5829
5863
  Note:
5830
5864
  ★ **둘째 자리부터는 키워드로만 받는다.** 예전 둘째 자리는 ``ilcc_port`` 였다.
5831
5865
  그대로 위치 인자를 받으면 ``ILClusterProperty("C1", 27091)`` 이 **예외 없이**
5832
5866
  ``is_auto_start = 27091`` 로 들어가 조용히 다른 뜻이 된다 - **자리 재사용은 제거보다
5833
5867
  나쁘다.** 키워드 전용으로 막아 ``TypeError`` 로 터지게 한다.
5834
-
5835
- Raises:
5836
- ILDeserializeException: 바이트 길이가 규격보다 짧거나, **판 표시가 없거나
5837
- (레거시) 이 클라보다 높을 때.**
5838
5868
  """
5839
5869
  if isinstance(name, (bytes, bytearray)):
5840
5870
  ba, name = name, ""
@@ -5855,6 +5885,8 @@ class ILClusterProperty(_AutoAccessor):
5855
5885
  self.defTerm = None
5856
5886
  self.defVersion = None
5857
5887
  self.signature = ""
5888
+ self.actingCoordinator = None
5889
+ self.localCtrlPort = None
5858
5890
  #: (내부) 서버가 준 고정부 원본. **읽기만 하는 자리와 읽지도 쓰지도 않는 구간을
5859
5891
  #: 되돌려 보내기 위해** 들고 있다(:meth:`get_serialized` 참조).
5860
5892
  self._rawBytes = b""
@@ -5905,6 +5937,10 @@ class ILClusterProperty(_AutoAccessor):
5905
5937
  self.defTerm = ba_pick_int_opt(ba, o["defTerm"][0], o["defTerm"][1])
5906
5938
  self.defVersion = ba_pick_int_opt(ba, o["defVersion"][0], o["defVersion"][1])
5907
5939
  self.signature = ba_pick_string(ba, o["signature"][0], o["signature"][1])
5940
+ self.actingCoordinator = ba_pick_int_opt(ba, o["actingCoordinator"][0],
5941
+ o["actingCoordinator"][1])
5942
+ self.localCtrlPort = ba_pick_int_opt(ba, o["localCtrlPort"][0],
5943
+ o["localCtrlPort"][1])
5908
5944
  _sr_off, _sr_len = o["statusReason"]
5909
5945
  self.statusReason = bytes(ba[_sr_off:_sr_off + _sr_len]).split(b"\x00")[0].decode(
5910
5946
  "utf-8", "replace")
@@ -5924,6 +5960,11 @@ class ILClusterProperty(_AutoAccessor):
5924
5960
  Returns:
5925
5961
  list[ILClusterNode]: ``coordinator`` 가 **True 인 노드만**. ``None``(모름)은
5926
5962
  넣지 않는다 - 모르는 것을 정족수에 세면 정족수가 틀린다.
5963
+
5964
+ Note:
5965
+ ☠ **빈 목록은 두 뜻이다** - *"컨트롤러가 없다"* 와 *"아직 모른다"*. 목록
5966
+ 하나로는 삼상태를 못 싣는다. 가르려면 노드별
5967
+ :meth:`ILClusterNode.is_coordinator` 를 ``is None`` 으로 본다.
5927
5968
  """
5928
5969
  return [n for n in self.nodes if n.coordinator is True]
5929
5970
 
@@ -5943,6 +5984,53 @@ class ILClusterProperty(_AutoAccessor):
5943
5984
 
5944
5985
  getBrokerNodes = get_broker_nodes
5945
5986
 
5987
+ def _validate_addresses(self) -> None:
5988
+ """노드 주소가 서로 겹치는지 **모든 짝**을 본다.
5989
+
5990
+ Raises:
5991
+ ILOperationException: 브로커 둘이 같은 ``(host, port)`` 를 들거나,
5992
+ 노드 둘이 같은 ``(host, ctrlPort)`` 를 들 때
5993
+ (``MIMQE_INVALID_ARGUMENT``).
5994
+
5995
+ Note:
5996
+ ★ **ctrl 포트 검사는 역할을 안 본다.** ctrl 포트는 *"컨트롤러의 투표
5997
+ 포트"* 가 아니라 **그 서비스의 ilcc 포트**다 - 컨트롤러가 아닌 서비스도
5998
+ ilcc 를 돌린다(브로커에 붙어 하트비트와 승격을 한다). 컨트롤러끼리만
5999
+ 보면 **역할 경계에 사각이 생긴다**(브로커 노드와 컨트롤러 전용 노드가
6000
+ 같은 포트를 들어도 안 걸린다).
6001
+
6002
+ Note:
6003
+ ☠ **한 호스트에 컨트롤러 전용 노드가 둘인 것은 여기서 막지 않는다.**
6004
+ 주소가 다르면 정의는 정당하고(한 호스트에 서비스 둘이 각각 컨트롤러를
6005
+ 들 수 있다), 엔진이 *"둘 중 내가 누구인지 못 가린다"* 는 것뿐이다. 그
6006
+ 판정은 **요청을 받는 서비스가 누구인가**에 달렸는데 클라는 그것을
6007
+ 모른다 - 걸면 **정당한 정의를 거절**하게 된다.
6008
+
6009
+ 같은 이유로 **컨트롤러 수 {1, 3, 5} 도 여기서 안 본다.** 그것은 엔진
6010
+ 정책이라 넓어질 수 있고, 그날 이 클라가 이유 없이 거절하는 쪽이 된다.
6011
+ ⇒ 클라가 걸어도 되는 것은 **정의 자체로 판정되는 모순**뿐이다.
6012
+ """
6013
+ seen: dict = {}
6014
+ for n in self.nodes: # 브로커끼리 데이터 주소
6015
+ if not n.is_broker():
6016
+ continue
6017
+ key = (n.host, n.port)
6018
+ if key in seen:
6019
+ raise ILOperationException(
6020
+ "MIMQE_INVALID_ARGUMENT (nodes %d and %d share qmgr port %d)"
6021
+ % (seen[key], n.nodeId, n.port))
6022
+ seen[key] = n.nodeId
6023
+ seen = {}
6024
+ for n in self.nodes: # ★ 역할 무관 - ctrl 포트가 있으면 본다
6025
+ if not n.ctrlPort:
6026
+ continue
6027
+ key = (n.host, n.ctrlPort)
6028
+ if key in seen:
6029
+ raise ILOperationException(
6030
+ "MIMQE_INVALID_ARGUMENT (nodes %d and %d share ctrl port %d)"
6031
+ % (seen[key], n.nodeId, n.ctrlPort))
6032
+ seen[key] = n.nodeId
6033
+
5946
6034
  def get_node(self, node_id: int):
5947
6035
  """번호로 노드를 찾는다.
5948
6036
 
@@ -199,7 +199,7 @@ class ILQmgr:
199
199
  그 외에는 사용법을 안내한다.
200
200
  """
201
201
  if len(args) != 1:
202
- print("available argrments.")
202
+ print("available arguments.")
203
203
  print(" --version : displays the library version.")
204
204
  ILC.print_version(args[0] if args else "")
205
205
 
@@ -461,9 +461,11 @@ class ILQmgr:
461
461
  conn_mode = ILC.MIMQ_SESSION_TRANSACTED
462
462
  if auto_commit:
463
463
  conn_mode = ILC.MIMQ_AUTO_ACKNOWLEDGE
464
- conn_res_msg = self.service.request(
465
- ILC.MIMQ_NEW_CONNECTION, str(conn_mode), client_name, "USR"
466
- )
464
+ # 0.18.0: 능력 선언. ☠ arg1(conn_mode)은 자동커밋 플래그라 건드리지 않는다.
465
+ _caps = ILC.client_caps_extension()
466
+ _cargs = ((ILC.MIMQ_NEW_CONNECTION,) + ((_caps,) if _caps else ())
467
+ + (str(conn_mode), client_name, "USR"))
468
+ conn_res_msg = self.service.request(*_cargs)
467
469
  if conn_res_msg is None:
468
470
  raise ILOperationException("MIMQE_INTERNAL_ERROR")
469
471
  if conn_res_msg.getCode() != ILC.MIMQ_SUCCESSFUL_CONNECTION:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jetstream-api
3
- Version: 0.17.0
3
+ Version: 0.18.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jetstream-api"
7
- version = "0.17.0"
7
+ version = "0.18.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
File without changes