rucio-clients 32.8.6__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 rucio-clients might be problematic. Click here for more details.

Files changed (88) hide show
  1. rucio/__init__.py +18 -0
  2. rucio/alembicrevision.py +16 -0
  3. rucio/client/__init__.py +16 -0
  4. rucio/client/accountclient.py +413 -0
  5. rucio/client/accountlimitclient.py +155 -0
  6. rucio/client/baseclient.py +929 -0
  7. rucio/client/client.py +77 -0
  8. rucio/client/configclient.py +113 -0
  9. rucio/client/credentialclient.py +54 -0
  10. rucio/client/didclient.py +691 -0
  11. rucio/client/diracclient.py +48 -0
  12. rucio/client/downloadclient.py +1674 -0
  13. rucio/client/exportclient.py +44 -0
  14. rucio/client/fileclient.py +51 -0
  15. rucio/client/importclient.py +42 -0
  16. rucio/client/lifetimeclient.py +74 -0
  17. rucio/client/lockclient.py +99 -0
  18. rucio/client/metaclient.py +137 -0
  19. rucio/client/pingclient.py +45 -0
  20. rucio/client/replicaclient.py +444 -0
  21. rucio/client/requestclient.py +109 -0
  22. rucio/client/rseclient.py +664 -0
  23. rucio/client/ruleclient.py +287 -0
  24. rucio/client/scopeclient.py +88 -0
  25. rucio/client/subscriptionclient.py +161 -0
  26. rucio/client/touchclient.py +78 -0
  27. rucio/client/uploadclient.py +871 -0
  28. rucio/common/__init__.py +14 -0
  29. rucio/common/cache.py +74 -0
  30. rucio/common/config.py +796 -0
  31. rucio/common/constants.py +92 -0
  32. rucio/common/constraints.py +18 -0
  33. rucio/common/didtype.py +187 -0
  34. rucio/common/exception.py +1092 -0
  35. rucio/common/extra.py +37 -0
  36. rucio/common/logging.py +404 -0
  37. rucio/common/pcache.py +1387 -0
  38. rucio/common/policy.py +84 -0
  39. rucio/common/schema/__init__.py +143 -0
  40. rucio/common/schema/atlas.py +411 -0
  41. rucio/common/schema/belleii.py +406 -0
  42. rucio/common/schema/cms.py +478 -0
  43. rucio/common/schema/domatpc.py +399 -0
  44. rucio/common/schema/escape.py +424 -0
  45. rucio/common/schema/generic.py +431 -0
  46. rucio/common/schema/generic_multi_vo.py +410 -0
  47. rucio/common/schema/icecube.py +404 -0
  48. rucio/common/schema/lsst.py +423 -0
  49. rucio/common/stomp_utils.py +160 -0
  50. rucio/common/stopwatch.py +56 -0
  51. rucio/common/test_rucio_server.py +148 -0
  52. rucio/common/types.py +158 -0
  53. rucio/common/utils.py +1946 -0
  54. rucio/rse/__init__.py +97 -0
  55. rucio/rse/protocols/__init__.py +14 -0
  56. rucio/rse/protocols/cache.py +123 -0
  57. rucio/rse/protocols/dummy.py +112 -0
  58. rucio/rse/protocols/gfal.py +701 -0
  59. rucio/rse/protocols/globus.py +243 -0
  60. rucio/rse/protocols/gsiftp.py +93 -0
  61. rucio/rse/protocols/http_cache.py +83 -0
  62. rucio/rse/protocols/mock.py +124 -0
  63. rucio/rse/protocols/ngarc.py +210 -0
  64. rucio/rse/protocols/posix.py +251 -0
  65. rucio/rse/protocols/protocol.py +530 -0
  66. rucio/rse/protocols/rclone.py +365 -0
  67. rucio/rse/protocols/rfio.py +137 -0
  68. rucio/rse/protocols/srm.py +339 -0
  69. rucio/rse/protocols/ssh.py +414 -0
  70. rucio/rse/protocols/storm.py +207 -0
  71. rucio/rse/protocols/webdav.py +547 -0
  72. rucio/rse/protocols/xrootd.py +295 -0
  73. rucio/rse/rsemanager.py +752 -0
  74. rucio/vcsversion.py +11 -0
  75. rucio/version.py +46 -0
  76. rucio_clients-32.8.6.data/data/etc/rse-accounts.cfg.template +25 -0
  77. rucio_clients-32.8.6.data/data/etc/rucio.cfg.atlas.client.template +42 -0
  78. rucio_clients-32.8.6.data/data/etc/rucio.cfg.template +257 -0
  79. rucio_clients-32.8.6.data/data/requirements.txt +55 -0
  80. rucio_clients-32.8.6.data/data/rucio_client/merge_rucio_configs.py +147 -0
  81. rucio_clients-32.8.6.data/scripts/rucio +2540 -0
  82. rucio_clients-32.8.6.data/scripts/rucio-admin +2434 -0
  83. rucio_clients-32.8.6.dist-info/METADATA +50 -0
  84. rucio_clients-32.8.6.dist-info/RECORD +88 -0
  85. rucio_clients-32.8.6.dist-info/WHEEL +5 -0
  86. rucio_clients-32.8.6.dist-info/licenses/AUTHORS.rst +94 -0
  87. rucio_clients-32.8.6.dist-info/licenses/LICENSE +201 -0
  88. rucio_clients-32.8.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,691 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright European Organization for Nuclear Research (CERN) since 2012
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ from datetime import datetime
17
+ from json import dumps
18
+ from urllib.parse import quote_plus
19
+
20
+ from requests.status_codes import codes
21
+
22
+ from rucio.client.baseclient import BaseClient
23
+ from rucio.client.baseclient import choice
24
+ from rucio.common.exception import DeprecationError
25
+ from rucio.common.utils import build_url, render_json, render_json_list, date_to_str
26
+
27
+
28
+ class DIDClient(BaseClient):
29
+
30
+ """DataIdentifier client class for working with data identifiers"""
31
+
32
+ DIDS_BASEURL = 'dids'
33
+ ARCHIVES_BASEURL = 'archives'
34
+
35
+ def list_dids(self, scope, filters, did_type='collection', long=False, recursive=False):
36
+ """
37
+ List all data identifiers in a scope which match a given pattern.
38
+
39
+ :param scope: The scope name.
40
+ :param filters: A nested dictionary of key/value pairs like [{'key1': 'value1', 'key2.lte': 'value2'}, {'key3.gte, 'value3'}].
41
+ Keypairs in the same dictionary are AND'ed together, dictionaries are OR'ed together. Keys should be suffixed
42
+ like <key>.<operation>, e.g. key1 >= value1 is equivalent to {'key1.gte': value}, where <operation> belongs to one
43
+ of the set {'lte', 'gte', 'gt', 'lt', 'ne' or ''}. Equivalence doesn't require an operator.
44
+ :param did_type: The type of the did: 'all'(container, dataset or file)|'collection'(dataset or container)|'dataset'|'container'|'file'
45
+ :param long: Long format option to display more information for each DID.
46
+ :param recursive: Recursively list DIDs content.
47
+ """
48
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), 'dids', 'search'])
49
+
50
+ # stringify dates.
51
+ if isinstance(filters, dict): # backwards compatability for filters as single {}
52
+ filters = [filters]
53
+ for or_group in filters:
54
+ for key, value in or_group.items():
55
+ if isinstance(value, datetime):
56
+ or_group[key] = date_to_str(value)
57
+
58
+ payload = {
59
+ 'type': did_type,
60
+ 'filters': filters,
61
+ 'long': long,
62
+ 'recursive': recursive
63
+ }
64
+
65
+ url = build_url(choice(self.list_hosts), path=path, params=payload)
66
+
67
+ r = self._send_request(url, type_='GET')
68
+
69
+ if r.status_code == codes.ok:
70
+ dids = self._load_json_data(r)
71
+ return dids
72
+ else:
73
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
74
+ raise exc_cls(exc_msg)
75
+
76
+ def list_dids_extended(self, scope, filters, did_type='collection', long=False, recursive=False):
77
+ """
78
+ List all data identifiers in a scope which match a given pattern (DEPRECATED)
79
+ """
80
+ raise DeprecationError("Command or function has been deprecated. Please use list_dids instead.")
81
+
82
+ def add_did(self, scope, name, did_type, statuses=None, meta=None, rules=None, lifetime=None, dids=None, rse=None):
83
+ """
84
+ Add data identifier for a dataset or container.
85
+
86
+ :param scope: The scope name.
87
+ :param name: The data identifier name.
88
+ :param did_type: The data identifier type (file|dataset|container).
89
+ :param statuses: Dictionary with statuses, e.g.g {'monotonic':True}.
90
+ :param meta: Meta-data associated with the data identifier is represented using key/value pairs in a dictionary.
91
+ :param rules: Replication rules associated with the data identifier. A list of dictionaries, e.g., [{'copies': 2, 'rse_expression': 'TIERS1'}, ].
92
+ :param lifetime: DID's lifetime (in seconds).
93
+ :param dids: The content.
94
+ :param rse: The RSE name when registering replicas.
95
+ """
96
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name)])
97
+ url = build_url(choice(self.list_hosts), path=path)
98
+ # Build json
99
+ data = {'type': did_type}
100
+ if statuses:
101
+ data['statuses'] = statuses
102
+ if meta:
103
+ data['meta'] = meta
104
+ if rules:
105
+ data['rules'] = rules
106
+ if lifetime:
107
+ data['lifetime'] = lifetime
108
+ if dids:
109
+ data['dids'] = dids
110
+ if rse:
111
+ data['rse'] = rse
112
+ r = self._send_request(url, type_='POST', data=render_json(**data))
113
+ if r.status_code == codes.created:
114
+ return True
115
+ else:
116
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
117
+ raise exc_cls(exc_msg)
118
+
119
+ def add_dids(self, dids):
120
+ """
121
+ Bulk add datasets/containers.
122
+ """
123
+ path = '/'.join([self.DIDS_BASEURL])
124
+ url = build_url(choice(self.list_hosts), path=path)
125
+ r = self._send_request(url, type_='POST', data=render_json_list(dids))
126
+ if r.status_code == codes.created:
127
+ return True
128
+ else:
129
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
130
+ raise exc_cls(exc_msg)
131
+
132
+ def add_dataset(self, scope, name, statuses=None, meta=None, rules=None, lifetime=None, files=None, rse=None):
133
+ """
134
+ Add data identifier for a dataset.
135
+
136
+ :param scope: The scope name.
137
+ :param name: The data identifier name.
138
+ :param statuses: Dictionary with statuses, e.g.g {'monotonic':True}.
139
+ :param meta: Meta-data associated with the data identifier is represented using key/value pairs in a dictionary.
140
+ :param rules: Replication rules associated with the data identifier. A list of dictionaries, e.g., [{'copies': 2, 'rse_expression': 'TIERS1'}, ].
141
+ :param lifetime: DID's lifetime (in seconds).
142
+ :param files: The content.
143
+ :param rse: The RSE name when registering replicas.
144
+ """
145
+ return self.add_did(scope=scope, name=name, did_type='DATASET',
146
+ statuses=statuses, meta=meta, rules=rules,
147
+ lifetime=lifetime, dids=files, rse=rse)
148
+
149
+ def add_datasets(self, dsns):
150
+ """
151
+ Bulk add datasets.
152
+
153
+ :param dsns: A list of datasets.
154
+ """
155
+ return self.add_dids(dids=[dict(list(dsn.items()) + [('type', 'DATASET')]) for dsn in dsns])
156
+
157
+ def add_container(self, scope, name, statuses=None, meta=None, rules=None, lifetime=None):
158
+ """
159
+ Add data identifier for a container.
160
+
161
+ :param scope: The scope name.
162
+ :param name: The data identifier name.
163
+ :param statuses: Dictionary with statuses, e.g.g {'monotonic':True}.
164
+ :param meta: Meta-data associated with the data identifier is represented using key/value pairs in a dictionary.
165
+ :param rules: Replication rules associated with the data identifier. A list of dictionaries, e.g., [{'copies': 2, 'rse_expression': 'TIERS1'}, ].
166
+ :param lifetime: DID's lifetime (in seconds).
167
+ """
168
+ return self.add_did(scope=scope, name=name, did_type='CONTAINER', statuses=statuses, meta=meta, rules=rules, lifetime=lifetime)
169
+
170
+ def add_containers(self, cnts):
171
+ """
172
+ Bulk add containers.
173
+
174
+ :param cnts: A list of containers.
175
+ """
176
+ return self.add_dids(dids=[dict(list(cnts.items()) + [('type', 'CONTAINER')]) for cnt in cnts])
177
+
178
+ def attach_dids(self, scope, name, dids, rse=None):
179
+ """
180
+ Attach data identifier.
181
+
182
+ :param scope: The scope name.
183
+ :param name: The data identifier name.
184
+ :param dids: The content.
185
+ :param rse: The RSE name when registering replicas.
186
+ """
187
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'dids'])
188
+ url = build_url(choice(self.list_hosts), path=path)
189
+ data = {'dids': dids}
190
+ if rse:
191
+ data['rse'] = rse
192
+ r = self._send_request(url, type_='POST', data=render_json(**data))
193
+ if r.status_code == codes.created:
194
+ return True
195
+ else:
196
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
197
+ raise exc_cls(exc_msg)
198
+
199
+ def detach_dids(self, scope, name, dids):
200
+ """
201
+ Detach data identifier
202
+
203
+ :param scope: The scope name.
204
+ :param name: The data identifier name.
205
+ :param dids: The content.
206
+ """
207
+
208
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'dids'])
209
+ url = build_url(choice(self.list_hosts), path=path)
210
+ data = {'dids': dids}
211
+ r = self._send_request(url, type_='DEL', data=render_json(**data))
212
+ if r.status_code == codes.ok:
213
+ return True
214
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
215
+ raise exc_cls(exc_msg)
216
+
217
+ def attach_dids_to_dids(self, attachments, ignore_duplicate=False):
218
+ """
219
+ Add dids to dids.
220
+
221
+ :param attachments: The attachments.
222
+ attachments is: [attachment, attachment, ...]
223
+ attachment is: {'scope': scope, 'name': name, 'dids': dids}
224
+ dids is: [{'scope': scope, 'name': name}, ...]
225
+ :param ignore_duplicate: If True, ignore duplicate entries.
226
+ """
227
+ path = '/'.join([self.DIDS_BASEURL, 'attachments'])
228
+ url = build_url(choice(self.list_hosts), path=path)
229
+ data = {'ignore_duplicate': ignore_duplicate, 'attachments': attachments}
230
+ r = self._send_request(url, type_='POST', data=dumps(data))
231
+ if r.status_code in (codes.ok, codes.no_content, codes.created):
232
+ return True
233
+
234
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
235
+ raise exc_cls(exc_msg)
236
+
237
+ def add_files_to_datasets(self, attachments, ignore_duplicate=False):
238
+ """
239
+ Add files to datasets.
240
+
241
+ :param attachments: The attachments.
242
+ attachments is: [attachment, attachment, ...]
243
+ attachment is: {'scope': scope, 'name': name, 'dids': dids}
244
+ dids is: [{'scope': scope, 'name': name}, ...]
245
+ :param ignore_duplicate: If True, ignore duplicate entries.
246
+ """
247
+ return self.attach_dids_to_dids(attachments=attachments,
248
+ ignore_duplicate=ignore_duplicate)
249
+
250
+ def add_datasets_to_containers(self, attachments):
251
+ """
252
+ Add datasets_to_containers.
253
+
254
+ :param attachments: The attachments.
255
+ attachments is: [attachment, attachment, ...]
256
+ attachment is: {'scope': scope, 'name': name, 'dids': dids}
257
+ dids is: [{'scope': scope, 'name': name}, ...]
258
+ """
259
+ return self.attach_dids_to_dids(attachments=attachments)
260
+
261
+ def add_containers_to_containers(self, attachments):
262
+ """
263
+ Add containers_to_containers.
264
+
265
+ :param attachments: The attachments.
266
+ attachments is: [attachment, attachment, ...]
267
+ attachment is: {'scope': scope, 'name': name, 'dids': dids}
268
+ dids is: [{'scope': scope, 'name': name}, ...]
269
+ """
270
+ return self.attach_dids_to_dids(attachments=attachments)
271
+
272
+ def add_files_to_dataset(self, scope, name, files, rse=None):
273
+ """
274
+ Add files to datasets.
275
+
276
+ :param scope: The scope name.
277
+ :param name: The dataset name.
278
+ :param files: The content.
279
+ :param rse: The RSE name when registering replicas.
280
+ """
281
+ return self.attach_dids(scope=scope, name=name, dids=files, rse=rse)
282
+
283
+ def add_files_to_archive(self, scope, name, files):
284
+ """
285
+ Add files to archive.
286
+
287
+ :param scope: The scope name.
288
+ :param name: The dataset name.
289
+ :param files: The content.
290
+ """
291
+ return self.attach_dids(scope=scope, name=name, dids=files)
292
+
293
+ def add_datasets_to_container(self, scope, name, dsns):
294
+ """
295
+ Add datasets to container.
296
+
297
+ :param scope: The scope name.
298
+ :param name: The dataset name.
299
+ :param dsns: The content.
300
+ """
301
+ return self.attach_dids(scope=scope, name=name, dids=dsns)
302
+
303
+ def add_containers_to_container(self, scope, name, cnts):
304
+ """
305
+ Add containers to container.
306
+
307
+ :param scope: The scope name.
308
+ :param name: The dataset name.
309
+ :param cnts: The content.
310
+ """
311
+ return self.attach_dids(scope=scope, name=name, dids=cnts)
312
+
313
+ def list_content(self, scope, name):
314
+ """
315
+ List data identifier contents.
316
+
317
+ :param scope: The scope name.
318
+ :param name: The data identifier name.
319
+ """
320
+
321
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'dids'])
322
+ url = build_url(choice(self.list_hosts), path=path)
323
+ r = self._send_request(url, type_='GET')
324
+ if r.status_code == codes.ok:
325
+ return self._load_json_data(r)
326
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
327
+ raise exc_cls(exc_msg)
328
+
329
+ def list_content_history(self, scope, name):
330
+ """
331
+ List data identifier contents history.
332
+
333
+ :param scope: The scope name.
334
+ :param name: The data identifier name.
335
+ """
336
+
337
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'dids', 'history'])
338
+ url = build_url(choice(self.list_hosts), path=path)
339
+ r = self._send_request(url, type_='GET')
340
+ if r.status_code == codes.ok:
341
+ return self._load_json_data(r)
342
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
343
+ raise exc_cls(exc_msg)
344
+
345
+ def list_files(self, scope, name, long=None):
346
+ """
347
+ List data identifier file contents.
348
+
349
+ :param scope: The scope name.
350
+ :param name: The data identifier name.
351
+ :param long: A boolean to choose if GUID is returned or not.
352
+ """
353
+
354
+ payload = {}
355
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'files'])
356
+ if long:
357
+ payload['long'] = True
358
+ url = build_url(choice(self.list_hosts), path=path, params=payload)
359
+
360
+ r = self._send_request(url, type_='GET')
361
+ if r.status_code == codes.ok:
362
+ return self._load_json_data(r)
363
+ else:
364
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
365
+ raise exc_cls(exc_msg)
366
+
367
+ def get_did(self, scope, name, dynamic=False, dynamic_depth=None):
368
+ """
369
+ Retrieve a single data identifier.
370
+
371
+ :param scope: The scope name.
372
+ :param name: The data identifier name.
373
+ :param dynamic_depth: The DID type as string ('FILE'/'DATASET') at which to stop the dynamic
374
+ length/bytes calculation. If not set, the size will not be computed dynamically.
375
+ :param dynamic: (Deprecated) same as dynamic_depth = 'FILE'
376
+ """
377
+
378
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name)])
379
+ params = {}
380
+ if dynamic_depth:
381
+ params['dynamic_depth'] = dynamic_depth
382
+ elif dynamic:
383
+ params['dynamic_depth'] = 'FILE'
384
+ url = build_url(choice(self.list_hosts), path=path, params=params)
385
+ r = self._send_request(url, type_='GET')
386
+ if r.status_code == codes.ok:
387
+ return next(self._load_json_data(r))
388
+ else:
389
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
390
+ raise exc_cls(exc_msg)
391
+
392
+ def get_metadata(self, scope, name, plugin='DID_COLUMN'):
393
+ """
394
+ Get data identifier metadata
395
+
396
+ :param scope: The scope name.
397
+ :param name: The data identifier name.
398
+ :param plugin: Backend Metadata plugin the Rucio server should use to query data.
399
+ """
400
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'meta'])
401
+ url = build_url(choice(self.list_hosts), path=path)
402
+ payload = {}
403
+ payload['plugin'] = plugin
404
+ r = self._send_request(url, type_='GET', params=payload)
405
+ if r.status_code == codes.ok:
406
+ meta = self._load_json_data(r)
407
+ return next(meta)
408
+ else:
409
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
410
+ raise exc_cls(exc_msg)
411
+
412
+ def get_metadata_bulk(self, dids, inherit=False):
413
+ """
414
+ Bulk get data identifier metadata
415
+ :param inherit: A boolean. If set to true, the metadata of the parent are concatenated.
416
+ :param dids: A list of dids.
417
+ """
418
+ data = {'dids': dids, 'inherit': inherit}
419
+ path = '/'.join([self.DIDS_BASEURL, 'bulkmeta'])
420
+ url = build_url(choice(self.list_hosts), path=path)
421
+ r = self._send_request(url, type_='POST', data=dumps(data))
422
+ if r.status_code == codes.ok:
423
+ return self._load_json_data(r)
424
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
425
+ raise exc_cls(exc_msg)
426
+
427
+ def set_metadata(self, scope, name, key, value, recursive=False):
428
+ """
429
+ Set data identifier metadata
430
+
431
+ :param scope: The scope name.
432
+ :param name: The data identifier name.
433
+ :param key: the key.
434
+ :param value: the value.
435
+ :param recursive: Option to propagate the metadata change to content.
436
+ """
437
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'meta', key])
438
+ url = build_url(choice(self.list_hosts), path=path)
439
+ data = dumps({'value': value, 'recursive': recursive})
440
+ r = self._send_request(url, type_='POST', data=data)
441
+ if r.status_code == codes.created:
442
+ return True
443
+ else:
444
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
445
+ raise exc_cls(exc_msg)
446
+
447
+ def set_metadata_bulk(self, scope, name, meta, recursive=False):
448
+ """
449
+ Set data identifier metadata in bulk.
450
+
451
+ :param scope: The scope name.
452
+ :param name: The data identifier name.
453
+ :param meta: the metadata key-values.
454
+ :type meta: dict
455
+ :param recursive: Option to propagate the metadata change to content.
456
+ """
457
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'meta'])
458
+ url = build_url(choice(self.list_hosts), path=path)
459
+ data = dumps({'meta': meta, 'recursive': recursive})
460
+ r = self._send_request(url, type_='POST', data=data)
461
+ if r.status_code == codes.created:
462
+ return True
463
+ else:
464
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
465
+ raise exc_cls(exc_msg)
466
+
467
+ def set_dids_metadata_bulk(self, dids, recursive=False):
468
+ """
469
+ Set metadata to a list of data identifiers.
470
+
471
+ :param dids: A list of dids including metadata, i.e. [['scope': scope1, 'name': name1, 'meta': {key1: value1, key2: value2}] .
472
+ :param recursive: Option to propagate the metadata update to content.
473
+ """
474
+ path = '/'.join([self.DIDS_BASEURL, 'bulkdidsmeta'])
475
+ url = build_url(choice(self.list_hosts), path=path)
476
+ data = dumps({'dids': dids, 'recursive': recursive})
477
+ r = self._send_request(url, type_='POST', data=data)
478
+ if r.status_code == codes.created:
479
+ return True
480
+ else:
481
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
482
+ raise exc_cls(exc_msg)
483
+
484
+ def set_status(self, scope, name, **kwargs):
485
+ """
486
+ Set data identifier status
487
+
488
+ :param scope: The scope name.
489
+ :param name: The data identifier name.
490
+ :param kwargs: Keyword arguments of the form status_name=value.
491
+ """
492
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'status'])
493
+ url = build_url(choice(self.list_hosts), path=path)
494
+ data = dumps(kwargs)
495
+ r = self._send_request(url, type_='PUT', data=data)
496
+ if r.status_code in (codes.ok, codes.no_content, codes.created):
497
+ return True
498
+
499
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
500
+ raise exc_cls(exc_msg)
501
+
502
+ def close(self, scope, name):
503
+ """
504
+ close dataset/container
505
+
506
+ :param scope: The scope name.
507
+ :param name: The dataset/container name.
508
+ """
509
+ return self.set_status(scope=scope, name=name, open=False)
510
+
511
+ def delete_metadata(self, scope, name, key):
512
+ """
513
+ Delete data identifier metadata
514
+
515
+ :param scope: The scope name.
516
+ :param name: The data identifier.
517
+ :param key: the key.
518
+ """
519
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'meta'])
520
+ url = build_url(choice(self.list_hosts), path=path, params={'key': key})
521
+
522
+ r = self._send_request(url, type_='DEL')
523
+ if r.status_code == codes.ok:
524
+ return True
525
+ else:
526
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
527
+ raise exc_cls(exc_msg)
528
+
529
+ def list_did_rules(self, scope, name):
530
+ """
531
+ List the associated rules of a data identifier.
532
+
533
+ :param scope: The scope name.
534
+ :param name: The data identifier name.
535
+ """
536
+
537
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'rules'])
538
+ url = build_url(choice(self.list_hosts), path=path)
539
+ r = self._send_request(url, type_='GET')
540
+ if r.status_code == codes.ok:
541
+ return self._load_json_data(r)
542
+ else:
543
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
544
+ raise exc_cls(exc_msg)
545
+
546
+ def list_associated_rules_for_file(self, scope, name):
547
+ """
548
+ List the associated rules a file is affected from..
549
+
550
+ :param scope: The scope name.
551
+ :param name: The file name.
552
+ """
553
+
554
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'associated_rules'])
555
+ url = build_url(choice(self.list_hosts), path=path)
556
+ r = self._send_request(url, type_='GET')
557
+ if r.status_code == codes.ok:
558
+ return self._load_json_data(r)
559
+ else:
560
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
561
+ raise exc_cls(exc_msg)
562
+
563
+ def get_dataset_by_guid(self, guid):
564
+ """
565
+ Get the parent datasets for a given GUID.
566
+ :param guid: The GUID.
567
+
568
+ :returns: A did
569
+ """
570
+
571
+ path = '/'.join([self.DIDS_BASEURL, guid, 'guid'])
572
+ url = build_url(choice(self.list_hosts), path=path)
573
+ r = self._send_request(url, type_='GET')
574
+ if r.status_code == codes.ok:
575
+ return self._load_json_data(r)
576
+ else:
577
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
578
+ raise exc_cls(exc_msg)
579
+
580
+ def scope_list(self, scope, name=None, recursive=False):
581
+ """
582
+ List data identifiers in a scope.
583
+
584
+ :param scope: The scope name.
585
+ :param name: The data identifier name.
586
+ :param recursive: boolean, True or False.
587
+ """
588
+
589
+ payload = {}
590
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), ''])
591
+ if name:
592
+ payload['name'] = name
593
+ if recursive:
594
+ payload['recursive'] = True
595
+ url = build_url(choice(self.list_hosts), path=path, params=payload)
596
+
597
+ r = self._send_request(url, type_='GET')
598
+ if r.status_code == codes.ok:
599
+ return self._load_json_data(r)
600
+ else:
601
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
602
+ raise exc_cls(exc_msg)
603
+
604
+ def list_parent_dids(self, scope, name):
605
+ """
606
+ List parent dataset/containers of a did.
607
+
608
+ :param scope: The scope.
609
+ :param name: The name.
610
+ """
611
+
612
+ path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'parents'])
613
+ url = build_url(choice(self.list_hosts), path=path)
614
+
615
+ r = self._send_request(url, type_='GET')
616
+ if r.status_code == codes.ok:
617
+ return self._load_json_data(r)
618
+ else:
619
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
620
+ raise exc_cls(exc_msg)
621
+
622
+ def create_did_sample(self, input_scope, input_name, output_scope, output_name, nbfiles):
623
+ """
624
+ Create a sample from an input collection.
625
+
626
+ :param input_scope: The scope of the input DID.
627
+ :param input_name: The name of the input DID.
628
+ :param output_scope: The scope of the output dataset.
629
+ :param output_name: The name of the output dataset.
630
+ :param account: The account.
631
+ :param nbfiles: The number of files to register in the output dataset.
632
+ """
633
+ path = '/'.join([self.DIDS_BASEURL, 'sample'])
634
+ data = dumps({
635
+ 'input_scope': input_scope,
636
+ 'input_name': input_name,
637
+ 'output_scope': output_scope,
638
+ 'output_name': output_name,
639
+ 'nbfiles': str(nbfiles)
640
+ })
641
+ url = build_url(choice(self.list_hosts), path=path)
642
+ r = self._send_request(url, type_='POST', data=data)
643
+ if r.status_code == codes.created:
644
+ return True
645
+ else:
646
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
647
+ raise exc_cls(exc_msg)
648
+
649
+ def resurrect(self, dids):
650
+ """
651
+ Resurrect a list of dids.
652
+
653
+ :param dids: A list of dids [{'scope': scope, 'name': name}, ...]
654
+ """
655
+ path = '/'.join([self.DIDS_BASEURL, 'resurrect'])
656
+ url = build_url(choice(self.list_hosts), path=path)
657
+ r = self._send_request(url, type_='POST', data=dumps(dids))
658
+ if r.status_code == codes.created:
659
+ return True
660
+ else:
661
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
662
+ raise exc_cls(exc_msg)
663
+
664
+ def add_temporary_dids(self, dids):
665
+ """
666
+ Bulk add temporary data identifiers.
667
+
668
+ :param dids: A list of dids.
669
+ """
670
+ url = build_url(choice(self.list_hosts), path='tmp_dids')
671
+ r = self._send_request(url, type_='POST', data=dumps(dids))
672
+ if r.status_code == codes.created:
673
+ return True
674
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
675
+ raise exc_cls(exc_msg)
676
+
677
+ def list_archive_content(self, scope, name):
678
+ """
679
+ List archive contents.
680
+
681
+ :param scope: The scope name.
682
+ :param name: The data identifier name.
683
+ """
684
+ path = '/'.join([self.ARCHIVES_BASEURL, quote_plus(scope), quote_plus(name), 'files'])
685
+ url = build_url(choice(self.list_hosts), path=path)
686
+
687
+ r = self._send_request(url, type_='GET')
688
+ if r.status_code == codes.ok:
689
+ return self._load_json_data(r)
690
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
691
+ raise exc_cls(exc_msg)