meerschaum 2.4.0rc2__py3-none-any.whl → 2.4.2__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.
@@ -336,7 +336,7 @@ def _bootstrap_connectors(
336
336
  return abort_tuple
337
337
  new_attributes['flavor'] = flavor
338
338
  required = sorted(list(connector_attributes[_type]['flavors'][flavor]['requirements']))
339
- required = sorted(list(connector_attributes[_type]['flavors'][flavor]['optional']))
339
+ optional = sorted(list(connector_attributes[_type]['flavors'][flavor].get('optional', {})))
340
340
  default = type_attributes['flavors'][flavor].get('defaults', {})
341
341
  else:
342
342
  required = sorted(list(type_attributes.get('required', {})))
@@ -214,6 +214,13 @@ def update_content(*args):
214
214
  trigger = ctx.triggered[0]['prop_id'].split('.')[0] if not trigger else trigger
215
215
 
216
216
  session_data = args[-1]
217
+ mrsm_location_href = session_data.get('mrsm-location.href', None)
218
+ if (
219
+ initial_load
220
+ and mrsm_location_href
221
+ and not mrsm_location_href.rstrip('/').endswith('/dash')
222
+ ):
223
+ raise PreventUpdate
217
224
 
218
225
  ### NOTE: functions MUST return a list of content and a list of alerts
219
226
  triggers = {
@@ -5,7 +5,9 @@
5
5
  Define callbacks for the `/dash/pipes/` page.
6
6
  """
7
7
 
8
- from dash.dependencies import Input, Output
8
+ from urllib.parse import parse_qs
9
+
10
+ from dash.dependencies import Input, Output, State
9
11
  from dash import no_update
10
12
 
11
13
  import meerschaum as mrsm
@@ -13,17 +15,28 @@ from meerschaum.api.dash import dash_app
13
15
  from meerschaum.api.dash.pipes import build_pipe_card
14
16
  from meerschaum.api import CHECK_UPDATE
15
17
  from meerschaum.utils.packages import import_html, import_dcc
18
+ from meerschaum.api.dash.sessions import is_session_authenticated
19
+ from meerschaum.utils.typing import Optional, Dict, Any
16
20
  html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHECK_UPDATE)
17
21
 
18
22
 
19
23
  @dash_app.callback(
20
24
  Output('pipe-output-div', 'children'),
21
25
  Input('pipes-location', 'pathname'),
26
+ State('pipes-location', 'search'),
27
+ State('session-store', 'data'),
22
28
  )
23
- def render_page_from_url(pathname):
29
+ def render_page_from_url(
30
+ pathname: str,
31
+ pipe_search: str,
32
+ session_data: Optional[Dict[str, Any]],
33
+ ):
24
34
  if not str(pathname).startswith('/dash/pipes'):
25
35
  return no_update
26
36
 
37
+ session_id = (session_data or {}).get('session-id', None)
38
+ authenticated = is_session_authenticated(str(session_id))
39
+
27
40
  keys = pathname.replace('/dash/pipes', '').lstrip('/').rstrip('/').split('/')
28
41
  if len(keys) not in (2, 3):
29
42
  return no_update
@@ -31,11 +44,12 @@ def render_page_from_url(pathname):
31
44
  ck = keys[0]
32
45
  mk = keys[1]
33
46
  lk = keys[2] if len(keys) == 3 else None
47
+ query_params = parse_qs(pipe_search.lstrip('?')) if pipe_search else {}
48
+ instance = query_params.get('instance', [None])[0]
34
49
 
35
- pipe = mrsm.Pipe(ck, mk, lk)
36
- ### TODO Check if logged in
50
+ pipe = mrsm.Pipe(ck, mk, lk, instance=instance)
37
51
  return [
38
52
  html.Br(),
39
- build_pipe_card(pipe, authenticated=False),
53
+ build_pipe_card(pipe, authenticated=authenticated, include_manage=False),
40
54
  html.Br(),
41
55
  ]
@@ -10,6 +10,8 @@ from __future__ import annotations
10
10
  import json
11
11
  import shlex
12
12
  from textwrap import dedent
13
+ from urllib.parse import urlencode
14
+
13
15
  from dash.dependencies import Input, Output, State
14
16
  from meerschaum.utils.typing import List, Optional, Dict, Any, Tuple, Union
15
17
  from meerschaum.utils.misc import string_to_dict
@@ -30,6 +32,7 @@ html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHEC
30
32
  humanfriendly = attempt_import('humanfriendly', check_update=CHECK_UPDATE)
31
33
  pd = import_pandas()
32
34
 
35
+
33
36
  def pipe_from_ctx(ctx, trigger_property: str = 'n_clicks') -> Union[mrsm.Pipe, None]:
34
37
  """
35
38
  Return a `meerschaum.Pipe` object from a dynamic object with an
@@ -105,6 +108,7 @@ def pipes_from_state(
105
108
  def build_pipe_card(
106
109
  pipe: mrsm.Pipe,
107
110
  authenticated: bool = False,
111
+ include_manage: bool = True,
108
112
  _build_children_num: int = 10,
109
113
  ) -> 'dbc.Card':
110
114
  """
@@ -118,6 +122,9 @@ def build_pipe_card(
118
122
  authenticated: bool, default False
119
123
  If `True`, allow editing functionality to the card.
120
124
 
125
+ include_manage: bool, default True
126
+ If `True` and `authenticated` is `True`, include the "Manage" dropdown.
127
+
121
128
  Returns
122
129
  -------
123
130
  A dash bootstrap components Card representation of the pipe.
@@ -184,7 +191,7 @@ def build_pipe_card(
184
191
  size='sm',
185
192
  color='secondary',
186
193
  )
187
- ) if authenticated else [],
194
+ ) if authenticated and include_manage else [],
188
195
  width=2,
189
196
  ),
190
197
  dbc.Col(width=6),
@@ -217,8 +224,16 @@ def build_pipe_card(
217
224
 
218
225
  ]
219
226
 
227
+ query_params = {}
228
+ default_instance = get_config('meerschaum', 'instance')
229
+ if pipe.instance_keys != default_instance:
230
+ query_params['instance'] = pipe.instance_keys
220
231
  pipe_url = (
221
- f"/dash/pipes/{pipe.connector_keys}/{pipe.metric_key}/{pipe.location_key}"
232
+ f"/dash/pipes/"
233
+ + f"{pipe.connector_keys}/"
234
+ + f"{pipe.metric_key}/"
235
+ + (f"{pipe.location_key}" if pipe.location_key is not None else '')
236
+ + (f"?{urlencode(query_params)}" if query_params else "")
222
237
  )
223
238
 
224
239
  card_header_children = dbc.Row(
@@ -79,15 +79,15 @@ def set_config(cf: Dict[str, Any]) -> Dict[str, Any]:
79
79
 
80
80
 
81
81
  def get_config(
82
- *keys: str,
83
- patch: bool = True,
84
- substitute: bool = True,
85
- sync_files: bool = True,
86
- write_missing: bool = True,
87
- as_tuple: bool = False,
88
- warn: bool = True,
89
- debug: bool = False
90
- ) -> Any:
82
+ *keys: str,
83
+ patch: bool = True,
84
+ substitute: bool = True,
85
+ sync_files: bool = True,
86
+ write_missing: bool = True,
87
+ as_tuple: bool = False,
88
+ warn: bool = True,
89
+ debug: bool = False
90
+ ) -> Any:
91
91
  """
92
92
  Return the Meerschaum configuration dictionary.
93
93
  If positional arguments are provided, index by the keys.
@@ -10,12 +10,12 @@ from meerschaum.utils.typing import Optional, Dict, Any, List, Tuple, Union
10
10
  from meerschaum.config import get_config
11
11
 
12
12
  def read_config(
13
- directory: Optional[str] = None,
14
- keys: Optional[List[str]] = None,
15
- write_missing : bool = True,
16
- substitute : bool = True,
17
- with_filenames : bool = False,
18
- ) -> Union[Dict[str, Any], Tuple[Dict[str, Any], List[str]]]:
13
+ directory: Optional[str] = None,
14
+ keys: Optional[List[str]] = None,
15
+ write_missing : bool = True,
16
+ substitute : bool = True,
17
+ with_filenames : bool = False,
18
+ ) -> Union[Dict[str, Any], Tuple[Dict[str, Any], List[str]]]:
19
19
  """
20
20
  Read the configuration directory.
21
21
 
@@ -313,10 +313,10 @@ def search_and_substitute_config(
313
313
  try:
314
314
  valid, value = get_config(
315
315
  *keys,
316
- substitute = False,
317
- as_tuple = True,
318
- write_missing = False,
319
- sync_files = False,
316
+ substitute=False,
317
+ as_tuple=True,
318
+ write_missing=False,
319
+ sync_files=False,
320
320
  )
321
321
  except Exception as e:
322
322
  import traceback
@@ -7,17 +7,20 @@ Synchronize across config files
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+ import pathlib
10
11
  from meerschaum.utils.typing import Optional, List, Tuple
11
12
 
13
+
12
14
  def sync_yaml_configs(
13
- keys: List[str],
14
- sub_path: pathlib.Path,
15
- substitute: bool = True,
16
- permissions: Optional[int] = None,
17
- replace_tuples: Optional[List[Tuple[str, str]]] = None,
18
- ) -> None:
19
- """Synchronize sub-configuration with main configuration file.
20
-
15
+ keys: List[str],
16
+ sub_path: pathlib.Path,
17
+ substitute: bool = True,
18
+ permissions: Optional[int] = None,
19
+ replace_tuples: Optional[List[Tuple[str, str]]] = None,
20
+ ) -> None:
21
+ """
22
+ Synchronize sub-configuration with main configuration file.
23
+
21
24
  Parameters
22
25
  ----------
23
26
  keys: List[str]
@@ -84,12 +87,14 @@ def sync_yaml_configs(
84
87
  new_path = sub_path
85
88
 
86
89
  ### write changes
90
+ new_path.parent.mkdir(exist_ok=True, parents=True)
87
91
  with open(new_path, 'w+', encoding='utf-8') as f:
88
92
  f.write(new_header)
89
93
  f.write(new_config_text)
90
94
  if permissions is not None:
91
95
  os.chmod(new_path, permissions)
92
96
 
97
+
93
98
  def sync_files(keys: Optional[List[str]] = None):
94
99
  if keys is None:
95
100
  keys = []
@@ -110,21 +115,23 @@ def sync_files(keys: Optional[List[str]] = None):
110
115
  sync_yaml_configs(
111
116
  ['stack', STACK_COMPOSE_FILENAME],
112
117
  STACK_COMPOSE_PATH,
113
- substitute = True,
114
- replace_tuples = [('$', '$$'), ('<DOLLAR>', '$')],
118
+ substitute=True,
119
+ replace_tuples=[
120
+ ('$', '$$'),
121
+ ('<DOLLAR>', '$'),
122
+ ],
115
123
  )
116
124
  sync_yaml_configs(
117
125
  ['stack', 'grafana', 'datasource'],
118
126
  GRAFANA_DATASOURCE_PATH,
119
- substitute = True,
127
+ substitute=True,
120
128
  )
121
129
  sync_yaml_configs(
122
130
  ['stack', 'grafana', 'dashboard'],
123
131
  GRAFANA_DASHBOARD_PATH,
124
- substitute = True,
132
+ substitute=True,
125
133
  )
126
134
 
127
-
128
135
  key_functions = {
129
136
  'stack': _stack,
130
137
  }
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.4.0rc2"
5
+ __version__ = "2.4.2"
@@ -55,7 +55,7 @@ env_dict['MEERSCHAUM_API_CONFIG'] = json.dumps(
55
55
  'meerschaum': 'MRSM{!meerschaum}',
56
56
  'system': 'MRSM{!system}',
57
57
  },
58
- indent = 4,
58
+ indent=4,
59
59
  ).replace(
60
60
  '"MRSM{!system}"', 'MRSM{!system}'
61
61
  ).replace(
@@ -168,7 +168,8 @@ class APIConnector(Connector):
168
168
  @property
169
169
  def session(self):
170
170
  if self._session is None:
171
- requests = attempt_import('requests')
171
+ certifi = attempt_import('certifi', lazy=False)
172
+ requests = attempt_import('requests', lazy=False)
172
173
  if requests:
173
174
  self._session = requests.Session()
174
175
  if self._session is None:
@@ -27,39 +27,39 @@ default_create_engine_args = {
27
27
  'connect_args': {},
28
28
  }
29
29
  flavor_configs = {
30
- 'timescaledb' : {
31
- 'engine' : 'postgresql+psycopg',
32
- 'create_engine' : default_create_engine_args,
30
+ 'timescaledb': {
31
+ 'engine': 'postgresql+psycopg',
32
+ 'create_engine': default_create_engine_args,
33
33
  'omit_create_engine': {'method',},
34
- 'to_sql' : {},
35
- 'requirements' : default_requirements,
36
- 'defaults' : {
37
- 'port' : 5432,
34
+ 'to_sql': {},
35
+ 'requirements': default_requirements,
36
+ 'defaults': {
37
+ 'port': 5432,
38
38
  },
39
39
  },
40
- 'postgresql' : {
41
- 'engine' : 'postgresql+psycopg',
42
- 'create_engine' : default_create_engine_args,
40
+ 'postgresql': {
41
+ 'engine': 'postgresql+psycopg',
42
+ 'create_engine': default_create_engine_args,
43
43
  'omit_create_engine': {'method',},
44
- 'to_sql' : {},
45
- 'requirements' : default_requirements,
46
- 'defaults' : {
47
- 'port' : 5432,
44
+ 'to_sql': {},
45
+ 'requirements': default_requirements,
46
+ 'defaults': {
47
+ 'port': 5432,
48
48
  },
49
49
  },
50
- 'citus' : {
51
- 'engine' : 'postgresql+psycopg',
52
- 'create_engine' : default_create_engine_args,
50
+ 'citus': {
51
+ 'engine': 'postgresql+psycopg',
52
+ 'create_engine': default_create_engine_args,
53
53
  'omit_create_engine': {'method',},
54
- 'to_sql' : {},
55
- 'requirements' : default_requirements,
56
- 'defaults' : {
57
- 'port' : 5432,
54
+ 'to_sql': {},
55
+ 'requirements': default_requirements,
56
+ 'defaults': {
57
+ 'port': 5432,
58
58
  },
59
59
  },
60
- 'mssql' : {
61
- 'engine' : 'mssql+pyodbc',
62
- 'create_engine' : {
60
+ 'mssql': {
61
+ 'engine': 'mssql+pyodbc',
62
+ 'create_engine': {
63
63
  'fast_executemany': True,
64
64
  'isolation_level': 'AUTOCOMMIT',
65
65
  'use_setinputsizes': False,
@@ -68,84 +68,81 @@ flavor_configs = {
68
68
  'to_sql': {
69
69
  'method': None,
70
70
  },
71
- 'requirements' : default_requirements,
72
- 'defaults' : {
73
- 'port' : 1433,
74
- 'options' : {
75
- 'driver' : 'ODBC Driver 17 for SQL Server',
76
- 'UseFMTONLY': 'Yes',
77
- },
71
+ 'requirements': default_requirements,
72
+ 'defaults': {
73
+ 'port': 1433,
74
+ 'options': "driver=ODBC Driver 17 for SQL Server&UseFMTONLY=Yes",
78
75
  },
79
76
  },
80
- 'mysql' : {
81
- 'engine' : 'mysql+pymysql',
82
- 'create_engine' : default_create_engine_args,
77
+ 'mysql': {
78
+ 'engine': 'mysql+pymysql',
79
+ 'create_engine': default_create_engine_args,
83
80
  'omit_create_engine': {'method',},
84
81
  'to_sql': {
85
82
  'method': 'multi',
86
83
  },
87
- 'requirements' : default_requirements,
88
- 'defaults' : {
89
- 'port' : 3306,
84
+ 'requirements': default_requirements,
85
+ 'defaults': {
86
+ 'port': 3306,
90
87
  },
91
88
  },
92
- 'mariadb' : {
93
- 'engine' : 'mysql+pymysql',
94
- 'create_engine' : default_create_engine_args,
89
+ 'mariadb': {
90
+ 'engine': 'mysql+pymysql',
91
+ 'create_engine': default_create_engine_args,
95
92
  'omit_create_engine': {'method',},
96
93
  'to_sql': {
97
94
  'method': 'multi',
98
95
  },
99
- 'requirements' : default_requirements,
100
- 'defaults' : {
101
- 'port' : 3306,
96
+ 'requirements': default_requirements,
97
+ 'defaults': {
98
+ 'port': 3306,
102
99
  },
103
100
  },
104
- 'oracle' : {
105
- 'engine' : 'oracle+cx_oracle',
106
- 'create_engine' : default_create_engine_args,
101
+ 'oracle': {
102
+ 'engine': 'oracle+cx_oracle',
103
+ 'create_engine': default_create_engine_args,
107
104
  'omit_create_engine': {'method',},
108
105
  'to_sql': {
109
106
  'method': None,
110
107
  },
111
- 'requirements' : default_requirements,
112
- 'defaults' : {
113
- 'port' : 1521,
108
+ 'requirements': default_requirements,
109
+ 'defaults': {
110
+ 'port': 1521,
114
111
  },
115
112
  },
116
- 'sqlite' : {
117
- 'engine' : 'sqlite',
118
- 'create_engine' : default_create_engine_args,
113
+ 'sqlite': {
114
+ 'engine': 'sqlite',
115
+ 'create_engine': default_create_engine_args,
119
116
  'omit_create_engine': {'method',},
120
117
  'to_sql': {
121
118
  'method': 'multi',
122
119
  },
123
- 'requirements' : {'database'},
124
- 'defaults' : {},
120
+ 'requirements': {'database'},
121
+ 'defaults': {},
125
122
  },
126
- 'duckdb' : {
127
- 'engine' : 'duckdb',
128
- 'create_engine' : {},
123
+ 'duckdb': {
124
+ 'engine': 'duckdb',
125
+ 'create_engine': {},
129
126
  'omit_create_engine': {'ALL',},
130
127
  'to_sql': {
131
128
  'method': 'multi',
132
129
  },
133
- 'requirements' : '',
134
- 'defaults' : {},
130
+ 'requirements': '',
131
+ 'defaults': {},
135
132
  },
136
- 'cockroachdb' : {
137
- 'engine' : 'cockroachdb',
133
+ 'cockroachdb': {
134
+ 'engine': 'cockroachdb',
138
135
  'omit_create_engine': {'method',},
139
136
  'create_engine': default_create_engine_args,
140
137
  'to_sql': {
141
138
  'method': 'multi',
142
139
  },
143
- 'requirements' : {'host'},
144
- 'defaults' : {
145
- 'port' : 26257,
146
- 'database' : 'defaultdb',
147
- 'username' : 'root',
148
- 'password' : 'admin',
140
+ 'requirements': {'host'},
141
+ 'defaults': {
142
+ 'port': 26257,
143
+ 'database': 'defaultdb',
144
+ 'username': 'root',
145
+ 'password': 'admin',
149
146
  },
150
147
  },
151
148
  }
@@ -909,7 +909,7 @@ def get_pipe_data_query(
909
909
 
910
910
  cols_names = [sql_item_name(col, self.flavor, None) for col in select_columns]
911
911
  select_cols_str = (
912
- 'SELECT\n'
912
+ 'SELECT\n '
913
913
  + ',\n '.join(
914
914
  [
915
915
  (
@@ -953,14 +953,14 @@ def get_pipe_data_query(
953
953
  warn(
954
954
  f"No datetime could be determined for {pipe}."
955
955
  + "\n Ignoring begin and end...",
956
- stack = False,
956
+ stack=False,
957
957
  )
958
958
  begin, end = None, None
959
959
  else:
960
960
  warn(
961
961
  f"A datetime wasn't specified for {pipe}.\n"
962
962
  + f" Using column \"{_dt}\" for datetime bounds...",
963
- stack = False,
963
+ stack=False,
964
964
  )
965
965
 
966
966
  is_dt_bound = False
@@ -1014,9 +1014,12 @@ def get_pipe_data_query(
1014
1014
 
1015
1015
  if isinstance(limit, int):
1016
1016
  if self.flavor == 'mssql':
1017
- query = f'SELECT TOP {limit} ' + query[len("SELECT *"):]
1017
+ query = f'SELECT TOP {limit}\n' + query[len("SELECT "):]
1018
1018
  elif self.flavor == 'oracle':
1019
- query = f"SELECT * FROM (\n {query}\n)\nWHERE ROWNUM = 1"
1019
+ query = (
1020
+ f"SELECT * FROM (\n {query}\n)\n"
1021
+ + f"WHERE ROWNUM IN ({', '.join([str(i) for i in range(1, limit+1)])})"
1022
+ )
1020
1023
  else:
1021
1024
  query += f"\nLIMIT {limit}"
1022
1025
 
@@ -106,7 +106,7 @@ def get_data(
106
106
  from meerschaum.connectors import get_connector_plugin
107
107
  from meerschaum.utils.misc import iterate_chunks, items_str
108
108
  from meerschaum.utils.dtypes import to_pandas_dtype
109
- from meerschaum.utils.dataframe import add_missing_cols_to_df
109
+ from meerschaum.utils.dataframe import add_missing_cols_to_df, df_is_chunk_generator
110
110
  from meerschaum.utils.packages import attempt_import
111
111
  dd = attempt_import('dask.dataframe') if as_dask else None
112
112
  dask = attempt_import('dask') if as_dask else None
@@ -122,6 +122,8 @@ def get_data(
122
122
  as_iterator = as_iterator or as_chunks
123
123
 
124
124
  def _sort_df(_df):
125
+ if df_is_chunk_generator(_df):
126
+ return _df
125
127
  dt_col = self.columns.get('datetime', None)
126
128
  indices = [] if dt_col not in _df.columns else [dt_col]
127
129
  non_dt_cols = [
@@ -130,12 +132,19 @@ def get_data(
130
132
  if col_ix != 'datetime' and col in _df.columns
131
133
  ]
132
134
  indices.extend(non_dt_cols)
133
- _df.sort_values(
134
- by=indices,
135
- inplace=True,
136
- ascending=(str(order).lower() == 'asc')
137
- )
138
- _df.reset_index(drop=True, inplace=True)
135
+ if 'dask' not in _df.__module__:
136
+ _df.sort_values(
137
+ by=indices,
138
+ inplace=True,
139
+ ascending=(str(order).lower() == 'asc'),
140
+ )
141
+ _df.reset_index(drop=True, inplace=True)
142
+ else:
143
+ _df = _df.sort_values(
144
+ by=indices,
145
+ ascending=(str(order).lower() == 'asc'),
146
+ )
147
+ _df = _df.reset_index(drop=True)
139
148
  if limit is not None and len(_df) > limit:
140
149
  return _df.head(limit)
141
150
  return _df
@@ -26,8 +26,6 @@ from meerschaum.utils.typing import (
26
26
  SuccessTuple,
27
27
  Dict,
28
28
  List,
29
- Iterable,
30
- Generator,
31
29
  )
32
30
  from meerschaum.utils.warnings import warn, error
33
31
 
@@ -11,11 +11,12 @@ import pathlib
11
11
  import meerschaum as mrsm
12
12
  from meerschaum.utils.typing import Dict, Optional, List, SuccessTuple
13
13
 
14
- from meerschaum.jobs._Job import Job
14
+ from meerschaum.jobs._Job import Job, StopMonitoringLogs
15
15
  from meerschaum.jobs._Executor import Executor
16
16
 
17
17
  __all__ = (
18
18
  'Job',
19
+ 'StopMonitoringLogs',
19
20
  'systemd',
20
21
  'get_jobs',
21
22
  'get_filtered_jobs',