meerschaum 2.2.1__py3-none-any.whl → 2.2.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.
Files changed (39) hide show
  1. meerschaum/_internal/shell/Shell.py +40 -16
  2. meerschaum/_internal/term/__init__.py +3 -2
  3. meerschaum/_internal/term/tools.py +1 -1
  4. meerschaum/actions/api.py +65 -31
  5. meerschaum/actions/python.py +56 -24
  6. meerschaum/actions/start.py +2 -4
  7. meerschaum/actions/uninstall.py +5 -9
  8. meerschaum/actions/upgrade.py +11 -3
  9. meerschaum/api/__init__.py +1 -0
  10. meerschaum/api/dash/callbacks/__init__.py +4 -0
  11. meerschaum/api/dash/callbacks/custom.py +39 -0
  12. meerschaum/api/dash/callbacks/dashboard.py +39 -6
  13. meerschaum/api/dash/callbacks/login.py +3 -1
  14. meerschaum/api/dash/components.py +5 -2
  15. meerschaum/api/dash/pipes.py +145 -97
  16. meerschaum/config/_default.py +1 -0
  17. meerschaum/config/_paths.py +12 -12
  18. meerschaum/config/_version.py +1 -1
  19. meerschaum/config/paths.py +10 -0
  20. meerschaum/config/static/__init__.py +1 -1
  21. meerschaum/connectors/__init__.py +1 -1
  22. meerschaum/core/Pipe/__init__.py +5 -0
  23. meerschaum/core/Pipe/_sync.py +2 -3
  24. meerschaum/plugins/__init__.py +67 -9
  25. meerschaum/utils/daemon/Daemon.py +7 -2
  26. meerschaum/utils/misc.py +6 -0
  27. meerschaum/utils/packages/__init__.py +212 -53
  28. meerschaum/utils/packages/_packages.py +1 -0
  29. meerschaum/utils/process.py +12 -2
  30. meerschaum/utils/schedule.py +1 -1
  31. meerschaum/utils/venv/__init__.py +46 -11
  32. {meerschaum-2.2.1.dist-info → meerschaum-2.2.2.dist-info}/METADATA +5 -1
  33. {meerschaum-2.2.1.dist-info → meerschaum-2.2.2.dist-info}/RECORD +39 -37
  34. {meerschaum-2.2.1.dist-info → meerschaum-2.2.2.dist-info}/WHEEL +1 -1
  35. {meerschaum-2.2.1.dist-info → meerschaum-2.2.2.dist-info}/LICENSE +0 -0
  36. {meerschaum-2.2.1.dist-info → meerschaum-2.2.2.dist-info}/NOTICE +0 -0
  37. {meerschaum-2.2.1.dist-info → meerschaum-2.2.2.dist-info}/entry_points.txt +0 -0
  38. {meerschaum-2.2.1.dist-info → meerschaum-2.2.2.dist-info}/top_level.txt +0 -0
  39. {meerschaum-2.2.1.dist-info → meerschaum-2.2.2.dist-info}/zip-safe +0 -0
@@ -12,7 +12,7 @@ from meerschaum.utils.packages import attempt_import, import_dcc, import_html
12
12
  from meerschaum.utils.typing import SuccessTuple, List
13
13
  from meerschaum.config.static import STATIC_CONFIG
14
14
  from meerschaum.utils.misc import remove_ansi
15
- from meerschaum.actions import get_shell
15
+ from meerschaum._internal.shell.Shell import get_shell_intro
16
16
  from meerschaum.api import endpoints, CHECK_UPDATE
17
17
  from meerschaum.connectors import instance_types
18
18
  from meerschaum.utils.misc import get_connector_labels
@@ -69,7 +69,10 @@ bottom_buttons_content = dbc.Card(
69
69
  ])
70
70
  )
71
71
  )
72
- console_div = html.Div(id='console-div', children=[html.Pre(get_shell().intro, id='console-pre')])
72
+ console_div = html.Div(
73
+ id = 'console-div',
74
+ children = [html.Pre(get_shell_intro(), id='console-pre')],
75
+ )
73
76
 
74
77
  location = dcc.Location(id='location', refresh=False)
75
78
 
@@ -22,7 +22,7 @@ from meerschaum.api.dash import (
22
22
  dash_app, debug, _get_pipes
23
23
  )
24
24
  from meerschaum.api.dash.connectors import get_web_connector
