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.
- meerschaum/_internal/arguments/__init__.py +2 -1
- meerschaum/_internal/arguments/_parse_arguments.py +88 -12
- meerschaum/_internal/docs/index.py +3 -2
- meerschaum/_internal/entry.py +42 -20
- meerschaum/_internal/shell/Shell.py +38 -44
- meerschaum/_internal/term/TermPageHandler.py +2 -3
- meerschaum/_internal/term/__init__.py +13 -11
- meerschaum/actions/api.py +26 -23
- meerschaum/actions/bootstrap.py +38 -11
- meerschaum/actions/copy.py +3 -3
- meerschaum/actions/delete.py +4 -1
- meerschaum/actions/register.py +1 -3
- meerschaum/actions/stack.py +24 -19
- meerschaum/actions/start.py +41 -41
- meerschaum/actions/sync.py +53 -52
- meerschaum/api/__init__.py +48 -14
- meerschaum/api/_events.py +26 -17
- meerschaum/api/_oauth2.py +2 -2
- meerschaum/api/_websockets.py +5 -4
- meerschaum/api/dash/__init__.py +7 -16
- meerschaum/api/dash/callbacks/__init__.py +1 -0
- meerschaum/api/dash/callbacks/dashboard.py +52 -58
- meerschaum/api/dash/callbacks/jobs.py +15 -16
- meerschaum/api/dash/callbacks/login.py +16 -10
- meerschaum/api/dash/callbacks/pipes.py +41 -0
- meerschaum/api/dash/callbacks/plugins.py +1 -1
- meerschaum/api/dash/callbacks/register.py +15 -11
- meerschaum/api/dash/components.py +54 -59
- meerschaum/api/dash/jobs.py +5 -9
- meerschaum/api/dash/pages/__init__.py +1 -0
- meerschaum/api/dash/pages/pipes.py +19 -0
- meerschaum/api/dash/pipes.py +86 -58
- meerschaum/api/dash/plugins.py +6 -4
- meerschaum/api/dash/sessions.py +176 -0
- meerschaum/api/dash/users.py +3 -41
- meerschaum/api/dash/webterm.py +12 -17
- meerschaum/api/resources/static/js/terminado.js +1 -1
- meerschaum/api/routes/_actions.py +4 -118
- meerschaum/api/routes/_jobs.py +45 -24
- meerschaum/api/routes/_login.py +4 -4
- meerschaum/api/routes/_pipes.py +3 -3
- meerschaum/api/routes/_webterm.py +5 -6
- meerschaum/config/_default.py +15 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +64 -21
- meerschaum/config/static/__init__.py +6 -0
- meerschaum/connectors/{Connector.py → _Connector.py} +19 -13
- meerschaum/connectors/__init__.py +24 -14
- meerschaum/connectors/api/{APIConnector.py → _APIConnector.py} +3 -1
- meerschaum/connectors/api/__init__.py +2 -1
- meerschaum/connectors/api/_actions.py +22 -36
- meerschaum/connectors/api/_jobs.py +1 -0
- meerschaum/connectors/parse.py +18 -16
- meerschaum/connectors/poll.py +30 -24
- meerschaum/connectors/sql/__init__.py +3 -1
- meerschaum/connectors/sql/_pipes.py +172 -197
- meerschaum/connectors/sql/_plugins.py +45 -43
- meerschaum/connectors/sql/_users.py +46 -38
- meerschaum/connectors/valkey/_ValkeyConnector.py +535 -0
- meerschaum/connectors/valkey/__init__.py +10 -0
- meerschaum/connectors/valkey/_fetch.py +75 -0
- meerschaum/connectors/valkey/_pipes.py +844 -0
- meerschaum/connectors/valkey/_plugins.py +265 -0
- meerschaum/connectors/valkey/_users.py +305 -0
- meerschaum/core/Pipe/__init__.py +3 -0
- meerschaum/core/Pipe/_attributes.py +1 -2
- meerschaum/core/Pipe/_clear.py +16 -13
- meerschaum/core/Pipe/_copy.py +106 -0
- meerschaum/core/Pipe/_data.py +165 -101
- meerschaum/core/Pipe/_drop.py +4 -4
- meerschaum/core/Pipe/_dtypes.py +14 -14
- meerschaum/core/Pipe/_edit.py +15 -14
- meerschaum/core/Pipe/_sync.py +134 -53
- meerschaum/core/Pipe/_verify.py +11 -11
- meerschaum/core/User/_User.py +14 -12
- meerschaum/jobs/_Job.py +27 -14
- meerschaum/jobs/__init__.py +7 -2
- meerschaum/jobs/systemd.py +20 -8
- meerschaum/plugins/_Plugin.py +17 -13
- meerschaum/utils/_get_pipes.py +14 -20
- meerschaum/utils/dataframe.py +291 -101
- meerschaum/utils/dtypes/__init__.py +31 -6
- meerschaum/utils/dtypes/sql.py +4 -4
- meerschaum/utils/formatting/_shell.py +5 -6
- meerschaum/utils/misc.py +3 -3
- meerschaum/utils/packages/__init__.py +14 -9
- meerschaum/utils/packages/_packages.py +2 -0
- meerschaum/utils/prompt.py +1 -1
- meerschaum/utils/schedule.py +1 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/METADATA +7 -1
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/RECORD +98 -89
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/WHEEL +1 -1
- meerschaum/api/dash/actions.py +0 -255
- /meerschaum/connectors/sql/{SQLConnector.py → _SQLConnector.py} +0 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.3.5.dev0.dist-info → meerschaum-2.4.0.dist-info}/top_level.txt +0 -0
- {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,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)}]
|