meerschaum 2.7.5__py3-none-any.whl → 2.7.7__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- meerschaum/_internal/shell/Shell.py +4 -6
- meerschaum/_internal/shell/ShellCompleter.py +6 -5
- meerschaum/actions/clear.py +6 -3
- meerschaum/actions/copy.py +33 -27
- meerschaum/actions/drop.py +100 -22
- meerschaum/actions/index.py +71 -0
- meerschaum/actions/register.py +8 -12
- meerschaum/actions/sql.py +1 -1
- meerschaum/actions/sync.py +22 -18
- meerschaum/api/dash/pipes.py +2 -3
- meerschaum/api/routes/_pipes.py +18 -0
- meerschaum/api/routes/_plugins.py +1 -1
- meerschaum/api/routes/_users.py +62 -61
- meerschaum/config/_default.py +5 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/connectors/api/_misc.py +3 -2
- meerschaum/connectors/api/_pipes.py +28 -9
- meerschaum/connectors/sql/_SQLConnector.py +7 -3
- meerschaum/connectors/sql/_create_engine.py +1 -1
- meerschaum/connectors/sql/_fetch.py +4 -9
- meerschaum/connectors/sql/_instance.py +3 -3
- meerschaum/connectors/sql/_pipes.py +292 -76
- meerschaum/connectors/sql/_plugins.py +11 -16
- meerschaum/connectors/sql/_sql.py +13 -9
- meerschaum/connectors/sql/_uri.py +9 -9
- meerschaum/connectors/sql/_users.py +10 -12
- meerschaum/connectors/sql/tables/__init__.py +13 -14
- meerschaum/core/Pipe/__init__.py +12 -2
- meerschaum/core/Pipe/_attributes.py +32 -38
- meerschaum/core/Pipe/_drop.py +73 -2
- meerschaum/core/Pipe/_index.py +68 -0
- meerschaum/jobs/_Job.py +1 -0
- meerschaum/plugins/__init__.py +7 -3
- meerschaum/utils/daemon/Daemon.py +5 -1
- meerschaum/utils/daemon/__init__.py +2 -2
- meerschaum/utils/dtypes/sql.py +2 -2
- meerschaum/utils/misc.py +7 -6
- meerschaum/utils/packages/__init__.py +31 -27
- meerschaum/utils/packages/_packages.py +1 -1
- meerschaum/utils/prompt.py +54 -36
- meerschaum/utils/sql.py +80 -34
- meerschaum/utils/venv/__init__.py +12 -3
- {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/METADATA +17 -5
- {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/RECORD +50 -48
- {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/WHEEL +1 -1
- {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/LICENSE +0 -0
- {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/NOTICE +0 -0
- {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/top_level.txt +0 -0
- {meerschaum-2.7.5.dist-info → meerschaum-2.7.7.dist-info}/zip-safe +0 -0
@@ -29,7 +29,7 @@ from meerschaum.core import Plugin
|
|
29
29
|
starlette_responses = attempt_import('starlette.responses', warn=False)
|
30
30
|
FileResponse = starlette_responses.FileResponse
|
31
31
|
|
32
|
-
sqlalchemy = attempt_import('sqlalchemy')
|
32
|
+
sqlalchemy = attempt_import('sqlalchemy', lazy=False)
|
33
33
|
plugins_endpoint = endpoints['plugins']
|
34
34
|
|
35
35
|
@app.post(plugins_endpoint + '/{name}', tags=['Plugins'])
|
meerschaum/api/routes/_users.py
CHANGED
@@ -7,36 +7,34 @@ Routes for managing users
|
|
7
7
|
"""
|
8
8
|
|
9
9
|
from __future__ import annotations
|
10
|
+
|
10
11
|
from meerschaum.utils.typing import (
|
11
|
-
|
12
|
+
Union, SuccessTuple, Any, Dict, List
|
12
13
|
)
|
13
14
|
|
14
15
|
from meerschaum.utils.packages import attempt_import
|
15
16
|
from meerschaum.api import (
|
16
|
-
fastapi, app, endpoints, get_api_connector,
|
17
|
-
|
17
|
+
fastapi, app, endpoints, get_api_connector, manager,
|
18
|
+
debug, check_allow_chaining, DISALLOW_CHAINING_MESSAGE,
|
18
19
|
no_auth, private,
|
19
20
|
)
|
20
21
|
from meerschaum.utils.misc import string_to_dict
|
21
22
|
from meerschaum.config import get_config
|
22
|
-
from meerschaum.api.tables import get_tables
|
23
|
-
from starlette.responses import Response, JSONResponse
|
24
23
|
from meerschaum.core import User
|
25
|
-
import os, pathlib, datetime
|
26
24
|
|
27
|
-
|
28
|
-
sqlalchemy = attempt_import('sqlalchemy')
|
25
|
+
sqlalchemy = attempt_import('sqlalchemy', lazy=False)
|
29
26
|
users_endpoint = endpoints['users']
|
30
27
|
|
31
28
|
import fastapi
|
32
29
|
from fastapi import HTTPException, Form
|
33
30
|
|
31
|
+
|
34
32
|
@app.get(users_endpoint + "/me", tags=['Users'])
|
35
33
|
def read_current_user(
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
34
|
+
curr_user = (
|
35
|
+
fastapi.Depends(manager) if not no_auth else None
|
36
|
+
),
|
37
|
+
) -> Dict[str, Union[str, int]]:
|
40
38
|
"""
|
41
39
|
Get information about the currently logged-in user.
|
42
40
|
"""
|
@@ -58,12 +56,13 @@ def read_current_user(
|
|
58
56
|
),
|
59
57
|
}
|
60
58
|
|
59
|
+
|
61
60
|
@app.get(users_endpoint, tags=['Users'])
|
62
61
|
def get_users(
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
curr_user = (
|
63
|
+
fastapi.Depends(manager) if private else None
|
64
|
+
),
|
65
|
+
) -> List[str]:
|
67
66
|
"""
|
68
67
|
Get a list of the registered users.
|
69
68
|
"""
|
@@ -72,15 +71,15 @@ def get_users(
|
|
72
71
|
|
73
72
|
@app.post(users_endpoint + "/register", tags=['Users'])
|
74
73
|
def register_user(
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
74
|
+
username: str = Form(None),
|
75
|
+
password: str = Form(None),
|
76
|
+
attributes: str = Form(None),
|
77
|
+
type: str = Form(None),
|
78
|
+
email: str = Form(None),
|
79
|
+
curr_user = (
|
80
|
+
fastapi.Depends(manager) if private else None
|
81
|
+
),
|
82
|
+
) -> SuccessTuple:
|
84
83
|
"""
|
85
84
|
Register a new user.
|
86
85
|
"""
|
@@ -90,7 +89,7 @@ def register_user(
|
|
90
89
|
if attributes is not None:
|
91
90
|
try:
|
92
91
|
attributes = string_to_dict(attributes)
|
93
|
-
except Exception
|
92
|
+
except Exception:
|
94
93
|
return False, f"Invalid dictionary string received for attributes."
|
95
94
|
|
96
95
|
allow_users = get_config('system', 'api', 'permissions', 'registration', 'users')
|
@@ -114,22 +113,22 @@ def register_user(
|
|
114
113
|
|
115
114
|
@app.post(users_endpoint + "/edit", tags=['Users'])
|
116
115
|
def edit_user(
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
116
|
+
username: str = Form(None),
|
117
|
+
password: str = Form(None),
|
118
|
+
type: str = Form(None),
|
119
|
+
email: str = Form(None),
|
120
|
+
attributes: str = Form(None),
|
121
|
+
curr_user = (
|
122
|
+
fastapi.Depends(manager) if not no_auth else None
|
123
|
+
),
|
124
|
+
) -> SuccessTuple:
|
126
125
|
"""
|
127
126
|
Edit an existing user.
|
128
127
|
"""
|
129
128
|
if attributes is not None:
|
130
129
|
try:
|
131
130
|
attributes = string_to_dict(attributes)
|
132
|
-
except Exception
|
131
|
+
except Exception:
|
133
132
|
return False, f"Invalid dictionary string received for attributes."
|
134
133
|
|
135
134
|
user = User(username, password, email=email, attributes=attributes)
|
@@ -144,11 +143,11 @@ def edit_user(
|
|
144
143
|
|
145
144
|
@app.get(users_endpoint + "/{username}/id", tags=['Users'])
|
146
145
|
def get_user_id(
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
146
|
+
username : str,
|
147
|
+
curr_user = (
|
148
|
+
fastapi.Depends(manager) if not no_auth else None
|
149
|
+
),
|
150
|
+
) -> Union[int, None]:
|
152
151
|
"""
|
153
152
|
Get a user's ID.
|
154
153
|
"""
|
@@ -157,23 +156,24 @@ def get_user_id(
|
|
157
156
|
|
158
157
|
@app.get(users_endpoint + "/{username}/attributes", tags=['Users'])
|
159
158
|
def get_user_attributes(
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
159
|
+
username : str,
|
160
|
+
curr_user = (
|
161
|
+
fastapi.Depends(manager) if private else None
|
162
|
+
),
|
163
|
+
) -> Union[Dict[str, Any], None]:
|
165
164
|
"""
|
166
165
|
Get a user's attributes.
|
167
166
|
"""
|
168
167
|
return get_api_connector().get_user_attributes(User(username), debug=debug)
|
169
168
|
|
169
|
+
|
170
170
|
@app.delete(users_endpoint + "/{username}", tags=['Users'])
|
171
171
|
def delete_user(
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
172
|
+
username: str,
|
173
|
+
curr_user = (
|
174
|
+
fastapi.Depends(manager) if not no_auth else None
|
175
|
+
),
|
176
|
+
) -> SuccessTuple:
|
177
177
|
"""
|
178
178
|
Delete a user.
|
179
179
|
"""
|
@@ -193,11 +193,11 @@ def delete_user(
|
|
193
193
|
|
194
194
|
@app.get(users_endpoint + '/{username}/password_hash', tags=['Users'])
|
195
195
|
def get_user_password_hash(
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
196
|
+
username: str,
|
197
|
+
curr_user = (
|
198
|
+
fastapi.Depends(manager) if not no_auth else None
|
199
|
+
),
|
200
|
+
) -> str:
|
201
201
|
"""
|
202
202
|
If configured to allow chaining, return a user's password_hash.
|
203
203
|
"""
|
@@ -205,13 +205,14 @@ def get_user_password_hash(
|
|
205
205
|
raise HTTPException(status_code=403, detail=DISALLOW_CHAINING_MESSAGE)
|
206
206
|
return get_api_connector().get_user_password_hash(User(username), debug=debug)
|
207
207
|
|
208
|
+
|
208
209
|
@app.get(users_endpoint + '/{username}/type', tags=['Users'])
|
209
210
|
def get_user_type(
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
211
|
+
username : str,
|
212
|
+
curr_user = (
|
213
|
+
fastapi.Depends(manager) if not no_auth else None
|
214
|
+
),
|
215
|
+
) -> str:
|
215
216
|
"""
|
216
217
|
If configured to allow chaining, return a user's type.
|
217
218
|
"""
|
meerschaum/config/_default.py
CHANGED
@@ -76,6 +76,11 @@ default_system_config = {
|
|
76
76
|
},
|
77
77
|
'instance': {
|
78
78
|
'stale_temporary_tables_minutes': 1440,
|
79
|
+
'temporary_target': {
|
80
|
+
'prefix': '_',
|
81
|
+
'transaction_id_length': 4,
|
82
|
+
'separator': '_',
|
83
|
+
},
|
79
84
|
},
|
80
85
|
'chunksize': 100_000,
|
81
86
|
'poolclass': 'sqlalchemy.pool.QueuePool',
|
meerschaum/config/_version.py
CHANGED
@@ -20,12 +20,13 @@ def get_mrsm_version(self, **kw) -> Optional[str]:
|
|
20
20
|
use_token=False,
|
21
21
|
**kw
|
22
22
|
).json()
|
23
|
-
except Exception
|
23
|
+
except Exception:
|
24
24
|
return None
|
25
25
|
if isinstance(j, dict) and 'detail' in j:
|
26
26
|
return None
|
27
27
|
return j
|
28
28
|
|
29
|
+
|
29
30
|
def get_chaining_status(self, **kw) -> Optional[bool]:
|
30
31
|
"""
|
31
32
|
Fetch the chaining status of the API instance.
|
@@ -39,7 +40,7 @@ def get_chaining_status(self, **kw) -> Optional[bool]:
|
|
39
40
|
)
|
40
41
|
if not response:
|
41
42
|
return None
|
42
|
-
except Exception
|
43
|
+
except Exception:
|
43
44
|
return None
|
44
45
|
|
45
46
|
return response.json()
|
@@ -9,8 +9,7 @@ Register or fetch Pipes from the API
|
|
9
9
|
from __future__ import annotations
|
10
10
|
import time
|
11
11
|
import json
|
12
|
-
from
|
13
|
-
from datetime import datetime
|
12
|
+
from datetime import datetime, timedelta
|
14
13
|
|
15
14
|
import meerschaum as mrsm
|
16
15
|
from meerschaum.utils.debug import dprint
|
@@ -178,11 +177,11 @@ def sync_pipe(
|
|
178
177
|
"""Sync a DataFrame into a Pipe."""
|
179
178
|
from decimal import Decimal
|
180
179
|
from meerschaum.utils.debug import dprint
|
181
|
-
from meerschaum.utils.misc import json_serialize_datetime, items_str
|
180
|
+
from meerschaum.utils.misc import json_serialize_datetime, items_str, interval_str
|
182
181
|
from meerschaum.config import get_config
|
183
182
|
from meerschaum.utils.packages import attempt_import
|
184
|
-
from meerschaum.utils.dataframe import get_numeric_cols, to_json
|
185
|
-
begin = time.
|
183
|
+
from meerschaum.utils.dataframe import get_numeric_cols, to_json
|
184
|
+
begin = time.perf_counter()
|
186
185
|
more_itertools = attempt_import('more_itertools')
|
187
186
|
if df is None:
|
188
187
|
msg = f"DataFrame is `None`. Cannot sync {pipe}."
|
@@ -304,9 +303,10 @@ def sync_pipe(
|
|
304
303
|
num_success_chunks += 1
|
305
304
|
|
306
305
|
success_tuple = True, (
|
307
|
-
f"It took {
|
306
|
+
f"It took {interval_str(timedelta(seconds=(time.perf_counter() - begin)))} "
|
307
|
+
+ "to sync {rowcount:,} row"
|
308
308
|
+ ('s' if rowcount != 1 else '')
|
309
|
-
+ f" across {num_success_chunks} chunk" + ('s' if num_success_chunks != 1 else '') +
|
309
|
+
+ f" across {num_success_chunks:,} chunk" + ('s' if num_success_chunks != 1 else '') +
|
310
310
|
f" to {pipe}."
|
311
311
|
)
|
312
312
|
return success_tuple
|
@@ -549,10 +549,9 @@ def create_metadata(
|
|
549
549
|
if debug:
|
550
550
|
dprint("Create metadata response: {response.text}")
|
551
551
|
try:
|
552
|
-
|
552
|
+
_ = json.loads(response.text)
|
553
553
|
except Exception as e:
|
554
554
|
warn(f"Failed to create metadata on {self}:\n{e}")
|
555
|
-
metadata_response = False
|
556
555
|
return False
|
557
556
|
|
558
557
|
|
@@ -758,3 +757,23 @@ def get_pipe_columns_indices(
|
|
758
757
|
warn(response.text)
|
759
758
|
return None
|
760
759
|
return j
|
760
|
+
|
761
|
+
|
762
|
+
def get_pipe_index_names(self, pipe: mrsm.Pipe, debug: bool = False) -> Dict[str, str]:
|
763
|
+
"""
|
764
|
+
Return the templated index names.
|
765
|
+
"""
|
766
|
+
r_url = pipe_r_url(pipe) + '/indices/names'
|
767
|
+
response = self.get(
|
768
|
+
r_url,
|
769
|
+
debug=debug
|
770
|
+
)
|
771
|
+
j = response.json()
|
772
|
+
if isinstance(j, dict) and 'detail' in j and len(j.keys()) == 1:
|
773
|
+
warn(j['detail'])
|
774
|
+
return None
|
775
|
+
if not isinstance(j, dict):
|
776
|
+
warn(response.text)
|
777
|
+
return None
|
778
|
+
return j
|
779
|
+
|
@@ -69,6 +69,10 @@ class SQLConnector(Connector):
|
|
69
69
|
get_pipe_schema,
|
70
70
|
create_pipe_table_from_df,
|
71
71
|
get_pipe_columns_indices,
|
72
|
+
get_temporary_target,
|
73
|
+
create_pipe_indices,
|
74
|
+
drop_pipe_indices,
|
75
|
+
get_pipe_index_names,
|
72
76
|
)
|
73
77
|
from ._plugins import (
|
74
78
|
register_plugin,
|
@@ -221,7 +225,7 @@ class SQLConnector(Connector):
|
|
221
225
|
return None
|
222
226
|
|
223
227
|
from meerschaum.utils.packages import attempt_import
|
224
|
-
sqlalchemy_orm = attempt_import('sqlalchemy.orm')
|
228
|
+
sqlalchemy_orm = attempt_import('sqlalchemy.orm', lazy=False)
|
225
229
|
session_factory = sqlalchemy_orm.sessionmaker(self.engine)
|
226
230
|
self._Session = sqlalchemy_orm.scoped_session(session_factory)
|
227
231
|
|
@@ -289,7 +293,7 @@ class SQLConnector(Connector):
|
|
289
293
|
Return the metadata bound to this configured schema.
|
290
294
|
"""
|
291
295
|
from meerschaum.utils.packages import attempt_import
|
292
|
-
sqlalchemy = attempt_import('sqlalchemy')
|
296
|
+
sqlalchemy = attempt_import('sqlalchemy', lazy=False)
|
293
297
|
if '_metadata' not in self.__dict__:
|
294
298
|
self._metadata = sqlalchemy.MetaData(schema=self.schema)
|
295
299
|
return self._metadata
|
@@ -367,7 +371,7 @@ class SQLConnector(Connector):
|
|
367
371
|
self.__dict__['schema'] = None
|
368
372
|
return None
|
369
373
|
|
370
|
-
sqlalchemy = mrsm.attempt_import('sqlalchemy')
|
374
|
+
sqlalchemy = mrsm.attempt_import('sqlalchemy', lazy=False)
|
371
375
|
_schema = sqlalchemy.inspect(self.engine).default_schema_name
|
372
376
|
self.__dict__['schema'] = _schema
|
373
377
|
return _schema
|
@@ -186,7 +186,7 @@ def create_engine(
|
|
186
186
|
"""Create a sqlalchemy engine by building the engine string."""
|
187
187
|
from meerschaum.utils.packages import attempt_import
|
188
188
|
from meerschaum.utils.warnings import error, warn
|
189
|
-
sqlalchemy = attempt_import('sqlalchemy')
|
189
|
+
sqlalchemy = attempt_import('sqlalchemy', lazy=False)
|
190
190
|
import urllib
|
191
191
|
import copy
|
192
192
|
### Install and patch required drivers.
|
@@ -148,7 +148,6 @@ def get_pipe_metadef(
|
|
148
148
|
from meerschaum.utils.warnings import warn
|
149
149
|
from meerschaum.utils.sql import sql_item_name, dateadd_str, build_where
|
150
150
|
from meerschaum.utils.dtypes.sql import get_db_type_from_pd_type
|
151
|
-
from meerschaum.utils.misc import is_int
|
152
151
|
from meerschaum.config import get_config
|
153
152
|
|
154
153
|
dt_col = pipe.columns.get('datetime', None)
|
@@ -191,7 +190,7 @@ def get_pipe_metadef(
|
|
191
190
|
else begin
|
192
191
|
)
|
193
192
|
|
194
|
-
if begin and end and begin >= end:
|
193
|
+
if begin not in (None, '') and end is not None and begin >= end:
|
195
194
|
begin = None
|
196
195
|
|
197
196
|
if dt_name:
|
@@ -203,7 +202,7 @@ def get_pipe_metadef(
|
|
203
202
|
begin=begin,
|
204
203
|
db_type=db_dt_typ,
|
205
204
|
)
|
206
|
-
if begin
|
205
|
+
if begin not in ('', None)
|
207
206
|
else None
|
208
207
|
)
|
209
208
|
end_da = (
|
@@ -214,7 +213,7 @@ def get_pipe_metadef(
|
|
214
213
|
begin=end,
|
215
214
|
db_type=db_dt_typ,
|
216
215
|
)
|
217
|
-
if end
|
216
|
+
if end is not None
|
218
217
|
else None
|
219
218
|
)
|
220
219
|
|
@@ -228,11 +227,7 @@ def get_pipe_metadef(
|
|
228
227
|
|
229
228
|
has_where = 'where' in meta_def.lower()[meta_def.lower().rfind('definition'):]
|
230
229
|
if dt_name and (begin_da or end_da):
|
231
|
-
definition_dt_name =
|
232
|
-
dateadd_str(self.flavor, 'minute', 0, f"{definition_name}.{dt_name}", db_type=db_dt_typ)
|
233
|
-
if not is_int((begin_da or end_da))
|
234
|
-
else f"{definition_name}.{dt_name}"
|
235
|
-
)
|
230
|
+
definition_dt_name = f"{definition_name}.{dt_name}"
|
236
231
|
meta_def += "\n" + ("AND" if has_where else "WHERE") + " "
|
237
232
|
has_where = True
|
238
233
|
if begin_da:
|
@@ -25,7 +25,7 @@ def _log_temporary_tables_creation(
|
|
25
25
|
"""
|
26
26
|
from meerschaum.utils.misc import items_str
|
27
27
|
from meerschaum.connectors.sql.tables import get_tables
|
28
|
-
sqlalchemy = mrsm.attempt_import('sqlalchemy')
|
28
|
+
sqlalchemy = mrsm.attempt_import('sqlalchemy', lazy=False)
|
29
29
|
temp_tables_table = get_tables(
|
30
30
|
mrsm_instance=self,
|
31
31
|
create=create,
|
@@ -86,7 +86,7 @@ def _drop_temporary_tables(self, debug: bool = False) -> SuccessTuple:
|
|
86
86
|
"""
|
87
87
|
from meerschaum.utils.misc import items_str
|
88
88
|
from meerschaum.connectors.sql.tables import get_tables
|
89
|
-
sqlalchemy = mrsm.attempt_import('sqlalchemy')
|
89
|
+
sqlalchemy = mrsm.attempt_import('sqlalchemy', lazy=False)
|
90
90
|
temp_tables_table = get_tables(
|
91
91
|
mrsm_instance=self,
|
92
92
|
create=False,
|
@@ -150,7 +150,7 @@ def _drop_old_temporary_tables(
|
|
150
150
|
"""
|
151
151
|
from meerschaum.config import get_config
|
152
152
|
from meerschaum.connectors.sql.tables import get_tables
|
153
|
-
sqlalchemy = mrsm.attempt_import('sqlalchemy')
|
153
|
+
sqlalchemy = mrsm.attempt_import('sqlalchemy', lazy=False)
|
154
154
|
temp_tables_table = get_tables(mrsm_instance=self, create=False, debug=debug)['temp_tables']
|
155
155
|
last_check = getattr(self, '_stale_temporary_tables_check_timestamp', 0)
|
156
156
|
now_ts = time.perf_counter()
|