rucio-clients 35.7.0__py3-none-any.whl → 37.0.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 (85) hide show
  1. rucio/alembicrevision.py +1 -1
  2. rucio/cli/__init__.py +14 -0
  3. rucio/cli/account.py +216 -0
  4. rucio/cli/bin_legacy/__init__.py +13 -0
  5. rucio_clients-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
  6. rucio_clients-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
  7. rucio/cli/command.py +272 -0
  8. rucio/cli/config.py +72 -0
  9. rucio/cli/did.py +191 -0
  10. rucio/cli/download.py +128 -0
  11. rucio/cli/lifetime_exception.py +33 -0
  12. rucio/cli/replica.py +162 -0
  13. rucio/cli/rse.py +293 -0
  14. rucio/cli/rule.py +158 -0
  15. rucio/cli/scope.py +40 -0
  16. rucio/cli/subscription.py +73 -0
  17. rucio/cli/upload.py +60 -0
  18. rucio/cli/utils.py +226 -0
  19. rucio/client/accountclient.py +0 -1
  20. rucio/client/baseclient.py +33 -24
  21. rucio/client/client.py +45 -1
  22. rucio/client/didclient.py +5 -3
  23. rucio/client/downloadclient.py +6 -8
  24. rucio/client/replicaclient.py +0 -2
  25. rucio/client/richclient.py +317 -0
  26. rucio/client/rseclient.py +4 -4
  27. rucio/client/uploadclient.py +26 -12
  28. rucio/common/bittorrent.py +234 -0
  29. rucio/common/cache.py +66 -29
  30. rucio/common/checksum.py +168 -0
  31. rucio/common/client.py +122 -0
  32. rucio/common/config.py +22 -35
  33. rucio/common/constants.py +61 -3
  34. rucio/common/didtype.py +72 -24
  35. rucio/common/exception.py +65 -8
  36. rucio/common/extra.py +5 -10
  37. rucio/common/logging.py +13 -13
  38. rucio/common/pcache.py +8 -7
  39. rucio/common/plugins.py +59 -27
  40. rucio/common/policy.py +12 -3
  41. rucio/common/schema/__init__.py +84 -34
  42. rucio/common/schema/generic.py +0 -17
  43. rucio/common/schema/generic_multi_vo.py +0 -17
  44. rucio/common/test_rucio_server.py +12 -6
  45. rucio/common/types.py +132 -52
  46. rucio/common/utils.py +93 -643
  47. rucio/rse/__init__.py +3 -3
  48. rucio/rse/protocols/bittorrent.py +11 -1
  49. rucio/rse/protocols/cache.py +0 -11
  50. rucio/rse/protocols/dummy.py +0 -11
  51. rucio/rse/protocols/gfal.py +14 -9
  52. rucio/rse/protocols/globus.py +1 -1
  53. rucio/rse/protocols/http_cache.py +1 -1
  54. rucio/rse/protocols/posix.py +2 -2
  55. rucio/rse/protocols/protocol.py +84 -317
  56. rucio/rse/protocols/rclone.py +2 -1
  57. rucio/rse/protocols/rfio.py +10 -1
  58. rucio/rse/protocols/ssh.py +2 -1
  59. rucio/rse/protocols/storm.py +2 -13
  60. rucio/rse/protocols/webdav.py +74 -30
  61. rucio/rse/protocols/xrootd.py +2 -1
  62. rucio/rse/rsemanager.py +170 -53
  63. rucio/rse/translation.py +260 -0
  64. rucio/vcsversion.py +4 -4
  65. rucio/version.py +7 -0
  66. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/etc/rucio.cfg.atlas.client.template +3 -2
  67. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/etc/rucio.cfg.template +3 -19
  68. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/requirements.client.txt +11 -7
  69. rucio_clients-37.0.0.data/scripts/rucio +133 -0
  70. rucio_clients-37.0.0.data/scripts/rucio-admin +97 -0
  71. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/METADATA +18 -14
  72. rucio_clients-37.0.0.dist-info/RECORD +104 -0
  73. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/licenses/AUTHORS.rst +3 -0
  74. rucio/common/schema/atlas.py +0 -413
  75. rucio/common/schema/belleii.py +0 -408
  76. rucio/common/schema/domatpc.py +0 -401
  77. rucio/common/schema/escape.py +0 -426
  78. rucio/common/schema/icecube.py +0 -406
  79. rucio/rse/protocols/gsiftp.py +0 -92
  80. rucio_clients-35.7.0.dist-info/RECORD +0 -88
  81. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/etc/rse-accounts.cfg.template +0 -0
  82. {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/rucio_client/merge_rucio_configs.py +0 -0
  83. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/WHEEL +0 -0
  84. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/licenses/LICENSE +0 -0
  85. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/top_level.txt +0 -0
@@ -16,326 +16,50 @@
16
16
  This module defines the base class for implementing a transfer protocol,
17
17
  along with some of the default methods for LFN2PFN translations.
18
18
  """
19
- import hashlib
20
19
  import logging
21
- from collections.abc import Callable, Mapping
22
- from configparser import NoOptionError, NoSectionError
23
- from typing import Any, Optional, TypeVar
20
+ from abc import ABC, abstractmethod
21
+ from typing import TYPE_CHECKING, Any, Optional, Union
24
22
  from urllib.parse import urlparse
25
23
 
26
- from rucio.common import config, exception
27
- from rucio.common.constants import RseAttr
28
- from rucio.common.plugins import PolicyPackageAlgorithms
24
+ from rucio.common import exception
29
25
  from rucio.rse import rsemanager
26
+ from rucio.rse.translation import RSEDeterministicTranslation
30
27
 
31
28
  if getattr(rsemanager, 'CLIENT_MODE', None):
32
29
  from rucio.client.rseclient import RSEClient
33
30
 
34
31
  if getattr(rsemanager, 'SERVER_MODE', None):
35
- from rucio.common.types import InternalScope
36
32
  from rucio.core import replica
37
33
  from rucio.core.rse import get_rse_vo
38
34
 
35
+ if getattr(rsemanager, 'SERVER_MODE', None) or TYPE_CHECKING:
36
+ from rucio.common.types import InternalScope, LFNDict, LoggerFunction, RSESettingsDict
39
37
 
40
- class RSEDeterministicScopeTranslation(PolicyPackageAlgorithms):
41
- """
42
- Translates a pfn dictionary into a scope and name
43
- """
38
+ if TYPE_CHECKING:
39
+ from collections.abc import Iterable
44
40
 
45
- _algorithm_type = "pfn2lfn"
41
+ from rucio.common.types import DIDDict
46
42
 
47
- def __init__(self, vo: str = 'def'):
48
- super().__init__()
49
43
 
50
- self.register(RSEDeterministicScopeTranslation._default, "def")
51
- self.register(RSEDeterministicScopeTranslation._atlas, "atlas")
44
+ class RSEProtocol(ABC):
45
+ """ This class is virtual and acts as a base to inherit new protocols from. It further provides some common functionality which applies for the majority of the protocols."""
52
46
 
53
- logger = logging.getLogger(__name__)
54
-
55
- try:
56
- # Use the function defined in the policy package if it's configured so
57
- algorithm_name = config.config_get('policy', self._algorithm_type)
58
- except (NoOptionError, NoSectionError, RuntimeError):
59
- # Don't use a function from the policy package. Use one defined in this class according to vo
60
- logger.debug("PFN2LFN function will not be fetched from the policy package")
61
- if super()._supports(self._algorithm_type, vo):
62
- algorithm_name = vo
63
- else:
64
- algorithm_name = "def"
65
-
66
- self.parser = self.get_parser(algorithm_name)
67
-
68
- @classmethod
69
- def get_parser(cls, algorithm_name: str) -> Callable[..., Any]:
70
- return super()._get_one_algorithm(cls._algorithm_type, algorithm_name)
71
-
72
- @classmethod
73
- def register(
74
- cls,
75
- pfn2lfn_callable: Callable,
76
- name: Optional[str] = None
77
- ) -> None:
78
- """
79
- Provided a callable function, register it as one of the valid PFN2LFN algorithms.
80
-
81
-
82
- :param pfn2lfn_callable: Callable function to use.
83
- :param name: Algorithm name used for registration.
84
- """
85
- if name is None:
86
- name = pfn2lfn_callable.__name__
87
- algorithm_dict = {name: pfn2lfn_callable}
88
- super()._register(cls._algorithm_type, algorithm_dict)
89
-
90
- @staticmethod
91
- def _default(parsed_pfn: Mapping[str, str]) -> tuple[str, str]:
92
- """ Translate pfn to name/scope pair
93
-
94
- :param parsed_pfn: dictionary representing pfn containing:
95
- - path: str,
96
- - name: str
97
- :return: tuple containing name, scope
98
- """
99
- path = parsed_pfn['path']
100
- scope = path.lstrip('/').split('/')[0]
101
- name = parsed_pfn['name']
102
- return name, scope
103
-
104
- @staticmethod
105
- def _atlas(parsed_pfn: Mapping[str, str]) -> tuple[str, str]:
106
- """ Translate pfn to name/scope pair
107
-
108
- :param parsed_pfn: dictionary representing pfn containing:
109
- - path: str,
110
- - name: str
111
- :return: tuple containing name, scope
112
- """
113
- path = parsed_pfn['path']
114
- if path.startswith('/user') or path.startswith('/group'):
115
- scope = '%s.%s' % (path.split('/')[1], path.split('/')[2])
116
- name = parsed_pfn['name']
117
- else:
118
- name, scope = RSEDeterministicScopeTranslation._default(parsed_pfn)
119
-
120
- return name, scope
121
-
122
-
123
- RSEDeterministicScopeTranslation()
124
-
125
-
126
- RSEDeterministicTranslationT = TypeVar('RSEDeterministicTranslationT', bound='RSEDeterministicTranslation')
127
-
128
-
129
- class RSEDeterministicTranslation(PolicyPackageAlgorithms):
130
- """
131
- Execute the logic for translating a LFN to a path.
132
- """
133
-
134
- _DEFAULT_LFN2PFN = "hash"
135
- _algorithm_type = "lfn2pfn"
136
-
137
- def __init__(self, rse=None, rse_attributes=None, protocol_attributes=None):
138
- """
139
- Initialize a translator object from the RSE, its attributes, and the protocol-specific
140
- attributes.
141
-
142
- :param rse: Name of RSE for this translation.
143
- :param rse_attributes: A dictionary of RSE-specific attributes for use in the translation.
144
- :param protocol_attributes: A dictionary of RSE/protocol-specific attributes.
145
- """
146
- super().__init__()
147
- self.rse = rse
148
- self.rse_attributes = rse_attributes if rse_attributes else {}
149
- self.protocol_attributes = protocol_attributes if protocol_attributes else {}
150
-
151
- @classmethod
152
- def supports(cls, name):
153
- """
154
- Check to see if a specific algorithm is supported.
155
-
156
- :param name: Name of the deterministic algorithm.
157
- :returns: True if `name` is an algorithm supported by the translator class, False otherwise
158
- """
159
- return super()._supports(cls._algorithm_type, name)
160
-
161
- @classmethod
162
- def register(cls, lfn2pfn_callable, name=None):
163
- """
164
- Provided a callable function, register it as one of the valid LFN2PFN algorithms.
165
-
166
- The callable will receive five arguments:
167
- - scope: Scope of the LFN.
168
- - name: LFN's path name
169
- - rse: RSE name the translation is being done for.
170
- - rse_attributes: Attributes of the RSE.
171
- - protocol_attributes: Attributes of the RSE's protocol
172
- The return value should be the last part of the PFN - it will be appended to the
173
- rest of the URL.
174
-
175
- :param lfn2pfn_callable: Callable function to use for generating paths.
176
- :param name: Algorithm name used for registration. If None, then `lfn2pfn_callable.__name__` is used.
177
- """
178
- if name is None:
179
- name = lfn2pfn_callable.__name__
180
- algorithm_dict = {name: lfn2pfn_callable}
181
- super()._register(cls._algorithm_type, algorithm_dict)
182
-
183
- @staticmethod
184
- def __hash(scope, name, rse, rse_attrs, protocol_attrs):
185
- """
186
- Given a LFN, turn it into a sub-directory structure using a hash function.
187
-
188
- This takes the MD5 of the LFN and uses the first four characters as a subdirectory
189
- name.
190
-
191
- :param scope: Scope of the LFN.
192
- :param name: File name of the LFN.
193
- :param rse: RSE for PFN (ignored)
194
- :param rse_attrs: RSE attributes for PFN (ignored)
195
- :param protocol_attrs: RSE protocol attributes for PFN (ignored)
196
- :returns: Path for use in the PFN generation.
197
- """
198
- del rse
199
- del rse_attrs
200
- del protocol_attrs
201
- hstr = hashlib.md5(('%s:%s' % (scope, name)).encode('utf-8')).hexdigest()
202
- if scope.startswith('user') or scope.startswith('group'):
203
- scope = scope.replace('.', '/')
204
- return '%s/%s/%s/%s' % (scope, hstr[0:2], hstr[2:4], name)
205
-
206
- @staticmethod
207
- def __identity(scope, name, rse, rse_attrs, protocol_attrs):
208
- """
209
- Given a LFN, convert it directly to a path using the mapping:
210
-
211
- scope:path -> scope/path
212
-
213
- :param scope: Scope of the LFN.
214
- :param name: File name of the LFN.
215
- :param rse: RSE for PFN (ignored)
216
- :param rse_attrs: RSE attributes for PFN (ignored)
217
- :param protocol_attrs: RSE protocol attributes for PFN (ignored)
218
- :returns: Path for use in the PFN generation.
219
- """
220
- del rse
221
- del rse_attrs
222
- del protocol_attrs
223
- if scope.startswith('user') or scope.startswith('group'):
224
- scope = scope.replace('.', '/')
225
- return '%s/%s' % (scope, name)
226
-
227
- @staticmethod
228
- def __belleii(scope, name, rse, rse_attrs, protocol_attrs):
229
- """
230
- Given a LFN, convert it directly to a path using the mapping:
231
-
232
- path -> path
233
- This is valid only for the belleii convention where the scope can be determined
234
- from the LFN using a determinitic function.
235
-
236
- :param scope: Scope of the LFN.
237
- :param name: File name of the LFN.
238
- :param rse: RSE for PFN (ignored)
239
- :param rse_attrs: RSE attributes for PFN (ignored)
240
- :param protocol_attrs: RSE protocol attributes for PFN (ignored)
241
- :returns: Path for use in the PFN generation.
242
- """
243
- del scope
244
- del rse
245
- del rse_attrs
246
- del protocol_attrs
247
- return name
248
-
249
- @staticmethod
250
- def __ligo(scope, name, rse, rse_attrs, protocol_attrs):
251
- """
252
- Given a LFN, convert it directly to a path using the Caltech schema
253
-
254
- e.g.,: ER8:H-H1_HOFT_C02-1126256640-4096 ->
255
- ER8/hoft_C02/H1/H-H1_HOFT_C02-11262/H-H1_HOFT_C02-1126256640-4096
256
-
257
- :param scope: Scope of the LFN (observing run: ER8, O2, postO1, ...)
258
- :param name: File name of the LFN (E.g., H-H1_HOFT_C02-1126256640-4096.gwf)
259
- :param rse: RSE for PFN (ignored)
260
- :param rse_attrs: RSE attributes for PFN (ignored)
261
- :param protocol_attrs: RSE protocol attributes for PFN (ignored)
262
- :returns: Path for use in the PFN generation.
263
- """
264
- del rse
265
- del rse_attrs
266
- del protocol_attrs
267
- from ligo_rucio import lfn2pfn as ligo_lfn2pfn # pylint: disable=import-error
268
- return ligo_lfn2pfn.ligo_lab(scope, name, None, None, None)
269
-
270
- @staticmethod
271
- def __xenon(scope, name, rse, rse_attrs, protocol_attrs):
272
- """
273
- Given a LFN, turn it into a two level sub-directory structure based on the scope
274
- plus a third level based on the name
275
- :param scope: Scope of the LFN.
276
- :param name: File name of the LFN.
277
- :param rse: RSE for PFN (ignored)
278
- :param rse_attrs: RSE attributes for PFN (ignored)
279
- :param protocol_attrs: RSE protocol attributes for PFN (ignored)
280
- :returns: Path for use in the PFN generation.
281
- """
282
- del rse
283
- del rse_attrs
284
- del protocol_attrs
285
-
286
- return '%s/%s/%s/%s' % (scope[0:7], scope[4:len(scope)], name.split('-')[0] + "-" + name.split('-')[1], name)
287
-
288
- @classmethod
289
- def _module_init_(cls):
290
- """
291
- Initialize the class object on first module load.
292
- """
293
- cls.register(cls.__hash, "hash")
294
- cls.register(cls.__identity, "identity")
295
- cls.register(cls.__ligo, "ligo")
296
- cls.register(cls.__belleii, "belleii")
297
- cls.register(cls.__xenon, "xenon")
298
- policy_module = None
299
- try:
300
- policy_module = config.config_get('policy', 'lfn2pfn_module')
301
- except (NoOptionError, NoSectionError):
302
- pass
303
- if policy_module:
304
- # TODO: The import of importlib is done like this due to a dependency issue with python 2.6 and incompatibility of the module with py3.x
305
- # More information https://github.com/rucio/rucio/issues/875
306
- import importlib
307
- importlib.import_module(policy_module)
308
-
309
- cls._DEFAULT_LFN2PFN = config.get_lfn2pfn_algorithm_default()
310
-
311
- def path(self, scope, name):
312
- """ Transforms the logical file name into a PFN's path.
313
-
314
- :param lfn: filename
315
- :param scope: scope
316
-
317
- :returns: RSE specific URI of the physical file
318
- """
319
- algorithm = self.rse_attributes.get(RseAttr.LFN2PFN_ALGORITHM, 'default')
320
- if algorithm == 'default':
321
- algorithm = RSEDeterministicTranslation._DEFAULT_LFN2PFN
322
- algorithm_callable = super()._get_one_algorithm(RSEDeterministicTranslation._algorithm_type, algorithm)
323
- return algorithm_callable(scope, name, self.rse, self.rse_attributes, self.protocol_attributes)
324
-
325
-
326
- RSEDeterministicTranslation._module_init_() # pylint: disable=protected-access
327
-
328
-
329
- class RSEProtocol:
330
- """ This class is virtual and acts as a base to inherit new protocols from. It further provides some common functionality which applies for the amjority of the protocols."""
331
-
332
- def __init__(self, protocol_attr, rse_settings, logger=logging.log):
47
+ def __init__(
48
+ self,
49
+ protocol_attr: dict[str, Any],
50
+ rse_settings: "RSESettingsDict",
51
+ logger: "LoggerFunction" = logging.log
52
+ ):
333
53
  """ Initializes the object with information about the referred RSE.
334
54
 
335
55
  :param protocol_attr: Properties of the requested protocol.
336
56
  :param rse_settting: The RSE settings.
337
57
  :param logger: Optional decorated logger that can be passed from the calling daemons or servers.
338
58
  """
59
+
60
+ if 'auth_token' not in protocol_attr:
61
+ raise exception.NoAuthInformation('No authentication token passed for the RSE protocol')
62
+
339
63
  self.auth_token = protocol_attr['auth_token']
340
64
  protocol_attr.pop('auth_token')
341
65
  self.attributes = protocol_attr
@@ -356,7 +80,10 @@ class RSEProtocol:
356
80
  if getattr(rsemanager, 'SERVER_MODE', None):
357
81
  setattr(self, '_get_path', self._get_path_nondeterministic_server)
358
82
 
359
- def lfns2pfns(self, lfns):
83
+ def lfns2pfns(
84
+ self,
85
+ lfns: Union[list["LFNDict"], "LFNDict"]
86
+ ) -> dict[str, str]:
360
87
  """
361
88
  Returns a fully qualified PFN for the file referred by path.
362
89
 
@@ -398,7 +125,10 @@ class RSEProtocol:
398
125
  self.logger(logging.WARNING, str(e))
399
126
  return pfns
400
127
 
401
- def __lfns2pfns_client(self, lfns):
128
+ def __lfns2pfns_client(
129
+ self,
130
+ lfns: Union[list["DIDDict"], "DIDDict"]
131
+ ) -> dict[str, str]:
402
132
  """ Provides the path of a replica for non-deterministic sites. Will be assigned to get path by the __init__ method if necessary.
403
133
 
404
134
  :param scope: list of DIDs
@@ -411,7 +141,10 @@ class RSEProtocol:
411
141
  lfn_query = ["%s:%s" % (lfn['scope'], lfn['name']) for lfn in lfns]
412
142
  return client.lfns2pfns(self.rse['rse'], lfn_query, scheme=self.attributes['scheme'])
413
143
 
414
- def _get_path(self, scope, name):
144
+ def _get_path(
145
+ self,
146
+ scope: str,
147
+ name: str):
415
148
  """ Transforms the logical file name into a PFN.
416
149
  Suitable for sites implementing the RUCIO naming convention.
417
150
  This implementation is only invoked if the RSE is deterministic.
@@ -421,26 +154,33 @@ class RSEProtocol:
421
154
 
422
155
  :returns: RSE specific URI of the physical file
423
156
  """
424
- return self.translator.path(scope, name)
157
+ return self.translator.path(scope, name) # type: ignore (translator could be none)
425
158
 
426
- def _get_path_nondeterministic_server(self, scope, name): # pylint: disable=invalid-name
159
+ def _get_path_nondeterministic_server( # pylint: disable=invalid-name
160
+ self,
161
+ scope: str,
162
+ name: str
163
+ ) -> str:
427
164
  """ Provides the path of a replica for non-deterministic sites. Will be assigned to get path by the __init__ method if necessary. """
428
165
  vo = get_rse_vo(self.rse['id']) # pylint: disable=E0601
429
- scope = InternalScope(scope, vo=vo) # pylint: disable=E0601
430
- rep = replica.get_replica(scope=scope, name=name, rse_id=self.rse['id']) # pylint: disable=E0601
166
+ internal_scope = InternalScope(scope, vo=vo) # pylint: disable=E0601
167
+ rep = replica.get_replica(scope=internal_scope, name=name, rse_id=self.rse['id']) # pylint: disable=E0601
431
168
  if 'path' in rep and rep['path'] is not None:
432
169
  path = rep['path']
433
170
  elif 'state' in rep and (rep['state'] is None or rep['state'] == 'UNAVAILABLE'):
434
- raise exception.ReplicaUnAvailable('Missing path information and state is UNAVAILABLE for replica %s:%s on non-deterministic storage named %s' % (scope, name, self.rse['rse']))
171
+ raise exception.ReplicaUnAvailable('Missing path information and state is UNAVAILABLE for replica %s:%s on non-deterministic storage named %s' % (internal_scope, name, self.rse['rse']))
435
172
  else:
436
- raise exception.ReplicaNotFound('Missing path information for replica %s:%s on non-deterministic storage named %s' % (scope, name, self.rse['rse']))
173
+ raise exception.ReplicaNotFound('Missing path information for replica %s:%s on non-deterministic storage named %s' % (internal_scope, name, self.rse['rse']))
437
174
  if path.startswith('/'):
438
175
  path = path[1:]
439
176
  if path.endswith('/'):
440
177
  path = path[:-1]
441
178
  return path
442
179
 
443
- def parse_pfns(self, pfns):
180
+ def parse_pfns(
181
+ self,
182
+ pfns: Union['Iterable[str]', str]
183
+ ) -> dict[str, dict[str, str]]:
444
184
  """
445
185
  Splits the given PFN into the parts known by the protocol. It is also checked if the provided protocol supports the given PFNs.
446
186
 
@@ -494,7 +234,10 @@ class RSEProtocol:
494
234
 
495
235
  return ret
496
236
 
497
- def exists(self, path):
237
+ def exists(
238
+ self,
239
+ path: Optional[str]
240
+ ) -> bool:
498
241
  """
499
242
  Checks if the requested file is known by the referred RSE.
500
243
 
@@ -506,7 +249,8 @@ class RSEProtocol:
506
249
  """
507
250
  raise NotImplementedError
508
251
 
509
- def connect(self):
252
+ @abstractmethod
253
+ def connect(self) -> None:
510
254
  """
511
255
  Establishes the actual connection to the referred RSE.
512
256
 
@@ -514,11 +258,18 @@ class RSEProtocol:
514
258
  """
515
259
  raise NotImplementedError
516
260
 
517
- def close(self):
261
+ @abstractmethod
262
+ def close(self) -> None:
518
263
  """ Closes the connection to RSE."""
519
264
  raise NotImplementedError
520
265
 
521
- def get(self, path, dest, transfer_timeout=None):
266
+ @abstractmethod
267
+ def get(
268
+ self,
269
+ path: str,
270
+ dest: str,
271
+ transfer_timeout: Optional[int] = None
272
+ ) -> None:
522
273
  """
523
274
  Provides access to files stored inside connected the RSE.
524
275
 
@@ -532,7 +283,14 @@ class RSEProtocol:
532
283
  """
533
284
  raise NotImplementedError
534
285
 
535
- def put(self, source, target, source_dir, transfer_timeout=None):
286
+ @abstractmethod
287
+ def put(
288
+ self,
289
+ source: str,
290
+ target: str,
291
+ source_dir: Optional[str],
292
+ transfer_timeout: Optional[int] = None
293
+ ) -> None:
536
294
  """
537
295
  Allows to store files inside the referred RSE.
538
296
 
@@ -547,7 +305,11 @@ class RSEProtocol:
547
305
  """
548
306
  raise NotImplementedError
549
307
 
550
- def delete(self, path):
308
+ @abstractmethod
309
+ def delete(
310
+ self,
311
+ path: str
312
+ ) -> None:
551
313
  """
552
314
  Deletes a file from the connected RSE.
553
315
 
@@ -558,7 +320,12 @@ class RSEProtocol:
558
320
  """
559
321
  raise NotImplementedError
560
322
 
561
- def rename(self, path, new_path):
323
+ @abstractmethod
324
+ def rename(
325
+ self,
326
+ path: str,
327
+ new_path: str
328
+ ) -> None:
562
329
  """ Allows to rename a file stored inside the connected RSE.
563
330
 
564
331
  :param path: path to the current file on the storage
@@ -570,17 +337,17 @@ class RSEProtocol:
570
337
  """
571
338
  raise NotImplementedError
572
339
 
573
- def get_space_usage(self):
340
+ def get_space_usage(self) -> tuple[int, int]:
574
341
  """
575
342
  Get RSE space usage information.
576
343
 
577
- :returns: a list with dict containing 'totalsize' and 'unusedsize'
344
+ :returns: a tuple 'totalsize' and 'unusedsize'
578
345
 
579
346
  :raises ServiceUnavailable: if some generic error occurred in the library.
580
347
  """
581
348
  raise NotImplementedError
582
349
 
583
- def stat(self, path):
350
+ def stat(self, path: str) -> dict[str, Any]:
584
351
  """
585
352
  Returns the stats of a file.
586
353
 
@@ -17,8 +17,9 @@ import logging
17
17
  import os
18
18
 
19
19
  from rucio.common import exception
20
+ from rucio.common.checksum import PREFERRED_CHECKSUM
20
21
  from rucio.common.config import get_config_dirs
21
- from rucio.common.utils import PREFERRED_CHECKSUM, execute
22
+ from rucio.common.utils import execute
22
23
  from rucio.rse.protocols import protocol
23
24
 
24
25
 
@@ -37,7 +37,7 @@ class Default(protocol.RSEProtocol):
37
37
  :raises RSEAccessDenied: if no connection could be established.
38
38
  """
39
39
  extended_attributes = self.rse['protocol']['extended_attributes']
40
- if 'STAGE_SVCCLASS' in extended_attributes:
40
+ if isinstance(extended_attributes, dict) and 'STAGE_SVCCLASS' in extended_attributes:
41
41
  os.environ['STAGE_SVCCLASS'] = extended_attributes['STAGE_SVCCLASS']
42
42
 
43
43
  def path2pfn(self, path):
@@ -134,3 +134,12 @@ class Default(protocol.RSEProtocol):
134
134
  ret['path'] = ret['path'].partition(ret['name'])[0]
135
135
 
136
136
  return ret
137
+
138
+ def delete(self, path):
139
+ raise NotImplementedError
140
+
141
+ def get(self, path, dest, transfer_timeout=None):
142
+ raise NotImplementedError
143
+
144
+ def rename(self, path, new_path):
145
+ raise NotImplementedError
@@ -17,7 +17,8 @@ import os
17
17
  import re
18
18
 
19
19
  from rucio.common import exception
20
- from rucio.common.utils import PREFERRED_CHECKSUM, execute
20
+ from rucio.common.checksum import PREFERRED_CHECKSUM
21
+ from rucio.common.utils import execute
21
22
  from rucio.rse.protocols import protocol
22
23
 
23
24
 
@@ -68,17 +68,6 @@ class Default(protocol.RSEProtocol):
68
68
  """
69
69
  return ''.join([self.rse['scheme'], '://%s' % self.rse['hostname'], path])
70
70
 
71
- def exists(self, pfn):
72
- """ Checks if the requested file is known by the referred RSE.
73
-
74
- :param pfn: Physical file name
75
-
76
- :returns: True if the file exists, False if it doesn't
77
-
78
- :raise ServiceUnavailable
79
- """
80
- raise NotImplementedError
81
-
82
71
  def connect(self):
83
72
  """ Establishes the actual connection to the referred RSE.
84
73
 
@@ -139,7 +128,7 @@ class Default(protocol.RSEProtocol):
139
128
  name = pfn.split('/')[-1]
140
129
  if name not in target:
141
130
  target = None
142
- except:
131
+ except Exception:
143
132
  target = None
144
133
  pass
145
134
 
@@ -149,7 +138,7 @@ class Default(protocol.RSEProtocol):
149
138
  # requests preferable
150
139
  try:
151
140
  rcode, etag_meta = requests_etag(pfn, 300)
152
- except:
141
+ except Exception:
153
142
  pass
154
143
  # fallback to davix
155
144
  if rcode != 207: