meerschaum 2.4.11__py3-none-any.whl → 2.4.13__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/_parse_arguments.py +15 -1
- meerschaum/_internal/docs/index.py +1 -0
- meerschaum/_internal/entry.py +1 -0
- meerschaum/_internal/shell/Shell.py +19 -9
- meerschaum/_internal/shell/ShellCompleter.py +11 -6
- meerschaum/actions/bootstrap.py +119 -16
- meerschaum/actions/clear.py +41 -30
- meerschaum/actions/delete.py +1 -1
- meerschaum/actions/edit.py +118 -3
- meerschaum/actions/sh.py +11 -10
- meerschaum/actions/start.py +61 -4
- meerschaum/actions/sync.py +14 -16
- meerschaum/actions/upgrade.py +5 -4
- meerschaum/api/dash/callbacks/dashboard.py +2 -1
- meerschaum/api/dash/callbacks/jobs.py +53 -7
- meerschaum/api/dash/callbacks/pipes.py +1 -1
- meerschaum/api/dash/jobs.py +86 -60
- meerschaum/api/dash/pages/__init__.py +1 -0
- meerschaum/api/dash/pages/job.py +21 -0
- meerschaum/api/routes/_jobs.py +3 -3
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/sql/_fetch.py +67 -61
- meerschaum/connectors/sql/_pipes.py +36 -29
- meerschaum/plugins/__init__.py +6 -2
- meerschaum/plugins/bootstrap.py +15 -15
- meerschaum/utils/formatting/__init__.py +32 -16
- meerschaum/utils/formatting/_pipes.py +1 -1
- meerschaum/utils/formatting/_shell.py +4 -3
- meerschaum/utils/process.py +18 -8
- meerschaum/utils/prompt.py +16 -15
- meerschaum/utils/sql.py +107 -35
- meerschaum/utils/venv/__init__.py +35 -24
- meerschaum/utils/warnings.py +7 -7
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/METADATA +1 -1
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/RECORD +41 -40
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/WHEEL +1 -1
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/LICENSE +0 -0
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/NOTICE +0 -0
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/top_level.txt +0 -0
- {meerschaum-2.4.11.dist-info → meerschaum-2.4.13.dist-info}/zip-safe +0 -0
@@ -7,27 +7,29 @@ Implement the Connector fetch() method
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
|
10
11
|
from datetime import datetime, timedelta
|
11
12
|
import meerschaum as mrsm
|
12
|
-
from meerschaum.utils.typing import Optional, Union, Callable, Any
|
13
|
+
from meerschaum.utils.typing import Optional, Union, Callable, Any, List, Dict
|
14
|
+
|
13
15
|
|
14
16
|
def fetch(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
self,
|
18
|
+
pipe: mrsm.Pipe,
|
19
|
+
begin: Union[datetime, int, str, None] = '',
|
20
|
+
end: Union[datetime, int, str, None] = None,
|
21
|
+
check_existing: bool = True,
|
22
|
+
chunk_hook: Optional[Callable[['pd.DataFrame'], Any]] = None,
|
23
|
+
chunksize: Optional[int] = -1,
|
24
|
+
workers: Optional[int] = None,
|
25
|
+
debug: bool = False,
|
26
|
+
**kw: Any
|
27
|
+
) -> Union['pd.DataFrame', List[Any], None]:
|
26
28
|
"""Execute the SQL definition and return a Pandas DataFrame.
|
27
29
|
|
28
30
|
Parameters
|
29
31
|
----------
|
30
|
-
pipe:
|
32
|
+
pipe: mrsm.Pipe
|
31
33
|
The pipe object which contains the `fetch` metadata.
|
32
34
|
|
33
35
|
- pipe.columns['datetime']: str
|
@@ -63,7 +65,7 @@ def fetch(
|
|
63
65
|
|
64
66
|
debug: bool, default False
|
65
67
|
Verbosity toggle.
|
66
|
-
|
68
|
+
|
67
69
|
Returns
|
68
70
|
-------
|
69
71
|
A pandas DataFrame or `None`.
|
@@ -71,20 +73,20 @@ def fetch(
|
|
71
73
|
"""
|
72
74
|
meta_def = self.get_pipe_metadef(
|
73
75
|
pipe,
|
74
|
-
begin
|
75
|
-
end
|
76
|
-
check_existing
|
77
|
-
debug
|
76
|
+
begin=begin,
|
77
|
+
end=end,
|
78
|
+
check_existing=check_existing,
|
79
|
+
debug=debug,
|
78
80
|
**kw
|
79
81
|
)
|
80
82
|
as_hook_results = chunk_hook is not None
|
81
83
|
chunks = self.read(
|
82
84
|
meta_def,
|
83
|
-
chunk_hook
|
84
|
-
as_hook_results
|
85
|
-
chunksize
|
86
|
-
workers
|
87
|
-
debug
|
85
|
+
chunk_hook=chunk_hook,
|
86
|
+
as_hook_results=as_hook_results,
|
87
|
+
chunksize=chunksize,
|
88
|
+
workers=workers,
|
89
|
+
debug=debug,
|
88
90
|
)
|
89
91
|
### if sqlite, parse for datetimes
|
90
92
|
if not as_hook_results and self.flavor == 'sqlite':
|
@@ -97,8 +99,8 @@ def fetch(
|
|
97
99
|
return (
|
98
100
|
parse_df_datetimes(
|
99
101
|
chunk,
|
100
|
-
ignore_cols
|
101
|
-
debug
|
102
|
+
ignore_cols=ignore_cols,
|
103
|
+
debug=debug,
|
102
104
|
)
|
103
105
|
for chunk in chunks
|
104
106
|
)
|
@@ -106,15 +108,15 @@ def fetch(
|
|
106
108
|
|
107
109
|
|
108
110
|
def get_pipe_metadef(
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
111
|
+
self,
|
112
|
+
pipe: mrsm.Pipe,
|
113
|
+
params: Optional[Dict[str, Any]] = None,
|
114
|
+
begin: Union[datetime, int, str, None] = '',
|
115
|
+
end: Union[datetime, int, str, None] = None,
|
116
|
+
check_existing: bool = True,
|
117
|
+
debug: bool = False,
|
118
|
+
**kw: Any
|
119
|
+
) -> Union[str, None]:
|
118
120
|
"""
|
119
121
|
Return a pipe's meta definition fetch query.
|
120
122
|
|
@@ -173,7 +175,6 @@ def get_pipe_metadef(
|
|
173
175
|
stack = False
|
174
176
|
)
|
175
177
|
|
176
|
-
|
177
178
|
apply_backtrack = begin == '' and check_existing
|
178
179
|
backtrack_interval = pipe.get_backtrack_interval(check_existing=check_existing, debug=debug)
|
179
180
|
btm = (
|
@@ -189,35 +190,34 @@ def get_pipe_metadef(
|
|
189
190
|
|
190
191
|
if begin and end and begin >= end:
|
191
192
|
begin = None
|
192
|
-
|
193
|
-
da = None
|
193
|
+
|
194
194
|
if dt_name:
|
195
195
|
begin_da = (
|
196
196
|
dateadd_str(
|
197
|
-
flavor
|
198
|
-
datepart
|
199
|
-
number
|
200
|
-
begin
|
197
|
+
flavor=self.flavor,
|
198
|
+
datepart='minute',
|
199
|
+
number=((-1 * btm) if apply_backtrack else 0),
|
200
|
+
begin=begin,
|
201
201
|
)
|
202
202
|
if begin
|
203
203
|
else None
|
204
204
|
)
|
205
205
|
end_da = (
|
206
206
|
dateadd_str(
|
207
|
-
flavor
|
208
|
-
datepart
|
209
|
-
number
|
210
|
-
begin
|
207
|
+
flavor=self.flavor,
|
208
|
+
datepart='minute',
|
209
|
+
number=0,
|
210
|
+
begin=end,
|
211
211
|
)
|
212
212
|
if end
|
213
213
|
else None
|
214
214
|
)
|
215
215
|
|
216
216
|
meta_def = (
|
217
|
-
_simple_fetch_query(pipe) if (
|
217
|
+
_simple_fetch_query(pipe, self.flavor) if (
|
218
218
|
(not (pipe.columns or {}).get('id', None))
|
219
219
|
or (not get_config('system', 'experimental', 'join_fetch'))
|
220
|
-
) else _join_fetch_query(pipe, debug=debug, **kw)
|
220
|
+
) else _join_fetch_query(pipe, self.flavor, debug=debug, **kw)
|
221
221
|
)
|
222
222
|
|
223
223
|
has_where = 'where' in meta_def.lower()[meta_def.lower().rfind('definition'):]
|
@@ -300,25 +300,30 @@ def set_pipe_query(pipe: mrsm.Pipe, query: str) -> None:
|
|
300
300
|
dict_to_set[key_to_set] = query
|
301
301
|
|
302
302
|
|
303
|
-
def _simple_fetch_query(
|
303
|
+
def _simple_fetch_query(
|
304
|
+
pipe: mrsm.Pipe,
|
305
|
+
flavor: str,
|
306
|
+
debug: bool = False,
|
307
|
+
**kw
|
308
|
+
) -> str:
|
304
309
|
"""Build a fetch query from a pipe's definition."""
|
305
|
-
|
310
|
+
from meerschaum.utils.sql import format_cte_subquery
|
306
311
|
definition = get_pipe_query(pipe)
|
307
|
-
|
308
|
-
f"
|
309
|
-
|
310
|
-
|
311
|
-
)
|
312
|
+
if definition is None:
|
313
|
+
raise ValueError(f"No SQL definition could be found for {pipe}.")
|
314
|
+
return format_cte_subquery(definition, flavor, 'definition')
|
315
|
+
|
312
316
|
|
313
317
|
def _join_fetch_query(
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
318
|
+
pipe: mrsm.Pipe,
|
319
|
+
flavor: str,
|
320
|
+
debug: bool = False,
|
321
|
+
new_ids: bool = True,
|
322
|
+
**kw
|
323
|
+
) -> str:
|
319
324
|
"""Build a fetch query based on the datetime and ID indices."""
|
320
325
|
if not pipe.exists(debug=debug):
|
321
|
-
return _simple_fetch_query(pipe, debug=debug, **kw)
|
326
|
+
return _simple_fetch_query(pipe, flavor, debug=debug, **kw)
|
322
327
|
|
323
328
|
from meerschaum.utils.sql import sql_item_name, dateadd_str
|
324
329
|
pipe_instance_name = sql_item_name(
|
@@ -350,7 +355,8 @@ def _join_fetch_query(
|
|
350
355
|
"""
|
351
356
|
sync_times = pipe.instance_connector.read(sync_times_query, debug=debug, silent=False)
|
352
357
|
if sync_times is None:
|
353
|
-
return _simple_fetch_query(pipe, debug=debug, **kw)
|
358
|
+
return _simple_fetch_query(pipe, flavor, debug=debug, **kw)
|
359
|
+
|
354
360
|
_sync_times_q = f",\n{sync_times_remote_name} AS ("
|
355
361
|
for _id, _st in sync_times.itertuples(index=False):
|
356
362
|
_sync_times_q += (
|
@@ -2096,7 +2096,7 @@ def get_pipe_rowcount(
|
|
2096
2096
|
An `int` for the number of rows if the `pipe` exists, otherwise `None`.
|
2097
2097
|
|
2098
2098
|
"""
|
2099
|
-
from meerschaum.utils.sql import dateadd_str, sql_item_name,
|
2099
|
+
from meerschaum.utils.sql import dateadd_str, sql_item_name, wrap_query_with_cte
|
2100
2100
|
from meerschaum.connectors.sql._fetch import get_pipe_query
|
2101
2101
|
if remote:
|
2102
2102
|
msg = f"'fetch:definition' must be an attribute of {pipe} to get a remote rowcount."
|
@@ -2175,20 +2175,10 @@ def get_pipe_rowcount(
|
|
2175
2175
|
if not remote
|
2176
2176
|
else get_pipe_query(pipe)
|
2177
2177
|
)
|
2178
|
-
|
2179
|
-
|
2180
|
-
WITH src AS ({src})
|
2181
|
-
SELECT COUNT(*)
|
2182
|
-
FROM src
|
2183
|
-
"""
|
2184
|
-
) if self.flavor not in ('mysql', 'mariadb') else (
|
2185
|
-
f"""
|
2186
|
-
SELECT COUNT(*)
|
2187
|
-
FROM ({src}) AS src
|
2188
|
-
"""
|
2189
|
-
)
|
2178
|
+
parent_query = f"SELECT COUNT(*)\nFROM {sql_item_name('src', self.flavor)}"
|
2179
|
+
query = wrap_query_with_cte(src, parent_query, self.flavor)
|
2190
2180
|
if begin is not None or end is not None:
|
2191
|
-
query += "
|
2181
|
+
query += "\nWHERE"
|
2192
2182
|
if begin is not None:
|
2193
2183
|
query += f"""
|
2194
2184
|
{dt} >= {dateadd_str(self.flavor, datepart='minute', number=0, begin=begin)}
|
@@ -2330,10 +2320,10 @@ def clear_pipe(
|
|
2330
2320
|
|
2331
2321
|
|
2332
2322
|
def get_pipe_table(
|
2333
|
-
|
2334
|
-
|
2335
|
-
|
2336
|
-
|
2323
|
+
self,
|
2324
|
+
pipe: mrsm.Pipe,
|
2325
|
+
debug: bool = False,
|
2326
|
+
) -> Union['sqlalchemy.Table', None]:
|
2337
2327
|
"""
|
2338
2328
|
Return the `sqlalchemy.Table` object for a `mrsm.Pipe`.
|
2339
2329
|
|
@@ -2352,18 +2342,18 @@ def get_pipe_table(
|
|
2352
2342
|
return None
|
2353
2343
|
return get_sqlalchemy_table(
|
2354
2344
|
pipe.target,
|
2355
|
-
connector
|
2356
|
-
schema
|
2357
|
-
debug
|
2358
|
-
refresh
|
2345
|
+
connector=self,
|
2346
|
+
schema=self.get_pipe_schema(pipe),
|
2347
|
+
debug=debug,
|
2348
|
+
refresh=True,
|
2359
2349
|
)
|
2360
2350
|
|
2361
2351
|
|
2362
2352
|
def get_pipe_columns_types(
|
2363
|
-
|
2364
|
-
|
2365
|
-
|
2366
|
-
|
2353
|
+
self,
|
2354
|
+
pipe: mrsm.Pipe,
|
2355
|
+
debug: bool = False,
|
2356
|
+
) -> Dict[str, str]:
|
2367
2357
|
"""
|
2368
2358
|
Get the pipe's columns and types.
|
2369
2359
|
|
@@ -2394,8 +2384,8 @@ def get_pipe_columns_types(
|
|
2394
2384
|
return get_table_cols_types(
|
2395
2385
|
pipe.target,
|
2396
2386
|
self,
|
2397
|
-
flavor
|
2398
|
-
schema
|
2387
|
+
flavor=self.flavor,
|
2388
|
+
schema=self.get_pipe_schema(pipe),
|
2399
2389
|
)
|
2400
2390
|
|
2401
2391
|
table_columns = {}
|
@@ -2448,6 +2438,7 @@ def get_add_columns_queries(
|
|
2448
2438
|
from meerschaum.utils.sql import (
|
2449
2439
|
sql_item_name,
|
2450
2440
|
SINGLE_ALTER_TABLE_FLAVORS,
|
2441
|
+
get_table_cols_types,
|
2451
2442
|
)
|
2452
2443
|
from meerschaum.utils.dtypes.sql import (
|
2453
2444
|
get_pd_type_from_db_type,
|
@@ -2480,6 +2471,14 @@ def get_add_columns_queries(
|
|
2480
2471
|
db_cols_types = {
|
2481
2472
|
col: get_pd_type_from_db_type(str(typ.type))
|
2482
2473
|
for col, typ in table_obj.columns.items()
|
2474
|
+
} if table_obj is not None else {
|
2475
|
+
col: get_pd_type_from_db_type(typ)
|
2476
|
+
for col, typ in get_table_cols_types(
|
2477
|
+
pipe.target,
|
2478
|
+
self,
|
2479
|
+
schema=self.get_pipe_schema(pipe),
|
2480
|
+
debug=debug,
|
2481
|
+
).items()
|
2483
2482
|
}
|
2484
2483
|
new_cols = set(df_cols_types) - set(db_cols_types)
|
2485
2484
|
if not new_cols:
|
@@ -2552,7 +2551,7 @@ def get_alter_columns_queries(
|
|
2552
2551
|
"""
|
2553
2552
|
if not pipe.exists(debug=debug):
|
2554
2553
|
return []
|
2555
|
-
from meerschaum.utils.sql import sql_item_name, DROP_IF_EXISTS_FLAVORS
|
2554
|
+
from meerschaum.utils.sql import sql_item_name, DROP_IF_EXISTS_FLAVORS, get_table_cols_types
|
2556
2555
|
from meerschaum.utils.dataframe import get_numeric_cols
|
2557
2556
|
from meerschaum.utils.dtypes import are_dtypes_equal
|
2558
2557
|
from meerschaum.utils.dtypes.sql import (
|
@@ -2583,6 +2582,14 @@ def get_alter_columns_queries(
|
|
2583
2582
|
db_cols_types = {
|
2584
2583
|
col: get_pd_type_from_db_type(str(typ.type))
|
2585
2584
|
for col, typ in table_obj.columns.items()
|
2585
|
+
} if table_obj is not None else {
|
2586
|
+
col: get_pd_type_from_db_type(typ)
|
2587
|
+
for col, typ in get_table_cols_types(
|
2588
|
+
pipe.target,
|
2589
|
+
self,
|
2590
|
+
schema=self.get_pipe_schema(pipe),
|
2591
|
+
debug=debug,
|
2592
|
+
).items()
|
2586
2593
|
}
|
2587
2594
|
pipe_bool_cols = [col for col, typ in pipe.dtypes.items() if are_dtypes_equal(str(typ), 'bool')]
|
2588
2595
|
pd_db_df_aliases = {
|
meerschaum/plugins/__init__.py
CHANGED
@@ -316,7 +316,11 @@ def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
|
|
316
316
|
|
317
317
|
### NOTE: Allow plugins to be installed via `pip`.
|
318
318
|
packaged_plugin_paths = []
|
319
|
-
|
319
|
+
try:
|
320
|
+
discovered_packaged_plugins_eps = entry_points(group='meerschaum.plugins')
|
321
|
+
except TypeError:
|
322
|
+
discovered_packaged_plugins_eps = []
|
323
|
+
|
320
324
|
for ep in discovered_packaged_plugins_eps:
|
321
325
|
module_name = ep.name
|
322
326
|
for package_file_path in ep.dist.files:
|
@@ -330,7 +334,7 @@ def sync_plugins_symlinks(debug: bool = False, warn: bool = True) -> None:
|
|
330
334
|
if is_symlink(PLUGINS_RESOURCES_PATH) or not PLUGINS_RESOURCES_PATH.exists():
|
331
335
|
try:
|
332
336
|
PLUGINS_RESOURCES_PATH.unlink()
|
333
|
-
except Exception
|
337
|
+
except Exception:
|
334
338
|
pass
|
335
339
|
|
336
340
|
PLUGINS_RESOURCES_PATH.mkdir(exist_ok=True)
|
meerschaum/plugins/bootstrap.py
CHANGED
@@ -47,7 +47,6 @@ IMPORTS_LINES: Dict[str, str] = {
|
|
47
47
|
),
|
48
48
|
}
|
49
49
|
|
50
|
-
### TODO: Add feature for custom connectors.
|
51
50
|
FEATURE_LINES: Dict[str, str] = {
|
52
51
|
'header': (
|
53
52
|
"#! /usr/bin/env python3\n"
|
@@ -94,7 +93,7 @@ FEATURE_LINES: Dict[str, str] = {
|
|
94
93
|
"class {plugin_name_capitalized}Connector(Connector):\n"
|
95
94
|
" \"\"\"Implement '{plugin_name_lower}' connectors.\"\"\"\n\n"
|
96
95
|
" REQUIRED_ATTRIBUTES: list[str] = []\n"
|
97
|
-
"
|
96
|
+
"\n"
|
98
97
|
" def fetch(\n"
|
99
98
|
" self,\n"
|
100
99
|
" pipe: mrsm.Pipe,\n"
|
@@ -149,11 +148,12 @@ FEATURE_LINES: Dict[str, str] = {
|
|
149
148
|
),
|
150
149
|
}
|
151
150
|
|
151
|
+
|
152
152
|
def bootstrap_plugin(
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
153
|
+
plugin_name: str,
|
154
|
+
debug: bool = False,
|
155
|
+
**kwargs: Any
|
156
|
+
) -> SuccessTuple:
|
157
157
|
"""
|
158
158
|
Prompt the user for features and create a plugin file.
|
159
159
|
"""
|
@@ -177,9 +177,9 @@ def bootstrap_plugin(
|
|
177
177
|
features: List[str] = choose(
|
178
178
|
"Which of the following features would you like to add to your plugin?",
|
179
179
|
list(FEATURE_CHOICES.items()),
|
180
|
-
default
|
181
|
-
multiple
|
182
|
-
as_indices
|
180
|
+
default='fetch',
|
181
|
+
multiple=True,
|
182
|
+
as_indices=True,
|
183
183
|
**kwargs
|
184
184
|
)
|
185
185
|
|
@@ -256,7 +256,7 @@ def bootstrap_plugin(
|
|
256
256
|
_ = prompt(
|
257
257
|
f"Press [Enter] to edit plugin '{plugin_name}',"
|
258
258
|
+ " [CTRL+C] to skip.",
|
259
|
-
icon
|
259
|
+
icon=False,
|
260
260
|
)
|
261
261
|
except (KeyboardInterrupt, Exception):
|
262
262
|
return True, "Success"
|
@@ -267,7 +267,7 @@ def bootstrap_plugin(
|
|
267
267
|
|
268
268
|
def _get_plugins_dir_path() -> pathlib.Path:
|
269
269
|
from meerschaum.config.paths import PLUGINS_DIR_PATHS
|
270
|
-
|
270
|
+
|
271
271
|
if not PLUGINS_DIR_PATHS:
|
272
272
|
raise EnvironmentError("No plugin dir path could be found.")
|
273
273
|
|
@@ -278,9 +278,9 @@ def _get_plugins_dir_path() -> pathlib.Path:
|
|
278
278
|
choose(
|
279
279
|
"In which directory do you want to write your plugin?",
|
280
280
|
[path.as_posix() for path in PLUGINS_DIR_PATHS],
|
281
|
-
numeric
|
282
|
-
multiple
|
283
|
-
default
|
281
|
+
numeric=True,
|
282
|
+
multiple=False,
|
283
|
+
default=PLUGINS_DIR_PATHS[0].as_posix(),
|
284
284
|
)
|
285
285
|
)
|
286
286
|
|
@@ -290,7 +290,7 @@ def _ask_to_uninstall(plugin: mrsm.Plugin, **kwargs: Any) -> SuccessTuple:
|
|
290
290
|
warn(f"Plugin '{plugin}' is already installed!", stack=False)
|
291
291
|
uninstall_plugin = yes_no(
|
292
292
|
f"Do you want to first uninstall '{plugin}'?",
|
293
|
-
default
|
293
|
+
default='n',
|
294
294
|
**kwargs
|
295
295
|
)
|
296
296
|
if not uninstall_plugin:
|
@@ -11,7 +11,7 @@ import platform
|
|
11
11
|
import os
|
12
12
|
import sys
|
13
13
|
import meerschaum as mrsm
|
14
|
-
from meerschaum.utils.typing import Optional, Union, Any, Dict
|
14
|
+
from meerschaum.utils.typing import Optional, Union, Any, Dict, Iterable
|
15
15
|
from meerschaum.utils.formatting._shell import make_header
|
16
16
|
from meerschaum.utils.formatting._pprint import pprint
|
17
17
|
from meerschaum.utils.formatting._pipes import (
|
@@ -322,14 +322,14 @@ def format_success_tuple(
|
|
322
322
|
from meerschaum.config import get_config
|
323
323
|
status_config = get_config('formatting', status, patch=True)
|
324
324
|
|
325
|
-
msg = (' ' * left_padding) + status_config[CHARSET]['icon'] + ' ' + str(tup[1])
|
325
|
+
msg = (' ' * left_padding) + status_config[CHARSET]['icon'] + ' ' + str(highlight_pipes(tup[1]))
|
326
326
|
lines = msg.split('\n')
|
327
327
|
lines = [lines[0]] + [
|
328
328
|
((' ' + line if not line.startswith(' ') else line))
|
329
329
|
for line in lines[1:]
|
330
330
|
]
|
331
331
|
if ANSI:
|
332
|
-
lines[0] = fill_ansi(
|
332
|
+
lines[0] = fill_ansi(lines[0], **status_config['ansi']['rich'])
|
333
333
|
|
334
334
|
msg = '\n'.join(lines)
|
335
335
|
msg = ('\n' * upper_padding) + msg + ('\n' * lower_padding)
|
@@ -337,7 +337,7 @@ def format_success_tuple(
|
|
337
337
|
|
338
338
|
|
339
339
|
def print_options(
|
340
|
-
options: Optional[
|
340
|
+
options: Optional[Iterable[Any]] = None,
|
341
341
|
nopretty: bool = False,
|
342
342
|
no_rich: bool = False,
|
343
343
|
name: str = 'options',
|
@@ -345,6 +345,7 @@ def print_options(
|
|
345
345
|
num_cols: Optional[int] = None,
|
346
346
|
adjust_cols: bool = True,
|
347
347
|
sort_options: bool = False,
|
348
|
+
number_options: bool = False,
|
348
349
|
**kw
|
349
350
|
) -> None:
|
350
351
|
"""
|
@@ -373,6 +374,12 @@ def print_options(
|
|
373
374
|
adjust_cols: bool, default True
|
374
375
|
If `True`, adjust the number of columns depending on the terminal size.
|
375
376
|
|
377
|
+
sort_options: bool, default False
|
378
|
+
If `True`, print the options in sorted order.
|
379
|
+
|
380
|
+
number_options: bool, default False
|
381
|
+
If `True`, print the option's number in the list (1 index).
|
382
|
+
|
376
383
|
"""
|
377
384
|
import os
|
378
385
|
from meerschaum.utils.packages import import_rich
|
@@ -398,9 +405,10 @@ def print_options(
|
|
398
405
|
print()
|
399
406
|
print(make_header(_header))
|
400
407
|
### print actions
|
401
|
-
for option in _options:
|
408
|
+
for i, option in enumerate(_options):
|
409
|
+
marker = '-' if not number_options else (str(i + 1) + '.')
|
402
410
|
if not nopretty:
|
403
|
-
print("
|
411
|
+
print(f" {marker} ", end="")
|
404
412
|
print(option)
|
405
413
|
if not nopretty:
|
406
414
|
print()
|
@@ -434,11 +442,11 @@ def print_options(
|
|
434
442
|
|
435
443
|
if _header is not None:
|
436
444
|
table = Table(
|
437
|
-
title
|
438
|
-
box
|
439
|
-
show_header
|
440
|
-
show_footer
|
441
|
-
title_style
|
445
|
+
title=_header,
|
446
|
+
box=box.SIMPLE,
|
447
|
+
show_header=False,
|
448
|
+
show_footer=False,
|
449
|
+
title_style='',
|
442
450
|
expand = True,
|
443
451
|
)
|
444
452
|
else:
|
@@ -447,18 +455,26 @@ def print_options(
|
|
447
455
|
table.add_column()
|
448
456
|
|
449
457
|
if len(_options) < 12:
|
450
|
-
|
451
|
-
for option in _options:
|
452
|
-
|
458
|
+
### If fewer than 12 items, use a single column
|
459
|
+
for i, option in enumerate(_options):
|
460
|
+
item = highlight_pipes(option)
|
461
|
+
if number_options:
|
462
|
+
item = str(i + 1) + '. ' + item
|
463
|
+
table.add_row(Text.from_ansi(item))
|
453
464
|
else:
|
454
|
-
|
465
|
+
### Otherwise, use multiple columns as before
|
455
466
|
num_rows = (len(_options) + num_cols - 1) // num_cols
|
467
|
+
item_ix = 0
|
456
468
|
for i in range(num_rows):
|
457
469
|
row = []
|
458
470
|
for j in range(num_cols):
|
459
471
|
index = i + j * num_rows
|
460
472
|
if index < len(_options):
|
461
|
-
|
473
|
+
item = highlight_pipes(_options[index])
|
474
|
+
if number_options:
|
475
|
+
item = str(i + 1) + '. ' + item
|
476
|
+
row.append(Text.from_ansi(item))
|
477
|
+
item_ix += 1
|
462
478
|
else:
|
463
479
|
row.append('')
|
464
480
|
table.add_row(*row)
|
@@ -10,10 +10,11 @@ from re import sub
|
|
10
10
|
from meerschaum.utils.threading import Lock
|
11
11
|
_locks = {'_tried_clear_command': Lock()}
|
12
12
|
|
13
|
+
|
13
14
|
def make_header(
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
message: str,
|
16
|
+
ruler: str = '─',
|
17
|
+
) -> str:
|
17
18
|
"""Format a message string with a ruler.
|
18
19
|
Length of the ruler is the length of the longest word.
|
19
20
|
|
meerschaum/utils/process.py
CHANGED
@@ -178,22 +178,32 @@ def run_process(
|
|
178
178
|
|
179
179
|
return ret
|
180
180
|
|
181
|
+
|
181
182
|
def poll_process(
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
183
|
+
proc: subprocess.Popen,
|
184
|
+
line_callback: Callable[[bytes], Any],
|
185
|
+
timeout_seconds: Union[int, float, None] = None,
|
186
|
+
timeout_callback: Optional[Callable[[Any], Any]] = None,
|
187
|
+
timeout_callback_args: Optional[Tuple[Any]] = None,
|
188
|
+
timeout_callback_kwargs: Optional[Dict[str, Any]] = None,
|
189
|
+
) -> int:
|
189
190
|
"""
|
190
191
|
Poll a process and execute a callback function for each line printed to the process's `stdout`.
|
191
192
|
"""
|
192
193
|
from meerschaum.utils.threading import Timer
|
194
|
+
from meerschaum.utils.warnings import warn
|
193
195
|
|
194
196
|
def timeout_handler():
|
195
197
|
nonlocal timeout_callback_args, timeout_callback_kwargs
|
196
|
-
|
198
|
+
try:
|
199
|
+
if platform.system() != 'Windows':
|
200
|
+
### The process being killed may have children.
|
201
|
+
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
|
202
|
+
else:
|
203
|
+
proc.send_signal(signal.CTRL_BREAK_EVENT)
|
204
|
+
proc.terminate()
|
205
|
+
except Exception as e:
|
206
|
+
warn(f"Failed to kill process:\n{e}")
|
197
207
|
if timeout_callback_args is None:
|
198
208
|
timeout_callback_args = []
|
199
209
|
if timeout_callback_kwargs is None:
|