stinger-python-utils 0.1.8rc5__tar.gz → 0.2.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 (25) hide show
  1. stinger_python_utils-0.2.0/.agents/skills/release/SKILL.md +67 -0
  2. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/.github/workflows/python-tests.yml +3 -2
  3. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/.github/workflows/python37.yml +1 -1
  4. stinger_python_utils-0.2.0/.vscode/settings.json +10 -0
  5. stinger_python_utils-0.2.0/PKG-INFO +84 -0
  6. stinger_python_utils-0.2.0/README.md +73 -0
  7. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/pyproject.toml +3 -12
  8. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/src/stinger_python_utils/message_creator.py +168 -22
  9. stinger_python_utils-0.2.0/tests/test_message_creator.py +611 -0
  10. stinger_python_utils-0.2.0/uv.lock +1603 -0
  11. stinger_python_utils-0.1.8rc5/.python-version +0 -4
  12. stinger_python_utils-0.1.8rc5/.vscode/settings.json +0 -4
  13. stinger_python_utils-0.1.8rc5/PKG-INFO +0 -276
  14. stinger_python_utils-0.1.8rc5/README.md +0 -261
  15. stinger_python_utils-0.1.8rc5/src/stinger_python_utils/mcp/__init__.py +0 -20
  16. stinger_python_utils-0.1.8rc5/src/stinger_python_utils/mcp/__main__.py +0 -58
  17. stinger_python_utils-0.1.8rc5/src/stinger_python_utils/mcp/plugin.py +0 -220
  18. stinger_python_utils-0.1.8rc5/src/stinger_python_utils/mcp/server.py +0 -633
  19. stinger_python_utils-0.1.8rc5/uv.lock +0 -868
  20. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/.gitignore +0 -0
  21. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/LICENSE +0 -0
  22. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/src/stinger_python_utils/__init__.py +0 -0
  23. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/src/stinger_python_utils/return_codes.py +0 -0
  24. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/test/__init__.py +0 -0
  25. {stinger_python_utils-0.1.8rc5 → stinger_python_utils-0.2.0}/test/test_message_creator.py +0 -0
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: release
3
+ description: "Release stinger-python-utils. Use when: releasing a new version, cutting a release, bumping the version, publishing, shipping, preparing a release, running pre-release checks."
4
+ argument-hint: "optional notes or release context"
5
+ ---
6
+
7
+ # Release stinger-python-utils
8
+
9
+ Runs pre-release checks, bumps the version in `pyproject.toml`, then offers to commit and push.
10
+
11
+ ## Procedure
12
+
13
+ ### 1. Run mypy
14
+
15
+ ```
16
+ uv run mypy src/ tests/
17
+ ```
18
+
19
+ If mypy reports errors, fix them before continuing. Do not proceed until mypy is clean.
20
+
21
+ ### 2. Run pytest
22
+
23
+ ```
24
+ uv run pytest
25
+ ```
26
+
27
+ If any tests fail, fix them before continuing. Do not proceed until all tests pass.
28
+
29
+ ### 3. Bump the version
30
+
31
+ Read the current version from `pyproject.toml`. Then ask the user:
32
+
33
+ > The current version is `X.Y.Z`. How would you like to bump it?
34
+ > - **patch** → `X.Y.Z+1`
35
+ > - **minor** → `X.Y+1.0`
36
+ > - **major** → `X+1.0.0`
37
+ > - **manual** → enter a custom version string
38
+
39
+ Apply the chosen version to the `version` field in `pyproject.toml`.
40
+
41
+ ### 4. Confirm commit and push
42
+
43
+ Ask the user:
44
+
45
+ > Ready to commit and push `vX.Y.Z` to `origin/main`?
46
+ > - **Yes** — stage `pyproject.toml`, commit with message `chore: bump version to X.Y.Z`, then push to `origin main`
47
+ > - **No** — stop here; leave the version bump in place for the user to handle manually
48
+
49
+ Only run `git add`, `git commit`, and `git push` if the user explicitly confirms.
50
+
51
+ ### 5. Build
52
+
53
+ ```
54
+ uv build
55
+ ```
56
+
57
+ If the build fails, report the error and stop. Do not proceed to publish.
58
+
59
+ ### 6. Confirm publish
60
+
61
+ Ask the user:
62
+
63
+ > Build succeeded. Publish `vX.Y.Z` to PyPI with `uv publish`?
64
+ > - **Yes** — run `uv publish`
65
+ > - **No** — stop here; the built artifacts are in `dist/`
66
+
67
+ Only run `uv publish` if the user explicitly confirms.
@@ -11,7 +11,8 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
14
+ # Python 3.7 requires a completely different setup because it is very EOL.
15
+ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
15
16
  steps:
