meerschaum 2.9.5__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) 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 +33 -4
  5. meerschaum/_internal/cli/__init__.py +6 -0
  6. meerschaum/_internal/cli/daemons.py +103 -0
  7. meerschaum/_internal/cli/entry.py +220 -0
  8. meerschaum/_internal/cli/workers.py +435 -0
  9. meerschaum/_internal/docs/index.py +48 -2
  10. meerschaum/_internal/entry.py +50 -14
  11. meerschaum/_internal/shell/Shell.py +121 -29
  12. meerschaum/_internal/shell/__init__.py +4 -1
  13. meerschaum/_internal/static.py +359 -0
  14. meerschaum/_internal/term/TermPageHandler.py +1 -2
  15. meerschaum/_internal/term/__init__.py +40 -6
  16. meerschaum/_internal/term/tools.py +33 -8
  17. meerschaum/actions/__init__.py +6 -4
  18. meerschaum/actions/api.py +53 -13
  19. meerschaum/actions/attach.py +1 -0
  20. meerschaum/actions/bootstrap.py +8 -8
  21. meerschaum/actions/delete.py +4 -2
  22. meerschaum/actions/edit.py +171 -25
  23. meerschaum/actions/login.py +8 -8
  24. meerschaum/actions/register.py +143 -6
  25. meerschaum/actions/reload.py +22 -5
  26. meerschaum/actions/restart.py +14 -0
  27. meerschaum/actions/show.py +184 -31
  28. meerschaum/actions/start.py +166 -17
  29. meerschaum/actions/stop.py +38 -2
  30. meerschaum/actions/sync.py +7 -2
  31. meerschaum/actions/tag.py +9 -8
  32. meerschaum/actions/verify.py +5 -8
  33. meerschaum/api/__init__.py +45 -15
  34. meerschaum/api/_events.py +46 -4
  35. meerschaum/api/_oauth2.py +162 -9
  36. meerschaum/api/_tokens.py +102 -0
  37. meerschaum/api/dash/__init__.py +0 -3
  38. meerschaum/api/dash/callbacks/__init__.py +1 -0
  39. meerschaum/api/dash/callbacks/custom.py +4 -3
  40. meerschaum/api/dash/callbacks/dashboard.py +198 -118
  41. meerschaum/api/dash/callbacks/jobs.py +14 -7
  42. meerschaum/api/dash/callbacks/login.py +10 -1
  43. meerschaum/api/dash/callbacks/pipes.py +194 -14
  44. meerschaum/api/dash/callbacks/plugins.py +0 -1
  45. meerschaum/api/dash/callbacks/register.py +10 -3
  46. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  47. meerschaum/api/dash/callbacks/tokens.py +389 -0
  48. meerschaum/api/dash/components.py +36 -15
  49. meerschaum/api/dash/jobs.py +1 -1
  50. meerschaum/api/dash/keys.py +35 -93
  51. meerschaum/api/dash/pages/__init__.py +2 -1
  52. meerschaum/api/dash/pages/dashboard.py +1 -20
  53. meerschaum/api/dash/pages/{job.py → jobs.py} +10 -7
  54. meerschaum/api/dash/pages/login.py +2 -2
  55. meerschaum/api/dash/pages/pipes.py +16 -5
  56. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  57. meerschaum/api/dash/pages/tokens.py +53 -0
  58. meerschaum/api/dash/pipes.py +382 -95
  59. meerschaum/api/dash/sessions.py +12 -0
  60. meerschaum/api/dash/tokens.py +603 -0
  61. meerschaum/api/dash/websockets.py +1 -1
  62. meerschaum/api/dash/webterm.py +18 -6
  63. meerschaum/api/models/__init__.py +23 -3
  64. meerschaum/api/models/_actions.py +22 -0
  65. meerschaum/api/models/_pipes.py +91 -7
  66. meerschaum/api/models/_tokens.py +81 -0
  67. meerschaum/api/resources/static/js/terminado.js +3 -0
  68. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  69. meerschaum/api/resources/templates/termpage.html +13 -0
  70. meerschaum/api/routes/__init__.py +1 -0
  71. meerschaum/api/routes/_actions.py +3 -4
  72. meerschaum/api/routes/_connectors.py +3 -7
  73. meerschaum/api/routes/_jobs.py +26 -35
  74. meerschaum/api/routes/_login.py +120 -15
  75. meerschaum/api/routes/_misc.py +5 -10
  76. meerschaum/api/routes/_pipes.py +178 -143
  77. meerschaum/api/routes/_plugins.py +38 -28
  78. meerschaum/api/routes/_tokens.py +236 -0
  79. meerschaum/api/routes/_users.py +47 -35
  80. meerschaum/api/routes/_version.py +3 -3
  81. meerschaum/api/routes/_webterm.py +3 -3
  82. meerschaum/config/__init__.py +100 -30
  83. meerschaum/config/_default.py +132 -64
  84. meerschaum/config/_edit.py +38 -32
  85. meerschaum/config/_formatting.py +2 -0
  86. meerschaum/config/_patch.py +10 -8
  87. meerschaum/config/_paths.py +133 -13
  88. meerschaum/config/_read_config.py +87 -36
  89. meerschaum/config/_sync.py +6 -3
  90. meerschaum/config/_version.py +1 -1
  91. meerschaum/config/environment.py +262 -0
  92. meerschaum/config/stack/__init__.py +37 -15
  93. meerschaum/config/static.py +18 -0
  94. meerschaum/connectors/_Connector.py +11 -6
  95. meerschaum/connectors/__init__.py +41 -22
  96. meerschaum/connectors/api/_APIConnector.py +34 -6
  97. meerschaum/connectors/api/_actions.py +2 -2
  98. meerschaum/connectors/api/_jobs.py +12 -1
  99. meerschaum/connectors/api/_login.py +33 -7
  100. meerschaum/connectors/api/_misc.py +2 -2
  101. meerschaum/connectors/api/_pipes.py +23 -32
  102. meerschaum/connectors/api/_plugins.py +2 -2
  103. meerschaum/connectors/api/_request.py +1 -1
  104. meerschaum/connectors/api/_tokens.py +146 -0
  105. meerschaum/connectors/api/_users.py +70 -58
  106. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  107. meerschaum/connectors/instance/__init__.py +10 -0
  108. meerschaum/connectors/instance/_pipes.py +442 -0
  109. meerschaum/connectors/instance/_plugins.py +159 -0
  110. meerschaum/connectors/instance/_tokens.py +317 -0
  111. meerschaum/connectors/instance/_users.py +188 -0
  112. meerschaum/connectors/parse.py +5 -2
  113. meerschaum/connectors/sql/_SQLConnector.py +22 -5
  114. meerschaum/connectors/sql/_cli.py +12 -11
  115. meerschaum/connectors/sql/_create_engine.py +12 -168
  116. meerschaum/connectors/sql/_fetch.py +2 -18
  117. meerschaum/connectors/sql/_pipes.py +295 -278
  118. meerschaum/connectors/sql/_plugins.py +29 -0
  119. meerschaum/connectors/sql/_sql.py +46 -21
  120. meerschaum/connectors/sql/_users.py +36 -2
  121. meerschaum/connectors/sql/tables/__init__.py +254 -122
  122. meerschaum/connectors/valkey/_ValkeyConnector.py +5 -7
  123. meerschaum/connectors/valkey/_pipes.py +60 -31
  124. meerschaum/connectors/valkey/_plugins.py +2 -26
  125. meerschaum/core/Pipe/__init__.py +115 -85
  126. meerschaum/core/Pipe/_attributes.py +425 -124
  127. meerschaum/core/Pipe/_bootstrap.py +54 -24
  128. meerschaum/core/Pipe/_cache.py +555 -0
  129. meerschaum/core/Pipe/_clear.py +0 -11
  130. meerschaum/core/Pipe/_data.py +96 -68
  131. meerschaum/core/Pipe/_deduplicate.py +0 -13
  132. meerschaum/core/Pipe/_delete.py +12 -21
  133. meerschaum/core/Pipe/_drop.py +11 -23
  134. meerschaum/core/Pipe/_dtypes.py +49 -19
  135. meerschaum/core/Pipe/_edit.py +14 -4
  136. meerschaum/core/Pipe/_fetch.py +1 -1
  137. meerschaum/core/Pipe/_index.py +8 -14
  138. meerschaum/core/Pipe/_show.py +5 -5
  139. meerschaum/core/Pipe/_sync.py +123 -204
  140. meerschaum/core/Pipe/_verify.py +4 -4
  141. meerschaum/{plugins → core/Plugin}/_Plugin.py +16 -12
  142. meerschaum/core/Plugin/__init__.py +1 -1
  143. meerschaum/core/Token/_Token.py +220 -0
  144. meerschaum/core/Token/__init__.py +12 -0
  145. meerschaum/core/User/_User.py +35 -10
  146. meerschaum/core/User/__init__.py +9 -1
  147. meerschaum/core/__init__.py +1 -0
  148. meerschaum/jobs/_Executor.py +88 -4
  149. meerschaum/jobs/_Job.py +149 -38
  150. meerschaum/jobs/__init__.py +3 -2
  151. meerschaum/jobs/systemd.py +8 -3
  152. meerschaum/models/__init__.py +35 -0
  153. meerschaum/models/pipes.py +247 -0
  154. meerschaum/models/tokens.py +38 -0
  155. meerschaum/models/users.py +26 -0
  156. meerschaum/plugins/__init__.py +301 -88
  157. meerschaum/plugins/bootstrap.py +510 -4
  158. meerschaum/utils/_get_pipes.py +97 -30
  159. meerschaum/utils/daemon/Daemon.py +199 -43
  160. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  161. meerschaum/utils/daemon/RotatingFile.py +63 -36
  162. meerschaum/utils/daemon/StdinFile.py +53 -13
  163. meerschaum/utils/daemon/__init__.py +47 -6
  164. meerschaum/utils/daemon/_names.py +6 -3
  165. meerschaum/utils/dataframe.py +479 -81
  166. meerschaum/utils/debug.py +49 -19
  167. meerschaum/utils/dtypes/__init__.py +476 -34
  168. meerschaum/utils/dtypes/sql.py +369 -29
  169. meerschaum/utils/formatting/__init__.py +5 -2
  170. meerschaum/utils/formatting/_jobs.py +1 -1
  171. meerschaum/utils/formatting/_pipes.py +52 -50
  172. meerschaum/utils/formatting/_pprint.py +1 -0
  173. meerschaum/utils/formatting/_shell.py +44 -18
  174. meerschaum/utils/misc.py +268 -186
  175. meerschaum/utils/packages/__init__.py +25 -40
  176. meerschaum/utils/packages/_packages.py +42 -34
  177. meerschaum/utils/pipes.py +213 -0
  178. meerschaum/utils/process.py +2 -2
  179. meerschaum/utils/prompt.py +175 -144
  180. meerschaum/utils/schedule.py +2 -1
  181. meerschaum/utils/sql.py +134 -47
  182. meerschaum/utils/threading.py +42 -0
  183. meerschaum/utils/typing.py +1 -4
  184. meerschaum/utils/venv/_Venv.py +2 -2
  185. meerschaum/utils/venv/__init__.py +7 -7
  186. meerschaum/utils/warnings.py +19 -13
  187. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/METADATA +94 -96
  188. meerschaum-3.0.0.dist-info/RECORD +289 -0
  189. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/WHEEL +1 -1
  190. meerschaum-3.0.0.dist-info/licenses/NOTICE +2 -0
  191. meerschaum/api/models/_interfaces.py +0 -15
  192. meerschaum/api/models/_locations.py +0 -15
  193. meerschaum/api/models/_metrics.py +0 -15
  194. meerschaum/config/_environment.py +0 -145
  195. meerschaum/config/static/__init__.py +0 -186
  196. meerschaum-2.9.5.dist-info/RECORD +0 -263
  197. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/entry_points.txt +0 -0
  198. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/licenses/LICENSE +0 -0
  199. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/top_level.txt +0 -0
  200. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0.dist-info}/zip-safe +0 -0
