meerschaum 2.2.5.dev0__py3-none-any.whl → 2.2.5.dev3__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 (30) hide show
  1. meerschaum/_internal/arguments/_parse_arguments.py +23 -14
  2. meerschaum/_internal/arguments/_parser.py +4 -2
  3. meerschaum/_internal/docs/index.py +31 -125
  4. meerschaum/_internal/shell/Shell.py +0 -3
  5. meerschaum/actions/bootstrap.py +14 -235
  6. meerschaum/actions/edit.py +98 -15
  7. meerschaum/actions/show.py +13 -4
  8. meerschaum/actions/stack.py +12 -12
  9. meerschaum/actions/uninstall.py +24 -29
  10. meerschaum/api/__init__.py +0 -1
  11. meerschaum/api/dash/__init__.py +0 -1
  12. meerschaum/api/dash/callbacks/custom.py +1 -1
  13. meerschaum/api/dash/plugins.py +5 -6
  14. meerschaum/config/__init__.py +16 -6
  15. meerschaum/config/_version.py +1 -1
  16. meerschaum/core/Pipe/_fetch.py +25 -21
  17. meerschaum/core/Pipe/_sync.py +89 -59
  18. meerschaum/plugins/bootstrap.py +333 -0
  19. meerschaum/utils/formatting/__init__.py +22 -10
  20. meerschaum/utils/prompt.py +11 -4
  21. meerschaum/utils/warnings.py +1 -0
  22. {meerschaum-2.2.5.dev0.dist-info → meerschaum-2.2.5.dev3.dist-info}/METADATA +1 -1
  23. {meerschaum-2.2.5.dev0.dist-info → meerschaum-2.2.5.dev3.dist-info}/RECORD +29 -29
  24. meerschaum/actions/backup.py +0 -43
  25. {meerschaum-2.2.5.dev0.dist-info → meerschaum-2.2.5.dev3.dist-info}/LICENSE +0 -0
  26. {meerschaum-2.2.5.dev0.dist-info → meerschaum-2.2.5.dev3.dist-info}/NOTICE +0 -0
  27. {meerschaum-2.2.5.dev0.dist-info → meerschaum-2.2.5.dev3.dist-info}/WHEEL +0 -0
  28. {meerschaum-2.2.5.dev0.dist-info → meerschaum-2.2.5.dev3.dist-info}/entry_points.txt +0 -0
  29. {meerschaum-2.2.5.dev0.dist-info → meerschaum-2.2.5.dev3.dist-info}/top_level.txt +0 -0
  30. {meerschaum-2.2.5.dev0.dist-info → meerschaum-2.2.5.dev3.dist-info}/zip-safe +0 -0
@@ -80,8 +80,9 @@ def parse_arguments(sysargs: List[str]) -> Dict[str, Any]:
80
80
 
81
81
  ### rebuild sysargs without sub_arguments
82
82
  filtered_sysargs = [
83
- word for i, word in enumerate(sysargs)
84
- if i not in sub_arg_indices
83
+ word
84
+ for i, word in enumerate(sysargs)
85
+ if i not in sub_arg_indices
85
86
  ]
86
87
 
87
88
  try:
@@ -99,7 +100,6 @@ def parse_arguments(sysargs: List[str]) -> Dict[str, Any]:
99
100
  except Exception as _e:
100
101
  args_dict['text'] = ' '.join(sysargs)
101
102
  args_dict[STATIC_CONFIG['system']['arguments']['failure_key']] = e
102
- unknown = []
103
103
 
104
104
  false_flags = [arg for arg, val in args_dict.items() if val is False]
105
105
  for arg in false_flags:
@@ -126,17 +126,26 @@ def parse_arguments(sysargs: List[str]) -> Dict[str, Any]:
126
126
 
127
127
  ### remove None (but not False) args
128
128
  none_args = []
129
+ none_args_keep = []
129
130
  for a, v in args_dict.items():
130
131
  if v is None:
131
132
  none_args.append(a)
133
+ elif v == 'None':
134
+ none_args_keep.append(a)
132
135
  for a in none_args:
133
136
  del args_dict[a]
137
+ for a in none_args_keep:
138
+ args_dict[a] = None
134
139
 
135
140
  ### location_key '[None]' or 'None' -> None
