meerschaum 2.6.0.dev1__py3-none-any.whl → 2.6.1__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 (36) hide show
  1. meerschaum/api/dash/pages/login.py +17 -17
  2. meerschaum/api/dash/pipes.py +13 -4
  3. meerschaum/api/routes/_pipes.py +162 -136
  4. meerschaum/config/_version.py +1 -1
  5. meerschaum/config/static/__init__.py +1 -0
  6. meerschaum/connectors/api/_APIConnector.py +1 -0
  7. meerschaum/connectors/api/_pipes.py +46 -13
  8. meerschaum/connectors/sql/_SQLConnector.py +4 -3
  9. meerschaum/connectors/sql/_fetch.py +4 -2
  10. meerschaum/connectors/sql/_pipes.py +496 -148
  11. meerschaum/connectors/sql/_sql.py +37 -16
  12. meerschaum/connectors/valkey/_ValkeyConnector.py +3 -2
  13. meerschaum/connectors/valkey/_pipes.py +13 -5
  14. meerschaum/core/Pipe/__init__.py +20 -0
  15. meerschaum/core/Pipe/_attributes.py +179 -9
  16. meerschaum/core/Pipe/_clear.py +10 -8
  17. meerschaum/core/Pipe/_copy.py +2 -0
  18. meerschaum/core/Pipe/_data.py +57 -28
  19. meerschaum/core/Pipe/_deduplicate.py +30 -28
  20. meerschaum/core/Pipe/_dtypes.py +12 -2
  21. meerschaum/core/Pipe/_fetch.py +11 -9
  22. meerschaum/core/Pipe/_sync.py +24 -7
  23. meerschaum/core/Pipe/_verify.py +51 -48
  24. meerschaum/utils/dataframe.py +16 -8
  25. meerschaum/utils/dtypes/__init__.py +9 -1
  26. meerschaum/utils/dtypes/sql.py +32 -6
  27. meerschaum/utils/misc.py +8 -8
  28. meerschaum/utils/sql.py +485 -16
  29. {meerschaum-2.6.0.dev1.dist-info → meerschaum-2.6.1.dist-info}/METADATA +1 -1
  30. {meerschaum-2.6.0.dev1.dist-info → meerschaum-2.6.1.dist-info}/RECORD +36 -36
  31. {meerschaum-2.6.0.dev1.dist-info → meerschaum-2.6.1.dist-info}/LICENSE +0 -0
  32. {meerschaum-2.6.0.dev1.dist-info → meerschaum-2.6.1.dist-info}/NOTICE +0 -0
  33. {meerschaum-2.6.0.dev1.dist-info → meerschaum-2.6.1.dist-info}/WHEEL +0 -0
  34. {meerschaum-2.6.0.dev1.dist-info → meerschaum-2.6.1.dist-info}/entry_points.txt +0 -0
  35. {meerschaum-2.6.0.dev1.dist-info → meerschaum-2.6.1.dist-info}/top_level.txt +0 -0
  36. {meerschaum-2.6.0.dev1.dist-info → meerschaum-2.6.1.dist-info}/zip-safe +0 -0
@@ -19,13 +19,13 @@ allow_user_registration = permissions_config['registration']['users']
19
19
 
