meerschaum 2.9.5__py3-none-any.whl → 3.0.0rc1__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 (153) 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 +17 -1
  5. meerschaum/_internal/entry.py +6 -6
  6. meerschaum/_internal/shell/Shell.py +1 -1
  7. meerschaum/_internal/static.py +372 -0
  8. meerschaum/actions/api.py +12 -2
  9. meerschaum/actions/bootstrap.py +7 -7
  10. meerschaum/actions/edit.py +142 -18
  11. meerschaum/actions/register.py +137 -6
  12. meerschaum/actions/show.py +117 -29
  13. meerschaum/actions/stop.py +4 -1
  14. meerschaum/actions/sync.py +1 -1
  15. meerschaum/actions/tag.py +9 -8
  16. meerschaum/api/__init__.py +9 -2
  17. meerschaum/api/_events.py +39 -2
  18. meerschaum/api/_oauth2.py +118 -8
  19. meerschaum/api/_tokens.py +102 -0
  20. meerschaum/api/dash/__init__.py +0 -1
  21. meerschaum/api/dash/callbacks/custom.py +2 -2
  22. meerschaum/api/dash/callbacks/dashboard.py +102 -18
  23. meerschaum/api/dash/callbacks/plugins.py +0 -1
  24. meerschaum/api/dash/callbacks/register.py +1 -1
  25. meerschaum/api/dash/callbacks/settings/__init__.py +1 -0
  26. meerschaum/api/dash/callbacks/settings/password_reset.py +2 -2
  27. meerschaum/api/dash/callbacks/settings/tokens.py +388 -0
  28. meerschaum/api/dash/components.py +30 -8
  29. meerschaum/api/dash/keys.py +19 -93
  30. meerschaum/api/dash/pages/dashboard.py +1 -20
  31. meerschaum/api/dash/pages/settings/__init__.py +1 -0
  32. meerschaum/api/dash/pages/settings/password_reset.py +1 -1
  33. meerschaum/api/dash/pages/settings/tokens.py +55 -0
  34. meerschaum/api/dash/pipes.py +94 -59
  35. meerschaum/api/dash/sessions.py +12 -0
  36. meerschaum/api/dash/tokens.py +606 -0
  37. meerschaum/api/dash/websockets.py +1 -1
  38. meerschaum/api/dash/webterm.py +4 -0
  39. meerschaum/api/models/__init__.py +23 -3
  40. meerschaum/api/models/_actions.py +22 -0
  41. meerschaum/api/models/_pipes.py +85 -7
  42. meerschaum/api/models/_tokens.py +81 -0
  43. meerschaum/api/resources/templates/termpage.html +12 -0
  44. meerschaum/api/routes/__init__.py +1 -0
  45. meerschaum/api/routes/_actions.py +3 -4
  46. meerschaum/api/routes/_connectors.py +3 -7
  47. meerschaum/api/routes/_jobs.py +14 -35
  48. meerschaum/api/routes/_login.py +49 -12
  49. meerschaum/api/routes/_misc.py +5 -10
  50. meerschaum/api/routes/_pipes.py +134 -111
  51. meerschaum/api/routes/_plugins.py +38 -28
  52. meerschaum/api/routes/_tokens.py +236 -0
  53. meerschaum/api/routes/_users.py +47 -35
  54. meerschaum/api/routes/_version.py +3 -3
  55. meerschaum/config/__init__.py +43 -20
  56. meerschaum/config/_default.py +32 -5
  57. meerschaum/config/_edit.py +28 -24
  58. meerschaum/config/_environment.py +1 -1
  59. meerschaum/config/_patch.py +6 -6
  60. meerschaum/config/_paths.py +5 -1
  61. meerschaum/config/_read_config.py +65 -34
  62. meerschaum/config/_sync.py +6 -3
  63. meerschaum/config/_version.py +1 -1
  64. meerschaum/config/stack/__init__.py +24 -5
  65. meerschaum/config/static.py +18 -0
  66. meerschaum/connectors/_Connector.py +10 -4
  67. meerschaum/connectors/__init__.py +4 -20
  68. meerschaum/connectors/api/_APIConnector.py +34 -6
  69. meerschaum/connectors/api/_actions.py +2 -2
  70. meerschaum/connectors/api/_jobs.py +1 -1
  71. meerschaum/connectors/api/_login.py +33 -7
  72. meerschaum/connectors/api/_misc.py +2 -2
  73. meerschaum/connectors/api/_pipes.py +15 -14
  74. meerschaum/connectors/api/_plugins.py +2 -2
  75. meerschaum/connectors/api/_request.py +1 -1
  76. meerschaum/connectors/api/_tokens.py +146 -0
  77. meerschaum/connectors/api/_users.py +70 -58
  78. meerschaum/connectors/instance/_InstanceConnector.py +83 -0
  79. meerschaum/connectors/instance/__init__.py +10 -0
  80. meerschaum/connectors/instance/_pipes.py +442 -0
  81. meerschaum/connectors/instance/_plugins.py +151 -0
  82. meerschaum/connectors/instance/_tokens.py +296 -0
  83. meerschaum/connectors/instance/_users.py +181 -0
  84. meerschaum/connectors/parse.py +4 -1
  85. meerschaum/connectors/sql/_SQLConnector.py +8 -5
  86. meerschaum/connectors/sql/_cli.py +12 -11
  87. meerschaum/connectors/sql/_create_engine.py +6 -154
  88. meerschaum/connectors/sql/_fetch.py +2 -18
  89. meerschaum/connectors/sql/_pipes.py +42 -31
  90. meerschaum/connectors/sql/_plugins.py +29 -0
  91. meerschaum/connectors/sql/_sql.py +8 -1
  92. meerschaum/connectors/sql/_users.py +29 -2
  93. meerschaum/connectors/sql/tables/__init__.py +1 -1
  94. meerschaum/connectors/valkey/_ValkeyConnector.py +2 -4
  95. meerschaum/connectors/valkey/_pipes.py +9 -10
  96. meerschaum/connectors/valkey/_plugins.py +2 -26
  97. meerschaum/core/Pipe/__init__.py +31 -14
  98. meerschaum/core/Pipe/_attributes.py +156 -58
  99. meerschaum/core/Pipe/_bootstrap.py +54 -24
  100. meerschaum/core/Pipe/_data.py +41 -1
  101. meerschaum/core/Pipe/_dtypes.py +29 -14
  102. meerschaum/core/Pipe/_edit.py +12 -4
  103. meerschaum/core/Pipe/_show.py +5 -5
  104. meerschaum/core/Pipe/_sync.py +48 -53
  105. meerschaum/core/Pipe/_verify.py +1 -1
  106. meerschaum/{plugins → core/Plugin}/_Plugin.py +9 -11
  107. meerschaum/core/Plugin/__init__.py +1 -1
  108. meerschaum/core/Token/_Token.py +221 -0
  109. meerschaum/core/Token/__init__.py +12 -0
  110. meerschaum/core/User/_User.py +34 -8
  111. meerschaum/core/User/__init__.py +9 -1
  112. meerschaum/core/__init__.py +1 -0
  113. meerschaum/jobs/_Job.py +3 -2
  114. meerschaum/jobs/__init__.py +3 -2
  115. meerschaum/jobs/systemd.py +1 -1
  116. meerschaum/models/__init__.py +35 -0
  117. meerschaum/models/pipes.py +247 -0
  118. meerschaum/models/tokens.py +38 -0
  119. meerschaum/models/users.py +26 -0
  120. meerschaum/plugins/__init__.py +22 -7
  121. meerschaum/plugins/bootstrap.py +2 -1
  122. meerschaum/utils/_get_pipes.py +68 -27
  123. meerschaum/utils/daemon/Daemon.py +2 -1
  124. meerschaum/utils/daemon/__init__.py +30 -2
  125. meerschaum/utils/dataframe.py +95 -14
  126. meerschaum/utils/dtypes/__init__.py +91 -18
  127. meerschaum/utils/dtypes/sql.py +44 -0
  128. meerschaum/utils/formatting/__init__.py +1 -1
  129. meerschaum/utils/formatting/_pipes.py +5 -4
  130. meerschaum/utils/formatting/_shell.py +11 -9
  131. meerschaum/utils/misc.py +237 -80
  132. meerschaum/utils/packages/__init__.py +3 -6
  133. meerschaum/utils/packages/_packages.py +34 -32
  134. meerschaum/utils/pipes.py +181 -0
  135. meerschaum/utils/process.py +1 -1
  136. meerschaum/utils/prompt.py +3 -1
  137. meerschaum/utils/schedule.py +1 -0
  138. meerschaum/utils/sql.py +114 -37
  139. meerschaum/utils/typing.py +1 -4
  140. meerschaum/utils/venv/_Venv.py +2 -2
  141. meerschaum/utils/venv/__init__.py +5 -7
  142. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/METADATA +88 -80
  143. meerschaum-3.0.0rc1.dist-info/RECORD +282 -0
  144. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/WHEEL +1 -1
  145. meerschaum/api/models/_interfaces.py +0 -15
  146. meerschaum/api/models/_locations.py +0 -15
  147. meerschaum/api/models/_metrics.py +0 -15
  148. meerschaum/config/static/__init__.py +0 -186
  149. meerschaum-2.9.5.dist-info/RECORD +0 -263
  150. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/entry_points.txt +0 -0
  151. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
  152. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/top_level.txt +0 -0
  153. {meerschaum-2.9.5.dist-info → meerschaum-3.0.0rc1.dist-info}/zip-safe +0 -0