136
141
  if 'location_keys' in args_dict:
137
142
  args_dict['location_keys'] = [
138
- None if lk in ('[None]', 'None')
139
- else lk for lk in args_dict['location_keys']
143
+ (
144
+ None
145
+ if lk in ('[None]', 'None')
146
+ else lk
147
+ )
148
+ for lk in args_dict['location_keys']
140
149
  ]
141
150
 
142
151
  return parse_synonyms(args_dict)
@@ -145,7 +154,7 @@ def parse_arguments(sysargs: List[str]) -> Dict[str, Any]:
145
154
  def parse_line(line: str) -> Dict[str, Any]:
146
155
  """
147
156
  Parse a line of text into standard Meerschaum arguments.
148
-
157
+
149
158
  Parameters
150
159
  ----------
151
160
  line: str
@@ -165,12 +174,12 @@ def parse_line(line: str) -> Dict[str, Any]:
165
174
  try:
166
175
  return parse_arguments(shlex.split(line))
167
176
  except Exception as e:
168
- return {'action': [], 'text': line,}
177
+ return {'action': [], 'text': line}
169
178
 
170
179
 
171
180
  def parse_synonyms(
172
- args_dict: Dict[str, Any]
173
- ) -> Dict[str, Any]:
181
+ args_dict: Dict[str, Any]
182
+ ) -> Dict[str, Any]:
174
183
  """Check for synonyms (e.g. `async` = `True` -> `unblock` = `True`)"""
175
184
  if args_dict.get('async', None):
176
185
  args_dict['unblock'] = True
@@ -194,8 +203,8 @@ def parse_synonyms(
194
203
 
195
204
 
196
205
  def parse_dict_to_sysargs(
197
- args_dict : Dict[str, Any]
198
- ) -> List[str]:
206
+ args_dict: Dict[str, Any]
207
+ ) -> List[str]:
199
208
  """Revert an arguments dictionary back to a command line list."""
200
209
  from meerschaum._internal.arguments._parser import get_arguments_triggers
201
210
  sysargs = []
@@ -230,9 +239,9 @@ def parse_dict_to_sysargs(
230
239
 
231
240
 
232
241
  def remove_leading_action(
233
- action: List[str],
234
- _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
235
- ) -> List[str]:
242
+ action: List[str],
243
+ _actions: Optional[Dict[str, Callable[[Any], SuccessTuple]]] = None,
244
+ ) -> List[str]:
236
245
  """
237
246
  Remove the leading strings in the `action` list.
238
247
 
@@ -37,12 +37,15 @@ class ArgumentParser(argparse.ArgumentParser):
37
37
  return result
38
38
 
39
39
 
40
- def parse_datetime(dt_str: str) -> datetime:
40
+ def parse_datetime(dt_str: str) -> Union[datetime, int, str]:
41
41
  """Parse a string into a datetime."""
42
42
  from meerschaum.utils.misc import is_int
43
43
  if is_int(dt_str):
44
44
  return int(dt_str)
45
45
 
46
+ if dt_str in ('None', '[None]'):
47
+ return 'None'
48
+
46
49
  from meerschaum.utils.packages import attempt_import
47
50
  dateutil_parser = attempt_import('dateutil.parser')
48
51
 
@@ -155,7 +158,6 @@ groups['actions'].add_argument(
155
158
  '-d', '--daemon', action='store_true',
156
159
  help = "Run an action as a background daemon."
157
160
  )
158
-
159
161
  groups['actions'].add_argument(
160
162
  '--rm', action='store_true', help="Delete a job once it has finished executing."
161
163
  )
@@ -5,130 +5,36 @@
5
5
  """
6
6
  <img src="https://meerschaum.io/assets/banner_1920x320.png" alt="Meerschaum banner" style="width: 100%;"/>
7
7
 
8
- | PyPI | GitHub | Info | Stats |
9
- |---|---|---|---|
10
- | ![PyPI]( https://img.shields.io/pypi/v/meerschaum?color=%2300cc66&label=Version ) | ![GitHub Repo stars](https://img.shields.io/github/stars/bmeares/Meerschaum?style=social) | ![License](https://img.shields.io/github/license/bmeares/Meerschaum?label=License) | ![Number of plugins]( https://img.shields.io/badge/dynamic/json?color=3098c1&label=Public%20Plugins&query=num_plugins&url=https%3A%2F%2Fapi.mrsm.io%2Finfo ) |
11
- | ![PyPI - Python Version]( https://img.shields.io/pypi/pyversions/meerschaum?label=Python&logo=python&logoColor=%23ffffff ) | ![GitHub Sponsors](https://img.shields.io/github/sponsors/bmeares?color=eadf15&label=Sponsors) | [![meerschaum Tutorials](https://badges.openbase.com/python/tutorials/meerschaum.svg?token=2Yi8Oav9UZYWocO1ncwnIOnpUN5dTnUMWai7lAKTB+k=)](https://openbase.com/python/meerschaum?utm_source=embedded&amp;utm_medium=badge&amp;utm_campaign=rate-badge) | ![Number of registered users]( https://img.shields.io/badge/dynamic/json?color=3098c1&label=Registered%20Users&query=num_users&url=https%3A%2F%2Fapi.mrsm.io%2Finfo ) |
8
+ # Meerschaum Python API
9
+
10
+ Welcome to the Meerschaum Python API technical documentation!
11
+
12
+ - `meerschaum.actions`
13
+ Access functions for actions and subactions.
14
+ - `meerschaum.connectors`
15
+ - `meerschaum.utils`
16
+ Utility functions are available in several submodules, such as functions for:
17
+ - `meerschaum.utils.daemon`
18
+ Managing background processes.\n
19
+ - `meerschaum.utils.dataframe`
20
+ Manipulating dataframes.\n
21
+ - `meerschaum.utils.dtypes`
22
+ Working with data types.\n
23
+ - `meerschaum.utils.formatting`
24
+ Formatting output text.\n
25
+ - `meerschaum.utils.misc`
26
+ Miscellaneous utility functions (e.g. `meerschaum.utils.misc.round_time()`).\n
27
+ - `meerschaum.utils.packages`
28
+ Managing Python packages.\n
29
+ - `meerschaum.utils.prompt`
30
+ Reading input from the user.\n
31
+ - `meerschaum.utils.schedule`
32
+ Scheduling processes and threads.\n
33
+ - `meerschaum.utils.sql`
34
+ Building SQL queries.\n
35
+ - `meerschaum.utils.venv`
36
+ Managing virtual environments.\n
37
+ - `meerschaum.utils.warnings`
38
+ Print warnings, errors, info, and debug messages.
12
39
 
13
- <p align="center">
14
- <img src="https://meerschaum.io/files/images/demo.gif" alt="Meerschaum demo" style="width: 100%;">
15
- </p>
16
-
17
- ## What is Meerschaum?
18
- Meerschaum is a tool for quickly synchronizing time-series data streams called **pipes**. With Meerschaum, you can have a data visualization stack running in minutes.
19
-
20
- <p align="center">
21
- <img src="https://meerschaum.io/assets/screenshots/weather_pipes.png" style="width: 100%;"/>
22
- </p>
23
-
24
- ## Why Meerschaum?
25
-
26
- If you've worked with time-series data, you know the headaches that come with ETL.
27
- Data engineering often gets in analysts' way, and when work needs to get done, every minute spent on pipelining is time taken away from real analysis.
28
-
29
- Rather than copy / pasting your ETL scripts, simply build pipes with Meerschaum! [Meerschaum gives you the tools to design your data streams how you like](https://towardsdatascience.com/easy-time-series-etl-for-data-scientists-with-meerschaum-5aade339b398) ― and don't worry — you can always incorporate Meerschaum into your existing systems!
30
-
31
- ## Features
32
-
33
- - 📊 **Built for Data Scientists and Analysts**
34
- - Integrate with Pandas, Grafana and other popular [data analysis tools](https://meerschaum.io/reference/data-analysis-tools/).
35
- - Persist your dataframes and always get the latest data.
36
- - ⚡️ **Production-Ready, Batteries Included**
37
- - [Synchronization engine](https://meerschaum.io/reference/pipes/syncing/) concurrently updates many time-series data streams.
38
- - One-click deploy a [TimescaleDB and Grafana stack](https://meerschaum.io/reference/stack/) for prototyping.
39
- - Serve data to your entire organization through the power of `uvicorn`, `gunicorn`, and `FastAPI`.
40
- - 🔌 **Easily Expandable**
41
- - Ingest any data source with a simple [plugin](https://meerschaum.io/reference/plugins/writing-plugins/). Just return a DataFrame, and Meerschaum handles the rest.
42
- - [Add any function as a command](https://meerschaum.io/reference/plugins/types-of-plugins/#action-plugins) to the Meerschaum system.
43
- - Include Meerschaum in your projects with its [easy-to-use Python API](https://docs.meerschaum.io).
44
- - ✨ **Tailored for Your Experience**
45
- - Rich CLI makes managing your data streams surprisingly enjoyable!
46
- - Web dashboard for those who prefer a more graphical experience.
47
- - Manage your database connections with [Meerschaum connectors](https://meerschaum.io/reference/connectors/)
48
- - Utility commands with sensible syntax let you control many pipes with grace.
49
- - 💼 **Portable from the Start**
50
- - The environment variables `$MRSM_ROOT_DIR`, `$MRSM_PLUGINS_DIR`, and `$MRSM_VENVS_DIR` let you emulate multiple installations and group together your [instances](https://meerschaum.io/reference/connectors/#instances-and-repositories).
51
- - No dependencies required; anything needed will be installed into virtual environments.
52
- - [Specify required packages for your plugins](https://meerschaum.io/reference/plugins/writing-plugins/), and users will get those packages in a virtual environment.
53
-
54
- ## Installation
55
-
56
- For a more thorough setup guide, visit the [Getting Started](https://meerschaum.io/get-started/) page at [meerschaum.io](https://meerschaum.io).
57
-
58
- ### TL;DR
59
-
60
- ```bash
61
- pip install -U --user meerschaum
62
- mrsm stack up -d db grafana
63
- mrsm bootstrap pipes
64
- ```
65
-
66
- ## Usage Documentation
67
-
68
- Please visit [meerschaum.io](https://meerschaum.io) for setup, usage, and troubleshooting information. You can find technical documentation at [docs.meerschaum.io](https://docs.meerschaum.io), and here is a complete list of the [Meerschaum actions](https://meerschaum.io/reference/actions/).
69
-
70
- ```python
71
- >>> import meerschaum as mrsm
72
- >>> pipe = mrsm.Pipe("plugin:noaa", "weather")
73
- >>> cols_to_select = ['timestamp', 'station', 'temperature (degC)']
74
- >>> df = pipe.get_data(cols_to_select, begin='2023-11-15', end='2023-11-20')
75
- >>> df
76
- timestamp station temperature (degC)
77
- 0 2023-11-15 00:52:00 KATL 16.1
78
- 1 2023-11-15 00:52:00 KCLT 11.7
79
- 2 2023-11-15 00:53:00 KGMU 15.0
80
- 3 2023-11-15 00:54:00 KCEU 13.9
81
- 4 2023-11-15 01:52:00 KATL 15.6
82
- .. ... ... ...
83
- 535 2023-11-19 22:54:00 KCEU 15.6
84
- 536 2023-11-19 23:52:00 KATL 16.7
85
- 537 2023-11-19 23:52:00 KCLT 13.9
86
- 538 2023-11-19 23:53:00 KGMU 15.6
87
- 539 2023-11-19 23:54:00 KCEU 15.0
88
-
89
- [540 rows x 3 columns]
90
- >>>
91
- ```
92
-
93
- ## Plugins
94
-
95
- Check out the [Awesome Meerschaum list](https://github.com/bmeares/awesome-meerschaum) for a list of community plugins as well as the [public plugins repository](https://api.mrsm.io/dash/plugins).
96
-
97
- For details on installing, using, and writing plugins, check out the [plugins documentation](https://meerschaum.io/reference/plugins/types-of-plugins) at [meerschaum.io](https://meerschaum.io).
98
-
99
- #### Example Plugin
100
-
101
- ```python
102
- # ~/.config/meerschaum/plugins/example.py
103
- __version__ = '0.0.1'
104
- required = []
105
-
106
- def register(pipe, **kw):
107
- return {
108
- 'columns': {
109
- 'datetime': 'dt',
110
- 'id': 'id',
111
- 'value': 'val',
112
- }
113
- }
114
-
115
- def fetch(pipe, **kw):
116
- import random
117
- from datetime import datetime
118
- docs = [
119
- {
120
- 'dt': datetime.now(),
121
- 'id': i,
122
- 'val': random.ranint(0, 200),
123
- }
124
- for i in range(random.randint(0, 100))
125
- ]
126
- return docs
127
- ```
128
-
129
- ## Support Meerschaum's Development
130
-
131
- For consulting services and to support Meerschaum's development, please considering sponsoring me on [GitHub sponsors](https://github.com/sponsors/bmeares).
132
-
133
- Additionally, you can always [buy me a coffee☕](https://www.buymeacoffee.com/bmeares)!
134
40
  """
