meerschaum 2.9.5__py3-none-any.whl → 3.0.0rc2__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 (158) hide show
  1. meerschaum/__init__.py +5 -2
  2. meerschaum/_internal/__init__.py +1 -0
  3. meerschaum/_internal/arguments/_parse_arguments.py +4 -4
  4. meerschaum/_internal/arguments/_parser.py +19 -2
  5. meerschaum/_internal/docs/index.py +49 -2
  6. meerschaum/_internal/entry.py +6 -6
  7. meerschaum/_internal/shell/Shell.py +1 -1
  8. meerschaum/_internal/static.py +356 -0
  9. meerschaum/actions/api.py +12 -2
  10. meerschaum/actions/bootstrap.py +7 -7
  11. meerschaum/actions/edit.py +142 -18
  12. meerschaum/actions/register.py +137 -6
  13. meerschaum/actions/show.py +117 -29
  14. meerschaum/actions/stop.py +4 -1
  15. meerschaum/actions/sync.py +1 -1
  16. meerschaum/actions/tag.py +9 -8
  17. meerschaum/actions/verify.py +5 -8
  18. meerschaum/api/__init__.py +11 -3
  19. meerschaum/api/_events.py +39 -2
  20. meerschaum/api/_oauth2.py +118 -8
  21. meerschaum/api/_tokens.py +102 -0
  22. meerschaum/api/dash/__init__.py +0 -3
  23. meerschaum/api/dash/callbacks/custom.py +2 -2
  24. meerschaum/api/dash/callbacks/dashboard.py +103 -19
  25. meerschaum/api/dash/callbacks/plugins.py +0 -1
  26. meerschaum/api/dash/callbacks/register.py +1 -1
  27. meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
  28. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  29. meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
  30. meerschaum/api/dash/components.py +30 -8
  31. meerschaum/api/dash/keys.py +19 -93
  32. meerschaum/api/dash/pages/dashboard.py +1 -20
  33. meerschaum/api/dash/pages/settings/__init__.py +1 -0
  34. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  35. meerschaum/api/dash/pages/settings/tokens.py +55 -0
  36. meerschaum/api/dash/pipes.py +94 -59
  37. meerschaum/api/dash/sessions.py +12 -0
  38. meerschaum/api/dash/tokens.py +606 -0
  39. meerschaum/api/dash/websockets.py +1 -1
  40. meerschaum/api/dash/webterm.py +4 -0
  41. meerschaum/api/models/__init__.py +23 -3
  42. meerschaum/api/models/_actions.py +22 -0
  43. meerschaum/api/models/_pipes.py +85 -7
  44. meerschaum/api/models/_tokens.py +81 -0
  45. meerschaum/api/resources/templates/termpage.html +12 -0
  46. meerschaum/api/routes/__init__.py +1 -0
  47. meerschaum/api/routes/_actions.py +3 -4
  48. meerschaum/api/routes/_connectors.py +3 -7
  49. meerschaum/api/routes/_jobs.py +14 -35
  50. meerschaum/api/routes/_login.py +49 -12
  51. meerschaum/api/routes/_misc.py +5 -10
  52. meerschaum/api/routes/_pipes.py +173 -140
  53. meerschaum/api/routes/_plugins.py +38 -28
  54. meerschaum/api/routes/_tokens.py +236 -0
  55. meerschaum/api/routes/_users.py +47 -35
  56. meerschaum/api/routes/_version.py +3 -3
  57. meerschaum/config/__init__.py +43 -20
  58. meerschaum/config/_default.py +43 -6
  59. meerschaum/config/_edit.py +28 -24
  60. meerschaum/config/_environment.py +1 -1
  61. meerschaum/config/_patch.py +6 -6
  62. meerschaum/config/_paths.py +5 -1
  63. meerschaum/config/_read_config.py +65 -34
  64. meerschaum/config/_sync.py +6 -3
  65. meerschaum/config/_version.py +1 -1
  66. meerschaum/config/stack/__init__.py +31 -11
  67. meerschaum/config/static.py +18 -0
  68. meerschaum/connectors/_Connector.py +10 -4
  69. meerschaum/connectors/__init__.py +4 -20
  70. meerschaum/connectors/api/_APIConnector.py +34 -6
  71. meerschaum/connectors/api/_actions.py +2 -2
  72. meerschaum/connectors/api/_jobs.py +1 -1
  73. meerschaum/connectors/api/_login.py +33 -7
  74. meerschaum/connectors/api/_misc.py +2 -2
  75. meerschaum/connectors/api/_pipes.py +16 -31
  76. meerschaum/connectors/api/_plugins.py +2 -2
  77. meerschaum/connectors/api/_request.py +1 -1
  78. meerschaum/connectors/api/_tokens.py +146 -0
  79. meerschaum/connectors/api/_users.py +70 -58
  80. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  81. meerschaum/connectors/instance/__init__.py +10 -0
  82. meerschaum/connectors/instance/_pipes.py +442 -0
  83. meerschaum/connectors/instance/_plugins.py +151 -0
  84. meerschaum/connectors/instance/_tokens.py +296 -0
  85. meerschaum/connectors/instance/_users.py +181 -0
  86. meerschaum/connectors/parse.py +4 -1
  87. meerschaum/connectors/sql/_SQLConnector.py +8 -5
  88. meerschaum/connectors/sql/_cli.py +12 -11
  89. meerschaum/connectors/sql/_create_engine.py +9 -168
  90. meerschaum/connectors/sql/_fetch.py +2 -18
  91. meerschaum/connectors/sql/_pipes.py +156 -190
  92. meerschaum/connectors/sql/_plugins.py +29 -0
  93. meerschaum/connectors/sql/_sql.py +46 -21
  94. meerschaum/connectors/sql/_users.py +29 -2
  95. meerschaum/connectors/sql/tables/__init__.py +1 -1
  96. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
  97. meerschaum/connectors/valkey/_pipes.py +53 -26
  98. meerschaum/connectors/valkey/_plugins.py +2 -26
  99. meerschaum/core/Pipe/__init__.py +59 -19
  100. meerschaum/core/Pipe/_attributes.py +412 -90
  101. meerschaum/core/Pipe/_bootstrap.py +54 -24
  102. meerschaum/core/Pipe/_data.py +96 -18
  103. meerschaum/core/Pipe/_dtypes.py +48 -18
  104. meerschaum/core/Pipe/_edit.py +14 -4
  105. meerschaum/core/Pipe/_fetch.py +1 -1
  106. meerschaum/core/Pipe/_show.py +5 -5
  107. meerschaum/core/Pipe/_sync.py +118 -193
  108. meerschaum/core/Pipe/_verify.py +4 -4
  109. meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
  110. meerschaum/core/Plugin/__init__.py +1 -1
  111. meerschaum/core/Token/_Token.py +220 -0
  112. meerschaum/core/Token/__init__.py +12 -0
  113. meerschaum/core/User/_User.py +34 -8
  114. meerschaum/core/User/__init__.py +9 -1
  115. meerschaum/core/__init__.py +1 -0
  116. meerschaum/jobs/_Job.py +3 -2
  117. meerschaum/jobs/__init__.py +3 -2
  118. meerschaum/jobs/systemd.py +1 -1
  119. meerschaum/models/__init__.py +35 -0
  120. meerschaum/models/pipes.py +247 -0
  121. meerschaum/models/tokens.py +38 -0
  122. meerschaum/models/users.py +26 -0
  123. meerschaum/plugins/__init__.py +22 -7
  124. meerschaum/plugins/bootstrap.py +2 -1
  125. meerschaum/utils/_get_pipes.py +68 -27
  126. meerschaum/utils/daemon/Daemon.py +2 -1
  127. meerschaum/utils/daemon/__init__.py +30 -2
  128. meerschaum/utils/dataframe.py +473 -81
  129. meerschaum/utils/debug.py +15 -15
  130. meerschaum/utils/dtypes/__init__.py +473 -34
  131. meerschaum/utils/dtypes/sql.py +368 -28
  132. meerschaum/utils/formatting/__init__.py +1 -1
  133. meerschaum/utils/formatting/_pipes.py +5 -4
  134. meerschaum/utils/formatting/_shell.py +11 -9
  135. meerschaum/utils/misc.py +246 -148
  136. meerschaum/utils/packages/__init__.py +10 -27
  137. meerschaum/utils/packages/_packages.py +41 -34
  138. meerschaum/utils/pipes.py +181 -0
  139. meerschaum/utils/process.py +1 -1
  140. meerschaum/utils/prompt.py +3 -1
  141. meerschaum/utils/schedule.py +2 -1
  142. meerschaum/utils/sql.py +121 -44
  143. meerschaum/utils/typing.py +1 -4
  144. meerschaum/utils/venv/_Venv.py +2 -2
  145. meerschaum/utils/venv/__init__.py +5 -7
  146. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/METADATA +92 -96
  147. meerschaum-3.0.0rc2.dist-info/RECORD +283 -0
  148. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/WHEEL +1 -1
  149. meerschaum-3.0.0rc2.dist-info/licenses/NOTICE +2 -0
  150. meerschaum/api/models/_interfaces.py +0 -15
  151. meerschaum/api/models/_locations.py +0 -15
  152. meerschaum/api/models/_metrics.py +0 -15
  153. meerschaum/config/static/__init__.py +0 -186
  154. meerschaum-2.9.5.dist-info/RECORD +0 -263
  155. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/entry_points.txt +0 -0
  156. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
  157. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/top_level.txt +0 -0
  158. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc2.dist-info}/zip-safe +0 -0
@@ -75,7 +75,9 @@ def get_module_path(
75
75
 
76
76
  venv_target_candidate_paths = [vtp]
77
77
  if venv is None:
78
- site_user_packages_dirs = [pathlib.Path(site.getusersitepackages())]
78
+ site_user_packages_dirs = [
79
+ pathlib.Path(site.getusersitepackages())
80
+ ] if not inside_venv() else []
79
81
  site_packages_dirs = [pathlib.Path(path) for path in site.getsitepackages()]
80
82
 
81
83
  paths_to_add = [
@@ -755,7 +757,7 @@ def get_pip(
755
757
  import subprocess
756
758
  from meerschaum.utils.misc import wget
757
759
  from meerschaum.config._paths import CACHE_RESOURCES_PATH
758
- from meerschaum.config.static import STATIC_CONFIG
760
+ from meerschaum._internal.static import STATIC_CONFIG
759
761
  url = STATIC_CONFIG['system']['urls']['get-pip.py']
760
762
  dest = CACHE_RESOURCES_PATH / 'get-pip.py'
761
763
  try:
@@ -837,7 +839,7 @@ def pip_install(
837
839
 
838
840
  """
839
841
  from meerschaum.config._paths import VIRTENV_RESOURCES_PATH
840
- from meerschaum.config.static import STATIC_CONFIG
842
+ from meerschaum._internal.static import STATIC_CONFIG
841
843
  from meerschaum.utils.warnings import warn
842
844
  if args is None:
843
845
  args = ['--upgrade'] if not _uninstall else []
@@ -1500,15 +1502,12 @@ def import_pandas(
1500
1502
  def import_rich(
1501
1503
  lazy: bool = True,
1502
1504
  debug: bool = False,
1503
- **kw : Any
1505
+ **kw: Any
1504
1506
  ) -> 'ModuleType':
1505
1507
  """
1506
1508
  Quality of life function for importing `rich`.
1507
1509
  """
1508
1510
  from meerschaum.utils.formatting import ANSI, UNICODE
1509
- if not ANSI and not UNICODE:
1510
- return None
1511
-
1512
1511
  ## need typing_extensions for `from rich import box`
1513
1512
  typing_extensions = attempt_import(
1514
1513
  'typing_extensions', lazy=False, debug=debug
@@ -1773,6 +1772,10 @@ def is_installed(
1773
1772
  allow_outside_venv: bool, default True
1774
1773
  If `True`, search outside of the specified virtual environment
1775
1774
  if the package cannot be found.
1775
+
1776
+ Returns
1777
+ -------
1778
+ A bool indicating whether a package may be imported.
1776
1779
  """
1777
1780
  if debug:
1778
1781
  from meerschaum.utils.debug import dprint
@@ -1864,26 +1867,6 @@ def ensure_readline() -> 'ModuleType':
1864
1867
  sys.modules['readline'] = readline
1865
1868
  return readline
1866
1869
 
1867
- _pkg_resources_get_distribution = None
1868
- _custom_distributions = {}
1869
- def _monkey_patch_get_distribution(_dist: str, _version: str) -> None:
1870
- """
1871
- Monkey patch `pkg_resources.get_distribution` to allow for importing `flask_compress`.
1872
- """
1873
- import pkg_resources
1874
- from collections import namedtuple
1875
- global _pkg_resources_get_distribution
1876
- with _locks['_pkg_resources_get_distribution']:
1877
- _pkg_resources_get_distribution = pkg_resources.get_distribution
1878
- _custom_distributions[_dist] = _version
1879
- _Dist = namedtuple('_Dist', ['version'])
1880
- def _get_distribution(dist):
1881
- """Hack for flask-compress."""
1882
- if dist in _custom_distributions:
1883
- return _Dist(_custom_distributions[dist])
1884
- return _pkg_resources_get_distribution(dist)
1885
- pkg_resources.get_distribution = _get_distribution
1886
-
1887
1870
 
1888
1871
  def _get_pip_os_env(color: bool = True):
1889
1872
  """
@@ -13,8 +13,7 @@ packages dictionary is structured in the following schema:
13
13
  }
14
14
  """
15
15
 
16
- from __future__ import annotations
17
- from meerschaum.utils.typing import Dict
16
+ from typing import Dict
18
17
 
19
18
  _MRSM_PACKAGE_ARCHIVES_PREFIX: str = "https://meerschaum.io/files/archives/wheels/"
20
19
 
@@ -54,23 +53,25 @@ packages: Dict[str, Dict[str, str]] = {
54
53
  'virtualenv' : 'virtualenv>=20.1.0',
55
54
  'attrs' : 'attrs>=24.2.0',
56
55
  'uv' : 'uv>=0.2.11',
56
+ 'pydantic' : 'pydantic>=2.11.7',
57
+ 'annotated-types' : 'annotated-types>=0.7.0',
57
58
  },
58
59
  '_internal' : {
59
60
  'apscheduler' : (
60
61
  f"{_MRSM_PACKAGE_ARCHIVES_PREFIX}"
61
- "APScheduler-4.0.0a5.post87+mrsm-py3-none-any.whl>=4.0.0a5"
62
+ "apscheduler-4.0.0a6.post8+mrsm-py3-none-any.whl>=4.0.0a6"
62
63
  ),
63
- 'dataclass_wizard' : 'dataclass-wizard>=0.28.0',
64
+ 'dataclass_wizard' : 'dataclass-wizard>=0.35.0',
64
65
  },
65
66
  'jobs': {
66
- 'dill' : 'dill>=0.3.3',
67
- 'daemon' : 'python-daemon>=0.2.3',
68
- 'watchfiles' : 'watchfiles>=0.21.0',
69
- 'psutil' : 'psutil>=5.8.0',
67
+ 'dill' : 'dill>=0.4.0',
68
+ 'daemon' : 'python-daemon>=3.1.2',
69
+ 'watchfiles' : 'watchfiles>=1.1.0',
70
+ 'psutil' : 'psutil>=7.0.0',
70
71
  },
71
72
  'drivers': {
72
73
  'cryptography' : 'cryptography>=38.0.1',
73
- 'psycopg' : 'psycopg[binary]>=3.2.1',
74
+ 'psycopg' : 'psycopg[binary]>=3.2.9',
74
75
  'pymysql' : 'PyMySQL>=0.9.0',
75
76
  'aiomysql' : 'aiomysql>=0.0.21',
76
77
  'sqlalchemy_cockroachdb' : 'sqlalchemy-cockroachdb>=2.0.0',
@@ -122,7 +123,7 @@ packages: Dict[str, Dict[str, str]] = {
122
123
  'mkdocs_section_index' : 'mkdocs-section-index>=0.3.3',
123
124
  'mkdocs_linkcheck' : 'mkdocs-linkcheck>=1.0.6',
124
125
  'mkdocs_redirects' : 'mkdocs-redirects>=1.0.4',
125
- 'jinja2' : 'jinja2==3.0.3',
126
+ 'jinja2' : 'jinja2>=3.1.6',
126
127
  },
127
128
  'gui': {
128
129
  'webview' : 'pywebview>=3.6.3',
@@ -137,43 +138,43 @@ packages: Dict[str, Dict[str, str]] = {
137
138
  },
138
139
  }
139
140
  packages['sql'] = {
140
- 'numpy' : 'numpy>=1.18.5',
141
- 'pandas' : 'pandas[parquet]>=2.0.1',
142
- 'pyarrow' : 'pyarrow>=16.1.0',
141
+ 'numpy' : 'numpy>=2.3.1',
142
+ 'pandas' : 'pandas[parquet]>=2.3.1',
143
+ 'pyarrow' : 'pyarrow>=20.0.0',
143
144
  'dask' : 'dask[complete]>=2024.12.1',
144
145
  'partd' : 'partd>=1.4.2',
145
146
  'pytz' : 'pytz',
146
- 'joblib' : 'joblib>=0.17.0',
147
- 'sqlalchemy' : 'SQLAlchemy>=2.0.5',
147
+ 'joblib' : 'joblib>=1.5.1',
148
+ 'sqlalchemy' : 'SQLAlchemy>=2.0.41',
148
149
  'geoalchemy' : 'GeoAlchemy2>=0.17.1',
149
- 'databases' : 'databases>=0.4.0',
150
- 'aiosqlite' : 'aiosqlite>=0.16.0',
151
- 'asyncpg' : 'asyncpg>=0.21.0',
150
+ 'databases' : 'databases>=0.9.0',
151
+ 'aiosqlite' : 'aiosqlite>=0.21.0',
152
+ 'asyncpg' : 'asyncpg>=0.30.0',
152
153
  }
153
154
  packages['sql'].update(packages['drivers'])
154
155
  packages['sql'].update(packages['core'])
155
156
  packages['sql'].update(packages['gis'])
156
157
  packages['dash'] = {
157
- 'flask_compress' : 'Flask-Compress>=1.10.1',
158
- 'dash' : 'dash>=2.6.2',
158
+ 'flask_compress' : 'Flask-Compress>=1.17.0',
159
+ 'dash' : 'dash>=3.1.1',
159
160
  'dash_bootstrap_components' : 'dash-bootstrap-components>=1.7.1',
160
161
  'dash_ace' : 'dash-ace>=0.2.1',
161
- 'dash_extensions' : 'dash-extensions>=1.0.4',
162
- 'dash_daq' : 'dash-daq>=0.5.0',
163
- 'terminado' : 'terminado>=0.12.1',
164
- 'tornado' : 'tornado>=6.1.0',
162
+ 'dash_extensions' : 'dash-extensions>=2.0.4',
163
+ 'dash_daq' : 'dash-daq>=0.6.0',
164
+ 'terminado' : 'terminado>=0.18.1',
165
+ 'tornado' : 'tornado>=6.5.1',
165
166
  }
166
167
  packages['api'] = {
167
- 'uvicorn' : 'uvicorn[standard]>=0.29.0',
168
- 'gunicorn' : 'gunicorn>=22.0.0',
169
- 'dotenv' : 'python-dotenv>=0.20.0',
170
- 'websockets' : 'websockets>=11.0.3',
171
- 'fastapi' : 'fastapi>=0.111.0',
172
- 'fastapi_login' : 'fastapi-login>=1.7.2',
173
- 'multipart' : 'python-multipart>=0.0.9',
174
- 'httpx' : 'httpx>=0.27.2',
175
- 'httpcore' : 'httpcore>=1.0.6',
176
- 'valkey' : 'valkey>=6.0.0',
168
+ 'uvicorn' : 'uvicorn[standard]>=0.35.0',
169
+ 'gunicorn' : 'gunicorn>=23.0.0',
170
+ 'dotenv' : 'python-dotenv>=1.1.1',
171
+ 'websockets' : 'websockets>=15.0.1',
172
+ 'fastapi' : 'fastapi>=0.116.0',
173
+ 'fastapi_login' : 'fastapi-login>=1.10.3',
174
+ 'multipart' : 'python-multipart>=0.0.20',
175
+ 'httpx' : 'httpx>=0.28.1',
176
+ 'httpcore' : 'httpcore>=1.0.9',
177
+ 'valkey' : 'valkey>=6.1.0',
177
178
  }
178
179
  packages['api'].update(packages['sql'])
179
180
  packages['api'].update(packages['formatting'])
@@ -216,3 +217,9 @@ for group, import_names in packages.items():
216
217
  for import_name, install_name in import_names.items():
217
218
  _full[import_name] = install_name
218
219
  packages['full'] = _full
220
+
221
+ extras = {
222
+ group: list(import_names_install_names_map.values())
223
+ for group, import_names_install_names_map in packages.items()
224
+ if not group.startswith('_')
225
+ }
@@ -0,0 +1,181 @@
1
+ #! /usr/bin/env python3
2
+ # vim:fenc=utf-8
3
+
4
+ """
5
+ Define utilities for working with pipes.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, Callable
11
+ import re
12
+ import json
13
+ import ast
14
+ import copy
15
+ import uuid
16
+
17
+ from meerschaum.utils.typing import PipesDict, Optional, Any
18
+ import meerschaum as mrsm
19
+
20
+
21
+ def evaluate_pipe_access_chain(access_chain: str, pipe: mrsm.Pipe):
22
+ """
23
+ Safely evaluate the access chain on a Pipe.
24
+ """
25
+ expr = f"pipe{access_chain}"
26
+ tree = ast.parse(expr, mode='eval')
27
+
28
+ def _eval(node, context):
29
+ if isinstance(node, ast.Expression):
30
+ return _eval(node.body, context)
31
+
32
+ elif isinstance(node, ast.Name):
33
+ if node.id == "pipe":
34
+ return context
35
+ raise ValueError(f"Unknown variable: {node.id}")
36
+
37
+ elif isinstance(node, ast.Attribute):
38
+ value = _eval(node.value, context)
39
+ return getattr(value, node.attr)
40
+
41
+ elif isinstance(node, ast.Subscript):
42
+ value = _eval(node.value, context)
43
+ key = _eval(node.slice, context) if isinstance(node.slice, ast.Index) else _eval(node.slice, context)
44
+ return value[key]
45
+
46
+ elif isinstance(node, ast.Constant): # Python 3.8+
47
+ return node.value
48
+
49
+ elif isinstance(node, ast.Str): # Older Python
50
+ return node.s
51
+
52
+ elif isinstance(node, ast.Index): # Older Python AST style
53
+ return _eval(node.value, context)
54
+
55
+ else:
56
+ raise TypeError(f"Unsupported AST node: {ast.dump(node)}")
57
+
58
+ return _eval(tree, pipe)
59
+
60
+
61
+
62
+ def _evaluate_pipe_access_chain_from_match(pipe_match: re.Match) -> Any:
63
+ """
64
+ Helper function to evaluate a pipe from a regex match object.
65
+ """
66
+ from meerschaum.utils.warnings import warn
67
+ from meerschaum.utils.misc import parse_arguments_str
68
+ from meerschaum.utils.sql import sql_item_name
69
+ try:
70
+ args_str = pipe_match.group(1)
71
+ access_chain = pipe_match.group(2)
72
+ args, kwargs = parse_arguments_str(args_str)
73
+ pipe = mrsm.Pipe(*args, **kwargs)
74
+ except Exception as e:
75
+ warn(f"Failed to parse pipe from template string:\n{e}")
76
+ raise e
77
+
78
+ if not access_chain:
79
+ target = pipe.target
80
+ schema = (
81
+ pipe.instance_connector.get_pipe_schema(pipe)
82
+ if hasattr(pipe.instance_connector, 'get_pipe_schema')
83
+ else None
84
+ )
85
+ return (
86
+ sql_item_name(target, pipe.instance_connector.flavor, schema)
87
+ if pipe.instance_connector.type == 'sql'
88
+ else pipe.target
89
+ )
90
+
91
+ return evaluate_pipe_access_chain(access_chain, pipe)
92
+
93
+
94
+ def replace_pipes_syntax(text: str) -> Any:
95
+ """
96
+ Parse a string containing the `{{ Pipe() }}` syntax.
97
+ """
98
+ from meerschaum.utils.warnings import warn
99
+ from meerschaum.utils.sql import sql_item_name
100
+ from meerschaum.utils.dtypes import json_serialize_value
101
+ from meerschaum.utils.misc import parse_arguments_str
102
+ pattern = r'\{\{\s*(?:mrsm\.)?Pipe\((.*?)\)((?:\.[\w]+|\[[^\]]+\])*)\s*\}\}'
103
+
104
+ matches = list(re.finditer(pattern, text))
105
+ if not matches:
106
+ return text
107
+
108
+ placeholders = {}
109
+ for match in matches:
110
+ placeholder = f"__mrsm_pipe_placeholder_{uuid.uuid4().hex}__"
111
+ placeholders[placeholder] = match
112
+
113
+ substituted_text = text
114
+ for placeholder, match in placeholders.items():
115
+ substituted_text = substituted_text.replace(match.group(0), placeholder)
116
+
117
+ resolved_values = {}
118
+ for placeholder, match in placeholders.items():
119
+ try:
120
+ resolved_values[placeholder] = _evaluate_pipe_access_chain_from_match(match)
121
+ except Exception as e:
122
+ warn(f"Failed to resolve pipe syntax '{match.group(0)}': {e}")
123
+ resolved_values[placeholder] = match.group(0)
124
+
125
+ if len(matches) == 1:
126
+ match = matches[0]
127
+ placeholder = list(placeholders.keys())[0]
128
+ if text.strip() == match.group(0):
129
+ return resolved_values[placeholder]
130
+
131
+ final_text = substituted_text
132
+ for placeholder, value in resolved_values.items():
133
+ if isinstance(value, (dict, list, bool, int, float)) or value is None:
134
+ final_text = final_text.replace(placeholder, json.dumps(value, default=json_serialize_value))
135
+ else:
136
+ final_text = final_text.replace(placeholder, str(value))
137
+
138
+ return final_text
139
+
140
+
141
+ def replace_pipes_in_dict(
142
+ pipes: Optional[PipesDict] = None,
143
+ func: Callable[[Any], Any] = str,
144
+ debug: bool = False,
145
+ **kw
146
+ ) -> PipesDict:
147
+ """
148
+ Replace the Pipes in a Pipes dict with the result of another function.
149
+
150
+ Parameters
151
+ ----------
152
+ pipes: Optional[PipesDict], default None
153
+ The pipes dict to be processed.
154
+
155
+ func: Callable[[Any], Any], default str
156
+ The function to be applied to every pipe.
157
+ Defaults to the string constructor.
158
+
159
+ debug: bool, default False
160
+ Verbosity toggle.
161
+
162
+
163
+ Returns
164
+ -------
165
+ A dictionary where every pipe is replaced with the output of a function.
166
+
167
+ """
168
+ def change_dict(d : Dict[Any, Any], func : 'function') -> None:
169
+ for k, v in d.items():
170
+ if isinstance(v, dict):
171
+ change_dict(v, func)
172
+ else:
173
+ d[k] = func(v)
174
+
175
+ if pipes is None:
176
+ from meerschaum import get_pipes
177
+ pipes = get_pipes(debug=debug, **kw)
178
+
179
+ result = copy.deepcopy(pipes)
180
+ change_dict(result, func)
181
+ return result
@@ -18,7 +18,7 @@ import platform
18
18
 
19
19
  import meerschaum as mrsm
20
20
  from meerschaum.utils.typing import Union, Optional, Any, Callable, Dict, Tuple
21
- from meerschaum.config.static import STATIC_CONFIG
21
+ from meerschaum._internal.static import STATIC_CONFIG
22
22
 
23
23
  _child_processes = []
24
24
  def signal_handler(sig, frame):
@@ -399,6 +399,8 @@ def choose(
399
399
  noask = noask,
400
400
  **kw
401
401
  )
402
+ if not answer:
403
+ continue
402
404
  ### Split along the delimiter.
403
405
  _answers = [answer] if not multiple else [a for a in answer.split(delimiter)]
404
406
 
@@ -564,7 +566,7 @@ def check_noask(noask: bool = False) -> bool:
564
566
  """
565
567
  Flip `noask` to `True` if `MRSM_NOASK` is set.
566
568
  """
567
- from meerschaum.config.static import STATIC_CONFIG
569
+ from meerschaum._internal.static import STATIC_CONFIG
568
570
  NOASK = STATIC_CONFIG['environment']['noask']
569
571
  if noask:
570
572
  return True
@@ -14,6 +14,7 @@ import meerschaum as mrsm
14
14
  from meerschaum.utils.typing import Callable, Any, Optional, List, Dict
15
15
  from meerschaum.utils.warnings import warn, error
16
16
 
17
+
17
18
  STARTING_KEYWORD: str = 'starting'
18
19
  INTERVAL_UNITS: List[str] = ['months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'years']
19
20
  FREQUENCY_ALIASES: Dict[str, str] = {
@@ -292,7 +293,7 @@ def parse_start_time(schedule: str, now: Optional[datetime] = None) -> datetime:
292
293
  >>> parse_start_time('hourly starting 00:30')
293
294
  datetime.datetime(2024, 5, 13, 0, 30, tzinfo=datetime.timezone.utc)
294
295
  """
295
- from meerschaum.utils.misc import round_time
296
+ from meerschaum.utils.dtypes import round_time
296
297
  dateutil_parser = mrsm.attempt_import('dateutil.parser')
297
298
  starting_parts = schedule.split(STARTING_KEYWORD)
298
299
  starting_str = ('now' if len(starting_parts) == 1 else starting_parts[-1]).strip()