25
- from meerschaum.api.dash.components import alert_from_success_tuple
25
+ from meerschaum.api.dash.components import alert_from_success_tuple, build_cards_grid
26
26
  from meerschaum.api.dash.users import is_session_authenticated
27
27
  from meerschaum.config import get_config
28
28
  import meerschaum as mrsm
@@ -101,6 +101,132 @@ def pipes_from_state(
101
101
  return False, str(e)
102
102
  return _pipes
103
103
 
104
+
105
+ def build_pipe_card(
106
+ pipe: mrsm.Pipe,
107
+ authenticated: bool = False,
108
+ _build_children_num: int = 10,
109
+ ) -> 'dbc.Card':
110
+ """
111
+ Return a card for the given pipe.
112
+
113
+ Parameters
114
+ ----------
115
+ pipe: mrsm.Pipe
116
+ The pipe from which to build the card.
117
+
118
+ authenticated: bool, default False
119
+ If `True`, allow editing functionality to the card.
120
+
121
+ Returns
122
+ -------
123
+ A dash bootstrap components Card representation of the pipe.
124
+ """
125
+ meta_str = json.dumps(pipe.meta)
126
+ footer_children = dbc.Row(
127
+ [
128
+ dbc.Col(
129
+ (
130
+ dbc.DropdownMenu(
131
+ label = "Manage",
132
+ children = [
133
+ dbc.DropdownMenuItem(
134
+ 'Open in Python',
135
+ id = {
136
+ 'type': 'manage-pipe-button',
137
+ 'index': meta_str,
138
+ 'action': 'python',
139
+ },
140
+ ),
141
+ dbc.DropdownMenuItem(
142
+ 'Delete',
143
+ id = {
144
+ 'type': 'manage-pipe-button',
145
+ 'index': meta_str,
146
+ 'action': 'delete',
147
+ },
148
+ ),
149
+ dbc.DropdownMenuItem(
150
+ 'Drop',
151
+ id = {
152
+ 'type': 'manage-pipe-button',
153
+ 'index': meta_str,
154
+ 'action': 'drop',
155
+ },
156
+ ),
157
+ dbc.DropdownMenuItem(
158
+ 'Clear',
159
+ id = {
160
+ 'type': 'manage-pipe-button',
161
+ 'index': meta_str,
162
+ 'action': 'clear',
163
+ },
164
+ ),
165
+ dbc.DropdownMenuItem(
166
+ 'Verify',
167
+ id = {
168
+ 'type': 'manage-pipe-button',
169
+ 'index': meta_str,
170
+ 'action': 'verify',
171
+ },
172
+ ),
173
+ dbc.DropdownMenuItem(
174
+ 'Sync',
175
+ id = {
176
+ 'type': 'manage-pipe-button',
177
+ 'index': meta_str,
178
+ 'action': 'sync',
179
+ },
180
+ ),
181
+ ],
182
+ direction = "up",
183
+ menu_variant = "dark",
184
+ size = 'sm',
185
+ color = 'secondary',
186
+ )
187
+ ) if authenticated else [],
188
+ width = 2,
189
+ ),
190
+ dbc.Col(width=6),
191
+ dbc.Col(
192
+ dbc.Button(
193
+ 'Download CSV',
194
+ size = 'sm',
195
+ color = 'link',
196
+ style = {'float': 'right'},
197
+ id = {'type': 'pipe-download-csv-button', 'index': meta_str},
198
+ ),
199
+ width = 4,
200
+ ),
201
+ ],
202
+ justify = 'start',
203
+ )
204
+ card_body_children = [
205
+ html.H5(
206
+ html.B(str(pipe)),
207
+ className = 'card-title',
208
+ style = {'font-family': ['monospace']}
209
+ ),
210
+ html.Div(
211
+ dbc.Accordion(
212
+ accordion_items_from_pipe(
213
+ pipe,
214
+ authenticated = authenticated,
215
+ _build_children_num = _build_children_num,
216
+ ),
217
+ flush = True,
218
+ start_collapsed = True,
219
+ id = {'type': 'pipe-accordion', 'index': meta_str},
220
+ )
221
+ )
222
+
223
+ ]
224
+ return dbc.Card([
225
+ dbc.CardBody(children=card_body_children),
226
+ dbc.CardFooter(children=footer_children),
227
+ ])
228
+
229
+
104
230
  def get_pipes_cards(*keys, session_data: Optional[Dict[str, Any]] = None):
105
231
  """
106
232
  Returns a tuple:
@@ -120,99 +246,7 @@ def get_pipes_cards(*keys, session_data: Optional[Dict[str, Any]] = None):
120
246
  overflow_pipes = pipes[max_num_pipes_cards:]
121
247
 
122
248
  for pipe in pipes[:max_num_pipes_cards]:
123
- meta_str = json.dumps(pipe.meta)
124
- footer_children = dbc.Row(
125
- [
126
- dbc.Col(
127
- (
128
- dbc.DropdownMenu(
129
- label = "Manage",
130
- children = [
131
- dbc.DropdownMenuItem(
132
- 'Delete',
133
- id = {
134
- 'type': 'manage-pipe-button',
135
- 'index': meta_str,
136
- 'action': 'delete',
137
- },
138
- ),
139
- dbc.DropdownMenuItem(
140
- 'Drop',
141
- id = {
142
- 'type': 'manage-pipe-button',
143
- 'index': meta_str,
144
- 'action': 'drop',
145
- },
146
- ),
147
- dbc.DropdownMenuItem(
148
- 'Clear',
149
- id = {
150
- 'type': 'manage-pipe-button',
151
- 'index': meta_str,
152
- 'action': 'clear',
153
- },
154
- ),
155
- dbc.DropdownMenuItem(
156
- 'Verify',
157
- id = {
158
- 'type': 'manage-pipe-button',
159
- 'index': meta_str,
160
- 'action': 'verify',
161
- },
162
- ),
163
- dbc.DropdownMenuItem(
164
- 'Sync',
165
- id = {
166
- 'type': 'manage-pipe-button',
167
- 'index': meta_str,
168
- 'action': 'sync',
169
- },
170
- ),
171
- ],
172
- direction = "up",
173
- menu_variant = "dark",
174
- size = 'sm',
175
- color = 'secondary',
176
- )
177
- ) if authenticated else [],
178
- width = 2,
179
- ),
180
- dbc.Col(width=6),
181
- dbc.Col(
182
- dbc.Button(
183
- 'Download CSV',
184
- size = 'sm',
185
- color = 'link',
186
- style = {'float': 'right'},
187
- id = {'type': 'pipe-download-csv-button', 'index': meta_str},
188
- ),
189
- width = 4,
190
- ),
191
- ],
192
- justify = 'start',
193
- )
194
- card_body_children = [
195
- html.H5(
196
- html.B(str(pipe)),
197
- className = 'card-title',
198
- style = {'font-family': ['monospace']}
199
- ),
200
- html.Div(
201
- dbc.Accordion(
202
- accordion_items_from_pipe(pipe, authenticated=authenticated),
203
- flush = True,
204
- start_collapsed = True,
205
- id = {'type': 'pipe-accordion', 'index': meta_str},
206
- )
207
- )
208
-
209
- ]
210
- cards.append(
211
- dbc.Card([
212
- dbc.CardBody(children=card_body_children),
213
- dbc.CardFooter(children=footer_children),
214
- ])
215
- )
249
+ cards.append(build_pipe_card(pipe, authenticated=authenticated))
216
250
 
217
251
  if overflow_pipes:
218
252
  cards.append(
@@ -239,7 +273,8 @@ def accordion_items_from_pipe(
239
273
  pipe: mrsm.Pipe,
240
274
  active_items: Optional[List[str]] = None,
241
275
  authenticated: bool = False,
242
- ) -> List[dbc.AccordionItem]:
276
+ _build_children_num: int = 10,
277
+ ) -> 'List[dbc.AccordionItem]':
243
278
  """
244
279
  Build the accordion items for a given pipe.
245
280
  """
@@ -401,7 +436,7 @@ def accordion_items_from_pipe(
401
436
  size = 'sm',
402
437
  style = {'text-decoration': 'none', 'margin-left': '10px'},
403
438
  )
404
- items_bodies['parameters'] = html.Div([
439
+ parameters_div_children = [
405
440
  parameters_editor,
406
441
  html.Br(),
407
442
  dbc.Row([
@@ -422,8 +457,21 @@ def accordion_items_from_pipe(
422
457
  width=True,
423
458
  )
424
459
  ]),
460
+ ]
461
+ if _build_children_num > 0 and pipe.children:
462
+ children_cards = [
463
+ build_pipe_card(
464
+ child_pipe,
465
+ authenticated = authenticated,
466
+ _build_children_num = (_build_children_num - 1),
467
+ )
468
+ for child_pipe in pipe.children
469
+ ]
470
+ children_grid = build_cards_grid(children_cards, num_columns=1)
471
+ chidren_div_items = [html.Br(), html.H3('Children Pipes'), html.Br(), children_grid]
472
+ parameters_div_children.extend([html.Br()] + chidren_div_items)
425
473
 
426
- ])
474
+ items_bodies['parameters'] = html.Div(parameters_div_children)
427
475
 
428
476
  if 'sql' in active_items:
429
477
  query = dedent((get_pipe_query(pipe, warn=False) or "")).lstrip().rstrip()
@@ -110,6 +110,7 @@ default_system_config = {
110
110
  'space': False,
111
111
  'join_fetch': False,
112
112
  'inplace_sync': True,
113
+ 'uv_pip': True,
113
114
  },
114
115
  }
115
116
  default_pipes_config = {
@@ -82,24 +82,24 @@ for _plugin_path in _plugins_paths_to_remove:
82
82
 
83
83
  ENVIRONMENT_VENVS_DIR = STATIC_CONFIG['environment']['venvs']
84
84
  if ENVIRONMENT_VENVS_DIR in os.environ:
85
- VENVS_DIR_PATH = Path(os.environ[ENVIRONMENT_VENVS_DIR]).resolve()
86
- if not VENVS_DIR_PATH.exists():
85
+ _VENVS_DIR_PATH = Path(os.environ[ENVIRONMENT_VENVS_DIR]).resolve()
86
+ if not _VENVS_DIR_PATH.exists():
87
87
  try:
88
- VENVS_DIR_PATH.mkdir(parents=True, exist_ok=True)
88
+ _VENVS_DIR_PATH.mkdir(parents=True, exist_ok=True)
89
89
  except Exception as e:
90
90
  print(
91
91
  f"Invalid path set for environment variable '{ENVIRONMENT_VENVS_DIR}':\n"
92
- + f"{VENVS_DIR_PATH}"
92
+ + f"{_VENVS_DIR_PATH}"
93
93
  )
94
- VENVS_DIR_PATH = (_ROOT_DIR_PATH / 'venvs').resolve()
95
- print(f"Will use the following path for venvs instead:\n{VENVS_DIR_PATH}")
94
+ _VENVS_DIR_PATH = (_ROOT_DIR_PATH / 'venvs').resolve()
95
+ print(f"Will use the following path for venvs instead:\n{_VENVS_DIR_PATH}")
96
96
  else:
97
- VENVS_DIR_PATH = _ROOT_DIR_PATH / 'venvs'
97
+ _VENVS_DIR_PATH = _ROOT_DIR_PATH / 'venvs'
98
98
 
99
99
  paths = {
100
- 'PACKAGE_ROOT_PATH' : str(Path(__file__).parent.parent.resolve()),
101
- 'ROOT_DIR_PATH' : str(_ROOT_DIR_PATH),
102
- 'VIRTENV_RESOURCES_PATH' : str(VENVS_DIR_PATH),
100
+ 'PACKAGE_ROOT_PATH' : Path(__file__).parent.parent.resolve().as_posix(),
101
+ 'ROOT_DIR_PATH' : _ROOT_DIR_PATH.as_posix(),
102
+ 'VIRTENV_RESOURCES_PATH' : _VENVS_DIR_PATH.as_posix(),
103
103
  'CONFIG_DIR_PATH' : ('{ROOT_DIR_PATH}', 'config'),
104
104
  'DEFAULT_CONFIG_DIR_PATH' : ('{ROOT_DIR_PATH}', 'default_config'),
105
105
  'PATCH_DIR_PATH' : ('{ROOT_DIR_PATH}', 'patch_config'),
@@ -114,6 +114,7 @@ paths = {
114
114
 
115
115
  'SHELL_RESOURCES_PATH' : ('{ROOT_DIR_PATH}', ),
116
116
  'SHELL_HISTORY_PATH' : ('{SHELL_RESOURCES_PATH}', '.mrsm_history'),
117
+ 'PYTHON_RESOURCES_PATH' : ('{INTERNAL_RESOURCES_PATH}', 'python'),
117
118
 
118
119
  'API_RESOURCES_PATH' : ('{PACKAGE_ROOT_PATH}', 'api', 'resources'),
119
120
  'API_STATIC_PATH' : ('{API_RESOURCES_PATH}', 'static'),
@@ -186,7 +187,6 @@ def __getattr__(name: str) -> Path:
186
187
  if name.endswith('RESOURCES_PATH') or name == 'CONFIG_DIR_PATH':
187
188
  path.mkdir(parents=True, exist_ok=True)
188
189
  elif 'FILENAME' in name:
189
- path = str(path)
190
+ path = path.as_posix()
190
191
 
191
192
  return path
192
-
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.2.1"
5
+ __version__ = "2.2.2"
@@ -0,0 +1,10 @@
1
+ #! /usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # vim:fenc=utf-8
4
+
5
+ """
6
+ External API for importing Meerschaum paths.
7
+ """
8
+
9
+ from meerschaum.config._paths import __getattr__, paths
10
+ __all__ = tuple(paths.keys())
@@ -110,7 +110,7 @@ STATIC_CONFIG: Dict[str, Any] = {
110
110
  'pbkdf2_sha256',
111
111
  ],
112
112
  'default': 'pbkdf2_sha256',
113
- 'pbkdf2_sha256__default_rounds': 3_000_000,
113
+ 'pbkdf2_sha256__default_rounds': 1_000_000,
114
114
  },
115
115
  'min_username_length': 1,
116
116
  'max_username_length': 26,
@@ -328,7 +328,7 @@ def load_plugin_connectors():
328
328
  continue
329
329
  with open(plugin.__file__, encoding='utf-8') as f:
330
330
  text = f.read()
331
- if 'make_connector' in text:
331
+ if 'make_connector' in text or 'Connector' in text:
332
332
  to_import.append(plugin.name)
333
333
  if not to_import:
334
334
  return
@@ -436,6 +436,11 @@ class Pipe:
436
436
  def __repr__(self, **kw) -> str:
437
437
  return pipe_repr(self, **kw)
438
438
 
439
+ def __pt_repr__(self):
440
+ from meerschaum.utils.packages import attempt_import
441
+ prompt_toolkit_formatted_text = attempt_import('prompt_toolkit.formatted_text', lazy=False)
442
+ return prompt_toolkit_formatted_text.ANSI(self.__repr__())
443
+
439
444
  def __getstate__(self) -> Dict[str, Any]:
440
445
  """
441
446
  Define the state dictionary (pickling).
@@ -215,9 +215,8 @@ def sync(
215
215
 
216
216
  ### Activate and invoke `sync(pipe)` for plugin connectors with `sync` methods.
217
217
  try:
218
- if p.connector.type == 'plugin' and p.connector.sync is not None:
219
- connector_plugin = Plugin(p.connector.label)
220
- with Venv(connector_plugin, debug=debug):
218
+ if getattr(p.connector, 'sync', None) is not None:
219
+ with Venv(get_connector_plugin(p.connector), debug=debug):
221
220
  return_tuple = p.connector.sync(p, debug=debug, **kw)
222
221
  p._exists = None
223
222
  if not isinstance(return_tuple, tuple):
@@ -7,6 +7,7 @@ Expose plugin management APIs from the `meerschaum.plugins` module.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+ import functools
10
11
  from meerschaum.utils.typing import Callable, Any, Union, Optional, Dict, List, Tuple
11
12
  from meerschaum.utils.threading import Lock, RLock
12
13
  from meerschaum.plugins._Plugin import Plugin
@@ -16,6 +17,7 @@ _pre_sync_hooks: Dict[str, List[Callable[[Any], Any]]] = {}
16
17
  _post_sync_hooks: Dict[str, List[Callable[[Any], Any]]] = {}
17
18
  _locks = {
18
19
  '_api_plugins': RLock(),
20
+ '_dash_plugins': RLock(),
19
21
  '_pre_sync_hooks': RLock(),
20
22
  '_post_sync_hooks': RLock(),
21
23
  '__path__': RLock(),
@@ -156,6 +158,71 @@ def post_sync_hook(
156
158
  return function
157
159
 
158
160
 
161
+ _plugin_endpoints_to_pages = {}
162
+ def web_page(
163
+ page: Union[str, None, Callable[[Any], Any]] = None,
164
+ login_required: bool = True,
165
+ **kwargs
166
+ ) -> Any:
167
+ """
168
+ Quickly add pages to the dash application.
169
+
170
+ Examples
171
+ --------
172
+ >>> import meerschaum as mrsm
173
+ >>> from meerschaum.plugins import web_page
174
+ >>> html = mrsm.attempt_import('dash.html')
175
+ >>>
176
+ >>> @web_page('foo/bar', login_required=False)
177
+ >>> def foo_bar():
178
+ ... return html.Div([html.H1("Hello, World!")])
179
+ >>>
180
+ """
181
+ page_str = None
182
+
183
+ def _decorator(_func: Callable[[Any], Any]) -> Callable[[Any], Any]:
184
+ nonlocal page_str
185
+
186
+ @functools.wraps(_func)
187
+ def wrapper(*_args, **_kwargs):
188
+ return _func(*_args, **_kwargs)
189
+
190
+ if page_str is None:
191
+ page_str = _func.__name__
192
+
193
+ page_str = page_str.lstrip('/').rstrip('/').strip()
194
+ _plugin_endpoints_to_pages[page_str] = {
195
+ 'function': _func,
196
+ 'login_required': login_required,
197
+ }
198
+ return wrapper
199
+
200
+ if callable(page):
201
+ decorator_to_return = _decorator(page)
202
+ page_str = page.__name__
203
+ else:
204
+ decorator_to_return = _decorator
205
+ page_str = page
206
+
207
+ return decorator_to_return
208
+
209
+
210
+ _dash_plugins = {}
211
+ def dash_plugin(function: Callable[[Any], Any]) -> Callable[[Any], Any]:
212
+ """
213
+ Execute the function when starting the Dash application.
214
+ """
215
+ with _locks['_dash_plugins']:
216
+ try:
217
+ if function.__module__ not in _dash_plugins:
218
+ _dash_plugins[function.__module__] = []
219
+ _dash_plugins[function.__module__].append(function)
220
+ except Exception as e:
221
+ from meerschaum.utils.warnings import warn
222
+ warn(e)
223
+ return function
224
+
225
+
159
226
  def api_plugin(function: Callable[[Any], Any]) -> Callable[[Any], Any]:
160
227
  """
161
228
  Execute the function when initializing the Meerschaum API module.
@@ -164,15 +231,6 @@ def api_plugin(function: Callable[[Any], Any]) -> Callable[[Any], Any]:
164
231
 
165
232
  The FastAPI app will be passed as the only parameter.
166
233
 
167
- Parameters
168
- ----------
169
- function: Callable[[Any, Any]]
170
- The function to be called before starting the Meerschaum API.
171
-
172
- Returns
173
- -------
174
- Another function (decorator function).
175
-
176
234
  Examples
177
235
  --------
178
236
  >>> from meerschaum.plugins import api_plugin
@@ -465,8 +465,9 @@ class Daemon:
465
465
  Handle `SIGINT` within the Daemon context.
466
466
  This method is injected into the `DaemonContext`.
467
467
  """
468
- # from meerschaum.utils.daemon.FileDescriptorInterceptor import STOP_READING_FD_EVENT
469
- # STOP_READING_FD_EVENT.set()
468
+ from meerschaum.utils.process import signal_handler
469
+ signal_handler(signal_number, stack_frame)
470
+
470
471
  self.rotating_log.stop_log_fd_interception(unused_only=False)
471
472
  timer = self.__dict__.get('_log_refresh_timer', None)
472
473
  if timer is not None:
@@ -477,6 +478,7 @@ class Daemon:
477
478
  daemon_context.close()
478
479
 
479
480
  _close_pools()
481
+
480
482
  import threading
481
483
  for thread in threading.enumerate():
482
484
  if thread.name == 'MainThread':
@@ -495,6 +497,9 @@ class Daemon:
495
497
  Handle `SIGTERM` within the `Daemon` context.
496
498
  This method is injected into the `DaemonContext`.
497
499
  """
500
+ from meerschaum.utils.process import signal_handler
501
+ signal_handler(signal_number, stack_frame)
502
+
498
503
  timer = self.__dict__.get('_log_refresh_timer', None)
499
504
  if timer is not None:
500
505
  timer.cancel()
meerschaum/utils/misc.py CHANGED
@@ -1103,6 +1103,12 @@ def is_docker_available() -> bool:
1103
1103
  return has_docker
1104
1104
 
1105
1105
 
1106
+ def is_android() -> bool:
1107
+ """Return `True` if the current platform is Android."""
1108
+ import sys
1109
+ return hasattr(sys, 'getandroidapilevel')
1110
+
1111
+
1106
1112
  def is_bcp_available() -> bool:
1107
1113
  """Check if the MSSQL `bcp` utility is installed."""
1108
1114
  import subprocess