meerschaum 2.3.5.dev0__py3-none-any.whl → 2.4.0__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 (99) hide show
  1. meerschaum/_internal/arguments/__init__.py +2 -1
  2. meerschaum/_internal/arguments/_parse_arguments.py +88 -12
  3. meerschaum/_internal/docs/index.py +3 -2
  4. meerschaum/_internal/entry.py +42 -20
  5. meerschaum/_internal/shell/Shell.py +38 -44
  6. meerschaum/_internal/term/TermPageHandler.py +2 -3
  7. meerschaum/_internal/term/__init__.py +13 -11
  8. meerschaum/actions/api.py +26 -23
  9. meerschaum/actions/bootstrap.py +38 -11
  10. meerschaum/actions/copy.py +3 -3
  11. meerschaum/actions/delete.py +4 -1
  12. meerschaum/actions/register.py +1 -3
  13. meerschaum/actions/stack.py +24 -19
  14. meerschaum/actions/start.py +41 -41
  15. meerschaum/actions/sync.py +53 -52
  16. meerschaum/api/__init__.py +48 -14
  17. meerschaum/api/_events.py +26 -17
  18. meerschaum/api/_oauth2.py +2 -2
  19. meerschaum/api/_websockets.py +5 -4
  20. meerschaum/api/dash/__init__.py +7 -16
  21. meerschaum/api/dash/callbacks/__init__.py +1 -0
  22. meerschaum/api/dash/callbacks/dashboard.py +52 -58
  23. meerschaum/api/dash/callbacks/jobs.py +15 -16
  24. meerschaum/api/dash/callbacks/login.py +16 -10
  25. meerschaum/api/dash/callbacks/pipes.py +41 -0
  26. meerschaum/api/dash/callbacks/plugins.py +1 -1
  27. meerschaum/api/dash/callbacks/register.py +15 -11
  28. meerschaum/api/dash/components.py +54 -59
  29. meerschaum/api/dash/jobs.py +5 -9
  30. meerschaum/api/dash/pages/__init__.py +1 -0
  31. meerschaum/api/dash/pages/pipes.py +19 -0
  32. meerschaum/api/dash/pipes.py +86 -58
  33. meerschaum/api/dash/plugins.py +6 -4
  34. meerschaum/api/dash/sessions.py +176 -0
  35. meerschaum/api/dash/users.py +3 -41
  36. meerschaum/api/dash/webterm.py +12 -17
  37. meerschaum/api/resources/static/js/terminado.js +1 -1
  38. meerschaum/api/routes/_actions.py +4 -118
  39. meerschaum/api/routes/_jobs.py +45 -24
  40. meerschaum/api/routes/_login.py +4 -4
  41. meerschaum/api/routes/_pipes.py +3 -3
  42. meerschaum/api/routes/_webterm.py +5 -6
  43. meerschaum/config/_default.py +15 -3
  44. meerschaum/config/_version.py +1 -1
  45. meerschaum/config/stack/__init__.py +64 -21
  46. meerschaum/config/static/__init__.py +6 -0
  47. meerschaum/connectors/{Connector.py → _Connector.py} +19 -13
  48. meerschaum/connectors/__init__.py +24 -14
  49. meerschaum/connectors/api/{APIConnector.py → _APIConnector.py} +3 -1
  50. meerschaum/connectors/api/__init__.py +2 -1
  51. meerschaum/connectors/api/_actions.py +22 -36
  52. meerschaum/connectors/api/_jobs.py +1 -0
  53. meerschaum/connectors/parse.py +18 -16
  54. meerschaum/connectors/poll.py +30 -24
  55. meerschaum/connectors/sql/__init__.py +3 -1
  56. meerschaum/connectors/sql/_pipes.py +172 -197
  57. meerschaum/connectors/sql/_plugins.py +45 -43
  58. meerschaum/connectors/sql/_users.py +46 -38
  59. meerschaum/connectors/valkey/_ValkeyConnector.py +535 -0
  60. meerschaum/connectors/valkey/__init__.py +10 -0
  61. meerschaum/connectors/valkey/_fetch.py +75 -0
  62. meerschaum/connectors/valkey/_pipes.py +844 -0
  63. meerschaum/connectors/valkey/_plugins.py +265 -0
  64. meerschaum/connectors/valkey/_users.py +305 -0
  65. meerschaum/core/Pipe/__init__.py +3 -0
  66. meerschaum/core/Pipe/_attributes.py +1 -2
  67. meerschaum/core/Pipe/_clear.py +16 -13
  68. meerschaum/core/Pipe/_copy.py +106 -0
  69. meerschaum/core/Pipe/_data.py +165 -101
  70. meerschaum/core/Pipe/_drop.py +4 -4
  71. meerschaum/core/Pipe/_dtypes.py +14 -14
  72. meerschaum/core/Pipe/_edit.py +15 -14
  73. meerschaum/core/Pipe/_sync.py +134 -53
  74. meerschaum/core/Pipe/_verify.py +11 -11
  75. meerschaum/core/User/_User.py +14 -12
  76. meerschaum/jobs/_Job.py +27 -14
  77. meerschaum/jobs/__init__.py +7 -2
  78. meerschaum/jobs/systemd.py +20 -8
  79. meerschaum/plugins/_Plugin.py +17 -13
  80. meerschaum/utils/_get_pipes.py +14 -20
  81. meerschaum/utils/dataframe.py +291 -101
  82. meerschaum/utils/dtypes/__init__.py +31 -6
  83. meerschaum/utils/dtypes/sql.py +4 -4
  84. meerschaum/utils/formatting/_shell.py +5 -6
  85. meerschaum/utils/misc.py +3 -3
  86. meerschaum/utils/packages/__init__.py +14 -9
  87. meerschaum/utils/packages/_packages.py +2 -0
  88. meerschaum/utils/prompt.py +1 -1
  89. meerschaum/utils/schedule.py +1 -0
  90. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/METADATA +7 -1
  91. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/RECORD +98 -89
  92. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/WHEEL +1 -1
  93. meerschaum/api/dash/actions.py +0 -255
  94. /meerschaum/connectors/sql/{SQLConnector.py → _SQLConnector.py} +0 -0
  95. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/LICENSE +0 -0
  96. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/NOTICE +0 -0
  97. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/entry_points.txt +0 -0
  98. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/top_level.txt +0 -0
  99. {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,535 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Define the `ValkeyConnector`.
6
+ """
7
+
8
+ import shlex
9
+ import json
10
+ from datetime import datetime, timezone
11
+
12
+ import meerschaum as mrsm
13
+ from meerschaum.connectors import Connector, make_connector
14
+ from meerschaum.utils.typing import List, Dict, Any, Optional, Union
15
+ from meerschaum.utils.warnings import dprint
16
+
17
+
18
+ @make_connector
19
+ class ValkeyConnector(Connector):
20
+ """
21
+ Manage a Valkey instance.
22
+
23
+ Build a `ValkeyConnector` from connection attributes or a URI string.
24
+ """
25
+ IS_INSTANCE: bool = True
26
+ REQUIRED_ATTRIBUTES: List[str] = ['host']
27
+ OPTIONAL_ATTRIBUTES: List[str] = [
28
+ 'port', 'username', 'password', 'db', 'socket_timeout',
29
+ ]
30
+ DEFAULT_ATTRIBUTES: Dict[str, Any] = {
31
+ 'username': 'default',
32
+ 'port': 6379,
33
+ 'db': 0,
34
+ 'socket_timeout': 300,
35
+ }
36
+ KEY_SEPARATOR: str = ':'
37
+
38
+ from ._pipes import (
39
+ register_pipe,
40
+ get_pipe_id,
41
+ get_pipe_attributes,
42
+ edit_pipe,
43
+ pipe_exists,
44
+ drop_pipe,
45
+ delete_pipe,
46
+ get_pipe_data,
47
+ sync_pipe,
48
+ get_pipe_columns_types,
49
+ clear_pipe,
50
+ get_sync_time,
51
+ get_pipe_rowcount,
52
+ fetch_pipes_keys,
53
+ )
54
+ from ._fetch import (
55
+ fetch,
56
+ )
57
+
58
+ from ._users import (
59
+ get_users_pipe,
60
+ get_user_key,
61
+ get_user_keys_vals,
62
+ register_user,
63
+ get_user_id,
64
+ edit_user,
65
+ get_user_attributes,
66
+ delete_user,
67
+ get_users,
68
+ get_user_password_hash,
69
+ get_user_type,
70
+ )
71
+ from ._plugins import (
72
+ get_plugins_pipe,
73
+ get_plugin_key,
74
+ get_plugin_keys_vals,
75
+ register_plugin,
76
+ get_plugin_id,
77
+ get_plugin_version,
78
+ get_plugin_user_id,
79
+ get_plugin_username,
80
+ get_plugin_attributes,
81
+ get_plugins,
82
+ delete_plugin,
83
+ )
84
+
85
+ @property
86
+ def client(self):
87
+ """
88
+ Return the Valkey client.
89
+ """
90
+ if '_client' in self.__dict__:
91
+ return self.__dict__['_client']
92
+
93
+ valkey = mrsm.attempt_import('valkey')
94
+
95
+ if 'uri' in self.__dict__:
96
+ self._client = valkey.Valkey.from_url(self.__dict__.get('uri'))
97
+ return self._client
98
+
99
+ optional_kwargs = {
100
+ key: self.__dict__.get(key)
101
+ for key in self.OPTIONAL_ATTRIBUTES
102
+ if key in self.__dict__
103
+ }
104
+ connection_kwargs = {
105
+ 'host': self.host,
106
+ **optional_kwargs
107
+ }
108
+
109
+ self._client = valkey.Valkey(**connection_kwargs)
110
+ return self._client
111
+
112
+ @property
113
+ def URI(self) -> str:
114
+ """
115
+ Return the connection URI for this connector.
116
+ """
117
+ import urllib.parse
118
+
119
+ if 'uri' in self.__dict__:
120
+ return self.__dict__.get('uri')
121
+
122
+ uri = "valkey://"
123
+ if 'username' in self.__dict__:
124
+ uri += urllib.parse.quote_plus(self.username) + ':'
125
+
126
+ if 'password' in self.__dict__:
127
+ uri += urllib.parse.quote_plus(self.password) + '@'
128
+
129
+ if 'host' in self.__dict__:
130
+ uri += self.host
131
+
132
+ if 'port' in self.__dict__:
133
+ uri += f':{self.port}'
134
+
135
+ if 'db' in self.__dict__:
136
+ uri += f"/{self.db}"
137
+
138
+ if 'socket_timeout' in self.__dict__:
139
+ uri += f"?timeout={self.socket_timeout}s"
140
+
141
+ return uri
142
+
143
+ def set(self, key: str, value: Any, **kwargs: Any) -> None:
144
+ """
145
+ Set the `key` to `value`.
146
+ """
147
+ return self.client.set(key, value, **kwargs)
148
+
149
+ def get(self, key: str) -> Union[str, None]:
150
+ """
151
+ Get the value for `key`.
152
+ """
153
+ val = self.client.get(key)
154
+ if val is None:
155
+ return None
156
+
157
+ return val.decode('utf-8')
158
+
159
+ def test_connection(self) -> bool:
160
+ """
161
+ Return whether a connection may be established.
162
+ """
163
+ return self.client.ping()
164
+
165
+ @classmethod
166
+ def quote_table(cls, table: str) -> str:
167
+ """
168
+ Return a quoted key.
169
+ """
170
+ return shlex.quote(table)
171
+
172
+ @classmethod
173
+ def get_counter_key(cls, table: str) -> str:
174
+ """
175
+ Return the counter key for a given table.
176
+ """
177
+ table_name = cls.quote_table(table)
178
+ return f"{table_name}:counter"
179
+
180
+ def push_df(
181
+ self,
182
+ df: 'pd.DataFrame',
183
+ table: str,
184
+ datetime_column: Optional[str] = None,
185
+ debug: bool = False,
186
+ ) -> int:
187
+ """
188
+ Append a pandas DataFrame to a table.
189
+
190
+ Parameters
191
+ ----------
192
+ df: pd.DataFrame
193
+ The pandas DataFrame to append to the table.
194
+
195
+ table: str
196
+ The "table" name (root key).
197
+
198
+ datetime_column: Optional[str], default None
199
+ If provided, use this key as the datetime index.
200
+
201
+ Returns
202
+ -------
203
+ The current index counter value (how many docs have been pushed).
204
+ """
205
+ docs_str = df.to_json(
206
+ date_format='iso',
207
+ orient='records',
208
+ date_unit='us',
209
+ )
210
+ docs = json.loads(docs_str)
211
+ return self.push_docs(
212
+ docs,
213
+ table,
214
+ datetime_column=datetime_column,
215
+ debug=debug,
216
+ )
217
+
218
+ def push_docs(
219
+ self,
220
+ docs: List[Dict[str, Any]],
221
+ table: str,
222
+ datetime_column: Optional[str] = None,
223
+ debug: bool = False,
224
+ ) -> int:
225
+ """
226
+ Append a list of documents to a table.
227
+
228
+ Parameters
229
+ ----------
230
+ docs: List[Dict[str, Any]]
231
+ The docs to be pushed.
232
+ All keys and values will be coerced into strings.
233
+
234
+ table: str
235
+ The "table" name (root key).
236
+
237
+ datetime_column: Optional[str], default None
238
+ If set, create a sorted set with this datetime column as the index.
239
+ Otherwise push the docs to a list.
240
+
241
+ Returns
242
+ -------
243
+ The current index counter value (how many docs have been pushed).
244
+ """
245
+ from meerschaum.utils.misc import json_serialize_datetime
246
+ table_name = self.quote_table(table)
247
+ datetime_column_key = self.get_datetime_column_key(table)
248
+ remote_datetime_column = self.get(datetime_column_key)
249
+ datetime_column = datetime_column or remote_datetime_column
250
+ dateutil_parser = mrsm.attempt_import('dateutil.parser')
251
+
252
+ old_len = (
253
+ self.client.zcard(table_name)
254
+ if datetime_column
255
+ else self.client.scard(table_name)
256
+ )
257
+ for doc in docs:
258
+ original_dt_val = (
259
+ doc[datetime_column]
260
+ if datetime_column and datetime_column in doc
261
+ else 0
262
+ )
263
+ dt_val = (
264
+ dateutil_parser.parse(str(original_dt_val))
265
+ if not isinstance(original_dt_val, int)
266
+ else int(original_dt_val)
267
+ ) if datetime_column else None
268
+ ts = (
269
+ int(dt_val.replace(tzinfo=timezone.utc).timestamp())
270
+ if isinstance(dt_val, datetime)
271
+ else int(dt_val)
272
+ ) if datetime_column else None
273
+ doc_str = json.dumps(
274
+ doc,
275
+ default=(lambda x: json_serialize_datetime(x) if hasattr(x, 'tzinfo') else str(x)),
276
+ separators=(',', ':'),
277
+ sort_keys=True,
278
+ )
279
+ if datetime_column:
280
+ self.client.zadd(table_name, {doc_str: ts})
281
+ else:
282
+ self.client.sadd(table_name, doc_str)
283
+
284
+ if datetime_column:
285
+ self.set(datetime_column_key, datetime_column)
286
+ new_len = (
287
+ self.client.zcard(table_name)
288
+ if datetime_column
289
+ else self.client.scard(table_name)
290
+ )
291
+
292
+ return new_len - old_len
293
+
294
+ def _push_hash_docs_to_list(self, docs: List[Dict[str, Any]], table: str) -> int:
295
+ table_name = self.quote_table(table)
296
+ next_ix = max(self.client.llen(table_name) or 0, 1)
297
+ for i, doc in enumerate(docs):
298
+ doc_key = f"{table_name}:{next_ix + i}"
299
+ self.client.hset(
300
+ doc_key,
301
+ mapping={
302
+ str(k): str(v)
303
+ for k, v in doc.items()
304
+ },
305
+ )
306
+ self.client.rpush(table_name, doc_key)
307
+
308
+ return next_ix + len(docs)
309
+
310
+ def get_datetime_column_key(self, table: str) -> str:
311
+ """
312
+ Return the key to store the datetime index for `table`.
313
+ """
314
+ table_name = self.quote_table(table)
315
+ return f'{table_name}:datetime_column'
316
+
317
+ def read(
318
+ self,
319
+ table: str,
320
+ begin: Union[datetime, int, str, None] = None,
321
+ end: Union[datetime, int, str, None] = None,
322
+ params: Optional[Dict[str, Any]] = None,
323
+ datetime_column: Optional[str] = None,
324
+ select_columns: Optional[List[str]] = None,
325
+ omit_columns: Optional[List[str]] = None,
326
+ debug: bool = False
327
+ ) -> Union['pd.DataFrame', None]:
328
+ """
329
+ Query the table and return the result dataframe.
330
+
331
+ Parameters
332
+ ----------
333
+ table: str
334
+ The "table" name to be queried.
335
+
336
+ begin: Union[datetime, int, str, None], default None
337
+ If provided, only return rows greater than or equal to this datetime.
338
+
339
+ end: Union[datetime, int, str, None], default None
340
+ If provided, only return rows older than this datetime.
341
+
342
+ params: Optional[Dict[str, Any]]
343
+ Additional Meerschaum filter parameters.
344
+
345
+ datetime_column: Optional[str], default None
346
+ If provided, use this column for the datetime index.
347
+ Otherwise infer from the table metadata.
348
+
349
+ select_columns: Optional[List[str]], default None
350
+ If provided, only return these columns.
351
+
352
+ omit_columns: Optional[List[str]], default None
353
+ If provided, do not include these columns in the result.
354
+
355
+ Returns
356
+ -------
357
+ A Pandas DataFrame of the result, or `None`.
358
+ """
359
+ from meerschaum.utils.dataframe import parse_df_datetimes, query_df
360
+ docs = self.read_docs(
361
+ table,
362
+ begin=begin,
363
+ end=end,
364
+ debug=debug,
365
+ )
366
+ df = parse_df_datetimes(docs)
367
+ datetime_column_key = self.get_datetime_column_key(table)
368
+ datetime_column = datetime_column or self.get(datetime_column_key)
369
+
370
+ return query_df(
371
+ df,
372
+ begin=(begin if datetime_column is not None else None),
373
+ end=(end if datetime_column is not None else None),
374
+ params=params,
375
+ datetime_column=datetime_column,
376
+ select_columns=select_columns,
377
+ omit_columns=omit_columns,
378
+ inplace=True,
379
+ reset_index=True,
380
+ debug=debug,
381
+ )
382
+
383
+ def read_docs(
384
+ self,
385
+ table: str,
386
+ begin: Union[datetime, int, str, None] = None,
387
+ end: Union[datetime, int, str, None] = None,
388
+ debug: bool = False,
389
+ ) -> List[Dict[str, str]]:
390
+ """
391
+ Return a list of previously pushed docs.
392
+
393
+ Parameters
394
+ ----------
395
+ table: str
396
+ The "table" name (root key) under which the docs were pushed.
397
+
398
+ begin: Union[datetime, int, str, None], default None
399
+ If provided and the table was created with a datetime index, only return documents
400
+ newer than this datetime.
401
+ If the table was not created with a datetime index and `begin` is an `int`,
402
+ return documents with a positional index greater than or equal to this value.
403
+
404
+ end: Union[datetime, int, str, None], default None
405
+ If provided and the table was created with a datetime index, only return documents
406
+ older than this datetime.
407
+ If the table was not created with a datetime index and `begin` is an `int`,
408
+ return documents with a positional index less than this value.
409
+
410
+ Returns
411
+ -------
412
+ A list of dictionaries, where all keys and values are strings.
413
+ """
414
+ table_name = self.quote_table(table)
415
+ datetime_column_key = self.get_datetime_column_key(table)
416
+ datetime_column = self.get(datetime_column_key)
417
+
418
+ if debug:
419
+ dprint(f"Reading documents from '{table}' with {begin=}, {end=}")
420
+
421
+ if not datetime_column:
422
+ return [
423
+ json.loads(doc_bytes.decode('utf-8'))
424
+ for doc_bytes in self.client.smembers(table_name)
425
+ ]
426
+
427
+ dateutil_parser = mrsm.attempt_import('dateutil.parser')
428
+
429
+ if isinstance(begin, str):
430
+ begin = dateutil_parser.parse(begin)
431
+
432
+ if isinstance(end, str):
433
+ end = dateutil_parser.parse(end)
434
+
435
+ begin_ts = (
436
+ (
437
+ int(begin.replace(tzinfo=timezone.utc).timestamp())
438
+ if isinstance(begin, datetime)
439
+ else int(begin)
440
+ )
441
+ if begin is not None else '-inf'
442
+ )
443
+ end_ts = (
444
+ (
445
+ int(end.replace(tzinfo=timezone.utc).timestamp())
446
+ if isinstance(end, datetime)
447
+ else int(end)
448
+ )
449
+ if end is not None else '+inf'
450
+ )
451
+
452
+ if debug:
453
+ dprint(f"Reading documents with {begin_ts=}, {end_ts=}")
454
+
455
+ return [
456
+ json.loads(doc_bytes.decode('utf-8'))
457
+ for doc_bytes in self.client.zrangebyscore(
458
+ table_name,
459
+ begin_ts,
460
+ end_ts,
461
+ withscores=False,
462
+ )
463
+ ]
464
+
465
+ def _read_docs_from_list(
466
+ self,
467
+ table: str,
468
+ begin_ix: Optional[int] = 0,
469
+ end_ix: Optional[int] = -1,
470
+ debug: bool = False,
471
+ ):
472
+ """
473
+ Read a list of documents from a "table".
474
+
475
+ Parameters
476
+ ----------
477
+ table: str
478
+ The "table" (root key) from which to read docs.
479
+
480
+ begin_ix: Optional[int], default 0
481
+ If provided, only read documents from this starting index.
482
+
483
+ end_ix: Optional[int], default -1
484
+ If provided, only read documents up to (not including) this index.
485
+
486
+ Returns
487
+ -------
488
+ A list of documents.
489
+ """
490
+ if begin_ix is None:
491
+ begin_ix = 0
492
+
493
+ if end_ix == 0:
494
+ return
495
+
496
+ if end_ix is None:
497
+ end_ix = -1
498
+ else:
499
+ end_ix -= 1
500
+
501
+ table_name = self.quote_table(table)
502
+ doc_keys = self.client.lrange(table_name, begin_ix, end_ix)
503
+ for doc_key in doc_keys:
504
+ yield {
505
+ key.decode('utf-8'): value.decode('utf-8')
506
+ for key, value in self.client.hgetall(doc_key).items()
507
+ }
508
+
509
+ def drop_table(self, table: str, debug: bool = False) -> None:
510
+ """
511
+ Drop a "table" of documents.
512
+
513
+ Parameters
514
+ ----------
515
+ table: str
516
+ The "table" name (root key) to be deleted.
517
+ """
518
+ table_name = self.quote_table(table)
519
+ datetime_column_key = self.get_datetime_column_key(table)
520
+ self.client.delete(table_name)
521
+ self.client.delete(datetime_column_key)
522
+
523
+ @classmethod
524
+ def get_entity_key(cls, *keys: Any) -> str:
525
+ """
526
+ Return a joined key to set an entity.
527
+ """
528
+ if not keys:
529
+ raise ValueError("No keys to be joined.")
530
+
531
+ for key in keys:
532
+ if cls.KEY_SEPARATOR in str(key):
533
+ raise ValueError(f"Key cannot contain separator '{cls.KEY_SEPARATOR}'.")
534
+
535
+ return cls.KEY_SEPARATOR.join([str(key) for key in keys])
@@ -0,0 +1,10 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Import the `ValkeyConnector`.
6
+ """
7
+
8
+ from meerschaum.connectors.valkey._ValkeyConnector import ValkeyConnector
9
+
10
+ __all__ = ('ValkeyConnector',)
@@ -0,0 +1,75 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Define the `fetch` method for reading from Valkey databases.
6
+ """
7
+
8
+ import json
9
+ from datetime import datetime, timezone
10
+
11
+ import meerschaum as mrsm
12
+ from meerschaum.utils.typing import Optional, Dict, Any, List, Union
13
+ from meerschaum.utils.warnings import warn, dprint
14
+
15
+
16
+ def fetch(
17
+ self,
18
+ pipe: mrsm.Pipe,
19
+ begin: Union[datetime, int, None] = None,
20
+ end: Union[datetime, int, None] = None,
21
+ params: Optional[Dict[str, Any]] = None,
22
+ debug: bool = False,
23
+ **kwargs: Any
24
+ ) -> List[Dict[str, Any]]:
25
+ """
26
+ Return data from a source database.
27
+ """
28
+ source_key = pipe.parameters.get('valkey', {}).get('key', None)
29
+ if not source_key:
30
+ return []
31
+
32
+ try:
33
+ key_type = self.client.type(source_key).decode('utf-8')
34
+ except Exception:
35
+ warn(f"Could not determine the type for key '{source_key}'.")
36
+ return []
37
+
38
+ begin_ts = (
39
+ (
40
+ int(begin.replace(tzinfo=timezone.utc).timestamp())
41
+ if isinstance(begin, datetime)
42
+ else int(begin)
43
+ )
44
+ if begin is not None else '-inf'
45
+ )
46
+ end_ts = (
47
+ (
48
+ int(end.replace(tzinfo=timezone.utc).timestamp())
49
+ if isinstance(end, datetime)
50
+ else int(end)
51
+ )
52
+ if end is not None else '+inf'
53
+ )
54
+
55
+ if debug:
56
+ dprint(f"Reading documents with {begin_ts=}, {end_ts=}")
57
+
58
+ if key_type == 'set':
59
+ return [
60
+ json.loads(doc_bytes.decode('utf-8'))
61
+ for doc_bytes in self.client.smembers(source_key)
62
+ ]
63
+
64
+ if key_type == 'zset':
65
+ return [
66
+ json.loads(doc_bytes.decode('utf-8'))
67
+ for doc_bytes in self.client.zrangebyscore(
68
+ source_key,
69
+ begin_ts,
70
+ end_ts,
71
+ withscores=False,
72
+ )
73
+ ]
74
+
75
+ return [{source_key: self.get(source_key)}]