16
17
  - uses: actions/checkout@v4
17
18
  - name: Install UV
@@ -20,7 +21,7 @@ jobs:
20
21
  python-version: ${{ matrix.python-version }}
21
22
  - name: Install project
22
23
  run: |
23
- uv sync --locked --all-extras --dev
24
+ uv sync --locked --dev
24
25
  - name: Run unit tests
25
26
  run: |
26
27
  uv run pytest
@@ -7,7 +7,7 @@ on:
7
7
  branches: [ main ]
8
8
 
9
9
  jobs:
10
- test:
10
+ test_py37:
11
11
  runs-on: ubuntu-22.04
12
12
  steps:
13
13
  - uses: actions/checkout@v4
@@ -0,0 +1,10 @@
1
+ {
2
+ "python-envs.defaultEnvManager": "ms-python.python:system",
3
+ "python-envs.pythonProjects": [],
4
+ "chat.tools.terminal.autoApprove": {
5
+ "/^uv run mypy src/ tests/$/": {
6
+ "approve": true,
7
+ "matchCommandLine": true
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.5
2
+ Name: stinger-python-utils
3
+ Version: 0.2.0
4
+ Summary: Common utilities for Stinger Python services.
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.7
8
+ Requires-Dist: pydantic>=2.5.3
9
+ Requires-Dist: pyqttier>=0.2.0
10
+ Description-Content-Type: text/markdown
11
+
12
+ # stinger-python-utils
13
+
14
+ Shared utilities for Stinger Python services, providing convenient message creation for MQTT communication.
15
+
16
+
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ uv add stinger-python-utils
22
+ ```
23
+
24
+ ## MessageCreator
25
+
26
+ `MessageCreator` is a utility class for creating MQTT messages with standardized properties and payloads.
27
+
28
+ ### Basic Usage
29
+
30
+ ```python
31
+ from pydantic import BaseModel
32
+ from stinger_python_utils.message_creator import MessageCreator
33
+
34
+ class MyPayload(BaseModel):
35
+ name: str
36
+ value: int
37
+
38
+ payload = MyPayload(name="test", value=42)
39
+ message = MessageCreator.signal_message("my/topic", payload)
40
+ ```
41
+
42
+ ### Methods
43
+
44
+ | Method | Purpose | Return Code |
45
+ |--------|---------|-------------|
46
+ | `signal_message(topic, payload)` | Send a signal with one-time delivery | QoS 1, no retain |
47
+ | `status_message(topic, payload, expiry_seconds)` | Send status that expires | QoS 1, retained, with expiry |
48
+ | `error_response_message(topic, return_code, correlation_id, debug_info)` | Error response to a request | QoS 1, user properties: `ReturnCode` |
49
+ | `response_message(topic, payload, return_code, correlation_id)` | Successful response to a request | QoS 1, user properties: `ReturnCode` |
50
+ | `property_state_message(topic, payload, state_version)` | Publish property state | QoS 1, retained, JSON content type |
51
+ | `property_update_request_message(topic, payload, version, response_topic, correlation_id)` | Request property update | QoS 1, user property: `PropertyVersion` |
52
+ | `property_response_message(topic, payload, version, return_code, correlation_id, debug_info)` | Respond to property update | QoS 1, user properties: `ReturnCode`, `PropertyVersion` |
53
+ | `request_message(topic, payload, response_topic, correlation_id)` | Send a request (auto-generates UUID if no correlation_id) | QoS 1, auto correlation ID |
54
+
55
+ ### Example: Request/Response Pattern
56
+
57
+ ```python
58
+ from pydantic import BaseModel
59
+ from stinger_python_utils.message_creator import MessageCreator
60
+
61
+ class Request(BaseModel):
62
+ action: str
63
+
64
+ request = Request(action="start")
65
+ msg = MessageCreator.request_message(
66
+ "devices/cmd",
67
+ request,
68
+ response_topic="devices/response"
69
+ )
70
+ # Returns a Message with auto-generated correlation ID
71
+ ```
72
+
73
+ ### Example: Error Response
74
+
75
+ ```python
76
+ msg = MessageCreator.error_response_message(
77
+ "devices/response",
78
+ return_code=500,
79
+ correlation_id="req-123",
80
+ debug_info="Device not found"
81
+ )
82
+ # User properties include: ReturnCode=500, DebugInfo=Device not found
83
+ ```
84
+
@@ -0,0 +1,73 @@
1
+ # stinger-python-utils
2
+
3
+ Shared utilities for Stinger Python services, providing convenient message creation for MQTT communication.
4
+
5
+
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ uv add stinger-python-utils
11
+ ```
12
+
13
+ ## MessageCreator
14
+
15
+ `MessageCreator` is a utility class for creating MQTT messages with standardized properties and payloads.
16
+
17
+ ### Basic Usage
18
+
19
+ ```python
20
+ from pydantic import BaseModel
21
+ from stinger_python_utils.message_creator import MessageCreator
22
+
23
+ class MyPayload(BaseModel):
24
+ name: str
25
+ value: int
26
+
27
+ payload = MyPayload(name="test", value=42)
28
+ message = MessageCreator.signal_message("my/topic", payload)
29
+ ```
30
+
31
+ ### Methods
32
+
33
+ | Method | Purpose | Return Code |
34
+ |--------|---------|-------------|
35
+ | `signal_message(topic, payload)` | Send a signal with one-time delivery | QoS 1, no retain |
36
+ | `status_message(topic, payload, expiry_seconds)` | Send status that expires | QoS 1, retained, with expiry |
37
+ | `error_response_message(topic, return_code, correlation_id, debug_info)` | Error response to a request | QoS 1, user properties: `ReturnCode` |
38
+ | `response_message(topic, payload, return_code, correlation_id)` | Successful response to a request | QoS 1, user properties: `ReturnCode` |
39
+ | `property_state_message(topic, payload, state_version)` | Publish property state | QoS 1, retained, JSON content type |
40
+ | `property_update_request_message(topic, payload, version, response_topic, correlation_id)` | Request property update | QoS 1, user property: `PropertyVersion` |
41
+ | `property_response_message(topic, payload, version, return_code, correlation_id, debug_info)` | Respond to property update | QoS 1, user properties: `ReturnCode`, `PropertyVersion` |
42
+ | `request_message(topic, payload, response_topic, correlation_id)` | Send a request (auto-generates UUID if no correlation_id) | QoS 1, auto correlation ID |
43
+
44
+ ### Example: Request/Response Pattern
45
+
46
+ ```python
47
+ from pydantic import BaseModel
48
+ from stinger_python_utils.message_creator import MessageCreator
49
+
50
+ class Request(BaseModel):
51
+ action: str
52
+
53
+ request = Request(action="start")
54
+ msg = MessageCreator.request_message(
55
+ "devices/cmd",
56
+ request,
57
+ response_topic="devices/response"
58
+ )
59
+ # Returns a Message with auto-generated correlation ID
60
+ ```
61
+
62
+ ### Example: Error Response
63
+
64
+ ```python
65
+ msg = MessageCreator.error_response_message(
66
+ "devices/response",
67
+ return_code=500,
68
+ correlation_id="req-123",
69
+ debug_info="Device not found"
70
+ )
71
+ # User properties include: ReturnCode=500, DebugInfo=Device not found
72
+ ```
73
+
@@ -4,26 +4,17 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "stinger-python-utils"
7
- version = "0.1.8rc5"
7
+ version = "0.2.0"
8
8
  description = "Common utilities for Stinger Python services."
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.7"
12
+
12
13
  dependencies = [
13
14
  "pydantic>=2.5.3",
14
15
  "pyqttier>=0.2.0",
15
16
  ]
16
17
 
17
- [project.optional-dependencies]
18
- mcp = [
19
- "mcp>=1.0.0",
20
- "stevedore>=5.0",
21
- "uvicorn>=0.20.0",
22
- ]
23
-
24
- [project.scripts]
25
- stinger-mcp-server = "stinger_python_utils.mcp.__main__:main"
26
-
27
18
  [tool.uv]
28
19
  managed = true
29
20
 
@@ -31,5 +22,5 @@ managed = true
31
22
  dev = [
32
23
  "black>=23.3.0",
33
24
  "mypy>=1.4.1",
34
- "pytest>=7.0.0",
25
+ "pytest>=7.0.0; python_version >= '3.10'",
35
26
  ]
@@ -7,27 +7,82 @@ from .return_codes import MethodReturnCode
7
7
 
8
8
  class MessageCreator:
9
9
 
10
+ @staticmethod
11
+ def _validate_topic(topic: str, param_name: str = "topic") -> None:
12
+ if "+" in topic:
13
+ raise ValueError(
14
+ f"{param_name} must not contain '+', got: {topic!r}"
15
+ )
16
+
10
17
  @classmethod
11
18
  def signal_message(cls, topic: str, payload: BaseModel) -> Message:
19
+ return cls.binary_signal_message(
20
+ topic,
21
+ payload.model_dump_json(by_alias=True).encode("utf-8"),
22
+ "application/json",
23
+ )
24
+
25
+ @classmethod
26
+ def binary_signal_message(cls, topic: str, payload: bytes, content_type: str) -> Message:
27
+ cls._validate_topic(topic)
12
28
  return Message(
13
29
  topic=topic,
14
- payload=payload.model_dump_json(by_alias=True).encode("utf-8"),
30
+ payload=payload,
15
31
  qos=1,
16
32
  retain=False,
17
- content_type="application/json",
33
+ content_type=content_type,
34
+ )
35
+
36
+ @classmethod
37
+ def command_message(cls, topic: str, payload: BaseModel, qos: int = 2) -> Message:
38
+ return cls.binary_command_message(
39
+ topic,
40
+ payload.model_dump_json(by_alias=True).encode("utf-8"),
41
+ "application/json",
42
+ qos=qos,
43
+ )
44
+
45
+ @classmethod
46
+ def binary_command_message(
47
+ cls, topic: str, payload: bytes, content_type: str, qos: int = 2
48
+ ) -> Message:
49
+ """
50
+ A command is a signal in reverse: the client publishes it to the server and never
51
+ learns whether it was acted on. There is therefore no response topic and no
52
+ correlation data on the message.
53
+ """
54
+ cls._validate_topic(topic)
55
+ return Message(
56
+ topic=topic,
57
+ payload=payload,
58
+ qos=qos,
59
+ retain=False,
60
+ content_type=content_type,
18
61
  )
19
62
 
20
63
  @classmethod
21
64
  def status_message(
22
- cls, topic, status_message: BaseModel, expiry_seconds: int
65
+ cls, topic: str, status_message: BaseModel, expiry_seconds: int
66
+ ) -> Message:
67
+ return cls.binary_status_message(
68
+ topic,
69
+ status_message.model_dump_json(by_alias=True).encode("utf-8"),
70
+ "application/json",
71
+ expiry_seconds=expiry_seconds,
72
+ )
73
+
74
+ @classmethod
75
+ def binary_status_message(
76
+ cls, topic: str, payload: bytes, content_type: str, expiry_seconds: int
23
77
  ) -> Message:
78
+ cls._validate_topic(topic)
24
79
  return Message(
25
80
  topic=topic,
26
- payload=status_message.model_dump_json(by_alias=True).encode("utf-8"),
81
+ payload=payload,
27
82
  qos=1,
28
83
  retain=True,
29
84
  message_expiry_interval=expiry_seconds,
30
- content_type="application/json",
85
+ content_type=content_type,
31
86
  )
32
87
 
33
88
  @classmethod
@@ -37,10 +92,16 @@ class MessageCreator:
37
92
  return_code: Union[int, MethodReturnCode],
38
93
  correlation_id: Union[str, bytes, None] = None,
39
94
  debug_info: Optional[str] = None,
95
+ content_type: str = "application/json",
40
96
  ) -> Message:
41
97
  """
42
98
  This could be used for a response to a request, but where there was an error fulfilling the request.
99
+
100
+ An error carries no data -- the return code and any debug info travel in MQTT user
101
+ properties -- so the body is an empty JSON object for a JSON response and empty
102
+ bytes for any other content type, since a protobuf consumer would reject "{}".
43
103
  """
104
+ cls._validate_topic(topic)
44
105
  rc = (
45
106
  return_code.value
46
107
  if isinstance(return_code, MethodReturnCode)
@@ -48,7 +109,7 @@ class MessageCreator:
48
109
  )
49
110
  msg_obj = Message(
50
111
  topic=topic,
51
- payload=b"{}",
112
+ payload=b"{}" if content_type == "application/json" else b"",
52
113
  qos=1,
53
114
  retain=False,
54
115
  correlation_data=(
@@ -57,7 +118,7 @@ class MessageCreator:
57
118
  else correlation_id
58
119
  ),
59
120
  user_properties={"ReturnCode": str(rc)},
60
- content_type="application/json",
121
+ content_type=content_type,
61
122
  )
62
123
  if (
63
124
  debug_info is not None and msg_obj.user_properties is not None
@@ -82,12 +143,26 @@ class MessageCreator:
82
143
  payload = response_obj.encode("utf-8")
83
144
  else:
84
145
  payload = response_obj
146
+ return cls.binary_response_message(
147
+ response_topic, payload, "application/json", return_code, correlation_id
148
+ )
149
+
150
+ @classmethod
151
+ def binary_response_message(
152
+ cls,
153
+ response_topic: str,
154
+ payload: bytes,
155
+ content_type: str,
156
+ return_code: Union[int, MethodReturnCode],
157
+ correlation_id: Union[str, bytes, None] = None,
158
+ ) -> Message:
159
+ cls._validate_topic(response_topic, "response_topic")
85
160
  rc = (
86
161
  return_code.value
87
162
  if isinstance(return_code, MethodReturnCode)
88
163
  else return_code
89
164
  )
90
- msg_obj = Message(
165
+ return Message(
91
166
  topic=response_topic,
92
167
  payload=payload,
93
168
  qos=1,
@@ -98,9 +173,8 @@ class MessageCreator:
98
173
  else correlation_id
99
174
  ),
100
175
  user_properties={"ReturnCode": str(rc)},
101
- content_type="application/json",
176
+ content_type=content_type,
102
177
  )
103
- return msg_obj
104
178
 
105
179
  @classmethod
106
180
  def property_state_message(
@@ -109,12 +183,24 @@ class MessageCreator:
109
183
  """
110
184
  Creates a retained message representing the state/value of a property.
111
185
  """
186
+ return cls.binary_property_state_message(
187
+ topic,
188
+ state_obj.model_dump_json(by_alias=True).encode("utf-8"),
189
+ "application/json",
190
+ state_version,
191
+ )
192
+
193
+ @classmethod
194
+ def binary_property_state_message(
195
+ cls, topic: str, payload: bytes, content_type: str, state_version: Optional[int] = None
196
+ ) -> Message:
197
+ cls._validate_topic(topic)
112
198
  msg_obj = Message(
113
199
  topic=topic,
114
- payload=state_obj.model_dump_json(by_alias=True).encode("utf-8"),
200
+ payload=payload,
115
201
  qos=1,
116
202
  retain=True,
117
- content_type="application/json",
203
+ content_type=content_type,
118
204
  )
119
205
  if state_version is not None:
120
206
  msg_obj.user_properties = {"PropertyVersion": str(state_version)}
@@ -132,12 +218,33 @@ class MessageCreator:
132
218
  """
133
219
  Creates a message representing a request to update a property.
134
220
  """
135
- msg_obj = Message(
221
+ return cls.binary_property_update_request_message(
222
+ topic,
223
+ property_obj.model_dump_json(by_alias=True).encode("utf-8"),
224
+ "application/json",
225
+ version,
226
+ response_topic,
227
+ correlation_id,
228
+ )
229
+
230
+ @classmethod
231
+ def binary_property_update_request_message(
232
+ cls,
233
+ topic: str,
234
+ payload: bytes,
235
+ content_type: str,
236
+ version: str,
237
+ response_topic: str,
238
+ correlation_id: Union[str, bytes, None] = None,
239
+ ) -> Message:
240
+ cls._validate_topic(topic)
241
+ cls._validate_topic(response_topic, "response_topic")
242
+ return Message(
136
243
  topic=topic,
137
- payload=property_obj.model_dump_json(by_alias=True).encode("utf-8"),
244
+ payload=payload,
138
245
  qos=1,
139
246
  retain=False,
140
- content_type="application/json",
247
+ content_type=content_type,
141
248
  response_topic=response_topic,
142
249
  correlation_data=(
143
250
  correlation_id.encode("utf-8")
@@ -146,7 +253,6 @@ class MessageCreator:
146
253
  ),
147
254
  user_properties={"PropertyVersion": str(version)},
148
255
  )
149
- return msg_obj
150
256
 
151
257
  @classmethod
152
258
  def property_response_message(
@@ -161,6 +267,28 @@ class MessageCreator:
161
267
  """
162
268
  Creates a message representing a response to a property update request.
163
269
  """
270
+ return cls.binary_property_response_message(
271
+ response_topic,
272
+ property_obj.model_dump_json(by_alias=True).encode("utf-8"),
273
+ "application/json",
274
+ version,
275
+ return_code,
276
+ correlation_id,
277
+ debug_info,
278
+ )
279
+
280
+ @classmethod
281
+ def binary_property_response_message(
282
+ cls,
283
+ response_topic: str,
284
+ payload: bytes,
285
+ content_type: str,
286
+ version: str,
287
+ return_code: Union[int, MethodReturnCode],
288
+ correlation_id: Union[str, bytes, None] = None,
289
+ debug_info: Optional[str] = None,
290
+ ) -> Message:
291
+ cls._validate_topic(response_topic, "response_topic")
164
292
  rc = (
165
293
  return_code.value
166
294
  if isinstance(return_code, MethodReturnCode)
@@ -168,10 +296,10 @@ class MessageCreator:
168
296
  )
169
297
  msg_obj = Message(
170
298
  topic=response_topic,
171
- payload=property_obj.model_dump_json(by_alias=True).encode("utf-8"),
299
+ payload=payload,
172
300
  qos=1,
173
301
  retain=False,
174
- content_type="application/json",
302
+ content_type=content_type,
175
303
  correlation_data=(
176
304
  correlation_id.encode("utf-8")
177
305
  if isinstance(correlation_id, str)
@@ -196,19 +324,37 @@ class MessageCreator:
196
324
  response_topic: str,
197
325
  correlation_id: Union[str, bytes, None] = None,
198
326
  ) -> Message:
327
+ return cls.binary_request_message(
328
+ topic,
329
+ request_obj.model_dump_json(by_alias=True).encode("utf-8"),
330
+ "application/json",
331
+ response_topic,
332
+ correlation_id,
333
+ )
334
+
335
+ @classmethod
336
+ def binary_request_message(
337
+ cls,
338
+ topic: str,
339
+ payload: bytes,
340
+ content_type: str,
341
+ response_topic: str,
342
+ correlation_id: Union[str, bytes, None] = None,
343
+ ) -> Message:
344
+ cls._validate_topic(topic)
345
+ cls._validate_topic(response_topic, "response_topic")
199
346
  if correlation_id is None:
200
347
  correlation_id = str(uuid.uuid4())
201
- msg_obj = Message(
348
+ return Message(
202
349
  topic=topic,
203
- payload=request_obj.model_dump_json(by_alias=True).encode("utf-8"),
350
+ payload=payload,
204
351
  qos=1,
205
352
  retain=False,
206
353
  response_topic=response_topic,
207
- content_type="application/json",
354
+ content_type=content_type,
208
355
  correlation_data=(
209
356
  correlation_id.encode("utf-8")
210
357
  if isinstance(correlation_id, str)
211
358
  else correlation_id
212
359
  ),
213
360
  )
214
- return msg_obj