FlowerPower 0.9.12.4__py3-none-any.whl → 1.0.0b1__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.
Files changed (81) hide show
  1. flowerpower/__init__.py +17 -2
  2. flowerpower/cfg/__init__.py +201 -149
  3. flowerpower/cfg/base.py +122 -24
  4. flowerpower/cfg/pipeline/__init__.py +254 -0
  5. flowerpower/cfg/pipeline/adapter.py +66 -0
  6. flowerpower/cfg/pipeline/run.py +40 -11
  7. flowerpower/cfg/pipeline/schedule.py +69 -79
  8. flowerpower/cfg/project/__init__.py +149 -0
  9. flowerpower/cfg/project/adapter.py +57 -0
  10. flowerpower/cfg/project/job_queue.py +165 -0
  11. flowerpower/cli/__init__.py +92 -35
  12. flowerpower/cli/job_queue.py +878 -0
  13. flowerpower/cli/mqtt.py +49 -4
  14. flowerpower/cli/pipeline.py +576 -381
  15. flowerpower/cli/utils.py +55 -0
  16. flowerpower/flowerpower.py +12 -7
  17. flowerpower/fs/__init__.py +20 -2
  18. flowerpower/fs/base.py +350 -26
  19. flowerpower/fs/ext.py +797 -216
  20. flowerpower/fs/storage_options.py +1097 -55
  21. flowerpower/io/base.py +13 -18
  22. flowerpower/io/loader/__init__.py +28 -0
  23. flowerpower/io/loader/deltatable.py +7 -10
  24. flowerpower/io/metadata.py +1 -0
  25. flowerpower/io/saver/__init__.py +28 -0
  26. flowerpower/io/saver/deltatable.py +4 -3
  27. flowerpower/job_queue/__init__.py +252 -0
  28. flowerpower/job_queue/apscheduler/__init__.py +11 -0
  29. flowerpower/job_queue/apscheduler/_setup/datastore.py +110 -0
  30. flowerpower/job_queue/apscheduler/_setup/eventbroker.py +93 -0
  31. flowerpower/job_queue/apscheduler/manager.py +1063 -0
  32. flowerpower/job_queue/apscheduler/setup.py +524 -0
  33. flowerpower/job_queue/apscheduler/trigger.py +169 -0
  34. flowerpower/job_queue/apscheduler/utils.py +309 -0
  35. flowerpower/job_queue/base.py +382 -0
  36. flowerpower/job_queue/rq/__init__.py +10 -0
  37. flowerpower/job_queue/rq/_trigger.py +37 -0
  38. flowerpower/job_queue/rq/concurrent_workers/gevent_worker.py +226 -0
  39. flowerpower/job_queue/rq/concurrent_workers/thread_worker.py +231 -0
  40. flowerpower/job_queue/rq/manager.py +1449 -0
  41. flowerpower/job_queue/rq/setup.py +150 -0
  42. flowerpower/job_queue/rq/utils.py +69 -0
  43. flowerpower/pipeline/__init__.py +5 -0
  44. flowerpower/pipeline/base.py +118 -0
  45. flowerpower/pipeline/io.py +407 -0
  46. flowerpower/pipeline/job_queue.py +505 -0
  47. flowerpower/pipeline/manager.py +1586 -0
  48. flowerpower/pipeline/registry.py +560 -0
  49. flowerpower/pipeline/runner.py +560 -0
  50. flowerpower/pipeline/visualizer.py +142 -0
  51. flowerpower/plugins/mqtt/__init__.py +12 -0
  52. flowerpower/plugins/mqtt/cfg.py +16 -0
  53. flowerpower/plugins/mqtt/manager.py +789 -0
  54. flowerpower/settings.py +110 -0
  55. flowerpower/utils/logging.py +21 -0
  56. flowerpower/utils/misc.py +57 -9
  57. flowerpower/utils/sql.py +122 -24
  58. flowerpower/utils/templates.py +18 -142
  59. flowerpower/web/app.py +0 -0
  60. flowerpower-1.0.0b1.dist-info/METADATA +324 -0
  61. flowerpower-1.0.0b1.dist-info/RECORD +94 -0
  62. {flowerpower-0.9.12.4.dist-info → flowerpower-1.0.0b1.dist-info}/WHEEL +1 -1
  63. flowerpower/cfg/pipeline/tracker.py +0 -14
  64. flowerpower/cfg/project/open_telemetry.py +0 -8
  65. flowerpower/cfg/project/tracker.py +0 -11
  66. flowerpower/cfg/project/worker.py +0 -19
  67. flowerpower/cli/scheduler.py +0 -309
  68. flowerpower/event_handler.py +0 -23
  69. flowerpower/mqtt.py +0 -525
  70. flowerpower/pipeline.py +0 -2419
  71. flowerpower/scheduler.py +0 -680
  72. flowerpower/tui.py +0 -79
  73. flowerpower/utils/datastore.py +0 -186
  74. flowerpower/utils/eventbroker.py +0 -127
  75. flowerpower/utils/executor.py +0 -58
  76. flowerpower/utils/trigger.py +0 -140
  77. flowerpower-0.9.12.4.dist-info/METADATA +0 -575
  78. flowerpower-0.9.12.4.dist-info/RECORD +0 -70
  79. /flowerpower/{cfg/pipeline/params.py → cli/worker.py} +0 -0
  80. {flowerpower-0.9.12.4.dist-info → flowerpower-1.0.0b1.dist-info}/entry_points.txt +0 -0
  81. {flowerpower-0.9.12.4.dist-info → flowerpower-1.0.0b1.dist-info}/top_level.txt +0 -0
