meerschaum 2.5.0__py3-none-any.whl → 2.6.0.dev1__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 (35) hide show
  1. meerschaum/_internal/arguments/_parser.py +6 -1
  2. meerschaum/_internal/entry.py +16 -5
  3. meerschaum/actions/edit.py +6 -6
  4. meerschaum/actions/sql.py +12 -11
  5. meerschaum/api/dash/pipes.py +95 -13
  6. meerschaum/api/routes/_webterm.py +1 -0
  7. meerschaum/config/_edit.py +46 -19
  8. meerschaum/config/_read_config.py +20 -9
  9. meerschaum/config/_version.py +1 -1
  10. meerschaum/config/stack/__init__.py +1 -1
  11. meerschaum/connectors/sql/_pipes.py +80 -24
  12. meerschaum/connectors/sql/_sql.py +29 -10
  13. meerschaum/connectors/valkey/_pipes.py +1 -1
  14. meerschaum/core/Pipe/__init__.py +8 -9
  15. meerschaum/core/Pipe/_attributes.py +33 -11
  16. meerschaum/core/Pipe/_data.py +26 -7
  17. meerschaum/core/Pipe/_dtypes.py +4 -4
  18. meerschaum/core/Pipe/_fetch.py +1 -1
  19. meerschaum/core/Pipe/_sync.py +16 -4
  20. meerschaum/core/Pipe/_verify.py +1 -1
  21. meerschaum/utils/dataframe.py +58 -31
  22. meerschaum/utils/dtypes/__init__.py +16 -5
  23. meerschaum/utils/dtypes/sql.py +58 -28
  24. meerschaum/utils/misc.py +49 -16
  25. meerschaum/utils/packages/_packages.py +2 -1
  26. meerschaum/utils/schedule.py +7 -5
  27. meerschaum/utils/sql.py +224 -40
  28. {meerschaum-2.5.0.dist-info → meerschaum-2.6.0.dev1.dist-info}/METADATA +5 -3
  29. {meerschaum-2.5.0.dist-info → meerschaum-2.6.0.dev1.dist-info}/RECORD +35 -35
  30. {meerschaum-2.5.0.dist-info → meerschaum-2.6.0.dev1.dist-info}/WHEEL +1 -1
  31. {meerschaum-2.5.0.dist-info → meerschaum-2.6.0.dev1.dist-info}/LICENSE +0 -0
  32. {meerschaum-2.5.0.dist-info → meerschaum-2.6.0.dev1.dist-info}/NOTICE +0 -0
  33. {meerschaum-2.5.0.dist-info → meerschaum-2.6.0.dev1.dist-info}/entry_points.txt +0 -0
  34. {meerschaum-2.5.0.dist-info → meerschaum-2.6.0.dev1.dist-info}/top_level.txt +0 -0
  35. {meerschaum-2.5.0.dist-info → meerschaum-2.6.0.dev1.dist-info}/zip-safe +0 -0
@@ -90,8 +90,13 @@ def parse_datetime(dt_str: str) -> Union[datetime, int, str]:
90
90
  except Exception as e:
91
91
  dt = None
92
92
  if dt is None:
93
- from meerschaum.utils.warnings import warn, error
93
+ from meerschaum.utils.warnings import error
94
94
  error(f"'{dt_str}' is not a valid datetime format.", stack=False)
95
+
96
+ if isinstance(dt, datetime):
97
+ from meerschaum.utils.dtypes import coerce_timezone
98
+ dt = coerce_timezone(dt)
99
+
95
100
  return dt
96
101
 
97
102
 
@@ -13,6 +13,7 @@ import os
13
13
  import sys
14
14
  import pathlib
15
15
 
16
+ import meerschaum as mrsm
16
17
  from meerschaum.utils.typing import SuccessTuple, List, Optional, Dict, Callable, Any
17
18
  from meerschaum.config.static import STATIC_CONFIG as _STATIC_CONFIG
18
19
 