@@ -16,7 +16,8 @@ from meerschaum.utils.formatting._shell import clear_screen
16
16
 
17
17
  FEATURE_CHOICES: Dict[str, str] = {
18
18
  'fetch' : 'Fetch data\n (e.g. extracting from a remote API)\n',
19
- 'connector': 'Custom connector\n (e.g. manage credentials)\n',
19
+ 'connector': 'Connector\n (fetch data & manage credentials)\n',
20
+ 'instance-connector': 'Instance connector\n (implement the pipes interface)\n',
20
21
  'action' : 'New actions\n (e.g. `mrsm sing song`)\n',
21
22
  'api' : 'New API endpoints\n (e.g. `POST /my/new/endpoint`)\n',
22
23
  'web' : 'New web console page\n (e.g. `/dash/my-web-app`)\n',
@@ -25,6 +26,7 @@ FEATURE_CHOICES: Dict[str, str] = {
25
26
  IMPORTS_LINES: Dict[str, str] = {
26
27
  'stdlib': (
27
28
  "from datetime import datetime, timedelta, timezone\n"
29
+ "from typing import Any, Union, List, Dict\n"
28
30
  ),
29
31
  'default': (
30
32
  "import meerschaum as mrsm\n"
@@ -33,6 +35,9 @@ IMPORTS_LINES: Dict[str, str] = {
33
35
  'connector': (
34
36
  "from meerschaum.connectors import Connector, make_connector\n"
35
37
  ),
38
+ 'instance-connector': (
39
+ "from meerschaum.connectors import InstanceConnector, make_connector\n"
40
+ ),
36
41
  'action': (
37
42
  "from meerschaum.actions import make_action\n"
38
43
  ),
@@ -106,6 +111,501 @@ FEATURE_LINES: Dict[str, str] = {
106
111
  " # populate docs with dictionaries (rows).\n"
107
112
  " return docs\n\n\n"
108
113
  ),
114
+ 'instance-connector': (
115
+ "@make_connector\n"
116
+ "class {plugin_name_capitalized}Connector(InstanceConnector):\n"
117
+ " \"\"\"Implement '{plugin_name_lower}' connectors.\"\"\"\n\n"
118
+ " REQUIRED_ATTRIBUTES: list[str] = []\n"
119
+ "\n"
120
+ " def fetch(\n"
121
+ " self,\n"
122
+ " pipe: mrsm.Pipe,\n"
123
+ " begin: datetime | None = None,\n"
124
+ " end: datetime | None = None,\n"
125
+ " **kwargs\n"
126
+ " ):\n"
127
+ " \"\"\"Return or yield dataframes.\"\"\"\n"
128
+ " docs = []\n"
129
+ " # populate docs with dictionaries (rows).\n"
130
+ " return docs\n"
131
+ """
132
+ def register_pipe(
133
+ self,
134
+ pipe: mrsm.Pipe,
135
+ debug: bool = False,
136
+ **kwargs: Any
137
+ ) -> mrsm.SuccessTuple:
138
+ \"\"\"
139
+ Insert the pipe's attributes into the internal `pipes` table.
140
+
141
+ Parameters
142
+ ----------
143
+ pipe: mrsm.Pipe
144
+ The pipe to be registered.
145
+
146
+ Returns
147
+ -------
148
+ A `SuccessTuple` of the result.
149
+ \"\"\"
150
+ attributes = {{
151
+ 'connector_keys': str(pipe.connector_keys),
152
+ 'metric_key': str(pipe.metric_key),
153
+ 'location_key': str(pipe.location_key),
154
+ 'parameters': pipe._attributes.get('parameters', {{}}),
155
+ }}
156
+
157
+ ### TODO insert `attributes` as a row in the pipes table.
158
+ # self.pipes_collection.insert_one(attributes)
159
+
160
+ return True, \"Success\"
161
+
162
+ def get_pipe_attributes(
163
+ self,
164
+ pipe: mrsm.Pipe,
165
+ debug: bool = False,
166
+ **kwargs: Any
167
+ ) -> dict[str, Any]:
168
+ \"\"\"
169
+ Return the pipe's document from the internal `pipes` collection.
170
+
171
+ Parameters
172
+ ----------
173
+ pipe: mrsm.Pipe
174
+ The pipe whose attributes should be retrieved.
175
+
176
+ Returns
177
+ -------
178
+ The document that matches the keys of the pipe.
179
+ \"\"\"
180
+ query = {{
181
+ 'connector_keys': str(pipe.connector_keys),
182
+ 'metric_key': str(pipe.metric_key),
183
+ 'location_key': str(pipe.location_key),
184
+ }}
185
+ ### TODO query the `pipes` table either using these keys or `get_pipe_id()`.
186
+ result = {{}}
187
+ # result = self.pipes_collection.find_one(query) or {{}}
188
+ return result
189
+
190
+ def get_pipe_id(
191
+ self,
192
+ pipe: mrsm.Pipe,
193
+ debug: bool = False,
194
+ **kwargs: Any
195
+ ) -> str | int | None:
196
+ \"\"\"
197
+ Return the ID for the pipe if it exists.
198
+
199
+ Parameters
200
+ ----------
201
+ pipe: mrsm.Pipe
202
+ The pipe whose ID to fetch.
203
+
204
+ Returns
205
+ -------
206
+ The ID for the pipe or `None`.
207
+ \"\"\"
208
+ query = {{
209
+ 'connector_keys': str(pipe.connector_keys),
210
+ 'metric_key': str(pipe.metric_key),
211
+ 'location_key': str(pipe.location_key),
212
+ }}
213
+ ### TODO fetch the ID mapped to this pipe.
214
+ return None
215
+
216
+ def edit_pipe(
217
+ self,
218
+ pipe: mrsm.Pipe,
219
+ debug: bool = False,
220
+ **kwargs: Any
221
+ ) -> mrsm.SuccessTuple:
222
+ \"\"\"
223
+ Edit the attributes of the pipe.
224
+
225
+ Parameters
226
+ ----------
227
+ pipe: mrsm.Pipe
228
+ The pipe whose in-memory parameters must be persisted.
229
+
230
+ Returns
231
+ -------
232
+ A `SuccessTuple` indicating success.
233
+ \"\"\"
234
+ query = {{
235
+ 'connector_keys': str(pipe.connector_keys),
236
+ 'metric_key': str(pipe.metric_key),
237
+ 'location_key': str(pipe.location_key),
238
+ }}
239
+ pipe_parameters = pipe._attributes.get('parameters', {{}})
240
+ ### TODO Update the row with new parameters.
241
+ # self.pipes_collection.update_one(query, {{'$set': {{'parameters': pipe_parameters}}}})
242
+ return True, "Success"
243
+
244
+ def delete_pipe(
245
+ self,
246
+ pipe: mrsm.Pipe,
247
+ debug: bool = False,
248
+ **kwargs: Any
249
+ ) -> mrsm.SuccessTuple:
250
+ \"\"\"
251
+ Delete a pipe's registration from the `pipes` collection.
252
+
253
+ Parameters
254
+ ----------
255
+ pipe: mrsm.Pipe
256
+ The pipe to be deleted.
257
+
258
+ Returns
259
+ -------
260
+ A `SuccessTuple` indicating success.
261
+ \"\"\"
262
+ ### TODO Delete the pipe's row from the pipes table.
263
+ # self.pipes_collection.delete_one({{'_id': pipe_id}})
264
+ return True, "Success"
265
+
266
+ def fetch_pipes_keys(
267
+ self,
268
+ connector_keys: list[str] | None = None,
269
+ metric_keys: list[str] | None = None,
270
+ location_keys: list[str] | None = None,
271
+ tags: list[str] | None = None,
272
+ debug: bool = False,
273
+ **kwargs: Any
274
+ ) -> list[tuple[str, str, str]]:
275
+ \"\"\"
276
+ Return a list of tuples for the registered pipes' keys according to the provided filters.
277
+
278
+ Parameters
279
+ ----------
280
+ connector_keys: list[str] | None, default None
281
+ The keys passed via `-c`.
282
+
283
+ metric_keys: list[str] | None, default None
284
+ The keys passed via `-m`.
285
+
286
+ location_keys: list[str] | None, default None
287
+ The keys passed via `-l`.
288
+
289
+ tags: List[str] | None, default None
290
+ Tags passed via `--tags` which are stored under `parameters:tags`.
291
+
292
+ Returns
293
+ -------
294
+ A list of connector, metric, and location keys in tuples.
295
+ You may return the string "None" for location keys in place of nulls.
296
+
297
+ Examples
298
+ --------
299
+ >>> import meerschaum as mrsm
300
+ >>> conn = mrsm.get_connector('example:demo')
301
+ >>>
302
+ >>> pipe_a = mrsm.Pipe('a', 'demo', tags=['foo'], instance=conn)
303
+ >>> pipe_b = mrsm.Pipe('b', 'demo', tags=['bar'], instance=conn)
304
+ >>> pipe_a.register()
305
+ >>> pipe_b.register()
306
+ >>>
307
+ >>> conn.fetch_pipes_keys(['a', 'b'])
308
+ [('a', 'demo', 'None'), ('b', 'demo', 'None')]
309
+ >>> conn.fetch_pipes_keys(metric_keys=['demo'])
310
+ [('a', 'demo', 'None'), ('b', 'demo', 'None')]
311
+ >>> conn.fetch_pipes_keys(tags=['foo'])
312
+ [('a', 'demo', 'None')]
313
+ >>> conn.fetch_pipes_keys(location_keys=[None])
314
+ [('a', 'demo', 'None'), ('b', 'demo', 'None')]
315
+
316
+ \"\"\"
317
+ from meerschaum.utils.misc import separate_negation_values
318
+
319
+ in_ck, nin_ck = separate_negation_values([str(val) for val in (connector_keys or [])])
320
+ in_mk, nin_mk = separate_negation_values([str(val) for val in (metric_keys or [])])
321
+ in_lk, nin_lk = separate_negation_values([str(val) for val in (location_keys or [])])
322
+ in_tags, nin_tags = separate_negation_values([str(val) for val in (tags or [])])
323
+
324
+ ### TODO build a query like so, only including clauses if the given list is not empty.
325
+ ### The `tags` clause is an OR ("?|"), meaning any of the tags may match.
326
+ ###
327
+ ###
328
+ ### SELECT connector_keys, metric_key, location_key
329
+ ### FROM pipes
330
+ ### WHERE connector_keys IN ({{in_ck}})
331
+ ### AND connector_keys NOT IN ({{nin_ck}})
332
+ ### AND metric_key IN ({{in_mk}})
333
+ ### AND metric_key NOT IN ({{nin_mk}})
334
+ ### AND location_key IN ({{in_lk}})
335
+ ### AND location_key NOT IN ({{nin_lk}})
336
+ ### AND (parameters->'tags')::JSONB ?| ARRAY[{{tags}}]
337
+ ### AND NOT (parameters->'tags')::JSONB ?| ARRAY[{{nin_tags}}]
338
+ return []
339
+
340
+ def pipe_exists(
341
+ self,
342
+ pipe: mrsm.Pipe,
343
+ debug: bool = False,
344
+ **kwargs: Any
345
+ ) -> bool:
346
+ \"\"\"
347
+ Check whether a pipe's target table exists.
348
+
349
+ Parameters
350
+ ----------
351
+ pipe: mrsm.Pipe
352
+ The pipe to check whether its table exists.
353
+
354
+ Returns
355
+ -------
356
+ A `bool` indicating the table exists.
357
+ \"\"\"
358
+ table_name = pipe.target
359
+ ### TODO write a query to determine the existence of `table_name`.
360
+ table_exists = False
361
+ return table_exists
362
+
363
+ def drop_pipe(
364
+ self,
365
+ pipe: mrsm.Pipe,
366
+ debug: bool = False,
367
+ **kwargs: Any
368
+ ) -> mrsm.SuccessTuple:
369
+ \"\"\"
370
+ Drop a pipe's collection if it exists.
371
+
372
+ Parameters
373
+ ----------
374
+ pipe: mrsm.Pipe
375
+ The pipe to be dropped.
376
+
377
+ Returns
378
+ -------
379
+ A `SuccessTuple` indicating success.
380
+ \"\"\"
381
+ ### TODO write a query to drop `table_name`.
382
+ table_name = pipe.target
383
+ return True, \"Success\"
384
+
385
+ def sync_pipe(
386
+ self,
387
+ pipe: mrsm.Pipe,
388
+ df: 'pd.DataFrame',
389
+ debug: bool = False,
390
+ **kwargs: Any
391
+ ) -> mrsm.SuccessTuple:
392
+ \"\"\"
393
+ Upsert new documents into the pipe's target table.
394
+
395
+ Parameters
396
+ ----------
397
+ pipe: mrsm.Pipe
398
+ The pipe to which the data should be upserted.
399
+
400
+ df: pd.DataFrame
401
+ The data to be synced.
402
+
403
+ Returns
404
+ -------
405
+ A `SuccessTuple` indicating success.
406
+ \"\"\"
407
+ ### TODO Write the upsert logic for the target table.
408
+ ### `pipe.filter_existing()` is provided for your convenience to
409
+ ### remove duplicates and separate inserts from updates.
410
+
411
+ unseen_df, update_df, delta_df = pipe.filter_existing(df, debug=debug)
412
+ return True, \"Success\"
413
+
414
+ def clear_pipe(
415
+ self,
416
+ pipe: mrsm.Pipe,
417
+ begin: datetime | int | None = None,
418
+ end: datetime | int | None = None,
419
+ params: dict[str, Any] | None = None,
420
+ debug: bool = False,
421
+ ) -> mrsm.SuccessTuple:
422
+ \"\"\"
423
+ Delete rows within `begin`, `end`, and `params`.
424
+
425
+ Parameters
426
+ ----------
427
+ pipe: mrsm.Pipe
428
+ The pipe whose rows to clear.
429
+
430
+ begin: datetime | int | None, default None
431
+ If provided, remove rows >= `begin`.
432
+
433
+ end: datetime | int | None, default None
434
+ If provided, remove rows < `end`.
435
+
436
+ params: dict[str, Any] | None, default None
437
+ If provided, only remove rows which match the `params` filter.
438
+
439
+ Returns
440
+ -------
441
+ A `SuccessTuple` indicating success.
442
+ \"\"\"
443
+ ### TODO Write a query to remove rows which match `begin`, `end`, and `params`.
444
+ return True, \"Success\"
445
+
446
+ def get_pipe_data(
447
+ self,
448
+ pipe: mrsm.Pipe,
449
+ select_columns: list[str] | None = None,
450
+ omit_columns: list[str] | None = None,
451
+ begin: datetime | int | None = None,
452
+ end: datetime | int | None = None,
453
+ params: dict[str, Any] | None = None,
454
+ debug: bool = False,
455
+ **kwargs: Any
456
+ ) -> Union['pd.DataFrame', None]:
457
+ \"\"\"
458
+ Query a pipe's target table and return the DataFrame.
459
+
460
+ Parameters
461
+ ----------
462
+ pipe: mrsm.Pipe
463
+ The pipe with the target table from which to read.
464
+
465
+ select_columns: list[str] | None, default None
466
+ If provided, only select these given columns.
467
+ Otherwise select all available columns (i.e. `SELECT *`).
468
+
469
+ omit_columns: list[str] | None, default None
470
+ If provided, remove these columns from the selection.
471
+
472
+ begin: datetime | int | None, default None
473
+ The earliest `datetime` value to search from (inclusive).
474
+
475
+ end: datetime | int | None, default None
476
+ The lastest `datetime` value to search from (exclusive).
477
+
478
+ params: dict[str | str] | None, default None
479
+ Additional filters to apply to the query.
480
+
481
+ Returns
482
+ -------
483
+ The target table's data as a DataFrame.
484
+ \"\"\"
485
+ if not pipe.exists(debug=debug):
486
+ return None
487
+
488
+ table_name = pipe.target
489
+ dt_col = pipe.columns.get(\"datetime\", None)
490
+
491
+ ### TODO Write a query to fetch from `table_name`
492
+ ### and apply the filters `begin`, `end`, and `params`.
493
+ ###
494
+ ### To improve performance, add logic to only read from
495
+ ### `select_columns` and not `omit_columns` (if provided).
496
+ ###
497
+ ### SELECT {{', '.join(cols_to_select)}}
498
+ ### FROM \"{{table_name}}\"
499
+ ### WHERE \"{{dt_col}}\" >= '{{begin}}'
500
+ ### AND \"{{dt_col}}\" < '{{end}}'
501
+
502
+ ### The function `parse_df_datetimes()` is a convenience function
503
+ ### to cast a list of dictionaries into a DataFrame and convert datetime columns.
504
+ from meerschaum.utils.dataframe import parse_df_datetimes
505
+ rows = []
506
+ return parse_df_datetimes(rows)
507
+
508
+ def get_sync_time(
509
+ self,
510
+ pipe: mrsm.Pipe,
511
+ params: dict[str, Any] | None = None,
512
+ newest: bool = True,
513
+ debug: bool = False,
514
+ **kwargs: Any
515
+ ) -> datetime | int | None:
516
+ \"\"\"
517
+ Return the most recent value for the `datetime` axis.
518
+
519
+ Parameters
520
+ ----------
521
+ pipe: mrsm.Pipe
522
+ The pipe whose collection contains documents.
523
+
524
+ params: dict[str, Any] | None, default None
525
+ Filter certain parameters when determining the sync time.
526
+
527
+ newest: bool, default True
528
+ If `True`, return the maximum value for the column.
529
+
530
+ Returns
531
+ -------
532
+ The largest `datetime` or `int` value of the `datetime` axis.
533
+ \"\"\"
534
+ ### TODO write a query to get the largest value for `dt_col`.
535
+ ### If `newest` is `False`, return the smallest value.
536
+ ### Apply the `params` filter in case of multiplexing.
537
+ return None
538
+
539
+ def get_pipe_columns_types(
540
+ self,
541
+ pipe: mrsm.Pipe,
542
+ debug: bool = False,
543
+ **kwargs: Any
544
+ ) -> dict[str, str]:
545
+ \"\"\"
546
+ Return the data types for the columns in the target table for data type enforcement.
547
+
548
+ Parameters
549
+ ----------
550
+ pipe: mrsm.Pipe
551
+ The pipe whose target table contains columns and data types.
552
+
553
+ Returns
554
+ -------
555
+ A dictionary mapping columns to data types.
556
+ \"\"\"
557
+ table_name = pipe.target
558
+ ### TODO write a query to fetch the columns contained in `table_name`.
559
+ columns_types = {{}}
560
+
561
+ ### Return a dictionary mapping the columns
562
+ ### to their Pandas dtypes, e.g.:
563
+ ### `{{'foo': 'int64'`}}`
564
+ ### or to SQL-style dtypes, e.g.:
565
+ ### `{{'bar': 'INT'}}`
566
+ return columns_types
567
+
568
+ def get_pipe_rowcount(
569
+ self,
570
+ pipe: mrsm.Pipe,
571
+ begin: datetime | int | None = None,
572
+ end: datetime | int | None = None,
573
+ params: dict[str, Any] | None = None,
574
+ remote: bool = False,
575
+ debug: bool = False,
576
+ **kwargs: Any
577
+ ) -> int:
578
+ \"\"\"
579
+ Return the rowcount for the pipe's table.
580
+
581
+ Parameters
582
+ ----------
583
+ pipe: mrsm.Pipe
584
+ The pipe whose table should be counted.
585
+
586
+ begin: datetime | int | None, default None
587
+ If provided, only count rows >= `begin`.
588
+
589
+ end: datetime | int | None, default None
590
+ If provided, only count rows < `end`.
591
+
592
+ params: dict[str, Any] | None
593
+ If provided, only count rows othat match the `params` filter.
594
+
595
+ remote: bool, default False
596
+ If `True`, return the rowcount for the pipe's fetch definition.
597
+ In this case, `self` refers to `Pipe.connector`, not `Pipe.instance_connector`.
598
+
599
+ Returns
600
+ -------
601
+ The rowcount for this pipe's table according the given parameters.
602
+ \"\"\"
603
+ ### TODO write a query to count how many rows exist in `table_name` according to the filters.
604
+ table_name = pipe.target
605
+ count = 0
606
+ return count
607
+ """
608
+ ),
109
609
  'action': (
110
610
  "@make_action\n"
111
611
  "def {action_name}(**kwargs) -> mrsm.SuccessTuple:\n"
@@ -129,9 +629,10 @@ FEATURE_LINES: Dict[str, str] = {
129
629
  " import dash.html as html\n"
130
630
  " import dash.dcc as dcc\n"
131
631
  " from dash import Input, Output, State, no_update\n"
632
+ " from dash.exceptions import PreventUpdate\n"
132
633
  " import dash_bootstrap_components as dbc\n\n"
133
634
  " # Create a new page at the path `/dash/{plugin_name}`.\n"
134
- " @web_page('{plugin_name}', login_required=False)\n"
635
+ " @web_page('{plugin_name}', page_group='{plugin_name}', login_required=False)\n"
135
636
  " def page_layout():\n"
136
637
  " \"\"\"Return the layout objects for this page.\"\"\"\n"
137
638
  " return dbc.Container([\n"
@@ -211,8 +712,10 @@ def bootstrap_plugin(
211
712
  body_text += FEATURE_LINES['header'].format(**plugin_labels)
212
713
  body_text += IMPORTS_LINES['stdlib'].format(**plugin_labels)
213
714
  body_text += IMPORTS_LINES['default'].format(**plugin_labels)
214
- if 'connector' in features:
715
+ if 'connector' in features and 'instance-connector' not in features:
215
716
  body_text += IMPORTS_LINES['connector'].format(**plugin_labels)
717
+ if 'instance-connector' in features:
718
+ body_text += IMPORTS_LINES['instance-connector'].format(**plugin_labels)
216
719
  if 'action' in features:
217
720
  body_text += IMPORTS_LINES['action'].format(**plugin_labels)
218
721
  if 'api' in features and 'web' in features:
@@ -230,9 +733,12 @@ def bootstrap_plugin(
230
733
  body_text += FEATURE_LINES['register'].format(**plugin_labels)
231
734
  body_text += FEATURE_LINES['fetch'].format(**plugin_labels)
232
735
 
233
- if 'connector' in features:
736
+ if 'connector' in features and 'instance-connector' not in features:
234
737
  body_text += FEATURE_LINES['connector'].format(**plugin_labels)
235
738
 
739
+ if 'instance-connector' in features:
740
+ body_text += FEATURE_LINES['instance-connector'].format(**plugin_labels)
741
+
236
742
  if 'action' in features:
237
743
  body_text += FEATURE_LINES['action'].format(**plugin_labels)
238
744