@@ -182,7 +182,6 @@ def _check_complete_keys(line: str) -> Optional[List[str]]:
182
182
  is_trigger = True
183
183
  elif line.endswith(' '):
184
184
  ### return empty list so we don't try to parse an incomplete line.
185
- # print('ABORT')
186
185
  return []
187
186
 
188
187
  from meerschaum.utils.misc import get_connector_labels
@@ -196,8 +195,6 @@ def _check_complete_keys(line: str) -> Optional[List[str]]:
196
195
  if line.rstrip(' ').endswith(trigger):
197
196
  return get_connector_labels()
198
197
 
199
- # args = parse_line(line.rstrip(' '))
200
- # search_term = args[var] if var != 'connector_keys' else args[var][0]
201
198
  return get_connector_labels(search_term=last_word.rstrip(' '))
202
199
 
203
200
  return None
@@ -103,12 +103,14 @@ def _bootstrap_pipes(
103
103
  )
104
104
  try:
105
105
  ck = choose(
106
- f"Where are the data coming from?\n\n" +
107
- f" Please type the keys of a connector from below,\n" +
108
- f" or enter '{new_label}' to register a new connector.\n\n" +
109
- f" {get_config('formatting', 'emoji', 'connector')} Connector:\n",
106
+ (
107
+ "Where are the data coming from?\n\n" +
108
+ f" Please type the keys of a connector or enter '{new_label}'\n" +
109
+ " to register a new connector.\n\n" +
110
+ f" {get_config('formatting', 'emoji', 'connector')} Connector:"
111
+ ),
110
112
  get_connector_labels() + [new_label],
111
- numeric = False
113
+ numeric = False,
112
114
  )
113
115
  except KeyboardInterrupt:
114
116
  return abort_tuple
@@ -258,8 +260,8 @@ def _bootstrap_connectors(
258
260
  _type = choose(
259
261
  (
260
262
  'Please choose a connector type.\n'
261
- + 'For more information on connectors, '
262
- + 'please visit https://meerschaum.io/reference/connectors'
263
+ + ' See https://meerschaum.io/reference/connectors '
264
+ + 'for documentation on connectors.\n'
263
265
  ),
264
266
  sorted(list(connectors)),
265
267
  default = 'sql'
@@ -391,239 +393,16 @@ def _bootstrap_plugins(
391
393
  """
392
394
  Launch an interactive wizard to guide the user to creating a new plugin.
393
395
  """
394
- import pathlib
395
- import meerschaum as mrsm
396
- from meerschaum.utils.warnings import info, warn
397
- from meerschaum.utils.prompt import prompt, choose, yes_no
398
- from meerschaum.utils.formatting._shell import clear_screen
399
- from meerschaum.utils.misc import edit_file
400
- from meerschaum.config.paths import PLUGINS_DIR_PATHS
401
- from meerschaum._internal.entry import entry
396
+ from meerschaum.utils.prompt import prompt
397
+ from meerschaum.plugins.bootstrap import bootstrap_plugin
402
398
 
403
399
  if not action:
404
400
  action = [prompt("Enter the name of your new plugin:")]
405
401
 
406
- if len(PLUGINS_DIR_PATHS) > 1:
407
- plugins_dir_path = pathlib.Path(
408
- choose(
409
- "In which directory do you want to write your plugin?",
410
- [path.as_posix() for path in PLUGINS_DIR_PATHS],
411
- numeric = True,
412
- multiple = False,
413
- default = PLUGINS_DIR_PATHS[0].as_posix(),
414
- )
415
- )
416
- else:
417
- plugins_dir_path = PLUGINS_DIR_PATHS[0]
418
-
419
- clear_screen(debug=debug)
420
- info(
421
- "Answer the questions below to pick out features.\n"
422
- + " See the Writing Plugins guide for documentation:\n"
423
- + " https://meerschaum.io/reference/plugins/writing-plugins/ for documentation.\n"
424
- )
425
-
426
- imports_lines = {
427
- 'default': (
428
- "import meerschaum as mrsm\n"
429
- ),
430
- 'action': (
431
- "from meerschaum.actions import make_action\n"
432
- ),
433
- 'api': (
434
- "from meerschaum.plugins import api_plugin\n"
435
- ),
436
- 'web': (
437
- "from meerschaum.plugins import web_page, dash_plugin\n"
438
- ),
439
- 'api+web': (
440
- "from meerschaum.plugins import api_plugin, web_page, dash_plugin\n"
441
- ),
442
- }
443
-
444
- ### TODO: Add feature for custom connectors.
445
- feature_lines = {
446
- 'header': (
447
- "# {plugin_name}.py\n\n"
448
- ),
449
- 'default': (
450
- "__version__ = '0.0.1'\n"
451
- "\n# Add any depedencies to `required` (similar to `requirements.txt`).\n"
452
- "required = []\n\n"
453
- ),
454
- 'setup': (
455
- "def setup(**kwargs) -> mrsm.SuccessTuple:\n"
456
- " \"\"\"Executed during installation and `mrsm setup plugin {plugin_name}`.\"\"\"\n"
457
- " return True, \"Success\"\n\n\n"
458
- ),
459
- 'register': (
460
- "def register(pipe: mrsm.Pipe):\n"
461
- " \"\"\"Return the default parameters for a new pipe.\"\"\"\n"
462
- " return {\n"
463
- " 'columns': {\n"
464
- " 'datetime': None,\n"
465
- " }\n"
466
- " }\n\n\n"
467
- ),
468
- 'fetch': (
469
- "def fetch(pipe: mrsm.Pipe, **kwargs):\n"
470
- " \"\"\"Return or yield dataframe-like objects.\"\"\"\n"
471
- " docs = []\n"
472
- " # populate docs with dictionaries (rows).\n"
473
- " return docs\n\n\n"
474
- ),
475
- 'action': (
476
- "@make_action\n"
477
- "def {action_name}(**kwargs) -> mrsm.SuccessTuple:\n"
478
- " \"\"\"Run `mrsm {action_spaces}` to trigger.\"\"\"\n"
479
- " return True, \"Success\"\n\n\n"
480
- ),
481
- 'api': (
482
- "@api_plugin\n"
483
- "def init_app(fastapi_app):\n"
484
- " \"\"\"Add new endpoints to the FastAPI app.\"\"\"\n\n"
485
- " import fastapi\n"
486
- " from meerschaum.api import manager\n\n"
487
- " @fastapi_app.get('/my/endpoint')\n"
488
- " def get_my_endpoint(curr_user=fastapi.Depends(manager)):\n"
489
- " return {'message': 'Hello, World!'}\n\n\n"
490
- ),
491
- 'web': (
492
- "@dash_plugin\n"
493
- "def init_dash(dash_app):\n"
494
- " \"\"\"Initialize the Plotly Dash application.\"\"\"\n"
495
- " import dash.html as html\n"
496
- " import dash.dcc as dcc\n"
497
- " from dash import Input, Output, State, no_update\n"
498
- " import dash_bootstrap_components as dbc\n\n"
499
- " # Create a new page at the path `/dash/{plugin_name}`.\n"
500
- " @web_page('{plugin_name}', login_required=False)\n"
501
- " def page_layout():\n"
502
- " \"\"\"Return the layout objects for this page.\"\"\"\n"
503
- " return dbc.Container([\n"
504
- " dcc.Location(id='{plugin_name}-location'),\n"
505
- " html.Div(id='output-div'),\n"
506
- " ])\n\n"
507
- " @dash_app.callback(\n"
508
- " Output('output-div', 'children'),\n"
509
- " Input('{plugin_name}-location', 'pathname'),\n"
510
- " )\n"
511
- " def render_page_on_url_change(pathname: str):\n"
512
- " \"\"\"Reload page contents when the URL path changes.\"\"\"\n"
513
- " return html.H1(\"Hello from plugin '{plugin_name}'!\")\n\n\n"
514
- ),
515
- }
516
-
517
402
  for plugin_name in action:
518
- plugin_path = plugins_dir_path / (plugin_name + '.py')
519
- plugin = mrsm.Plugin(plugin_name)
520
- if plugin.is_installed():
521
- warn(f"Plugin '{plugin_name}' is already installed!", stack=False)
522
- uninstall_plugin = yes_no(
523
- f"Do you want to first uninstall '{plugin}'?",
524
- default = 'n',
525
- **kwargs
526
- )
527
- if not uninstall_plugin:
528
- return False, f"Plugin '{plugin_name}' already exists."
529
-
530
- uninstall_success, uninstall_msg = entry(['uninstall', 'plugin', plugin_name, '-f'])
531
- if not uninstall_success:
532
- return uninstall_success, uninstall_msg
533
- clear_screen(debug=debug)
534
-
535
- features = choose(
536
- "Which of the following features would you like to add to your plugin?",
537
- [
538
- (
539
- 'fetch',
540
- 'Fetch data\n (e.g. extracting from a remote API)\n'
541
- ),
542
- (
543
- 'action',
544
- 'New actions\n (e.g. `mrsm sing song`)\n'
545
- ),
546
- (
547
- 'api',
548
- 'New API endpoints\n (e.g. `POST /my/new/endpoint`)\n',
549
- ),
550
- (
551
- 'web',
552
- 'New web console page\n (e.g. `/dash/my-web-app`)\n',
553
- ),
554
- ],
555
- default = 'fetch',
556
- multiple = True,
557
- as_indices = True,
558
- **kwargs
559
- )
560
-
561
- action_name = ''
562
- if 'action' in features:
563
- while True:
564
- try:
565
- action_name = prompt(
566
- "What is name of your action?\n "
567
- + "(separate subactions with spaces, e.g. `sing song`):"
568
- ).replace(' ', '_')
569
- except KeyboardInterrupt as e:
570
- return False, "Aborted plugin creation."
571
-
572
- if action_name:
573
- break
574
- warn("Please enter an action.", stack=False)
575
-
576
- action_spaces = action_name.replace('_', ' ')
577
-
578
- plugin_labels = {
579
- 'plugin_name': plugin_name,
580
- 'action_name': action_name,
581
- 'action_spaces': action_spaces,
582
- }
583
-
584
- body_text = ""
585
- body_text += feature_lines['header'].format(**plugin_labels)
586
- body_text += imports_lines['default'].format(**plugin_labels)
587
- if 'action' in features:
588
- body_text += imports_lines['action']
589
- if 'api' in features and 'web' in features:
590
- body_text += imports_lines['api+web']
591
- elif 'api' in features:
592
- body_text += imports_lines['api']
593
- elif 'web' in features:
594
- body_text += imports_lines['web']
595
-
596
- body_text += "\n"
597
- body_text += feature_lines['default'].format(**plugin_labels)
598
- body_text += feature_lines['setup'].format(**plugin_labels)
599
-
600
- if 'fetch' in features:
601
- body_text += feature_lines['register']
602
- body_text += feature_lines['fetch']
603
-
604
- if 'action' in features:
605
- body_text += feature_lines['action'].format(**plugin_labels)
606
-
607
- if 'api' in features:
608
- body_text += feature_lines['api']
609
-
610
- if 'web' in features:
611
- body_text += feature_lines['web'].format(**plugin_labels)
612
-
613
- try:
614
- with open(plugin_path, 'w+', encoding='utf-8') as f:
615
- f.write(body_text.rstrip())
616
- except Exception as e:
617
- error_msg = f"Failed to write file '{plugin_path}':\n{e}"
618
- return False, error_msg
619
-
620
- mrsm.pprint((True, f"Successfully created file '{plugin_path}'."))
621
- try:
622
- _ = prompt(f"Press [Enter] to edit plugin '{plugin_name}', [CTRL+C] to skip.")
623
- except (KeyboardInterrupt, Exception):
624
- continue
625
-
626
- edit_file(plugin_path, debug=debug)
403
+ bootstrap_success, bootstrap_msg = bootstrap_plugin(plugin_name)
404
+ if not bootstrap_success:
405
+ return bootstrap_success, bootstrap_msg
627
406
 
628
407
  return True, "Success"
629
408