dafab-client 2.2.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.
Files changed (96) hide show
  1. dafab_client/__init__.py +65 -0
  2. dafab_client/_rucio/__init__.py +1 -0
  3. dafab_client/_rucio/client/__init__.py +15 -0
  4. dafab_client/_rucio/client/accountclient.py +578 -0
  5. dafab_client/_rucio/client/baseclient.py +1070 -0
  6. dafab_client/_rucio/client/client.py +133 -0
  7. dafab_client/_rucio/client/credentialclient.py +110 -0
  8. dafab_client/_rucio/client/didclient.py +1057 -0
  9. dafab_client/_rucio/client/downloadclient.py +2058 -0
  10. dafab_client/_rucio/client/pingclient.py +78 -0
  11. dafab_client/_rucio/client/replicaclient.py +570 -0
  12. dafab_client/_rucio/client/rseclient.py +974 -0
  13. dafab_client/_rucio/client/scopeclient.py +116 -0
  14. dafab_client/_rucio/client/uploadclient.py +1514 -0
  15. dafab_client/_rucio/common/cache.py +110 -0
  16. dafab_client/_rucio/common/checksum.py +168 -0
  17. dafab_client/_rucio/common/client.py +96 -0
  18. dafab_client/_rucio/common/config.py +786 -0
  19. dafab_client/_rucio/common/constants.py +247 -0
  20. dafab_client/_rucio/common/constraints.py +17 -0
  21. dafab_client/_rucio/common/didtype.py +237 -0
  22. dafab_client/_rucio/common/exception.py +1305 -0
  23. dafab_client/_rucio/common/extra.py +31 -0
  24. dafab_client/_rucio/common/filter.py +202 -0
  25. dafab_client/_rucio/common/logging.py +420 -0
  26. dafab_client/_rucio/common/pcache.py +1404 -0
  27. dafab_client/_rucio/common/plugins.py +236 -0
  28. dafab_client/_rucio/common/types.py +481 -0
  29. dafab_client/_rucio/common/utils.py +1886 -0
  30. dafab_client/_rucio/core/rse.py +2080 -0
  31. dafab_client/_rucio/dafab_lib.py +317 -0
  32. dafab_client/_rucio/global_utils.py +101 -0
  33. dafab_client/_rucio/overridden_rucio_client.py +788 -0
  34. dafab_client/_rucio/rse/__init__.py +124 -0
  35. dafab_client/_rucio/rse/protocols/__init__.py +13 -0
  36. dafab_client/_rucio/rse/protocols/gfal.py +708 -0
  37. dafab_client/_rucio/rse/protocols/protocol.py +365 -0
  38. dafab_client/_rucio/rse/protocols/webdav.py +621 -0
  39. dafab_client/_rucio/rse/rsemanager.py +897 -0
  40. dafab_client/_rucio/rse/translation.py +271 -0
  41. dafab_client/_rucio/secrets/user_dafab/config +12 -0
  42. dafab_client/_rucio/secrets/user_dafab/rucio_ca_bundle.pem +167 -0
  43. dafab_client/_rucio/version.py +42 -0
  44. dafab_client/example_notebooks/dafab_dasi_workflows.ipynb +315 -0
  45. dafab_client/example_notebooks/dafab_operator_workflows.ipynb +450 -0
  46. dafab_client/example_notebooks/dafab_simple_user_workflows.ipynb +917 -0
  47. dafab_client/example_notebooks/dafab_skim_workflows.ipynb +497 -0
  48. dafab_client/helpers/System/check_available_accounts.py +179 -0
  49. dafab_client/helpers/System/check_storage.py +96 -0
  50. dafab_client/helpers/__init__.py +15 -0
  51. dafab_client/helpers/check_server_health.py +86 -0
  52. dafab_client/helpers/container_management/ensure_container.py +94 -0
  53. dafab_client/helpers/container_management/list_containers.py +128 -0
  54. dafab_client/helpers/dataset_management/ensure_dataset.py +90 -0
  55. dafab_client/helpers/dataset_management/list_datasets.py +76 -0
  56. dafab_client/helpers/dataset_management/scope_management.py +183 -0
  57. dafab_client/helpers/db_bootstrap/account_manager.py +919 -0
  58. dafab_client/helpers/demo_imports.py +96 -0
  59. dafab_client/helpers/did_management/operations.py +160 -0
  60. dafab_client/helpers/file_management/Deletion/delete_file_of_scope_name.py +68 -0
  61. dafab_client/helpers/file_management/Deletion/delete_replica_of_scope_name.py +74 -0
  62. dafab_client/helpers/file_management/Insertion/upload_file_of_scope_name.py +142 -0
  63. dafab_client/helpers/file_management/Retrieval/download_file_of_scope_name.py +150 -0
  64. dafab_client/helpers/file_management/Workflows/manage_derived_assets.py +1240 -0
  65. dafab_client/helpers/file_management/list_files.py +121 -0
  66. dafab_client/helpers/filtering/filter_by_bbox.py +170 -0
  67. dafab_client/helpers/filtering/filter_by_bbox_and_timerange.py +203 -0
  68. dafab_client/helpers/filtering/filter_by_enhanced_filter.py +119 -0
  69. dafab_client/helpers/filtering/filter_by_relationships.py +207 -0
  70. dafab_client/helpers/filtering/filter_by_timerange.py +159 -0
  71. dafab_client/helpers/filtering/mapper.py +388 -0
  72. dafab_client/helpers/metadata_management/Insertion/set_metadata.py +110 -0
  73. dafab_client/helpers/metadata_management/Operations/common.py +100 -0
  74. dafab_client/helpers/metadata_management/Operations/delete_metadata_for_did.py +41 -0
  75. dafab_client/helpers/metadata_management/Operations/insert_metadata_for_did.py +50 -0
  76. dafab_client/helpers/metadata_management/Operations/update_metadata_for_did.py +50 -0
  77. dafab_client/helpers/metadata_management/Operations/upsert_metadata_for_did.py +50 -0
  78. dafab_client/helpers/metadata_management/Patch/patch_metadata_for_dids.py +104 -0
  79. dafab_client/helpers/metadata_management/Preflight/validate_derived_item_file.py +1707 -0
  80. dafab_client/helpers/metadata_management/Preflight/validate_facet_value_catalog_file.py +438 -0
  81. dafab_client/helpers/metadata_management/Preflight/validate_original_item_file.py +1326 -0
  82. dafab_client/helpers/metadata_management/Retrieval/Complete/get_metadata_for_scope_name.py +141 -0
  83. dafab_client/helpers/metadata_management/Retrieval/Partial/get_single_metadata_for_scope_name.py +116 -0
  84. dafab_client/helpers/metadata_management/Workflows/sync_spatial_bbox_integrity.py +1805 -0
  85. dafab_client/helpers/metadata_management/Workflows/update_derived_item.py +999 -0
  86. dafab_client/helpers/metadata_management/Workflows/update_original_item.py +575 -0
  87. dafab_client/helpers/metadata_management/document_api.py +64 -0
  88. dafab_client/helpers/resources/schemas/Schema_Copernicus_with_dafab.json +4781 -0
  89. dafab_client/helpers/resources/schemas/Schema_DaFab_Facet_Value_Catalog.json +52 -0
  90. dafab_client/helpers/resources/schemas/dafab-smart_agriculture-item.schema.json +413 -0
  91. dafab_client/helpers/resources/schemas/dafab-water_analysis-item.schema.json +1134 -0
  92. dafab_client/helpers/runtime_paths.py +80 -0
  93. dafab_client-2.2.0.dist-info/METADATA +122 -0
  94. dafab_client-2.2.0.dist-info/RECORD +96 -0
  95. dafab_client-2.2.0.dist-info/WHEEL +5 -0
  96. dafab_client-2.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,65 @@
