rucio 37.7.1__py3-none-any.whl → 38.0.0rc2__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 might be problematic. Click here for more details.
- rucio/alembicrevision.py +1 -1
- rucio/cli/bin_legacy/rucio.py +51 -107
- rucio/cli/bin_legacy/rucio_admin.py +26 -26
- rucio/cli/command.py +1 -0
- rucio/cli/did.py +2 -2
- rucio/cli/opendata.py +132 -0
- rucio/cli/replica.py +15 -5
- rucio/cli/rule.py +7 -2
- rucio/cli/scope.py +3 -2
- rucio/cli/utils.py +28 -4
- rucio/client/baseclient.py +9 -1
- rucio/client/client.py +2 -0
- rucio/client/diracclient.py +73 -12
- rucio/client/opendataclient.py +249 -0
- rucio/client/subscriptionclient.py +30 -0
- rucio/client/uploadclient.py +10 -13
- rucio/common/constants.py +4 -1
- rucio/common/exception.py +55 -0
- rucio/common/plugins.py +45 -8
- rucio/common/schema/generic.py +5 -3
- rucio/common/schema/generic_multi_vo.py +4 -2
- rucio/common/types.py +8 -7
- rucio/common/utils.py +176 -11
- rucio/core/dirac.py +5 -5
- rucio/core/opendata.py +744 -0
- rucio/core/rule.py +63 -8
- rucio/core/transfer.py +1 -1
- rucio/db/sqla/constants.py +6 -0
- rucio/db/sqla/migrate_repo/versions/a62db546a1f1_opendata_initial_model.py +85 -0
- rucio/db/sqla/models.py +67 -0
- rucio/db/sqla/util.py +2 -2
- rucio/gateway/dirac.py +1 -1
- rucio/gateway/opendata.py +190 -0
- rucio/gateway/subscription.py +5 -3
- rucio/rse/protocols/protocol.py +9 -5
- rucio/rse/translation.py +17 -6
- rucio/transfertool/fts3.py +1 -0
- rucio/transfertool/fts3_plugins.py +6 -1
- rucio/vcsversion.py +4 -4
- rucio/web/rest/flaskapi/v1/common.py +34 -14
- rucio/web/rest/flaskapi/v1/config.py +1 -1
- rucio/web/rest/flaskapi/v1/dids.py +447 -160
- rucio/web/rest/flaskapi/v1/heartbeats.py +1 -1
- rucio/web/rest/flaskapi/v1/identities.py +1 -1
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +1 -1
- rucio/web/rest/flaskapi/v1/locks.py +1 -1
- rucio/web/rest/flaskapi/v1/main.py +3 -8
- rucio/web/rest/flaskapi/v1/meta_conventions.py +1 -16
- rucio/web/rest/flaskapi/v1/nongrid_traces.py +1 -1
- rucio/web/rest/flaskapi/v1/opendata.py +391 -0
- rucio/web/rest/flaskapi/v1/opendata_public.py +146 -0
- rucio/web/rest/flaskapi/v1/requests.py +1 -1
- rucio/web/rest/flaskapi/v1/rses.py +1 -1
- rucio/web/rest/flaskapi/v1/rules.py +1 -1
- rucio/web/rest/flaskapi/v1/scopes.py +1 -1
- rucio/web/rest/flaskapi/v1/subscriptions.py +6 -9
- rucio/web/rest/flaskapi/v1/traces.py +1 -1
- rucio/web/rest/flaskapi/v1/vos.py +1 -1
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/alembic.ini.template +1 -1
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/alembic_offline.ini.template +1 -1
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/rucio.cfg.template +2 -2
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/rucio_multi_vo.cfg.template +3 -3
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/requirements.server.txt +6 -3
- rucio-38.0.0rc2.data/data/rucio/tools/reset_database.py +87 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio +2 -1
- {rucio-37.7.1.dist-info → rucio-38.0.0rc2.dist-info}/METADATA +37 -36
- {rucio-37.7.1.dist-info → rucio-38.0.0rc2.dist-info}/RECORD +119 -113
- rucio/client/fileclient.py +0 -57
- rucio-37.7.1.data/data/rucio/tools/reset_database.py +0 -40
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/globus-config.yml.template +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/ldap.cfg.template +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/tools/bootstrap.py +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-abacus-account +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-abacus-collection-replica +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-abacus-rse +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-admin +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-atropos +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-auditor +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-automatix +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-bb8 +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-cache-client +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-cache-consumer +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-conveyor-finisher +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-conveyor-poller +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-conveyor-preparer +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-conveyor-receiver +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-conveyor-stager +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-conveyor-submitter +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-conveyor-throttler +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-dark-reaper +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-dumper +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-follower +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-hermes +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-judge-cleaner +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-judge-evaluator +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-judge-injector +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-judge-repairer +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-kronos +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-minos +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-minos-temporary-expiration +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-necromancer +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-oauth-manager +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-reaper +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-replica-recoverer +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-rse-decommissioner +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-storage-consistency-actions +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-transmogrifier +0 -0
- {rucio-37.7.1.data → rucio-38.0.0rc2.data}/scripts/rucio-undertaker +0 -0
- {rucio-37.7.1.dist-info → rucio-38.0.0rc2.dist-info}/WHEEL +0 -0
- {rucio-37.7.1.dist-info → rucio-38.0.0rc2.dist-info}/licenses/AUTHORS.rst +0 -0
- {rucio-37.7.1.dist-info → rucio-38.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {rucio-37.7.1.dist-info → rucio-38.0.0rc2.dist-info}/top_level.txt +0 -0
|
@@ -110,7 +110,7 @@ class Heartbeat(ErrorHandlingMethodView):
|
|
|
110
110
|
return 'OK', 200
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
def blueprint():
|
|
113
|
+
def blueprint() -> AuthenticatedBlueprint:
|
|
114
114
|
bp = AuthenticatedBlueprint('heartbeats', __name__, url_prefix='/heartbeats')
|
|
115
115
|
|
|
116
116
|
heartbeat_view = Heartbeat.as_view('heartbeat')
|
|
@@ -262,7 +262,7 @@ class Accounts(ErrorHandlingMethodView):
|
|
|
262
262
|
return jsonify(accounts)
|
|
263
263
|
|
|
264
264
|
|
|
265
|
-
def blueprint():
|
|
265
|
+
def blueprint() -> AuthenticatedBlueprint:
|
|
266
266
|
bp = AuthenticatedBlueprint('identities', __name__, url_prefix='/identities')
|
|
267
267
|
|
|
268
268
|
userpass_view = UserPass.as_view('userpass')
|
|
@@ -293,7 +293,7 @@ class LifetimeExceptionId(ErrorHandlingMethodView):
|
|
|
293
293
|
return 'Created', 201
|
|
294
294
|
|
|
295
295
|
|
|
296
|
-
def blueprint():
|
|
296
|
+
def blueprint() -> AuthenticatedBlueprint:
|
|
297
297
|
bp = AuthenticatedBlueprint('lifetime_exceptions', __name__, url_prefix='/lifetime_exceptions')
|
|
298
298
|
|
|
299
299
|
lifetime_exception_view = LifetimeException.as_view('lifetime_exception')
|
|
@@ -335,7 +335,7 @@ class DatasetLocksForDids(ErrorHandlingMethodView):
|
|
|
335
335
|
return generate_http_error_flask(400, error)
|
|
336
336
|
|
|
337
337
|
|
|
338
|
-
def blueprint():
|
|
338
|
+
def blueprint() -> AuthenticatedBlueprint:
|
|
339
339
|
bp = AuthenticatedBlueprint('locks', __name__, url_prefix='/locks')
|
|
340
340
|
|
|
341
341
|
lock_by_rse_view = LockByRSE.as_view('lock_by_rse')
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
|
|
16
16
|
import importlib
|
|
17
|
-
import logging
|
|
18
17
|
from typing import TYPE_CHECKING
|
|
19
18
|
|
|
20
19
|
from flask import Flask
|
|
@@ -50,19 +49,15 @@ DEFAULT_ENDPOINTS = {
|
|
|
50
49
|
'rules',
|
|
51
50
|
'scopes',
|
|
52
51
|
'subscriptions',
|
|
52
|
+
'opendata',
|
|
53
|
+
'opendata_public',
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
56
|
def apply_endpoints(app: Flask, modules: "Iterable[str]") -> None:
|
|
57
57
|
for blueprint_module in modules:
|
|
58
|
-
# Legacy patch - TODO Remove in 38.0.0
|
|
59
|
-
if blueprint_module == "meta":
|
|
60
|
-
logging.log(logging.WARNING, "Endpoint `meta` is depreciated and will be removed in future releases")
|
|
61
|
-
blueprint_module = "meta_conventions"
|
|
62
58
|
try:
|
|
63
59
|
# searches for module names locally
|
|
64
|
-
blueprint_module = importlib.import_module('.' + blueprint_module,
|
|
65
|
-
package='rucio.web.rest.flaskapi.v1')
|
|
60
|
+
blueprint_module = importlib.import_module('.' + blueprint_module, package='rucio.web.rest.flaskapi.v1')
|
|
66
61
|
except ImportError:
|
|
67
62
|
raise ConfigurationError(f'Could not load "{blueprint_module}" provided in the endpoints configuration value')
|
|
68
63
|
|
|
@@ -204,7 +204,7 @@ class Values(ErrorHandlingMethodView):
|
|
|
204
204
|
return 'Created', 201
|
|
205
205
|
|
|
206
206
|
|
|
207
|
-
def blueprint():
|
|
207
|
+
def blueprint() -> AuthenticatedBlueprint:
|
|
208
208
|
bp = AuthenticatedBlueprint('meta_conventions', __name__, url_prefix='/meta_conventions')
|
|
209
209
|
|
|
210
210
|
meta_view = MetaConventions.as_view('meta_conventions')
|
|
@@ -217,25 +217,10 @@ def blueprint():
|
|
|
217
217
|
return bp
|
|
218
218
|
|
|
219
219
|
|
|
220
|
-
def blueprint_legacy():
|
|
221
|
-
# TODO: Remove in 38.0
|
|
222
|
-
bp = AuthenticatedBlueprint('meta', __name__, url_prefix='/meta')
|
|
223
|
-
|
|
224
|
-
meta_view = MetaConventions.as_view('meta')
|
|
225
|
-
bp.add_url_rule('/', view_func=meta_view, methods=['get', ])
|
|
226
|
-
bp.add_url_rule('/<key>', view_func=meta_view, methods=['post', ])
|
|
227
|
-
values_view = Values.as_view('values')
|
|
228
|
-
bp.add_url_rule('/<key>/', view_func=values_view, methods=['get', 'post'])
|
|
229
|
-
|
|
230
|
-
bp.after_request(response_headers)
|
|
231
|
-
return bp
|
|
232
|
-
|
|
233
|
-
|
|
234
220
|
def make_doc():
|
|
235
221
|
""" Only used for sphinx documentation """
|
|
236
222
|
doc_app = Flask(__name__)
|
|
237
223
|
|
|
238
224
|
doc_app.register_blueprint(blueprint())
|
|
239
|
-
doc_app.register_blueprint(blueprint_legacy())
|
|
240
225
|
|
|
241
226
|
return doc_app
|
|
@@ -80,7 +80,7 @@ class XAODTrace(ErrorHandlingMethodView):
|
|
|
80
80
|
return 'Created', 201, headers
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def blueprint():
|
|
83
|
+
def blueprint() -> Blueprint:
|
|
84
84
|
bp = Blueprint('nongrid_traces', __name__, url_prefix='/nongrid_traces')
|
|
85
85
|
|
|
86
86
|
xaod_trace_view = XAODTrace.as_view('xaod_trace')
|
|
@@ -0,0 +1,391 @@
|
|
|
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 flask import Blueprint, Flask, Response, request
|
|
16
|
+
|
|
17
|
+
from rucio.common.constants import DEFAULT_VO
|
|
18
|
+
from rucio.common.exception import AccessDenied, DataIdentifierNotFound, OpenDataDataIdentifierAlreadyExists, OpenDataDataIdentifierNotFound
|
|
19
|
+
from rucio.common.utils import render_json
|
|
20
|
+
from rucio.core.opendata import validate_opendata_did_state
|
|
21
|
+
from rucio.gateway import opendata
|
|
22
|
+
from rucio.web.rest.flaskapi.authenticated_bp import AuthenticatedBlueprint
|
|
23
|
+
from rucio.web.rest.flaskapi.v1.common import ErrorHandlingMethodView, check_accept_header_wrapper_flask, generate_http_error_flask, json_parameters, param_get, parse_scope_name, response_headers
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OpenDataView(ErrorHandlingMethodView):
|
|
27
|
+
@staticmethod
|
|
28
|
+
def get_helper(public: bool) -> "Response":
|
|
29
|
+
"""
|
|
30
|
+
Helper function to list Open Data DIDs. To be used by both authenticated and unauthenticated views.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
state = request.args.get("state", default=None) if not public else "PUBLIC"
|
|
34
|
+
if state is not None:
|
|
35
|
+
state = validate_opendata_did_state(state)
|
|
36
|
+
|
|
37
|
+
limit = request.args.get("limit", default=None, type=int) # type: ignore
|
|
38
|
+
offset = request.args.get("offset", default=None, type=int) # type: ignore
|
|
39
|
+
result = opendata.list_opendata_dids(limit=limit, offset=offset, state=state)
|
|
40
|
+
result = render_json(**result)
|
|
41
|
+
return Response(result, status=200, mimetype='application/json')
|
|
42
|
+
except AccessDenied as error:
|
|
43
|
+
return generate_http_error_flask(401, error)
|
|
44
|
+
except Exception as error:
|
|
45
|
+
return generate_http_error_flask(400, error)
|
|
46
|
+
|
|
47
|
+
@check_accept_header_wrapper_flask(["application/json"])
|
|
48
|
+
def get(self) -> "Response":
|
|
49
|
+
"""
|
|
50
|
+
---
|
|
51
|
+
summary: List Opendata DIDs
|
|
52
|
+
description: "Retrieves a list of Opendata Data Identifiers (DIDs). Supports optional query parameters for pagination and filtering by state."
|
|
53
|
+
tags:
|
|
54
|
+
- Opendata
|
|
55
|
+
parameters:
|
|
56
|
+
- name: limit
|
|
57
|
+
in: query
|
|
58
|
+
description: "Maximum number of results to return."
|
|
59
|
+
schema:
|
|
60
|
+
type: integer
|
|
61
|
+
required: false
|
|
62
|
+
style: form
|
|
63
|
+
- name: offset
|
|
64
|
+
in: query
|
|
65
|
+
description: "Number of items to skip before starting to collect the result set."
|
|
66
|
+
schema:
|
|
67
|
+
type: integer
|
|
68
|
+
required: false
|
|
69
|
+
style: form
|
|
70
|
+
- name: state
|
|
71
|
+
in: query
|
|
72
|
+
description: "Filter DIDs by their state (e.g., 'PUBLIC')."
|
|
73
|
+
schema:
|
|
74
|
+
type: string
|
|
75
|
+
required: false
|
|
76
|
+
style: form
|
|
77
|
+
responses:
|
|
78
|
+
200:
|
|
79
|
+
description: "Successful retrieval of the list of Opendata DIDs."
|
|
80
|
+
content:
|
|
81
|
+
application/json:
|
|
82
|
+
schema:
|
|
83
|
+
type: object
|
|
84
|
+
401:
|
|
85
|
+
description: "Access denied: Invalid authentication."
|
|
86
|
+
400:
|
|
87
|
+
description: "Invalid request or query parameters."
|
|
88
|
+
"""
|
|
89
|
+
return self.get_helper(public=False)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class OpenDataDIDsView(ErrorHandlingMethodView):
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def get_helper(*, scope: str, name: str, public: bool) -> "Response":
|
|
96
|
+
"""
|
|
97
|
+
Helper function to get Open Data DID information. To be used by both authenticated and unauthenticated views.
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
vo = request.environ.get("vo", DEFAULT_VO) if not public else DEFAULT_VO
|
|
101
|
+
state = request.args.get("state", default=None) if not public else "public"
|
|
102
|
+
if state is not None:
|
|
103
|
+
state = validate_opendata_did_state(state)
|
|
104
|
+
|
|
105
|
+
scope, name = parse_scope_name(f"{scope}/{name}", vo=vo)
|
|
106
|
+
include_files = request.args.get("files", default="0").lower() == "1"
|
|
107
|
+
include_metadata = request.args.get("meta", default="0").lower() == "1"
|
|
108
|
+
include_doi = request.args.get("doi", default="1").lower() == "1"
|
|
109
|
+
result = opendata.get_opendata_did(scope=scope, name=name, vo=vo,
|
|
110
|
+
state=state,
|
|
111
|
+
include_files=include_files,
|
|
112
|
+
include_metadata=include_metadata,
|
|
113
|
+
include_doi=include_doi,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
result = render_json(**result)
|
|
117
|
+
return Response(result, status=200, mimetype='application/json')
|
|
118
|
+
except AccessDenied as error:
|
|
119
|
+
return generate_http_error_flask(401, error)
|
|
120
|
+
except OpenDataDataIdentifierNotFound as error:
|
|
121
|
+
return generate_http_error_flask(404, error)
|
|
122
|
+
except Exception as error:
|
|
123
|
+
return generate_http_error_flask(400, error)
|
|
124
|
+
|
|
125
|
+
@check_accept_header_wrapper_flask(["application/json"])
|
|
126
|
+
def get(self, scope: str, name: str) -> "Response":
|
|
127
|
+
"""
|
|
128
|
+
---
|
|
129
|
+
summary: Get Opendata DID Information
|
|
130
|
+
description: "Retrieves detailed Opendata information for the given scope and name. Supports optional query parameters to control the inclusion of files, metadata, and DOI information."
|
|
131
|
+
Tags:
|
|
132
|
+
- Opendata
|
|
133
|
+
parameters:
|
|
134
|
+
- name: scope
|
|
135
|
+
in: path
|
|
136
|
+
description: "The scope of the data identifier."
|
|
137
|
+
schema:
|
|
138
|
+
type: string
|
|
139
|
+
required: true
|
|
140
|
+
style: simple
|
|
141
|
+
- name: name
|
|
142
|
+
in: path
|
|
143
|
+
description: "The name of the data identifier."
|
|
144
|
+
schema:
|
|
145
|
+
type: string
|
|
146
|
+
required: true
|
|
147
|
+
style: simple
|
|
148
|
+
- name: files
|
|
149
|
+
in: query
|
|
150
|
+
description: "Whether to include the list of files. '1' to include, '0' to exclude. Default is '0'."
|
|
151
|
+
schema:
|
|
152
|
+
type: string
|
|
153
|
+
enum: ['0', '1']
|
|
154
|
+
required: false
|
|
155
|
+
style: form
|
|
156
|
+
- name: meta
|
|
157
|
+
in: query
|
|
158
|
+
description: "Whether to include metadata. '1' to include, '0' to exclude. Default is '0'."
|
|
159
|
+
schema:
|
|
160
|
+
type: string
|
|
161
|
+
enum: ['0', '1']
|
|
162
|
+
required: false
|
|
163
|
+
style: form
|
|
164
|
+
- name: doi
|
|
165
|
+
in: query
|
|
166
|
+
description: "Whether to include the Digital Object Identifier (DOI). '1' to include, '0' to exclude. Default is '1'."
|
|
167
|
+
schema:
|
|
168
|
+
type: string
|
|
169
|
+
enum: ['0', '1']
|
|
170
|
+
required: false
|
|
171
|
+
style: form
|
|
172
|
+
- name: state
|
|
173
|
+
in: query
|
|
174
|
+
description: "Optional state filter for the data identifier."
|
|
175
|
+
schema:
|
|
176
|
+
type: string
|
|
177
|
+
required: false
|
|
178
|
+
style: form
|
|
179
|
+
responses:
|
|
180
|
+
200:
|
|
181
|
+
description: "Successful retrieval of Opendata DID information."
|
|
182
|
+
content:
|
|
183
|
+
application/json:
|
|
184
|
+
schema:
|
|
185
|
+
type: object
|
|
186
|
+
401:
|
|
187
|
+
description: "Access denied: Invalid authentication."
|
|
188
|
+
404:
|
|
189
|
+
description: "Data Identifier not found."
|
|
190
|
+
400:
|
|
191
|
+
description: "Invalid request or input parameters."
|
|
192
|
+
"""
|
|
193
|
+
return self.get_helper(scope=scope, name=name, public=False)
|
|
194
|
+
|
|
195
|
+
def post(self, scope: str, name: str) -> "Response":
|
|
196
|
+
"""
|
|
197
|
+
---
|
|
198
|
+
summary: Register Opendata DID
|
|
199
|
+
description: "Registers an existing DID as Opendata."
|
|
200
|
+
tags:
|
|
201
|
+
- Opendata
|
|
202
|
+
parameters:
|
|
203
|
+
- name: scope
|
|
204
|
+
in: path
|
|
205
|
+
description: "The scope of the data identifier to be registered."
|
|
206
|
+
schema:
|
|
207
|
+
type: string
|
|
208
|
+
required: true
|
|
209
|
+
style: simple
|
|
210
|
+
- name: name
|
|
211
|
+
in: path
|
|
212
|
+
description: "The name of the data identifier to be registered."
|
|
213
|
+
schema:
|
|
214
|
+
type: string
|
|
215
|
+
required: true
|
|
216
|
+
style: simple
|
|
217
|
+
responses:
|
|
218
|
+
201:
|
|
219
|
+
description: "Opendata DID successfully registered."
|
|
220
|
+
content:
|
|
221
|
+
application/json:
|
|
222
|
+
schema:
|
|
223
|
+
type: string
|
|
224
|
+
enum: []
|
|
225
|
+
400:
|
|
226
|
+
description: "Invalid input: The provided scope/name is not valid."
|
|
227
|
+
401:
|
|
228
|
+
description: "Access denied: Invalid authentication."
|
|
229
|
+
404:
|
|
230
|
+
description: "Data Identifier not found."
|
|
231
|
+
409:
|
|
232
|
+
description: "Data Identifier already exists in the Opendata catalog."
|
|
233
|
+
"""
|
|
234
|
+
vo = request.environ.get("vo", DEFAULT_VO)
|
|
235
|
+
try:
|
|
236
|
+
scope, name = parse_scope_name(f"{scope}/{name}", vo=vo)
|
|
237
|
+
opendata.add_opendata_did(scope=scope, name=name, vo=vo)
|
|
238
|
+
except AccessDenied as error:
|
|
239
|
+
return generate_http_error_flask(401, error)
|
|
240
|
+
except DataIdentifierNotFound as error:
|
|
241
|
+
return generate_http_error_flask(404, error)
|
|
242
|
+
except OpenDataDataIdentifierAlreadyExists as error:
|
|
243
|
+
return generate_http_error_flask(409, error)
|
|
244
|
+
except Exception as error:
|
|
245
|
+
return generate_http_error_flask(400, error)
|
|
246
|
+
|
|
247
|
+
return Response(status=201, mimetype='application/json')
|
|
248
|
+
|
|
249
|
+
def put(self, scope: str, name: str) -> "Response":
|
|
250
|
+
"""
|
|
251
|
+
---
|
|
252
|
+
summary: Update Opendata DID
|
|
253
|
+
description: "Updates the properties of an existing Opendata DID."
|
|
254
|
+
tags:
|
|
255
|
+
- Opendata
|
|
256
|
+
parameters:
|
|
257
|
+
- name: scope
|
|
258
|
+
in: path
|
|
259
|
+
description: "The scope of the data identifier to be updated."
|
|
260
|
+
schema:
|
|
261
|
+
type: string
|
|
262
|
+
required: true
|
|
263
|
+
style: simple
|
|
264
|
+
- name: name
|
|
265
|
+
in: path
|
|
266
|
+
description: "The name of the data identifier to be updated."
|
|
267
|
+
schema:
|
|
268
|
+
type: string
|
|
269
|
+
required: true
|
|
270
|
+
style: simple
|
|
271
|
+
- name: body
|
|
272
|
+
in: body
|
|
273
|
+
description: "JSON object containing the fields to update: 'state', 'meta', and/or 'doi'."
|
|
274
|
+
required: false
|
|
275
|
+
content:
|
|
276
|
+
application/json:
|
|
277
|
+
schema:
|
|
278
|
+
type: object
|
|
279
|
+
properties:
|
|
280
|
+
state:
|
|
281
|
+
type: string
|
|
282
|
+
description: "New state for the DID."
|
|
283
|
+
enum: ["draft", "public", "suspended"]
|
|
284
|
+
meta:
|
|
285
|
+
type: object
|
|
286
|
+
description: "New metadata dictionary for the DID. Supports arbitrary JSON objects."
|
|
287
|
+
example: {"key": "value", "another_key": "another_value"}
|
|
288
|
+
doi:
|
|
289
|
+
type: string
|
|
290
|
+
description: "Digital Object Identifier (DOI) for the DID.
|
|
291
|
+
example: '10.1234/abcd.efgh'."
|
|
292
|
+
responses:
|
|
293
|
+
200:
|
|
294
|
+
description: "Opendata DID successfully updated."
|
|
295
|
+
content:
|
|
296
|
+
application/json:
|
|
297
|
+
schema:
|
|
298
|
+
type: string
|
|
299
|
+
enum: []
|
|
300
|
+
400:
|
|
301
|
+
description: "Invalid input or update parameters."
|
|
302
|
+
401:
|
|
303
|
+
description: "Access denied: Invalid authentication."
|
|
304
|
+
404:
|
|
305
|
+
description: "Data Identifier not found."
|
|
306
|
+
"""
|
|
307
|
+
try:
|
|
308
|
+
scope, name = parse_scope_name(f"{scope}/{name}", request.environ.get("vo", DEFAULT_VO))
|
|
309
|
+
parameters = json_parameters()
|
|
310
|
+
state = param_get(parameters, 'state', default=None)
|
|
311
|
+
meta = param_get(parameters, 'meta', default=None)
|
|
312
|
+
doi = param_get(parameters, 'doi', default=None)
|
|
313
|
+
opendata.update_opendata_did(scope=scope,
|
|
314
|
+
name=name,
|
|
315
|
+
state=state,
|
|
316
|
+
meta=meta,
|
|
317
|
+
doi=doi,
|
|
318
|
+
vo=request.environ.get("vo", DEFAULT_VO),
|
|
319
|
+
)
|
|
320
|
+
except AccessDenied as error:
|
|
321
|
+
return generate_http_error_flask(401, error)
|
|
322
|
+
except OpenDataDataIdentifierNotFound as error:
|
|
323
|
+
return generate_http_error_flask(404, error)
|
|
324
|
+
except Exception as error:
|
|
325
|
+
return generate_http_error_flask(400, error)
|
|
326
|
+
|
|
327
|
+
return Response(status=200, mimetype='application/json')
|
|
328
|
+
|
|
329
|
+
def delete(self, scope: str, name: str) -> "Response":
|
|
330
|
+
"""
|
|
331
|
+
---
|
|
332
|
+
summary: Delete Opendata DID
|
|
333
|
+
description: "Deletes an entry in the Opendata catalog."
|
|
334
|
+
tags:
|
|
335
|
+
- Opendata
|
|
336
|
+
parameters:
|
|
337
|
+
- name: scope
|
|
338
|
+
in: path
|
|
339
|
+
description: "The scope of the data identifier to be deleted."
|
|
340
|
+
schema:
|
|
341
|
+
type: string
|
|
342
|
+
required: true
|
|
343
|
+
style: simple
|
|
344
|
+
- name: name
|
|
345
|
+
in: path
|
|
346
|
+
description: "The name of the data identifier to be deleted."
|
|
347
|
+
schema:
|
|
348
|
+
type: string
|
|
349
|
+
required: true
|
|
350
|
+
style: simple
|
|
351
|
+
responses:
|
|
352
|
+
204:
|
|
353
|
+
description: "Opendata DID successfully deleted. No content is returned."
|
|
354
|
+
400:
|
|
355
|
+
description: "Invalid input: The provided scope/name is not valid."
|
|
356
|
+
401:
|
|
357
|
+
description: "Access denied: Invalid authentication."
|
|
358
|
+
404:
|
|
359
|
+
description: "Data Identifier not found."
|
|
360
|
+
"""
|
|
361
|
+
try:
|
|
362
|
+
scope, name = parse_scope_name(f"{scope}/{name}", request.environ.get("vo", DEFAULT_VO))
|
|
363
|
+
opendata.delete_opendata_did(scope=scope, name=name, vo=request.environ.get("vo", DEFAULT_VO))
|
|
364
|
+
except AccessDenied as error:
|
|
365
|
+
return generate_http_error_flask(401, error)
|
|
366
|
+
except OpenDataDataIdentifierNotFound as error:
|
|
367
|
+
return generate_http_error_flask(404, error)
|
|
368
|
+
except Exception as error:
|
|
369
|
+
return generate_http_error_flask(400, error)
|
|
370
|
+
return Response(status=204, mimetype='application/json')
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def blueprint() -> "Blueprint":
|
|
374
|
+
bp = AuthenticatedBlueprint("opendata", __name__, url_prefix="/opendata")
|
|
375
|
+
|
|
376
|
+
opendata_view = OpenDataView.as_view("opendata")
|
|
377
|
+
bp.add_url_rule("/dids", view_func=opendata_view, methods=["get"])
|
|
378
|
+
|
|
379
|
+
opendata_did_view = OpenDataDIDsView.as_view("opendata_did")
|
|
380
|
+
bp.add_url_rule("/dids/<scope>/<name>", view_func=opendata_did_view, methods=["get", "post", "put", "delete"])
|
|
381
|
+
|
|
382
|
+
bp.after_request(response_headers)
|
|
383
|
+
|
|
384
|
+
return bp
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def make_doc() -> "Flask":
|
|
388
|
+
""" Only used for sphinx documentation """
|
|
389
|
+
doc_app = Flask(__name__)
|
|
390
|
+
doc_app.register_blueprint(blueprint())
|
|
391
|
+
return doc_app
|
|
@@ -0,0 +1,146 @@
|
|
|
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 flask import Blueprint, Flask, Response
|
|
16
|
+
|
|
17
|
+
from rucio.web.rest.flaskapi.v1.common import ErrorHandlingMethodView, check_accept_header_wrapper_flask, response_headers
|
|
18
|
+
from rucio.web.rest.flaskapi.v1.opendata import OpenDataDIDsView, OpenDataView
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OpenDataPublicView(ErrorHandlingMethodView):
|
|
22
|
+
|
|
23
|
+
@check_accept_header_wrapper_flask(["application/json"])
|
|
24
|
+
def get(self) -> "Response":
|
|
25
|
+
"""
|
|
26
|
+
---
|
|
27
|
+
summary: List Opendata DIDs marked as public
|
|
28
|
+
description: "Retrieves a list of public Opendata Data Identifiers (DIDs). Supports optional query parameters for pagination."
|
|
29
|
+
tags:
|
|
30
|
+
- Opendata
|
|
31
|
+
parameters:
|
|
32
|
+
- name: limit
|
|
33
|
+
in: query
|
|
34
|
+
description: "Maximum number of results to return."
|
|
35
|
+
schema:
|
|
36
|
+
type: integer
|
|
37
|
+
required: false
|
|
38
|
+
style: form
|
|
39
|
+
- name: offset
|
|
40
|
+
in: query
|
|
41
|
+
description: "Number of items to skip before starting to collect the result set."
|
|
42
|
+
schema:
|
|
43
|
+
type: integer
|
|
44
|
+
required: false
|
|
45
|
+
style: form
|
|
46
|
+
responses:
|
|
47
|
+
200:
|
|
48
|
+
description: "Successful retrieval of the list of Opendata DIDs."
|
|
49
|
+
content:
|
|
50
|
+
application/json:
|
|
51
|
+
schema:
|
|
52
|
+
type: object
|
|
53
|
+
401:
|
|
54
|
+
description: "Access denied: Invalid authentication."
|
|
55
|
+
400:
|
|
56
|
+
description: "Invalid request or query parameters."
|
|
57
|
+
"""
|
|
58
|
+
return OpenDataView.get_helper(public=True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class OpenDataPublicDIDsView(ErrorHandlingMethodView):
|
|
62
|
+
|
|
63
|
+
@check_accept_header_wrapper_flask(["application/json"])
|
|
64
|
+
def get(self, scope: str, name: str) -> "Response":
|
|
65
|
+
"""
|
|
66
|
+
---
|
|
67
|
+
summary: Get Opendata DID Information for public Opendata DIDs
|
|
68
|
+
description: "Retrieves detailed Opendata information for the given scope and name. Only works for public opendata DIDs. Supports optional query parameters to control the inclusion of files, metadata, and DOI information."
|
|
69
|
+
Tags:
|
|
70
|
+
- Opendata
|
|
71
|
+
parameters:
|
|
72
|
+
- name: scope
|
|
73
|
+
in: path
|
|
74
|
+
description: "The scope of the data identifier."
|
|
75
|
+
schema:
|
|
76
|
+
type: string
|
|
77
|
+
required: true
|
|
78
|
+
style: simple
|
|
79
|
+
- name: name
|
|
80
|
+
in: path
|
|
81
|
+
description: "The name of the data identifier."
|
|
82
|
+
schema:
|
|
83
|
+
type: string
|
|
84
|
+
required: true
|
|
85
|
+
style: simple
|
|
86
|
+
- name: files
|
|
87
|
+
in: query
|
|
88
|
+
description: "Whether to include the list of files. '1' to include, '0' to exclude. Default is '0'."
|
|
89
|
+
schema:
|
|
90
|
+
type: string
|
|
91
|
+
enum: ['0', '1']
|
|
92
|
+
required: false
|
|
93
|
+
style: form
|
|
94
|
+
- name: meta
|
|
95
|
+
in: query
|
|
96
|
+
description: "Whether to include metadata. '1' to include, '0' to exclude. Default is '0'."
|
|
97
|
+
schema:
|
|
98
|
+
type: string
|
|
99
|
+
enum: ['0', '1']
|
|
100
|
+
required: false
|
|
101
|
+
style: form
|
|
102
|
+
- name: doi
|
|
103
|
+
in: query
|
|
104
|
+
description: "Whether to include the Digital Object Identifier (DOI). '1' to include, '0' to exclude. Default is '1'."
|
|
105
|
+
schema:
|
|
106
|
+
type: string
|
|
107
|
+
enum: ['0', '1']
|
|
108
|
+
required: false
|
|
109
|
+
style: form
|
|
110
|
+
responses:
|
|
111
|
+
200:
|
|
112
|
+
description: "Successful retrieval of Opendata DID information."
|
|
113
|
+
content:
|
|
114
|
+
application/json:
|
|
115
|
+
schema:
|
|
116
|
+
type: object
|
|
117
|
+
401:
|
|
118
|
+
description: "Access denied: Invalid authentication."
|
|
119
|
+
404:
|
|
120
|
+
description: "Data Identifier not found."
|
|
121
|
+
400:
|
|
122
|
+
description: "Invalid request or input parameters."
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
return OpenDataDIDsView.get_helper(scope=scope, name=name, public=True)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def blueprint() -> "Blueprint":
|
|
129
|
+
bp = Blueprint("opendata_public", __name__, url_prefix="/opendata/public")
|
|
130
|
+
|
|
131
|
+
opendata_public_view = OpenDataPublicView.as_view("opendata")
|
|
132
|
+
bp.add_url_rule("/dids", view_func=opendata_public_view, methods=["get"])
|
|
133
|
+
|
|
134
|
+
opendata_private_did_view = OpenDataPublicDIDsView.as_view("opendata_did")
|
|
135
|
+
bp.add_url_rule("/dids/<scope>/<name>", view_func=opendata_private_did_view, methods=["get"])
|
|
136
|
+
|
|
137
|
+
bp.after_request(response_headers)
|
|
138
|
+
|
|
139
|
+
return bp
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def make_doc() -> "Flask":
|
|
143
|
+
""" Only used for sphinx documentation """
|
|
144
|
+
doc_app = Flask(__name__)
|
|
145
|
+
doc_app.register_blueprint(blueprint())
|
|
146
|
+
return doc_app
|
|
@@ -1166,7 +1166,7 @@ class TransferLimits(ErrorHandlingMethodView):
|
|
|
1166
1166
|
return '', 200
|
|
1167
1167
|
|
|
1168
1168
|
|
|
1169
|
-
def blueprint():
|
|
1169
|
+
def blueprint() -> AuthenticatedBlueprint:
|
|
1170
1170
|
bp = AuthenticatedBlueprint('requests', __name__, url_prefix='/requests')
|
|
1171
1171
|
|
|
1172
1172
|
request_get_view = RequestGet.as_view('request_get')
|
|
@@ -2207,7 +2207,7 @@ class QoSPolicy(ErrorHandlingMethodView):
|
|
|
2207
2207
|
return generate_http_error_flask(404, error)
|
|
2208
2208
|
|
|
2209
2209
|
|
|
2210
|
-
def blueprint():
|
|
2210
|
+
def blueprint() -> AuthenticatedBlueprint:
|
|
2211
2211
|
bp = AuthenticatedBlueprint('rses', __name__, url_prefix='/rses')
|
|
2212
2212
|
|
|
2213
2213
|
attributes_view = Attributes.as_view('attributes')
|
|
@@ -823,7 +823,7 @@ class RuleAnalysis(ErrorHandlingMethodView):
|
|
|
823
823
|
return Response(render_json(**analysis), content_type='application/json')
|
|
824
824
|
|
|
825
825
|
|
|
826
|
-
def blueprint():
|
|
826
|
+
def blueprint() -> AuthenticatedBlueprint:
|
|
827
827
|
bp = AuthenticatedBlueprint('rules', __name__, url_prefix='/rules')
|
|
828
828
|
|
|
829
829
|
rule_view = Rule.as_view('rule')
|