@@ -229,7 +230,7 @@ def entry_with_args(
229
230
  _ = get_shell(**kw).cmdloop()
230
231
  return True, "Success"
231
232
 
232
- skip_schedule = False
233
+ _skip_schedule = False
233
234
 
234
235
  executor_keys = kw.get('executor_keys', None)
235
236
  if executor_keys is None:
@@ -246,7 +247,7 @@ def entry_with_args(
246
247
  api_label = executor_keys.split(':')[-1]
247
248
  kw['action'].insert(0, 'api')
248
249
  kw['action'].insert(1, api_label)
249
- skip_schedule = True
250
+ _skip_schedule = True
250
251
 
251
252
  ### If the `--daemon` flag is present, prepend 'start job'.
252
253
  if kw.get('daemon', False) and kw['action'][0] != 'stack':
@@ -271,7 +272,7 @@ def entry_with_args(
271
272
  and kw['action'][0] == 'start'
272
273
  and kw['action'][1] in ('job', 'jobs')
273
274
  ):
274
- skip_schedule = True
275
+ _skip_schedule = True
275
276
 
276
277
  kw['action'] = remove_leading_action(kw['action'], _actions=_actions)
277
278
 
@@ -279,10 +280,11 @@ def entry_with_args(
279
280
  _do_action_wrapper,
280
281
  action_function,
281
282
  plugin_name,
283
+ _skip_schedule=_skip_schedule,
282
284
  **kw
283
285
  )
284
286
 
285
- if kw.get('schedule', None) and not skip_schedule:
287
+ if kw.get('schedule', None) and not _skip_schedule:
286
288
  from meerschaum.utils.schedule import schedule_function
287
289
  from meerschaum.utils.misc import interval_str
288
290
  import time
@@ -304,7 +306,12 @@ def entry_with_args(
304
306
  return result
305
307
 
306
308
 
307
- def _do_action_wrapper(action_function, plugin_name, **kw):
309
+ def _do_action_wrapper(
310
+ action_function,
311
+ plugin_name,
312
+ _skip_schedule: bool = False,
313
+ **kw
314
+ ):
308
315
  from meerschaum.plugins import Plugin
309
316
  from meerschaum.utils.venv import Venv
310
317
  from meerschaum.utils.misc import filter_keywords
@@ -328,6 +335,10 @@ def _do_action_wrapper(action_function, plugin_name, **kw):
328
335
  )
329
336
  except KeyboardInterrupt:
330
337
  result = False, f"Cancelled action `{action_name.lstrip()}`."
338
+
339
+ if kw.get('schedule', None) and not _skip_schedule:
340
+ mrsm.pprint(result)
341
+
331
342
  return result
332
343
 
333
344
  _shells = []
@@ -65,24 +65,24 @@ def _complete_edit(
65
65
  from meerschaum._internal.shell import default_action_completer
66
66
  return default_action_completer(action=(['edit'] + action), **kw)
67
67
 
68
- def _edit_config(action : Optional[List[str]] = None, **kw : Any) -> SuccessTuple:
68
+
69
+ def _edit_config(action: Optional[List[str]] = None, **kw : Any) -> SuccessTuple:
69
70
  """
70
71
  Edit Meerschaum configuration files.
71
-
72
+
72
73
  Specify a specific configuration key to edit.
73
74
  Defaults to editing `meerschaum` configuration (connectors, instance, etc.).
74
-
75
+
75
76
  Examples:
76
77
  ```
77
78
  ### Edit the main 'meerschaum' configuration.
78
79
  edit config
79
-
80
+
80
81
  ### Edit 'system' configuration.
81
82
  edit config system
82
-
83
+
83
84
  ### Create a new configuration file called 'myconfig'.
84
85
  edit config myconfig
85
-
86
86
  ```
87
87
  """
88
88
  from meerschaum.config._edit import edit_config
meerschaum/actions/sql.py CHANGED
@@ -14,6 +14,7 @@ exec_methods = {
14
14
  'exec',
15
15
  }
16
16
 
17
+
17
18
  def sql(
18
19
  action: Optional[List[str]] = None,
19
20
  gui: bool = False,
@@ -22,40 +23,40 @@ def sql(
22
23
  **kw: Any
23
24
  ):
24
25
  """Execute a SQL query or launch an interactive CLI. All positional arguments are optional.
25
-
26
+
26
27
  Usage:
27
28
  `sql {label} {method} {query / table}`
28
-
29
+
29
30
  Options:
30
31
  - `sql {label}`
31
32
  Launch an interactive CLI. If {label} is omitted, use 'main'.
32
-
33
+
33
34
  - `sql {label} read [query / table]`
34
35
  Read a table or query as a pandas DataFrame and print the result.
35
-
36
+
36
37
  - `sql {label} exec [query]`
37
38
  Execute a query and print the success status.
38
-
39
+
39
40
  Examples:
40
41
  - `sql`
41
42
  Open an interactive CLI for `sql:main`.
42
-
43
+
43
44
  - `sql local`
44
45
  Open an interactive CLI for `sql:local`.
45
-
46
+
46
47
  - `sql table`
47
48
  Read from `table` on `sql:main`
48
49
  (translates to `SELECT * FROM table`).
49
-
50
+
50
51
  - `sql local table`
51
52
  Read from `table` on `sql:local`.
52
-
53
+
53
54
  - `sql local read table`
54
55
  Read from `table` on `sql:local`.
55
-
56
+
56
57
  - `sql "SELECT * FROM table WHERE id = 1"`
57
58
  Execute the above query on `sql:main` and print the results.
58
-
59
+
59
60
  - `sql local exec "INSERT INTO table (id) VALUES (1)"
60
61
  Execute the above query on `sql:local`.
61
62
  """
@@ -346,6 +346,8 @@ def accordion_items_from_pipe(
346
346
  for item in skip_items:
347
347
  _ = items_titles.pop(item, None)
348
348
 
349
+ pipe_meta_str = json.dumps(pipe.meta, sort_keys=True)
350
+
349
351
  ### Only generate items if they're in the `active_items` list.
350
352
  items_bodies = {}
351
353
  if 'overview' in active_items:
@@ -358,22 +360,103 @@ def accordion_items_from_pipe(
358
360
  html.Tr([html.Td("Instance"), html.Td(html.Pre(f"{pipe.instance_keys}"))]),
359
361
  html.Tr([html.Td("Target Table"), html.Td(html.Pre(f"{pipe.target}"))]),
360
362
  ]
361
- columns = pipe.columns.copy()
362
- if columns:
363
- datetime_index = columns.pop('datetime', None)
364
- columns_items = []
365
- if datetime_index:
366
- columns_items.append(html.Li(f"{datetime_index} (datetime)"))
367
- columns_items.extend([
368
- html.Li(f"{col}")
369
- for col_key, col in columns.items()
370
- ])
363
+
364
+ indices_header = [
365
+ html.Thead(
366
+ html.Tr(
367
+ [
368
+ html.Th(
369
+ html.Span(
370
+ "Key",
371
+ id={'type': 'key-table-header', 'id': pipe_meta_str},
372
+ style={"textDecoration": "underline", "cursor": "pointer"},
373
+ ),
374
+ ),
375
+ html.Th(
376
+ html.Span(
377
+ "Column",
378
+ id={'type': 'column-table-header', 'id': pipe_meta_str},
379
+ style={"textDecoration": "underline", "cursor": "pointer"},
380
+ ),
381
+ ),
382
+ html.Th(
383
+ html.Span(
384
+ "Index",
385
+ id={'type': 'index-table-header', 'id': pipe_meta_str},
386
+ style={"textDecoration": "underline", "cursor": "pointer"},
387
+ ),
388
+ ),
389
+ html.Th(
390
+ html.Span(
391
+ "Is Composite",
392
+ id={'type': 'is-composite-table-header', 'id': pipe_meta_str},
393
+ style={"textDecoration": "underline", "cursor": "pointer"},
394
+ ),
395
+ ),
396
+ dbc.Tooltip(
397
+ "Unique reference name for the index "
398
+ "(e.g. `datetime` for the range axis)",
399
+ target={'type': 'key-table-header', 'id': pipe_meta_str},
400
+ ),
401
+ dbc.Tooltip(
402
+ "The actual column (field name) in the target dataset.",
403
+ target={'type': 'column-table-header', 'id': pipe_meta_str},
404
+ ),
405
+ dbc.Tooltip(
406
+ "The name of the index created on the given columns.",
407
+ target={'type': 'index-table-header', 'id': pipe_meta_str},
408
+ ),
409
+ dbc.Tooltip(
410
+ "Whether the column is used in the composite primary key "
411
+ "to determine updates.",
412
+ target={'type': 'is-composite-table-header', 'id': pipe_meta_str},
413
+ ),
414
+ ]
415
+ )
416
+ )
417
+ ]
418
+
419
+ indices = pipe.indices
420
+ columns = pipe.columns
421
+ index_column_names = pipe.get_indices()
422
+ indices_rows = []
423
+ for ix_key, ix_name in index_column_names.items():
424
+ col = columns.get(ix_key, None)
425
+ ix_cols = indices.get(ix_key, None)
426
+ if not col and not ix_cols:
427
+ continue
428
+ if not isinstance(ix_cols, (list, tuple)):
429
+ ix_cols = [ix_cols]
430
+ if col:
431
+ col_item = html.Pre(col)
432
+ elif len(ix_cols) == 1:
433
+ col_item = html.Pre(ix_cols[0])
434
+ else:
435
+ col_item = html.Pre(html.Ul([html.Li(_c) for _c in ix_cols]))
436
+ is_composite_item = "✅" if col else ""
437
+ ix_key_item = html.Pre(ix_key) if ix_key != 'datetime' else html.Pre(f"🕓 {ix_key}")
438
+ indices_rows.append(
439
+ html.Tr([
440
+ html.Td(ix_key_item),
441
+ html.Td(col_item),
442
+ html.Td(html.Pre(ix_name)),
443
+ html.Td(is_composite_item),
444
+ ])
445
+ )
446
+ indices_table = dbc.Table(
447
+ indices_header + [html.Tbody(indices_rows)],
448
+ bordered=True,
449
+ hover=False,
450
+ striped=True,
451
+ )
452
+ if indices_rows:
371
453
  overview_rows.append(
372
454
  html.Tr([
373
- html.Td("Indices" if len(columns_items) != 1 else "Index"),
374
- html.Td(html.Pre(html.Ul(columns_items))),
455
+ html.Td("Indices" if len(indices_rows) != 1 else "Index"),
456
+ html.Td(indices_table),
375
457
  ])
376
458
  )
459
+
377
460
  tags = pipe.tags
378
461
  if tags:
379
462
  tags_items = html.Ul([
@@ -420,7 +503,6 @@ def accordion_items_from_pipe(
420
503
  if newest_time is not None:
421
504
  stats_rows.append(html.Tr([html.Td("Newest time"), html.Td(str(newest_time))]))
422
505
 
423
-
424
506
  items_bodies['stats'] = dbc.Table(stats_header + [html.Tbody(stats_rows)], hover=True)
425
507
 
426
508
  if 'columns' in active_items:
@@ -13,6 +13,7 @@ from meerschaum.utils.packages import attempt_import
13
13
  from meerschaum.api.dash.sessions import is_session_authenticated
14
14
  fastapi, fastapi_responses = attempt_import('fastapi', 'fastapi.responses')
15
15
  import starlette
16
+ httpcore = attempt_import('httpcore')
16
17
  httpx = attempt_import('httpx')
17
18
  websockets = attempt_import('websockets')
18
19
  Request = fastapi.Request
@@ -7,44 +7,71 @@ Functions for editing the configuration file
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- import sys
11
10
  import pathlib
12
11
  from meerschaum.utils.typing import Optional, Any, SuccessTuple, Mapping, Dict, List, Union
13
12
 
13
+
14
14
  def edit_config(
15
- keys : Optional[List[str]] = None,
16
- params : Optional[Dict[str, Any]] = None,
17
- debug : bool = False,
18
- **kw : Any
19
- ) -> SuccessTuple:
15
+ keys: Optional[List[str]] = None,
16
+ params: Optional[Dict[str, Any]] = None,
17
+ debug: bool = False,
18
+ **kw: Any
19
+ ) -> SuccessTuple:
20
20
  """Edit the configuration files."""
21
21
  from meerschaum.config import get_config, config
22
- from meerschaum.config._read_config import get_keyfile_path
22
+ from meerschaum.config._read_config import get_keyfile_path, read_config
23
23
  from meerschaum.config._paths import CONFIG_DIR_PATH
24
24
  from meerschaum.utils.packages import reload_meerschaum
25
25
  from meerschaum.utils.misc import edit_file
26
- from meerschaum.utils.debug import dprint
26
+ from meerschaum.utils.warnings import warn, dprint
27
+ from meerschaum.utils.prompt import prompt
27
28
 
28
29
  if keys is None:
29
30
  keys = []
30
31
 
31
- for k in keys:
32
- ### If defined in default, create the config file.
33
- if isinstance(config, dict) and k in config:
34
- del config[k]
35
- get_config(k, write_missing=True, warn=False)
36
- edit_file(get_keyfile_path(k, create_new=True))
32
+ def _edit_key(key: str):
33
+ while True:
34
+ ### If defined in default, create the config file.
35
+ key_config = config.pop(key, None)
36
+ keyfile_path = get_keyfile_path(key, create_new=True)
37
+ get_config(key, write_missing=True, warn=False)
38
+
39
+ edit_file(get_keyfile_path(key, create_new=True))
40
+
41
+ ### TODO: verify that the file is valid. Retry if not.
42
+ try:
43
+ new_key_config = read_config(
44
+ CONFIG_DIR_PATH,
45
+ [key],
46
+ write_missing=False,
47
+ raise_parsing_errors=True,
48
+ )
49
+ except Exception:
50
+ if key_config:
51
+ config[key] = key_config
52
+ warn(f"Could not parse key '{key}'.", stack=False)
53
+ _ = prompt(f"Press [Enter] to edit '{keyfile_path}', [CTRL+C] to exit.")
54
+ continue
55
+
56
+ if new_key_config:
57
+ break
58
+
59
+ try:
60
+ for k in keys:
61
+ _edit_key(k)
62
+ except KeyboardInterrupt:
63
+ return False, f""
37
64
 
38
65
  reload_meerschaum(debug=debug)
39
66
  return (True, "Success")
40
67
 
41
68
 
42
69
  def write_config(
43
- config_dict: Optional[Dict[str, Any]] = None,
44
- directory: Union[str, pathlib.Path, None] = None,
45
- debug: bool = False,
46
- **kw : Any
47
- ) -> bool:
70
+ config_dict: Optional[Dict[str, Any]] = None,
71
+ directory: Union[str, pathlib.Path, None] = None,
72
+ debug: bool = False,
73
+ **kw: Any
74
+ ) -> bool:
48
75
  """Write YAML and JSON files to the configuration directory.
49
76
 
50
77
  Parameters
@@ -6,22 +6,26 @@ Import the config yaml file
6
6
  """
7
7
 
8
8
  from __future__ import annotations
9
+ import pathlib
10
+
9
11
  from meerschaum.utils.typing import Optional, Dict, Any, List, Tuple, Union
10
12
  from meerschaum.config import get_config
11
13
 
14
+
12
15
  def read_config(
13
- directory: Optional[str] = None,
16
+ directory: Union[pathlib.Path, str, None] = None,
14
17
  keys: Optional[List[str]] = None,
15
- write_missing : bool = True,
16
- substitute : bool = True,
17
- with_filenames : bool = False,
18
+ write_missing: bool = True,
19
+ substitute: bool = True,
20
+ with_filenames: bool = False,
21
+ raise_parsing_errors: bool = False,
18
22
  ) -> Union[Dict[str, Any], Tuple[Dict[str, Any], List[str]]]:
19
23
  """
20
24
  Read the configuration directory.
21
25
 
22
26
  Parameters
23
27
  ----------
24
- directory: Optional[str], default None
28
+ directory: Union[pathlib.Path, str, None], default None
25
29
  The directory with configuration files (.json and .yaml).
26
30
 
27
31
  keys: Optional[List[str]], default None
@@ -36,7 +40,10 @@ def read_config(
36
40
 
37
41
  with_filename: bool, default False
38
42
  If `True`, return a tuple of the configuration dictionary with a list of read filenames.
39
-
43
+
44
+ raise_parsing_errors: bool, default False
45
+ If `True`, re-raise parsing exceptions.
46
+
40
47
  Examples
41
48
  --------
42
49
  >>> read_config(keys=['meerschaum'], with_filename=True)
@@ -63,9 +70,9 @@ def read_config(
63
70
 
64
71
  default_filetype = STATIC_CONFIG['config']['default_filetype']
65
72
  filetype_loaders = {
66
- 'yml' : yaml.load,
67
- 'yaml' : yaml.load,
68
- 'json' : json.load,
73
+ 'yml': yaml.load,
74
+ 'yaml': yaml.load,
75
+ 'json': json.load,
69
76
  }
70
77
 
71
78
  ### Construct filekeys (files to parse).
@@ -167,6 +174,8 @@ def read_config(
167
174
  _config_key = filetype_loaders[_type](f)
168
175
  except Exception as e:
169
176
  print(f"Error processing file: {filepath}")
177
+ if raise_parsing_errors:
178
+ raise e
170
179
  import traceback
171
180
  traceback.print_exc()
172
181
  _config_key = {}
@@ -184,6 +193,8 @@ def read_config(
184
193
  config[symlinks_key][key] = _single_key_config[symlinks_key][key]
185
194
  break
186
195
  except Exception as e:
196
+ if raise_parsing_errors:
197
+ raise e
187
198
  print(f"Unable to parse {filename}!")
188
199
  import traceback
189
200
  traceback.print_exc()
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.5.0"
5
+ __version__ = "2.6.0.dev1"
@@ -39,7 +39,7 @@ valkey_password = 'MRSM{meerschaum:connectors:valkey:main:password}'
39
39
 
40
40
  env_dict = {
41
41
  'COMPOSE_PROJECT_NAME': 'mrsm',
42
- 'TIMESCALEDB_VERSION': 'latest-pg16-oss',
42
+ 'TIMESCALEDB_VERSION': 'latest-pg16',
43
43
  'POSTGRES_USER': db_user,
44
44
  'POSTGRES_PASSWORD': db_pass,
45
45
  'POSTGRES_DB': db_base,