dtlpy 1.113.10__py3-none-any.whl → 1.114.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.
Files changed (243) hide show
  1. dtlpy/__init__.py +488 -488
  2. dtlpy/__version__.py +1 -1
  3. dtlpy/assets/__init__.py +26 -26
  4. dtlpy/assets/__pycache__/__init__.cpython-38.pyc +0 -0
  5. dtlpy/assets/code_server/config.yaml +2 -2
  6. dtlpy/assets/code_server/installation.sh +24 -24
  7. dtlpy/assets/code_server/launch.json +13 -13
  8. dtlpy/assets/code_server/settings.json +2 -2
  9. dtlpy/assets/main.py +53 -53
  10. dtlpy/assets/main_partial.py +18 -18
  11. dtlpy/assets/mock.json +11 -11
  12. dtlpy/assets/model_adapter.py +83 -83
  13. dtlpy/assets/package.json +61 -61
  14. dtlpy/assets/package_catalog.json +29 -29
  15. dtlpy/assets/package_gitignore +307 -307
  16. dtlpy/assets/service_runners/__init__.py +33 -33
  17. dtlpy/assets/service_runners/converter.py +96 -96
  18. dtlpy/assets/service_runners/multi_method.py +49 -49
  19. dtlpy/assets/service_runners/multi_method_annotation.py +54 -54
  20. dtlpy/assets/service_runners/multi_method_dataset.py +55 -55
  21. dtlpy/assets/service_runners/multi_method_item.py +52 -52
  22. dtlpy/assets/service_runners/multi_method_json.py +52 -52
  23. dtlpy/assets/service_runners/single_method.py +37 -37
  24. dtlpy/assets/service_runners/single_method_annotation.py +43 -43
  25. dtlpy/assets/service_runners/single_method_dataset.py +43 -43
  26. dtlpy/assets/service_runners/single_method_item.py +41 -41
  27. dtlpy/assets/service_runners/single_method_json.py +42 -42
  28. dtlpy/assets/service_runners/single_method_multi_input.py +45 -45
  29. dtlpy/assets/voc_annotation_template.xml +23 -23
  30. dtlpy/caches/base_cache.py +32 -32
  31. dtlpy/caches/cache.py +473 -473
  32. dtlpy/caches/dl_cache.py +201 -201
  33. dtlpy/caches/filesystem_cache.py +89 -89
  34. dtlpy/caches/redis_cache.py +84 -84
  35. dtlpy/dlp/__init__.py +20 -20
  36. dtlpy/dlp/cli_utilities.py +367 -367
  37. dtlpy/dlp/command_executor.py +764 -764
  38. dtlpy/dlp/dlp +1 -1
  39. dtlpy/dlp/dlp.bat +1 -1
  40. dtlpy/dlp/dlp.py +128 -128
  41. dtlpy/dlp/parser.py +651 -651
  42. dtlpy/entities/__init__.py +83 -83
  43. dtlpy/entities/analytic.py +311 -311
  44. dtlpy/entities/annotation.py +1879 -1879
  45. dtlpy/entities/annotation_collection.py +699 -699
  46. dtlpy/entities/annotation_definitions/__init__.py +20 -20
  47. dtlpy/entities/annotation_definitions/base_annotation_definition.py +100 -100
  48. dtlpy/entities/annotation_definitions/box.py +195 -195
  49. dtlpy/entities/annotation_definitions/classification.py +67 -67
  50. dtlpy/entities/annotation_definitions/comparison.py +72 -72
  51. dtlpy/entities/annotation_definitions/cube.py +204 -204
  52. dtlpy/entities/annotation_definitions/cube_3d.py +149 -149
  53. dtlpy/entities/annotation_definitions/description.py +32 -32
  54. dtlpy/entities/annotation_definitions/ellipse.py +124 -124
  55. dtlpy/entities/annotation_definitions/free_text.py +62 -62
  56. dtlpy/entities/annotation_definitions/gis.py +69 -69
  57. dtlpy/entities/annotation_definitions/note.py +139 -139
  58. dtlpy/entities/annotation_definitions/point.py +117 -117
  59. dtlpy/entities/annotation_definitions/polygon.py +182 -182
  60. dtlpy/entities/annotation_definitions/polyline.py +111 -111
  61. dtlpy/entities/annotation_definitions/pose.py +92 -92
  62. dtlpy/entities/annotation_definitions/ref_image.py +86 -86
  63. dtlpy/entities/annotation_definitions/segmentation.py +240 -240
  64. dtlpy/entities/annotation_definitions/subtitle.py +34 -34
  65. dtlpy/entities/annotation_definitions/text.py +85 -85
  66. dtlpy/entities/annotation_definitions/undefined_annotation.py +74 -74
  67. dtlpy/entities/app.py +220 -220
  68. dtlpy/entities/app_module.py +107 -107
  69. dtlpy/entities/artifact.py +174 -174
  70. dtlpy/entities/assignment.py +399 -399
  71. dtlpy/entities/base_entity.py +214 -214
  72. dtlpy/entities/bot.py +113 -113
  73. dtlpy/entities/codebase.py +296 -296
  74. dtlpy/entities/collection.py +38 -38
  75. dtlpy/entities/command.py +169 -169
  76. dtlpy/entities/compute.py +442 -442
  77. dtlpy/entities/dataset.py +1285 -1285
  78. dtlpy/entities/directory_tree.py +44 -44
  79. dtlpy/entities/dpk.py +470 -470
  80. dtlpy/entities/driver.py +222 -222
  81. dtlpy/entities/execution.py +397 -397
  82. dtlpy/entities/feature.py +124 -124
  83. dtlpy/entities/feature_set.py +145 -145
  84. dtlpy/entities/filters.py +641 -641
  85. dtlpy/entities/gis_item.py +107 -107
  86. dtlpy/entities/integration.py +184 -184
  87. dtlpy/entities/item.py +953 -953
  88. dtlpy/entities/label.py +123 -123
  89. dtlpy/entities/links.py +85 -85
  90. dtlpy/entities/message.py +175 -175
  91. dtlpy/entities/model.py +694 -691
  92. dtlpy/entities/node.py +1005 -1005
  93. dtlpy/entities/ontology.py +803 -803
  94. dtlpy/entities/organization.py +287 -287
  95. dtlpy/entities/package.py +657 -657
  96. dtlpy/entities/package_defaults.py +5 -5
  97. dtlpy/entities/package_function.py +185 -185
  98. dtlpy/entities/package_module.py +113 -113
  99. dtlpy/entities/package_slot.py +118 -118
  100. dtlpy/entities/paged_entities.py +290 -267
  101. dtlpy/entities/pipeline.py +593 -593
  102. dtlpy/entities/pipeline_execution.py +279 -279
  103. dtlpy/entities/project.py +394 -394
  104. dtlpy/entities/prompt_item.py +499 -499
  105. dtlpy/entities/recipe.py +301 -301
  106. dtlpy/entities/reflect_dict.py +102 -102
  107. dtlpy/entities/resource_execution.py +138 -138
  108. dtlpy/entities/service.py +958 -958
  109. dtlpy/entities/service_driver.py +117 -117
  110. dtlpy/entities/setting.py +294 -294
  111. dtlpy/entities/task.py +491 -491
  112. dtlpy/entities/time_series.py +143 -143
  113. dtlpy/entities/trigger.py +426 -426
  114. dtlpy/entities/user.py +118 -118
  115. dtlpy/entities/webhook.py +124 -124
  116. dtlpy/examples/__init__.py +19 -19
  117. dtlpy/examples/add_labels.py +135 -135
  118. dtlpy/examples/add_metadata_to_item.py +21 -21
  119. dtlpy/examples/annotate_items_using_model.py +65 -65
  120. dtlpy/examples/annotate_video_using_model_and_tracker.py +75 -75
  121. dtlpy/examples/annotations_convert_to_voc.py +9 -9
  122. dtlpy/examples/annotations_convert_to_yolo.py +9 -9
  123. dtlpy/examples/convert_annotation_types.py +51 -51
  124. dtlpy/examples/converter.py +143 -143
  125. dtlpy/examples/copy_annotations.py +22 -22
  126. dtlpy/examples/copy_folder.py +31 -31
  127. dtlpy/examples/create_annotations.py +51 -51
  128. dtlpy/examples/create_video_annotations.py +83 -83
  129. dtlpy/examples/delete_annotations.py +26 -26
  130. dtlpy/examples/filters.py +113 -113
  131. dtlpy/examples/move_item.py +23 -23
  132. dtlpy/examples/play_video_annotation.py +13 -13
  133. dtlpy/examples/show_item_and_mask.py +53 -53
  134. dtlpy/examples/triggers.py +49 -49
  135. dtlpy/examples/upload_batch_of_items.py +20 -20
  136. dtlpy/examples/upload_items_and_custom_format_annotations.py +55 -55
  137. dtlpy/examples/upload_items_with_modalities.py +43 -43
  138. dtlpy/examples/upload_segmentation_annotations_from_mask_image.py +44 -44
  139. dtlpy/examples/upload_yolo_format_annotations.py +70 -70
  140. dtlpy/exceptions.py +125 -125
  141. dtlpy/miscellaneous/__init__.py +20 -20
  142. dtlpy/miscellaneous/dict_differ.py +95 -95
  143. dtlpy/miscellaneous/git_utils.py +217 -217
  144. dtlpy/miscellaneous/json_utils.py +14 -14
  145. dtlpy/miscellaneous/list_print.py +105 -105
  146. dtlpy/miscellaneous/zipping.py +130 -130
  147. dtlpy/ml/__init__.py +20 -20
  148. dtlpy/ml/base_feature_extractor_adapter.py +27 -27
  149. dtlpy/ml/base_model_adapter.py +945 -940
  150. dtlpy/ml/metrics.py +461 -461
  151. dtlpy/ml/predictions_utils.py +274 -274
  152. dtlpy/ml/summary_writer.py +57 -57
  153. dtlpy/ml/train_utils.py +60 -60
  154. dtlpy/new_instance.py +252 -252
  155. dtlpy/repositories/__init__.py +56 -56
  156. dtlpy/repositories/analytics.py +85 -85
  157. dtlpy/repositories/annotations.py +916 -916
  158. dtlpy/repositories/apps.py +383 -383
  159. dtlpy/repositories/artifacts.py +452 -452
  160. dtlpy/repositories/assignments.py +599 -599
  161. dtlpy/repositories/bots.py +213 -213
  162. dtlpy/repositories/codebases.py +559 -559
  163. dtlpy/repositories/collections.py +332 -348
  164. dtlpy/repositories/commands.py +158 -158
  165. dtlpy/repositories/compositions.py +61 -61
  166. dtlpy/repositories/computes.py +434 -406
  167. dtlpy/repositories/datasets.py +1291 -1291
  168. dtlpy/repositories/downloader.py +895 -895
  169. dtlpy/repositories/dpks.py +433 -433
  170. dtlpy/repositories/drivers.py +266 -266
  171. dtlpy/repositories/executions.py +817 -817
  172. dtlpy/repositories/feature_sets.py +226 -226
  173. dtlpy/repositories/features.py +238 -238
  174. dtlpy/repositories/integrations.py +484 -484
  175. dtlpy/repositories/items.py +909 -915
  176. dtlpy/repositories/messages.py +94 -94
  177. dtlpy/repositories/models.py +877 -867
  178. dtlpy/repositories/nodes.py +80 -80
  179. dtlpy/repositories/ontologies.py +511 -511
  180. dtlpy/repositories/organizations.py +525 -525
  181. dtlpy/repositories/packages.py +1941 -1941
  182. dtlpy/repositories/pipeline_executions.py +448 -448
  183. dtlpy/repositories/pipelines.py +642 -642
  184. dtlpy/repositories/projects.py +539 -539
  185. dtlpy/repositories/recipes.py +399 -399
  186. dtlpy/repositories/resource_executions.py +137 -137
  187. dtlpy/repositories/schema.py +120 -120
  188. dtlpy/repositories/service_drivers.py +213 -213
  189. dtlpy/repositories/services.py +1704 -1704
  190. dtlpy/repositories/settings.py +339 -339
  191. dtlpy/repositories/tasks.py +1124 -1124
  192. dtlpy/repositories/times_series.py +278 -278
  193. dtlpy/repositories/triggers.py +536 -536
  194. dtlpy/repositories/upload_element.py +257 -257
  195. dtlpy/repositories/uploader.py +651 -651
  196. dtlpy/repositories/webhooks.py +249 -249
  197. dtlpy/services/__init__.py +22 -22
  198. dtlpy/services/aihttp_retry.py +131 -131
  199. dtlpy/services/api_client.py +1782 -1782
  200. dtlpy/services/api_reference.py +40 -40
  201. dtlpy/services/async_utils.py +133 -133
  202. dtlpy/services/calls_counter.py +44 -44
  203. dtlpy/services/check_sdk.py +68 -68
  204. dtlpy/services/cookie.py +115 -115
  205. dtlpy/services/create_logger.py +156 -156
  206. dtlpy/services/events.py +84 -84
  207. dtlpy/services/logins.py +235 -235
  208. dtlpy/services/reporter.py +256 -256
  209. dtlpy/services/service_defaults.py +91 -91
  210. dtlpy/utilities/__init__.py +20 -20
  211. dtlpy/utilities/annotations/__init__.py +16 -16
  212. dtlpy/utilities/annotations/annotation_converters.py +269 -269
  213. dtlpy/utilities/base_package_runner.py +264 -264
  214. dtlpy/utilities/converter.py +1650 -1650
  215. dtlpy/utilities/dataset_generators/__init__.py +1 -1
  216. dtlpy/utilities/dataset_generators/dataset_generator.py +670 -670
  217. dtlpy/utilities/dataset_generators/dataset_generator_tensorflow.py +23 -23
  218. dtlpy/utilities/dataset_generators/dataset_generator_torch.py +21 -21
  219. dtlpy/utilities/local_development/__init__.py +1 -1
  220. dtlpy/utilities/local_development/local_session.py +179 -179
  221. dtlpy/utilities/reports/__init__.py +2 -2
  222. dtlpy/utilities/reports/figures.py +343 -343
  223. dtlpy/utilities/reports/report.py +71 -71
  224. dtlpy/utilities/videos/__init__.py +17 -17
  225. dtlpy/utilities/videos/video_player.py +598 -598
  226. dtlpy/utilities/videos/videos.py +470 -470
  227. {dtlpy-1.113.10.data → dtlpy-1.114.13.data}/scripts/dlp +1 -1
  228. dtlpy-1.114.13.data/scripts/dlp.bat +2 -0
  229. {dtlpy-1.113.10.data → dtlpy-1.114.13.data}/scripts/dlp.py +128 -128
  230. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/LICENSE +200 -200
  231. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/METADATA +172 -172
  232. dtlpy-1.114.13.dist-info/RECORD +240 -0
  233. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/WHEEL +1 -1
  234. tests/features/environment.py +551 -550
  235. dtlpy-1.113.10.data/scripts/dlp.bat +0 -2
  236. dtlpy-1.113.10.dist-info/RECORD +0 -244
  237. tests/assets/__init__.py +0 -0
  238. tests/assets/models_flow/__init__.py +0 -0
  239. tests/assets/models_flow/failedmain.py +0 -52
  240. tests/assets/models_flow/main.py +0 -62
  241. tests/assets/models_flow/main_model.py +0 -54
  242. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/entry_points.txt +0 -0
  243. {dtlpy-1.113.10.dist-info → dtlpy-1.114.13.dist-info}/top_level.txt +0 -0
