franzmq 0.4.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.
franzmq-0.4.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Alpamayo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
franzmq-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,317 @@
1
+ Metadata-Version: 2.4
2
+ Name: franzmq
3
+ Version: 0.4.0
4
+ Summary: Typed MQTT client and topic/payload model for ISA-95-ish messaging.
5
+ Author-email: Alpamayo <info@alpamayo-solutions.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Alpamayo
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/alpamayo-solutions/franzmq
29
+ Project-URL: Issues, https://github.com/alpamayo-solutions/franzmq/issues
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Programming Language :: Python :: 3.10
32
+ Classifier: Topic :: Internet
33
+ Classifier: Topic :: System :: Networking
34
+ Requires-Python: >=3.10
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: paho-mqtt>=2.0
38
+ Requires-Dist: python-decouple>=3.8
39
+ Provides-Extra: dev
40
+ Requires-Dist: build>=1.2.1; extra == "dev"
41
+ Requires-Dist: twine>=5.0.0; extra == "dev"
42
+ Requires-Dist: pytest>=8.0; extra == "dev"
43
+ Requires-Dist: ruff>=0.5.0; extra == "dev"
44
+ Requires-Dist: black>=24.0; extra == "dev"
45
+ Requires-Dist: mypy>=1.10; extra == "dev"
46
+ Dynamic: license-file
47
+
48
+ # FranzMQ
49
+
50
+ FranzMQ is a structured MQTT communication library for edge and cloud applications. It builds on `paho-mqtt` and introduces typed payloads, hierarchical topics, priority-based callbacks, and a command/acknowledge pattern -- all with optional ISA-95 topic modeling and TLS auto-configuration.
51
+
52
+ ## Features
53
+
54
+ - **Typed payloads** using Python dataclasses with automatic JSON encoding/decoding
55
+ - **Priority-based concurrent callbacks** for message handling
56
+ - **Command/acknowledge pattern** with two-phase handshake for request-response over MQTT
57
+ - **Class-based topic definitions** for type-safe, hierarchical topic construction
58
+ - **ISA-95 topic modeling** for enterprise-ready messaging structures
59
+ - **TLS support** with environment-based auto-configuration
60
+ - **MQTT-based logging** with seamless integration
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ pip install franzmq
66
+ ```
67
+
68
+ ## Quick Start
69
+
70
+ ```python
71
+ from franzmq import Client, Topic, Metric
72
+
73
+ client = Client.autocreate_and_connect(client_id="my-client")
74
+
75
+ topic = Topic(payload_type=Metric, context=("sensor", "temperature"))
76
+ metric = Metric(value=22.5)
77
+
78
+ client.publish(topic, metric)
79
+ ```
80
+
81
+ ## Topics
82
+
83
+ FranzMQ topics follow the structure `{prefix}/{version}/{_PayloadType}/{context...}`.
84
+
85
+ ### Basic Topic
86
+
87
+ ```python
88
+ from franzmq import Topic, Metric
89
+
90
+ topic = Topic(payload_type=Metric, context=("sensor", "temperature"))
91
+ # example/v1/_Metric/sensor/temperature
92
+ ```
93
+
94
+ ### ISA-95 Topic
95
+
96
+ For enterprise-level communication with ISA-95 hierarchy levels:
97
+
98
+ ```python
99
+ from franzmq import Topic, Metric, Isa95Topic, Isa95Fields
100
+
101
+ basic_topic = Topic(payload_type=Metric, context=("sensor", "temperature"))
102
+
103
+ isa95_fields = Isa95Fields(
104
+ enterprise="ent1",
105
+ site="s1",
106
+ area="a1",
107
+ production_line="pl1",
108
+ work_cell="wc1",
109
+ origin_id="origin1"
110
+ )
111
+ isa95_topic = Isa95Topic.from_topic(basic_topic, isa95_fields)
112
+ # example/v1-isa95/ent1/s1/a1/pl1/wc1/origin1/_Metric/sensor/temperature
113
+ ```
114
+
115
+ ## Typed Payloads
116
+
117
+ All messages use structured dataclasses that encode/decode automatically to/from JSON. The following payload types are included:
118
+
119
+ | Payload | Purpose |
120
+ |---------|---------|
121
+ | `Metric` | Timestamped measurement values |
122
+ | `Log` | Structured log entries (level, message, module, etc.) |
123
+ | `ServiceDetails` | Service registration with type and metadata |
124
+ | `Cmd` | Command with correlation ID and expiration |
125
+ | `Ack` | Acknowledgement with result code and message |
126
+
127
+ Custom payloads extend the `Payload` base class:
128
+
129
+ ```python
130
+ from dataclasses import dataclass
131
+ from franzmq import Payload
132
+
133
+ @dataclass
134
+ class SensorReading(Payload):
135
+ sensor_id: str
136
+ value: float
137
+ unit: str
138
+ ```
139
+
140
+ ## Callback System
141
+
142
+ Subscribe to topics and register callbacks with optional priority. Callbacks receive a single `message: Message` argument containing the decoded topic and payload.
143
+
144
+ ```python
145
+ from franzmq import Message
146
+
147
+ def on_metric(message: Message):
148
+ print(f"Received: {message.payload.value} on {message.topic}")
149
+
150
+ client.subscribe(topic, qos=1, callback=on_metric, priority=10)
151
+ ```
152
+
153
+ Callbacks are ordered by descending priority (higher numbers run first). Callbacks with the same priority are executed concurrently in separate threads.
154
+
155
+ ## Command/Acknowledge Pattern
156
+
157
+ FranzMQ supports request-response semantics over MQTT using a two-phase acknowledgement flow. This avoids the need for a separate API when you need confirmed command execution.
158
+
159
+ ### Flow
160
+
161
+ ```
162
+ Sender Receiver
163
+ | |
164
+ |-- Cmd (correlation_id) ------>|
165
+ | | (check expiration)
166
+ |<-- Ack (result_code=-1) ------| (handshake)
167
+ | | (execute callback)
168
+ |<-- Ack (result_code=200) -----| (final result)
169
+ | |
170
+ ```
171
+
172
+ The handshake ack (`result_code=-1`) confirms the receiver is alive and processing. If the handshake arrives before the command expires, the sender extends its wait up to `max_command_duration`.
173
+
174
+ ### Result codes
175
+
176
+ | Code | Meaning |
177
+ |------|---------|
178
+ | -1 | Handshake (receiver acknowledged receipt) |
179
+ | 200 | Success |
180
+ | 400 | Bad request |
181
+ | 500 | Internal error or timeout |
182
+ | 598 | Exception in command callback |
183
+
184
+ ### Sending commands
185
+
186
+ `publish_command` subscribes to the ack topic, publishes the command, waits for the two-phase response, and returns the final `Ack`.
187
+
188
+ ```python
189
+ from franzmq import Client, Topic, Cmd, Ack
190
+
191
+ client = Client.autocreate_and_connect(client_id="sender")
192
+
193
+ cmd_topic = Topic(
194
+ prefix="myproject",
195
+ payload_type=Cmd,
196
+ context=("device1", "settings")
197
+ )
198
+
199
+ ack = client.publish_command(
200
+ topic=cmd_topic,
201
+ command={"enabled": True, "interval_ms": 500},
202
+ validity_duration=30.0,
203
+ max_command_duration=60.0,
204
+ )
205
+
206
+ if ack.result_code >= 500:
207
+ raise Exception(f"Command failed: {ack.message}")
208
+ ```
209
+
210
+ ### Receiving commands
211
+
212
+ `subscribe_to_command` handles expiration checks, handshake acks, and final acks automatically. The callback receives a `Message` and returns a result code.
213
+
214
+ ```python
215
+ from franzmq import Client, Topic, Cmd, Message
216
+
217
+ client = Client.autocreate_and_connect(client_id="receiver")
218
+
219
+ cmd_topic = Topic(
220
+ prefix="myproject",
221
+ payload_type=Cmd,
222
+ context=("device1", "settings")
223
+ )
224
+
225
+ def handle_settings(message: Message) -> int:
226
+ settings = message.payload.command
227
+ apply_settings(settings)
228
+ return 200 # success
229
+
230
+ client.subscribe_to_command(
231
+ topic=cmd_topic,
232
+ callback=handle_settings,
233
+ qos=1,
234
+ )
235
+ ```
236
+
237
+ The callback can return:
238
+ - `None` -- treated as 200 (success)
239
+ - An `int` result code
240
+ - A `(int, str)` tuple of (result_code, message)
241
+
242
+ Commands for the same topic are executed sequentially via an internal queue.
243
+
244
+ ### Custom command payloads
245
+
246
+ Extend `Cmd` for typed command payloads:
247
+
248
+ ```python
249
+ from dataclasses import dataclass, field
250
+ from franzmq import Cmd
251
+
252
+ @dataclass
253
+ class DeviceSettingsCmd(Cmd):
254
+ command: dict = field(default_factory=dict)
255
+ ```
256
+
257
+ Then use `DeviceSettingsCmd` as the topic's `payload_type`.
258
+
259
+ ## Class-Based Topic Definitions
260
+
261
+ For projects with many topics, use `TopicBase` and `classproperty` to define hierarchical topic trees:
262
+
263
+ ```python
264
+ from franzmq import TopicBase, classproperty, Metric
265
+ from franzmq.data_contracts.base import ServiceDetails
266
+
267
+ class DeviceTopic(TopicBase):
268
+ prefix = "myproject"
269
+ version = "v1"
270
+ context = ("device1",)
271
+
272
+ @classproperty
273
+ def State(cls):
274
+ return cls._topic(["state"], payload_type=ServiceDetails)
275
+
276
+ @classproperty
277
+ def Temperature(cls):
278
+ return cls._topic(["temperature"], payload_type=Metric)
279
+ ```
280
+
281
+ Access topics as class attributes:
282
+
283
+ ```python
284
+ DeviceTopic.State # myproject/v1/_ServiceDetails/device1/state
285
+ DeviceTopic.Temperature # myproject/v1/_Metric/device1/temperature
286
+ ```
287
+
288
+ Nested hierarchies use `_parent_class_name` and `_prefix` to compose topic paths from parent classes.
289
+
290
+ ## Logging over MQTT
291
+
292
+ Enable MQTT-based logging by calling:
293
+
294
+ ```python
295
+ import logging
296
+
297
+ client.configure_mqtt_logger(level=logging.INFO)
298
+ ```
299
+
300
+ ## Auto Configuration via Environment Variables
301
+
302
+ Uses [`python-decouple`](https://github.com/henriquebastos/python-decouple) for environment configuration.
303
+
304
+ | Variable | Required | Default | Description |
305
+ |----------|----------|---------|-------------|
306
+ | `MQTT_IP` | No | `broker` | Broker hostname |
307
+ | `MQTT_PORT` | No | `8883` (TLS) / `1883` (plain) | Broker port |
308
+ | `MQTT_USERNAME` | No | `franz` | Auth username |
309
+ | `MQTT_PASSWORD` | No | `franz` | Auth password |
310
+ | `USE_MQTTS` | No | `True` | Enable TLS |
311
+ | `CA_CERT_FILE` | If TLS | -- | CA certificate path |
312
+ | `TLS_CERT_FILE` | If TLS | -- | Client certificate path |
313
+ | `TLS_KEY_FILE` | If TLS | -- | Client private key path |
314
+
315
+ ## License
316
+
317
+ MIT License
@@ -0,0 +1,270 @@
1
+ # FranzMQ
2
+
3
+ FranzMQ is a structured MQTT communication library for edge and cloud applications. It builds on `paho-mqtt` and introduces typed payloads, hierarchical topics, priority-based callbacks, and a command/acknowledge pattern -- all with optional ISA-95 topic modeling and TLS auto-configuration.
4
+
5
+ ## Features
6
+
7
+ - **Typed payloads** using Python dataclasses with automatic JSON encoding/decoding
8
+ - **Priority-based concurrent callbacks** for message handling
9
+ - **Command/acknowledge pattern** with two-phase handshake for request-response over MQTT
10
+ - **Class-based topic definitions** for type-safe, hierarchical topic construction
11
+ - **ISA-95 topic modeling** for enterprise-ready messaging structures
12
+ - **TLS support** with environment-based auto-configuration
13
+ - **MQTT-based logging** with seamless integration
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install franzmq
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from franzmq import Client, Topic, Metric
25
+
26
+ client = Client.autocreate_and_connect(client_id="my-client")
27
+
28
+ topic = Topic(payload_type=Metric, context=("sensor", "temperature"))
29
+ metric = Metric(value=22.5)
30
+
31
+ client.publish(topic, metric)
32
+ ```
33
+
34
+ ## Topics
35
+
36
+ FranzMQ topics follow the structure `{prefix}/{version}/{_PayloadType}/{context...}`.
37
+
38
+ ### Basic Topic
39
+
40
+ ```python
41
+ from franzmq import Topic, Metric
42
+
43
+ topic = Topic(payload_type=Metric, context=("sensor", "temperature"))
44
+ # example/v1/_Metric/sensor/temperature
45
+ ```
46
+
47
+ ### ISA-95 Topic
48
+
49
+ For enterprise-level communication with ISA-95 hierarchy levels:
50
+
51
+ ```python
52
+ from franzmq import Topic, Metric, Isa95Topic, Isa95Fields
53
+
54
+ basic_topic = Topic(payload_type=Metric, context=("sensor", "temperature"))
55
+
56
+ isa95_fields = Isa95Fields(
57
+ enterprise="ent1",
58
+ site="s1",
59
+ area="a1",
60
+ production_line="pl1",
61
+ work_cell="wc1",
62
+ origin_id="origin1"
63
+ )
64
+ isa95_topic = Isa95Topic.from_topic(basic_topic, isa95_fields)
65
+ # example/v1-isa95/ent1/s1/a1/pl1/wc1/origin1/_Metric/sensor/temperature
66
+ ```
67
+
68
+ ## Typed Payloads
69
+
70
+ All messages use structured dataclasses that encode/decode automatically to/from JSON. The following payload types are included:
71
+
72
+ | Payload | Purpose |
73
+ |---------|---------|
74
+ | `Metric` | Timestamped measurement values |
75
+ | `Log` | Structured log entries (level, message, module, etc.) |
76
+ | `ServiceDetails` | Service registration with type and metadata |
77
+ | `Cmd` | Command with correlation ID and expiration |
78
+ | `Ack` | Acknowledgement with result code and message |
79
+
80
+ Custom payloads extend the `Payload` base class:
81
+
82
+ ```python
83
+ from dataclasses import dataclass
84
+ from franzmq import Payload
85
+
86
+ @dataclass
87
+ class SensorReading(Payload):
88
+ sensor_id: str
89
+ value: float
90
+ unit: str
91
+ ```
92
+
93
+ ## Callback System
94
+
95
+ Subscribe to topics and register callbacks with optional priority. Callbacks receive a single `message: Message` argument containing the decoded topic and payload.
96
+
97
+ ```python
98
+ from franzmq import Message
99
+
100
+ def on_metric(message: Message):
101
+ print(f"Received: {message.payload.value} on {message.topic}")
102
+
103
+ client.subscribe(topic, qos=1, callback=on_metric, priority=10)
104
+ ```
105
+
106
+ Callbacks are ordered by descending priority (higher numbers run first). Callbacks with the same priority are executed concurrently in separate threads.
107
+
108
+ ## Command/Acknowledge Pattern
109
+
110
+ FranzMQ supports request-response semantics over MQTT using a two-phase acknowledgement flow. This avoids the need for a separate API when you need confirmed command execution.
111
+
112
+ ### Flow
113
+
114
+ ```
115
+ Sender Receiver
116
+ | |
117
+ |-- Cmd (correlation_id) ------>|
118
+ | | (check expiration)
119
+ |<-- Ack (result_code=-1) ------| (handshake)
120
+ | | (execute callback)
121
+ |<-- Ack (result_code=200) -----| (final result)
122
+ | |
123
+ ```
124
+
125
+ The handshake ack (`result_code=-1`) confirms the receiver is alive and processing. If the handshake arrives before the command expires, the sender extends its wait up to `max_command_duration`.
126
+
127
+ ### Result codes
128
+
129
+ | Code | Meaning |
130
+ |------|---------|
131
+ | -1 | Handshake (receiver acknowledged receipt) |
132
+ | 200 | Success |
133
+ | 400 | Bad request |
134
+ | 500 | Internal error or timeout |
135
+ | 598 | Exception in command callback |
136
+
137
+ ### Sending commands
138
+
139
+ `publish_command` subscribes to the ack topic, publishes the command, waits for the two-phase response, and returns the final `Ack`.
140
+
141
+ ```python
142
+ from franzmq import Client, Topic, Cmd, Ack
143
+
144
+ client = Client.autocreate_and_connect(client_id="sender")
145
+
146
+ cmd_topic = Topic(
147
+ prefix="myproject",
148
+ payload_type=Cmd,
149
+ context=("device1", "settings")
150
+ )
151
+
152
+ ack = client.publish_command(
153
+ topic=cmd_topic,
154
+ command={"enabled": True, "interval_ms": 500},
155
+ validity_duration=30.0,
156
+ max_command_duration=60.0,
157
+ )
158
+
159
+ if ack.result_code >= 500:
160
+ raise Exception(f"Command failed: {ack.message}")
161
+ ```
162
+
163
+ ### Receiving commands
164
+
165
+ `subscribe_to_command` handles expiration checks, handshake acks, and final acks automatically. The callback receives a `Message` and returns a result code.
166
+
167
+ ```python
168
+ from franzmq import Client, Topic, Cmd, Message
169
+
170
+ client = Client.autocreate_and_connect(client_id="receiver")
171
+
172
+ cmd_topic = Topic(
173
+ prefix="myproject",
174
+ payload_type=Cmd,
175
+ context=("device1", "settings")
176
+ )
177
+
178
+ def handle_settings(message: Message) -> int:
179
+ settings = message.payload.command
180
+ apply_settings(settings)
181
+ return 200 # success
182
+
183
+ client.subscribe_to_command(
184
+ topic=cmd_topic,
185
+ callback=handle_settings,
186
+ qos=1,
187
+ )
188
+ ```
189
+
190
+ The callback can return:
191
+ - `None` -- treated as 200 (success)
192
+ - An `int` result code
193
+ - A `(int, str)` tuple of (result_code, message)
194
+
195
+ Commands for the same topic are executed sequentially via an internal queue.
196
+
197
+ ### Custom command payloads
198
+
199
+ Extend `Cmd` for typed command payloads:
200
+
201
+ ```python
202
+ from dataclasses import dataclass, field
203
+ from franzmq import Cmd
204
+
205
+ @dataclass
206
+ class DeviceSettingsCmd(Cmd):
207
+ command: dict = field(default_factory=dict)
208
+ ```
209
+
210
+ Then use `DeviceSettingsCmd` as the topic's `payload_type`.
211
+
212
+ ## Class-Based Topic Definitions
213
+
214
+ For projects with many topics, use `TopicBase` and `classproperty` to define hierarchical topic trees:
215
+
216
+ ```python
217
+ from franzmq import TopicBase, classproperty, Metric
218
+ from franzmq.data_contracts.base import ServiceDetails
219
+
220
+ class DeviceTopic(TopicBase):
221
+ prefix = "myproject"
222
+ version = "v1"
223
+ context = ("device1",)
224
+
225
+ @classproperty
226
+ def State(cls):
227
+ return cls._topic(["state"], payload_type=ServiceDetails)
228
+
229
+ @classproperty
230
+ def Temperature(cls):
231
+ return cls._topic(["temperature"], payload_type=Metric)
232
+ ```
233
+
234
+ Access topics as class attributes:
235
+
236
+ ```python
237
+ DeviceTopic.State # myproject/v1/_ServiceDetails/device1/state
238
+ DeviceTopic.Temperature # myproject/v1/_Metric/device1/temperature
239
+ ```
240
+
241
+ Nested hierarchies use `_parent_class_name` and `_prefix` to compose topic paths from parent classes.
242
+
243
+ ## Logging over MQTT
244
+
245
+ Enable MQTT-based logging by calling:
246
+
247
+ ```python
248
+ import logging
249
+
250
+ client.configure_mqtt_logger(level=logging.INFO)
251
+ ```
252
+
253
+ ## Auto Configuration via Environment Variables
254
+
255
+ Uses [`python-decouple`](https://github.com/henriquebastos/python-decouple) for environment configuration.
256
+
257
+ | Variable | Required | Default | Description |
258
+ |----------|----------|---------|-------------|
259
+ | `MQTT_IP` | No | `broker` | Broker hostname |
260
+ | `MQTT_PORT` | No | `8883` (TLS) / `1883` (plain) | Broker port |
261
+ | `MQTT_USERNAME` | No | `franz` | Auth username |
262
+ | `MQTT_PASSWORD` | No | `franz` | Auth password |
263
+ | `USE_MQTTS` | No | `True` | Enable TLS |
264
+ | `CA_CERT_FILE` | If TLS | -- | CA certificate path |
265
+ | `TLS_CERT_FILE` | If TLS | -- | Client certificate path |
266
+ | `TLS_KEY_FILE` | If TLS | -- | Client private key path |
267
+
268
+ ## License
269
+
270
+ MIT License
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "franzmq"
7
+ version = "0.4.0"
8
+ description = "Typed MQTT client and topic/payload model for ISA-95-ish messaging."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { file = "LICENSE" }
12
+ authors = [{ name = "Alpamayo", email = "info@alpamayo-solutions.com" }]
13
+ dependencies = [
14
+ "paho-mqtt>=2.0",
15
+ "python-decouple>=3.8",
16
+ ]
17
+
18
+ # These show up on PyPI
19
+ classifiers = [
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Topic :: Internet",
23
+ "Topic :: System :: Networking",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/alpamayo-solutions/franzmq"
28
+ Issues = "https://github.com/alpamayo-solutions/franzmq/issues"
29
+
30
+ [tool.setuptools]
31
+ package-dir = {"" = "src"}
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
35
+ namespaces = true
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "build>=1.2.1",
40
+ "twine>=5.0.0",
41
+ "pytest>=8.0",
42
+ "ruff>=0.5.0",
43
+ "black>=24.0",
44
+ "mypy>=1.10",
45
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,14 @@
1
+ from .client import Client
2
+ from .topic import Topic, Isa95Fields, Isa95Topic
3
+ from .topic_base import TopicBase, classproperty
4
+ from .message import Message
5
+ from .data_contracts.base import Payload, Metric, Log, Cmd, Ack
6
+ from .log_handlers import configure_logging
7
+
8
+ __all__ = [
9
+ "Payload", "Message", "Topic", "Client",
10
+ "Isa95Fields", "Isa95Topic",
11
+ "Metric", "Log", "Cmd", "Ack",
12
+ "TopicBase", "classproperty",
13
+ "configure_logging",
14
+ ]