rocket-welder-sdk 1.1.32__py3-none-any.whl → 1.1.34__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.
- rocket_welder_sdk/__init__.py +39 -6
- rocket_welder_sdk/controllers.py +138 -101
- rocket_welder_sdk/frame_metadata.py +138 -0
- rocket_welder_sdk/high_level/__init__.py +52 -0
- rocket_welder_sdk/high_level/client.py +262 -0
- rocket_welder_sdk/high_level/connection_strings.py +331 -0
- rocket_welder_sdk/high_level/data_context.py +169 -0
- rocket_welder_sdk/high_level/schema.py +197 -0
- rocket_welder_sdk/high_level/transport_protocol.py +238 -0
- rocket_welder_sdk/keypoints_protocol.py +642 -0
- rocket_welder_sdk/rocket_welder_client.py +94 -3
- rocket_welder_sdk/segmentation_result.py +420 -0
- rocket_welder_sdk/session_id.py +238 -0
- rocket_welder_sdk/transport/__init__.py +30 -0
- rocket_welder_sdk/transport/frame_sink.py +77 -0
- rocket_welder_sdk/transport/frame_source.py +74 -0
- rocket_welder_sdk/transport/nng_transport.py +197 -0
- rocket_welder_sdk/transport/stream_transport.py +193 -0
- rocket_welder_sdk/transport/tcp_transport.py +154 -0
- rocket_welder_sdk/transport/unix_socket_transport.py +339 -0
- {rocket_welder_sdk-1.1.32.dist-info → rocket_welder_sdk-1.1.34.dist-info}/METADATA +15 -2
- rocket_welder_sdk-1.1.34.dist-info/RECORD +39 -0
- rocket_welder_sdk-1.1.32.dist-info/RECORD +0 -22
- {rocket_welder_sdk-1.1.32.dist-info → rocket_welder_sdk-1.1.34.dist-info}/WHEEL +0 -0
- {rocket_welder_sdk-1.1.32.dist-info → rocket_welder_sdk-1.1.34.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data context types for per-frame keypoints and segmentation data.
|
|
3
|
+
|
|
4
|
+
Implements the Unit of Work pattern - contexts are created per-frame
|
|
5
|
+
and auto-commit when the processing delegate returns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import TYPE_CHECKING, Sequence, Tuple, Union
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import numpy.typing as npt
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from rocket_welder_sdk.keypoints_protocol import IKeyPointsWriter
|
|
18
|
+
from rocket_welder_sdk.segmentation_result import SegmentationResultWriter
|
|
19
|
+
|
|
20
|
+
from .schema import KeyPointDefinition, SegmentClass
|
|
21
|
+
|
|
22
|
+
# Type aliases
|
|
23
|
+
Point = Tuple[int, int]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IKeyPointsDataContext(ABC):
|
|
27
|
+
"""
|
|
28
|
+
Unit of Work for keypoints data, scoped to a single frame.
|
|
29
|
+
|
|
30
|
+
Auto-commits when the processing delegate returns.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def frame_id(self) -> int:
|
|
36
|
+
"""Current frame ID."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def add(self, point: KeyPointDefinition, x: int, y: int, confidence: float) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Add a keypoint detection for this frame.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
point: KeyPointDefinition from schema definition
|
|
46
|
+
x: X coordinate in pixels
|
|
47
|
+
y: Y coordinate in pixels
|
|
48
|
+
confidence: Detection confidence (0.0 to 1.0)
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def add_point(self, point: KeyPointDefinition, position: Point, confidence: float) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Add a keypoint detection using a Point tuple.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
point: KeyPointDefinition from schema definition
|
|
59
|
+
position: (x, y) tuple
|
|
60
|
+
confidence: Detection confidence (0.0 to 1.0)
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def commit(self) -> None:
|
|
66
|
+
"""Commit the context (called automatically when delegate returns)."""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ISegmentationDataContext(ABC):
|
|
71
|
+
"""
|
|
72
|
+
Unit of Work for segmentation data, scoped to a single frame.
|
|
73
|
+
|
|
74
|
+
Auto-commits when the processing delegate returns.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def frame_id(self) -> int:
|
|
80
|
+
"""Current frame ID."""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def add(
|
|
85
|
+
self,
|
|
86
|
+
segment_class: SegmentClass,
|
|
87
|
+
instance_id: int,
|
|
88
|
+
points: Union[Sequence[Point], npt.NDArray[np.int32]],
|
|
89
|
+
) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Add a segmentation instance for this frame.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
segment_class: SegmentClass from schema definition
|
|
95
|
+
instance_id: Instance ID (for multiple instances of same class, 0-255)
|
|
96
|
+
points: Contour points defining the instance boundary
|
|
97
|
+
"""
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def commit(self) -> None:
|
|
102
|
+
"""Commit the context (called automatically when delegate returns)."""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class KeyPointsDataContext(IKeyPointsDataContext):
|
|
107
|
+
"""Implementation of keypoints data context."""
|
|
108
|
+
|
|
109
|
+
def __init__(
|
|
110
|
+
self,
|
|
111
|
+
frame_id: int,
|
|
112
|
+
writer: IKeyPointsWriter,
|
|
113
|
+
) -> None:
|
|
114
|
+
self._frame_id = frame_id
|
|
115
|
+
self._writer = writer
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def frame_id(self) -> int:
|
|
119
|
+
return self._frame_id
|
|
120
|
+
|
|
121
|
+
def add(self, point: KeyPointDefinition, x: int, y: int, confidence: float) -> None:
|
|
122
|
+
"""Add a keypoint detection for this frame."""
|
|
123
|
+
self._writer.append(point.id, x, y, confidence)
|
|
124
|
+
|
|
125
|
+
def add_point(self, point: KeyPointDefinition, position: Point, confidence: float) -> None:
|
|
126
|
+
"""Add a keypoint detection using a Point tuple."""
|
|
127
|
+
self._writer.append_point(point.id, position, confidence)
|
|
128
|
+
|
|
129
|
+
def commit(self) -> None:
|
|
130
|
+
"""Commit the context (called automatically when delegate returns)."""
|
|
131
|
+
self._writer.close()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class SegmentationDataContext(ISegmentationDataContext):
|
|
135
|
+
"""Implementation of segmentation data context."""
|
|
136
|
+
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
frame_id: int,
|
|
140
|
+
writer: SegmentationResultWriter,
|
|
141
|
+
) -> None:
|
|
142
|
+
self._frame_id = frame_id
|
|
143
|
+
self._writer = writer
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def frame_id(self) -> int:
|
|
147
|
+
return self._frame_id
|
|
148
|
+
|
|
149
|
+
def add(
|
|
150
|
+
self,
|
|
151
|
+
segment_class: SegmentClass,
|
|
152
|
+
instance_id: int,
|
|
153
|
+
points: Union[Sequence[Point], npt.NDArray[np.int32]],
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Add a segmentation instance for this frame."""
|
|
156
|
+
if instance_id < 0 or instance_id > 255:
|
|
157
|
+
raise ValueError(f"instance_id must be 0-255, got {instance_id}")
|
|
158
|
+
|
|
159
|
+
# Convert to numpy array if needed
|
|
160
|
+
if isinstance(points, np.ndarray):
|
|
161
|
+
points_array = points
|
|
162
|
+
else:
|
|
163
|
+
points_array = np.array(points, dtype=np.int32)
|
|
164
|
+
|
|
165
|
+
self._writer.append(segment_class.class_id, instance_id, points_array)
|
|
166
|
+
|
|
167
|
+
def commit(self) -> None:
|
|
168
|
+
"""Commit the context (called automatically when delegate returns)."""
|
|
169
|
+
self._writer.close()
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Schema types for KeyPoints and Segmentation.
|
|
3
|
+
|
|
4
|
+
Provides type-safe definitions for keypoints and segmentation classes
|
|
5
|
+
that are defined at initialization time and used during processing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Dict, List, Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class KeyPointDefinition:
|
|
18
|
+
"""
|
|
19
|
+
A keypoint definition with ID and name.
|
|
20
|
+
|
|
21
|
+
Created via IKeyPointsSchema.define_point().
|
|
22
|
+
Used as a type-safe handle when adding keypoints to data context.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
id: int
|
|
26
|
+
name: str
|
|
27
|
+
|
|
28
|
+
def __str__(self) -> str:
|
|
29
|
+
return f"KeyPointDefinition({self.id}, '{self.name}')"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class SegmentClass:
|
|
34
|
+
"""
|
|
35
|
+
A segmentation class definition with class ID and name.
|
|
36
|
+
|
|
37
|
+
Created via ISegmentationSchema.define_class().
|
|
38
|
+
Used as a type-safe handle when adding instances to data context.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
class_id: int
|
|
42
|
+
name: str
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
return f"SegmentClass({self.class_id}, '{self.name}')"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class IKeyPointsSchema(ABC):
|
|
49
|
+
"""
|
|
50
|
+
Interface for defining keypoints schema.
|
|
51
|
+
|
|
52
|
+
Keypoints are defined once at initialization and referenced by handle
|
|
53
|
+
when adding data to the context.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def define_point(self, name: str) -> KeyPointDefinition:
|
|
58
|
+
"""
|
|
59
|
+
Define a new keypoint.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
name: Human-readable name for the keypoint (e.g., "nose", "left_eye")
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
KeyPointDefinition handle for use with IKeyPointsDataContext.add()
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def defined_points(self) -> List[KeyPointDefinition]:
|
|
72
|
+
"""Get all defined keypoints."""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def get_metadata_json(self) -> str:
|
|
77
|
+
"""Get JSON metadata for serialization."""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ISegmentationSchema(ABC):
|
|
82
|
+
"""
|
|
83
|
+
Interface for defining segmentation classes schema.
|
|
84
|
+
|
|
85
|
+
Classes are defined once at initialization and referenced by handle
|
|
86
|
+
when adding instances to the context.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
def define_class(self, class_id: int, name: str) -> SegmentClass:
|
|
91
|
+
"""
|
|
92
|
+
Define a new segmentation class.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
class_id: Unique class identifier (0-255)
|
|
96
|
+
name: Human-readable name for the class (e.g., "person", "car")
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
SegmentClass handle for use with ISegmentationDataContext.add()
|
|
100
|
+
"""
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
@abstractmethod
|
|
105
|
+
def defined_classes(self) -> List[SegmentClass]:
|
|
106
|
+
"""Get all defined classes."""
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def get_metadata_json(self) -> str:
|
|
111
|
+
"""Get JSON metadata for serialization."""
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class KeyPointsSchema(IKeyPointsSchema):
|
|
116
|
+
"""Implementation of keypoints schema."""
|
|
117
|
+
|
|
118
|
+
def __init__(self) -> None:
|
|
119
|
+
self._points: Dict[str, KeyPointDefinition] = {}
|
|
120
|
+
self._next_id = 0
|
|
121
|
+
|
|
122
|
+
def define_point(self, name: str) -> KeyPointDefinition:
|
|
123
|
+
"""Define a new keypoint."""
|
|
124
|
+
if name in self._points:
|
|
125
|
+
raise ValueError(f"Keypoint '{name}' already defined")
|
|
126
|
+
|
|
127
|
+
point = KeyPointDefinition(id=self._next_id, name=name)
|
|
128
|
+
self._points[name] = point
|
|
129
|
+
self._next_id += 1
|
|
130
|
+
return point
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def defined_points(self) -> List[KeyPointDefinition]:
|
|
134
|
+
"""Get all defined keypoints."""
|
|
135
|
+
return list(self._points.values())
|
|
136
|
+
|
|
137
|
+
def get_metadata_json(self) -> str:
|
|
138
|
+
"""
|
|
139
|
+
Get JSON metadata for serialization.
|
|
140
|
+
|
|
141
|
+
Format matches C# SDK:
|
|
142
|
+
{
|
|
143
|
+
"version": 1,
|
|
144
|
+
"type": "keypoints",
|
|
145
|
+
"points": [{"id": 0, "name": "nose"}, ...]
|
|
146
|
+
}
|
|
147
|
+
"""
|
|
148
|
+
metadata: Dict[str, Any] = {
|
|
149
|
+
"version": 1,
|
|
150
|
+
"type": "keypoints",
|
|
151
|
+
"points": [{"id": p.id, "name": p.name} for p in self._points.values()],
|
|
152
|
+
}
|
|
153
|
+
return json.dumps(metadata, indent=2)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class SegmentationSchema(ISegmentationSchema):
|
|
157
|
+
"""Implementation of segmentation schema."""
|
|
158
|
+
|
|
159
|
+
def __init__(self) -> None:
|
|
160
|
+
self._classes: Dict[int, SegmentClass] = {}
|
|
161
|
+
|
|
162
|
+
def define_class(self, class_id: int, name: str) -> SegmentClass:
|
|
163
|
+
"""Define a new segmentation class."""
|
|
164
|
+
if class_id < 0 or class_id > 255:
|
|
165
|
+
raise ValueError(f"class_id must be 0-255, got {class_id}")
|
|
166
|
+
|
|
167
|
+
if class_id in self._classes:
|
|
168
|
+
raise ValueError(f"Class ID {class_id} already defined")
|
|
169
|
+
|
|
170
|
+
segment_class = SegmentClass(class_id=class_id, name=name)
|
|
171
|
+
self._classes[class_id] = segment_class
|
|
172
|
+
return segment_class
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def defined_classes(self) -> List[SegmentClass]:
|
|
176
|
+
"""Get all defined classes."""
|
|
177
|
+
return list(self._classes.values())
|
|
178
|
+
|
|
179
|
+
def get_metadata_json(self) -> str:
|
|
180
|
+
"""
|
|
181
|
+
Get JSON metadata for serialization.
|
|
182
|
+
|
|
183
|
+
Format matches C# SDK:
|
|
184
|
+
{
|
|
185
|
+
"version": 1,
|
|
186
|
+
"type": "segmentation",
|
|
187
|
+
"classes": [{"classId": 1, "name": "person"}, ...]
|
|
188
|
+
}
|
|
189
|
+
"""
|
|
190
|
+
metadata: Dict[str, Any] = {
|
|
191
|
+
"version": 1,
|
|
192
|
+
"type": "segmentation",
|
|
193
|
+
"classes": [
|
|
194
|
+
{"classId": c.class_id, "name": c.name} for c in self._classes.values()
|
|
195
|
+
],
|
|
196
|
+
}
|
|
197
|
+
return json.dumps(metadata, indent=2)
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified transport protocol as a value type.
|
|
3
|
+
|
|
4
|
+
Supports: file://, socket://, nng+push+ipc://, nng+push+tcp://, etc.
|
|
5
|
+
|
|
6
|
+
Examples:
|
|
7
|
+
file:///home/user/output.bin - absolute file path
|
|
8
|
+
socket:///tmp/my.sock - Unix domain socket
|
|
9
|
+
nng+push+ipc://tmp/keypoints - NNG Push over IPC
|
|
10
|
+
nng+push+tcp://host:5555 - NNG Push over TCP
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from enum import Enum, auto
|
|
16
|
+
from typing import ClassVar, Dict, Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TransportKind(Enum):
|
|
20
|
+
"""Transport kind enumeration."""
|
|
21
|
+
|
|
22
|
+
FILE = auto()
|
|
23
|
+
"""File output."""
|
|
24
|
+
|
|
25
|
+
SOCKET = auto()
|
|
26
|
+
"""Unix domain socket (direct, no messaging library)."""
|
|
27
|
+
|
|
28
|
+
NNG_PUSH_IPC = auto()
|
|
29
|
+
"""NNG Push over IPC."""
|
|
30
|
+
|
|
31
|
+
NNG_PUSH_TCP = auto()
|
|
32
|
+
"""NNG Push over TCP."""
|
|
33
|
+
|
|
34
|
+
NNG_PULL_IPC = auto()
|
|
35
|
+
"""NNG Pull over IPC."""
|
|
36
|
+
|
|
37
|
+
NNG_PULL_TCP = auto()
|
|
38
|
+
"""NNG Pull over TCP."""
|
|
39
|
+
|
|
40
|
+
NNG_PUB_IPC = auto()
|
|
41
|
+
"""NNG Pub over IPC."""
|
|
42
|
+
|
|
43
|
+
NNG_PUB_TCP = auto()
|
|
44
|
+
"""NNG Pub over TCP."""
|
|
45
|
+
|
|
46
|
+
NNG_SUB_IPC = auto()
|
|
47
|
+
"""NNG Sub over IPC."""
|
|
48
|
+
|
|
49
|
+
NNG_SUB_TCP = auto()
|
|
50
|
+
"""NNG Sub over TCP."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TransportProtocol:
|
|
54
|
+
"""
|
|
55
|
+
Unified transport protocol specification as a value type.
|
|
56
|
+
|
|
57
|
+
Supports: file://, socket://, nng+push+ipc://, nng+push+tcp://, etc.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# Predefined protocols
|
|
61
|
+
File: TransportProtocol
|
|
62
|
+
Socket: TransportProtocol
|
|
63
|
+
NngPushIpc: TransportProtocol
|
|
64
|
+
NngPushTcp: TransportProtocol
|
|
65
|
+
NngPullIpc: TransportProtocol
|
|
66
|
+
NngPullTcp: TransportProtocol
|
|
67
|
+
NngPubIpc: TransportProtocol
|
|
68
|
+
NngPubTcp: TransportProtocol
|
|
69
|
+
NngSubIpc: TransportProtocol
|
|
70
|
+
NngSubTcp: TransportProtocol
|
|
71
|
+
|
|
72
|
+
_SCHEMA_MAP: ClassVar[Dict[str, TransportKind]] = {
|
|
73
|
+
"file": TransportKind.FILE,
|
|
74
|
+
"socket": TransportKind.SOCKET,
|
|
75
|
+
"nng+push+ipc": TransportKind.NNG_PUSH_IPC,
|
|
76
|
+
"nng+push+tcp": TransportKind.NNG_PUSH_TCP,
|
|
77
|
+
"nng+pull+ipc": TransportKind.NNG_PULL_IPC,
|
|
78
|
+
"nng+pull+tcp": TransportKind.NNG_PULL_TCP,
|
|
79
|
+
"nng+pub+ipc": TransportKind.NNG_PUB_IPC,
|
|
80
|
+
"nng+pub+tcp": TransportKind.NNG_PUB_TCP,
|
|
81
|
+
"nng+sub+ipc": TransportKind.NNG_SUB_IPC,
|
|
82
|
+
"nng+sub+tcp": TransportKind.NNG_SUB_TCP,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_KIND_TO_SCHEMA: ClassVar[Dict[TransportKind, str]] = {}
|
|
86
|
+
|
|
87
|
+
def __init__(self, kind: TransportKind, schema: str) -> None:
|
|
88
|
+
self._kind = kind
|
|
89
|
+
self._schema = schema
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def kind(self) -> TransportKind:
|
|
93
|
+
"""The transport kind."""
|
|
94
|
+
return self._kind
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def schema(self) -> str:
|
|
98
|
+
"""The schema string (e.g., 'file', 'socket', 'nng+push+ipc')."""
|
|
99
|
+
return self._schema
|
|
100
|
+
|
|
101
|
+
# Classification properties
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def is_file(self) -> bool:
|
|
105
|
+
"""True if this is a file transport."""
|
|
106
|
+
return self._kind == TransportKind.FILE
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def is_socket(self) -> bool:
|
|
110
|
+
"""True if this is a Unix socket transport."""
|
|
111
|
+
return self._kind == TransportKind.SOCKET
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def is_nng(self) -> bool:
|
|
115
|
+
"""True if this is any NNG-based transport."""
|
|
116
|
+
return self._kind in {
|
|
117
|
+
TransportKind.NNG_PUSH_IPC,
|
|
118
|
+
TransportKind.NNG_PUSH_TCP,
|
|
119
|
+
TransportKind.NNG_PULL_IPC,
|
|
120
|
+
TransportKind.NNG_PULL_TCP,
|
|
121
|
+
TransportKind.NNG_PUB_IPC,
|
|
122
|
+
TransportKind.NNG_PUB_TCP,
|
|
123
|
+
TransportKind.NNG_SUB_IPC,
|
|
124
|
+
TransportKind.NNG_SUB_TCP,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def is_push(self) -> bool:
|
|
129
|
+
"""True if this is a Push pattern."""
|
|
130
|
+
return self._kind in {TransportKind.NNG_PUSH_IPC, TransportKind.NNG_PUSH_TCP}
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def is_pull(self) -> bool:
|
|
134
|
+
"""True if this is a Pull pattern."""
|
|
135
|
+
return self._kind in {TransportKind.NNG_PULL_IPC, TransportKind.NNG_PULL_TCP}
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def is_pub(self) -> bool:
|
|
139
|
+
"""True if this is a Pub pattern."""
|
|
140
|
+
return self._kind in {TransportKind.NNG_PUB_IPC, TransportKind.NNG_PUB_TCP}
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def is_sub(self) -> bool:
|
|
144
|
+
"""True if this is a Sub pattern."""
|
|
145
|
+
return self._kind in {TransportKind.NNG_SUB_IPC, TransportKind.NNG_SUB_TCP}
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def is_ipc(self) -> bool:
|
|
149
|
+
"""True if this uses IPC layer."""
|
|
150
|
+
return self._kind in {
|
|
151
|
+
TransportKind.NNG_PUSH_IPC,
|
|
152
|
+
TransportKind.NNG_PULL_IPC,
|
|
153
|
+
TransportKind.NNG_PUB_IPC,
|
|
154
|
+
TransportKind.NNG_SUB_IPC,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def is_tcp(self) -> bool:
|
|
159
|
+
"""True if this uses TCP layer."""
|
|
160
|
+
return self._kind in {
|
|
161
|
+
TransportKind.NNG_PUSH_TCP,
|
|
162
|
+
TransportKind.NNG_PULL_TCP,
|
|
163
|
+
TransportKind.NNG_PUB_TCP,
|
|
164
|
+
TransportKind.NNG_SUB_TCP,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def create_nng_address(self, path_or_host: str) -> str:
|
|
168
|
+
"""
|
|
169
|
+
Create the NNG address from a path/host.
|
|
170
|
+
|
|
171
|
+
For IPC: ipc:///path
|
|
172
|
+
For TCP: tcp://host:port
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
ValueError: If this is not an NNG protocol.
|
|
176
|
+
"""
|
|
177
|
+
if not self.is_nng:
|
|
178
|
+
raise ValueError(f"Cannot create NNG address for {self._kind} transport")
|
|
179
|
+
|
|
180
|
+
if self.is_ipc:
|
|
181
|
+
# IPC paths need leading "/" for absolute paths
|
|
182
|
+
if not path_or_host.startswith("/"):
|
|
183
|
+
return f"ipc:///{path_or_host}"
|
|
184
|
+
return f"ipc://{path_or_host}"
|
|
185
|
+
|
|
186
|
+
# TCP
|
|
187
|
+
return f"tcp://{path_or_host}"
|
|
188
|
+
|
|
189
|
+
def __str__(self) -> str:
|
|
190
|
+
return self._schema
|
|
191
|
+
|
|
192
|
+
def __repr__(self) -> str:
|
|
193
|
+
return f"TransportProtocol({self._kind.name}, '{self._schema}')"
|
|
194
|
+
|
|
195
|
+
def __eq__(self, other: object) -> bool:
|
|
196
|
+
if isinstance(other, TransportProtocol):
|
|
197
|
+
return self._kind == other._kind
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
def __hash__(self) -> int:
|
|
201
|
+
return hash(self._kind)
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def parse(cls, s: str) -> TransportProtocol:
|
|
205
|
+
"""Parse a protocol string (e.g., 'nng+push+ipc')."""
|
|
206
|
+
result = cls.try_parse(s)
|
|
207
|
+
if result is None:
|
|
208
|
+
raise ValueError(f"Invalid transport protocol: {s}")
|
|
209
|
+
return result
|
|
210
|
+
|
|
211
|
+
@classmethod
|
|
212
|
+
def try_parse(cls, s: Optional[str]) -> Optional[TransportProtocol]:
|
|
213
|
+
"""Try to parse a protocol string."""
|
|
214
|
+
if not s:
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
schema = s.lower().strip()
|
|
218
|
+
kind = cls._SCHEMA_MAP.get(schema)
|
|
219
|
+
if kind is None:
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
return cls(kind, schema)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# Initialize predefined protocols
|
|
226
|
+
TransportProtocol.File = TransportProtocol(TransportKind.FILE, "file")
|
|
227
|
+
TransportProtocol.Socket = TransportProtocol(TransportKind.SOCKET, "socket")
|
|
228
|
+
TransportProtocol.NngPushIpc = TransportProtocol(TransportKind.NNG_PUSH_IPC, "nng+push+ipc")
|
|
229
|
+
TransportProtocol.NngPushTcp = TransportProtocol(TransportKind.NNG_PUSH_TCP, "nng+push+tcp")
|
|
230
|
+
TransportProtocol.NngPullIpc = TransportProtocol(TransportKind.NNG_PULL_IPC, "nng+pull+ipc")
|
|
231
|
+
TransportProtocol.NngPullTcp = TransportProtocol(TransportKind.NNG_PULL_TCP, "nng+pull+tcp")
|
|
232
|
+
TransportProtocol.NngPubIpc = TransportProtocol(TransportKind.NNG_PUB_IPC, "nng+pub+ipc")
|
|
233
|
+
TransportProtocol.NngPubTcp = TransportProtocol(TransportKind.NNG_PUB_TCP, "nng+pub+tcp")
|
|
234
|
+
TransportProtocol.NngSubIpc = TransportProtocol(TransportKind.NNG_SUB_IPC, "nng+sub+ipc")
|
|
235
|
+
TransportProtocol.NngSubTcp = TransportProtocol(TransportKind.NNG_SUB_TCP, "nng+sub+tcp")
|
|
236
|
+
|
|
237
|
+
# Initialize reverse lookup map
|
|
238
|
+
TransportProtocol._KIND_TO_SCHEMA = {v: k for k, v in TransportProtocol._SCHEMA_MAP.items()}
|