dtlpy 1.117.6__py3-none-any.whl → 1.118.13__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.
- dtlpy/__version__.py +1 -1
- dtlpy/dlp/command_executor.py +55 -5
- dtlpy/entities/annotation.py +1 -1
- dtlpy/entities/app.py +1 -1
- dtlpy/entities/compute.py +1 -0
- dtlpy/entities/dataset.py +17 -2
- dtlpy/entities/model.py +1 -1
- dtlpy/entities/paged_entities.py +7 -3
- dtlpy/entities/service.py +4 -4
- dtlpy/miscellaneous/__init__.py +1 -0
- dtlpy/miscellaneous/path_utils.py +264 -0
- dtlpy/ml/base_model_adapter.py +32 -31
- dtlpy/repositories/annotations.py +1 -4
- dtlpy/repositories/apps.py +12 -13
- dtlpy/repositories/feature_sets.py +144 -85
- dtlpy/repositories/packages.py +9 -0
- dtlpy/repositories/projects.py +1 -3
- dtlpy/services/api_client.py +20 -4
- dtlpy/services/check_sdk.py +1 -4
- dtlpy/services/logins.py +21 -17
- dtlpy/utilities/videos/videos.py +4 -0
- {dtlpy-1.117.6.dist-info → dtlpy-1.118.13.dist-info}/METADATA +14 -15
- {dtlpy-1.117.6.dist-info → dtlpy-1.118.13.dist-info}/RECORD +30 -31
- {dtlpy-1.117.6.dist-info → dtlpy-1.118.13.dist-info}/WHEEL +1 -1
- {dtlpy-1.117.6.dist-info → dtlpy-1.118.13.dist-info}/top_level.txt +0 -1
- tests/features/__init__.py +0 -0
- tests/features/environment.py +0 -551
- {dtlpy-1.117.6.data → dtlpy-1.118.13.data}/scripts/dlp +0 -0
- {dtlpy-1.117.6.data → dtlpy-1.118.13.data}/scripts/dlp.bat +0 -0
- {dtlpy-1.117.6.data → dtlpy-1.118.13.data}/scripts/dlp.py +0 -0
- {dtlpy-1.117.6.dist-info → dtlpy-1.118.13.dist-info}/entry_points.txt +0 -0
- {dtlpy-1.117.6.dist-info → dtlpy-1.118.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,6 @@ from copy import deepcopy
|
|
|
2
2
|
import traceback
|
|
3
3
|
import logging
|
|
4
4
|
import json
|
|
5
|
-
import jwt
|
|
6
5
|
import os
|
|
7
6
|
from PIL import Image
|
|
8
7
|
from io import BytesIO
|
|
@@ -336,9 +335,7 @@ class Annotations:
|
|
|
336
335
|
|
|
337
336
|
def _delete_single_annotation(self, w_annotation_id):
|
|
338
337
|
try:
|
|
339
|
-
|
|
340
|
-
verify=False, options={'verify_signature': False})['email']
|
|
341
|
-
payload = {'username': creator}
|
|
338
|
+
payload = {'username': self._client_api.info()['user_email']}
|
|
342
339
|
success, response = self._client_api.gen_request(req_type='delete',
|
|
343
340
|
path='/annotations/{}'.format(w_annotation_id),
|
|
344
341
|
json_req=payload)
|
dtlpy/repositories/apps.py
CHANGED
|
@@ -272,15 +272,16 @@ class Apps:
|
|
|
272
272
|
|
|
273
273
|
return app
|
|
274
274
|
|
|
275
|
-
def uninstall(self, app_id: str = None, app_name: str = None, wait: bool = True) -> bool:
|
|
275
|
+
def uninstall(self, app_id: str = None, app_name: str = None, wait: bool = True, app: entities.App = None) -> bool:
|
|
276
276
|
"""
|
|
277
277
|
Delete an app entity.
|
|
278
278
|
|
|
279
279
|
Note: You are required to add either app_id or app_name.
|
|
280
280
|
|
|
281
281
|
:param str app_id: optional - the id of the app.
|
|
282
|
-
:param str app_name:
|
|
282
|
+
:param str app_name: [DEPRECATED] - the name of the app.
|
|
283
283
|
:param bool wait: optional - wait for the operation to finish.
|
|
284
|
+
:param entities.App app: optional - the app entity.
|
|
284
285
|
:return whether we succeed uninstalling the specified app.
|
|
285
286
|
:rtype bool
|
|
286
287
|
|
|
@@ -288,12 +289,12 @@ class Apps:
|
|
|
288
289
|
.. code-block:: python
|
|
289
290
|
# succeed = dl.apps.delete(app_id='app_id')
|
|
290
291
|
"""
|
|
291
|
-
if
|
|
292
|
+
if app is None and app_id is None:
|
|
292
293
|
raise exceptions.PlatformException(
|
|
293
294
|
error='400',
|
|
294
295
|
message='You must provide an identifier in inputs')
|
|
295
|
-
|
|
296
|
-
|
|
296
|
+
|
|
297
|
+
if app is not None:
|
|
297
298
|
app_id = app.id
|
|
298
299
|
|
|
299
300
|
success, response = self._client_api.gen_request(req_type='delete', path='/apps/{}'.format(app_id))
|
|
@@ -302,18 +303,16 @@ class Apps:
|
|
|
302
303
|
|
|
303
304
|
try:
|
|
304
305
|
app = self.get(app_id=app_id)
|
|
305
|
-
except
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
raise e
|
|
310
|
-
if app.metadata:
|
|
306
|
+
except exceptions.NotFound:
|
|
307
|
+
return success
|
|
308
|
+
|
|
309
|
+
if wait and app.status == entities.CompositionStatus.TERMINATING and app.metadata is not None:
|
|
311
310
|
command_id = app.metadata.get('system', {}).get('commands', {}).get('uninstall', None)
|
|
312
|
-
if
|
|
311
|
+
if command_id is not None:
|
|
313
312
|
command = self.commands.get(command_id=command_id, url='api/v1/commands/faas/{}'.format(command_id))
|
|
314
313
|
command.wait()
|
|
315
314
|
|
|
316
|
-
logger.debug(f"App deleted successfully (id: {
|
|
315
|
+
logger.debug(f"App deleted successfully (id: {app.id}, name: {app.name}")
|
|
317
316
|
return success
|
|
318
317
|
|
|
319
318
|
def resume(self, app: entities.App = None, app_id: str = None) -> bool:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import copy
|
|
2
3
|
from .. import exceptions, entities, miscellaneous, _api_reference, repositories
|
|
3
4
|
from ..services.api_client import ApiClient
|
|
4
5
|
|
|
@@ -9,18 +10,25 @@ class FeatureSets:
|
|
|
9
10
|
"""
|
|
10
11
|
Feature Sets repository
|
|
11
12
|
"""
|
|
13
|
+
|
|
12
14
|
URL = '/features/sets'
|
|
13
15
|
|
|
14
|
-
def __init__(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
client_api: ApiClient,
|
|
19
|
+
project_id: str = None,
|
|
20
|
+
project: entities.Project = None,
|
|
21
|
+
model_id: str = None,
|
|
22
|
+
model: entities.Model = None,
|
|
23
|
+
dataset_id: str = None,
|
|
24
|
+
dataset: entities.Dataset = None,
|
|
25
|
+
):
|
|
20
26
|
self._project = project
|
|
21
27
|
self._project_id = project_id
|
|
22
28
|
self._model = model
|
|
23
29
|
self._model_id = model_id
|
|
30
|
+
self._dataset = dataset
|
|
31
|
+
self._dataset_id = dataset_id
|
|
24
32
|
self._client_api = client_api
|
|
25
33
|
|
|
26
34
|
############
|
|
@@ -28,6 +36,9 @@ class FeatureSets:
|
|
|
28
36
|
############
|
|
29
37
|
@property
|
|
30
38
|
def project(self) -> entities.Project:
|
|
39
|
+
if self._project is None and self._project_id is None and self.dataset is not None:
|
|
40
|
+
self._project = self.dataset.project
|
|
41
|
+
self._project_id = self._project.id
|
|
31
42
|
if self._project is None and self._project_id is not None:
|
|
32
43
|
# get from id
|
|
33
44
|
self._project = repositories.Projects(client_api=self._client_api).get(project_id=self._project_id)
|
|
@@ -38,9 +49,8 @@ class FeatureSets:
|
|
|
38
49
|
self._project = entities.Project.from_json(_json=project, client_api=self._client_api)
|
|
39
50
|
if self._project is None:
|
|
40
51
|
raise exceptions.PlatformException(
|
|
41
|
-
error='2001',
|
|
42
|
-
|
|
43
|
-
' Please checkout or set a project')
|
|
52
|
+
error='2001', message='Cannot perform action WITHOUT Project entity in FeatureSets repository.' ' Please checkout or set a project'
|
|
53
|
+
)
|
|
44
54
|
assert isinstance(self._project, entities.Project)
|
|
45
55
|
return self._project
|
|
46
56
|
|
|
@@ -50,21 +60,27 @@ class FeatureSets:
|
|
|
50
60
|
# get from id
|
|
51
61
|
self._model = repositories.Models(client_api=self._client_api).get(model_id=self._model_id)
|
|
52
62
|
if self._model is None:
|
|
53
|
-
raise exceptions.PlatformException(
|
|
54
|
-
error='2001',
|
|
55
|
-
message='Cannot perform action WITHOUT Model entity in FeatureSets repository.')
|
|
63
|
+
raise exceptions.PlatformException(error='2001', message='Cannot perform action WITHOUT Model entity in FeatureSets repository.')
|
|
56
64
|
assert isinstance(self._model, entities.Model)
|
|
57
65
|
return self._model
|
|
58
66
|
|
|
67
|
+
@property
|
|
68
|
+
def dataset(self) -> entities.Dataset:
|
|
69
|
+
if self._dataset is None and self._dataset_id is not None:
|
|
70
|
+
# get from id
|
|
71
|
+
self._dataset = repositories.Datasets(client_api=self._client_api).get(dataset_id=self._dataset_id)
|
|
72
|
+
if self._dataset is None:
|
|
73
|
+
return None
|
|
74
|
+
assert isinstance(self._dataset, entities.Dataset)
|
|
75
|
+
return self._dataset
|
|
76
|
+
|
|
59
77
|
###########
|
|
60
78
|
# methods #
|
|
61
79
|
###########
|
|
62
80
|
|
|
63
81
|
def _list(self, filters: entities.Filters):
|
|
64
82
|
# request
|
|
65
|
-
success, response = self._client_api.gen_request(req_type='POST',
|
|
66
|
-
path='/features/sets/query',
|
|
67
|
-
json_req=filters.prepare())
|
|
83
|
+
success, response = self._client_api.gen_request(req_type='POST', path='/features/sets/query', json_req=filters.prepare())
|
|
68
84
|
if not success:
|
|
69
85
|
raise exceptions.PlatformException(response)
|
|
70
86
|
return response.json()
|
|
@@ -78,29 +94,101 @@ class FeatureSets:
|
|
|
78
94
|
:return: Paged entity
|
|
79
95
|
:rtype: dtlpy.entities.paged_entities.PagedEntities
|
|
80
96
|
"""
|
|
81
|
-
#
|
|
97
|
+
# Step 1: Initialize filter (create empty if None)
|
|
82
98
|
if filters is None:
|
|
83
99
|
filters = entities.Filters(resource=entities.FiltersResource.FEATURE_SET)
|
|
84
|
-
if self._project is not None:
|
|
85
|
-
filters.context = {'projects': [self._project.id]}
|
|
86
|
-
|
|
87
|
-
# assert type filters
|
|
88
|
-
if not isinstance(filters, entities.Filters):
|
|
89
|
-
raise exceptions.PlatformException(error='400',
|
|
90
|
-
message='Unknown filters type: {!r}'.format(type(filters)))
|
|
91
100
|
|
|
92
101
|
if filters.resource != entities.FiltersResource.FEATURE_SET:
|
|
93
102
|
raise exceptions.PlatformException(
|
|
94
103
|
error='400',
|
|
95
|
-
message='Filters resource must to be FiltersResource.FEATURE_SET. Got: {!r}'.format(filters.resource)
|
|
104
|
+
message='Filters resource must to be FiltersResource.FEATURE_SET. Got: {!r}'.format(filters.resource),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Step 2: Extract IDs inline (no helper functions, no property access)
|
|
108
|
+
# Extract project_id inline
|
|
109
|
+
if self._project_id is not None:
|
|
110
|
+
project_id = self._project_id
|
|
111
|
+
elif self._project is not None:
|
|
112
|
+
project_id = self._project.id
|
|
113
|
+
else:
|
|
114
|
+
raise exceptions.PlatformException(
|
|
115
|
+
error='2001', message='Cannot perform action WITHOUT Project entity in FeatureSets repository.' ' Please checkout or set a project'
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Extract dataset_id inline (only when needed for filtering)
|
|
119
|
+
if self._dataset_id is not None:
|
|
120
|
+
dataset_id = self._dataset_id
|
|
121
|
+
elif self._dataset is not None:
|
|
122
|
+
dataset_id = self._dataset.id
|
|
123
|
+
else:
|
|
124
|
+
dataset_id = None # No dataset filtering needed
|
|
125
|
+
|
|
126
|
+
# Set context with project_id
|
|
127
|
+
filters.context = {'projects': [project_id]}
|
|
128
|
+
|
|
129
|
+
# Preserve original page and page size values before any operations that might modify them
|
|
130
|
+
received_filter = copy.deepcopy(filters)
|
|
131
|
+
|
|
132
|
+
# Step 3: Execute received filter - Run filter with appropriate pagination
|
|
133
|
+
# When dataset_id is None: use original pagination (respect user's page request)
|
|
134
|
+
# When dataset_id is not None: start from page 0 to collect all IDs (pagination applied later)
|
|
135
|
+
if dataset_id is not None:
|
|
136
|
+
page_offset = 0
|
|
137
|
+
received_filter_paged = entities.PagedEntities(
|
|
138
|
+
items_repository=self,
|
|
139
|
+
filters=filters,
|
|
140
|
+
page_offset=page_offset,
|
|
141
|
+
page_size=filters.page_size,
|
|
142
|
+
client_api=self._client_api,
|
|
143
|
+
)
|
|
144
|
+
# Step 5: Dataset_id exists and items exist - extract IDs from all pages
|
|
145
|
+
# Extract feature set IDs from all pages (received_filter_paged.all() will fetch all pages starting from page 0)
|
|
146
|
+
filter_fs_ids = [feature_set.id for feature_set in received_filter_paged.all()]
|
|
147
|
+
|
|
148
|
+
# Step 6: Run aggregation API (when dataset_id exists)
|
|
149
|
+
payload = {
|
|
150
|
+
"projectId": project_id,
|
|
151
|
+
"datasetIds": [dataset_id],
|
|
152
|
+
}
|
|
153
|
+
success, response = self._client_api.gen_request(req_type="POST", path="/features/vectors/project-count-aggregation", json_req=payload)
|
|
154
|
+
if not success:
|
|
155
|
+
raise exceptions.PlatformException(response)
|
|
156
|
+
result = response.json()
|
|
157
|
+
# Extract dataset feature set IDs from response, filtering out entries where count == 0
|
|
158
|
+
dataset_fs_ids = [item['featureSetId'] for item in result if item.get('count', 0) > 0]
|
|
159
|
+
|
|
160
|
+
# Step 7: Intersect IDs
|
|
161
|
+
final_fs_ids = list(set(filter_fs_ids).intersection(set(dataset_fs_ids)))
|
|
162
|
+
|
|
163
|
+
# Step 8: Final return path - Create filter with intersected IDs (no join needed)
|
|
164
|
+
intersected_ids_filter = entities.Filters(resource=entities.FiltersResource.FEATURE_SET)
|
|
165
|
+
intersected_ids_filter.add(field='id', operator=entities.FiltersOperations.IN, values=final_fs_ids)
|
|
166
|
+
intersected_ids_filter.page = received_filter.page # Preserve original pagination
|
|
167
|
+
intersected_ids_filter.page_size = received_filter.page_size
|
|
168
|
+
intersected_ids_filter.context = {'projects': [project_id]}
|
|
169
|
+
|
|
170
|
+
filter_paged = entities.PagedEntities(
|
|
171
|
+
items_repository=self,
|
|
172
|
+
filters=intersected_ids_filter,
|
|
173
|
+
page_offset=intersected_ids_filter.page,
|
|
174
|
+
page_size=intersected_ids_filter.page_size,
|
|
175
|
+
client_api=self._client_api,
|
|
176
|
+
)
|
|
177
|
+
filter_paged.get_page()
|
|
178
|
+
|
|
179
|
+
else:
|
|
180
|
+
page_offset = filters.page if dataset_id is None else 0
|
|
96
181
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
182
|
+
filter_paged = entities.PagedEntities(
|
|
183
|
+
items_repository=self,
|
|
184
|
+
filters=filters,
|
|
185
|
+
page_offset=page_offset,
|
|
186
|
+
page_size=filters.page_size,
|
|
187
|
+
client_api=self._client_api,
|
|
188
|
+
)
|
|
189
|
+
filter_paged.get_page()
|
|
190
|
+
|
|
191
|
+
return filter_paged
|
|
104
192
|
|
|
105
193
|
@_api_reference.add(path='/features/sets/{id}', method='get')
|
|
106
194
|
def get(self, feature_set_name: str = None, feature_set_id: str = None) -> entities.Feature:
|
|
@@ -112,45 +200,31 @@ class FeatureSets:
|
|
|
112
200
|
:return: Feature object
|
|
113
201
|
"""
|
|
114
202
|
if feature_set_id is not None:
|
|
115
|
-
success, response = self._client_api.gen_request(req_type="GET",
|
|
116
|
-
path="{}/{}".format(self.URL, feature_set_id))
|
|
203
|
+
success, response = self._client_api.gen_request(req_type="GET", path="{}/{}".format(self.URL, feature_set_id))
|
|
117
204
|
if not success:
|
|
118
205
|
raise exceptions.PlatformException(response)
|
|
119
|
-
feature_set = entities.FeatureSet.from_json(client_api=self._client_api,
|
|
120
|
-
_json=response.json())
|
|
206
|
+
feature_set = entities.FeatureSet.from_json(client_api=self._client_api, _json=response.json())
|
|
121
207
|
elif feature_set_name is not None:
|
|
122
208
|
if not isinstance(feature_set_name, str):
|
|
123
|
-
raise exceptions.PlatformException(
|
|
124
|
-
error='400',
|
|
125
|
-
message='feature_set_name must be string')
|
|
209
|
+
raise exceptions.PlatformException(error='400', message='feature_set_name must be string')
|
|
126
210
|
filters = entities.Filters(resource=entities.FiltersResource.FEATURE_SET)
|
|
127
211
|
filters.add(field='name', values=feature_set_name)
|
|
128
212
|
feature_sets = self.list(filters=filters)
|
|
129
213
|
if feature_sets.items_count == 0:
|
|
130
|
-
raise exceptions.PlatformException(
|
|
131
|
-
error='404',
|
|
132
|
-
message='Feature set not found. name: {!r}'.format(feature_set_name))
|
|
214
|
+
raise exceptions.PlatformException(error='404', message='Feature set not found. name: {!r}'.format(feature_set_name))
|
|
133
215
|
elif feature_sets.items_count > 1:
|
|
134
216
|
# more than one matching project
|
|
135
|
-
raise exceptions.PlatformException(
|
|
136
|
-
error='404',
|
|
137
|
-
message='More than one feature_set with same name. Please "get" by id')
|
|
217
|
+
raise exceptions.PlatformException(error='404', message='More than one feature_set with same name. Please "get" by id')
|
|
138
218
|
else:
|
|
139
219
|
feature_set = feature_sets.items[0]
|
|
140
220
|
else:
|
|
141
|
-
raise exceptions.PlatformException(
|
|
142
|
-
error='400',
|
|
143
|
-
message='Must provide an identifier in inputs, feature_set_name or feature_set_id')
|
|
221
|
+
raise exceptions.PlatformException(error='400', message='Must provide an identifier in inputs, feature_set_name or feature_set_id')
|
|
144
222
|
return feature_set
|
|
145
223
|
|
|
146
224
|
@_api_reference.add(path='/features/sets', method='post')
|
|
147
|
-
def create(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
entity_type: entities.FeatureEntityType,
|
|
151
|
-
project_id: str = None,
|
|
152
|
-
model_id: set = None,
|
|
153
|
-
org_id: str = None):
|
|
225
|
+
def create(
|
|
226
|
+
self, name: str, size: int, set_type: str, entity_type: entities.FeatureEntityType, project_id: str = None, model_id: set = None, org_id: str = None
|
|
227
|
+
):
|
|
154
228
|
"""
|
|
155
229
|
Create a new Feature Set
|
|
156
230
|
|
|
@@ -169,25 +243,17 @@ class FeatureSets:
|
|
|
169
243
|
else:
|
|
170
244
|
project_id = self._project.id
|
|
171
245
|
|
|
172
|
-
payload = {'name': name,
|
|
173
|
-
'size': size,
|
|
174
|
-
'type': set_type,
|
|
175
|
-
'project': project_id,
|
|
176
|
-
'modelId': model_id,
|
|
177
|
-
'entityType': entity_type}
|
|
246
|
+
payload = {'name': name, 'size': size, 'type': set_type, 'project': project_id, 'modelId': model_id, 'entityType': entity_type}
|
|
178
247
|
if org_id is not None:
|
|
179
248
|
payload['org'] = org_id
|
|
180
|
-
success, response = self._client_api.gen_request(req_type="post",
|
|
181
|
-
json_req=payload,
|
|
182
|
-
path=self.URL)
|
|
249
|
+
success, response = self._client_api.gen_request(req_type="post", json_req=payload, path=self.URL)
|
|
183
250
|
|
|
184
251
|
# exception handling
|
|
185
252
|
if not success:
|
|
186
253
|
raise exceptions.PlatformException(response)
|
|
187
254
|
|
|
188
255
|
# return entity
|
|
189
|
-
return entities.FeatureSet.from_json(client_api=self._client_api,
|
|
190
|
-
_json=response.json()[0])
|
|
256
|
+
return entities.FeatureSet.from_json(client_api=self._client_api, _json=response.json()[0])
|
|
191
257
|
|
|
192
258
|
@_api_reference.add(path='/features/sets/{id}', method='delete')
|
|
193
259
|
def delete(self, feature_set_id: str):
|
|
@@ -199,8 +265,7 @@ class FeatureSets:
|
|
|
199
265
|
:rtype: bool
|
|
200
266
|
"""
|
|
201
267
|
|
|
202
|
-
success, response = self._client_api.gen_request(req_type="delete",
|
|
203
|
-
path=f"{self.URL}/{feature_set_id}")
|
|
268
|
+
success, response = self._client_api.gen_request(req_type="delete", path=f"{self.URL}/{feature_set_id}")
|
|
204
269
|
|
|
205
270
|
# check response
|
|
206
271
|
if success:
|
|
@@ -212,31 +277,27 @@ class FeatureSets:
|
|
|
212
277
|
@_api_reference.add(path='/features/set/{id}', method='patch')
|
|
213
278
|
def update(self, feature_set: entities.FeatureSet) -> entities.FeatureSet:
|
|
214
279
|
"""
|
|
215
|
-
|
|
280
|
+
Update a Feature Set
|
|
216
281
|
|
|
217
|
-
|
|
282
|
+
**Prerequisites**: You must be in the role of an *owner* or *developer*.
|
|
218
283
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
284
|
+
:param dtlpy.entities.FeatureSet feature_set: FeatureSet object
|
|
285
|
+
:return: FeatureSet
|
|
286
|
+
:rtype: dtlpy.entities.FeatureSet
|
|
222
287
|
|
|
223
|
-
|
|
288
|
+
**Example**:
|
|
224
289
|
|
|
225
|
-
|
|
290
|
+
.. code-block:: python
|
|
226
291
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
success, response = self._client_api.gen_request(req_type="patch",
|
|
230
|
-
path=f"{self.URL}/{feature_set.id}",
|
|
231
|
-
json_req=feature_set.to_json())
|
|
292
|
+
dl.feature_sets.update(feature_set='feature_set')
|
|
293
|
+
"""
|
|
294
|
+
success, response = self._client_api.gen_request(req_type="patch", path=f"{self.URL}/{feature_set.id}", json_req=feature_set.to_json())
|
|
232
295
|
if not success:
|
|
233
296
|
raise exceptions.PlatformException(response)
|
|
234
297
|
|
|
235
298
|
logger.debug("feature_set updated successfully")
|
|
236
299
|
# update dataset labels
|
|
237
|
-
feature_set = entities.FeatureSet.from_json(_json=response.json(),
|
|
238
|
-
client_api=self._client_api,
|
|
239
|
-
is_fetched=True)
|
|
300
|
+
feature_set = entities.FeatureSet.from_json(_json=response.json(), client_api=self._client_api, is_fetched=True)
|
|
240
301
|
return feature_set
|
|
241
302
|
|
|
242
303
|
def _build_entities_from_response(self, response_items) -> miscellaneous.List[entities.Item]:
|
|
@@ -244,9 +305,7 @@ class FeatureSets:
|
|
|
244
305
|
jobs = [None for _ in range(len(response_items))]
|
|
245
306
|
# return triggers list
|
|
246
307
|
for i_item, item in enumerate(response_items):
|
|
247
|
-
jobs[i_item] = pool.submit(entities.FeatureSet._protected_from_json,
|
|
248
|
-
**{'client_api': self._client_api,
|
|
249
|
-
'_json': item})
|
|
308
|
+
jobs[i_item] = pool.submit(entities.FeatureSet._protected_from_json, **{'client_api': self._client_api, '_json': item})
|
|
250
309
|
# get all results
|
|
251
310
|
results = [j.result() for j in jobs]
|
|
252
311
|
# log errors
|
dtlpy/repositories/packages.py
CHANGED
|
@@ -513,6 +513,13 @@ class Packages:
|
|
|
513
513
|
if codebase is None:
|
|
514
514
|
src_path = os.getcwd()
|
|
515
515
|
logger.warning('No src_path is given, getting package information from cwd: {}'.format(src_path))
|
|
516
|
+
else:
|
|
517
|
+
# Validate src_path to prevent path traversal
|
|
518
|
+
miscellaneous.PathUtils.validate_directory_path(
|
|
519
|
+
src_path,
|
|
520
|
+
base_path=os.getcwd(),
|
|
521
|
+
must_exist=True
|
|
522
|
+
)
|
|
516
523
|
|
|
517
524
|
# get package json
|
|
518
525
|
package_from_json = dict()
|
|
@@ -941,6 +948,8 @@ class Packages:
|
|
|
941
948
|
|
|
942
949
|
project.packages.deploy_from_file(project='project_entity', json_filepath='json_filepath')
|
|
943
950
|
"""
|
|
951
|
+
# Validate file path to prevent path traversal
|
|
952
|
+
miscellaneous.PathUtils.validate_file_path(json_filepath)
|
|
944
953
|
with open(json_filepath, 'r') as f:
|
|
945
954
|
data = json.load(f)
|
|
946
955
|
|
dtlpy/repositories/projects.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from urllib.parse import quote
|
|
3
|
-
import jwt
|
|
4
3
|
|
|
5
4
|
from .. import entities, miscellaneous, exceptions, _api_reference
|
|
6
5
|
from ..services.api_client import ApiClient
|
|
@@ -159,8 +158,7 @@ class Projects:
|
|
|
159
158
|
assert isinstance(title, str)
|
|
160
159
|
assert isinstance(content, str)
|
|
161
160
|
if self._client_api.token is not None:
|
|
162
|
-
sender =
|
|
163
|
-
verify=False, options={'verify_signature': False})['email']
|
|
161
|
+
sender = self._client_api.info()['user_email']
|
|
164
162
|
else:
|
|
165
163
|
raise exceptions.PlatformException('600', 'Token expired please log in')
|
|
166
164
|
|
dtlpy/services/api_client.py
CHANGED
|
@@ -856,8 +856,16 @@ class ApiClient:
|
|
|
856
856
|
"""
|
|
857
857
|
user_email = 'null'
|
|
858
858
|
if self.token is not None:
|
|
859
|
-
|
|
860
|
-
|
|
859
|
+
# oxsec-disable jwt-signature-disabled - Client-side SDK: signature verification disabled intentionally to extract user email; server validates on API calls
|
|
860
|
+
payload = jwt.decode(
|
|
861
|
+
self.token,
|
|
862
|
+
options={
|
|
863
|
+
"verify_signature": False,
|
|
864
|
+
"verify_exp": False,
|
|
865
|
+
"verify_aud": False,
|
|
866
|
+
"verify_iss": False,
|
|
867
|
+
}
|
|
868
|
+
)
|
|
861
869
|
user_email = payload['email']
|
|
862
870
|
information = {'environment': self.environment,
|
|
863
871
|
'user_email': user_email}
|
|
@@ -1353,8 +1361,16 @@ class ApiClient:
|
|
|
1353
1361
|
if self.token is None or self.token == '':
|
|
1354
1362
|
expired = True
|
|
1355
1363
|
else:
|
|
1356
|
-
|
|
1357
|
-
|
|
1364
|
+
# oxsec-disable jwt-signature-disabled - Client-side SDK: signature verification disabled intentionally to check token expiration; server validates on API calls
|
|
1365
|
+
payload = jwt.decode(
|
|
1366
|
+
self.token,
|
|
1367
|
+
options={
|
|
1368
|
+
"verify_signature": False,
|
|
1369
|
+
"verify_exp": False,
|
|
1370
|
+
"verify_aud": False,
|
|
1371
|
+
"verify_iss": False,
|
|
1372
|
+
}
|
|
1373
|
+
)
|
|
1358
1374
|
d = datetime.datetime.now(datetime.timezone.utc)
|
|
1359
1375
|
epoch = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
|
1360
1376
|
now = (d - epoch).total_seconds()
|
dtlpy/services/check_sdk.py
CHANGED
|
@@ -2,7 +2,6 @@ import time
|
|
|
2
2
|
import logging
|
|
3
3
|
import threading
|
|
4
4
|
import traceback
|
|
5
|
-
import jwt
|
|
6
5
|
|
|
7
6
|
logger = logging.getLogger(name='dtlpy')
|
|
8
7
|
|
|
@@ -21,9 +20,7 @@ def check_in_thread(version, client_api):
|
|
|
21
20
|
|
|
22
21
|
# try read token for email
|
|
23
22
|
try:
|
|
24
|
-
|
|
25
|
-
verify=False, options={'verify_signature': False})
|
|
26
|
-
user_email = payload['email']
|
|
23
|
+
user_email = client_api.info()['user_email']
|
|
27
24
|
except Exception:
|
|
28
25
|
user_email = 'na'
|
|
29
26
|
logger.debug('SDK info: user: {}, version: {}'.format(user_email, version))
|
dtlpy/services/logins.py
CHANGED
|
@@ -32,15 +32,12 @@ def login_secret(api_client, email, password, client_id, client_secret=None, for
|
|
|
32
32
|
# TODO add deprecation warning to client_id
|
|
33
33
|
# check if already logged in with SAME email
|
|
34
34
|
if api_client.token is not None or api_client.token == '':
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
not force:
|
|
42
|
-
return True
|
|
43
|
-
except jwt.exceptions.DecodeError:
|
|
35
|
+
logged_email = api_client.info()['user_email']
|
|
36
|
+
if logged_email == email and \
|
|
37
|
+
not api_client.token_expired() and \
|
|
38
|
+
not force:
|
|
39
|
+
return True
|
|
40
|
+
else:
|
|
44
41
|
logger.debug('{}'.format('Cant decode token. Force login is used'))
|
|
45
42
|
|
|
46
43
|
logger.info('[Start] Login Secret')
|
|
@@ -71,13 +68,12 @@ def login_secret(api_client, email, password, client_id, client_secret=None, for
|
|
|
71
68
|
api_client.refresh_token = response_dict['refresh_token']
|
|
72
69
|
|
|
73
70
|
# set new client id for refresh
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if '
|
|
77
|
-
logger.info('[Done] Login Secret. User: {}'
|
|
71
|
+
logged_email = api_client.info()['user_email']
|
|
72
|
+
|
|
73
|
+
if not logged_email == 'null':
|
|
74
|
+
logger.info(f'[Done] Login Secret. User: {logged_email}')
|
|
78
75
|
else:
|
|
79
|
-
logger.info('[Done] Login Secret. User: {}'
|
|
80
|
-
logger.info(payload)
|
|
76
|
+
logger.info(f'[Done] Login Secret. User: {email}')
|
|
81
77
|
return True
|
|
82
78
|
|
|
83
79
|
|
|
@@ -206,8 +202,16 @@ def login(api_client, auth0_url=None, audience=None, client_id=None, login_domai
|
|
|
206
202
|
success, tokens = server.process_request()
|
|
207
203
|
|
|
208
204
|
if success:
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
# oxsec-disable jwt-signature-disabled - Client-side SDK: signature verification disabled intentionally to extract claims for display; server validates on API calls
|
|
206
|
+
decoded_jwt = jwt.decode(
|
|
207
|
+
tokens['id'],
|
|
208
|
+
options={
|
|
209
|
+
"verify_signature": False,
|
|
210
|
+
"verify_exp": False,
|
|
211
|
+
"verify_aud": False,
|
|
212
|
+
"verify_iss": False,
|
|
213
|
+
}
|
|
214
|
+
)
|
|
211
215
|
|
|
212
216
|
if 'email' in decoded_jwt:
|
|
213
217
|
logger.info('Logged in: {}'.format(decoded_jwt['email']))
|
dtlpy/utilities/videos/videos.py
CHANGED
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import logging
|
|
8
8
|
import dtlpy as dl
|
|
9
9
|
import shutil
|
|
10
|
+
from dtlpy import miscellaneous
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(name='dtlpy')
|
|
12
13
|
|
|
@@ -344,6 +345,9 @@ class Videos:
|
|
|
344
345
|
raise
|
|
345
346
|
# https://www.ffmpeg.org/ffmpeg-formats.html#Examples-9
|
|
346
347
|
|
|
348
|
+
# Validate filepath to prevent path traversal
|
|
349
|
+
miscellaneous.PathUtils.validate_file_path(filepath)
|
|
350
|
+
|
|
347
351
|
if not os.path.isfile(filepath):
|
|
348
352
|
raise IOError('File doesnt exists: {}'.format(filepath))
|
|
349
353
|
logger.info('Extracting video information...')
|