meerschaum 2.4.7__py3-none-any.whl → 2.4.9__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.
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
  import sys
11
11
  import argparse
12
12
  import json
13
+ import re
13
14
  from datetime import datetime, timedelta, timezone
14
15
  from meerschaum.utils.typing import Union, Dict, List, Any, Tuple, Callable
15
16
  from meerschaum.utils.misc import string_to_dict
@@ -39,7 +40,7 @@ class ArgumentParser(argparse.ArgumentParser):
39
40
 
40
41
  def parse_datetime(dt_str: str) -> Union[datetime, int, str]:
41
42
  """Parse a string into a datetime."""
42
- from meerschaum.utils.misc import is_int
43
+ from meerschaum.utils.misc import is_int, round_time
43
44
  if is_int(dt_str):
44
45
  return int(dt_str)
45
46
 
@@ -47,11 +48,43 @@ def parse_datetime(dt_str: str) -> Union[datetime, int, str]:
47
48
  return 'None'
48
49
 
49
50
  from meerschaum.utils.packages import attempt_import
50
- dateutil_parser = attempt_import('dateutil.parser')
51
+ dateutil_parser, dateutil_relativedelta = attempt_import(
52
+ 'dateutil.parser', 'dateutil.relativedelta'
53
+ )
54
+ relativedelta = dateutil_relativedelta.relativedelta
55
+
56
+ ago_pattern = r'(\d+)\s+(days?|minutes?|seconds?|hours?|weeks?|months?|years?)\s+ago'
57
+ round_pattern = r'(\d+)\s+(days?|minutes?|seconds?|hours?|weeks?|years?)'
58
+ now = datetime.now(timezone.utc).replace(tzinfo=None)
59
+ ago_matches = re.findall(ago_pattern, dt_str.lower())
51
60
 
52
61
  try:
53
62
  if dt_str.lower() == 'now':
54
- dt = datetime.now(timezone.utc).replace(tzinfo=None)
63
+ dt = now
64
+ elif ago_matches:
65
+ val_str, unit_str = ago_matches[0]
66
+ if not unit_str.endswith('s'):
67
+ unit_str += 's'
68
+ val = int(val_str) if is_int(val_str) else float(val_str)
69
+ ago_delta = relativedelta(**{unit_str: val})
70
+ round_part = dt_str.lower().split('ago ')[-1]
71
+ round_delta = None
72
+ if round_part:
73
+ round_matches = re.findall(round_pattern, round_part)
74
+ if round_matches:
75
+ round_val_str, round_unit_str = round_matches[0]
76
+ if not round_unit_str.endswith('s'):
77
+ round_unit_str += 's'
78
+ round_val = (
79
+ int(round_val_str)
80
+ if is_int(round_val_str)
81
+ else float(round_val_str)
82
+ )
83
+ round_delta = timedelta(**{round_unit_str: round_val})
84
+
85
+ dt = now - ago_delta
86
+ if round_delta is not None:
87
+ dt = round_time(dt, round_delta)
55
88
  else:
56
89
  dt = dateutil_parser.parse(dt_str)
57
90
  except Exception as e:
@@ -7,6 +7,8 @@ This module contains functions for printing elements.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
11
+ from datetime import datetime
10
12
  import meerschaum as mrsm
11
13
  from meerschaum.utils.typing import SuccessTuple, Union, Sequence, Any, Optional, List, Dict, Tuple
12
14
 
@@ -274,8 +276,8 @@ def _show_arguments(
274
276
  def _show_data(
275
277
  action: Optional[List[str]] = None,
276
278
  gui: bool = False,
277
- begin: Optional[datetime.datetime] = None,
278
- end: Optional[datetime.datetime] = None,
279
+ begin: Union[datetime, int, None] = None,
280
+ end: Union[datetime, int, None] = None,
279
281
  params: Optional[Dict[str, Any]] = None,
280
282
  chunksize: Optional[int] = -1,
281
283
  nopretty: bool = False,
@@ -401,12 +403,15 @@ def _show_columns(
401
403
  def _show_rowcounts(
402
404
  action: Optional[List[str]] = None,
403
405
  workers: Optional[int] = None,
406
+ begin: Union[datetime, int, None] = None,
407
+ end: Union[datetime, int, None] = None,
408
+ params: Optional[Dict[str, Any]] = None,
404
409
  debug: bool = False,
405
410
  **kw: Any
406
411
  ) -> SuccessTuple:
407
412
  """
408
413
  Show the rowcounts for pipes.
409
-
414
+
410
415
  To see remote rowcounts (execute `COUNT(*)` on the source server),
411
416
  execute `show rowcounts remote`.
412
417
  """
@@ -421,7 +426,13 @@ def _show_rowcounts(
421
426
  pipes = get_pipes(as_list=True, debug=debug, **kw)
422
427
  pool = get_pool(workers=workers)
423
428
  def _get_rc(_pipe):
424
- return _pipe.get_rowcount(remote=remote, debug=debug)
429
+ return _pipe.get_rowcount(
430
+ begin=begin,
431
+ end=end,
432
+ params=params,
433
+ remote=remote,
434
+ debug=debug
435
+ )
425
436
 
426
437
  rowcounts = pool.map(_get_rc, pipes) if pool is not None else [_get_rc(p) for p in pipes]
427
438
 
meerschaum/actions/sql.py CHANGED
@@ -59,7 +59,7 @@ def sql(
59
59
  - `sql local exec "INSERT INTO table (id) VALUES (1)"
60
60
  Execute the above query on `sql:local`.
61
61
  """
62
- from meerschaum.utils.dataframe impor to_json
62
+ from meerschaum.utils.dataframe import to_json
63
63
 
64
64
  if action is None:
65
65
  action = []
@@ -408,8 +408,8 @@ def get_pipe_data(
408
408
  _params = None
409
409
  if not isinstance(_params, dict):
410
410
  raise fastapi.HTTPException(
411
- status_code = 409,
412
- detail = "Params must be a valid JSON-encoded dictionary.",
411
+ status_code=409,
412
+ detail="Params must be a valid JSON-encoded dictionary.",
413
413
  )
414
414
 
415
415
  _select_columns = []
@@ -422,8 +422,8 @@ def get_pipe_data(
422
422
  _select_columns = None
423
423
  if not isinstance(_select_columns, list):
424
424
  raise fastapi.HTTPException(
425
- status_code = 409,
426
- detail = "Selected columns must be a JSON-encoded list."
425
+ status_code=409,
426
+ detail="Selected columns must be a JSON-encoded list."
427
427
  )
428
428
 
429
429
  _omit_columns = []
@@ -436,35 +436,35 @@ def get_pipe_data(
436
436
  _omit_columns = None
437
437
  if _omit_columns is None:
438
438
  raise fastapi.HTTPException(
439
- status_code = 409,
440
- detail = "Omitted columns must be a JSON-encoded list.",
439
+ status_code=409,
440
+ detail="Omitted columns must be a JSON-encoded list.",
441
441
  )
442
442
 
443
443
  pipe = get_pipe(connector_keys, metric_key, location_key)
444
444
  if not is_pipe_registered(pipe, pipes(refresh=True)):
445
445
  raise fastapi.HTTPException(
446
- status_code = 409,
447
- detail = "Pipe must be registered with the datetime column specified."
446
+ status_code=409,
447
+ detail="Pipe must be registered with the datetime column specified."
448
448
  )
449
449
 
450
450
  if pipe.target in ('users', 'plugins', 'pipes'):
451
451
  raise fastapi.HTTPException(
452
- status_code = 409,
453
- detail = f"Cannot retrieve data from protected table '{pipe.target}'.",
452
+ status_code=409,
453
+ detail=f"Cannot retrieve data from protected table '{pipe.target}'.",
454
454
  )
455
455
 
456
456
  df = pipe.get_data(
457
- select_columns = _select_columns,
458
- omit_columns = _omit_columns,
459
- begin = begin,
460
- end = end,
461
- params = _params,
462
- debug = debug,
457
+ select_columns=_select_columns,
458
+ omit_columns=_omit_columns,
459
+ begin=begin,
460
+ end=end,
461
+ params=_params,
462
+ debug=debug,
463
463
  )
464
464
  if df is None:
465
465
  raise fastapi.HTTPException(
466
- status_code = 400,
467
- detail = f"Could not fetch data with the given parameters.",
466
+ status_code=400,
467
+ detail="Could not fetch data with the given parameters.",
468
468
  )
469
469
 
470
470
  ### NaN cannot be JSON-serialized.
@@ -482,16 +482,16 @@ def get_pipe_data(
482
482
 
483
483
  @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/csv', tags=['Pipes'])
484
484
  def get_pipe_csv(
485
- connector_keys: str,
486
- metric_key: str,
487
- location_key: str,
488
- begin: Union[str, int, None] = None,
489
- end: Union[str, int, None] = None,
490
- params: Optional[str] = None,
491
- curr_user = (
492
- fastapi.Depends(manager) if not no_auth else None
493
- ),
494
- ) -> str:
485
+ connector_keys: str,
486
+ metric_key: str,
487
+ location_key: str,
488
+ begin: Union[str, int, None] = None,
489
+ end: Union[str, int, None] = None,
490
+ params: Optional[str] = None,
491
+ curr_user = (
492
+ fastapi.Depends(manager) if not no_auth else None
493
+ ),
494
+ ) -> str:
495
495
  """
496
496
  Get a Pipe's data as a CSV file. Optionally set query boundaries.
497
497
  """
@@ -518,8 +518,8 @@ def get_pipe_csv(
518
518
 
519
519
  if not isinstance(_params, dict):
520
520
  raise fastapi.HTTPException(
521
- status_code = 409,
522
- detail = "Params must be a valid JSON-encoded dictionary.",
521
+ status_code=409,
522
+ detail="Params must be a valid JSON-encoded dictionary.",
523
523
  )
524
524
 
525
525
  p = get_pipe(connector_keys, metric_key, location_key)
@@ -529,7 +529,7 @@ def get_pipe_csv(
529
529
  detail = "Pipe must be registered with the datetime column specified."
530
530
  )
531
531
 
532
- dt_col = pipe.columns.get('datetime', None)
532
+ dt_col = p.columns.get('datetime', None)
533
533
  if dt_col:
534
534
  if begin is None:
535
535
  begin = p.get_sync_time(round_down=False, newest=False)
@@ -552,13 +552,13 @@ def get_pipe_csv(
552
552
 
553
553
  @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/id', tags=['Pipes'])
554
554
  def get_pipe_id(
555
- connector_keys: str,
556
- metric_key: str,
557
- location_key: str,
558
- curr_user = (
559
- fastapi.Depends(manager) if not no_auth else None
560
- ),
561
- ) -> int:
555
+ connector_keys: str,
556
+ metric_key: str,
557
+ location_key: str,
558
+ curr_user = (
559
+ fastapi.Depends(manager) if not no_auth else None
560
+ ),
561
+ ) -> int:
562
562
  """
563
563
  Get a Pipe's ID.
564
564
  """
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.4.7"
5
+ __version__ = "2.4.9"
@@ -346,14 +346,14 @@ def get_pipe_data(
346
346
  try:
347
347
  response = self.get(
348
348
  r_url + "/data",
349
- params = {
349
+ params={
350
350
  'select_columns': json.dumps(select_columns),
351
351
  'omit_columns': json.dumps(omit_columns),
352
352
  'begin': begin,
353
353
  'end': end,
354
- 'params': json.dumps(params)
354
+ 'params': json.dumps(params, default=str)
355
355
  },
356
- debug = debug
356
+ debug=debug
357
357
  )
358
358
  if not response.ok:
359
359
  return None
@@ -272,7 +272,7 @@ class SQLConnector(Connector):
272
272
  """
273
273
  Return whether this connector may be multithreaded.
274
274
  """
275
- if self.flavor == 'duckdb':
275
+ if self.flavor in ('duckdb', 'oracle'):
276
276
  return False
277
277
  if self.flavor == 'sqlite':
278
278
  return ':memory:' not in self.URI
@@ -15,12 +15,12 @@ from meerschaum.utils.warnings import warn
15
15
 
16
16
  _in_memory_temp_tables: Dict[str, bool] = {}
17
17
  def _log_temporary_tables_creation(
18
- self,
19
- tables: Union[str, List[str]],
20
- ready_to_drop: bool = False,
21
- create: bool = True,
22
- debug: bool = False,
23
- ) -> SuccessTuple:
18
+ self,
19
+ tables: Union[str, List[str]],
20
+ ready_to_drop: bool = False,
21
+ create: bool = True,
22
+ debug: bool = False,
23
+ ) -> SuccessTuple:
24
24
  """
25
25
  Log a temporary table's creation for later deletion.
26
26
  """
@@ -58,15 +58,15 @@ def _log_temporary_tables_creation(
58
58
 
59
59
 
60
60
  def _drop_temporary_table(
61
- self,
62
- table: str,
63
- debug: bool = False,
64
- ) -> SuccessTuple:
61
+ self,
62
+ table: str,
63
+ debug: bool = False,
64
+ ) -> SuccessTuple:
65
65
  """
66
66
  Drop a temporary table and clear it from the internal table.
67
67
  """
68
- from meerschaum.utils.sql import sql_item_name, table_exists, SKIP_IF_EXISTS_FLAVORS
69
- if_exists = "IF EXISTS" if self.flavor not in SKIP_IF_EXISTS_FLAVORS else ""
68
+ from meerschaum.utils.sql import sql_item_name, table_exists, DROP_IF_EXISTS_FLAVORS
69
+ if_exists = "IF EXISTS" if self.flavor in DROP_IF_EXISTS_FLAVORS else ""
70
70
  if not if_exists:
71
71
  if not table_exists(table, self, self.internal_schema, debug=debug):
72
72
  return True, "Success"