1
+ """Public import namespace for the DaFab client package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from shutil import copy2
7
+
8
+ from . import helpers
9
+ from ._rucio.global_utils import get_active_account, set_active_account
10
+
11
+ _ACCOUNT_API = ("get_active_account", "set_active_account")
12
+ _EXAMPLE_NOTEBOOKS_DIR = Path(__file__).resolve().parent / "example_notebooks"
13
+ _EXAMPLE_PROFILE_TO_NOTEBOOK = {
14
+ "user": "dafab_simple_user_workflows.ipynb",
15
+ "admin": "dafab_operator_workflows.ipynb",
16
+ "skim": "dafab_skim_workflows.ipynb",
17
+ "dasi": "dafab_dasi_workflows.ipynb",
18
+ }
19
+ _EXAMPLE_PROFILE_ALIASES = {
20
+ "operator": "admin",
21
+ }
22
+
23
+ __all__ = ("helpers", *_ACCOUNT_API, "get_example", *helpers.__all__)
24
+
25
+
26
+ def get_example(profile: str | None = None, destination: str | Path = ".") -> list[Path]:
27
+ """Copy packaged notebooks into ``destination`` and return copied local paths."""
28
+ if profile is None:
29
+ notebook_names = sorted(set(_EXAMPLE_PROFILE_TO_NOTEBOOK.values()))
30
+ else:
31
+ normalized = str(profile).strip().lower()
32
+ normalized = _EXAMPLE_PROFILE_ALIASES.get(normalized, normalized)
33
+ notebook_name = _EXAMPLE_PROFILE_TO_NOTEBOOK.get(normalized)
34
+ if notebook_name is None:
35
+ valid_profiles = ", ".join(sorted(_EXAMPLE_PROFILE_TO_NOTEBOOK))
36
+ raise ValueError(
37
+ f"Unknown profile '{profile}'. Supported values: {valid_profiles}."
38
+ )
39
+ notebook_names = [notebook_name]
40
+
41
+ target_dir = Path(destination).expanduser().resolve()
42
+ target_dir.mkdir(parents=True, exist_ok=True)
43
+
44
+ copied_paths: list[Path] = []
45
+ for notebook_name in notebook_names:
46
+ source_path = _EXAMPLE_NOTEBOOKS_DIR / notebook_name
47
+ if not source_path.is_file():
48
+ raise FileNotFoundError(f"Missing packaged notebook: {source_path}")
49
+ target_path = target_dir / notebook_name
50
+ copy2(source_path, target_path)
51
+ copied_paths.append(target_path)
52
+
53
+ return copied_paths
54
+
55
+
56
+ def __getattr__(name: str):
57
+ if name in helpers.__all__:
58
+ return getattr(helpers, name)
59
+ if name in _ACCOUNT_API:
60
+ return globals()[name]
61
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
62
+
63
+
64
+ def __dir__() -> list[str]:
65
+ return sorted(set(globals()) | set(__all__))
@@ -0,0 +1 @@
1
+ """Internal vendored Rucio modules used by dafab_client."""
@@ -0,0 +1,15 @@
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 .client import Client # noqa: F401
@@ -0,0 +1,578 @@
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 json import dumps
16
+ from typing import TYPE_CHECKING, Any, Optional
17
+ from urllib.parse import quote_plus
18
+
19
+ from requests.status_codes import codes
20
+
21
+ from dafab_client._rucio.client.baseclient import BaseClient, choice
22
+ from dafab_client._rucio.common.constants import HTTPMethod
23
+ from dafab_client._rucio.common.utils import build_url
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Iterator
27
+
28
+
29
+ class AccountClient(BaseClient):
30
+
31
+ """Account client class for working with rucio accounts"""
32
+
33
+ ACCOUNTS_BASEURL = 'accounts'
34
+
35
+ def add_account(self, account: str, type_: str, email: str) -> bool:
36
+ """
37
+ Sends the request to create a new account.
38
+
39
+ Parameters
40
+ ----------
41
+ account :
42
+ The name of the account.
43
+ type_ :
44
+ The account type.
45
+ email :
46
+ The Email address associated with the account.
47
+
48
+ Returns
49
+ -------
50
+
51
+ True if account was created successfully else False.
52
+
53
+ Raises
54
+ ------
55
+ Duplicate
56
+ If account already exists.
57
+ """
58
+
59
+ data = dumps({'type': type_, 'email': email})
60
+ path = '/'.join([self.ACCOUNTS_BASEURL, account])
61
+ url = build_url(choice(self.list_hosts), path=path)
62
+
63
+ res = self._send_request(url, method=HTTPMethod.POST, data=data)
64
+ if res.status_code == codes.created:
65
+ return True
66
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
67
+ raise exc_cls(exc_msg)
68
+
69
+ def delete_account(self, account: str) -> bool:
70
+ """
71
+ Send the request to disable an account.
72
+
73
+ Parameters
74
+ ----------
75
+ account :
76
+ The name of the account.
77
+
78
+ Returns
79
+ -------
80
+
81
+ True if account was disabled successfully. False otherwise.
82
+
83
+ Raises
84
+ ------
85
+ AccountNotFound
86
+ If account doesn't exist.
87
+ """
88
+
89
+ path = '/'.join([self.ACCOUNTS_BASEURL, account])
90
+ url = build_url(choice(self.list_hosts), path=path)
91
+
92
+ res = self._send_request(url, method=HTTPMethod.DELETE)
93
+
94
+ if res.status_code == codes.ok:
95
+ return True
96
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
97
+ raise exc_cls(exc_msg)
98
+
99
+ def get_account(self, account: str) -> Optional[dict[str, Any]]:
100
+ """
101
+ Send the request to get information about a given account.
102
+
103
+ Parameters
104
+ ----------
105
+ account :
106
+ The name of the account.
107
+
108
+ Returns
109
+ -------
110
+
111
+ A dictionary of attributes for the account. None if failure.
112
+
113
+ Raises
114
+ ------
115
+ AccountNotFound
116
+ If account doesn't exist.
117
+ """
118
+
119
+ path = '/'.join([self.ACCOUNTS_BASEURL, account])
120
+ url = build_url(choice(self.list_hosts), path=path)
121
+
122
+ res = self._send_request(url, method=HTTPMethod.GET)
123
+ if res.status_code == codes.ok:
124
+ acc = self._load_json_data(res)
125
+ return next(acc)
126
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
127
+ raise exc_cls(exc_msg)
128
+
129
+ def update_account(self, account: str, key: str, value: Any) -> bool:
130
+ """
131
+ Update a property of an account.
132
+
133
+ Parameters
134
+ ----------
135
+ account :
136
+ Name of the account.
137
+ key :
138
+ Account property like status.
139
+ value :
140
+ Property value.
141
+
142
+ Returns
143
+ -------
144
+
145
+ True if successful.
146
+
147
+ Raises
148
+ ------
149
+ Exception
150
+ If update fails.
151
+ """
152
+ data = dumps({key: value})
153
+ path = '/'.join([self.ACCOUNTS_BASEURL, account])
154
+ url = build_url(choice(self.list_hosts), path=path)
155
+
156
+ res = self._send_request(url, method=HTTPMethod.PUT, data=data)
157
+
158
+ if res.status_code == codes.ok:
159
+ return True
160
+ else:
161
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
162
+ raise exc_cls(exc_msg)
163
+
164
+ def list_accounts(
165
+ self,
166
+ account_type: Optional[str] = None,
167
+ identity: Optional[str] = None,
168
+ filters: Optional[dict[str, Any]] = None
169
+ ) -> "Iterator[dict[str, Any]]":
170
+ """
171
+ Send the request to list all rucio accounts.
172
+
173
+ Parameters
174
+ ----------
175
+ account_type :
176
+ The account type.
177
+ identity :
178
+ The identity key name. For example x509 DN, or a username.
179
+ filters :
180
+ A dictionary key:account attribute to use for the filtering.
181
+
182
+ Returns
183
+ -------
184
+
185
+ An iterator of dictionaries containing account information.
186
+
187
+ Raises
188
+ ------
189
+ AccountNotFound
190
+ If account doesn't exist.
191
+ """
192
+ path = '/'.join([self.ACCOUNTS_BASEURL])
193
+ url = build_url(choice(self.list_hosts), path=path)
194
+ params = {}
195
+ if account_type:
196
+ params['account_type'] = account_type
197
+ if identity:
198
+ params['identity'] = identity
199
+ if filters:
200
+ for key in filters:
201
+ params[key] = filters[key]
202
+
203
+ res = self._send_request(url, method=HTTPMethod.GET, params=params)
204
+
205
+ if res.status_code == codes.ok:
206
+ accounts = self._load_json_data(res)
207
+ return accounts
208
+ else:
209
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
210
+ raise exc_cls(exc_msg)
211
+
212
+ def whoami(self) -> Optional[dict[str, Any]]:
213
+ """
214
+ Get information about account whose token is used.
215
+
216
+ Returns
217
+ -------
218
+
219
+ A dictionary of attributes for the account. None if failure.
220
+
221
+ Raises
222
+ ------
223
+ AccountNotFound
224
+ If account doesn't exist.
225
+ """
226
+ return self.get_account('whoami')
227
+
228
+ def add_identity(
229
+ self,
230
+ account: str,
231
+ identity: str,
232
+ authtype: str,
233
+ email: str,
234
+ default: bool = False,
235
+ password: Optional[str] = None
236
+ ) -> bool:
237
+ """
238
+ Add a membership association between identity and account.
239
+
240
+ Parameters
241
+ ----------
242
+ account :
243
+ The account name.
244
+ identity :
245
+ The identity key name. For example x509 DN, or a username.
246
+ authtype :
247
+ The type of the authentication (x509, gss, userpass).
248
+ email :
249
+ The Email address associated with the identity.
250
+ default :
251
+ If True, the account should be used by default with the provided identity.
252
+ password :
253
+ Password if authtype is userpass.
254
+
255
+ Returns
256
+ -------
257
+
258
+ True if successful.
259
+
260
+ """
261
+
262
+ data = dumps({'identity': identity, 'authtype': authtype, 'default': default, 'email': email, 'password': password})
263
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'identities'])
264
+
265
+ url = build_url(choice(self.list_hosts), path=path)
266
+
267
+ res = self._send_request(url, method=HTTPMethod.POST, data=data)
268
+
269
+ if res.status_code == codes.created:
270
+ return True
271
+ else:
272
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
273
+ raise exc_cls(exc_msg)
274
+
275
+ def del_identity(
276
+ self,
277
+ account: str,
278
+ identity: str,
279
+ authtype: str
280
+ ) -> bool:
281
+ """
282
+ Delete an identity's membership association with an account.
283
+
284
+ Parameters
285
+ ----------
286
+ account :
287
+ The account name.
288
+ identity :
289
+ The identity key name. For example x509 DN, or a username.
290
+ authtype :
291
+ The type of the authentication (x509, gss, userpass).
292
+
293
+ Returns
294
+ -------
295
+
296
+ True if successful.
297
+ """
298
+
299
+ data = dumps({'identity': identity, 'authtype': authtype})
300
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'identities'])
301
+
302
+ url = build_url(choice(self.list_hosts), path=path)
303
+
304
+ res = self._send_request(url, method=HTTPMethod.DELETE, data=data)
305
+
306
+ if res.status_code == codes.ok:
307
+ return True
308
+ else:
309
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
310
+ raise exc_cls(exc_msg)
311
+
312
+ def list_identities(self, account: str) -> "Iterator[dict[str, Any]]":
313
+ """
314
+ List all identities on an account.
315
+
316
+ Parameters
317
+ ----------
318
+ account :
319
+ The account name.
320
+ """
321
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'identities'])
322
+ url = build_url(choice(self.list_hosts), path=path)
323
+ res = self._send_request(url, method=HTTPMethod.GET)
324
+ if res.status_code == codes.ok:
325
+ identities = self._load_json_data(res)
326
+ return identities
327
+ else:
328
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
329
+ raise exc_cls(exc_msg)
330
+
331
+ def list_account_rules(self, account: str) -> "Iterator[dict[str, Any]]":
332
+ """
333
+ List the associated rules of an account.
334
+
335
+ Parameters
336
+ ----------
337
+ account :
338
+ The account name.
339
+
340
+ """
341
+
342
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'rules'])
343
+ url = build_url(choice(self.list_hosts), path=path)
344
+ res = self._send_request(url, method=HTTPMethod.GET)
345
+ if res.status_code == codes.ok:
346
+ return self._load_json_data(res)
347
+ else:
348
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
349
+ raise exc_cls(exc_msg)
350
+
351
+ def get_account_limits(self, account: str, rse_expression: str, locality: str) -> dict[str, Any]:
352
+ """
353
+ Return the correct account limits for the given locality.
354
+
355
+ Parameters
356
+ ----------
357
+ account :
358
+ The account name.
359
+ rse_expression :
360
+ Valid RSE expression.
361
+ locality :
362
+ The scope of the account limit. 'local' or 'global'.
363
+
364
+ """
365
+
366
+ if locality == 'local':
367
+ return self.get_local_account_limit(account, rse_expression)
368
+ elif locality == 'global':
369
+ return self.get_global_account_limit(account, rse_expression)
370
+ else:
371
+ from dafab_client._rucio.common.exception import UnsupportedOperation
372
+ raise UnsupportedOperation('The provided locality (%s) for the account limit was invalid' % locality)
373
+
374
+ def get_global_account_limit(self, account: str, rse_expression: str) -> dict[str, Any]:
375
+ """
376
+ List the account limit for the specific RSE expression.
377
+
378
+ Parameters
379
+ ----------
380
+ account :
381
+ The account name.
382
+ rse_expression :
383
+ The rse expression.
384
+
385
+ """
386
+
387
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'limits', 'global', quote_plus(rse_expression)])
388
+ url = build_url(choice(self.list_hosts), path=path)
389
+ res = self._send_request(url, method=HTTPMethod.GET)
390
+ if res.status_code == codes.ok:
391
+ return next(self._load_json_data(res))
392
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
393
+ raise exc_cls(exc_msg)
394
+
395
+ def get_global_account_limits(self, account: str) -> dict[str, Any]:
396
+ """
397
+ List all RSE expression limits of this account.
398
+
399
+ Parameters
400
+ ----------
401
+ account :
402
+ The account name.
403
+ """
404
+
405
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'limits', 'global'])
406
+ url = build_url(choice(self.list_hosts), path=path)
407
+ res = self._send_request(url, method=HTTPMethod.GET)
408
+ if res.status_code == codes.ok:
409
+ return next(self._load_json_data(res))
410
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
411
+ raise exc_cls(exc_msg)
412
+
413
+ def get_local_account_limits(self, account: str) -> dict[str, Any]:
414
+ """
415
+ List the account rse limits of this account.
416
+
417
+ Parameters
418
+ ----------
419
+ account :
420
+ The account name.
421
+ """
422
+
423
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'limits', 'local'])
424
+ url = build_url(choice(self.list_hosts), path=path)
425
+ res = self._send_request(url, method=HTTPMethod.GET)
426
+ if res.status_code == codes.ok:
427
+ return next(self._load_json_data(res))
428
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
429
+ raise exc_cls(exc_msg)
430
+
431
+ def get_local_account_limit(self, account: str, rse: str) -> dict[str, Any]:
432
+ """
433
+ List the account rse limits of this account for the specific rse.
434
+
435
+ Parameters
436
+ ----------
437
+ account :
438
+ The account name.
439
+ rse :
440
+ The rse name.
441
+ """
442
+
443
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'limits', 'local', rse])
444
+ url = build_url(choice(self.list_hosts), path=path)
445
+ res = self._send_request(url, method=HTTPMethod.GET)
446
+ if res.status_code == codes.ok:
447
+ return next(self._load_json_data(res))
448
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
449
+ raise exc_cls(exc_msg)
450
+
451
+ def get_local_account_usage(self, account: str, rse: Optional[str] = None) -> "Iterator[dict[str, Any]]":
452
+ """
453
+ List the account usage for one or all rses of this account.
454
+
455
+ Parameters
456
+ ----------
457
+ account :
458
+ The account name.
459
+ rse :
460
+ The rse name.
461
+ """
462
+ if rse:
463
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'usage', 'local', rse])
464
+ else:
465
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'usage', 'local'])
466
+ url = build_url(choice(self.list_hosts), path=path)
467
+ res = self._send_request(url, method=HTTPMethod.GET)
468
+ if res.status_code == codes.ok:
469
+ return self._load_json_data(res)
470
+ else:
471
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
472
+ raise exc_cls(exc_msg)
473
+
474
+ def get_global_account_usage(self, account: str, rse_expression: Optional[str] = None) -> "Iterator[dict[str, Any]]":
475
+ """
476
+ List the account usage for one or all RSE expressions of this account.
477
+
478
+ Parameters
479
+ ----------
480
+ account :
481
+ The account name.
482
+ rse_expression :
483
+ The rse expression.
484
+ """
485
+ if rse_expression:
486
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'usage', 'global', quote_plus(rse_expression)])
487
+ else:
488
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'usage', 'global'])
489
+ url = build_url(choice(self.list_hosts), path=path)
490
+ res = self._send_request(url, method=HTTPMethod.GET)
491
+ if res.status_code == codes.ok:
492
+ return self._load_json_data(res)
493
+ else:
494
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
495
+ raise exc_cls(exc_msg)
496
+
497
+ def get_account_usage_history(self, account: str, rse: str) -> dict[str, Any]:
498
+ """
499
+ List the account usage history of this account on rse.
500
+
501
+ Parameters
502
+ ----------
503
+ account :
504
+ The account name.
505
+ rse :
506
+ The rse name.
507
+ """
508
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'usage/history', rse])
509
+ url = build_url(choice(self.list_hosts), path=path)
510
+ res = self._send_request(url, method=HTTPMethod.GET)
511
+ if res.status_code == codes.ok:
512
+ return next(self._load_json_data(res))
513
+ else:
514
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
515
+ raise exc_cls(exc_msg)
516
+
517
+ def list_account_attributes(self, account: str) -> "Iterator[dict[dict[str, Any], Any]]":
518
+ """
519
+ List the attributes for an account.
520
+
521
+ Parameters
522
+ ----------
523
+ account :
524
+ The account name.
525
+ """
526
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'attr/'])
527
+ url = build_url(choice(self.list_hosts), path=path)
528
+ res = self._send_request(url, method=HTTPMethod.GET)
529
+ if res.status_code == codes.ok:
530
+ return self._load_json_data(res)
531
+ else:
532
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
533
+ raise exc_cls(exc_msg)
534
+
535
+ def add_account_attribute(self, account: str, key: str, value: Any) -> bool:
536
+ """
537
+ Add an attribute to an account.
538
+
539
+ Parameters
540
+ ----------
541
+ account :
542
+ The account name.
543
+ key :
544
+ The attribute key.
545
+ value :
546
+ The attribute value.
547
+ """
548
+
549
+ data = dumps({'key': key, 'value': value})
550
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'attr', key])
551
+ url = build_url(choice(self.list_hosts), path=path)
552
+ res = self._send_request(url, method=HTTPMethod.POST, data=data)
553
+ if res.status_code == codes.created:
554
+ return True
555
+ else:
556
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
557
+ raise exc_cls(exc_msg)
558
+
559
+ def delete_account_attribute(self, account: str, key: str) -> bool:
560
+ """
561
+ Delete an attribute for an account.
562
+
563
+ Parameters
564
+ ----------
565
+ account :
566
+ The account name.
567
+ key :
568
+ The attribute key.
569
+ """
570
+
571
+ path = '/'.join([self.ACCOUNTS_BASEURL, account, 'attr', key])
572
+ url = build_url(choice(self.list_hosts), path=path)
573
+ res = self._send_request(url, method=HTTPMethod.DELETE, data=None)
574
+ if res.status_code == codes.ok:
575
+ return True
576
+ else:
577
+ exc_cls, exc_msg = self._get_exception(headers=res.headers, status_code=res.status_code, data=res.content)
578
+ raise exc_cls(exc_msg)