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.
- rucio/alembicrevision.py +1 -1
- rucio/cli/__init__.py +14 -0
- rucio/cli/account.py +216 -0
- rucio/cli/bin_legacy/__init__.py +13 -0
- rucio_clients-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
- rucio_clients-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
- rucio/cli/command.py +272 -0
- rucio/cli/config.py +72 -0
- rucio/cli/did.py +191 -0
- rucio/cli/download.py +128 -0
- rucio/cli/lifetime_exception.py +33 -0
- rucio/cli/replica.py +162 -0
- rucio/cli/rse.py +293 -0
- rucio/cli/rule.py +158 -0
- rucio/cli/scope.py +40 -0
- rucio/cli/subscription.py +73 -0
- rucio/cli/upload.py +60 -0
- rucio/cli/utils.py +226 -0
- rucio/client/accountclient.py +0 -1
- rucio/client/baseclient.py +33 -24
- rucio/client/client.py +45 -1
- rucio/client/didclient.py +5 -3
- rucio/client/downloadclient.py +6 -8
- rucio/client/replicaclient.py +0 -2
- rucio/client/richclient.py +317 -0
- rucio/client/rseclient.py +4 -4
- rucio/client/uploadclient.py +26 -12
- rucio/common/bittorrent.py +234 -0
- rucio/common/cache.py +66 -29
- rucio/common/checksum.py +168 -0
- rucio/common/client.py +122 -0
- rucio/common/config.py +22 -35
- rucio/common/constants.py +61 -3
- rucio/common/didtype.py +72 -24
- rucio/common/exception.py +65 -8
- rucio/common/extra.py +5 -10
- rucio/common/logging.py +13 -13
- rucio/common/pcache.py +8 -7
- rucio/common/plugins.py +59 -27
- rucio/common/policy.py +12 -3
- rucio/common/schema/__init__.py +84 -34
- rucio/common/schema/generic.py +0 -17
- rucio/common/schema/generic_multi_vo.py +0 -17
- rucio/common/test_rucio_server.py +12 -6
- rucio/common/types.py +132 -52
- rucio/common/utils.py +93 -643
- rucio/rse/__init__.py +3 -3
- rucio/rse/protocols/bittorrent.py +11 -1
- rucio/rse/protocols/cache.py +0 -11
- rucio/rse/protocols/dummy.py +0 -11
- rucio/rse/protocols/gfal.py +14 -9
- rucio/rse/protocols/globus.py +1 -1
- rucio/rse/protocols/http_cache.py +1 -1
- rucio/rse/protocols/posix.py +2 -2
- rucio/rse/protocols/protocol.py +84 -317
- rucio/rse/protocols/rclone.py +2 -1
- rucio/rse/protocols/rfio.py +10 -1
- rucio/rse/protocols/ssh.py +2 -1
- rucio/rse/protocols/storm.py +2 -13
- rucio/rse/protocols/webdav.py +74 -30
- rucio/rse/protocols/xrootd.py +2 -1
- rucio/rse/rsemanager.py +170 -53
- rucio/rse/translation.py +260 -0
- rucio/vcsversion.py +4 -4
- rucio/version.py +7 -0
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/etc/rucio.cfg.atlas.client.template +3 -2
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/etc/rucio.cfg.template +3 -19
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/requirements.client.txt +11 -7
- rucio_clients-37.0.0.data/scripts/rucio +133 -0
- rucio_clients-37.0.0.data/scripts/rucio-admin +97 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/METADATA +18 -14
- rucio_clients-37.0.0.dist-info/RECORD +104 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/licenses/AUTHORS.rst +3 -0
- rucio/common/schema/atlas.py +0 -413
- rucio/common/schema/belleii.py +0 -408
- rucio/common/schema/domatpc.py +0 -401
- rucio/common/schema/escape.py +0 -426
- rucio/common/schema/icecube.py +0 -406
- rucio/rse/protocols/gsiftp.py +0 -92
- rucio_clients-35.7.0.dist-info/RECORD +0 -88
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/etc/rse-accounts.cfg.template +0 -0
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0.data}/data/rucio_client/merge_rucio_configs.py +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/WHEEL +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/licenses/LICENSE +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0.dist-info}/top_level.txt +0 -0
rucio/client/downloadclient.py
CHANGED
|
@@ -29,11 +29,13 @@ from typing import TYPE_CHECKING, Any, Optional
|
|
|
29
29
|
|
|
30
30
|
from rucio import version
|
|
31
31
|
from rucio.client.client import Client
|
|
32
|
+
from rucio.common.checksum import CHECKSUM_ALGO_DICT, GLOBALLY_SUPPORTED_CHECKSUMS, PREFERRED_CHECKSUM, adler32
|
|
33
|
+
from rucio.common.client import detect_client_location
|
|
32
34
|
from rucio.common.config import config_get
|
|
33
35
|
from rucio.common.didtype import DID
|
|
34
36
|
from rucio.common.exception import InputValidationError, NoFilesDownloaded, NotAllFilesDownloaded, RucioException
|
|
35
37
|
from rucio.common.pcache import Pcache
|
|
36
|
-
from rucio.common.utils import
|
|
38
|
+
from rucio.common.utils import execute, extract_scope, generate_uuid, parse_replicas_from_file, parse_replicas_from_string, send_trace, sizefmt
|
|
37
39
|
from rucio.rse import rsemanager as rsemgr
|
|
38
40
|
|
|
39
41
|
if TYPE_CHECKING:
|
|
@@ -323,8 +325,6 @@ class DownloadClient:
|
|
|
323
325
|
:param deactivate_file_download_exceptions: Boolean, if file download exceptions shouldn't be raised
|
|
324
326
|
:param sort: Select best replica by replica sorting algorithm. Available algorithms:
|
|
325
327
|
``geoip`` - based on src/dst IP topographical distance
|
|
326
|
-
``closeness`` - based on src/dst closeness
|
|
327
|
-
``dynamic`` - Rucio Dynamic Smart Sort (tm)
|
|
328
328
|
|
|
329
329
|
:returns: a list of dictionaries with an entry for each file, containing the input options, the did, and the clientState
|
|
330
330
|
|
|
@@ -834,8 +834,6 @@ class DownloadClient:
|
|
|
834
834
|
:param deactivate_file_download_exceptions: Boolean, if file download exceptions shouldn't be raised
|
|
835
835
|
:param sort: Select best replica by replica sorting algorithm. Available algorithms:
|
|
836
836
|
``geoip`` - based on src/dst IP topographical distance
|
|
837
|
-
``closeness`` - based on src/dst closeness
|
|
838
|
-
``dynamic`` - Rucio Dynamic Smart Sort (tm)
|
|
839
837
|
|
|
840
838
|
:returns: a list of dictionaries with an entry for each file, containing the input options, the did, and the clientState
|
|
841
839
|
|
|
@@ -1197,7 +1195,7 @@ class DownloadClient:
|
|
|
1197
1195
|
if self.is_tape_excluded:
|
|
1198
1196
|
try:
|
|
1199
1197
|
tape_rses = [endp['rse'] for endp in self.client.list_rses(rse_expression='istape=true')]
|
|
1200
|
-
except:
|
|
1198
|
+
except Exception:
|
|
1201
1199
|
logger(logging.DEBUG, 'No tapes found.')
|
|
1202
1200
|
|
|
1203
1201
|
# Matches each dereferenced DID back to a list of input items
|
|
@@ -1677,8 +1675,8 @@ class DownloadClient:
|
|
|
1677
1675
|
num_successful = 0
|
|
1678
1676
|
num_failed = 0
|
|
1679
1677
|
for item in output_items:
|
|
1680
|
-
|
|
1681
|
-
if
|
|
1678
|
+
client_state = item.get('clientState', FileDownloadState.FAILED)
|
|
1679
|
+
if client_state in success_states:
|
|
1682
1680
|
num_successful += 1
|
|
1683
1681
|
else:
|
|
1684
1682
|
num_failed += 1
|
rucio/client/replicaclient.py
CHANGED
|
@@ -143,8 +143,6 @@ class ReplicaClient(BaseClient):
|
|
|
143
143
|
:param rse_expression: The RSE expression to restrict replicas on a set of RSEs.
|
|
144
144
|
:param client_location: Client location dictionary for PFN modification {'ip', 'fqdn', 'site', 'latitude', 'longitude'}
|
|
145
145
|
:param sort: Sort the replicas: ``geoip`` - based on src/dst IP topographical distance
|
|
146
|
-
``closeness`` - based on src/dst closeness
|
|
147
|
-
``dynamic`` - Rucio Dynamic Smart Sort (tm)
|
|
148
146
|
:param domain: Define the domain. None is fallback to 'wan', otherwise 'wan, 'lan', or 'all'
|
|
149
147
|
:param signature_lifetime: If supported, in seconds, restrict the lifetime of the signed PFN.
|
|
150
148
|
:param nrandom: pick N random replicas. If the initial number of replicas is smaller than N, returns all replicas.
|
|
@@ -0,0 +1,317 @@
|
|
|
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
|
+
import logging
|
|
16
|
+
import pydoc
|
|
17
|
+
import re
|
|
18
|
+
import shutil
|
|
19
|
+
import sys
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
22
|
+
|
|
23
|
+
from rich import box
|
|
24
|
+
from rich.console import Console, JustifyMethod, RenderableType
|
|
25
|
+
from rich.logging import RichHandler
|
|
26
|
+
from rich.table import Table
|
|
27
|
+
from rich.text import Text
|
|
28
|
+
|
|
29
|
+
from rucio.common.config import config_get
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import Callable, Sequence
|
|
33
|
+
|
|
34
|
+
from rich.style import StyleType
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
MIN_CONSOLE_WIDTH = 80
|
|
38
|
+
MAX_TRACEBACK_WIDTH = 120 # Slightly higher than default width of rich.traceback (100).
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CLITheme:
|
|
42
|
+
"""
|
|
43
|
+
Class to define styles for Rich widgets and prints in the CLI.
|
|
44
|
+
"""
|
|
45
|
+
TABLE_FMT = box.SQUARE
|
|
46
|
+
|
|
47
|
+
TEXT_HIGHLIGHT = 'grey50' # Used to highlight prints between tables, e.g. get_metadata or stat.
|
|
48
|
+
SUBHEADER_HIGHLIGHT = 'cyan' # Used to highlight prints between tables for a section, e.g. protocols or usage per account.
|
|
49
|
+
SPINNER = 'dots2'
|
|
50
|
+
SPINNER_STYLE = 'green'
|
|
51
|
+
|
|
52
|
+
JSON_STR = 'green'
|
|
53
|
+
JSON_NUM = 'bold cyan'
|
|
54
|
+
|
|
55
|
+
SUCCESS_ICON = '[bold green]\u2714[/]'
|
|
56
|
+
FAILURE_ICON = '[bold red]\u2717[/]'
|
|
57
|
+
|
|
58
|
+
LOG_THEMES = {
|
|
59
|
+
'logging.level.info': 'default',
|
|
60
|
+
'logging.level.warning': 'bold yellow',
|
|
61
|
+
'logging.level.debug': 'dim',
|
|
62
|
+
'logging.level.critical': 'default on red',
|
|
63
|
+
'repr.bool_true': 'green',
|
|
64
|
+
'repr.bool_false': 'red',
|
|
65
|
+
'log.time': 'turquoise4'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
DID_TYPE = {
|
|
69
|
+
'CONTAINER': 'bold dodger_blue1',
|
|
70
|
+
'DATASET': 'bold orange3',
|
|
71
|
+
'FILE': 'bold default',
|
|
72
|
+
'COLLECTION': 'bold green',
|
|
73
|
+
'ALL': 'bold magenta',
|
|
74
|
+
'DERIVED': 'bold magenta'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
BOOLEAN = {
|
|
78
|
+
'True': 'green',
|
|
79
|
+
'False': 'red'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
RULE_STATE = {
|
|
83
|
+
'OK': 'bold green',
|
|
84
|
+
'REPLICATING': 'bold default',
|
|
85
|
+
'STUCK': 'bold orange3',
|
|
86
|
+
'WAITING APPROVAL': 'bold dodger_blue1',
|
|
87
|
+
'INJECT': 'bold medium_purple3',
|
|
88
|
+
'SUSPENDED': 'bold red'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
RSE_TYPE = {
|
|
92
|
+
'DISK': 'bold dodger_blue1',
|
|
93
|
+
'TAPE': 'bold orange3',
|
|
94
|
+
'UNKNOWN': 'bold'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
SUBSCRIPTION_STATE = {
|
|
98
|
+
'ACTIVE': 'bold green',
|
|
99
|
+
'INACTIVE': 'bold',
|
|
100
|
+
'NEW': 'bold dodger_blue1',
|
|
101
|
+
'UPDATED': 'bold green',
|
|
102
|
+
'BROKEN': 'bold red',
|
|
103
|
+
'UNKNOWN': 'bold orange3'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
AVAILABILITY = {
|
|
107
|
+
'AVAILABLE': 'bold green',
|
|
108
|
+
'DELETED': 'bold',
|
|
109
|
+
'LOST': 'bold red',
|
|
110
|
+
'UNKNOWN': 'bold orange3'
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
ACCOUNT_STATUS = {
|
|
114
|
+
'ACTIVE': 'bold green',
|
|
115
|
+
'SUSPENDED': 'bold red',
|
|
116
|
+
'DELETED': 'bold'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
REPLICA_STATE = {
|
|
120
|
+
'A': 'bold green',
|
|
121
|
+
'U': 'bold red',
|
|
122
|
+
'C': 'bold default',
|
|
123
|
+
'B': 'bold dodger_blue1',
|
|
124
|
+
'D': 'bold red',
|
|
125
|
+
'T': 'bold orange3'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
ACCOUNT_TYPE = {
|
|
129
|
+
'USER': 'default',
|
|
130
|
+
'GROUP': 'medium_purple3',
|
|
131
|
+
'SERVICE': 'yellow'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def setup_rich_logger(
|
|
136
|
+
module_name: Optional[str] = None,
|
|
137
|
+
logger_name: Optional[str] = None,
|
|
138
|
+
logger_level: Optional[int] = None,
|
|
139
|
+
verbose: bool = False,
|
|
140
|
+
console: Optional[Console] = None
|
|
141
|
+
) -> logging.Logger:
|
|
142
|
+
"""
|
|
143
|
+
Factory method to set logger with RichHandler.
|
|
144
|
+
|
|
145
|
+
The function is a copy of the method in rucio.common.utils setup_logger() with minor changes.
|
|
146
|
+
|
|
147
|
+
:param module_name: __name__ of the module that is calling this method
|
|
148
|
+
:param logger_name: name of the logger, typically name of the module.
|
|
149
|
+
:param logger_level: if not given, fetched from config.
|
|
150
|
+
:param verbose: verbose option set in bin/rucio
|
|
151
|
+
:param console: Rich console object
|
|
152
|
+
:returns: logger with RichHandler
|
|
153
|
+
"""
|
|
154
|
+
# Helper method for cfg check.
|
|
155
|
+
def _force_cfg_log_level(cfg_option: str) -> bool:
|
|
156
|
+
cfg_forced_modules = config_get('logging', cfg_option, raise_exception=False, default=None, clean_cached=True, check_config_table=False)
|
|
157
|
+
if cfg_forced_modules and module_name is not None:
|
|
158
|
+
if re.match(str(cfg_forced_modules), module_name):
|
|
159
|
+
return True
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
if not logger_name:
|
|
163
|
+
if not module_name:
|
|
164
|
+
logger_name = 'user'
|
|
165
|
+
else:
|
|
166
|
+
logger_name = module_name.split('.')[-1]
|
|
167
|
+
logger = logging.getLogger(logger_name)
|
|
168
|
+
|
|
169
|
+
# Extracting the log level.
|
|
170
|
+
if not logger_level:
|
|
171
|
+
logger_level = logging.INFO
|
|
172
|
+
if verbose:
|
|
173
|
+
logger_level = logging.DEBUG
|
|
174
|
+
|
|
175
|
+
# Overriding by the config.
|
|
176
|
+
cfg_levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR)
|
|
177
|
+
for level in cfg_levels:
|
|
178
|
+
cfg_opt = 'forceloglevel' + logging.getLevelName(level)
|
|
179
|
+
if _force_cfg_log_level(cfg_opt):
|
|
180
|
+
logger_level = level
|
|
181
|
+
|
|
182
|
+
logger.setLevel(logger_level)
|
|
183
|
+
|
|
184
|
+
def add_handler(logger: logging.Logger,
|
|
185
|
+
console: Optional[Console] = None,
|
|
186
|
+
verbose: bool = False) -> None:
|
|
187
|
+
|
|
188
|
+
def time_formatter(timestamp: datetime) -> Text:
|
|
189
|
+
return Text(f"[{timestamp.isoformat(sep=' ', timespec='milliseconds')}]")
|
|
190
|
+
|
|
191
|
+
console = console or Console()
|
|
192
|
+
handler = RichHandler(rich_tracebacks=True, markup=True, show_path=verbose, show_time=verbose, console=console, tracebacks_width=min(console.width, MAX_TRACEBACK_WIDTH),
|
|
193
|
+
tracebacks_word_wrap=True, log_time_format=time_formatter)
|
|
194
|
+
logger.addHandler(handler)
|
|
195
|
+
|
|
196
|
+
# Setting handler and formatter.
|
|
197
|
+
if not logger.handlers:
|
|
198
|
+
add_handler(logger, console, verbose)
|
|
199
|
+
|
|
200
|
+
return logger
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _format_value(value: Optional[Union[RenderableType, int, float, bool, datetime]] = None) -> RenderableType:
|
|
204
|
+
"""
|
|
205
|
+
Formats the value based on its type for Rich Table.
|
|
206
|
+
|
|
207
|
+
A helper function to format the value to Rich RenderableType.
|
|
208
|
+
|
|
209
|
+
:param value: value to format
|
|
210
|
+
:returns: formatted value
|
|
211
|
+
"""
|
|
212
|
+
if value is None or str(value) == 'None':
|
|
213
|
+
return ''
|
|
214
|
+
if isinstance(value, bool):
|
|
215
|
+
return CLITheme.BOOLEAN[str(value)]
|
|
216
|
+
if isinstance(value, (int, float, datetime)):
|
|
217
|
+
return str(value)
|
|
218
|
+
return value
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def generate_table(
|
|
222
|
+
rows: 'Sequence[Sequence[Union[RenderableType, int, float, bool, datetime]]]',
|
|
223
|
+
headers: Optional['Sequence[RenderableType]'] = None,
|
|
224
|
+
row_styles: Optional['Sequence[StyleType]'] = None,
|
|
225
|
+
col_alignments: Optional[list[JustifyMethod]] = None,
|
|
226
|
+
table_format: box.Box = CLITheme.TABLE_FMT,
|
|
227
|
+
) -> Table:
|
|
228
|
+
"""
|
|
229
|
+
Generates a Rich Table object from given input rows.
|
|
230
|
+
|
|
231
|
+
The elements in each row can be either plain strings or Rich renderable objects.
|
|
232
|
+
Passing strings will display them as simple text, while using Rich objects
|
|
233
|
+
allows you to introduce additional structure, styling, and widgets (e.g. Text, Trees) into
|
|
234
|
+
the table. Strings with style markup will be rendered as styled text.
|
|
235
|
+
|
|
236
|
+
:param table_format: style of the table
|
|
237
|
+
:param headers: list of headers
|
|
238
|
+
:param rows: list of rows
|
|
239
|
+
:param col_alignments: list of column alignments
|
|
240
|
+
:param row_styles: list of row styles
|
|
241
|
+
:returns: a Rich Table object
|
|
242
|
+
"""
|
|
243
|
+
table = Table(box=table_format, show_header=headers is not None and len(headers) > 0)
|
|
244
|
+
table.row_styles = row_styles or ['none', 'dim']
|
|
245
|
+
|
|
246
|
+
if len(rows) == 0:
|
|
247
|
+
if headers:
|
|
248
|
+
for header in headers:
|
|
249
|
+
table.add_column(header)
|
|
250
|
+
return table
|
|
251
|
+
|
|
252
|
+
# Auto-detect on first row, numerical values on the right.
|
|
253
|
+
col_alignments = col_alignments or ['right' if str(col).isnumeric() else 'left' for col in rows[0]]
|
|
254
|
+
headers = headers or [''] * len(rows[0])
|
|
255
|
+
while len(headers) > len(col_alignments):
|
|
256
|
+
col_alignments.append('left')
|
|
257
|
+
|
|
258
|
+
for header, alignment in zip(headers, col_alignments):
|
|
259
|
+
table.add_column(header, overflow='fold', justify=alignment)
|
|
260
|
+
|
|
261
|
+
for row in rows:
|
|
262
|
+
row = [_format_value(col) for col in row]
|
|
263
|
+
table.add_row(*row)
|
|
264
|
+
return table
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def print_output(
|
|
268
|
+
*output: Any,
|
|
269
|
+
console: Console,
|
|
270
|
+
no_pager: bool = False
|
|
271
|
+
) -> None:
|
|
272
|
+
"""
|
|
273
|
+
Prints the objects using the specified Rich console object. Optionally disables the pager if specified.
|
|
274
|
+
|
|
275
|
+
The function works similarly to Rich's `console.print()` method but provides additional control over the pager feature.
|
|
276
|
+
|
|
277
|
+
:param output: objects to print to the terminal
|
|
278
|
+
:param console: Rich console object
|
|
279
|
+
:param no_pager: flag to disable the pager
|
|
280
|
+
"""
|
|
281
|
+
if console.is_terminal:
|
|
282
|
+
if no_pager:
|
|
283
|
+
console.print(*output)
|
|
284
|
+
else:
|
|
285
|
+
console.width = sys.maxsize # Overwrite auto-detected console width.
|
|
286
|
+
console.begin_capture()
|
|
287
|
+
console.print(*output)
|
|
288
|
+
else:
|
|
289
|
+
console.width = sys.maxsize
|
|
290
|
+
console.print(*output)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def get_cli_config() -> str:
|
|
294
|
+
"""
|
|
295
|
+
Returns the CLI type from the config file.
|
|
296
|
+
|
|
297
|
+
:returns: CLI type (Rich or tabulate)
|
|
298
|
+
"""
|
|
299
|
+
cli_type = config_get('experimental', 'cli', raise_exception=False, default='tabulate').lower()
|
|
300
|
+
if cli_type not in ['rich', 'tabulate']:
|
|
301
|
+
cli_type = 'tabulate'
|
|
302
|
+
return cli_type
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def get_pager() -> 'Callable[[str], None]':
|
|
306
|
+
"""
|
|
307
|
+
Returns the pager function based on the terminal availability.
|
|
308
|
+
|
|
309
|
+
:returns: pager
|
|
310
|
+
"""
|
|
311
|
+
default_pager = 'less'
|
|
312
|
+
# Attempt to use the default pager if available.
|
|
313
|
+
if shutil.which(default_pager) is not None:
|
|
314
|
+
return lambda text: pydoc.pipepager(text, f'{default_pager} -FRSXKM')
|
|
315
|
+
|
|
316
|
+
# Fall back to pydoc.pager if the default pager is not available.
|
|
317
|
+
return pydoc.pager
|
rucio/client/rseclient.py
CHANGED
|
@@ -24,7 +24,7 @@ from rucio.common.utils import build_url
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
25
|
from collections.abc import Iterable, Iterator
|
|
26
26
|
|
|
27
|
-
from rucio.common.constants import
|
|
27
|
+
from rucio.common.constants import RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL, RSE_SUPPORTED_PROTOCOL_DOMAINS_LITERAL, SUPPORTED_PROTOCOLS_LITERAL
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class RSEClient(BaseClient):
|
|
@@ -238,7 +238,7 @@ class RSEClient(BaseClient):
|
|
|
238
238
|
self,
|
|
239
239
|
rse: str,
|
|
240
240
|
protocol_domain: "RSE_SUPPORTED_PROTOCOL_DOMAINS_LITERAL" = 'ALL',
|
|
241
|
-
operation: Optional["
|
|
241
|
+
operation: Optional["RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL"] = None,
|
|
242
242
|
default: bool = False,
|
|
243
243
|
scheme: Optional['SUPPORTED_PROTOCOLS_LITERAL'] = None
|
|
244
244
|
) -> Any:
|
|
@@ -287,7 +287,7 @@ class RSEClient(BaseClient):
|
|
|
287
287
|
rse: str,
|
|
288
288
|
lfns: 'Iterable[str]',
|
|
289
289
|
protocol_domain: 'RSE_SUPPORTED_PROTOCOL_DOMAINS_LITERAL' = 'ALL',
|
|
290
|
-
operation: Optional['
|
|
290
|
+
operation: Optional['RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL'] = None,
|
|
291
291
|
scheme: Optional['SUPPORTED_PROTOCOLS_LITERAL'] = None
|
|
292
292
|
) -> dict[str, str]:
|
|
293
293
|
"""
|
|
@@ -409,7 +409,7 @@ class RSEClient(BaseClient):
|
|
|
409
409
|
self,
|
|
410
410
|
rse: str,
|
|
411
411
|
domain: 'RSE_SUPPORTED_PROTOCOL_DOMAINS_LITERAL',
|
|
412
|
-
operation: '
|
|
412
|
+
operation: 'RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL',
|
|
413
413
|
scheme_a: 'SUPPORTED_PROTOCOLS_LITERAL',
|
|
414
414
|
scheme_b: 'SUPPORTED_PROTOCOLS_LITERAL'
|
|
415
415
|
) -> bool:
|
rucio/client/uploadclient.py
CHANGED
|
@@ -21,10 +21,13 @@ import os.path
|
|
|
21
21
|
import random
|
|
22
22
|
import socket
|
|
23
23
|
import time
|
|
24
|
-
from typing import TYPE_CHECKING, Any, Final, Optional,
|
|
24
|
+
from typing import TYPE_CHECKING, Any, Final, Optional, cast
|
|
25
25
|
|
|
26
26
|
from rucio import version
|
|
27
27
|
from rucio.client.client import Client
|
|
28
|
+
from rucio.common.bittorrent import bittorrent_v2_merkle_sha256
|
|
29
|
+
from rucio.common.checksum import GLOBALLY_SUPPORTED_CHECKSUMS, adler32, md5
|
|
30
|
+
from rucio.common.client import detect_client_location
|
|
28
31
|
from rucio.common.config import config_get, config_get_bool, config_get_int
|
|
29
32
|
from rucio.common.constants import RseAttr
|
|
30
33
|
from rucio.common.exception import (
|
|
@@ -42,13 +45,13 @@ from rucio.common.exception import (
|
|
|
42
45
|
ScopeNotFound,
|
|
43
46
|
ServiceUnavailable,
|
|
44
47
|
)
|
|
45
|
-
from rucio.common.utils import
|
|
48
|
+
from rucio.common.utils import execute, generate_uuid, make_valid_did, retry, send_trace
|
|
46
49
|
from rucio.rse import rsemanager as rsemgr
|
|
47
50
|
|
|
48
51
|
if TYPE_CHECKING:
|
|
49
52
|
from collections.abc import Iterable, Mapping
|
|
50
53
|
|
|
51
|
-
from rucio.common.types import AttachDict, DatasetDict, DIDStringDict, FileToUploadDict, FileToUploadWithCollectedAndDatasetInfoDict, FileToUploadWithCollectedInfoDict, LoggerFunction, PathTypeAlias, RSESettingsDict, TraceBaseDict, TraceDict
|
|
54
|
+
from rucio.common.types import AttachDict, DatasetDict, DIDStringDict, FileToUploadDict, FileToUploadWithCollectedAndDatasetInfoDict, FileToUploadWithCollectedInfoDict, LFNDict, LoggerFunction, PathTypeAlias, RSESettingsDict, TraceBaseDict, TraceDict
|
|
52
55
|
from rucio.rse.protocols.protocol import RSEProtocol
|
|
53
56
|
|
|
54
57
|
|
|
@@ -78,6 +81,16 @@ class UploadClient:
|
|
|
78
81
|
self.tracing = tracing
|
|
79
82
|
if not self.tracing:
|
|
80
83
|
logger(logging.DEBUG, 'Tracing is turned off.')
|
|
84
|
+
if self.client.account is None:
|
|
85
|
+
self.logger(logging.DEBUG, 'No account specified, querying rucio.')
|
|
86
|
+
try:
|
|
87
|
+
acc = self.client.whoami()
|
|
88
|
+
if acc is None:
|
|
89
|
+
raise InputValidationError('account not specified and rucio has no account with your identity')
|
|
90
|
+
self.client.account = acc['account']
|
|
91
|
+
except RucioException as e:
|
|
92
|
+
raise InputValidationError('account not specified and problem with rucio: %s' % e)
|
|
93
|
+
self.logger(logging.DEBUG, 'Discovered account as "%s"' % self.client.account)
|
|
81
94
|
self.default_file_scope: Final[str] = 'user.' + self.client.account
|
|
82
95
|
self.rses = {}
|
|
83
96
|
self.rse_expressions = {}
|
|
@@ -232,7 +245,7 @@ class UploadClient:
|
|
|
232
245
|
# if register_after_upload, file should be overwritten if it is not registered
|
|
233
246
|
# otherwise if file already exists on RSE we're done
|
|
234
247
|
if register_after_upload:
|
|
235
|
-
if rsemgr.exists(rse_settings, pfn if pfn else file_did, domain=domain, scheme=force_scheme, impl=impl, auth_token=self.auth_token, vo=self.client.vo, logger=logger):
|
|
248
|
+
if rsemgr.exists(rse_settings, pfn if pfn else file_did, domain=domain, scheme=force_scheme, impl=impl, auth_token=self.auth_token, vo=self.client.vo, logger=logger): # type: ignore (pfn is str)
|
|
236
249
|
try:
|
|
237
250
|
self.client.get_did(file['did_scope'], file['did_name'])
|
|
238
251
|
logger(logging.INFO, 'File already registered. Skipping upload.')
|
|
@@ -242,7 +255,7 @@ class UploadClient:
|
|
|
242
255
|
logger(logging.INFO, 'File already exists on RSE. Previous left overs will be overwritten.')
|
|
243
256
|
delete_existing = True
|
|
244
257
|
elif not is_deterministic and not no_register:
|
|
245
|
-
if rsemgr.exists(rse_settings, pfn, domain=domain, scheme=force_scheme, impl=impl, auth_token=self.auth_token, vo=self.client.vo, logger=logger):
|
|
258
|
+
if rsemgr.exists(rse_settings, pfn, domain=domain, scheme=force_scheme, impl=impl, auth_token=self.auth_token, vo=self.client.vo, logger=logger): # type: ignore (pfn is str)
|
|
246
259
|
logger(logging.INFO, 'File already exists on RSE with given pfn. Skipping upload. Existing replica has to be removed first.')
|
|
247
260
|
trace['stateReason'] = 'File already exists'
|
|
248
261
|
continue
|
|
@@ -251,7 +264,7 @@ class UploadClient:
|
|
|
251
264
|
trace['stateReason'] = 'File already exists'
|
|
252
265
|
continue
|
|
253
266
|
else:
|
|
254
|
-
if rsemgr.exists(rse_settings, pfn if pfn else file_did, domain=domain, scheme=force_scheme, impl=impl, auth_token=self.auth_token, vo=self.client.vo, logger=logger):
|
|
267
|
+
if rsemgr.exists(rse_settings, pfn if pfn else file_did, domain=domain, scheme=force_scheme, impl=impl, auth_token=self.auth_token, vo=self.client.vo, logger=logger): # type: ignore (pfn is str)
|
|
255
268
|
logger(logging.INFO, 'File already exists on RSE. Skipping upload')
|
|
256
269
|
trace['stateReason'] = 'File already exists'
|
|
257
270
|
continue
|
|
@@ -266,10 +279,11 @@ class UploadClient:
|
|
|
266
279
|
protocol = protocols.pop()
|
|
267
280
|
cur_scheme = protocol['scheme']
|
|
268
281
|
logger(logging.INFO, 'Trying upload with %s to %s' % (cur_scheme, rse))
|
|
269
|
-
lfn = {
|
|
282
|
+
lfn: "LFNDict" = {
|
|
283
|
+
'name': file['did_name'],
|
|
284
|
+
'scope': file['did_scope']
|
|
285
|
+
}
|
|
270
286
|
lfn['filename'] = basename
|
|
271
|
-
lfn['scope'] = file['did_scope']
|
|
272
|
-
lfn['name'] = file['did_name']
|
|
273
287
|
|
|
274
288
|
for checksum_name in GLOBALLY_SUPPORTED_CHECKSUMS:
|
|
275
289
|
if checksum_name in file:
|
|
@@ -612,7 +626,7 @@ class UploadClient:
|
|
|
612
626
|
self,
|
|
613
627
|
rse_settings: "RSESettingsDict",
|
|
614
628
|
rse_attributes: dict[str, Any],
|
|
615
|
-
lfn:
|
|
629
|
+
lfn: "LFNDict",
|
|
616
630
|
source_dir: Optional[str] = None,
|
|
617
631
|
domain: str = 'wan',
|
|
618
632
|
impl: Optional[str] = None,
|
|
@@ -674,7 +688,7 @@ class UploadClient:
|
|
|
674
688
|
pfn = self.client.get_signed_url(rse_settings['rse'], sign_service, 'write', pfn)
|
|
675
689
|
|
|
676
690
|
# Create a name of tmp file if renaming operation is supported
|
|
677
|
-
pfn_tmp = cast(str, '%s.rucio.upload' % pfn if protocol_write.renaming else pfn)
|
|
691
|
+
pfn_tmp = cast("str", '%s.rucio.upload' % pfn if protocol_write.renaming else pfn)
|
|
678
692
|
signed_read_pfn_tmp = '%s.rucio.upload' % signed_read_pfn if protocol_write.renaming else signed_read_pfn
|
|
679
693
|
|
|
680
694
|
# Either DID exists or not register_after_upload
|
|
@@ -750,7 +764,7 @@ class UploadClient:
|
|
|
750
764
|
try:
|
|
751
765
|
if protocol_write.renaming:
|
|
752
766
|
logger(logging.DEBUG, 'Renaming file %s to %s' % (pfn_tmp, pfn))
|
|
753
|
-
protocol_write.rename(pfn_tmp, pfn)
|
|
767
|
+
protocol_write.rename(pfn_tmp, pfn) # type: ignore (pfn might be None)
|
|
754
768
|
except Exception:
|
|
755
769
|
raise RucioException('Unable to rename the tmp file %s.' % pfn_tmp)
|
|
756
770
|
|