meerschaum 2.7.9__py3-none-any.whl → 2.8.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- meerschaum/_internal/arguments/_parser.py +17 -5
- meerschaum/_internal/term/TermPageHandler.py +1 -1
- meerschaum/_internal/term/__init__.py +1 -1
- meerschaum/actions/api.py +36 -10
- meerschaum/actions/copy.py +3 -1
- meerschaum/actions/index.py +1 -1
- meerschaum/actions/show.py +7 -7
- meerschaum/actions/sync.py +5 -1
- meerschaum/actions/verify.py +14 -1
- meerschaum/api/__init__.py +77 -41
- meerschaum/api/_exceptions.py +18 -0
- meerschaum/api/dash/__init__.py +4 -2
- meerschaum/api/dash/callbacks/dashboard.py +30 -1
- meerschaum/api/dash/components.py +2 -2
- meerschaum/api/dash/webterm.py +23 -4
- meerschaum/api/models/_pipes.py +8 -8
- meerschaum/api/resources/static/css/dash.css +2 -2
- meerschaum/api/resources/templates/termpage.html +5 -1
- meerschaum/api/routes/__init__.py +15 -12
- meerschaum/api/routes/_connectors.py +30 -28
- meerschaum/api/routes/_index.py +16 -7
- meerschaum/api/routes/_misc.py +30 -22
- meerschaum/api/routes/_pipes.py +244 -148
- meerschaum/api/routes/_plugins.py +58 -47
- meerschaum/api/routes/_users.py +39 -31
- meerschaum/api/routes/_version.py +8 -10
- meerschaum/api/routes/_webterm.py +2 -2
- meerschaum/config/_default.py +10 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +5 -2
- meerschaum/connectors/api/_APIConnector.py +4 -3
- meerschaum/connectors/api/_login.py +21 -17
- meerschaum/connectors/api/_pipes.py +1 -0
- meerschaum/connectors/api/_request.py +9 -10
- meerschaum/connectors/sql/_cli.py +11 -3
- meerschaum/connectors/sql/_instance.py +1 -1
- meerschaum/connectors/sql/_pipes.py +77 -57
- meerschaum/connectors/sql/_sql.py +26 -9
- meerschaum/core/Pipe/__init__.py +2 -0
- meerschaum/core/Pipe/_attributes.py +13 -2
- meerschaum/core/Pipe/_data.py +85 -0
- meerschaum/core/Pipe/_deduplicate.py +6 -8
- meerschaum/core/Pipe/_sync.py +63 -30
- meerschaum/core/Pipe/_verify.py +242 -77
- meerschaum/core/User/__init__.py +2 -6
- meerschaum/jobs/_Job.py +1 -1
- meerschaum/jobs/__init__.py +15 -0
- meerschaum/utils/dataframe.py +2 -0
- meerschaum/utils/dtypes/sql.py +26 -0
- meerschaum/utils/formatting/_pipes.py +1 -1
- meerschaum/utils/misc.py +11 -7
- meerschaum/utils/packages/_packages.py +1 -1
- meerschaum/utils/sql.py +6 -2
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/METADATA +4 -4
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/RECORD +61 -60
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/WHEEL +0 -0
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.7.9.dist-info → meerschaum-2.8.0.dist-info}/top_level.txt +0 -0
- {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
|
-
|
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,
|
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
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
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(
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
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(
|
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
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
|
meerschaum/api/routes/_users.py
CHANGED
@@ -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
|
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
|
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
|
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
|
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,
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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,
|
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
|
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
|
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
|
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
|
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
|
-
|
16
|
-
|
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
|
-
|
28
|
-
|
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'
|
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/
|
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:
|
meerschaum/config/_default.py
CHANGED
@@ -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
|
},
|
meerschaum/config/_version.py
CHANGED
@@ -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'/
|
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
|
-
|
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(
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
33
|
-
use_token
|
34
|
-
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
|
-
|
56
|
-
|
57
|
-
|
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
|
72
|
+
except Exception:
|
69
73
|
return False
|
@@ -248,16 +248,15 @@ def delete(self, r_url: str, **kwargs: Any) -> 'requests.Response':
|
|
248
248
|
|
249
249
|
|
250
250
|
def wget(
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
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:{
|
117
|
-
+ f"'--database', '{
|
118
|
-
+ f"'--username', '{
|
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
|
-
|
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
|