MindsDB 25.2.4.0__py3-none-any.whl → 25.3.1.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.
Potentially problematic release.
This version of MindsDB might be problematic. Click here for more details.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +15 -0
- mindsdb/api/executor/command_executor.py +1 -1
- mindsdb/api/executor/datahub/datanodes/system_tables.py +6 -1
- mindsdb/api/executor/planner/query_planner.py +6 -2
- mindsdb/api/executor/sql_query/steps/prepare_steps.py +2 -1
- mindsdb/api/mongo/classes/query_sql.py +2 -1
- mindsdb/api/mongo/responders/aggregate.py +2 -2
- mindsdb/api/mongo/responders/coll_stats.py +3 -2
- mindsdb/api/mongo/responders/db_stats.py +2 -1
- mindsdb/api/mongo/responders/insert.py +4 -2
- mindsdb/api/mysql/mysql_proxy/classes/fake_mysql_proxy/fake_mysql_proxy.py +2 -1
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +5 -4
- mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +2 -4
- mindsdb/integrations/handlers/autosklearn_handler/autosklearn_handler.py +1 -1
- mindsdb/integrations/handlers/gmail_handler/connection_args.py +2 -2
- mindsdb/integrations/handlers/gmail_handler/gmail_handler.py +19 -66
- mindsdb/integrations/handlers/gmail_handler/requirements.txt +0 -1
- mindsdb/integrations/handlers/google_calendar_handler/connection_args.py +15 -0
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +31 -41
- mindsdb/integrations/handlers/google_calendar_handler/requirements.txt +0 -2
- mindsdb/integrations/handlers/youtube_handler/youtube_handler.py +2 -38
- mindsdb/integrations/libs/llm/utils.py +2 -1
- mindsdb/integrations/utilities/handlers/auth_utilities/google/google_user_oauth_utilities.py +29 -38
- mindsdb/integrations/utilities/pydantic_utils.py +208 -0
- mindsdb/integrations/utilities/rag/pipelines/rag.py +11 -4
- mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +800 -135
- mindsdb/integrations/utilities/rag/settings.py +390 -152
- mindsdb/integrations/utilities/sql_utils.py +2 -1
- mindsdb/interfaces/agents/agents_controller.py +11 -7
- mindsdb/interfaces/agents/mindsdb_chat_model.py +4 -2
- mindsdb/interfaces/chatbot/chatbot_controller.py +9 -8
- mindsdb/interfaces/database/database.py +2 -1
- mindsdb/interfaces/database/projects.py +28 -2
- mindsdb/interfaces/jobs/jobs_controller.py +4 -1
- mindsdb/interfaces/model/model_controller.py +5 -2
- mindsdb/interfaces/skills/retrieval_tool.py +128 -39
- mindsdb/interfaces/skills/skill_tool.py +7 -7
- mindsdb/interfaces/skills/skills_controller.py +8 -4
- mindsdb/interfaces/storage/db.py +14 -0
- mindsdb/interfaces/storage/json.py +59 -0
- mindsdb/interfaces/storage/model_fs.py +85 -3
- mindsdb/interfaces/triggers/triggers_controller.py +2 -1
- mindsdb/migrations/versions/2022-10-14_43c52d23845a_projects.py +17 -3
- mindsdb/migrations/versions/2025-02-14_4521dafe89ab_added_encrypted_content_to_json_storage.py +29 -0
- mindsdb/migrations/versions/2025-02-19_11347c213b36_added_metadata_to_projects.py +41 -0
- mindsdb/utilities/config.py +5 -1
- mindsdb/utilities/functions.py +11 -0
- {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/METADATA +221 -223
- {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/RECORD +53 -51
- {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/WHEEL +1 -1
- mindsdb/integrations/handlers/gmail_handler/utils.py +0 -45
- {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/LICENSE +0 -0
- {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from mindsdb.utilities.functions import decrypt_json, encrypt_json
|
|
2
|
+
from mindsdb.utilities.config import config
|
|
1
3
|
from mindsdb.interfaces.storage import db
|
|
2
4
|
from mindsdb.interfaces.storage.fs import RESOURCE_GROUP
|
|
3
5
|
from mindsdb.utilities.context import context as ctx
|
|
@@ -90,8 +92,65 @@ class JsonStorage:
|
|
|
90
92
|
logger.error('cant delete records from JSON storage')
|
|
91
93
|
|
|
92
94
|
|
|
95
|
+
class EncryptedJsonStorage(JsonStorage):
|
|
96
|
+
def __init__(self, resource_group: str, resource_id: int):
|
|
97
|
+
super().__init__(resource_group, resource_id)
|
|
98
|
+
self.secret_key = config.get('secret_key', 'dummy-key')
|
|
99
|
+
|
|
100
|
+
def __setitem__(self, key: str, value: dict) -> None:
|
|
101
|
+
if isinstance(value, dict) is False:
|
|
102
|
+
raise TypeError(f"got {type(value)} instead of dict")
|
|
103
|
+
|
|
104
|
+
encrypted_value = encrypt_json(value, self.secret_key)
|
|
105
|
+
|
|
106
|
+
existing_record = self.get_record(key)
|
|
107
|
+
if existing_record is None:
|
|
108
|
+
record = db.JsonStorage(
|
|
109
|
+
name=key,
|
|
110
|
+
resource_group=self.resource_group,
|
|
111
|
+
resource_id=self.resource_id,
|
|
112
|
+
company_id=ctx.company_id,
|
|
113
|
+
encrypted_content=encrypted_value
|
|
114
|
+
)
|
|
115
|
+
db.session.add(record)
|
|
116
|
+
else:
|
|
117
|
+
existing_record.encrypted_content = encrypted_value
|
|
118
|
+
db.session.commit()
|
|
119
|
+
|
|
120
|
+
def set_bytes(self, key: str, encrypted_value: bytes):
|
|
121
|
+
existing_record = self.get_record(key)
|
|
122
|
+
if existing_record is None:
|
|
123
|
+
record = db.JsonStorage(
|
|
124
|
+
name=key,
|
|
125
|
+
resource_group=self.resource_group,
|
|
126
|
+
resource_id=self.resource_id,
|
|
127
|
+
company_id=ctx.company_id,
|
|
128
|
+
encrypted_content=encrypted_value
|
|
129
|
+
)
|
|
130
|
+
db.session.add(record)
|
|
131
|
+
else:
|
|
132
|
+
existing_record.encrypted_content = encrypted_value
|
|
133
|
+
db.session.commit()
|
|
134
|
+
|
|
135
|
+
def set_str(self, key: str, encrypted_value: str):
|
|
136
|
+
self.set_bytes(key, encrypted_value.encode())
|
|
137
|
+
|
|
138
|
+
def __getitem__(self, key: str) -> dict:
|
|
139
|
+
record = self.get_record(key)
|
|
140
|
+
if record is None:
|
|
141
|
+
return None
|
|
142
|
+
return decrypt_json(record.encrypted_content, self.secret_key)
|
|
143
|
+
|
|
144
|
+
|
|
93
145
|
def get_json_storage(resource_id: int, resource_group: str = RESOURCE_GROUP.PREDICTOR):
|
|
94
146
|
return JsonStorage(
|
|
95
147
|
resource_group=resource_group,
|
|
96
148
|
resource_id=resource_id,
|
|
97
149
|
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_encrypted_json_storage(resource_id: int, resource_group: str = RESOURCE_GROUP.PREDICTOR):
|
|
153
|
+
return EncryptedJsonStorage(
|
|
154
|
+
resource_group=resource_group,
|
|
155
|
+
resource_id=resource_id,
|
|
156
|
+
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
+
import json
|
|
3
4
|
import io
|
|
4
5
|
import zipfile
|
|
5
6
|
from typing import Union
|
|
@@ -7,7 +8,10 @@ from typing import Union
|
|
|
7
8
|
import mindsdb.interfaces.storage.db as db
|
|
8
9
|
|
|
9
10
|
from .fs import RESOURCE_GROUP, FileStorageFactory, SERVICE_FILES_NAMES
|
|
10
|
-
from .json import get_json_storage
|
|
11
|
+
from .json import get_json_storage, get_encrypted_json_storage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
JSON_STORAGE_FILE = 'json_storage.json'
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
class ModelStorage:
|
|
@@ -119,6 +123,13 @@ class ModelStorage:
|
|
|
119
123
|
)
|
|
120
124
|
return json_storage.set(name, data)
|
|
121
125
|
|
|
126
|
+
def encrypted_json_set(self, name: str, data: dict) -> None:
|
|
127
|
+
json_storage = get_encrypted_json_storage(
|
|
128
|
+
resource_id=self.predictor_id,
|
|
129
|
+
resource_group=RESOURCE_GROUP.PREDICTOR
|
|
130
|
+
)
|
|
131
|
+
return json_storage.set(name, data)
|
|
132
|
+
|
|
122
133
|
def json_get(self, name):
|
|
123
134
|
json_storage = get_json_storage(
|
|
124
135
|
resource_id=self.predictor_id,
|
|
@@ -126,6 +137,13 @@ class ModelStorage:
|
|
|
126
137
|
)
|
|
127
138
|
return json_storage.get(name)
|
|
128
139
|
|
|
140
|
+
def encrypted_json_get(self, name: str) -> dict:
|
|
141
|
+
json_storage = get_encrypted_json_storage(
|
|
142
|
+
resource_id=self.predictor_id,
|
|
143
|
+
resource_group=RESOURCE_GROUP.PREDICTOR
|
|
144
|
+
)
|
|
145
|
+
return json_storage.get(name)
|
|
146
|
+
|
|
129
147
|
def json_list(self):
|
|
130
148
|
...
|
|
131
149
|
|
|
@@ -237,6 +255,13 @@ class HandlerStorage:
|
|
|
237
255
|
)
|
|
238
256
|
return json_storage.set(name, content)
|
|
239
257
|
|
|
258
|
+
def encrypted_json_set(self, name: str, content: dict) -> None:
|
|
259
|
+
json_storage = get_encrypted_json_storage(
|
|
260
|
+
resource_id=self.integration_id,
|
|
261
|
+
resource_group=RESOURCE_GROUP.INTEGRATION
|
|
262
|
+
)
|
|
263
|
+
return json_storage.set(name, content)
|
|
264
|
+
|
|
240
265
|
def json_get(self, name):
|
|
241
266
|
json_storage = get_json_storage(
|
|
242
267
|
resource_id=self.integration_id,
|
|
@@ -244,6 +269,13 @@ class HandlerStorage:
|
|
|
244
269
|
)
|
|
245
270
|
return json_storage.get(name)
|
|
246
271
|
|
|
272
|
+
def encrypted_json_get(self, name: str) -> dict:
|
|
273
|
+
json_storage = get_encrypted_json_storage(
|
|
274
|
+
resource_id=self.integration_id,
|
|
275
|
+
resource_group=RESOURCE_GROUP.INTEGRATION
|
|
276
|
+
)
|
|
277
|
+
return json_storage.get(name)
|
|
278
|
+
|
|
247
279
|
def json_list(self):
|
|
248
280
|
...
|
|
249
281
|
|
|
@@ -251,8 +283,11 @@ class HandlerStorage:
|
|
|
251
283
|
...
|
|
252
284
|
|
|
253
285
|
def export_files(self) -> bytes:
|
|
254
|
-
|
|
286
|
+
json_storage = self.export_json_storage()
|
|
287
|
+
|
|
288
|
+
if self.is_empty() and not json_storage:
|
|
255
289
|
return None
|
|
290
|
+
|
|
256
291
|
folder_path = self.folder_get('')
|
|
257
292
|
|
|
258
293
|
zip_fd = io.BytesIO()
|
|
@@ -265,6 +300,11 @@ class HandlerStorage:
|
|
|
265
300
|
abs_path = os.path.join(root, file_name)
|
|
266
301
|
zipf.write(abs_path, os.path.relpath(abs_path, folder_path))
|
|
267
302
|
|
|
303
|
+
# If JSON storage is not empty, add it to the zip file.
|
|
304
|
+
if json_storage:
|
|
305
|
+
json_str = json.dumps(json_storage)
|
|
306
|
+
zipf.writestr(JSON_STORAGE_FILE, json_str)
|
|
307
|
+
|
|
268
308
|
zip_fd.seek(0)
|
|
269
309
|
return zip_fd.read()
|
|
270
310
|
|
|
@@ -277,6 +317,48 @@ class HandlerStorage:
|
|
|
277
317
|
zip_fd.seek(0)
|
|
278
318
|
|
|
279
319
|
with zipfile.ZipFile(zip_fd, 'r') as zip_ref:
|
|
280
|
-
zip_ref.
|
|
320
|
+
for name in zip_ref.namelist():
|
|
321
|
+
# If JSON storage file is in the zip file, import the content to the JSON storage.
|
|
322
|
+
# Thereafter, remove the file from the folder.
|
|
323
|
+
if name == JSON_STORAGE_FILE:
|
|
324
|
+
json_storage = zip_ref.read(JSON_STORAGE_FILE)
|
|
325
|
+
self.import_json_storage(json_storage)
|
|
326
|
+
|
|
327
|
+
else:
|
|
328
|
+
zip_ref.extract(name, folder_path)
|
|
281
329
|
|
|
282
330
|
self.folder_sync('')
|
|
331
|
+
|
|
332
|
+
def export_json_storage(self) -> list[dict]:
|
|
333
|
+
json_storage = get_json_storage(
|
|
334
|
+
resource_id=self.integration_id,
|
|
335
|
+
resource_group=RESOURCE_GROUP.INTEGRATION
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
records = []
|
|
339
|
+
for record in json_storage.get_all_records():
|
|
340
|
+
record_dict = record.to_dict()
|
|
341
|
+
if record_dict.get('encrypted_content'):
|
|
342
|
+
record_dict['encrypted_content'] = record_dict['encrypted_content'].decode()
|
|
343
|
+
records.append(record_dict)
|
|
344
|
+
|
|
345
|
+
return records
|
|
346
|
+
|
|
347
|
+
def import_json_storage(self, records: bytes) -> None:
|
|
348
|
+
json_storage = get_json_storage(
|
|
349
|
+
resource_id=self.integration_id,
|
|
350
|
+
resource_group=RESOURCE_GROUP.INTEGRATION
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
encrypted_json_storage = get_encrypted_json_storage(
|
|
354
|
+
resource_id=self.integration_id,
|
|
355
|
+
resource_group=RESOURCE_GROUP.INTEGRATION
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
records = json.loads(records.decode())
|
|
359
|
+
|
|
360
|
+
for record in records:
|
|
361
|
+
if record['encrypted_content']:
|
|
362
|
+
encrypted_json_storage.set_str(record['name'], record['encrypted_content'])
|
|
363
|
+
else:
|
|
364
|
+
json_storage.set(record['name'], record['content'])
|
|
@@ -5,6 +5,7 @@ from mindsdb_sql_parser import parse_sql, ParsingException
|
|
|
5
5
|
from mindsdb.interfaces.storage import db
|
|
6
6
|
from mindsdb.interfaces.database.projects import ProjectController
|
|
7
7
|
from mindsdb.utilities.context import context as ctx
|
|
8
|
+
from mindsdb.utilities.config import config
|
|
8
9
|
|
|
9
10
|
from mindsdb.api.executor.controllers.session_controller import SessionController
|
|
10
11
|
|
|
@@ -16,7 +17,7 @@ class TriggersController:
|
|
|
16
17
|
name = name.lower()
|
|
17
18
|
|
|
18
19
|
if project_name is None:
|
|
19
|
-
project_name = '
|
|
20
|
+
project_name = config.get('default_project')
|
|
20
21
|
project_controller = ProjectController()
|
|
21
22
|
project = project_controller.get(name=project_name)
|
|
22
23
|
|
|
@@ -32,12 +32,26 @@ def upgrade():
|
|
|
32
32
|
sa.UniqueConstraint('name', 'company_id', name='unique_project_name_company_id')
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
+
project_table = sa.Table(
|
|
36
|
+
'project',
|
|
37
|
+
sa.MetaData(),
|
|
38
|
+
sa.Column('id', sa.Integer()),
|
|
39
|
+
sa.Column('name', sa.String()),
|
|
40
|
+
sa.Column('company_id', sa.Integer()),
|
|
41
|
+
)
|
|
42
|
+
|
|
35
43
|
conn = op.get_bind()
|
|
36
44
|
session = sa.orm.Session(bind=conn)
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
conn.execute(
|
|
47
|
+
project_table.insert().values(
|
|
48
|
+
name='mindsdb'
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
project_record = conn.execute(
|
|
53
|
+
project_table.select().where(project_table.c.name == 'mindsdb')
|
|
54
|
+
).fetchone()
|
|
41
55
|
|
|
42
56
|
with op.batch_alter_table('predictor', schema=None) as batch_op:
|
|
43
57
|
batch_op.add_column(sa.Column('project_id', sa.Integer()))
|
mindsdb/migrations/versions/2025-02-14_4521dafe89ab_added_encrypted_content_to_json_storage.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""added_encrypted_content_to_json_storage
|
|
2
|
+
|
|
3
|
+
Revision ID: 4521dafe89ab
|
|
4
|
+
Revises: 6ab9903fc59a
|
|
5
|
+
Create Date: 2025-02-14 12:05:13.102594
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
import sqlalchemy as sa
|
|
10
|
+
import mindsdb.interfaces.storage.db # noqa
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# revision identifiers, used by Alembic.
|
|
14
|
+
revision = '4521dafe89ab'
|
|
15
|
+
down_revision = '6ab9903fc59a'
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade():
|
|
21
|
+
with op.batch_alter_table('json_storage', schema=None) as batch_op:
|
|
22
|
+
batch_op.add_column(sa.Column('encrypted_content', sa.LargeBinary(), nullable=True))
|
|
23
|
+
batch_op.alter_column('resource_id', existing_type=sa.Integer(), type_=sa.BigInteger())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def downgrade():
|
|
27
|
+
with op.batch_alter_table('json_storage', schema=None) as batch_op:
|
|
28
|
+
batch_op.drop_column('encrypted_content')
|
|
29
|
+
batch_op.alter_column('resource_id', existing_type=sa.BigInteger(), type_=sa.Integer())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""added_metadata_to_projects
|
|
2
|
+
|
|
3
|
+
Revision ID: 11347c213b36
|
|
4
|
+
Revises: 4521dafe89ab
|
|
5
|
+
Create Date: 2025-02-19 18:46:24.014843
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from alembic import op
|
|
9
|
+
from sqlalchemy.orm.attributes import flag_modified
|
|
10
|
+
import sqlalchemy as sa
|
|
11
|
+
|
|
12
|
+
import mindsdb.interfaces.storage.db as db
|
|
13
|
+
from mindsdb.utilities.config import config
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision = '11347c213b36'
|
|
18
|
+
down_revision = '4521dafe89ab'
|
|
19
|
+
branch_labels = None
|
|
20
|
+
depends_on = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade():
|
|
24
|
+
with op.batch_alter_table('project', schema=None) as batch_op:
|
|
25
|
+
batch_op.add_column(sa.Column('metadata', sa.JSON(), nullable=True))
|
|
26
|
+
|
|
27
|
+
conn = op.get_bind()
|
|
28
|
+
session = sa.orm.Session(bind=conn)
|
|
29
|
+
session.commit()
|
|
30
|
+
|
|
31
|
+
project = session.query(db.Project).filter_by(name='mindsdb').first()
|
|
32
|
+
if project:
|
|
33
|
+
project.name = config.get('default_project')
|
|
34
|
+
project.metadata_ = {"is_default": True}
|
|
35
|
+
flag_modified(project, 'metadata_')
|
|
36
|
+
session.commit()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def downgrade():
|
|
40
|
+
with op.batch_alter_table('project', schema=None) as batch_op:
|
|
41
|
+
batch_op.drop_column('metadata')
|
mindsdb/utilities/config.py
CHANGED
|
@@ -217,7 +217,8 @@ class Config:
|
|
|
217
217
|
},
|
|
218
218
|
"tasks": {
|
|
219
219
|
"disable": False
|
|
220
|
-
}
|
|
220
|
+
},
|
|
221
|
+
"default_project": "mindsdb"
|
|
221
222
|
}
|
|
222
223
|
# endregion
|
|
223
224
|
|
|
@@ -353,6 +354,9 @@ class Config:
|
|
|
353
354
|
if os.environ.get('MINDSDB_DB_CON', '') != '':
|
|
354
355
|
self._env_config['storage_db'] = os.environ['MINDSDB_DB_CON']
|
|
355
356
|
|
|
357
|
+
if os.environ.get('MINDSDB_DEFAULT_PROJECT', '') != '':
|
|
358
|
+
self._env_config['default_project'] = os.environ['MINDSDB_DEFAULT_PROJECT'].lower()
|
|
359
|
+
|
|
356
360
|
def parse_cmd_args(self) -> None:
|
|
357
361
|
"""Collect cmd args to self._cmd_args (accessable as self.cmd_args)
|
|
358
362
|
"""
|
mindsdb/utilities/functions.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import base64
|
|
3
3
|
import hashlib
|
|
4
|
+
import json
|
|
4
5
|
import datetime
|
|
5
6
|
import textwrap
|
|
6
7
|
from functools import wraps
|
|
@@ -195,3 +196,13 @@ def decrypt(encripted: bytes, key: str) -> bytes:
|
|
|
195
196
|
|
|
196
197
|
cipher = Fernet(fernet_key)
|
|
197
198
|
return cipher.decrypt(encripted)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def encrypt_json(data: dict, key: str) -> bytes:
|
|
202
|
+
json_str = json.dumps(data)
|
|
203
|
+
return encrypt(json_str.encode(), key)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def decrypt_json(encrypted_data: bytes, key: str) -> dict:
|
|
207
|
+
decrypted = decrypt(encrypted_data, key)
|
|
208
|
+
return json.loads(decrypted)
|