@@ -7,171 +7,23 @@ This module contains the logic that builds the sqlalchemy engine string.
7
7
  """
8
8
 
9
9
  import traceback
10
+
10
11
  import meerschaum as mrsm
11
12
  from meerschaum.utils.debug import dprint
13
+ from meerschaum._internal.static import STATIC_CONFIG
12
14
 
13
- ### determine driver and requirements from flavor
14
- default_requirements = {
15
- 'username',
16
- 'password',
17
- 'host',
18
- 'database',
19
- }
20
15
 
21
16
  ### NOTE: These are defined in the `system.json` config file and so this dictionary's values
22
17
  ### will all be overwritten if applicable.
23
- default_create_engine_args = {
24
- # 'method': 'multi',
25
- 'pool_size': 5,
26
- 'max_overflow': 10,
27
- 'pool_recycle': 3600,
28
- 'connect_args': {},
29
- }
30
- flavor_configs = {
31
- 'timescaledb': {
32
- 'engine': 'postgresql+psycopg',
33
- 'create_engine': default_create_engine_args,
34
- 'omit_create_engine': {'method',},
35
- 'to_sql': {},
36
- 'requirements': default_requirements,
37
- 'defaults': {
38
- 'port': 5432,
39
- },
40
- },
41
- 'postgresql': {
42
- 'engine': 'postgresql+psycopg',
43
- 'create_engine': default_create_engine_args,
44
- 'omit_create_engine': {'method',},
45
- 'to_sql': {},
46
- 'requirements': default_requirements,
47
- 'defaults': {
48
- 'port': 5432,
49
- },
50
- },
51
- 'postgis': {
52
- 'engine': 'postgresql+psycopg',
53
- 'create_engine': default_create_engine_args,
54
- 'omit_create_engine': {'method',},
55
- 'to_sql': {},
56
- 'requirements': default_requirements,
57
- 'defaults': {
58
- 'port': 5432,
59
- },
60
- },
61
- 'citus': {
62
- 'engine': 'postgresql+psycopg',
63
- 'create_engine': default_create_engine_args,
64
- 'omit_create_engine': {'method',},
65
- 'to_sql': {},
66
- 'requirements': default_requirements,
67
- 'defaults': {
68
- 'port': 5432,
69
- },
70
- },
71
- 'mssql': {
72
- 'engine': 'mssql+pyodbc',
73
- 'create_engine': {
74
- 'fast_executemany': True,
75
- 'use_insertmanyvalues': False,
76
- 'isolation_level': 'AUTOCOMMIT',
77
- 'use_setinputsizes': False,
78
- 'pool_pre_ping': True,
79
- 'ignore_no_transaction_on_rollback': True,
80
- },
81
- 'omit_create_engine': {'method',},
82
- 'to_sql': {
83
- 'method': None,
84
- },
85
- 'requirements': default_requirements,
86
- 'defaults': {
87
- 'port': 1433,
88
- 'options': (
89
- "driver=ODBC Driver 18 for SQL Server"
90
- "&UseFMTONLY=Yes"
91
- "&TrustServerCertificate=yes"
92
- "&Encrypt=no"
93
- "&MARS_Connection=yes"
94
- ),
95
- },
96
- },
97
- 'mysql': {
98
- 'engine': 'mysql+pymysql',
99
- 'create_engine': default_create_engine_args,
100
- 'omit_create_engine': {'method',},
101
- 'to_sql': {
102
- 'method': 'multi',
103
- },
104
- 'requirements': default_requirements,
105
- 'defaults': {
106
- 'port': 3306,
107
- },
108
- },
109
- 'mariadb': {
110
- 'engine': 'mysql+pymysql',
111
- 'create_engine': default_create_engine_args,
112
- 'omit_create_engine': {'method',},
113
- 'to_sql': {
114
- 'method': 'multi',
115
- },
116
- 'requirements': default_requirements,
117
- 'defaults': {
118
- 'port': 3306,
119
- },
120
- },
121
- 'oracle': {
122
- 'engine': 'oracle+oracledb',
123
- 'create_engine': default_create_engine_args,
124
- 'omit_create_engine': {'method',},
125
- 'to_sql': {
126
- 'method': None,
127
- },
128
- 'requirements': default_requirements,
129
- 'defaults': {
130
- 'port': 1521,
131
- },
132
- },
133
- 'sqlite': {
134
- 'engine': 'sqlite',
135
- 'create_engine': default_create_engine_args,
136
- 'omit_create_engine': {'method',},
137
- 'to_sql': {
138
- 'method': 'multi',
139
- },
140
- 'requirements': {'database'},
141
- 'defaults': {},
142
- },
143
- 'duckdb': {
144
- 'engine': 'duckdb',
145
- 'create_engine': {},
146
- 'omit_create_engine': {'ALL',},
147
- 'to_sql': {
148
- 'method': 'multi',
149
- },
150
- 'requirements': '',
151
- 'defaults': {},
152
- },
153
- 'cockroachdb': {
154
- 'engine': 'cockroachdb',
155
- 'omit_create_engine': {'method',},
156
- 'create_engine': default_create_engine_args,
157
- 'to_sql': {
158
- 'method': 'multi',
159
- },
160
- 'requirements': {'host'},
161
- 'defaults': {
162
- 'port': 26257,
163
- 'database': 'defaultdb',
164
- 'username': 'root',
165
- 'password': 'admin',
166
- },
167
- },
168
- }
18
+
19
+ flavor_configs = STATIC_CONFIG['sql']['create_engine_flavors']
169
20
  install_flavor_drivers = {
170
21
  'sqlite': ['aiosqlite'],
171
22
  'duckdb': ['duckdb', 'duckdb_engine'],
172
23
  'mysql': ['pymysql'],
173
24
  'mariadb': ['pymysql'],
174
25
  'timescaledb': ['psycopg'],
26
+ 'timescaledb-ha': ['psycopg', 'geoalchemy'],
175
27
  'postgresql': ['psycopg'],
176
28
  'postgis': ['psycopg', 'geoalchemy'],
177
29
  'citus': ['psycopg'],
@@ -268,7 +120,7 @@ def create_engine(
268
120
 
269
121
  ### Sometimes the timescaledb:// flavor can slip in.
270
122
  if _uri and self.flavor in _uri:
271
- if self.flavor in ('timescaledb', 'postgis'):
123
+ if self.flavor in ('timescaledb', 'timescaledb-ha', 'postgis'):
272
124
  engine_str = engine_str.replace(self.flavor, 'postgresql', 1)
273
125
  elif _uri.startswith('postgresql://'):
274
126
  engine_str = engine_str.replace('postgresql://', 'postgresql+psycopg2://')
@@ -230,11 +230,9 @@ def get_pipe_query(pipe: mrsm.Pipe, warn: bool = True) -> Union[str, None]:
230
230
  - query
231
231
  - sql
232
232
  """