20
20
  registration_div = html.Div(
21
21
  id = 'registration-div',
22
- style = {'height' : '100%'},
23
- children = (
22
+ style={'height' : '100%'},
23
+ children=(
24
24
  [
25
25
  dcc.Link(
26
26
  'No account? Create one here.',
27
- href = (dash_endpoint + '/register'),
28
- refresh = False
27
+ href=(dash_endpoint + '/register'),
28
+ refresh=False
29
29
  ),
30
30
  ] if allow_user_registration
31
31
  else [
@@ -36,12 +36,12 @@ registration_div = html.Div(
36
36
  dbc.Button(
37
37
  'More information.',
38
38
  id = 'show-user-registration-disabled-button',
39
- color = 'link',
40
- size = 'sm',
39
+ color='link',
40
+ size='sm',
41
41
  ),
42
42
  dbc.Collapse(
43
- id = 'user-registration-disabled-collapse',
44
- children = [
43
+ id='user-registration-disabled-collapse',
44
+ children=[
45
45
  dcc.Markdown(
46
46
  "For example, to register user `newuser` on instance `sql:main`:"
47
47
  ),
@@ -60,10 +60,10 @@ registration_div = html.Div(
60
60
  html.Pre(
61
61
  html.Code(
62
62
  json.dumps({
63
- 'api' : {
64
- 'permissions' : {
65
- 'registration' : {
66
- 'users' : True,
63
+ 'api': {
64
+ 'permissions': {
65
+ 'registration': {
66
+ 'users': True,
67
67
  },
68
68
  }
69
69
  }
@@ -112,11 +112,11 @@ layout = dbc.Container([
112
112
  dbc.Row([
113
113
  dbc.Col([
114
114
  html.Button(
115
- children = 'Login',
116
- n_clicks = 0,
117
- type = 'submit',
118
- id = 'login-button',
119
- className = 'btn btn-primary btn-lg'
115
+ children='Login',
116
+ n_clicks=0,
117
+ type='submit',
118
+ id='login-button',
119
+ className='btn btn-primary btn-lg'
120
120
  ),
121
121
  ]),
122
122
  ]),
@@ -33,6 +33,11 @@ html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHEC
33
33
  humanfriendly = attempt_import('humanfriendly', check_update=CHECK_UPDATE)
34
34
  pd = import_pandas()
35
35
 
36
+ INDEX_PREFIX_EMOJI: Dict[str, str] = {
37
+ 'datetime': '🕓',
38
+ 'primary': '🔑',
39
+ }
40
+
36
41
 
37
42
  def pipe_from_ctx(ctx, trigger_property: str = 'n_clicks') -> Union[mrsm.Pipe, None]:
38
43
  """
@@ -420,9 +425,9 @@ def accordion_items_from_pipe(
420
425
  columns = pipe.columns
421
426
  index_column_names = pipe.get_indices()
422
427
  indices_rows = []
423
- for ix_key, ix_name in index_column_names.items():
428
+ for ix_key, ix_cols in indices.items():
424
429
  col = columns.get(ix_key, None)
425
- ix_cols = indices.get(ix_key, None)
430
+ ix_name = index_column_names.get(ix_key, None)
426
431
  if not col and not ix_cols:
427
432
  continue
428
433
  if not isinstance(ix_cols, (list, tuple)):
@@ -434,12 +439,16 @@ def accordion_items_from_pipe(
434
439
  else:
435
440
  col_item = html.Pre(html.Ul([html.Li(_c) for _c in ix_cols]))
436
441
  is_composite_item = "✅" if col else ""
437
- ix_key_item = html.Pre(ix_key) if ix_key != 'datetime' else html.Pre(f"🕓 {ix_key}")
442
+ ix_key_item = html.Pre(
443
+ INDEX_PREFIX_EMOJI.get(ix_key, '')
444
+ + (' ' if ix_key in INDEX_PREFIX_EMOJI else '')
445
+ + ix_key
446
+ )
438
447
  indices_rows.append(
439
448
  html.Tr([
440
449
  html.Td(ix_key_item),
441
450
  html.Td(col_item),
442
- html.Td(html.Pre(ix_name)),
451
+ html.Td(html.Pre(ix_name or '')),
443
452
  html.Td(is_composite_item),
444
453
  ])
445
454
  )
@@ -7,8 +7,16 @@ Register Pipes via the Meerschaum API.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- from meerschaum.utils.typing import Any, Optional, Dict, Union
11
10
 
11
+
12
+ import io
13
+ import json
14
+ import fastapi
15
+ from decimal import Decimal
16
+ import datetime
17
+
18
+ import meerschaum as mrsm
19
+ from meerschaum.utils.typing import Any, Optional, Dict, Union, List
12
20
  from meerschaum.api import (
13
21
  fastapi,
14
22
  app,
@@ -19,27 +27,27 @@ from meerschaum.api import (
19
27
  _get_pipes,
20
28
  manager,
21
29
  debug,
22
- no_auth, private,
30
+ no_auth,
31
+ private,
23
32
  )
24
- import json
25
- import fastapi
26
- from decimal import Decimal
27
33
  from meerschaum import Pipe
28
34
  from meerschaum.api.models import MetaPipe
29
35
  from meerschaum.utils.packages import attempt_import, import_pandas
30
- from meerschaum.utils.dataframe import get_numeric_cols, to_json
36
+ from meerschaum.utils.dataframe import get_numeric_cols, to_json, parse_df_datetimes
37
+ from meerschaum.utils.dtypes import are_dtypes_equal
31
38
  from meerschaum.utils.misc import (
32
- is_pipe_registered, round_time, is_int, parse_df_datetimes,
39
+ is_pipe_registered,
40
+ round_time,
41
+ is_int,
33
42
  replace_pipes_in_dict,
34
43
  )
35
- from meerschaum.utils.typing import List, Dict, Any, Union
36
44
  import meerschaum.core.User
37
- import datetime
38
- pipes_endpoint = endpoints['pipes']
39
- from fastapi.responses import StreamingResponse
40
- import io
45
+
46
+ fastapi_responses = attempt_import('fastapi.responses', lazy=False)
47
+ StreamingResponse = fastapi_responses.StreamingResponse
41
48
  dateutil_parser = attempt_import('dateutil.parser', lazy=False)
42
- pd = attempt_import('pandas')
49
+ pipes_endpoint = endpoints['pipes']
50
+ pd = attempt_import('pandas', lazy=False)
43
51
 
44
52
 
45
53
  @app.post(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/register', tags=['Pipes'])
@@ -79,13 +87,13 @@ def register_pipe(
79
87
 
80
88
  @app.delete(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/delete', tags=['Pipes'])
81
89
  def delete_pipe(
82
- connector_keys: str,
83
- metric_key: str,
84
- location_key: str,
85
- curr_user = (
86
- fastapi.Depends(manager) if not no_auth else None
87
- ),
88
- ):
90
+ connector_keys: str,
91
+ metric_key: str,
92
+ location_key: str,
93
+ curr_user = (
94
+ fastapi.Depends(manager) if not no_auth else None
95
+ ),
96
+ ):
89
97
  """
90
98
  Delete a Pipe (without dropping its table).
91
99
  """
@@ -141,15 +149,15 @@ def drop_pipe(
141
149
 
142
150
  @app.patch(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/edit', tags=['Pipes'])
143
151
  def edit_pipe(
144
- connector_keys: str,
145
- metric_key: str,
146
- location_key: str,
147
- parameters: dict,
148
- patch: bool = False,
149
- curr_user = (
150
- fastapi.Depends(manager) if not no_auth else None
151
- ),
152
- ):
152
+ connector_keys: str,
153
+ metric_key: str,
154
+ location_key: str,
155
+ parameters: dict,
156
+ patch: bool = False,
157
+ curr_user = (
158
+ fastapi.Depends(manager) if not no_auth else None
159
+ ),
160
+ ):
153
161
  """
154
162
  Edit an existing pipe.
155
163
  """
@@ -177,15 +185,15 @@ def edit_pipe(
177
185
 
178
186
  @app.get(pipes_endpoint + '/keys', tags=['Pipes'])
179
187
  async def fetch_pipes_keys(
180
- connector_keys: str = "[]",
181
- metric_keys: str = "[]",
182
- location_keys: str = "[]",
183
- tags: str = "[]",
184
- params: str = "{}",
185
- curr_user = (
186
- fastapi.Depends(manager) if not no_auth else None
187
- ),
188
- ):
188
+ connector_keys: str = "[]",
189
+ metric_keys: str = "[]",
190
+ location_keys: str = "[]",
191
+ tags: str = "[]",
192
+ params: str = "{}",
193
+ curr_user = (
194
+ fastapi.Depends(manager) if not no_auth else None
195
+ ),
196
+ ):
189
197
  """
190
198
  Get a list of tuples of all registered Pipes' keys.
191
199
  """
@@ -201,14 +209,14 @@ async def fetch_pipes_keys(
201
209
 
202
210
  @app.get(pipes_endpoint, tags=['Pipes'])
203
211
  async def get_pipes(
204
- connector_keys: str = "",
205
- metric_keys: str = "",
206
- location_keys: str = "",
207
- curr_user=(
208
- fastapi.Depends(manager) if not no_auth else None
209
- ),
210
- debug: bool = False,
211
- ) -> Dict[str, Any]:
212
+ connector_keys: str = "",
213
+ metric_keys: str = "",
214
+ location_keys: str = "",
215
+ curr_user=(
216
+ fastapi.Depends(manager) if not no_auth else None
217
+ ),
218
+ debug: bool = False,
219
+ ) -> Dict[str, Any]:
212
220
  """
213
221
  Get all registered Pipes with metadata, excluding parameters.
214
222
  """
@@ -223,11 +231,11 @@ async def get_pipes(
223
231
 
224
232
  @app.get(pipes_endpoint + '/{connector_keys}', tags=['Pipes'])
225
233
  async def get_pipes_by_connector(
226
- connector_keys: str,
227
- curr_user = (
228
- fastapi.Depends(manager) if not no_auth else None
229
- ),
230
- ) -> Dict[str, Any]:
234
+ connector_keys: str,
235
+ curr_user = (
236
+ fastapi.Depends(manager) if not no_auth else None
237
+ ),
238
+ ) -> Dict[str, Any]:
231
239
  """
232
240
  Get all registered Pipes by connector_keys with metadata, excluding parameters.
233
241
  """
@@ -240,13 +248,13 @@ async def get_pipes_by_connector(
240
248
 
241
249
  @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}', tags=['Pipes'])
242
250
  async def get_pipes_by_connector_and_metric(
243
- connector_keys: str,
244
- metric_key: str,
245
- parent: bool = False,
246
- curr_user = (
247
- fastapi.Depends(manager) if not no_auth else None
248
- ),
249
- ):
251
+ connector_keys: str,
252
+ metric_key: str,
253
+ parent: bool = False,
254
+ curr_user = (
255
+ fastapi.Depends(manager) if not no_auth else None
256
+ ),
257
+ ):
250
258
  """
251
259
  Get all registered Pipes by connector_keys and metric_key with metadata, excluding parameters.
252
260
 
@@ -269,13 +277,13 @@ async def get_pipes_by_connector_and_metric(
269
277
 
270
278
  @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}', tags=['Pipes'])
271
279
  async def get_pipes_by_connector_and_metric_and_location(
272
- connector_keys: str,
273
- metric_key: str,
274
- location_key: str,
275
- curr_user = (
276
- fastapi.Depends(manager) if not no_auth else None
277
- ),
278
- ):
280
+ connector_keys: str,
281
+ metric_key: str,
282
+ location_key: str,
283
+ curr_user = (
284
+ fastapi.Depends(manager) if not no_auth else None
285
+ ),
286
+ ):
279
287
  """
280
288
  Get a specific Pipe with metadata, excluding parameters.
281
289
  """
@@ -297,17 +305,17 @@ async def get_pipes_by_connector_and_metric_and_location(
297
305
 
298
306
  @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/sync_time', tags=['Pipes'])
299
307
  def get_sync_time(
300
- connector_keys: str,
301
- metric_key: str,
302
- location_key: str,
303
- params: Optional[Dict[str, Any]] = None,
304
- newest: bool = True,
305
- round_down: bool = True,
306
- debug: bool = False,
307
- curr_user = (
308
- fastapi.Depends(manager) if not no_auth else None
309
- ),
310
- ) -> Union[str, int, None]:
308
+ connector_keys: str,
309
+ metric_key: str,
310
+ location_key: str,
311
+ params: Optional[Dict[str, Any]] = None,
312
+ newest: bool = True,
313
+ round_down: bool = True,
314
+ debug: bool = False,
315
+ curr_user = (
316
+ fastapi.Depends(manager) if not no_auth else None
317
+ ),
318
+ ) -> Union[str, int, None]:
311
319
  """
312
320
  Get a Pipe's latest datetime value.
313
321
  See `meerschaum.Pipe.get_sync_time`.
@@ -328,20 +336,20 @@ def get_sync_time(
328
336
 
329
337
  @app.post(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/data', tags=['Pipes'])
330
338
  def sync_pipe(
331
- connector_keys: str,
332
- metric_key: str,
333
- location_key: str,
334
- data: dict = None,
335
- check_existing: bool = True,
336
- blocking: bool = True,
337
- force: bool = False,
338
- workers: Optional[int] = None,
339
- columns: Optional[str] = None,
340
- curr_user = (
341
- fastapi.Depends(manager) if not no_auth else None
342
- ),
343
- debug: bool = False,
344
- ) -> List[Union[bool, str]]:
339
+ connector_keys: str,
340
+ metric_key: str,
341
+ location_key: str,
342
+ data: dict = None,
343
+ check_existing: bool = True,
344
+ blocking: bool = True,
345
+ force: bool = False,
346
+ workers: Optional[int] = None,
347
+ columns: Optional[str] = None,
348
+ curr_user = (
349
+ fastapi.Depends(manager) if not no_auth else None
350
+ ),
351
+ debug: bool = False,
352
+ ) -> List[Union[bool, str]]:
345
353
  """
346
354
  Add data to an existing Pipe.
347
355
  See `meerschaum.Pipe.sync`.
@@ -365,11 +373,11 @@ def sync_pipe(
365
373
 
366
374
  result = list(p.sync(
367
375
  data,
368
- debug = debug,
369
- check_existing = check_existing,
370
- blocking = blocking,
371
- force = force,
372
- workers = workers
376
+ debug=debug,
377
+ check_existing=check_existing,
378
+ blocking=blocking,
379
+ force=force,
380
+ workers=workers,
373
381
  ))
374
382
  return result
375
383
 
@@ -558,7 +566,7 @@ def get_pipe_id(
558
566
  curr_user = (
559
567
  fastapi.Depends(manager) if not no_auth else None
560
568
  ),
561
- ) -> int:
569
+ ) -> Union[int, str]:
562
570
  """
563
571
  Get a Pipe's ID.
564
572
  """
@@ -573,36 +581,36 @@ def get_pipe_id(
573
581
  tags = ['Pipes']
574
582
  )
575
583
  def get_pipe_attributes(
576
- connector_keys : str,
577
- metric_key : str,
578
- location_key : str,
579
- curr_user = (
580
- fastapi.Depends(manager) if not no_auth else None
581
- ),
582
- ) -> Dict[str, Any]:
584
+ connector_keys: str,
585
+ metric_key: str,
586
+ location_key: str,
587
+ curr_user=(
588
+ fastapi.Depends(manager) if not no_auth else None
589
+ ),
590
+ ) -> Dict[str, Any]:
583
591
  """Get a Pipe's attributes."""
584
- return get_pipe(connector_keys, metric_key, location_key).attributes
592
+ return get_pipe(connector_keys, metric_key, location_key, refresh=True).attributes
585
593
 
586
594
 
587
595
  @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/exists', tags=['Pipes'])
588
596
  def get_pipe_exists(
589
- connector_keys: str,
590
- metric_key: str,
591
- location_key: str,
592
- curr_user = (
593
- fastapi.Depends(manager) if not no_auth else None
594
- ),
595
- ) -> bool:
597
+ connector_keys: str,
598
+ metric_key: str,
599
+ location_key: str,
600
+ curr_user = (
601
+ fastapi.Depends(manager) if not no_auth else None
602
+ ),
603
+ ) -> bool:
596
604
  """Determine whether a Pipe exists."""
597
605
  return get_pipe(connector_keys, metric_key, location_key).exists()
598
606
 
599
607
 
600
608
  @app.post(endpoints['metadata'], tags=['Pipes'])
601
609
  def create_metadata(
602
- curr_user = (
603
- fastapi.Depends(manager) if not no_auth else None
604
- ),
605
- ) -> bool:
610
+ curr_user = (
611
+ fastapi.Depends(manager) if not no_auth else None
612
+ ),
613
+ ) -> bool:
606
614
  """Create Pipe metadata tables"""
607
615
  from meerschaum.connectors.sql.tables import get_tables
608
616
  try:
@@ -614,16 +622,16 @@ def create_metadata(
614
622
 
615
623
  @app.get(pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/rowcount', tags=['Pipes'])
616
624
  def get_pipe_rowcount(
617
- connector_keys: str,
618
- metric_key: str,
619
- location_key: str,
620
- begin: Union[str, int, None] = None,
621
- end: Union[str, int, None] = None,
622
- params: Optional[Dict[str, Any]] = None,
623
- curr_user = (
624
- fastapi.Depends(manager) if not no_auth else None
625
- ),
626
- ) -> int:
625
+ connector_keys: str,
626
+ metric_key: str,
627
+ location_key: str,
628
+ begin: Union[str, int, None] = None,
629
+ end: Union[str, int, None] = None,
630
+ params: Optional[Dict[str, Any]] = None,
631
+ curr_user = (
632
+ fastapi.Depends(manager) if not no_auth else None
633
+ ),
634
+ ) -> int:
627
635
  """
628
636
  Return a pipe's rowcount.
629
637
  """
@@ -632,10 +640,10 @@ def get_pipe_rowcount(
632
640
  if is_int(end):
633
641
  end = int(end)
634
642
  return get_pipe(connector_keys, metric_key, location_key).get_rowcount(
635
- begin = begin,
636
- end = end,
637
- params = params,
638
- debug = debug
643
+ begin=begin,
644
+ end=end,
645
+ params=params,
646
+ debug=debug,
639
647
  )
640
648
 
641
649
 
@@ -644,13 +652,13 @@ def get_pipe_rowcount(
644
652
  tags=['Pipes']
645
653
  )
646
654
  def get_pipe_columns_types(
647
- connector_keys: str,
648
- metric_key: str,
649
- location_key: str,
650
- curr_user = (
651
- fastapi.Depends(manager) if not no_auth else None
652
- ),
653
- ) -> Dict[str, str]:
655
+ connector_keys: str,
656
+ metric_key: str,
657
+ location_key: str,
658
+ curr_user=(
659
+ fastapi.Depends(manager) if not no_auth else None
660
+ ),
661
+ ) -> Dict[str, str]:
654
662
  """
655
663
  Return a dictionary of column names and types.
656
664
 
@@ -663,3 +671,21 @@ def get_pipe_columns_types(
663
671
  ```
664
672
  """
665
673
  return get_pipe(connector_keys, metric_key, location_key).dtypes
674
+
675
+
676
+ @app.get(
677
+ pipes_endpoint + '/{connector_keys}/{metric_key}/{location_key}/columns/indices',
678
+ tags=['Pipes']
679
+ )
680
+ def get_pipe_columns_indices(
681
+ connector_keys: str,
682
+ metric_key: str,
683
+ location_key: str,
684
+ curr_user=(
685
+ fastapi.Depends(manager) if not no_auth else None
686
+ ),
687
+ ) -> Dict[str, List[Dict[str, str]]]:
688
+ """
689
+ Return a dictionary of column names and related indices.
690
+ """
691
+ return get_pipe(connector_keys, metric_key, location_key).get_columns_indices()
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.6.0.dev1"
5
+ __version__ = "2.6.1"
@@ -148,6 +148,7 @@ STATIC_CONFIG: Dict[str, Any] = {
148
148
  'min_ratio_columns_changed_for_full_astype': 0.5,
149
149
  },
150
150
  'exists_timeout_seconds': 5.0,
151
+ 'static_schema_cache_seconds': 60.0,
151
152
  },
152
153
  'jobs': {
153
154
  'check_restart_seconds': 1.0,
@@ -58,6 +58,7 @@ class APIConnector(Connector):
58
58
  drop_pipe,
59
59
  clear_pipe,
60
60
  get_pipe_columns_types,
61
+ get_pipe_columns_indices,
61
62
  )
62
63
  from ._fetch import fetch
63
64
  from ._plugins import (