flowerpower/mqtt.py DELETED
@@ -1,525 +0,0 @@
1
- import datetime as dt
2
- import random
3
- import time
4
- from pathlib import Path
5
- from types import TracebackType
6
- from typing import Callable
7
-
8
- from fsspec import AbstractFileSystem
9
- from loguru import logger
10
- from munch import Munch
11
- from paho.mqtt.client import CallbackAPIVersion, Client
12
-
13
- from .cfg import Config
14
- from .pipeline import Pipeline
15
-
16
-
17
- class MQTTManager:
18
- def __init__(
19
- self,
20
- username: str | None = None,
21
- password: str | None = None,
22
- host: str | None = "localhost",
23
- port: int | None = 1883,
24
- topic: str | None = None,
25
- first_reconnect_delay: int = 1,
26
- max_reconnect_count: int = 5,
27
- reconnect_rate: int = 2,
28
- max_reconnect_delay: int = 60,
29
- transport: str = "tcp",
30
- **kwargs,
31
- ):
32
- if "user" in kwargs:
33
- username = kwargs["user"]
34
- if "pw" in kwargs:
35
- password = kwargs["pw"]
36
-
37
- self.topic = topic
38
-
39
- self._username = username
40
- self._password = password
41
- self._host = host
42
- self._port = port
43
- self._first_reconnect_delay = first_reconnect_delay
44
- self._max_reconnect_count = max_reconnect_count
45
- self._reconnect_rate = reconnect_rate
46
- self._max_reconnect_delay = max_reconnect_delay
47
- self._transport = transport
48
-
49
- self._client = None
50
-
51
- def __enter__(self) -> "MQTTManager":
52
- self.connect()
53
- return self
54
-
55
- def __exit__(
56
- self,
57
- exc_type: type[BaseException] | None,
58
- exc_val: BaseException | None,
59
- exc_tb: TracebackType | None,
60
- ) -> None:
61
- # Add any cleanup code here if needed
62
- self.disconnect()
63
-
64
- @staticmethod
65
- def _on_connect(client, userdata, flags, rc, properties):
66
- if rc == 0:
67
- logger.info(f"Connected to MQTT Broker {userdata.host}!")
68
- else:
69
- logger.error(f"Failed to connect, return code {rc}")
70
-
71
- @staticmethod
72
- def _on_disconnect(client, userdata, disconnect_flags, rc, properties=None):
73
- logger.info(f"Disconnected with result code: {rc}")
74
- reconnect_count, reconnect_delay = 0, userdata.first_reconnect_delay
75
-
76
- if userdata.max_reconnect_count == 0:
77
- logger.info("Disconnected successfully!")
78
- return
79
-
80
- while reconnect_count < userdata.max_reconnect_count:
81
- logger.info(f"Reconnecting in {reconnect_delay} seconds...")
82
- time.sleep(reconnect_delay)
83
-
84
- try:
85
- client.reconnect()
86
- logger.info("Reconnected successfully!")
87
- return
88
- except Exception as err:
89
- logger.error(f"{err}. Reconnect failed. Retrying...")
90
-
91
- reconnect_delay *= userdata.reconnect_rate
92
- reconnect_delay = min(reconnect_delay, userdata.max_reconnect_delay)
93
- reconnect_count += 1
94
- logger.info(f"Reconnect failed after {reconnect_count} attempts. Exiting...")
95
-
96
- @staticmethod
97
- def _on_publish(client, userdata, mid, rc, properties):
98
- logger.info(f"Published message id: {mid}")
99
-
100
- @staticmethod
101
- def _on_subscribe(client, userdata, mid, qos, properties):
102
- if isinstance(qos, list):
103
- qos_msg = str(qos[0])
104
- else:
105
- qos_msg = f"and granted QoS {qos[0]}"
106
- logger.info(f"Subscribed {qos_msg}")
107
-
108
- def connect(self) -> Client:
109
- client = Client(
110
- CallbackAPIVersion.VERSION2,
111
- client_id=f"flowerpower-{random.randint(0, 10000)}",
112
- transport=self._transport,
113
- userdata=Munch(
114
- user=self._username,
115
- pw=self._password,
116
- host=self._host,
117
- port=self._port,
118
- topic=self.topic,
119
- first_reconnect_delay=self._first_reconnect_delay,
120
- max_reconnect_count=self._max_reconnect_count,
121
- reconnect_rate=self._reconnect_rate,
122
- max_reconnect_delay=self._max_reconnect_delay,
123
- transport=self._transport,
124
- ),
125
- )
126
- if self._password != "" and self._username != "":
127
- client.username_pw_set(self._username, self._password)
128
-
129
- client.on_connect = self._on_connect # self._on_connect
130
- client.on_disconnect = self._on_disconnect # self._on_disconnect
131
- client.on_publish = self._on_publish
132
- client.on_subscribe = self._on_subscribe
133
-
134
- client.connect(self._host, self._port)
135
-
136
- # topic = topic or topic
137
- if self.topic:
138
- self.subscribe()
139
-
140
- self._client = client
141
-
142
- def disconnect(self):
143
- self._max_reconnect_count = 0
144
- self._client._userdata.max_reconnect_count = 0
145
- self._client.disconnect()
146
-
147
- def reconnect(self):
148
- self._client.reconnect()
149
-
150
- def publish(self, topic, payload):
151
- if self._client is None:
152
- self.connect()
153
- # elif self._client.is_connected() is False:
154
- # self.reconnect()
155
- self._client.publish(topic, payload)
156
-
157
- def subscribe(self, topic: str | None = None):
158
- if topic is not None:
159
- self.topic = topic
160
- self._client.subscribe(self.topic)
161
-
162
- def unsubscribe(self, topic: str | None = None):
163
- if topic is not None:
164
- self.topic = topic
165
- self._client.unsubscribe(self.topic)
166
-
167
- def register_on_message(self, on_message: Callable):
168
- self._client.on_message = on_message
169
-
170
- def run_in_background(
171
- self,
172
- on_message: Callable,
173
- topic: str | None = None,
174
- ) -> None:
175
- """
176
- Run the MQTT client in the background.
177
-
178
- Args:
179
- on_message: Callback function to run when a message is received
180
- topic: MQTT topic to listen to
181
-
182
- Returns:
183
- None
184
- """
185
- if self._client is None or not self._client.is_connected():
186
- self.connect()
187
-
188
- if topic:
189
- self.subscribe(topic)
190
-
191
- self._client.on_message = on_message
192
- self._client.loop_start()
193
-
194
- def run_until_break(
195
- self,
196
- on_message: Callable,
197
- topic: str | None = None,
198
- ):
199
- """
200
- Run the MQTT client until a break signal is received.
201
-
202
- Args:
203
- on_message: Callback function to run when a message is received
204
- topic: MQTT topic to listen to
205
-
206
- Returns:
207
- None
208
- """
209
- if self._client is None or not self._client.is_connected():
210
- self.connect()
211
-
212
- if topic:
213
- self.subscribe(topic)
214
-
215
- self._client.on_message = on_message
216
- self._client.loop_forever()
217
-
218
- def start_listener(
219
- self, on_message: Callable, topic: str | None = None, background: bool = False
220
- ) -> None:
221
- """
222
- Start the MQTT listener.
223
-
224
- Args:
225
- on_message: Callback function to run when a message is received
226
- topic: MQTT topic to listen to
227
- background: Run the listener in the background
228
-
229
- Returns:
230
- None
231
- """
232
- if background:
233
- self.run_in_background(on_message, topic)
234
- else:
235
- self.run_until_break(on_message, topic)
236
-
237
- def stop_listener(
238
- self,
239
- ) -> None:
240
- """
241
- Stop the MQTT listener.
242
-
243
- Returns:
244
- None
245
- """
246
- self._client.loop_stop()
247
- logger.info("Client stopped.")
248
-
249
- @classmethod
250
- def from_event_broker(cls, base_dir: str | None = None):
251
- base_dir = base_dir or str(Path.cwd())
252
-
253
- event_broker_cfg = Config.load(base_dir=base_dir).project.worker.event_broker
254
- if event_broker_cfg is not None:
255
- if event_broker_cfg.get("type", None) == "mqtt":
256
- return cls(
257
- user=event_broker_cfg.get("username", None),
258
- pw=event_broker_cfg.get("password", None),
259
- host=event_broker_cfg.get("host", "localhost"),
260
- port=event_broker_cfg.get("port", 1883),
261
- transport=event_broker_cfg.get("transport", "tcp"),
262
- )
263
- raise ValueError("No event broker configuration found in config file.")
264
- else:
265
- raise ValueError("No event broker configuration found in config file.")
266
-
267
- @classmethod
268
- def from_config(cls, cfg: dict):
269
- return cls(
270
- user=cfg.get("user", None),
271
- pw=cfg.get("pw", None),
272
- host=cfg.get("host", "localhost"),
273
- port=cfg.get("port", 1883),
274
- transport=cfg.get("transport", "tcp"),
275
- )
276
-
277
- @classmethod
278
- def from_dict(cls, cfg: dict):
279
- return cls(
280
- user=cfg.get("user", None),
281
- pw=cfg.get("pw", None),
282
- host=cfg.get("host", "localhost"),
283
- port=cfg.get("port", 1883),
284
- transport=cfg.get("transport", "tcp"),
285
- )
286
-
287
- def run_pipeline_on_message(
288
- self,
289
- name: str,
290
- topic: str | None = None,
291
- inputs: dict | None = None,
292
- final_vars: list | None = None,
293
- config: dict | None = None,
294
- executor: str | None = None,
295
- with_tracker: bool | None = None,
296
- with_opentelemetry: bool | None = None,
297
- with_progressbar: bool | None = None,
298
- reload: bool = False,
299
- result_expiration_time: float | dt.timedelta = 0,
300
- as_job: bool = False,
301
- base_dir: str | None = None,
302
- storage_options: dict = {},
303
- fs: AbstractFileSystem | None = None,
304
- background: bool = False,
305
- **kwargs,
306
- ):
307
- """
308
- Start a pipeline listener that listens to a topic and processes the message using a pipeline.
309
-
310
- Args:
311
- name: Name of the pipeline
312
- topic: MQTT topic to listen to
313
- inputs: Inputs for the pipeline
314
- final_vars: Final variables for the pipeline
315
- config: Configuration for the pipeline driver
316
- executor: Executor to use for the pipeline
317
- with_tracker: Use tracker for the pipeline
318
- with_opentelemetry: Use OpenTelemetry for the pipeline
319
- with_progressbar: Use progress for the pipeline
320
- reload: Reload the pipeline
321
- result_expiration_time: Result expiration time for the pipeline
322
- as_job: Run the pipeline as a job
323
- base_dir: Base directory for the pipeline
324
- storage_options: Storage options for the pipeline
325
- fs: File system for the pipeline
326
- background: Run the listener in the background
327
- **kwargs: Additional keyword arguments
328
-
329
- Returns:
330
- MQTTClient: MQTT client
331
- """
332
- if inputs is None:
333
- inputs = {}
334
-
335
- def on_message(client, userdata, msg):
336
- logger.info(f"Received message on topic {topic}")
337
-
338
- inputs["payload"] = msg.payload
339
- inputs["topic"] = msg.topic
340
-
341
- with Pipeline(
342
- name=name, storage_options=storage_options, fs=fs, base_dir=base_dir
343
- ) as pipeline:
344
- try:
345
- if as_job:
346
- pipeline.add_job(
347
- inputs=inputs,
348
- final_vars=final_vars,
349
- executor=executor,
350
- config=config,
351
- with_tracker=with_tracker,
352
- with_opentelemetry=with_opentelemetry,
353
- with_progressbar=with_progressbar,
354
- reload=reload,
355
- result_expiration_time=result_expiration_time,
356
- **kwargs,
357
- )
358
- else:
359
- pipeline.run(
360
- inputs=inputs,
361
- final_vars=final_vars,
362
- executor=executor,
363
- config=config,
364
- with_tracker=with_tracker,
365
- with_opentelemetry=with_opentelemetry,
366
- with_progressbar=with_progressbar,
367
- reload=reload,
368
- result_expiration_time=result_expiration_time,
369
- **kwargs,
370
- )
371
- logger.success("Message processed successfully")
372
- return
373
- except Exception as e:
374
- _ = e
375
- logger.exception(e)
376
-
377
- logger.warning("Message processing failed")
378
-
379
- self.start_listener(on_message=on_message, topic=topic, background=background)
380
-
381
-
382
- def start_listener(
383
- on_message: Callable,
384
- topic: str | None = None,
385
- background: bool = False,
386
- mqtt_cfg: dict = {},
387
- base_dir: str | None = None,
388
- username: str | None = None,
389
- password: str | None = None,
390
- host: str | None = None,
391
- port: int | None = None,
392
- ) -> None:
393
- """
394
- Start the MQTT listener.
395
-
396
- The connection to the MQTT broker is established using the provided configuration of a
397
- MQTT event broker defined in the project configuration file `conf/project.toml`.
398
- If no configuration is found, you have to provide either the argument `mqtt_cfg`, dict with the
399
- connection parameters or the arguments `username`, `password`, `host`, and `port`.
400
-
401
- Args:
402
- on_message: Callback function to run when a message is received
403
- topic: MQTT topic to listen to
404
- background: Run the listener in the background
405
- mqtt_cfg: MQTT client configuration. Use either this or arguments
406
- username, password, host, and port.
407
- base_dir: Base directory for the MQTT client
408
- username: Username for the MQTT client
409
- password: Password for the MQTT client
410
- host: Host for the MQTT client
411
- port: Port for the MQTT client
412
-
413
- Returns:
414
- None
415
- """
416
- try:
417
- client = MQTTManager.from_event_broker(base_dir)
418
- except ValueError:
419
- if mqtt_cfg:
420
- client = MQTTManager.from_dict(mqtt_cfg)
421
- elif host and port:
422
- client = MQTTManager(
423
- username=username,
424
- password=password,
425
- host=host,
426
- port=port,
427
- )
428
- else:
429
- raise ValueError(
430
- "No client configuration found. Please provide a client configuration "
431
- "or a FlowerPower project base directory, in which a event broker is "
432
- "configured in the `config/project.yml` file."
433
- )
434
-
435
- client.start_listener(on_message=on_message, topic=topic, background=background)
436
-
437
-
438
- def run_pipeline_on_message(
439
- name: str,
440
- topic: str | None = None,
441
- inputs: dict | None = None,
442
- final_vars: list | None = None,
443
- config: dict | None = None,
444
- executor: str | None = None,
445
- with_tracker: bool | None = None,
446
- with_opentelemetry: bool | None = None,
447
- with_progressbar: bool | None = None,
448
- reload: bool = False,
449
- result_expiration_time: float | dt.timedelta = 0,
450
- as_job: bool = False,
451
- base_dir: str | None = None,
452
- storage_options: dict = {},
453
- fs: AbstractFileSystem | None = None,
454
- background: bool = False,
455
- mqtt_cfg: dict = {},
456
- host: str | None = None,
457
- port: int | None = None,
458
- username: str | None = None,
459
- password: str | None = None,
460
- **kwargs,
461
- ):
462
- """
463
- Start a pipeline listener that listens to a topic and processes the message using a pipeline.
464
-
465
- Args:
466
- name: Name of the pipeline
467
- topic: MQTT topic to listen to
468
- inputs: Inputs for the pipeline
469
- final_vars: Final variables for the pipeline
470
- config: Configuration for the pipeline driver
471
- executor: Executor to use for the pipeline
472
- with_tracker: Use tracker for the pipeline
473
- with_opentelemetry: Use OpenTelemetry for the pipeline
474
- reload: Reload the pipeline
475
- result_expiration_time: Result expiration time for the pipeline
476
- as_job: Run the pipeline as a job
477
- base_dir: Base directory for the pipeline
478
- storage_options: Storage options for the pipeline
479
- fs: File system for the pipeline
480
- background: Run the listener in the background
481
- mqtt_cfg: MQTT client configuration. Use either this or arguments
482
- username, password, host, and port.
483
- host: Host for the MQTT client
484
- port: Port for the MQTT client
485
- username: Username for the MQTT client
486
- password: Password for the MQTT
487
- **kwargs: Additional keyword arguments
488
- """
489
- try:
490
- client = MQTTManager.from_event_broker(base_dir)
491
- except ValueError:
492
- if mqtt_cfg:
493
- client = MQTTManager.from_dict(mqtt_cfg)
494
- elif host and port:
495
- client = MQTTManager(
496
- user=username,
497
- pw=password,
498
- host=host,
499
- port=port,
500
- )
501
- else:
502
- raise ValueError(
503
- "No client configuration found. Please provide a client configuration "
504
- "or a FlowerPower project base directory, in which a event broker is "
505
- "configured in the `config/project.yml` file."
506
- )
507
- client.run_pipeline_on_message(
508
- name=name,
509
- topic=topic,
510
- inputs=inputs,
511
- final_vars=final_vars,
512
- config=config,
513
- executor=executor,
514
- with_tracker=with_tracker,
515
- with_opentelemetry=with_opentelemetry,
516
- with_progressbar=with_progressbar,
517
- reload=reload,
518
- result_expiration_time=result_expiration_time,
519
- as_job=as_job,
520
- base_dir=base_dir,
521
- storage_options=storage_options,
522
- fs=fs,
523
- background=background,
524
- **kwargs,
525
- )