meerschaum 3.0.0rc1__py3-none-any.whl → 3.0.0rc3__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/_parser.py +2 -1
- meerschaum/_internal/docs/index.py +49 -2
- meerschaum/_internal/shell/Shell.py +5 -4
- meerschaum/_internal/static.py +8 -24
- meerschaum/actions/bootstrap.py +1 -1
- meerschaum/actions/edit.py +6 -3
- meerschaum/actions/start.py +1 -1
- meerschaum/actions/verify.py +5 -8
- meerschaum/api/__init__.py +2 -1
- meerschaum/api/dash/__init__.py +0 -2
- meerschaum/api/dash/callbacks/__init__.py +1 -0
- meerschaum/api/dash/callbacks/dashboard.py +20 -19
- meerschaum/api/dash/callbacks/jobs.py +11 -5
- meerschaum/api/dash/callbacks/pipes.py +106 -5
- meerschaum/api/dash/callbacks/settings/__init__.py +0 -1
- meerschaum/api/dash/callbacks/{settings/tokens.py → tokens.py} +1 -1
- meerschaum/api/dash/jobs.py +1 -1
- meerschaum/api/dash/pages/__init__.py +2 -1
- meerschaum/api/dash/pages/{job.py → jobs.py} +10 -7
- meerschaum/api/dash/pages/pipes.py +4 -3
- meerschaum/api/dash/pages/settings/__init__.py +0 -1
- meerschaum/api/dash/pages/{settings/tokens.py → tokens.py} +6 -8
- meerschaum/api/dash/pipes.py +131 -0
- meerschaum/api/dash/tokens.py +28 -31
- meerschaum/api/routes/_pipes.py +47 -37
- meerschaum/config/_default.py +13 -2
- meerschaum/config/_paths.py +1 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/config/stack/__init__.py +9 -8
- meerschaum/connectors/api/_pipes.py +2 -18
- meerschaum/connectors/api/_tokens.py +2 -2
- meerschaum/connectors/instance/_tokens.py +10 -6
- meerschaum/connectors/sql/_SQLConnector.py +14 -0
- meerschaum/connectors/sql/_create_engine.py +3 -14
- meerschaum/connectors/sql/_pipes.py +175 -185
- meerschaum/connectors/sql/_sql.py +38 -20
- meerschaum/connectors/sql/tables/__init__.py +237 -122
- meerschaum/connectors/valkey/_pipes.py +44 -16
- meerschaum/core/Pipe/__init__.py +28 -5
- meerschaum/core/Pipe/_attributes.py +273 -46
- meerschaum/core/Pipe/_data.py +55 -17
- meerschaum/core/Pipe/_dtypes.py +19 -4
- meerschaum/core/Pipe/_edit.py +2 -0
- meerschaum/core/Pipe/_fetch.py +1 -1
- meerschaum/core/Pipe/_sync.py +90 -160
- meerschaum/core/Pipe/_verify.py +3 -3
- meerschaum/core/Token/_Token.py +4 -5
- meerschaum/plugins/bootstrap.py +508 -3
- meerschaum/utils/_get_pipes.py +1 -1
- meerschaum/utils/dataframe.py +385 -68
- meerschaum/utils/debug.py +15 -15
- meerschaum/utils/dtypes/__init__.py +387 -22
- meerschaum/utils/dtypes/sql.py +327 -31
- meerschaum/utils/misc.py +9 -68
- meerschaum/utils/packages/__init__.py +7 -21
- meerschaum/utils/packages/_packages.py +7 -2
- meerschaum/utils/schedule.py +1 -1
- meerschaum/utils/sql.py +8 -8
- {meerschaum-3.0.0rc1.dist-info → meerschaum-3.0.0rc3.dist-info}/METADATA +5 -17
- {meerschaum-3.0.0rc1.dist-info → meerschaum-3.0.0rc3.dist-info}/RECORD +66 -65
- meerschaum-3.0.0rc3.dist-info/licenses/NOTICE +2 -0
- {meerschaum-3.0.0rc1.dist-info → meerschaum-3.0.0rc3.dist-info}/WHEEL +0 -0
- {meerschaum-3.0.0rc1.dist-info → meerschaum-3.0.0rc3.dist-info}/entry_points.txt +0 -0
- {meerschaum-3.0.0rc1.dist-info → meerschaum-3.0.0rc3.dist-info}/licenses/LICENSE +0 -0
- {meerschaum-3.0.0rc1.dist-info → meerschaum-3.0.0rc3.dist-info}/top_level.txt +0 -0
- {meerschaum-3.0.0rc1.dist-info → meerschaum-3.0.0rc3.dist-info}/zip-safe +0 -0
@@ -131,23 +131,28 @@ def read(
|
|
131
131
|
"""
|
132
132
|
if chunks is not None and chunks <= 0:
|
133
133
|
return []
|
134
|
+
|
134
135
|
from meerschaum.utils.sql import sql_item_name, truncate_item_name
|
135
136
|
from meerschaum.utils.dtypes import are_dtypes_equal, coerce_timezone
|
136
137
|
from meerschaum.utils.dtypes.sql import TIMEZONE_NAIVE_FLAVORS
|
137
138
|
from meerschaum.utils.packages import attempt_import, import_pandas
|
138
139
|
from meerschaum.utils.pool import get_pool
|
139
140
|
from meerschaum.utils.dataframe import chunksize_to_npartitions, get_numeric_cols
|
141
|
+
from meerschaum.utils.misc import filter_arguments
|
140
142
|
import warnings
|
141
143
|
import traceback
|
142
144
|
from decimal import Decimal
|
145
|
+
|
143
146
|
pd = import_pandas()
|
144
147
|
dd = None
|
148
|
+
|
145
149
|
is_dask = 'dask' in pd.__name__
|
146
150
|
pandas = attempt_import('pandas')
|
147
151
|
is_dask = dd is not None
|
148
152
|
npartitions = chunksize_to_npartitions(chunksize)
|
149
153
|
if is_dask:
|
150
154
|
chunksize = None
|
155
|
+
|
151
156
|
schema = schema or self.schema
|
152
157
|
utc_dt_cols = [
|
153
158
|
col
|
@@ -158,7 +163,7 @@ def read(
|
|
158
163
|
if dtype and utc_dt_cols and self.flavor in TIMEZONE_NAIVE_FLAVORS:
|
159
164
|
dtype = dtype.copy()
|
160
165
|
for col in utc_dt_cols:
|
161
|
-
dtype[col] = 'datetime64[
|
166
|
+
dtype[col] = 'datetime64[us]'
|
162
167
|
|
163
168
|
pool = get_pool(workers=workers)
|
164
169
|
sqlalchemy = attempt_import("sqlalchemy", lazy=False)
|
@@ -222,26 +227,33 @@ def read(
|
|
222
227
|
else format_sql_query_for_dask(str_query)
|
223
228
|
)
|
224
229
|
|
230
|
+
def _get_chunk_args_kwargs(_chunk):
|
231
|
+
return filter_arguments(
|
232
|
+
chunk_hook,
|
233
|
+
_chunk,
|
234
|
+
workers=workers,
|
235
|
+
chunksize=chunksize,
|
236
|
+
debug=debug,
|
237
|
+
**kw
|
238
|
+
)
|
239
|
+
|
225
240
|
chunk_list = []
|
226
241
|
chunk_hook_results = []
|
227
242
|
def _process_chunk(_chunk, _retry_on_failure: bool = True):
|
228
243
|
if self.flavor in TIMEZONE_NAIVE_FLAVORS:
|
229
244
|
for col in utc_dt_cols:
|
230
|
-
_chunk[col] = coerce_timezone(_chunk[col],
|
245
|
+
_chunk[col] = coerce_timezone(_chunk[col], strip_utc=False)
|
231
246
|
if not as_hook_results:
|
232
247
|
chunk_list.append(_chunk)
|
248
|
+
|
233
249
|
if chunk_hook is None:
|
234
250
|
return None
|
235
251
|
|
252
|
+
chunk_args, chunk_kwargs = _get_chunk_args_kwargs(_chunk)
|
253
|
+
|
236
254
|
result = None
|
237
255
|
try:
|
238
|
-
result = chunk_hook(
|
239
|
-
_chunk,
|
240
|
-
workers=workers,
|
241
|
-
chunksize=chunksize,
|
242
|
-
debug=debug,
|
243
|
-
**kw
|
244
|
-
)
|
256
|
+
result = chunk_hook(*chunk_args, **chunk_kwargs)
|
245
257
|
except Exception:
|
246
258
|
result = False, traceback.format_exc()
|
247
259
|
from meerschaum.utils.formatting import get_console
|
@@ -292,8 +304,16 @@ def read(
|
|
292
304
|
self.engine,
|
293
305
|
**read_sql_query_kwargs
|
294
306
|
)
|
307
|
+
|
295
308
|
to_return = (
|
296
|
-
|
309
|
+
(
|
310
|
+
chunk_generator
|
311
|
+
if not (as_hook_results or chunksize is None)
|
312
|
+
else (
|
313
|
+
_process_chunk(_chunk)
|
314
|
+
for _chunk in chunk_generator
|
315
|
+
)
|
316
|
+
)
|
297
317
|
if as_iterator or chunksize is None
|
298
318
|
else (
|
299
319
|
list(pool.imap(_process_chunk, chunk_generator))
|
@@ -339,9 +359,8 @@ def read(
|
|
339
359
|
try:
|
340
360
|
for chunk in chunk_generator:
|
341
361
|
if chunk_hook is not None:
|
342
|
-
|
343
|
-
|
344
|
-
)
|
362
|
+
chunk_args, chunk_kwargs = _get_chunk_args_kwargs(chunk)
|
363
|
+
chunk_hook_results.append(chunk_hook(*chunk_args, **chunk_kwargs))
|
345
364
|
chunk_list.append(chunk)
|
346
365
|
read_chunks += 1
|
347
366
|
if chunks is not None and read_chunks >= chunks:
|
@@ -356,9 +375,8 @@ def read(
|
|
356
375
|
try:
|
357
376
|
for chunk in chunk_generator:
|
358
377
|
if chunk_hook is not None:
|
359
|
-
|
360
|
-
|
361
|
-
)
|
378
|
+
chunk_args, chunk_kwargs = _get_chunk_args_kwargs(chunk)
|
379
|
+
chunk_hook_results.append(chunk_hook(*chunk_args, **chunk_kwargs))
|
362
380
|
chunk_list.append(chunk)
|
363
381
|
read_chunks += 1
|
364
382
|
if chunks is not None and read_chunks >= chunks:
|
@@ -389,9 +407,8 @@ def read(
|
|
389
407
|
### call the hook on any missed chunks.
|
390
408
|
if chunk_hook is not None and len(chunk_list) > len(chunk_hook_results):
|
391
409
|
for c in chunk_list[len(chunk_hook_results):]:
|
392
|
-
|
393
|
-
|
394
|
-
)
|
410
|
+
chunk_args, chunk_kwargs = _get_chunk_args_kwargs(c)
|
411
|
+
chunk_hook_results.append(chunk_hook(*chunk_args, **chunk_kwargs))
|
395
412
|
|
396
413
|
### chunksize is not None so must iterate
|
397
414
|
if debug:
|
@@ -784,6 +801,7 @@ def to_sql(
|
|
784
801
|
from meerschaum.utils.warnings import error, warn
|
785
802
|
import warnings
|
786
803
|
import functools
|
804
|
+
import traceback
|
787
805
|
|
788
806
|
if name is None:
|
789
807
|
error(f"Name must not be `None` to insert data into {self}.")
|
@@ -1057,7 +1075,7 @@ def to_sql(
|
|
1057
1075
|
except Exception as e:
|
1058
1076
|
if not silent:
|
1059
1077
|
warn(str(e))
|
1060
|
-
success, msg = False,
|
1078
|
+
success, msg = False, traceback.format_exc()
|
1061
1079
|
|
1062
1080
|
end = time.perf_counter()
|
1063
1081
|
if success:
|
@@ -7,19 +7,24 @@ Define SQLAlchemy tables
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
|
11
|
+
import pickle
|
12
|
+
import threading
|
13
|
+
import meerschaum as mrsm
|
10
14
|
from meerschaum.utils.typing import Optional, Dict, Union, InstanceConnector, List
|
11
|
-
from meerschaum.utils.warnings import error, warn
|
15
|
+
from meerschaum.utils.warnings import error, warn, dprint
|
12
16
|
|
13
17
|
### store a tables dict for each connector
|
14
18
|
connector_tables = {}
|
19
|
+
_tables_locks = {}
|
15
20
|
|
16
21
|
_sequence_flavors = {'duckdb', 'oracle'}
|
17
22
|
_skip_index_names_flavors = {'mssql',}
|
18
23
|
|
19
24
|
def get_tables(
|
20
25
|
mrsm_instance: Optional[Union[str, InstanceConnector]] = None,
|
21
|
-
create: bool =
|
22
|
-
debug:
|
26
|
+
create: Optional[bool] = None,
|
27
|
+
debug: bool = False,
|
23
28
|
) -> Union[Dict[str, 'sqlalchemy.Table'], bool]:
|
24
29
|
"""
|
25
30
|
Create tables on the database and return the `sqlalchemy` tables.
|
@@ -29,10 +34,10 @@ def get_tables(
|
|
29
34
|
mrsm_instance: Optional[Union[str, InstanceConnector]], default None
|
30
35
|
The connector on which the tables reside.
|
31
36
|
|
32
|
-
create: bool, default
|
37
|
+
create: Optional[bool], default None
|
33
38
|
If `True`, create the tables if they don't exist.
|
34
39
|
|
35
|
-
debug:
|
40
|
+
debug: bool, default False
|
36
41
|
Verbosity Toggle.
|
37
42
|
|
38
43
|
Returns
|
@@ -42,7 +47,6 @@ def get_tables(
|
|
42
47
|
|
43
48
|
"""
|
44
49
|
from meerschaum.utils.debug import dprint
|
45
|
-
from meerschaum.utils.formatting import pprint
|
46
50
|
from meerschaum.connectors.parse import parse_instance_keys
|
47
51
|
from meerschaum.utils.packages import attempt_import
|
48
52
|
from meerschaum.utils.sql import json_flavors
|
@@ -54,7 +58,7 @@ def get_tables(
|
|
54
58
|
lazy=False,
|
55
59
|
)
|
56
60
|
if not sqlalchemy:
|
57
|
-
error(
|
61
|
+
error("Failed to import sqlalchemy. Is sqlalchemy installed?")
|
58
62
|
|
59
63
|
if mrsm_instance is None:
|
60
64
|
conn = get_connector(debug=debug)
|
@@ -63,149 +67,196 @@ def get_tables(
|
|
63
67
|
else: ### NOTE: mrsm_instance MUST BE a SQL Connector for this to work!
|
64
68
|
conn = mrsm_instance
|
65
69
|
|
66
|
-
|
67
|
-
|
68
|
-
if isinstance(conn, APIConnector):
|
69
|
-
if create:
|
70
|
-
return conn.create_metadata(debug=debug)
|
71
|
-
return {}
|
70
|
+
cache_expired = _check_create_cache(conn, debug=debug) if conn.type == 'sql' else False
|
71
|
+
create = create or cache_expired
|
72
72
|
|
73
73
|
### Skip if the connector is not a SQL connector.
|
74
74
|
if getattr(conn, 'type', None) != 'sql':
|
75
75
|
return {}
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
conn.
|
105
|
-
|
106
|
-
|
107
|
-
|
77
|
+
conn_key = str(conn)
|
78
|
+
if conn_key in connector_tables:
|
79
|
+
return connector_tables[conn_key]
|
80
|
+
|
81
|
+
fasteners = attempt_import('fasteners')
|
82
|
+
pickle_path = conn.get_metadata_cache_path(kind='pkl')
|
83
|
+
lock_path = pickle_path.with_suffix('.lock')
|
84
|
+
lock = fasteners.InterProcessLock(lock_path)
|
85
|
+
|
86
|
+
with lock:
|
87
|
+
if not cache_expired and pickle_path.exists():
|
88
|
+
try:
|
89
|
+
with open(pickle_path, 'rb') as f:
|
90
|
+
metadata = pickle.load(f)
|
91
|
+
metadata.bind = conn.engine
|
92
|
+
tables = {tbl.name.replace('mrsm_', ''): tbl for tbl in metadata.tables.values()}
|
93
|
+
connector_tables[conn_key] = tables
|
94
|
+
return tables
|
95
|
+
except Exception as e:
|
96
|
+
warn(f"Failed to load metadata from cache, rebuilding: {e}")
|
97
|
+
|
98
|
+
if conn_key not in _tables_locks:
|
99
|
+
_tables_locks[conn_key] = threading.Lock()
|
100
|
+
|
101
|
+
with _tables_locks[conn_key]:
|
102
|
+
if conn_key not in connector_tables:
|
103
|
+
if debug:
|
104
|
+
dprint(f"Building in-memory instance tables for '{conn}'.")
|
105
|
+
|
106
|
+
id_type = sqlalchemy.Integer
|
107
|
+
if conn.flavor in json_flavors:
|
108
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
109
|
+
params_type = JSONB
|
110
|
+
else:
|
111
|
+
params_type = sqlalchemy.types.Text
|
112
|
+
id_names = ('user_id', 'plugin_id', 'pipe_id')
|
113
|
+
sequences = {
|
114
|
+
k: sqlalchemy.Sequence(k + '_seq')
|
115
|
+
for k in id_names
|
116
|
+
}
|
117
|
+
id_col_args = { k: [k, id_type] for k in id_names }
|
118
|
+
id_col_kw = { k: {'primary_key': True} for k in id_names }
|
119
|
+
index_names = conn.flavor not in _skip_index_names_flavors
|
120
|
+
|
121
|
+
if conn.flavor in _sequence_flavors:
|
122
|
+
for k, args in id_col_args.items():
|
123
|
+
args.append(sequences[k])
|
124
|
+
for k, kw in id_col_kw.items():
|
125
|
+
kw.update({'server_default': sequences[k].next_value()})
|
126
|
+
|
127
|
+
_tables = {
|
128
|
+
'users': sqlalchemy.Table(
|
129
|
+
'mrsm_users',
|
130
|
+
conn.metadata,
|
131
|
+
sqlalchemy.Column(
|
132
|
+
*id_col_args['user_id'],
|
133
|
+
**id_col_kw['user_id'],
|
134
|
+
),
|
135
|
+
sqlalchemy.Column(
|
136
|
+
'username',
|
137
|
+
sqlalchemy.String(256),
|
138
|
+
index = index_names,
|
139
|
+
nullable = False,
|
140
|
+
),
|
141
|
+
sqlalchemy.Column('password_hash', sqlalchemy.String(1024)),
|
142
|
+
sqlalchemy.Column('email', sqlalchemy.String(256)),
|
143
|
+
sqlalchemy.Column('user_type', sqlalchemy.String(256)),
|
144
|
+
sqlalchemy.Column('attributes', params_type),
|
145
|
+
extend_existing = True,
|
108
146
|
),
|
109
|
-
sqlalchemy.
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
147
|
+
'plugins': sqlalchemy.Table(
|
148
|
+
*([
|
149
|
+
'mrsm_plugins',
|
150
|
+
conn.metadata,
|
151
|
+
sqlalchemy.Column(
|
152
|
+
*id_col_args['plugin_id'],
|
153
|
+
**id_col_kw['plugin_id'],
|
154
|
+
),
|
155
|
+
sqlalchemy.Column(
|
156
|
+
'plugin_name', sqlalchemy.String(256), index=index_names, nullable=False,
|
157
|
+
),
|
158
|
+
sqlalchemy.Column('user_id', sqlalchemy.Integer, nullable=False),
|
159
|
+
sqlalchemy.Column('version', sqlalchemy.String(256)),
|
160
|
+
sqlalchemy.Column('attributes', params_type),
|
161
|
+
] + ([
|
162
|
+
sqlalchemy.ForeignKeyConstraint(['user_id'], ['mrsm_users.user_id']),
|
163
|
+
] if conn.flavor != 'duckdb' else [])),
|
164
|
+
extend_existing = True,
|
114
165
|
),
|
115
|
-
|
116
|
-
|
117
|
-
sqlalchemy.Column('user_type', sqlalchemy.String(256)),
|
118
|
-
sqlalchemy.Column('attributes', params_type),
|
119
|
-
extend_existing = True,
|
120
|
-
),
|
121
|
-
'plugins': sqlalchemy.Table(
|
122
|
-
*([
|
123
|
-
'mrsm_plugins',
|
166
|
+
'temp_tables': sqlalchemy.Table(
|
167
|
+
'mrsm_temp_tables',
|
124
168
|
conn.metadata,
|
125
169
|
sqlalchemy.Column(
|
126
|
-
|
127
|
-
|
170
|
+
'date_created',
|
171
|
+
sqlalchemy.DateTime,
|
172
|
+
index = True,
|
173
|
+
nullable = False,
|
128
174
|
),
|
129
175
|
sqlalchemy.Column(
|
130
|
-
'
|
176
|
+
'table',
|
177
|
+
sqlalchemy.String(256),
|
178
|
+
index = index_names,
|
179
|
+
nullable = False,
|
131
180
|
),
|
132
|
-
sqlalchemy.Column(
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
181
|
+
sqlalchemy.Column(
|
182
|
+
'ready_to_drop',
|
183
|
+
sqlalchemy.DateTime,
|
184
|
+
index = False,
|
185
|
+
nullable = True,
|
186
|
+
),
|
187
|
+
extend_existing = True,
|
188
|
+
),
|
189
|
+
}
|
190
|
+
|
191
|
+
pipes_parameters_col = sqlalchemy.Column("parameters", params_type)
|
192
|
+
pipes_table_args = [
|
193
|
+
"mrsm_pipes",
|
142
194
|
conn.metadata,
|
143
195
|
sqlalchemy.Column(
|
144
|
-
'
|
145
|
-
|
146
|
-
|
196
|
+
*id_col_args['pipe_id'],
|
197
|
+
**id_col_kw['pipe_id'],
|
198
|
+
),
|
199
|
+
sqlalchemy.Column(
|
200
|
+
"connector_keys",
|
201
|
+
sqlalchemy.String(256),
|
202
|
+
index = index_names,
|
147
203
|
nullable = False,
|
148
204
|
),
|
149
205
|
sqlalchemy.Column(
|
150
|
-
|
206
|
+
"metric_key",
|
151
207
|
sqlalchemy.String(256),
|
152
208
|
index = index_names,
|
153
209
|
nullable = False,
|
154
210
|
),
|
155
211
|
sqlalchemy.Column(
|
156
|
-
|
157
|
-
sqlalchemy.
|
158
|
-
index =
|
212
|
+
"location_key",
|
213
|
+
sqlalchemy.String(256),
|
214
|
+
index = index_names,
|
159
215
|
nullable = True,
|
160
216
|
),
|
217
|
+
pipes_parameters_col,
|
218
|
+
]
|
219
|
+
if conn.flavor in json_flavors:
|
220
|
+
pipes_table_args.append(
|
221
|
+
sqlalchemy.Index(
|
222
|
+
'ix_mrsm_pipes_parameters_tags',
|
223
|
+
pipes_parameters_col['tags'],
|
224
|
+
postgresql_using='gin'
|
225
|
+
)
|
226
|
+
)
|
227
|
+
_tables['pipes'] = sqlalchemy.Table(
|
228
|
+
*pipes_table_args,
|
161
229
|
extend_existing = True,
|
162
|
-
),
|
163
|
-
}
|
164
|
-
|
165
|
-
_tables['pipes'] = sqlalchemy.Table(
|
166
|
-
"mrsm_pipes",
|
167
|
-
conn.metadata,
|
168
|
-
sqlalchemy.Column(
|
169
|
-
*id_col_args['pipe_id'],
|
170
|
-
**id_col_kw['pipe_id'],
|
171
|
-
),
|
172
|
-
sqlalchemy.Column(
|
173
|
-
"connector_keys",
|
174
|
-
sqlalchemy.String(256),
|
175
|
-
index = index_names,
|
176
|
-
nullable = False,
|
177
|
-
),
|
178
|
-
sqlalchemy.Column(
|
179
|
-
"metric_key",
|
180
|
-
sqlalchemy.String(256),
|
181
|
-
index = index_names,
|
182
|
-
nullable = False,
|
183
|
-
),
|
184
|
-
sqlalchemy.Column(
|
185
|
-
"location_key",
|
186
|
-
sqlalchemy.String(256),
|
187
|
-
index = index_names,
|
188
|
-
nullable = True,
|
189
|
-
),
|
190
|
-
sqlalchemy.Column("parameters", params_type),
|
191
|
-
extend_existing = True,
|
192
|
-
)
|
193
|
-
|
194
|
-
### store the table dict for reuse (per connector)
|
195
|
-
connector_tables[conn] = _tables
|
196
|
-
if create:
|
197
|
-
create_schemas(
|
198
|
-
conn,
|
199
|
-
schemas = [conn.internal_schema],
|
200
|
-
debug = debug,
|
201
230
|
)
|
202
|
-
create_tables(conn, tables=_tables)
|
203
231
|
|
204
|
-
|
232
|
+
### store the table dict for reuse (per connector)
|
233
|
+
connector_tables[conn_key] = _tables
|
234
|
+
|
235
|
+
if debug:
|
236
|
+
dprint(f"Built in-memory tables for '{conn}'.")
|
237
|
+
|
238
|
+
if create:
|
239
|
+
if debug:
|
240
|
+
dprint(f"Creating tables for connector '{conn}'.")
|
241
|
+
|
242
|
+
create_schemas(
|
243
|
+
conn,
|
244
|
+
schemas = [conn.internal_schema],
|
245
|
+
debug = debug,
|
246
|
+
)
|
247
|
+
create_tables(conn, tables=_tables)
|
248
|
+
|
249
|
+
_write_create_cache(mrsm.get_connector(str(mrsm_instance)), debug=debug)
|
250
|
+
|
251
|
+
with open(pickle_path, 'wb') as f:
|
252
|
+
pickle.dump(conn.metadata, f)
|
253
|
+
|
254
|
+
connector_tables[conn_key] = _tables
|
255
|
+
return connector_tables[conn_key]
|
205
256
|
|
206
257
|
|
207
258
|
def create_tables(
|
208
|
-
conn:
|
259
|
+
conn: mrsm.connectors.SQLConnector,
|
209
260
|
tables: Optional[Dict[str, 'sqlalchemy.Table']] = None,
|
210
261
|
) -> bool:
|
211
262
|
"""
|
@@ -224,14 +275,13 @@ def create_tables(
|
|
224
275
|
|
225
276
|
|
226
277
|
def create_schemas(
|
227
|
-
conn:
|
278
|
+
conn: mrsm.connectors.SQLConnector,
|
228
279
|
schemas: List[str],
|
229
280
|
debug: bool = False,
|
230
281
|
) -> bool:
|
231
282
|
"""
|
232
283
|
Create the internal Meerschaum schema on the database.
|
233
284
|
"""
|
234
|
-
from meerschaum._internal.static import STATIC_CONFIG
|
235
285
|
from meerschaum.utils.packages import attempt_import
|
236
286
|
from meerschaum.utils.sql import sql_item_name, NO_SCHEMA_FLAVORS, SKIP_IF_EXISTS_FLAVORS
|
237
287
|
if conn.flavor in NO_SCHEMA_FLAVORS:
|
@@ -257,3 +307,68 @@ def create_schemas(
|
|
257
307
|
except Exception as e:
|
258
308
|
warn(f"Failed to create internal schema '{schema}':\n{e}")
|
259
309
|
return all(successes.values())
|
310
|
+
|
311
|
+
|
312
|
+
def _check_create_cache(connector: mrsm.connectors.SQLConnector, debug: bool = False) -> bool:
|
313
|
+
"""
|
314
|
+
Return `True` if the metadata cache is missing or expired.
|
315
|
+
"""
|
316
|
+
import json
|
317
|
+
from datetime import datetime, timedelta
|
318
|
+
from meerschaum.utils.dtypes import get_current_timestamp
|
319
|
+
|
320
|
+
if connector.type != 'sql':
|
321
|
+
return False
|
322
|
+
|
323
|
+
path = connector.get_metadata_cache_path()
|
324
|
+
if not path.exists():
|
325
|
+
if debug:
|
326
|
+
dprint(f"Metadata cache doesn't exist for '{connector}'.")
|
327
|
+
return True
|
328
|
+
|
329
|
+
try:
|
330
|
+
with open(path, 'r', encoding='utf-8') as f:
|
331
|
+
metadata = json.load(f)
|
332
|
+
except Exception:
|
333
|
+
return True
|
334
|
+
|
335
|
+
created_str = metadata.get('created', None)
|
336
|
+
if not created_str:
|
337
|
+
return True
|
338
|
+
|
339
|
+
now = get_current_timestamp()
|
340
|
+
created = datetime.fromisoformat(created_str)
|
341
|
+
|
342
|
+
delta = now - created
|
343
|
+
threshold_minutes = (
|
344
|
+
mrsm.get_config('system', 'connectors', 'sql', 'instance', 'create_metadata_cache_minutes')
|
345
|
+
)
|
346
|
+
threshold_delta = timedelta(minutes=threshold_minutes)
|
347
|
+
if delta >= threshold_delta:
|
348
|
+
if debug:
|
349
|
+
dprint(f"Metadata cache expired for '{connector}'.")
|
350
|
+
return True
|
351
|
+
|
352
|
+
if debug:
|
353
|
+
dprint(f"Using cached metadata for '{connector}'.")
|
354
|
+
|
355
|
+
return False
|
356
|
+
|
357
|
+
|
358
|
+
def _write_create_cache(connector: mrsm.connectors.SQLConnector, debug: bool = False):
|
359
|
+
"""
|
360
|
+
Write the current timestamp to the cache file.
|
361
|
+
"""
|
362
|
+
if connector.type != 'sql':
|
363
|
+
return
|
364
|
+
|
365
|
+
import json
|
366
|
+
from meerschaum.utils.dtypes import get_current_timestamp, json_serialize_value
|
367
|
+
|
368
|
+
if debug:
|
369
|
+
dprint(f"Writing metadata cache for '{connector}'.")
|
370
|
+
|
371
|
+
path = connector.get_metadata_cache_path()
|
372
|
+
now = get_current_timestamp()
|
373
|
+
with open(path, 'w+', encoding='utf-8') as f:
|
374
|
+
json.dump({'created': now}, f, default=json_serialize_value)
|