meerschaum 2.7.9__py3-none-any.whl → 2.8.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 (61) hide show
  1. meerschaum/_internal/arguments/_parser.py +17 -5
  2. meerschaum/_internal/term/TermPageHandler.py +1 -1
  3. meerschaum/_internal/term/__init__.py +1 -1
  4. meerschaum/actions/api.py +36 -10
  5. meerschaum/actions/copy.py +3 -1
  6. meerschaum/actions/index.py +1 -1
  7. meerschaum/actions/show.py +7 -7
  8. meerschaum/actions/sync.py +5 -1
  9. meerschaum/actions/verify.py +14 -1
  10. meerschaum/api/__init__.py +77 -41
  11. meerschaum/api/_exceptions.py +18 -0
  12. meerschaum/api/dash/__init__.py +4 -2
  13. meerschaum/api/dash/callbacks/dashboard.py +30 -1
  14. meerschaum/api/dash/components.py +2 -2
  15. meerschaum/api/dash/webterm.py +23 -4
  16. meerschaum/api/models/_pipes.py +8 -8
  17. meerschaum/api/resources/static/css/dash.css +2 -2
  18. meerschaum/api/resources/templates/termpage.html +5 -1
  19. meerschaum/api/routes/__init__.py +15 -12
  20. meerschaum/api/routes/_connectors.py +30 -28
  21. meerschaum/api/routes/_index.py +16 -7
  22. meerschaum/api/routes/_misc.py +30 -22
  23. meerschaum/api/routes/_pipes.py +244 -148
  24. meerschaum/api/routes/_plugins.py +58 -47
  25. meerschaum/api/routes/_users.py +39 -31
  26. meerschaum/api/routes/_version.py +8 -10
  27. meerschaum/api/routes/_webterm.py +2 -2
  28. meerschaum/config/_default.py +10 -0
  29. meerschaum/config/_version.py +1 -1
  30. meerschaum/config/static/__init__.py +5 -2
  31. meerschaum/connectors/api/_APIConnector.py +4 -3
  32. meerschaum/connectors/api/_login.py +21 -17
  33. meerschaum/connectors/api/_pipes.py +1 -0
  34. meerschaum/connectors/api/_request.py +9 -10
  35. meerschaum/connectors/sql/_cli.py +11 -3
  36. meerschaum/connectors/sql/_instance.py +1 -1
  37. meerschaum/connectors/sql/_pipes.py +77 -57
  38. meerschaum/connectors/sql/_sql.py +26 -9
  39. meerschaum/core/Pipe/__init__.py +2 -0
  40. meerschaum/core/Pipe/_attributes.py +13 -2
  41. meerschaum/core/Pipe/_data.py +85 -0
  42. meerschaum/core/Pipe/_deduplicate.py +6 -8
  43. meerschaum/core/Pipe/_sync.py +63 -30
  44. meerschaum/core/Pipe/_verify.py +242 -77
  45. meerschaum/core/User/__init__.py +2 -6
  46. meerschaum/jobs/_Job.py +1 -1
  47. meerschaum/jobs/__init__.py +15 -0
  48. meerschaum/utils/dataframe.py +2 -0
  49. meerschaum/utils/dtypes/sql.py +26 -0
  50. meerschaum/utils/formatting/_pipes.py +1 -1
  51. meerschaum/utils/misc.py +11 -7
  52. meerschaum/utils/packages/_packages.py +1 -1
  53. meerschaum/utils/sql.py +6 -2
  54. {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/METADATA +4 -4
  55. {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/RECORD +61 -60
  56. {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/LICENSE +0 -0
  57. {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/NOTICE +0 -0
  58. {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/WHEEL +0 -0
  59. {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/entry_points.txt +0 -0
  60. {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/top_level.txt +0 -0
  61. {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/zip-safe +0 -0
@@ -7,41 +7,47 @@ Routes for managing plugins
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
- from meerschaum.utils.typing import Optional, List, SuccessTuple, Union, Any, Dict
10
+ import json
11
+ import shutil
12
+ import pathlib
13
+ import os
14
+
15
+ from meerschaum.utils.typing import Optional, List, SuccessTuple, Any, Dict
11
16
 
12
17
  from meerschaum.api import (
13
18
  fastapi,
14
19
  app,
15
20
  endpoints,
16
21
  get_api_connector,
17
- pipes,
18
- get_pipe,
19
22
  manager,
20
23
  debug,
21
- private, no_auth,
24
+ private,
25
+ no_auth,
26
+ default_instance_keys,
22
27
  )
23
- import fastapi
24
28
  from meerschaum.api.tables import get_tables
25
- from fastapi import FastAPI, File, UploadFile
29
+ from fastapi import File, UploadFile
26
30
  from meerschaum.utils.packages import attempt_import
27
- import meerschaum.core
28
31
  from meerschaum.core import Plugin
29
- starlette_responses = attempt_import('starlette.responses', warn=False)
32
+ starlette_responses = attempt_import('starlette.responses', warn=False, lazy=False)
30
33
  FileResponse = starlette_responses.FileResponse
31
34
 
32
35
  sqlalchemy = attempt_import('sqlalchemy', lazy=False)
33
36
  plugins_endpoint = endpoints['plugins']
34
37
 
38
+ PLUGINS_INSTANCE_KEYS = default_instance_keys
39
+
40
+
35
41
  @app.post(plugins_endpoint + '/{name}', tags=['Plugins'])
36
42
  def register_plugin(
37
- name: str,
38
- version: str = None,
39
- attributes: str = None,
40
- archive: UploadFile = File(...),
41
- curr_user = (
42
- fastapi.Depends(manager) if not no_auth else None
43
- ),
44
- ) -> SuccessTuple:
43
+ name: str,
44
+ version: str = None,
45
+ attributes: str = None,
46
+ archive: UploadFile = File(...),
47
+ curr_user = (
48
+ fastapi.Depends(manager) if not no_auth else None
49
+ ),
50
+ ) -> SuccessTuple:
45
51
  """
46
52
  Register a plugin and save its archive file.
47
53
 
@@ -78,7 +84,6 @@ def register_plugin(
78
84
  "you can toggle various registration types."
79
85
  )
80
86
 
81
- import json, shutil, pathlib, os
82
87
  get_tables()
83
88
  if attributes is None:
84
89
  attributes = json.dumps({})
@@ -86,7 +91,7 @@ def register_plugin(
86
91
  if isinstance(attributes, str) and attributes[0] == '{':
87
92
  try:
88
93
  attributes = json.loads(attributes)
89
- except Exception as e:
94
+ except Exception:
90
95
  pass
91
96
 
92
97
  plugin = Plugin(name, version=version, attributes=attributes)
@@ -97,13 +102,15 @@ def register_plugin(
97
102
  )
98
103
 
99
104
  if curr_user is not None:
100
- plugin_user_id = get_api_connector().get_plugin_user_id(plugin)
101
- curr_user_id = get_api_connector().get_user_id(curr_user) if curr_user is not None else -1
105
+ plugin_user_id = get_api_connector(PLUGINS_INSTANCE_KEYS).get_plugin_user_id(plugin)
106
+ curr_user_id = get_api_connector(PLUGINS_INSTANCE_KEYS).get_user_id(curr_user) if curr_user is not None else -1
102
107
  if plugin_user_id is not None and plugin_user_id != curr_user_id:
103
108
  return False, f"User '{curr_user.username}' cannot edit plugin '{plugin}'."
104
109
  plugin.user_id = curr_user_id
105
110
 
106
- success, msg = get_api_connector().register_plugin(plugin, make_archive=False, debug=debug)
111
+ success, msg = get_api_connector(
112
+ PLUGINS_INSTANCE_KEYS
113
+ ).register_plugin(plugin, make_archive=False, debug=debug)
107
114
 
108
115
  if success:
109
116
  archive_path = plugin.archive_path
@@ -117,11 +124,11 @@ def register_plugin(
117
124
 
118
125
  @app.get(plugins_endpoint + '/{name}', tags=['Plugins'])
119
126
  def get_plugin(
120
- name: str,
121
- curr_user = (
122
- fastapi.Depends(manager) if private else None
123
- ),
124
- ) -> Any:
127
+ name: str,
128
+ curr_user = (
129
+ fastapi.Depends(manager) if private else None
130
+ ),
131
+ ) -> Any:
125
132
  """
126
133
  Download a plugin's archive file.
127
134
  """
@@ -133,24 +140,25 @@ def get_plugin(
133
140
 
134
141
  @app.get(plugins_endpoint + '/{name}/attributes', tags=['Plugins'])
135
142
  def get_plugin_attributes(
136
- name: str,
137
- curr_user = (
138
- fastapi.Depends(manager) if private else None
139
- ),
140
- ) -> Dict[str, Any]:
143
+ name: str,
144
+ curr_user = (
145
+ fastapi.Depends(manager) if private else None
146
+ ),
147
+ ) -> Dict[str, Any]:
141
148
  """
142
149
  Get a plugin's attributes.
143
150
  """
144
- return get_api_connector().get_plugin_attributes(Plugin(name))
151
+ return get_api_connector(PLUGINS_INSTANCE_KEYS).get_plugin_attributes(Plugin(name))
152
+
145
153
 
146
154
  @app.get(plugins_endpoint, tags=['Plugins'])
147
155
  def get_plugins(
148
- user_id : Optional[int] = None,
149
- search_term : Optional[str] = None,
150
- curr_user = (
151
- fastapi.Depends(manager) if private else None
152
- ),
153
- ) -> List[str]:
156
+ user_id: Optional[int] = None,
157
+ search_term: Optional[str] = None,
158
+ curr_user = (
159
+ fastapi.Depends(manager) if private else None
160
+ ),
161
+ ) -> List[str]:
154
162
  """
155
163
  Get a list of plugins.
156
164
 
@@ -166,26 +174,29 @@ def get_plugins(
166
174
  -------
167
175
  A list of strings.
168
176
  """
169
- return get_api_connector().get_plugins(user_id=user_id, search_term=search_term)
177
+ return get_api_connector(
178
+ PLUGINS_INSTANCE_KEYS
179
+ ).get_plugins(user_id=user_id, search_term=search_term)
180
+
170
181
 
171
182
  @app.delete(plugins_endpoint + '/{name}', tags=['Plugins'])
172
183
  def delete_plugin(
173
- name : str,
174
- curr_user = (
175
- fastapi.Depends(manager) if private else None
176
- ),
177
- ) -> SuccessTuple:
184
+ name: str,
185
+ curr_user = (
186
+ fastapi.Depends(manager) if private else None
187
+ ),
188
+ ) -> SuccessTuple:
178
189
  """
179
190
  Delete a plugin and its archive file from the repository.
180
191
  """
181
192
  get_tables()
182
193
  plugin = Plugin(name)
183
- plugin_user_id = get_api_connector().get_plugin_user_id(plugin)
194
+ plugin_user_id = get_api_connector(PLUGINS_INSTANCE_KEYS).get_plugin_user_id(plugin)
184
195
  if plugin_user_id is None:
185
196
  return False, f"Plugin '{plugin}' is not registered."
186
197
 
187
198
  if curr_user is not None:
188
- curr_user_id = get_api_connector().get_user_id(curr_user)
199
+ curr_user_id = get_api_connector(PLUGINS_INSTANCE_KEYS).get_user_id(curr_user)
189
200
  if plugin_user_id != curr_user_id:
190
201
  return False, f"User '{curr_user.username}' cannot delete plugin '{plugin}'."
191
202
  else:
@@ -196,7 +207,7 @@ def delete_plugin(
196
207
  if not _remove_success[0]:
197
208
  return _remove_success
198
209
 
199
- _delete_success = get_api_connector().delete_plugin(plugin, debug=debug)
210
+ _delete_success = get_api_connector(PLUGINS_INSTANCE_KEYS).delete_plugin(plugin, debug=debug)
200
211
  if not _delete_success[0]:
201
212
  return _delete_success
202
213
 
@@ -16,7 +16,7 @@ from meerschaum.utils.packages import attempt_import
16
16
  from meerschaum.api import (
17
17
  fastapi, app, endpoints, get_api_connector, manager,
18
18
  debug, check_allow_chaining, DISALLOW_CHAINING_MESSAGE,
19
- no_auth, private,
19
+ no_auth, private, default_instance_keys,
20
20
  )
21
21
  from meerschaum.utils.misc import string_to_dict
22
22
  from meerschaum.config import get_config
@@ -28,31 +28,38 @@ users_endpoint = endpoints['users']
28
28
  import fastapi
29
29
  from fastapi import HTTPException, Form
30
30
 
31
+ USERS_INSTANCE_KEYS = default_instance_keys
32
+
31
33
 
32
34
  @app.get(users_endpoint + "/me", tags=['Users'])
33
35
  def read_current_user(
34
36
  curr_user = (
35
37
  fastapi.Depends(manager) if not no_auth else None
36
38
  ),
37
- ) -> Dict[str, Union[str, int]]:
39
+ ) -> Dict[str, Union[str, int, None, Dict[str, Any]]]:
38
40
  """
39
41
  Get information about the currently logged-in user.
40
42
  """
41
43
  return {
42
44
  'username': (
43
- curr_user.username if curr_user is not None else 'no_auth'
45
+ curr_user.username
46
+ if curr_user is not None
47
+ else 'no_auth'
44
48
  ),
45
49
  'user_id': (
46
- get_api_connector().get_user_id(curr_user)
47
- if curr_user is not None else -1
50
+ get_api_connector(USERS_INSTANCE_KEYS).get_user_id(curr_user)
51
+ if curr_user is not None
52
+ else -1
48
53
  ),
49
54
  'user_type': (
50
- get_api_connector().get_user_type(curr_user)
51
- if curr_user is not None else 'admin'
55
+ get_api_connector(USERS_INSTANCE_KEYS).get_user_type(curr_user)
56
+ if curr_user is not None
57
+ else 'admin'
52
58
  ),
53
59
  'attributes': (
54
- get_api_connector().get_user_attributes(curr_user)
55
- if curr_user is not None else {}
60
+ get_api_connector(USERS_INSTANCE_KEYS).get_user_attributes(curr_user)
61
+ if curr_user is not None
62
+ else {}
56
63
  ),
57
64
  }
58
65
 
@@ -66,7 +73,7 @@ def get_users(
66
73
  """
67
74
  Get a list of the registered users.
68
75
  """
69
- return get_api_connector().get_users(debug=debug)
76
+ return get_api_connector(USERS_INSTANCE_KEYS).get_users(debug=debug)
70
77
 
71
78
 
72
79
  @app.post(users_endpoint + "/register", tags=['Users'])
@@ -90,25 +97,25 @@ def register_user(
90
97
  try:
91
98
  attributes = string_to_dict(attributes)
92
99
  except Exception:
93
- return False, f"Invalid dictionary string received for attributes."
100
+ return False, "Invalid dictionary string received for attributes."
94
101
 
95
102
  allow_users = get_config('system', 'api', 'permissions', 'registration', 'users')
96
103
  if not allow_users:
97
104
  return False, (
98
105
  "The administrator for this server has not allowed user registration.\n\n"
99
- + "Please contact the system administrator, or if you are running this server, "
100
- + "open the configuration file with `edit config system` and search for 'permissions'. "
101
- + " Under the keys api:permissions:registration, "
102
- + "you can toggle various registration types."
106
+ "Please contact the system administrator, or if you are running this server, "
107
+ "open the configuration file with `edit config system` and search for 'permissions'. "
108
+ " Under the keys api:permissions:registration, "
109
+ "you can toggle various registration types."
103
110
  )
104
111
  if type == 'admin':
105
112
  return False, (
106
- "New users cannot be of type 'admin' when using the API connector. " +
107
- "Register a normal user first, then edit the user from an authorized account, " +
113
+ "New users cannot be of type 'admin' when using the API connector. "
114
+ "Register a normal user first, then edit the user from an authorized account, "
108
115
  "or use a SQL connector instead."
109
116
  )
110
117
  user = User(username, password, type=type, email=email, attributes=attributes)
111
- return get_api_connector().register_user(user, debug=debug)
118
+ return get_api_connector(USERS_INSTANCE_KEYS).register_user(user, debug=debug)
112
119
 
113
120
 
114
121
  @app.post(users_endpoint + "/edit", tags=['Users'])
@@ -129,21 +136,21 @@ def edit_user(
129
136
  try:
130
137
  attributes = string_to_dict(attributes)
131
138
  except Exception:
132
- return False, f"Invalid dictionary string received for attributes."
139
+ return False, "Invalid dictionary string received for attributes."
133
140
 
134
141
  user = User(username, password, email=email, attributes=attributes)
135
- user_type = get_api_connector().get_user_type(curr_user) if curr_user is not None else 'admin'
142
+ user_type = get_api_connector(USERS_INSTANCE_KEYS).get_user_type(curr_user) if curr_user is not None else 'admin'
136
143
  if user_type == 'admin' and type is not None:
137
144
  user.type = type
138
145
  if user_type == 'admin' or curr_user.username == user.username:
139
- return get_api_connector().edit_user(user, debug=debug)
146
+ return get_api_connector(USERS_INSTANCE_KEYS).edit_user(user, debug=debug)
140
147
 
141
148
  return False, f"Cannot edit user '{user}': Permission denied"
142
149
 
143
150
 
144
151
  @app.get(users_endpoint + "/{username}/id", tags=['Users'])
145
152
  def get_user_id(
146
- username : str,
153
+ username: str,
147
154
  curr_user = (
148
155
  fastapi.Depends(manager) if not no_auth else None
149
156
  ),
@@ -151,12 +158,12 @@ def get_user_id(
151
158
  """
152
159
  Get a user's ID.
153
160
  """
154
- return get_api_connector().get_user_id(User(username), debug=debug)
161
+ return get_api_connector(USERS_INSTANCE_KEYS).get_user_id(User(username), debug=debug)
155
162
 
156
163
 
157
164
  @app.get(users_endpoint + "/{username}/attributes", tags=['Users'])
158
165
  def get_user_attributes(
159
- username : str,
166
+ username: str,
160
167
  curr_user = (
161
168
  fastapi.Depends(manager) if private else None
162
169
  ),
@@ -164,7 +171,7 @@ def get_user_attributes(
164
171
  """
165
172
  Get a user's attributes.
166
173
  """
167
- return get_api_connector().get_user_attributes(User(username), debug=debug)
174
+ return get_api_connector(USERS_INSTANCE_KEYS).get_user_attributes(User(username), debug=debug)
168
175
 
169
176
 
170
177
  @app.delete(users_endpoint + "/{username}", tags=['Users'])
@@ -179,11 +186,12 @@ def delete_user(
179
186
  """
180
187
  user = User(username)
181
188
  user_type = (
182
- get_api_connector().get_user_type(curr_user, debug=debug)
183
- if curr_user is not None else 'admin'
189
+ get_api_connector(USERS_INSTANCE_KEYS).get_user_type(curr_user, debug=debug)
190
+ if curr_user is not None
191
+ else 'admin'
184
192
  )
185
193
  if user_type == 'admin' or curr_user.username == user.username:
186
- return get_api_connector().delete_user(user, debug=debug)
194
+ return get_api_connector(USERS_INSTANCE_KEYS).delete_user(user, debug=debug)
187
195
 
188
196
  return False, f"Cannot delete user '{user}': Permission denied"
189
197
 
@@ -203,12 +211,12 @@ def get_user_password_hash(
203
211
  """
204
212
  if not check_allow_chaining():
205
213
  raise HTTPException(status_code=403, detail=DISALLOW_CHAINING_MESSAGE)
206
- return get_api_connector().get_user_password_hash(User(username), debug=debug)
214
+ return get_api_connector(USERS_INSTANCE_KEYS).get_user_password_hash(User(username), debug=debug)
207
215
 
208
216
 
209
217
  @app.get(users_endpoint + '/{username}/type', tags=['Users'])
210
218
  def get_user_type(
211
- username : str,
219
+ username: str,
212
220
  curr_user = (
213
221
  fastapi.Depends(manager) if not no_auth else None
214
222
  ),
@@ -218,4 +226,4 @@ def get_user_type(
218
226
  """
219
227
  if not check_allow_chaining():
220
228
  raise HTTPException(status_code=403, detail=DISALLOW_CHAINING_MESSAGE)
221
- return get_api_connector().get_user_type(User(username))
229
+ return get_api_connector(USERS_INSTANCE_KEYS).get_user_type(User(username))
@@ -8,14 +8,13 @@ Return version information
8
8
 
9
9
  import fastapi
10
10
  from meerschaum.api import app, endpoints, private, manager
11
- from meerschaum.utils.typing import Union
12
11
 
13
12
  @app.get(endpoints['version'], tags=['Version'])
14
13
  def get_api_version(
15
- curr_user = (
16
- fastapi.Depends(manager) if private else None
17
- ),
18
- ):
14
+ curr_user = (
15
+ fastapi.Depends(manager) if private else None
16
+ ),
17
+ ):
19
18
  """
20
19
  Get the Meerschaum API version.
21
20
  """
@@ -24,13 +23,12 @@ def get_api_version(
24
23
 
25
24
  @app.get(endpoints['version'] + "/mrsm", tags=['Version'])
26
25
  def get_meerschaum_version(
27
- curr_user = (
28
- fastapi.Depends(manager) if private else None
29
- ),
30
- ):
26
+ curr_user = (
27
+ fastapi.Depends(manager) if private else None
28
+ ),
29
+ ):
31
30
  """
32
31
  Get the Meerschaum instance version.
33
32
  """
34
33
  from meerschaum import __version__ as version
35
34
  return version
36
-
@@ -76,7 +76,7 @@ async def get_webterm(
76
76
  text = response.text
77
77
  if request.url.scheme == 'https':
78
78
  text = text.replace('ws://', 'wss://')
79
- text = text.replace(f'_websocket/{username}', f'_websocket/{session_id}')
79
+ text = text.replace(f'websocket/{username}', f'websocket/{session_id}')
80
80
  return HTMLResponse(
81
81
  content=text,
82
82
  status_code=response.status_code,
@@ -100,7 +100,7 @@ async def webterm_websocket(websocket: WebSocket, session_id: str):
100
100
 
101
101
  username = get_username_from_session(session_id)
102
102
 
103
- ws_url = f"ws://localhost:8765/_websocket/{username or session_id}"
103
+ ws_url = f"ws://localhost:8765/websocket/{username or session_id}"
104
104
  async with websockets.connect(ws_url) as ws:
105
105
  async def forward_messages():
106
106
  try:
@@ -111,6 +111,12 @@ default_system_config = {
111
111
  'connector': 'valkey:main',
112
112
  'session_expires_minutes': 43200,
113
113
  },
114
+ 'data': {
115
+ 'max_response_row_limit': 100_000,
116
+ },
117
+ 'endpoints': {
118
+ 'docs_in_production': True,
119
+ },
114
120
  'permissions': {
115
121
  'registration': {
116
122
  'users': True,
@@ -124,6 +130,10 @@ default_system_config = {
124
130
  'insecure_parent_instance': False,
125
131
  'child_apis': False,
126
132
  },
133
+ 'instances': {
134
+ 'allow_multiple_instances': True,
135
+ 'allowed_instance_keys': ['*']
136
+ },
127
137
  },
128
138
  'protocol': default_meerschaum_config['connectors']['api']['default']['protocol'],
129
139
  },
@@ -2,4 +2,4 @@
2
2
  Specify the Meerschaum release version.
3
3
  """
4
4
 
5
- __version__ = "2.7.9"
5
+ __version__ = "2.8.0"
@@ -7,7 +7,6 @@ Insert non-user-editable configuration files here.
7
7
  """
8
8
 
9
9
  import os
10
- import uuid
11
10
  from typing import Dict, Any
12
11
  from meerschaum.utils.misc import generate_password
13
12
 
@@ -33,9 +32,12 @@ STATIC_CONFIG: Dict[str, Any] = {
33
32
  'websocket': '/ws',
34
33
  'dash': '/dash',
35
34
  'webterm': r'/webterm/{session_id}',
36
- 'webterm_websocket': r'/_websocket/{session_id}',
35
+ 'webterm_websocket': r'/websocket/{session_id}',
37
36
  'info': '/info',
38
37
  'healthcheck': '/healthcheck',
38
+ 'docs': '/docs',
39
+ 'redoc': '/redoc',
40
+ 'openapi': '/openapi.json',
39
41
  },
40
42
  'oauth': {
41
43
  'token_expires_minutes': 720,
@@ -149,6 +151,7 @@ STATIC_CONFIG: Dict[str, Any] = {
149
151
  },
150
152
  'exists_timeout_seconds': 5.0,
151
153
  'static_schema_cache_seconds': 60.0,
154
+ 'max_bound_time_days': 36525,
152
155
  },
153
156
  'jobs': {
154
157
  'check_restart_seconds': 1.0,
@@ -169,12 +169,12 @@ class APIConnector(Connector):
169
169
  @property
170
170
  def session(self):
171
171
  if self._session is None:
172
- certifi = attempt_import('certifi', lazy=False)
172
+ _ = attempt_import('certifi', lazy=False)
173
173
  requests = attempt_import('requests', lazy=False)
174
174
  if requests:
175
175
  self._session = requests.Session()
176
176
  if self._session is None:
177
- error(f"Failed to import requests. Is requests installed?")
177
+ error("Failed to import requests. Is requests installed?")
178
178
  return self._session
179
179
 
180
180
  @property
@@ -191,6 +191,7 @@ class APIConnector(Connector):
191
191
 
192
192
  if self._token is None or expired:
193
193
  success, msg = self.login()
194
- if not success:
194
+ if not success and not self.__dict__.get('_emitted_warning'):
195
195
  warn(msg, stack=False)
196
+ self._emitted_warning = True
196
197
  return self._token
@@ -7,19 +7,21 @@ Log into the API instance or refresh the token.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
11
+ import json
12
+ import datetime
10
13
  from meerschaum.utils.typing import SuccessTuple, Any, Union
14
+ from meerschaum.config.static import STATIC_CONFIG
15
+ from meerschaum.utils.warnings import warn as _warn
16
+
11
17
 
12
18
  def login(
13
- self,
14
- debug: bool = False,
15
- warn: bool = True,
16
- **kw: Any
17
- ) -> SuccessTuple:
19
+ self,
20
+ debug: bool = False,
21
+ warn: bool = True,
22
+ **kw: Any
23
+ ) -> SuccessTuple:
18
24
  """Log in and set the session token."""
19
- from meerschaum.utils.warnings import warn as _warn, info, error
20
- from meerschaum.core import User
21
- from meerschaum.config.static import STATIC_CONFIG
22
- import json, datetime
23
25
  try:
24
26
  login_data = {
25
27
  'username': self.username,
@@ -27,11 +29,12 @@ def login(
27
29
  }
28
30
  except AttributeError:
29
31
  return False, f"Please login with the command `login {self}`."
32
+
30
33
  response = self.post(
31
34
  STATIC_CONFIG['api']['endpoints']['login'],
32
- data = login_data,
33
- use_token = False,
34
- debug = debug
35
+ data=login_data,
36
+ use_token=False,
37
+ debug=debug,
35
38
  )
36
39
  if response:
37
40
  msg = f"Successfully logged into '{self}' as user '{login_data['username']}'."
@@ -45,16 +48,17 @@ def login(
45
48
  f"Failed to log into '{self}' as user '{login_data['username']}'.\n" +
46
49
  f" Please verify login details for connector '{self}'."
47
50
  )
48
- if warn:
51
+ if warn and not self.__dict__.get('_emitted_warning', False):
49
52
  _warn(msg, stack=False)
53
+ self._emitted_warning = True
50
54
 
51
55
  return response.__bool__(), msg
52
56
 
53
57
 
54
58
  def test_connection(
55
- self,
56
- **kw: Any
57
- ) -> Union[bool, None]:
59
+ self,
60
+ **kw: Any
61
+ ) -> Union[bool, None]:
58
62
  """Test if a successful connection to the API may be made."""
59
63
  from meerschaum.connectors.poll import retry_connect
60
64
  _default_kw = {
@@ -65,5 +69,5 @@ def test_connection(
65
69
  _default_kw.update(kw)
66
70
  try:
67
71
  return retry_connect(**_default_kw)
68
- except Exception as e:
72
+ except Exception:
69
73
  return False
@@ -581,6 +581,7 @@ def get_pipe_rowcount(
581
581
  If provided, bound the count by these parameters.
582
582
 
583
583
  remote: bool, default False
584
+ If `True`, return the rowcount for the fetch definition.
584
585
 
585
586
  Returns
586
587
  -------
@@ -248,16 +248,15 @@ def delete(self, r_url: str, **kwargs: Any) -> 'requests.Response':
248
248
 
249
249
 
250
250
  def wget(
251
- self,
252
- r_url: str,
253
- dest: Optional[Union[str, pathlib.Path]] = None,
254
- headers: Optional[Dict[str, Any]] = None,
255
- use_token: bool = True,
256
- debug: bool = False,
257
- **kw: Any
258
- ) -> pathlib.Path:
259
- """Mimic wget with requests.
260
- """
251
+ self,
252
+ r_url: str,
253
+ dest: Optional[Union[str, pathlib.Path]] = None,
254
+ headers: Optional[Dict[str, Any]] = None,
255
+ use_token: bool = True,
256
+ debug: bool = False,
257
+ **kw: Any
258
+ ) -> pathlib.Path:
259
+ """Mimic wget with requests."""
261
260
  from meerschaum.utils.misc import wget
262
261
  if headers is None:
263
262
  headers = {}
@@ -109,13 +109,21 @@ def _cli_exit(
109
109
  ### and because `main.cli()` is not defined.
110
110
  launch_cli = f"cli_main.cli(['{cli_arg_str}'])"
111
111
  if self.flavor == 'mssql':
112
+ attrs = self.parse_uri(self.URI)
113
+ host = attrs.get('host', None)
114
+ port = attrs.get('port', None)
115
+ database = attrs.get('database', None)
116
+ username = attrs.get('username', None)
117
+ password = attrs.get('password', None)
118
+ if not host or not port or not database:
119
+ raise ValueError(f"Cannot determine attributes for '{self}'.")
112
120
  launch_cli = (
113
121
  "mssqlclioptionsparser, mssql_cli = attempt_import("
114
122
  + "'mssqlcli.mssqlclioptionsparser', 'mssqlcli.mssql_cli', lazy=False)\n"
115
123
  + "ms_parser = mssqlclioptionsparser.create_parser()\n"
116
- + f"ms_options = ms_parser.parse_args(['--server', 'tcp:{self.host},{self.port}', "
117
- + f"'--database', '{self.database}', "
118
- + f"'--username', '{self.username}', '--password', '{self.password}'])\n"
124
+ + f"ms_options = ms_parser.parse_args(['--server', 'tcp:{host},{port}', "
125
+ + f"'--database', '{database}', "
126
+ + f"'--username', '{username}', '--password', '{password}'])\n"
119
127
  + "ms_object = mssql_cli.MssqlCli(ms_options)\n"
120
128
  + "try:\n"
121
129
  + " ms_object.connect_to_database()\n"
@@ -186,7 +186,7 @@ def _drop_old_temporary_tables(
186
186
  ]
187
187
  if docs:
188
188
  queries = [sqlalchemy.insert(temp_tables_table).values(**doc) for doc in docs]
189
- results = [self.exec(query, silent=True, debug=debug) for query in queries]
189
+ _ = [self.exec(query, silent=True, debug=debug) for query in queries]
190
190
  _in_memory_temp_tables.update(
191
191
  {
192
192
  table: True