meerschaum 2.8.4__py3-none-any.whl → 2.9.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/api/_chunks.py +67 -0
- meerschaum/api/dash/callbacks/__init__.py +5 -2
- meerschaum/api/dash/callbacks/custom.py +21 -8
- meerschaum/api/dash/callbacks/dashboard.py +26 -4
- meerschaum/api/dash/callbacks/settings/__init__.py +8 -0
- meerschaum/api/dash/callbacks/settings/password_reset.py +76 -0
- meerschaum/api/dash/components.py +136 -25
- meerschaum/api/dash/pages/__init__.py +1 -0
- meerschaum/api/dash/pages/dashboard.py +11 -9
- meerschaum/api/dash/pages/plugins.py +31 -27
- meerschaum/api/dash/pages/settings/__init__.py +8 -0
- meerschaum/api/dash/pages/settings/password_reset.py +63 -0
- meerschaum/api/dash/webterm.py +6 -3
- meerschaum/api/resources/static/css/dash.css +8 -1
- meerschaum/api/resources/templates/termpage.html +4 -0
- meerschaum/api/routes/_pipes.py +232 -79
- meerschaum/config/_default.py +4 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/__init__.py +1 -0
- meerschaum/connectors/api/_APIConnector.py +12 -1
- meerschaum/connectors/api/_pipes.py +106 -45
- meerschaum/connectors/api/_plugins.py +51 -45
- meerschaum/connectors/api/_request.py +1 -1
- meerschaum/connectors/parse.py +1 -2
- meerschaum/connectors/sql/_SQLConnector.py +3 -0
- meerschaum/connectors/sql/_cli.py +1 -0
- meerschaum/connectors/sql/_create_engine.py +51 -4
- meerschaum/connectors/sql/_pipes.py +38 -6
- meerschaum/connectors/sql/_sql.py +35 -4
- meerschaum/connectors/valkey/_ValkeyConnector.py +2 -0
- meerschaum/connectors/valkey/_pipes.py +51 -39
- meerschaum/core/Pipe/__init__.py +1 -0
- meerschaum/core/Pipe/_data.py +1 -2
- meerschaum/core/Pipe/_sync.py +64 -4
- meerschaum/plugins/_Plugin.py +21 -5
- meerschaum/plugins/__init__.py +32 -8
- meerschaum/utils/dataframe.py +139 -2
- meerschaum/utils/dtypes/__init__.py +211 -1
- meerschaum/utils/dtypes/sql.py +296 -5
- meerschaum/utils/formatting/_shell.py +1 -4
- meerschaum/utils/misc.py +1 -1
- meerschaum/utils/packages/_packages.py +7 -1
- meerschaum/utils/sql.py +139 -11
- meerschaum/utils/venv/__init__.py +6 -1
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/METADATA +17 -3
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/RECORD +52 -52
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/WHEEL +1 -1
- meerschaum/_internal/gui/__init__.py +0 -43
- meerschaum/_internal/gui/app/__init__.py +0 -50
- meerschaum/_internal/gui/app/_windows.py +0 -74
- meerschaum/_internal/gui/app/actions.py +0 -30
- meerschaum/_internal/gui/app/pipes.py +0 -47
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.8.4.dist-info → meerschaum-2.9.0.dist-info}/zip-safe +0 -0
@@ -31,6 +31,14 @@ def pipe_r_url(
|
|
31
31
|
)
|
32
32
|
|
33
33
|
|
34
|
+
def get_pipe_instance_keys(self, pipe: mrsm.Pipe) -> Union[str, None]:
|
35
|
+
"""
|
36
|
+
Return the configured instance keys for a pipe if set,
|
37
|
+
else fall back to the default `instance_keys` for this `APIConnector`.
|
38
|
+
"""
|
39
|
+
return pipe.parameters.get('instance_keys', self.instance_keys)
|
40
|
+
|
41
|
+
|
34
42
|
def register_pipe(
|
35
43
|
self,
|
36
44
|
pipe: mrsm.Pipe,
|
@@ -40,13 +48,12 @@ def register_pipe(
|
|
40
48
|
Returns a tuple of (success_bool, response_dict).
|
41
49
|
"""
|
42
50
|
from meerschaum.utils.debug import dprint
|
43
|
-
### NOTE: if `parameters` is supplied in the Pipe constructor,
|
44
|
-
### then `pipe.parameters` will exist and not be fetched from the database.
|
45
51
|
r_url = pipe_r_url(pipe)
|
46
52
|
response = self.post(
|
47
53
|
r_url + '/register',
|
48
|
-
json
|
49
|
-
|
54
|
+
json=pipe._attributes.get('parameters', {}),
|
55
|
+
params={'instance_keys': self.get_pipe_instance_keys(pipe)},
|
56
|
+
debug=debug,
|
50
57
|
)
|
51
58
|
if debug:
|
52
59
|
dprint(response.text)
|
@@ -79,9 +86,9 @@ def edit_pipe(
|
|
79
86
|
r_url = pipe_r_url(pipe)
|
80
87
|
response = self.patch(
|
81
88
|
r_url + '/edit',
|
82
|
-
params
|
83
|
-
json
|
84
|
-
debug
|
89
|
+
params={'patch': patch, 'instance_keys': self.get_pipe_instance_keys(pipe)},
|
90
|
+
json=pipe.parameters,
|
91
|
+
debug=debug,
|
85
92
|
)
|
86
93
|
if debug:
|
87
94
|
dprint(response.text)
|
@@ -149,12 +156,13 @@ def fetch_pipes_keys(
|
|
149
156
|
try:
|
150
157
|
j = self.get(
|
151
158
|
r_url,
|
152
|
-
params
|
159
|
+
params={
|
153
160
|
'connector_keys': json.dumps(connector_keys),
|
154
161
|
'metric_keys': json.dumps(metric_keys),
|
155
162
|
'location_keys': json.dumps(location_keys),
|
156
163
|
'tags': json.dumps(tags),
|
157
164
|
'params': json.dumps(params),
|
165
|
+
'instance_keys': self.instance_keys,
|
158
166
|
},
|
159
167
|
debug=debug
|
160
168
|
).json()
|
@@ -250,8 +258,10 @@ def sync_pipe(
|
|
250
258
|
chunks = (df[i] for i in more_itertools.chunked(df, _chunksize))
|
251
259
|
|
252
260
|
### Send columns in case the user has defined them locally.
|
261
|
+
request_params = kw.copy()
|
253
262
|
if pipe.columns:
|
254
|
-
|
263
|
+
request_params['columns'] = json.dumps(pipe.columns)
|
264
|
+
request_params['instance_keys'] = self.get_pipe_instance_keys(pipe)
|
255
265
|
r_url = pipe_r_url(pipe) + '/data'
|
256
266
|
|
257
267
|
rowcount = 0
|
@@ -268,10 +278,9 @@ def sync_pipe(
|
|
268
278
|
try:
|
269
279
|
response = self.post(
|
270
280
|
r_url,
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
debug = debug
|
281
|
+
params=request_params,
|
282
|
+
data=json_str,
|
283
|
+
debug=debug,
|
275
284
|
)
|
276
285
|
except Exception as e:
|
277
286
|
msg = f"Failed to post a chunk to {pipe}:\n{e}"
|
@@ -323,7 +332,8 @@ def delete_pipe(
|
|
323
332
|
r_url = pipe_r_url(pipe)
|
324
333
|
response = self.delete(
|
325
334
|
r_url + '/delete',
|
326
|
-
|
335
|
+
params={'instance_keys': self.get_pipe_instance_keys(pipe)},
|
336
|
+
debug=debug,
|
327
337
|
)
|
328
338
|
if debug:
|
329
339
|
dprint(response.text)
|
@@ -361,7 +371,9 @@ def get_pipe_data(
|
|
361
371
|
'omit_columns': json.dumps(omit_columns),
|
362
372
|
'begin': begin,
|
363
373
|
'end': end,
|
364
|
-
'params': json.dumps(params, default=str)
|
374
|
+
'params': json.dumps(params, default=str),
|
375
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
376
|
+
'as_chunks': as_chunks,
|
365
377
|
},
|
366
378
|
debug=debug
|
367
379
|
)
|
@@ -402,19 +414,24 @@ def get_pipe_id(
|
|
402
414
|
self,
|
403
415
|
pipe: mrsm.Pipe,
|
404
416
|
debug: bool = False,
|
405
|
-
) -> int:
|
417
|
+
) -> Union[int, str, None]:
|
406
418
|
"""Get a Pipe's ID from the API."""
|
407
419
|
from meerschaum.utils.misc import is_int
|
408
420
|
r_url = pipe_r_url(pipe)
|
409
421
|
response = self.get(
|
410
422
|
r_url + '/id',
|
411
|
-
|
423
|
+
params={
|
424
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
425
|
+
},
|
426
|
+
debug=debug,
|
412
427
|
)
|
413
428
|
if debug:
|
414
429
|
dprint(f"Got pipe ID: {response.text}")
|
415
430
|
try:
|
416
431
|
if is_int(response.text):
|
417
432
|
return int(response.text)
|
433
|
+
if response.text and response.text[0] != '{':
|
434
|
+
return response.text
|
418
435
|
except Exception as e:
|
419
436
|
warn(f"Failed to get the ID for {pipe}:\n{e}")
|
420
437
|
return None
|
@@ -438,7 +455,13 @@ def get_pipe_attributes(
|
|
438
455
|
If the pipe does not exist, return an empty dictionary.
|
439
456
|
"""
|
440
457
|
r_url = pipe_r_url(pipe)
|
441
|
-
response = self.get(
|
458
|
+
response = self.get(
|
459
|
+
r_url + '/attributes',
|
460
|
+
params={
|
461
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
462
|
+
},
|
463
|
+
debug=debug
|
464
|
+
)
|
442
465
|
try:
|
443
466
|
return json.loads(response.text)
|
444
467
|
except Exception as e:
|
@@ -477,9 +500,13 @@ def get_sync_time(
|
|
477
500
|
r_url = pipe_r_url(pipe)
|
478
501
|
response = self.get(
|
479
502
|
r_url + '/sync_time',
|
480
|
-
json
|
481
|
-
params
|
482
|
-
|
503
|
+
json=params,
|
504
|
+
params={
|
505
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
506
|
+
'newest': newest,
|
507
|
+
'debug': debug,
|
508
|
+
},
|
509
|
+
debug=debug,
|
483
510
|
)
|
484
511
|
if not response:
|
485
512
|
warn(f"Failed to get the sync time for {pipe}:\n" + response.text)
|
@@ -520,7 +547,13 @@ def pipe_exists(
|
|
520
547
|
from meerschaum.utils.debug import dprint
|
521
548
|
from meerschaum.utils.warnings import warn
|
522
549
|
r_url = pipe_r_url(pipe)
|
523
|
-
response = self.get(
|
550
|
+
response = self.get(
|
551
|
+
r_url + '/exists',
|
552
|
+
params={
|
553
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
554
|
+
},
|
555
|
+
debug=debug,
|
556
|
+
)
|
524
557
|
if not response:
|
525
558
|
warn(f"Failed to check if {pipe} exists:\n{response.text}")
|
526
559
|
return False
|
@@ -558,8 +591,8 @@ def create_metadata(
|
|
558
591
|
def get_pipe_rowcount(
|
559
592
|
self,
|
560
593
|
pipe: mrsm.Pipe,
|
561
|
-
begin:
|
562
|
-
end:
|
594
|
+
begin: Union[str, datetime, int, None] = None,
|
595
|
+
end: Union[str, datetime, int, None] = None,
|
563
596
|
params: Optional[Dict[str, Any]] = None,
|
564
597
|
remote: bool = False,
|
565
598
|
debug: bool = False,
|
@@ -571,10 +604,10 @@ def get_pipe_rowcount(
|
|
571
604
|
pipe: 'meerschaum.Pipe':
|
572
605
|
The pipe whose row count we are counting.
|
573
606
|
|
574
|
-
begin:
|
607
|
+
begin: Union[str, datetime, int, None], default None
|
575
608
|
If provided, bound the count by this datetime.
|
576
609
|
|
577
|
-
end:
|
610
|
+
end: Union[str, datetime, int, None], default None
|
578
611
|
If provided, bound the count by this datetime.
|
579
612
|
|
580
613
|
params: Optional[Dict[str, Any]], default None
|
@@ -596,6 +629,7 @@ def get_pipe_rowcount(
|
|
596
629
|
'begin': begin,
|
597
630
|
'end': end,
|
598
631
|
'remote': remote,
|
632
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
599
633
|
},
|
600
634
|
debug = debug
|
601
635
|
)
|
@@ -633,7 +667,10 @@ def drop_pipe(
|
|
633
667
|
r_url = pipe_r_url(pipe)
|
634
668
|
response = self.delete(
|
635
669
|
r_url + '/drop',
|
636
|
-
|
670
|
+
params={
|
671
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
672
|
+
},
|
673
|
+
debug=debug,
|
637
674
|
)
|
638
675
|
if debug:
|
639
676
|
dprint(response.text)
|
@@ -656,6 +693,9 @@ def drop_pipe(
|
|
656
693
|
def clear_pipe(
|
657
694
|
self,
|
658
695
|
pipe: mrsm.Pipe,
|
696
|
+
begin: Union[str, datetime, int, None] = None,
|
697
|
+
end: Union[str, datetime, int, None] = None,
|
698
|
+
params: Optional[Dict[str, Any]] = None,
|
659
699
|
debug: bool = False,
|
660
700
|
**kw
|
661
701
|
) -> SuccessTuple:
|
@@ -671,20 +711,33 @@ def clear_pipe(
|
|
671
711
|
-------
|
672
712
|
A success tuple.
|
673
713
|
"""
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
location_keys=pipe.location_key,
|
684
|
-
force=True,
|
714
|
+
r_url = pipe_r_url(pipe)
|
715
|
+
response = self.delete(
|
716
|
+
r_url + '/clear',
|
717
|
+
params={
|
718
|
+
'begin': begin,
|
719
|
+
'end': end,
|
720
|
+
'params': json.dumps(params),
|
721
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
722
|
+
},
|
685
723
|
debug=debug,
|
686
|
-
**kw
|
687
724
|
)
|
725
|
+
if debug:
|
726
|
+
dprint(response.text)
|
727
|
+
|
728
|
+
try:
|
729
|
+
data = response.json()
|
730
|
+
except Exception as e:
|
731
|
+
return False, f"Failed to clear {pipe} with constraints {begin=}, {end=}, {params=}."
|
732
|
+
|
733
|
+
if isinstance(data, list):
|
734
|
+
response_tuple = data[0], data[1]
|
735
|
+
elif 'detail' in response.json():
|
736
|
+
response_tuple = response.__bool__(), data['detail']
|
737
|
+
else:
|
738
|
+
response_tuple = response.__bool__(), response.text
|
739
|
+
|
740
|
+
return response_tuple
|
688
741
|
|
689
742
|
|
690
743
|
def get_pipe_columns_types(
|
@@ -716,7 +769,10 @@ def get_pipe_columns_types(
|
|
716
769
|
r_url = pipe_r_url(pipe) + '/columns/types'
|
717
770
|
response = self.get(
|
718
771
|
r_url,
|
719
|
-
|
772
|
+
params={
|
773
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
774
|
+
},
|
775
|
+
debug=debug,
|
720
776
|
)
|
721
777
|
j = response.json()
|
722
778
|
if isinstance(j, dict) and 'detail' in j and len(j.keys()) == 1:
|
@@ -748,7 +804,10 @@ def get_pipe_columns_indices(
|
|
748
804
|
r_url = pipe_r_url(pipe) + '/columns/indices'
|
749
805
|
response = self.get(
|
750
806
|
r_url,
|
751
|
-
|
807
|
+
params={
|
808
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
809
|
+
},
|
810
|
+
debug=debug,
|
752
811
|
)
|
753
812
|
j = response.json()
|
754
813
|
if isinstance(j, dict) and 'detail' in j and len(j.keys()) == 1:
|
@@ -767,14 +826,16 @@ def get_pipe_index_names(self, pipe: mrsm.Pipe, debug: bool = False) -> Dict[str
|
|
767
826
|
r_url = pipe_r_url(pipe) + '/indices/names'
|
768
827
|
response = self.get(
|
769
828
|
r_url,
|
770
|
-
|
829
|
+
params={
|
830
|
+
'instance': self.get_pipe_instance_keys(pipe),
|
831
|
+
},
|
832
|
+
debug=debug,
|
771
833
|
)
|
772
834
|
j = response.json()
|
773
835
|
if isinstance(j, dict) and 'detail' in j and len(j.keys()) == 1:
|
774
836
|
warn(j['detail'])
|
775
|
-
return
|
837
|
+
return {}
|
776
838
|
if not isinstance(j, dict):
|
777
839
|
warn(response.text)
|
778
|
-
return
|
840
|
+
return {}
|
779
841
|
return j
|
780
|
-
|
@@ -7,21 +7,25 @@ Manage plugins via the API connector
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
-
|
10
|
+
|
11
|
+
import meerschaum as mrsm
|
12
|
+
from meerschaum.utils.typing import Union, Any, Optional, SuccessTuple, List, Dict
|
13
|
+
|
11
14
|
|
12
15
|
def plugin_r_url(
|
13
|
-
|
14
|
-
|
16
|
+
plugin: Union[mrsm.core.Plugin, str],
|
17
|
+
) -> str:
|
15
18
|
"""Generate a relative URL path from a Plugin."""
|
16
19
|
from meerschaum.config.static import STATIC_CONFIG
|
17
20
|
return f"{STATIC_CONFIG['api']['endpoints']['plugins']}/{plugin}"
|
18
21
|
|
22
|
+
|
19
23
|
def register_plugin(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
self,
|
25
|
+
plugin: mrsm.core.Plugin,
|
26
|
+
make_archive: bool = True,
|
27
|
+
debug: bool = False,
|
28
|
+
) -> SuccessTuple:
|
25
29
|
"""Register a plugin and upload its archive."""
|
26
30
|
import json
|
27
31
|
archive_path = plugin.make_tar(debug=debug) if make_archive else plugin.archive_path
|
@@ -34,27 +38,30 @@ def register_plugin(
|
|
34
38
|
r_url = plugin_r_url(plugin)
|
35
39
|
try:
|
36
40
|
response = self.post(r_url, files=files, params=metadata, debug=debug)
|
37
|
-
except Exception
|
41
|
+
except Exception:
|
38
42
|
return False, f"Failed to register plugin '{plugin}'."
|
39
43
|
finally:
|
40
44
|
file_pointer.close()
|
41
45
|
|
42
46
|
try:
|
43
47
|
success, msg = json.loads(response.text)
|
44
|
-
except Exception
|
48
|
+
except Exception:
|
45
49
|
return False, response.text
|
46
50
|
|
47
51
|
return success, msg
|
48
52
|
|
53
|
+
|
49
54
|
def install_plugin(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
55
|
+
self,
|
56
|
+
name: str,
|
57
|
+
skip_deps: bool = False,
|
58
|
+
force: bool = False,
|
59
|
+
debug: bool = False
|
60
|
+
) -> SuccessTuple:
|
56
61
|
"""Download and attempt to install a plugin from the API."""
|
57
|
-
import os
|
62
|
+
import os
|
63
|
+
import pathlib
|
64
|
+
import json
|
58
65
|
from meerschaum.core import Plugin
|
59
66
|
from meerschaum.config._paths import PLUGINS_TEMP_RESOURCES_PATH
|
60
67
|
from meerschaum.utils.debug import dprint
|
@@ -75,41 +82,39 @@ def install_plugin(
|
|
75
82
|
success, msg = tuple(j)
|
76
83
|
elif isinstance(j, dict) and 'detail' in j:
|
77
84
|
success, msg = False, fail_msg
|
78
|
-
except Exception
|
85
|
+
except Exception:
|
79
86
|
success, msg = False, fail_msg
|
80
87
|
return success, msg
|
81
88
|
plugin = Plugin(name, archive_path=archive_path, repo_connector=self)
|
82
89
|
return plugin.install(skip_deps=skip_deps, force=force, debug=debug)
|
83
90
|
|
91
|
+
|
84
92
|
def get_plugins(
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
93
|
+
self,
|
94
|
+
user_id: Optional[int] = None,
|
95
|
+
search_term: Optional[str] = None,
|
96
|
+
debug: bool = False
|
97
|
+
) -> List[str]:
|
90
98
|
"""Return a list of registered plugin names.
|
91
99
|
|
92
100
|
Parameters
|
93
101
|
----------
|
94
|
-
user_id
|
102
|
+
user_id: Optional[int], default None
|
95
103
|
If specified, return all plugins from a certain user.
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
(Default value = None)
|
100
|
-
debug : bool :
|
101
|
-
(Default value = False)
|
104
|
+
|
105
|
+
search_term: Optional[str], default None
|
106
|
+
If specified, return plugins beginning with this string.
|
102
107
|
|
103
108
|
Returns
|
104
109
|
-------
|
105
|
-
|
110
|
+
A list of plugin names.
|
106
111
|
"""
|
107
112
|
import json
|
108
|
-
from meerschaum.utils.warnings import
|
113
|
+
from meerschaum.utils.warnings import error
|
109
114
|
from meerschaum.config.static import STATIC_CONFIG
|
110
115
|
response = self.get(
|
111
116
|
STATIC_CONFIG['api']['endpoints']['plugins'],
|
112
|
-
params = {'user_id'
|
117
|
+
params = {'user_id': user_id, 'search_term': search_term},
|
113
118
|
use_token = True,
|
114
119
|
debug = debug
|
115
120
|
)
|
@@ -120,11 +125,12 @@ def get_plugins(
|
|
120
125
|
error(response.text)
|
121
126
|
return plugins
|
122
127
|
|
128
|
+
|
123
129
|
def get_plugin_attributes(
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
130
|
+
self,
|
131
|
+
plugin: mrsm.core.Plugin,
|
132
|
+
debug: bool = False
|
133
|
+
) -> Dict[str, Any]:
|
128
134
|
"""
|
129
135
|
Return a plugin's attributes.
|
130
136
|
"""
|
@@ -136,7 +142,7 @@ def get_plugin_attributes(
|
|
136
142
|
if isinstance(attributes, str) and attributes and attributes[0] == '{':
|
137
143
|
try:
|
138
144
|
attributes = json.loads(attributes)
|
139
|
-
except Exception
|
145
|
+
except Exception:
|
140
146
|
pass
|
141
147
|
if not isinstance(attributes, dict):
|
142
148
|
error(response.text)
|
@@ -145,23 +151,23 @@ def get_plugin_attributes(
|
|
145
151
|
return {}
|
146
152
|
return attributes
|
147
153
|
|
154
|
+
|
148
155
|
def delete_plugin(
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
156
|
+
self,
|
157
|
+
plugin: mrsm.core.Plugin,
|
158
|
+
debug: bool = False
|
159
|
+
) -> SuccessTuple:
|
153
160
|
"""Delete a plugin from an API repository."""
|
154
161
|
import json
|
155
162
|
r_url = plugin_r_url(plugin)
|
156
163
|
try:
|
157
164
|
response = self.delete(r_url, debug=debug)
|
158
|
-
except Exception
|
165
|
+
except Exception:
|
159
166
|
return False, f"Failed to delete plugin '{plugin}'."
|
160
167
|
|
161
168
|
try:
|
162
169
|
success, msg = json.loads(response.text)
|
163
|
-
except Exception
|
170
|
+
except Exception:
|
164
171
|
return False, response.text
|
165
172
|
|
166
173
|
return success, msg
|
167
|
-
|
meerschaum/connectors/parse.py
CHANGED
@@ -151,6 +151,9 @@ class SQLConnector(Connector):
|
|
151
151
|
if uri.startswith('timescaledb://'):
|
152
152
|
uri = uri.replace('timescaledb://', 'postgresql+psycopg://', 1)
|
153
153
|
flavor = 'timescaledb'
|
154
|
+
if uri.startswith('postgis://'):
|
155
|
+
uri = uri.replace('postgis://', 'postgresql+psycopg://', 1)
|
156
|
+
flavor = 'postgis'
|
154
157
|
kw['uri'] = uri
|
155
158
|
from_uri_params = self.from_uri(kw['uri'], as_dict=True)
|
156
159
|
label = label or from_uri_params.get('label', None)
|
@@ -7,6 +7,7 @@ This module contains the logic that builds the sqlalchemy engine string.
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
import traceback
|
10
|
+
import meerschaum as mrsm
|
10
11
|
from meerschaum.utils.debug import dprint
|
11
12
|
|
12
13
|
### determine driver and requirements from flavor
|
@@ -47,6 +48,16 @@ flavor_configs = {
|
|
47
48
|
'port': 5432,
|
48
49
|
},
|
49
50
|
},
|
51
|
+
'postgis': {
|
52
|
+
'engine': 'postgresql+psycopg',
|
53
|
+
'create_engine': default_create_engine_args,
|
54
|
+
'omit_create_engine': {'method',},
|
55
|
+
'to_sql': {},
|
56
|
+
'requirements': default_requirements,
|
57
|
+
'defaults': {
|
58
|
+
'port': 5432,
|
59
|
+
},
|
60
|
+
},
|
50
61
|
'citus': {
|
51
62
|
'engine': 'postgresql+psycopg',
|
52
63
|
'create_engine': default_create_engine_args,
|
@@ -162,6 +173,7 @@ install_flavor_drivers = {
|
|
162
173
|
'mariadb': ['pymysql'],
|
163
174
|
'timescaledb': ['psycopg'],
|
164
175
|
'postgresql': ['psycopg'],
|
176
|
+
'postgis': ['psycopg', 'geoalchemy'],
|
165
177
|
'citus': ['psycopg'],
|
166
178
|
'cockroachdb': ['psycopg', 'sqlalchemy_cockroachdb', 'sqlalchemy_cockroachdb.psycopg'],
|
167
179
|
'mssql': ['pyodbc'],
|
@@ -198,8 +210,7 @@ def create_engine(
|
|
198
210
|
warn=False,
|
199
211
|
)
|
200
212
|
if self.flavor == 'mssql':
|
201
|
-
|
202
|
-
pyodbc.pooling = False
|
213
|
+
_init_mssql_sqlalchemy()
|
203
214
|
if self.flavor in require_patching_flavors:
|
204
215
|
from meerschaum.utils.packages import determine_version, _monkey_patch_get_distribution
|
205
216
|
import pathlib
|
@@ -257,8 +268,8 @@ def create_engine(
|
|
257
268
|
|
258
269
|
### Sometimes the timescaledb:// flavor can slip in.
|
259
270
|
if _uri and self.flavor in _uri:
|
260
|
-
if self.flavor
|
261
|
-
engine_str = engine_str.replace(
|
271
|
+
if self.flavor in ('timescaledb', 'postgis'):
|
272
|
+
engine_str = engine_str.replace(self.flavor, 'postgresql', 1)
|
262
273
|
elif _uri.startswith('postgresql://'):
|
263
274
|
engine_str = engine_str.replace('postgresql://', 'postgresql+psycopg2://')
|
264
275
|
|
@@ -313,3 +324,39 @@ def create_engine(
|
|
313
324
|
if include_uri:
|
314
325
|
return engine, engine_str
|
315
326
|
return engine
|
327
|
+
|
328
|
+
|
329
|
+
def _init_mssql_sqlalchemy():
|
330
|
+
"""
|
331
|
+
When first instantiating a SQLAlchemy connection to MSSQL,
|
332
|
+
monkey-patch `pyodbc` handling in SQLAlchemy.
|
333
|
+
"""
|
334
|
+
pyodbc, sqlalchemy_dialects_mssql_pyodbc = mrsm.attempt_import(
|
335
|
+
'pyodbc',
|
336
|
+
'sqlalchemy.dialects.mssql.pyodbc',
|
337
|
+
lazy=False,
|
338
|
+
warn=False,
|
339
|
+
)
|
340
|
+
pyodbc.pooling = False
|
341
|
+
|
342
|
+
MSDialect_pyodbc = sqlalchemy_dialects_mssql_pyodbc.MSDialect_pyodbc
|
343
|
+
|
344
|
+
def _handle_geometry(val):
|
345
|
+
from binascii import hexlify
|
346
|
+
hex_str = f"0x{hexlify(val).decode().upper()}"
|
347
|
+
return hex_str
|
348
|
+
|
349
|
+
def custom_on_connect(self):
|
350
|
+
super_ = super(MSDialect_pyodbc, self).on_connect()
|
351
|
+
|
352
|
+
def _on_connect(conn):
|
353
|
+
if super_ is not None:
|
354
|
+
super_(conn)
|
355
|
+
|
356
|
+
self._setup_timestampoffset_type(conn)
|
357
|
+
conn.add_output_converter(-151, _handle_geometry)
|
358
|
+
|
359
|
+
return _on_connect
|
360
|
+
|
361
|
+
### TODO: Parse proprietary MSSQL geometry bytes into WKB.
|
362
|
+
# MSDialect_pyodbc.on_connect = custom_on_connect
|