rucio-clients 35.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rucio-clients might be problematic. Click here for more details.

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