dtlpy/entities/filters.py CHANGED
@@ -1,641 +1,641 @@
1
- import urllib.parse
2
- import logging
3
- import json
4
- import os
5
- import io
6
- from enum import Enum
7
-
8
- from .. import exceptions, entities
9
-
10
- logger = logging.getLogger(name='dtlpy')
11
-
12
-
13
- class FiltersKnownFields(str, Enum):
14
- DIR = "dir"
15
- ANNOTATED = "annotated"
16
- FILENAME = "filename"
17
- CREATED_AT = "createdAt"
18
- UPDATED_AT = "updatedAt"
19
- LABEL = "label"
20
- NAME = "name"
21
- HIDDEN = "hidden"
22
- TYPE = 'type'
23
-
24
-
25
- class FiltersResource(str, Enum):
26
- ITEM = "items"
27
- ANNOTATION = "annotations"
28
- EXECUTION = "executions"
29
- PACKAGE = "packages"
30
- DPK = "dpks"
31
- APP = "apps"
32
- SERVICE = "services"
33
- TRIGGER = "triggers"
34
- MODEL = "models"
35
- WEBHOOK = "webhooks"
36
- RECIPE = 'recipe'
37
- DATASET = 'datasets'
38
- ONTOLOGY = 'ontology'
39
- TASK = 'tasks'
40
- PIPELINE = 'pipeline'
41
- PIPELINE_EXECUTION = 'pipelineState'
42
- COMPOSITION = 'composition'
43
- FEATURE = 'feature_vectors'
44
- FEATURE_SET = 'feature_sets'
45
- ORGANIZATIONS = 'organizations'
46
- DRIVERS = 'drivers'
47
- SETTINGS = 'setting'
48
- RESOURCE_EXECUTION = 'resourceExecution'
49
- METRICS = 'metrics',
50
- SERVICE_DRIVER = 'serviceDrivers',
51
- COMPUTE = 'compute'
52
-
53
-
54
- class FiltersOperations(str, Enum):
55
- OR = "or"
56
- AND = "and"
57
- IN = "in"
58
- NOT_EQUAL = "ne"
59
- EQUAL = "eq"
60
- GREATER_THAN = "gt"
61
- LESS_THAN = "lt"
62
- EXISTS = "exists"
63
- MATCH = "match"
64
- NIN = 'nin'
65
-
66
-
67
- class FiltersMethod(str, Enum):
68
- OR = "or"
69
- AND = "and"
70
-
71
-
72
- class FiltersOrderByDirection(str, Enum):
73
- DESCENDING = "descending"
74
- ASCENDING = "ascending"
75
-
76
-
77
- class Filters:
78
- """
79
- Filters entity to filter items from pages in platform
80
- """
81
-
82
- def __init__(
83
- self,
84
- field=None,
85
- values=None,
86
- operator: FiltersOperations = None,
87
- method: FiltersMethod = None,
88
- custom_filter=None,
89
- resource: FiltersResource = FiltersResource.ITEM,
90
- use_defaults=True,
91
- context=None,
92
- page_size=None,
93
- ):
94
- if page_size is None:
95
- if resource in [FiltersResource.EXECUTION,
96
- FiltersResource.PIPELINE_EXECUTION,
97
- FiltersResource.DPK]:
98
- page_size = 100
99
- else:
100
- page_size = 1000
101
-
102
- self.or_filter_list = list()
103
- self.and_filter_list = list()
104
- self._unique_fields = list()
105
- self.custom_filter = custom_filter
106
- self.known_operators = ['or', 'and', 'in', 'ne', 'eq', 'gt', 'lt', 'exists']
107
- self._resource = resource
108
- self.page = 0
109
- self.page_size = page_size
110
- self.method = FiltersMethod.AND
111
- self.sort = dict()
112
- self.join = None
113
- self.recursive = True
114
-
115
- # system only - task and assignment attributes
116
- self._user_query = 'true'
117
- self._ref_task = False
118
- self._ref_assignment = False
119
- self._ref_op = None
120
- self._ref_assignment_id = None
121
- self._ref_task_id = None
122
- self._system_space = None
123
-
124
- self._use_defaults = use_defaults
125
- self.__add_defaults()
126
- self.context = context
127
-
128
- if field is not None:
129
- self.add(field=field, values=values, operator=operator, method=method)
130
-
131
- def __validate_page_size(self):
132
- max_page_size = self.__max_page_size
133
- if self.page_size > max_page_size:
134
- logger.warning('Cannot list {} with page size greater than {}. Changing page_size to {}.'.format(
135
- self.resource, max_page_size, max_page_size
136
- ))
137
- self.page_size = max_page_size
138
-
139
- @property
140
- def __max_page_size(self):
141
- page_size = 1000
142
- if self.resource in [FiltersResource.EXECUTION, FiltersResource.PIPELINE_EXECUTION]:
143
- page_size = 100
144
- return page_size
145
-
146
- @property
147
- def resource(self):
148
- return f'{self._resource.value}' if isinstance(self._resource, FiltersResource) else f'{self._resource}'
149
-
150
- @resource.setter
151
- def resource(self, resource):
152
- self._resource = resource
153
- self.reset()
154
- self.__add_defaults()
155
-
156
- @property
157
- def system_space(self):
158
- return self._system_space
159
-
160
- @system_space.setter
161
- def system_space(self, val: bool):
162
- self._system_space = val
163
-
164
- def reset(self):
165
- self.or_filter_list = list()
166
- self.and_filter_list = list()
167
- self._unique_fields = list()
168
- self.custom_filter = None
169
- self.page = 0
170
- self.page_size = 1000
171
- self.method = FiltersMethod.AND
172
- self.sort = dict()
173
- self.join = None
174
- self.recursive = True
175
- self._nullify_refs()
176
-
177
- def _nullify_refs(self):
178
- self._ref_task = False
179
- self._ref_assignment = False
180
- self._ref_op = None
181
- self._ref_assignment_id = None
182
- self._ref_task_id = None
183
-
184
- def add(self, field, values, operator: FiltersOperations = None, method: FiltersMethod = None):
185
- """
186
- Add filter
187
-
188
- :param str field: Metadata field / attribute
189
- :param values: field values
190
- :param dl.FiltersOperations operator: optional - in, gt, lt, eq, ne
191
- :param dl.FiltersMethod method: Optional - or/and
192
-
193
- **Example**:
194
-
195
- .. code-block:: python
196
-
197
- filter.add(field='metadata.user', values=['1','2'], operator=dl.FiltersOperations.IN)
198
- """
199
- if method is None:
200
- method = self.method
201
- if 'metadata.system.refs.metadata' in field and self.resource == FiltersResource.ITEM:
202
- logger.warning('Filtering by metadata.system.refs.metadata may cause incorrect results. please use match operator')
203
-
204
- # create SingleFilter object and add to self.filter_list
205
- if method == FiltersMethod.OR:
206
- self.or_filter_list.append(SingleFilter(field=field, values=values, operator=operator))
207
- elif method == FiltersMethod.AND:
208
- self.__override(field=field, values=values, operator=operator)
209
- else:
210
- raise exceptions.PlatformException(error='400',
211
- message='Unknown method {}, please select from: or/and'.format(method))
212
-
213
- def __override(self, field, values, operator=None):
214
- if field in self._unique_fields:
215
- for i_single_filter, single_filter in enumerate(self.and_filter_list):
216
- if single_filter.field == field:
217
- self.and_filter_list.pop(i_single_filter)
218
- self.and_filter_list.append(
219
- SingleFilter(field=field, values=values, operator=operator)
220
- )
221
-
222
- def generate_url_query_params(self, url):
223
- """
224
- generate url query params
225
-
226
- :param str url:
227
- """
228
- url = '{}?'.format(url)
229
- for f in self.and_filter_list:
230
- if isinstance(f.values, list):
231
- url = '{}{}={}&'.format(url, f.field, ','.join(f.values))
232
- else:
233
- url = '{}{}={}&'.format(url, f.field, f.values)
234
- return '{}&pageOffset={}&pageSize={}'.format(url, self.page, self.page_size)
235
-
236
- def has_field(self, field):
237
- """
238
- is filter has field
239
-
240
- :param str field: field to check
241
- :return: Ture is have it
242
- :rtype: bool
243
- """
244
- for single_filter in self.or_filter_list:
245
- if single_filter.field == field:
246
- return True
247
-
248
- for single_filter in self.and_filter_list:
249
- if single_filter.field == field:
250
- return True
251
-
252
- return False
253
-
254
- def pop(self, field):
255
- """
256
- Pop filed
257
-
258
- :param str field: field to pop
259
- """
260
- for single_filter in self.or_filter_list:
261
- if single_filter.field == field:
262
- self.or_filter_list.remove(single_filter)
263
-
264
- for single_filter in self.and_filter_list:
265
- if single_filter.field == field:
266
- self.and_filter_list.remove(single_filter)
267
-
268
- def pop_join(self, field):
269
- """
270
- Pop join
271
-
272
- :param str field: field to pop
273
- """
274
- if self.join is not None:
275
- for single_filter in self.join['filter']['$and']:
276
- if field in single_filter:
277
- self.join['filter']['$and'].remove(single_filter)
278
-
279
- def add_join(self, field,
280
- values,
281
- operator: FiltersOperations = None,
282
- method: FiltersMethod = FiltersMethod.AND
283
- ):
284
- """
285
- join a query to the filter
286
-
287
- :param str field: Metadata field / attribute
288
- :param str or list values: field values
289
- :param dl.FiltersOperations operator: optional - in, gt, lt, eq, ne
290
- :param method: optional - str - FiltersMethod.AND, FiltersMethod.OR
291
-
292
- **Example**:
293
-
294
- .. code-block:: python
295
-
296
- filter.add_join(field='metadata.user', values=['1','2'], operator=dl.FiltersOperations.IN)
297
- """
298
- if self.resource not in [FiltersResource.ITEM, FiltersResource.ANNOTATION]:
299
- raise exceptions.PlatformException(error='400',
300
- message='Cannot join to {} filters'.format(self.resource))
301
-
302
- if self.join is None:
303
- self.join = dict()
304
- if 'on' not in self.join:
305
- if self.resource == FiltersResource.ITEM:
306
- self.join['on'] = {'resource': FiltersResource.ANNOTATION.value, 'local': 'itemId', 'forigen': 'id'}
307
- else:
308
- self.join['on'] = {'resource': FiltersResource.ITEM.value, 'local': 'id', 'forigen': 'itemId'}
309
- if 'filter' not in self.join:
310
- self.join['filter'] = dict()
311
- join_method = '$' + method
312
- if join_method not in self.join['filter']:
313
- self.join['filter'][join_method] = list()
314
- self.join['filter'][join_method].append(SingleFilter(field=field, values=values, operator=operator).prepare())
315
-
316
- def __add_defaults(self):
317
- if self._use_defaults:
318
- # add items defaults
319
- if self.resource == FiltersResource.ITEM:
320
- self._unique_fields = ['type', 'hidden']
321
- self.add(field='hidden', values=False, method=FiltersMethod.AND)
322
- self.add(field='type', values='file', method=FiltersMethod.AND)
323
- # add service defaults
324
- elif self.resource == FiltersResource.SERVICE:
325
- self._unique_fields = ['global']
326
- self.add(field='global', values=True, operator=FiltersOperations.NOT_EQUAL, method=FiltersMethod.AND)
327
- elif self.resource == FiltersResource.PACKAGE:
328
- self._unique_fields = ['global']
329
- self.add(field='global', values=True, operator=FiltersOperations.NOT_EQUAL, method=FiltersMethod.AND)
330
- # add annotations defaults
331
- elif self.resource == FiltersResource.ANNOTATION:
332
- self._unique_fields = ['type']
333
- values = [annotation_type.value for annotation_type in entities.AnnotationType]
334
- values.remove(entities.AnnotationType.NOTE.value)
335
- self.add(field='type', values=values, operator=FiltersOperations.IN, method=FiltersMethod.AND)
336
-
337
- def __generate_query(self):
338
- filters_dict = dict()
339
-
340
- if len(self.or_filter_list) > 0:
341
- or_filters = list()
342
- for single_filter in self.or_filter_list:
343
- or_filters.append(
344
- single_filter.prepare(recursive=self.recursive and self.resource == FiltersResource.ITEM))
345
- filters_dict['$or'] = or_filters
346
-
347
- if len(self.and_filter_list) > 0:
348
- and_filters = list()
349
- for single_filter in self.and_filter_list:
350
- and_filters.append(
351
- single_filter.prepare(recursive=self.recursive and self.resource == FiltersResource.ITEM))
352
- filters_dict['$and'] = and_filters
353
-
354
- return filters_dict
355
-
356
- def __generate_custom_query(self):
357
- filters_dict = dict()
358
- if 'filter' in self.custom_filter or 'join' in self.custom_filter:
359
- if 'filter' in self.custom_filter:
360
- filters_dict = self.custom_filter['filter']
361
- self.join = self.custom_filter.get('join', self.join)
362
- else:
363
- filters_dict = self.custom_filter
364
- return filters_dict
365
-
366
- def __generate_ref_query(self):
367
- refs = list()
368
- if self._ref_task:
369
- task_refs = list()
370
- if not isinstance(self._ref_task_id, list):
371
- self._ref_task_id = [self._ref_task_id]
372
-
373
- for ref_id in self._ref_task_id:
374
- task_refs.append({'type': 'task', 'id': ref_id})
375
-
376
- refs += task_refs
377
-
378
- if self._ref_assignment:
379
- assignment_refs = list()
380
- if not isinstance(self._ref_assignment_id, list):
381
- self._ref_assignment_id = [self._ref_assignment_id]
382
-
383
- for ref_id in self._ref_assignment_id:
384
- assignment_refs.append({'type': 'assignment', 'id': ref_id})
385
-
386
- refs += assignment_refs
387
-
388
- return refs
389
-
390
- def prepare(self, operation=None, update=None, query_only=False, system_update=None, system_metadata=False):
391
- """
392
- To dictionary for platform call
393
-
394
- :param str operation: operation
395
- :param update: update
396
- :param bool query_only: query only
397
- :param system_update: system update
398
- :param system_metadata: True, if you want to change metadata system
399
- :return: dict of the filter
400
- :rtype: dict
401
- """
402
- ########
403
- # json #
404
- ########
405
- _json = dict()
406
-
407
- if self.custom_filter is None:
408
- _json['filter'] = self.__generate_query()
409
- else:
410
- _json['filter'] = self.__generate_custom_query()
411
-
412
- ##################
413
- # filter options #
414
- ##################
415
- if not query_only:
416
- if len(self.sort) > 0:
417
- _json['sort'] = self.sort
418
-
419
- self.__validate_page_size()
420
-
421
- _json['page'] = self.page
422
- _json['pageSize'] = self.page_size
423
- _json['resource'] = self.resource
424
-
425
- ########
426
- # join #
427
- ########
428
- if self.join is not None:
429
- _json['join'] = self.join
430
-
431
- #####################
432
- # operation or refs #
433
- #####################
434
- if self._ref_assignment or self._ref_task:
435
- _json['references'] = {
436
- 'operation': self._ref_op,
437
- 'refs': self.__generate_ref_query()
438
- }
439
- elif operation is not None:
440
- if operation == 'update':
441
- if update:
442
- _json[operation] = {'metadata': {'user': update}}
443
- else:
444
- _json[operation] = dict()
445
- if system_metadata and system_update:
446
- _json['systemSpace'] = True
447
- _json[operation]['metadata'] = _json[operation].get('metadata', dict())
448
- _json[operation]['metadata']['system'] = system_update
449
- elif operation == 'delete':
450
- _json[operation] = True
451
- _json.pop('sort', None)
452
- if self.resource == FiltersResource.ITEM:
453
- _json.pop('page', None)
454
- _json.pop('pageSize', None)
455
- else:
456
- raise exceptions.PlatformException(error='400',
457
- message='Unknown operation: {}'.format(operation))
458
-
459
- if self.context is not None:
460
- _json['context'] = self.context
461
- if self._system_space is not None:
462
- _json['systemSpace'] = self._system_space
463
- return _json
464
-
465
- def print(self, indent=2):
466
- print(json.dumps(self.prepare(), indent=indent))
467
-
468
- def sort_by(self, field, value: FiltersOrderByDirection = FiltersOrderByDirection.ASCENDING):
469
- """
470
- sort the filter
471
-
472
- :param str field: field to sort by it
473
- :param dl.FiltersOrderByDirection value: FiltersOrderByDirection.ASCENDING, FiltersOrderByDirection.DESCENDING
474
-
475
- **Example**:
476
-
477
- .. code-block:: python
478
-
479
- filter.sort_by(field='metadata.user', values=dl.FiltersOrderByDirection.ASCENDING)
480
- """
481
- if value not in [FiltersOrderByDirection.ASCENDING, FiltersOrderByDirection.DESCENDING]:
482
- raise exceptions.PlatformException(error='400', message='Sort can be by ascending or descending order only')
483
- self.sort[field] = value.value if isinstance(value, FiltersOrderByDirection) else value
484
-
485
- def platform_url(self, resource) -> str:
486
- """
487
- Build a url with filters param to open in web browser
488
-
489
- :param str resource: dl entity to apply filter on. currently only supports dl.Dataset
490
- :return: url string
491
- :rtype: str
492
- """
493
- _json = self.prepare()
494
- # add the view option
495
- _json['view'] = 'icons'
496
- # convert from enum to string
497
- _json["resource"] = f'{_json["resource"]}'
498
- # convert the dictionary to a json string
499
- _json['dqlFilter'] = json.dumps({'filter': _json.pop('filter'),
500
- 'join': _json.pop('join', None),
501
- 'sort': _json.get('sort', None)})
502
- # set the page size as the UI default
503
- _json['pageSize'] = 100
504
- _json['page'] = _json['page']
505
- # build the url for the dataset data browser
506
- if isinstance(resource, entities.Dataset):
507
- url = resource.platform_url + f'?{urllib.parse.urlencode(_json)}'
508
- else:
509
- raise NotImplementedError('Not implemented for resource type: {}'.format(type(resource)))
510
- return url
511
-
512
- def open_in_web(self, resource):
513
- """
514
- Open the filter in the platform data browser (in a new web browser)
515
-
516
- :param str resource: dl entity to apply filter on. currently only supports dl.Dataset
517
- """
518
- if isinstance(resource, entities.Dataset):
519
- resource._client_api._open_in_web(url=self.platform_url(resource=resource))
520
- else:
521
- raise NotImplementedError('Not implemented for resource type: {}'.format(type(resource)))
522
-
523
- def save(self, project: entities.Project, filter_name: str):
524
- """
525
- Save the current DQL filter to the project
526
-
527
- :param project: dl.Project
528
- :param filter_name: the saved filter's name
529
- :return: True if success
530
- """
531
- _json_filter = self.prepare()
532
- shebang_dict = {"type": "dql",
533
- "shebang": "dataloop",
534
- "metadata": {
535
- "version": "1.0.0",
536
- "system": {
537
- "mimetype": "dql"
538
- },
539
- "dltype": "filter",
540
- "filterFieldsState": [],
541
- "resource": "items",
542
- "filter": _json_filter.pop('filter'),
543
- "join": _json_filter.pop('join')
544
- }
545
- }
546
- b_dataset = project.datasets._get_binaries_dataset()
547
- byte_io = io.BytesIO()
548
- byte_io.name = filter_name
549
- byte_io.write(json.dumps(shebang_dict).encode())
550
- byte_io.seek(0)
551
- b_dataset.items.upload(local_path=byte_io,
552
- remote_path='/.dataloop/dqlfilters/items',
553
- remote_name=filter_name)
554
- return True
555
-
556
- @classmethod
557
- def load(cls, project: entities.Project, filter_name: str) -> 'Filters':
558
- """
559
- Load a saved filter from the project by name
560
-
561
- :param project: dl.Project entity
562
- :param filter_name: filter name
563
- :return: dl.Filters
564
- """
565
- b_dataset = project.datasets._get_binaries_dataset()
566
- f = entities.Filters(custom_filter={
567
- 'filter': {'$and': [{'filename': f'/.dataloop/dqlfilters/items/{filter_name}'}]},
568
- 'page': 0,
569
- 'pageSize': 1000,
570
- 'resource': 'items'
571
- })
572
- pages = b_dataset.items.list(filters=f)
573
- if pages.items_count == 0:
574
- raise exceptions.NotFound(
575
- f'Saved filter not found: {filter_name}. Run `Filters.list()` to list existing filters')
576
- with open(pages.items[0].download()) as f:
577
- data = json.load(f)
578
- custom_filter = data['metadata']['filter']
579
- custom_filter['join'] = data['metadata']['join']
580
- return cls(custom_filter=custom_filter)
581
-
582
- @staticmethod
583
- def list(project: entities.Project) -> list:
584
- """
585
- List all saved filters for a project
586
- :param project: dl.Project entity
587
- :return: a list of all the saved filters' names
588
- """
589
- b_dataset = project.datasets._get_binaries_dataset()
590
- f = entities.Filters(use_defaults=False,
591
- field='dir',
592
- values='/.dataloop/dqlfilters/items')
593
- pages = b_dataset.items.list(filters=f)
594
- all_filter_items = list(pages.all())
595
- names = [i.name for i in all_filter_items]
596
- return names
597
-
598
-
599
- class SingleFilter:
600
- def __init__(self, field, values, operator: FiltersOperations = None):
601
- self.field = field
602
- self.values = values
603
- self.operator = operator
604
-
605
- @staticmethod
606
- def __add_recursive(value):
607
- if not value.endswith('*') and not os.path.splitext(value)[-1].startswith('.'):
608
- if value.endswith('/'):
609
- value = value + '**'
610
- else:
611
- value = value + '/**'
612
- return value
613
-
614
- def prepare(self, recursive=False):
615
- """
616
- To dictionary for platform call
617
-
618
- :param recursive:recursive
619
- """
620
- _json = dict()
621
- values = self.values
622
-
623
- if recursive and self.field == 'filename':
624
- if isinstance(values, str):
625
- values = self.__add_recursive(value=values)
626
- elif isinstance(values, list):
627
- for i_value, value in enumerate(values):
628
- values[i_value] = self.__add_recursive(value=value)
629
-
630
- if self.operator is None:
631
- _json[self.field] = values
632
- else:
633
- value = dict()
634
- op = self.operator.value if isinstance(self.operator, FiltersOperations) else self.operator
635
- value['${}'.format(op)] = values
636
- _json[self.field] = value
637
-
638
- return _json
639
-
640
- def print(self, indent=2):
641
- print(json.dumps(self.prepare(), indent=indent))
1
+ import urllib.parse
2
+ import logging
3
+ import json
4
+ import os
5
+ import io
6
+ from enum import Enum
7
+
8
+ from .. import exceptions, entities
9
+
10
+ logger = logging.getLogger(name='dtlpy')
11
+
12
+
13
+ class FiltersKnownFields(str, Enum):
14
+ DIR = "dir"
15
+ ANNOTATED = "annotated"
16
+ FILENAME = "filename"
17
+ CREATED_AT = "createdAt"
18
+ UPDATED_AT = "updatedAt"
19
+ LABEL = "label"
20
+ NAME = "name"
21
+ HIDDEN = "hidden"
22
+ TYPE = 'type'
23
+
24
+
25
+ class FiltersResource(str, Enum):
26
+ ITEM = "items"
27
+ ANNOTATION = "annotations"
28
+ EXECUTION = "executions"
29
+ PACKAGE = "packages"
30
+ DPK = "dpks"
31
+ APP = "apps"
32
+ SERVICE = "services"
33
+ TRIGGER = "triggers"
34
+ MODEL = "models"
35
+ WEBHOOK = "webhooks"
36
+ RECIPE = 'recipe'
37
+ DATASET = 'datasets'
38
+ ONTOLOGY = 'ontology'
39
+ TASK = 'tasks'
40
+ PIPELINE = 'pipeline'
41
+ PIPELINE_EXECUTION = 'pipelineState'
42
+ COMPOSITION = 'composition'
43
+ FEATURE = 'feature_vectors'
44
+ FEATURE_SET = 'feature_sets'
45
+ ORGANIZATIONS = 'organizations'
46
+ DRIVERS = 'drivers'
47
+ SETTINGS = 'setting'
48
+ RESOURCE_EXECUTION = 'resourceExecution'
49
+ METRICS = 'metrics',
50
+ SERVICE_DRIVER = 'serviceDrivers',
51
+ COMPUTE = 'compute'
52
+
53
+
54
+ class FiltersOperations(str, Enum):
55
+ OR = "or"
56
+ AND = "and"
57
+ IN = "in"
58
+ NOT_EQUAL = "ne"
59
+ EQUAL = "eq"
60
+ GREATER_THAN = "gt"
61
+ LESS_THAN = "lt"
62
+ EXISTS = "exists"
63
+ MATCH = "match"
64
+ NIN = 'nin'
65
+
66
+
67
+ class FiltersMethod(str, Enum):
68
+ OR = "or"
69
+ AND = "and"
70
+
71
+
72
+ class FiltersOrderByDirection(str, Enum):
73
+ DESCENDING = "descending"
74
+ ASCENDING = "ascending"
75
+
76
+
77
+ class Filters:
78
+ """
79
+ Filters entity to filter items from pages in platform
80
+ """
81
+
82
+ def __init__(
83
+ self,
84
+ field=None,
85
+ values=None,
86
+ operator: FiltersOperations = None,
87
+ method: FiltersMethod = None,
88
+ custom_filter=None,
89
+ resource: FiltersResource = FiltersResource.ITEM,
90
+ use_defaults=True,
91
+ context=None,
92
+ page_size=None,
93
+ ):
94
+ if page_size is None:
95
+ if resource in [FiltersResource.EXECUTION,
96
+ FiltersResource.PIPELINE_EXECUTION,
97
+ FiltersResource.DPK]:
98
+ page_size = 100
99
+ else:
100
+ page_size = 1000
101
+
102
+ self.or_filter_list = list()
103
+ self.and_filter_list = list()
104
+ self._unique_fields = list()
105
+ self.custom_filter = custom_filter
106
+ self.known_operators = ['or', 'and', 'in', 'ne', 'eq', 'gt', 'lt', 'exists']
107
+ self._resource = resource
108
+ self.page = 0
109
+ self.page_size = page_size
110
+ self.method = FiltersMethod.AND
111
+ self.sort = dict()
112
+ self.join = None
113
+ self.recursive = True
114
+
115
+ # system only - task and assignment attributes
116
+ self._user_query = 'true'
117
+ self._ref_task = False
118
+ self._ref_assignment = False
119
+ self._ref_op = None
120
+ self._ref_assignment_id = None
121
+ self._ref_task_id = None
122
+ self._system_space = None
123
+
124
+ self._use_defaults = use_defaults
125
+ self.__add_defaults()
126
+ self.context = context
127
+
128
+ if field is not None:
129
+ self.add(field=field, values=values, operator=operator, method=method)
130
+
131
+ def __validate_page_size(self):
132
+ max_page_size = self.__max_page_size
133
+ if self.page_size > max_page_size:
134
+ logger.warning('Cannot list {} with page size greater than {}. Changing page_size to {}.'.format(
135
+ self.resource, max_page_size, max_page_size
136
+ ))
137
+ self.page_size = max_page_size
138
+
139
+ @property
140
+ def __max_page_size(self):
141
+ page_size = 1000
142
+ if self.resource in [FiltersResource.EXECUTION, FiltersResource.PIPELINE_EXECUTION]:
143
+ page_size = 100
144
+ return page_size
145
+
146
+ @property
147
+ def resource(self):
148
+ return f'{self._resource.value}' if isinstance(self._resource, FiltersResource) else f'{self._resource}'
149
+
150
+ @resource.setter
151
+ def resource(self, resource):
152
+ self._resource = resource
153
+ self.reset()
154
+ self.__add_defaults()
155
+
156
+ @property
157
+ def system_space(self):
158
+ return self._system_space
159
+
160
+ @system_space.setter
161
+ def system_space(self, val: bool):
162
+ self._system_space = val
163
+
164
+ def reset(self):
165
+ self.or_filter_list = list()
166
+ self.and_filter_list = list()
167
+ self._unique_fields = list()
168
+ self.custom_filter = None
169
+ self.page = 0
170
+ self.page_size = 1000
171
+ self.method = FiltersMethod.AND
172
+ self.sort = dict()
173
+ self.join = None
174
+ self.recursive = True
175
+ self._nullify_refs()
176
+
177
+ def _nullify_refs(self):
178
+ self._ref_task = False
179
+ self._ref_assignment = False
180
+ self._ref_op = None
181
+ self._ref_assignment_id = None
182
+ self._ref_task_id = None
183
+
184
+ def add(self, field, values, operator: FiltersOperations = None, method: FiltersMethod = None):
185
+ """
186
+ Add filter
187
+
188
+ :param str field: Metadata field / attribute
189
+ :param values: field values
190
+ :param dl.FiltersOperations operator: optional - in, gt, lt, eq, ne
191
+ :param dl.FiltersMethod method: Optional - or/and
192
+
193
+ **Example**:
194
+
195
+ .. code-block:: python
196
+
197
+ filter.add(field='metadata.user', values=['1','2'], operator=dl.FiltersOperations.IN)
198
+ """
199
+ if method is None:
200
+ method = self.method
201
+ if 'metadata.system.refs.metadata' in field and self.resource == FiltersResource.ITEM:
202
+ logger.warning('Filtering by metadata.system.refs.metadata may cause incorrect results. please use match operator')
203
+
204
+ # create SingleFilter object and add to self.filter_list
205
+ if method == FiltersMethod.OR:
206
+ self.or_filter_list.append(SingleFilter(field=field, values=values, operator=operator))
207
+ elif method == FiltersMethod.AND:
208
+ self.__override(field=field, values=values, operator=operator)
209
+ else:
210
+ raise exceptions.PlatformException(error='400',
211
+ message='Unknown method {}, please select from: or/and'.format(method))
212
+
213
+ def __override(self, field, values, operator=None):
214
+ if field in self._unique_fields:
215
+ for i_single_filter, single_filter in enumerate(self.and_filter_list):
216
+ if single_filter.field == field:
217
+ self.and_filter_list.pop(i_single_filter)
218
+ self.and_filter_list.append(
219
+ SingleFilter(field=field, values=values, operator=operator)
220
+ )
221
+
222
+ def generate_url_query_params(self, url):
223
+ """
224
+ generate url query params
225
+
226
+ :param str url:
227
+ """
228
+ url = '{}?'.format(url)
229
+ for f in self.and_filter_list:
230
+ if isinstance(f.values, list):
231
+ url = '{}{}={}&'.format(url, f.field, ','.join(f.values))
232
+ else:
233
+ url = '{}{}={}&'.format(url, f.field, f.values)
234
+ return '{}&pageOffset={}&pageSize={}'.format(url, self.page, self.page_size)
235
+
236
+ def has_field(self, field):
237
+ """
238
+ is filter has field
239
+
240
+ :param str field: field to check
241
+ :return: Ture is have it
242
+ :rtype: bool
243
+ """
244
+ for single_filter in self.or_filter_list:
245
+ if single_filter.field == field:
246
+ return True
247
+
248
+ for single_filter in self.and_filter_list:
249
+ if single_filter.field == field:
250
+ return True
251
+
252
+ return False
253
+
254
+ def pop(self, field):
255
+ """
256
+ Pop filed
257
+
258
+ :param str field: field to pop
259
+ """
260
+ for single_filter in self.or_filter_list:
261
+ if single_filter.field == field:
262
+ self.or_filter_list.remove(single_filter)
263
+
264
+ for single_filter in self.and_filter_list:
265
+ if single_filter.field == field:
266
+ self.and_filter_list.remove(single_filter)
267
+
268
+ def pop_join(self, field):
269
+ """
270
+ Pop join
271
+
272
+ :param str field: field to pop
273
+ """
274
+ if self.join is not None:
275
+ for single_filter in self.join['filter']['$and']:
276
+ if field in single_filter:
277
+ self.join['filter']['$and'].remove(single_filter)
278
+
279
+ def add_join(self, field,
280
+ values,
281
+ operator: FiltersOperations = None,
282
+ method: FiltersMethod = FiltersMethod.AND
283
+ ):
284
+ """
285
+ join a query to the filter
286
+
287
+ :param str field: Metadata field / attribute
288
+ :param str or list values: field values
289
+ :param dl.FiltersOperations operator: optional - in, gt, lt, eq, ne
290
+ :param method: optional - str - FiltersMethod.AND, FiltersMethod.OR
291
+
292
+ **Example**:
293
+
294
+ .. code-block:: python
295
+
296
+ filter.add_join(field='metadata.user', values=['1','2'], operator=dl.FiltersOperations.IN)
297
+ """
298
+ if self.resource not in [FiltersResource.ITEM, FiltersResource.ANNOTATION]:
299
+ raise exceptions.PlatformException(error='400',
300
+ message='Cannot join to {} filters'.format(self.resource))
301
+
302
+ if self.join is None:
303
+ self.join = dict()
304
+ if 'on' not in self.join:
305
+ if self.resource == FiltersResource.ITEM:
306
+ self.join['on'] = {'resource': FiltersResource.ANNOTATION.value, 'local': 'itemId', 'forigen': 'id'}
307
+ else:
308
+ self.join['on'] = {'resource': FiltersResource.ITEM.value, 'local': 'id', 'forigen': 'itemId'}
309
+ if 'filter' not in self.join:
310
+ self.join['filter'] = dict()
311
+ join_method = '$' + method
312
+ if join_method not in self.join['filter']:
313
+ self.join['filter'][join_method] = list()
314
+ self.join['filter'][join_method].append(SingleFilter(field=field, values=values, operator=operator).prepare())
315
+
316
+ def __add_defaults(self):
317
+ if self._use_defaults:
318
+ # add items defaults
319
+ if self.resource == FiltersResource.ITEM:
320
+ self._unique_fields = ['type', 'hidden']
321
+ self.add(field='hidden', values=False, method=FiltersMethod.AND)
322
+ self.add(field='type', values='file', method=FiltersMethod.AND)
323
+ # add service defaults
324
+ elif self.resource == FiltersResource.SERVICE:
325
+ self._unique_fields = ['global']
326
+ self.add(field='global', values=True, operator=FiltersOperations.NOT_EQUAL, method=FiltersMethod.AND)
327
+ elif self.resource == FiltersResource.PACKAGE:
328
+ self._unique_fields = ['global']
329
+ self.add(field='global', values=True, operator=FiltersOperations.NOT_EQUAL, method=FiltersMethod.AND)
330
+ # add annotations defaults
331
+ elif self.resource == FiltersResource.ANNOTATION:
332
+ self._unique_fields = ['type']
333
+ values = [annotation_type.value for annotation_type in entities.AnnotationType]
334
+ values.remove(entities.AnnotationType.NOTE.value)
335
+ self.add(field='type', values=values, operator=FiltersOperations.IN, method=FiltersMethod.AND)
336
+
337
+ def __generate_query(self):
338
+ filters_dict = dict()
339
+
340
+ if len(self.or_filter_list) > 0:
341
+ or_filters = list()
342
+ for single_filter in self.or_filter_list:
343
+ or_filters.append(
344
+ single_filter.prepare(recursive=self.recursive and self.resource == FiltersResource.ITEM))
345
+ filters_dict['$or'] = or_filters
346
+
347
+ if len(self.and_filter_list) > 0:
348
+ and_filters = list()
349
+ for single_filter in self.and_filter_list:
350
+ and_filters.append(
351
+ single_filter.prepare(recursive=self.recursive and self.resource == FiltersResource.ITEM))
352
+ filters_dict['$and'] = and_filters
353
+
354
+ return filters_dict
355
+
356
+ def __generate_custom_query(self):
357
+ filters_dict = dict()
358
+ if 'filter' in self.custom_filter or 'join' in self.custom_filter:
359
+ if 'filter' in self.custom_filter:
360
+ filters_dict = self.custom_filter['filter']
361
+ self.join = self.custom_filter.get('join', self.join)
362
+ else:
363
+ filters_dict = self.custom_filter
364
+ return filters_dict
365
+
366
+ def __generate_ref_query(self):
367
+ refs = list()
368
+ if self._ref_task:
369
+ task_refs = list()
370
+ if not isinstance(self._ref_task_id, list):
371
+ self._ref_task_id = [self._ref_task_id]
372
+
373
+ for ref_id in self._ref_task_id:
374
+ task_refs.append({'type': 'task', 'id': ref_id})
375
+
376
+ refs += task_refs
377
+
378
+ if self._ref_assignment:
379
+ assignment_refs = list()
380
+ if not isinstance(self._ref_assignment_id, list):
381
+ self._ref_assignment_id = [self._ref_assignment_id]
382
+
383
+ for ref_id in self._ref_assignment_id:
384
+ assignment_refs.append({'type': 'assignment', 'id': ref_id})
385
+
386
+ refs += assignment_refs
387
+
388
+ return refs
389
+
390
+ def prepare(self, operation=None, update=None, query_only=False, system_update=None, system_metadata=False):
391
+ """
392
+ To dictionary for platform call
393
+
394
+ :param str operation: operation
395
+ :param update: update
396
+ :param bool query_only: query only
397
+ :param system_update: system update
398
+ :param system_metadata: True, if you want to change metadata system
399
+ :return: dict of the filter
400
+ :rtype: dict
401
+ """
402
+ ########
403
+ # json #
404
+ ########
405
+ _json = dict()
406
+
407
+ if self.custom_filter is None:
408
+ _json['filter'] = self.__generate_query()
409
+ else:
410
+ _json['filter'] = self.__generate_custom_query()
411
+
412
+ ##################
413
+ # filter options #
414
+ ##################
415
+ if not query_only:
416
+ if len(self.sort) > 0:
417
+ _json['sort'] = self.sort
418
+
419
+ self.__validate_page_size()
420
+
421
+ _json['page'] = self.page
422
+ _json['pageSize'] = self.page_size
423
+ _json['resource'] = self.resource
424
+
425
+ ########
426
+ # join #
427
+ ########
428
+ if self.join is not None:
429
+ _json['join'] = self.join
430
+
431
+ #####################
432
+ # operation or refs #
433
+ #####################
434
+ if self._ref_assignment or self._ref_task:
435
+ _json['references'] = {
436
+ 'operation': self._ref_op,
437
+ 'refs': self.__generate_ref_query()
438
+ }
439
+ elif operation is not None:
440
+ if operation == 'update':
441
+ if update:
442
+ _json[operation] = {'metadata': {'user': update}}
443
+ else:
444
+ _json[operation] = dict()
445
+ if system_metadata and system_update:
446
+ _json['systemSpace'] = True
447
+ _json[operation]['metadata'] = _json[operation].get('metadata', dict())
448
+ _json[operation]['metadata']['system'] = system_update
449
+ elif operation == 'delete':
450
+ _json[operation] = True
451
+ _json.pop('sort', None)
452
+ if self.resource == FiltersResource.ITEM:
453
+ _json.pop('page', None)
454
+ _json.pop('pageSize', None)
455
+ else:
456
+ raise exceptions.PlatformException(error='400',
457
+ message='Unknown operation: {}'.format(operation))
458
+
459
+ if self.context is not None:
460
+ _json['context'] = self.context
461
+ if self._system_space is not None:
462
+ _json['systemSpace'] = self._system_space
463
+ return _json
464
+
465
+ def print(self, indent=2):
466
+ print(json.dumps(self.prepare(), indent=indent))
467
+
468
+ def sort_by(self, field, value: FiltersOrderByDirection = FiltersOrderByDirection.ASCENDING):
469
+ """
470
+ sort the filter
471
+
472
+ :param str field: field to sort by it
473
+ :param dl.FiltersOrderByDirection value: FiltersOrderByDirection.ASCENDING, FiltersOrderByDirection.DESCENDING
474
+
475
+ **Example**:
476
+
477
+ .. code-block:: python
478
+
479
+ filter.sort_by(field='metadata.user', values=dl.FiltersOrderByDirection.ASCENDING)
480
+ """
481
+ if value not in [FiltersOrderByDirection.ASCENDING, FiltersOrderByDirection.DESCENDING]:
482
+ raise exceptions.PlatformException(error='400', message='Sort can be by ascending or descending order only')
483
+ self.sort[field] = value.value if isinstance(value, FiltersOrderByDirection) else value
484
+
485
+ def platform_url(self, resource) -> str:
486
+ """
487
+ Build a url with filters param to open in web browser
488
+
489
+ :param str resource: dl entity to apply filter on. currently only supports dl.Dataset
490
+ :return: url string
491
+ :rtype: str
492
+ """
493
+ _json = self.prepare()
494
+ # add the view option
495
+ _json['view'] = 'icons'
496
+ # convert from enum to string
497
+ _json["resource"] = f'{_json["resource"]}'
498
+ # convert the dictionary to a json string
499
+ _json['dqlFilter'] = json.dumps({'filter': _json.pop('filter'),
500
+ 'join': _json.pop('join', None),
501
+ 'sort': _json.get('sort', None)})
502
+ # set the page size as the UI default
503
+ _json['pageSize'] = 100
504
+ _json['page'] = _json['page']
505
+ # build the url for the dataset data browser
506
+ if isinstance(resource, entities.Dataset):
507
+ url = resource.platform_url + f'?{urllib.parse.urlencode(_json)}'
508
+ else:
509
+ raise NotImplementedError('Not implemented for resource type: {}'.format(type(resource)))
510
+ return url
511
+
512
+ def open_in_web(self, resource):
513
+ """
514
+ Open the filter in the platform data browser (in a new web browser)
515
+
516
+ :param str resource: dl entity to apply filter on. currently only supports dl.Dataset
517
+ """
518
+ if isinstance(resource, entities.Dataset):
519
+ resource._client_api._open_in_web(url=self.platform_url(resource=resource))
520
+ else:
521
+ raise NotImplementedError('Not implemented for resource type: {}'.format(type(resource)))
522
+
523
+ def save(self, project: entities.Project, filter_name: str):
524
+ """
525
+ Save the current DQL filter to the project
526
+
527
+ :param project: dl.Project
528
+ :param filter_name: the saved filter's name
529
+ :return: True if success
530
+ """
531
+ _json_filter = self.prepare()
532
+ shebang_dict = {"type": "dql",
533
+ "shebang": "dataloop",
534
+ "metadata": {
535
+ "version": "1.0.0",
536
+ "system": {
537
+ "mimetype": "dql"
538
+ },
539
+ "dltype": "filter",
540
+ "filterFieldsState": [],
541
+ "resource": "items",
542
+ "filter": _json_filter.pop('filter'),
543
+ "join": _json_filter.pop('join')
544
+ }
545
+ }
546
+ b_dataset = project.datasets._get_binaries_dataset()
547
+ byte_io = io.BytesIO()
548
+ byte_io.name = filter_name
549
+ byte_io.write(json.dumps(shebang_dict).encode())
550
+ byte_io.seek(0)
551
+ b_dataset.items.upload(local_path=byte_io,
552
+ remote_path='/.dataloop/dqlfilters/items',
553
+ remote_name=filter_name)
554
+ return True
555
+
556
+ @classmethod
557
+ def load(cls, project: entities.Project, filter_name: str) -> 'Filters':
558
+ """
559
+ Load a saved filter from the project by name
560
+
561
+ :param project: dl.Project entity
562
+ :param filter_name: filter name
563
+ :return: dl.Filters
564
+ """
565
+ b_dataset = project.datasets._get_binaries_dataset()
566
+ f = entities.Filters(custom_filter={
567
+ 'filter': {'$and': [{'filename': f'/.dataloop/dqlfilters/items/{filter_name}'}]},
568
+ 'page': 0,
569
+ 'pageSize': 1000,
570
+ 'resource': 'items'
571
+ })
572
+ pages = b_dataset.items.list(filters=f)
573
+ if pages.items_count == 0:
574
+ raise exceptions.NotFound(
575
+ f'Saved filter not found: {filter_name}. Run `Filters.list()` to list existing filters')
576
+ with open(pages.items[0].download()) as f:
577
+ data = json.load(f)
578
+ custom_filter = data['metadata']['filter']
579
+ custom_filter['join'] = data['metadata']['join']
580
+ return cls(custom_filter=custom_filter)
581
+
582
+ @staticmethod
583
+ def list(project: entities.Project) -> list:
584
+ """
585
+ List all saved filters for a project
586
+ :param project: dl.Project entity
587
+ :return: a list of all the saved filters' names
588
+ """
589
+ b_dataset = project.datasets._get_binaries_dataset()
590
+ f = entities.Filters(use_defaults=False,
591
+ field='dir',
592
+ values='/.dataloop/dqlfilters/items')
593
+ pages = b_dataset.items.list(filters=f)
594
+ all_filter_items = list(pages.all())
595
+ names = [i.name for i in all_filter_items]
596
+ return names
597
+
598
+
599
+ class SingleFilter:
600
+ def __init__(self, field, values, operator: FiltersOperations = None):
601
+ self.field = field
602
+ self.values = values
603
+ self.operator = operator
604
+
605
+ @staticmethod
606
+ def __add_recursive(value):
607
+ if not value.endswith('*') and not os.path.splitext(value)[-1].startswith('.'):
608
+ if value.endswith('/'):
609
+ value = value + '**'
610
+ else:
611
+ value = value + '/**'
612
+ return value
613
+
614
+ def prepare(self, recursive=False):
615
+ """
616
+ To dictionary for platform call
617
+
618
+ :param recursive:recursive
619
+ """
620
+ _json = dict()
621
+ values = self.values
622
+
623
+ if recursive and self.field == 'filename':
624
+ if isinstance(values, str):
625
+ values = self.__add_recursive(value=values)
626
+ elif isinstance(values, list):
627
+ for i_value, value in enumerate(values):
628
+ values[i_value] = self.__add_recursive(value=value)
629
+
630
+ if self.operator is None:
631
+ _json[self.field] = values
632
+ else:
633
+ value = dict()
634
+ op = self.operator.value if isinstance(self.operator, FiltersOperations) else self.operator
635
+ value['${}'.format(op)] = values
636
+ _json[self.field] = value
637
+
638
+ return _json
639
+
640
+ def print(self, indent=2):
641
+ print(json.dumps(self.prepare(), indent=indent))