233
- import re
234
233
  import textwrap
235
234
  from meerschaum.utils.warnings import warn as _warn
236
- from meerschaum.utils.misc import parse_arguments_str
237
- from meerschaum.utils.sql import sql_item_name
235
+ from meerschaum.utils.pipes import replace_pipes_syntax
238
236
  if pipe.parameters.get('fetch', {}).get('definition', None):
239
237
  definition = pipe.parameters['fetch']['definition']
240
238
  elif pipe.parameters.get('definition', None):
@@ -251,21 +249,7 @@ def get_pipe_query(pipe: mrsm.Pipe, warn: bool = True) -> Union[str, None]:
251
249
  )
252
250
  return None
253
251
 
254
- def replace_pipe_match(pipe_match):
255
- try:
256
- args_str = pipe_match.group(1)
257
- args, kwargs = parse_arguments_str(args_str)
258
- pipe = mrsm.Pipe(*args, **kwargs)
259
- except Exception as e:
260
- if warn:
261
- _warn(f"Failed to parse pipe from SQL definition:\n{e}")
262
- raise e
263
-
264
- target = pipe.target
265
- schema = pipe.instance_connector.get_pipe_schema(pipe)
266
- return sql_item_name(target, pipe.instance_connector.flavor, schema)
267
-
268
- definition = re.sub(r'\{\{Pipe\((.*?)\)\}\}', replace_pipe_match, definition)
252
+ definition = replace_pipes_syntax(definition)
269
253
  return textwrap.dedent(definition.lstrip().rstrip())
270
254
 
271
255
 
@@ -45,7 +45,7 @@ def register_pipe(
45
45
  ### (which shouldn't be able to be registered anyway but that's an issue for later).
46
46
  parameters = None
47
47
  try:
48
- parameters = pipe.parameters
48
+ parameters = pipe.get_parameters(apply_symlinks=False)
49
49
  except Exception as e:
50
50
  if debug:
51
51
  dprint(str(e))
@@ -76,7 +76,7 @@ def register_pipe(
76
76
 
77
77
  def edit_pipe(
78
78
  self,
79
- pipe : mrsm.Pipe = None,
79
+ pipe: mrsm.Pipe,
80
80
  patch: bool = False,
81
81
  debug: bool = False,
82
82
  **kw : Any
@@ -108,10 +108,10 @@ def edit_pipe(
108
108
  original_parameters = Pipe(
109
109
  pipe.connector_keys, pipe.metric_key, pipe.location_key,
110
110
  mrsm_instance=pipe.instance_keys
111
- ).parameters
111
+ ).get_parameters(apply_symlinks=False)
112
112
  parameters = apply_patch_to_config(
113
113
  original_parameters,
114
- pipe.parameters
114
+ pipe._attributes['parameters']
115
115
  )
116
116
 
117
117
  ### ensure pipes table exists
@@ -174,7 +174,7 @@ def fetch_pipes_keys(
174
174
  from meerschaum.utils.packages import attempt_import
175
175
  from meerschaum.utils.misc import separate_negation_values
176
176
  from meerschaum.utils.sql import OMIT_NULLSFIRST_FLAVORS, table_exists
177
- from meerschaum.config.static import STATIC_CONFIG
177
+ from meerschaum._internal.static import STATIC_CONFIG
178
178
  import json
179
179
  from copy import deepcopy
180
180
  sqlalchemy, sqlalchemy_sql_functions = attempt_import(
@@ -603,7 +603,10 @@ def get_create_index_queries(
603
603
  ### create datetime index
604
604
  dt_query = None
605
605
  if _datetime is not None:
606
- if self.flavor == 'timescaledb' and pipe.parameters.get('hypertable', True):
606
+ if (
607
+ self.flavor in ('timescaledb', 'timescaledb-ha')
608
+ and pipe.parameters.get('hypertable', True)
609
+ ):
607
610
  _id_count = (
608
611
  get_distinct_col_count(_id, f"SELECT {_id_name} FROM {_pipe_name}", self)
609
612
  if (_id is not None and _create_space_partition) else None
@@ -719,7 +722,7 @@ def get_create_index_queries(
719
722
  f"ADD CONSTRAINT {primary_key_constraint_name} PRIMARY KEY ({primary_key_name})"
720
723
  )
721
724
  ])
722
- elif self.flavor == 'timescaledb':
725
+ elif self.flavor in ('timescaledb', 'timescaledb-ha'):
723
726
  primary_queries.extend([
724
727
  (
725
728
  f"ALTER TABLE {_pipe_name}\n"
@@ -758,7 +761,7 @@ def get_create_index_queries(
758
761
 
759
762
  ### create id index
760
763
  if _id_name is not None:
761
- if self.flavor == 'timescaledb':
764
+ if self.flavor in ('timescaledb', 'timescaledb-ha'):
762
765
  ### Already created indices via create_hypertable.
763
766
  id_query = (
764
767
  None if (_id is not None and _create_space_partition)
@@ -797,7 +800,7 @@ def get_create_index_queries(
797
800
 
798
801
  cols_names_str = ", ".join(cols_names)
799
802
  index_query_params_clause = f" ({cols_names_str})"
800
- if self.flavor == 'postgis':
803
+ if self.flavor in ('postgis', 'timescaledb-ha'):
801
804
  for col in cols:
802
805
  col_typ = existing_cols_pd_types.get(cols[0], 'object')
803
806
  if col_typ != 'object' and are_dtypes_equal(col_typ, 'geometry'):
@@ -1073,14 +1076,14 @@ def get_pipe_data(
1073
1076
 
1074
1077
  cols_types = pipe.get_columns_types(debug=debug) if pipe.enforce else {}
1075
1078
  dtypes = {
1079
+ **{
1080
+ col: get_pd_type_from_db_type(typ)
1081
+ for col, typ in cols_types.items()
1082
+ },
1076
1083
  **{
1077
1084
  p_col: to_pandas_dtype(p_typ)
1078
1085
  for p_col, p_typ in pipe.dtypes.items()
1079
1086
  },
1080
- **{
1081
- col: get_pd_type_from_db_type(typ)
1082
- for col, typ in cols_types.items()
1083
- }
1084
1087
  } if pipe.enforce else {}
1085
1088
  if dtypes:
1086
1089
  if self.flavor == 'sqlite':
@@ -1419,7 +1422,7 @@ def get_pipe_data_query(
1419
1422
  if k in existing_cols or skip_existing_cols_check
1420
1423
  }
1421
1424
  if valid_params:
1422
- where += build_where(valid_params, self).replace(
1425
+ where += ' ' + build_where(valid_params, self).lstrip().replace(
1423
1426
  'WHERE', (' AND' if is_dt_bound else " ")
1424
1427
  )
1425
1428
 
@@ -1648,8 +1651,8 @@ def sync_pipe(
1648
1651
  self,
1649
1652
  pipe: mrsm.Pipe,
1650
1653
  df: Union[pd.DataFrame, str, Dict[Any, Any], None] = None,
1651
- begin: Optional[datetime] = None,
1652
- end: Optional[datetime] = None,
1654
+ begin: Union[datetime, int, None] = None,
1655
+ end: Union[datetime, int, None] = None,
1653
1656
  chunksize: Optional[int] = -1,
1654
1657
  check_existing: bool = True,
1655
1658
  blocking: bool = True,
@@ -1669,11 +1672,11 @@ def sync_pipe(
1669
1672
  An optional DataFrame or equivalent to sync into the pipe.
1670
1673
  Defaults to `None`.
1671
1674
 
1672
- begin: Optional[datetime], default None
1675
+ begin: Union[datetime, int, None], default None
1673
1676
  Optionally specify the earliest datetime to search for data.
1674
1677
  Defaults to `None`.
1675
1678
 
1676
- end: Optional[datetime], default None
1679
+ end: Union[datetime, int, None], default None
1677
1680
  Optionally specify the latest datetime to search for data.
1678
1681
  Defaults to `None`.
1679
1682
 
@@ -1833,10 +1836,12 @@ def sync_pipe(
1833
1836
  )
1834
1837
  )
1835
1838
  if autoincrement and autoincrement not in pipe.parameters:
1836
- pipe.parameters['autoincrement'] = autoincrement
1837
- edit_success, edit_msg = pipe.edit(debug=debug)
1838
- if not edit_success:
1839
- return edit_success, edit_msg
1839
+ update_success, update_msg = pipe.update_parameters(
1840
+ {'autoincrement': autoincrement},
1841
+ debug=debug,
1842
+ )
1843
+ if not update_success:
1844
+ return update_success, update_msg
1840
1845
 
1841
1846
  def _check_pk(_df_to_clear):
1842
1847
  if _df_to_clear is None:
@@ -1969,7 +1974,11 @@ def sync_pipe(
1969
1974
  if col and col in existing_cols
1970
1975
  ] if not primary_key or self.flavor == 'oracle' else (
1971
1976
  [dt_col, primary_key]
1972
- if self.flavor == 'timescaledb' and dt_col and dt_col in update_df.columns
1977
+ if (
1978
+ self.flavor in ('timescaledb', 'timescaledb-ha')
1979
+ and dt_col
1980
+ and dt_col in update_df.columns
1981
+ )
1973
1982
  else [primary_key]
1974
1983
  )
1975
1984
  update_queries = get_update_queries(
@@ -2833,7 +2842,6 @@ def get_pipe_rowcount(
2833
2842
  error(msg)
2834
2843
  return None
2835
2844
 
2836
-
2837
2845
  flavor = self.flavor if not remote else pipe.connector.flavor
2838
2846
  conn = self if not remote else pipe.connector
2839
2847
  _pipe_name = sql_item_name(pipe.target, flavor, self.get_pipe_schema(pipe))
@@ -3412,12 +3420,13 @@ def get_alter_columns_queries(
3412
3420
 
3413
3421
  if numeric_cols:
3414
3422
  pipe.dtypes.update({col: 'numeric' for col in numeric_cols})
3415
- edit_success, edit_msg = pipe.edit(debug=debug)
3416
- if not edit_success:
3417
- warn(
3418
- f"Failed to update dtypes for numeric columns {items_str(numeric_cols)}:\n"
3419
- + f"{edit_msg}"
3420
- )
3423
+ if not pipe.temporary:
3424
+ edit_success, edit_msg = pipe.edit(debug=debug)
3425
+ if not edit_success:
3426
+ warn(
3427
+ f"Failed to update dtypes for numeric columns {items_str(numeric_cols)}:\n"
3428
+ + f"{edit_msg}"
3429
+ )
3421
3430
  else:
3422
3431
  numeric_cols.extend([col for col, typ in pipe.dtypes.items() if typ.startswith('numeric')])
3423
3432
 
@@ -3881,13 +3890,15 @@ def get_pipe_schema(self, pipe: mrsm.Pipe) -> Union[str, None]:
3881
3890
  -------
3882
3891
  A schema string or `None` if nothing is configured.
3883
3892
  """
3893
+ if self.flavor == 'sqlite':
3894
+ return self.schema
3884
3895
  return pipe.parameters.get('schema', self.schema)
3885
3896
 
3886
3897
 
3887
3898
  @staticmethod
3888
3899
  def get_temporary_target(
3889
3900
  target: str,
3890
- transact_id: Optional[str, None] = None,
3901
+ transact_id: Optional[str] = None,
3891
3902
  label: Optional[str] = None,
3892
3903
  separator: Optional[str] = None,
3893
3904
  ) -> str:
@@ -13,6 +13,35 @@ import json
13
13
  import meerschaum as mrsm
14
14
  from meerschaum.utils.typing import Optional, Any, List, SuccessTuple, Dict
15
15
 
16
+
17
+ def get_plugins_pipe(self) -> mrsm.Pipe:
18
+ """
19
+ Return the internal metadata plugins pipe.
20
+ """
21
+ users_pipe = self.get_users_pipe()
22
+ user_id_dtype = users_pipe.dtypes.get('user_id', 'int')
23
+ return mrsm.Pipe(
24
+ 'mrsm', 'plugins',
25
+ instance=self,
26
+ temporary=True,
27
+ static=True,
28
+ null_indices=False,
29
+ columns={
30
+ 'primary': 'plugin_id',
31
+ 'user_id': 'user_id',
32
+ },
33
+ dtypes={
34
+ 'plugin_name': 'string',
35
+ 'user_id': user_id_dtype,
36
+ 'attributes': 'json',
37
+ 'version': 'string',
38
+ },
39
+ indices={
40
+ 'unique': 'plugin_name',
41
+ },
42
+ )
43
+
44
+
16
45
  def register_plugin(
17
46
  self,
18
47
  plugin: 'mrsm.core.Plugin',
@@ -17,7 +17,14 @@ from meerschaum.utils.debug import dprint
17
17
  from meerschaum.utils.warnings import warn
18
18
 
19
19
  ### database flavors that can use bulk insert
20
- _bulk_flavors = {'postgresql', 'postgis', 'timescaledb', 'citus', 'mssql'}
20
+ _bulk_flavors = {
21
+ 'postgresql',
22
+ 'postgis',
23
+ 'timescaledb',
24
+ 'timescaledb-ha',
25
+ 'citus',
26
+ 'mssql',
27
+ }
21
28
  ### flavors that do not support chunks
22
29
  _disallow_chunks_flavors = ['duckdb']
23
30
  _max_chunks_flavors = {'sqlite': 1000}
@@ -12,6 +12,32 @@ import meerschaum as mrsm
12
12
  from meerschaum.utils.typing import SuccessTuple, Optional, Any, Dict, List, Union
13
13
 
14
14
 
15
+ def get_users_pipe(self) -> mrsm.Pipe:
16
+ """
17
+ Return the internal metadata pipe for users management.
18
+ """
19
+ return mrsm.Pipe(
20
+ 'mrsm', 'users',
21
+ temporary=True,
22
+ static=True,
23
+ null_indices=False,
24
+ enforce=False,
25
+ autoincrement=True,
26
+ columns={
27
+ 'primary': 'user_id',
28
+ },
29
+ dtypes={
30
+ 'user_id': 'int',
31
+ 'username': 'string',
32
+ 'attributes': 'json',
33
+ 'user_type': 'string',
34
+ },
35
+ indices={
36
+ 'unique': 'username',
37
+ },
38
+ )
39
+
40
+
15
41
  def register_user(
16
42
  self,
17
43
  user: mrsm.core.User,
@@ -64,7 +90,7 @@ def register_user(
64
90
 
65
91
  def valid_username(username: str) -> SuccessTuple:
66
92
  """Verify that a given username is valid."""
67
- from meerschaum.config.static import STATIC_CONFIG
93
+ from meerschaum._internal.static import STATIC_CONFIG
68
94
  fail_reasons = []
69
95
 
70
96
  min_length = STATIC_CONFIG['users']['min_username_length']
@@ -104,6 +130,7 @@ def edit_user(
104
130
  ) -> SuccessTuple:
105
131
  """Update an existing user's metadata."""
106
132
  from meerschaum.utils.packages import attempt_import
133
+ from meerschaum.utils.sql import json_flavors
107
134
  sqlalchemy = attempt_import('sqlalchemy', lazy=False)
108
135
  from meerschaum.connectors.sql.tables import get_tables
109
136
  users_tbl = get_tables(mrsm_instance=self, debug=debug)['users']
@@ -131,7 +158,7 @@ def edit_user(
131
158
  bind_variables['email'] = user.email
132
159
  if user.attributes is not None and user.attributes != {}:
133
160
  bind_variables['attributes'] = (
134
- json.dumps(user.attributes) if self.flavor in ('duckdb',)
161
+ json.dumps(user.attributes) if self.flavor not in json_flavors
135
162
  else user.attributes
136
163
  )
137
164
  if user.type != '':
@@ -231,7 +231,7 @@ def create_schemas(
231
231
  """
232
232
  Create the internal Meerschaum schema on the database.
233
233
  """
234
- from meerschaum.config.static import STATIC_CONFIG
234
+ from meerschaum._internal.static import STATIC_CONFIG
235
235
  from meerschaum.utils.packages import attempt_import
236
236
  from meerschaum.utils.sql import sql_item_name, NO_SCHEMA_FLAVORS, SKIP_IF_EXISTS_FLAVORS
237
237
  if conn.flavor in NO_SCHEMA_FLAVORS:
@@ -10,19 +10,18 @@ import json
10
10
  from datetime import datetime, timezone
11
11
 
12
12
  import meerschaum as mrsm
13
- from meerschaum.connectors import Connector, make_connector
13
+ from meerschaum.connectors import InstanceConnector, make_connector
14
14
  from meerschaum.utils.typing import List, Dict, Any, Optional, Union
15
15
  from meerschaum.utils.warnings import dprint
16
16
 
17
17
 
18
18
  @make_connector
19
- class ValkeyConnector(Connector):
19
+ class ValkeyConnector(InstanceConnector):
20
20
  """
21
21
  Manage a Valkey instance.
22
22
 
23
23
  Build a `ValkeyConnector` from connection attributes or a URI string.
24
24
  """
25
- IS_INSTANCE: bool = True
26
25
  REQUIRED_ATTRIBUTES: List[str] = ['host']
27
26
  OPTIONAL_ATTRIBUTES: List[str] = [
28
27
  'port', 'username', 'password', 'db', 'socket_timeout',
@@ -80,7 +79,6 @@ class ValkeyConnector(Connector):
80
79
  get_plugin_user_id,
81
80
  get_plugin_username,
82
81
  get_plugin_attributes,
83
- get_plugins,
84
82
  delete_plugin,
85
83
  )
86
84
 
@@ -13,7 +13,7 @@ from meerschaum.utils.typing import SuccessTuple, Any, Union, Optional, Dict, Li
13
13
  from meerschaum.utils.misc import string_to_dict
14
14
  from meerschaum.utils.dtypes import json_serialize_value
15
15
  from meerschaum.utils.warnings import warn, dprint
16
- from meerschaum.config.static import STATIC_CONFIG
16
+ from meerschaum._internal.static import STATIC_CONFIG
17
17
 
18
18
  PIPES_TABLE: str = 'mrsm_pipes'
19
19
  PIPES_COUNTER: str = 'mrsm_pipes:counter'
@@ -276,7 +276,7 @@ def edit_pipe(
276
276
  return False, f"{pipe} is not registered."
277
277
 
278
278
  parameters_key = get_pipe_parameters_key(pipe)
279
- parameters_str = json.dumps(pipe.parameters, separators=(',', ':'))
279
+ parameters_str = json.dumps(pipe.get_parameters(apply_symlinks=False), separators=(',', ':'))
280
280
  self.set(parameters_key, parameters_str)
281
281
  return True, "Success"
282
282
 
@@ -337,7 +337,7 @@ def drop_pipe(
337
337
  if 'valkey' not in pipe.parameters:
338
338
  return True, "Success"
339
339
 
340
- pipe.parameters['valkey']['dtypes'] = {}
340
+ pipe._attributes['parameters']['valkey']['dtypes'] = {}
341
341
  if not pipe.temporary:
342
342
  edit_success, edit_msg = pipe.edit(debug=debug)
343
343
  if not edit_success:
@@ -577,13 +577,12 @@ def sync_pipe(
577
577
 
578
578
  if new_dtypes and (not static or not valkey_dtypes):
579
579
  valkey_dtypes.update(new_dtypes)
580
- if 'valkey' not in pipe.parameters:
581
- pipe.parameters['valkey'] = {}
582
- pipe.parameters['valkey']['dtypes'] = valkey_dtypes
583
- if not pipe.temporary:
584
- edit_success, edit_msg = pipe.edit(debug=debug)
585
- if not edit_success:
586
- return edit_success, edit_msg
580
+ update_success, update_msg = pipe.update_parameters(
581
+ {'valkey': {'dtypes': valkey_dtypes}},
582
+ debug=debug,
583
+ )
584
+ if not update_success:
585
+ return False, update_msg
587
586
 
588
587
  unseen_df, update_df, delta_df = (
589
588
  pipe.filter_existing(df, include_unchanged_columns=True, debug=debug)
@@ -127,7 +127,8 @@ def get_plugin_id(
127
127
  """
128
128
  Return a plugin's ID.
129
129
  """
130
- return plugin.name
130
+ user_id = self.get_plugin_user_id(plugin, debug=debug)
131
+ return plugin.name if user_id is not None else None
131
132
 
132
133
 
133
134
  def get_plugin_version(
@@ -199,31 +200,6 @@ def get_plugin_attributes(
199
200
  return {}
200
201
 
201
202
 
202
- def get_plugins(
203
- self,
204
- user_id: Optional[int] = None,
205
- search_term: Optional[str] = None,
206
- debug: bool = False,
207
- **kw: Any
208
- ) -> List[str]:
209
- """
210
- Return a list of plugin names.
211
- """
212
- plugins_pipe = self.get_plugins_pipe()
213
- params = {}
214
- if user_id:
215
- params['user_id'] = user_id
216
-
217
- df = plugins_pipe.get_data(['plugin_name'], params=params, debug=debug)
218
- docs = df.to_dict(orient='records')
219
-
220
- return [
221
- doc['plugin_name']
222
- for doc in docs
223
- if (plugin_name := doc['plugin_name']).startswith(search_term or '')
224
- ]
225
-
226
-
227
203
  def delete_plugin(
228
204
  self,
229
205
  plugin: 'mrsm.core.Plugin',