rucio 37.7.1__py3-none-any.whl → 38.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 might be problematic. Click here for more details.

Files changed (130) hide show
  1. rucio/alembicrevision.py +1 -1
  2. rucio/cli/bin_legacy/rucio.py +51 -107
  3. rucio/cli/bin_legacy/rucio_admin.py +26 -26
  4. rucio/cli/command.py +1 -0
  5. rucio/cli/did.py +2 -2
  6. rucio/cli/opendata.py +132 -0
  7. rucio/cli/replica.py +15 -5
  8. rucio/cli/rule.py +7 -2
  9. rucio/cli/scope.py +3 -2
  10. rucio/cli/utils.py +28 -4
  11. rucio/client/baseclient.py +9 -1
  12. rucio/client/client.py +2 -0
  13. rucio/client/diracclient.py +73 -12
  14. rucio/client/opendataclient.py +249 -0
  15. rucio/client/subscriptionclient.py +30 -0
  16. rucio/client/uploadclient.py +10 -13
  17. rucio/common/constants.py +4 -1
  18. rucio/common/exception.py +55 -0
  19. rucio/common/plugins.py +45 -8
  20. rucio/common/schema/generic.py +5 -3
  21. rucio/common/schema/generic_multi_vo.py +4 -2
  22. rucio/common/types.py +8 -7
  23. rucio/common/utils.py +176 -11
  24. rucio/core/dirac.py +5 -5
  25. rucio/core/opendata.py +744 -0
  26. rucio/core/rule.py +63 -8
  27. rucio/core/transfer.py +1 -1
  28. rucio/daemons/common.py +1 -1
  29. rucio/daemons/conveyor/finisher.py +2 -2
  30. rucio/daemons/conveyor/poller.py +2 -2
  31. rucio/daemons/conveyor/preparer.py +1 -1
  32. rucio/daemons/conveyor/submitter.py +2 -2
  33. rucio/daemons/conveyor/throttler.py +1 -1
  34. rucio/db/sqla/constants.py +6 -0
  35. rucio/db/sqla/migrate_repo/versions/a62db546a1f1_opendata_initial_model.py +85 -0
  36. rucio/db/sqla/models.py +69 -0
  37. rucio/db/sqla/session.py +8 -1
  38. rucio/db/sqla/util.py +2 -2
  39. rucio/gateway/dirac.py +1 -1
  40. rucio/gateway/opendata.py +190 -0
  41. rucio/gateway/subscription.py +5 -3
  42. rucio/rse/protocols/protocol.py +9 -5
  43. rucio/rse/translation.py +17 -6
  44. rucio/tests/common.py +64 -12
  45. rucio/transfertool/fts3.py +1 -0
  46. rucio/transfertool/fts3_plugins.py +6 -1
  47. rucio/vcsversion.py +4 -4
  48. rucio/web/rest/flaskapi/v1/auth.py +11 -2
  49. rucio/web/rest/flaskapi/v1/common.py +34 -14
  50. rucio/web/rest/flaskapi/v1/config.py +1 -1
  51. rucio/web/rest/flaskapi/v1/dids.py +447 -160
  52. rucio/web/rest/flaskapi/v1/heartbeats.py +1 -1
  53. rucio/web/rest/flaskapi/v1/identities.py +1 -1
  54. rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +1 -1
  55. rucio/web/rest/flaskapi/v1/locks.py +1 -1
  56. rucio/web/rest/flaskapi/v1/main.py +3 -7
  57. rucio/web/rest/flaskapi/v1/meta_conventions.py +1 -16
  58. rucio/web/rest/flaskapi/v1/nongrid_traces.py +1 -1
  59. rucio/web/rest/flaskapi/v1/opendata.py +391 -0
  60. rucio/web/rest/flaskapi/v1/opendata_public.py +146 -0
  61. rucio/web/rest/flaskapi/v1/requests.py +1 -1
  62. rucio/web/rest/flaskapi/v1/rses.py +1 -1
  63. rucio/web/rest/flaskapi/v1/rules.py +1 -1
  64. rucio/web/rest/flaskapi/v1/scopes.py +1 -1
  65. rucio/web/rest/flaskapi/v1/subscriptions.py +6 -9
  66. rucio/web/rest/flaskapi/v1/traces.py +1 -1
  67. rucio/web/rest/flaskapi/v1/vos.py +1 -1
  68. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/alembic.ini.template +1 -1
  69. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/alembic_offline.ini.template +1 -1
  70. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/rucio.cfg.template +2 -2
  71. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +3 -3
  72. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/requirements.server.txt +6 -3
  73. rucio-38.0.0.data/data/rucio/tools/reset_database.py +87 -0
  74. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio +2 -1
  75. {rucio-37.7.1.dist-info → rucio-38.0.0.dist-info}/METADATA +37 -36
  76. {rucio-37.7.1.dist-info → rucio-38.0.0.dist-info}/RECORD +128 -122
  77. {rucio-37.7.1.dist-info → rucio-38.0.0.dist-info}/licenses/AUTHORS.rst +1 -0
  78. rucio/client/fileclient.py +0 -57
  79. rucio-37.7.1.data/data/rucio/tools/reset_database.py +0 -40
  80. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
  81. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
  82. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
  83. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
  84. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
  85. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
  86. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
  87. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
  88. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
  89. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
  90. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/tools/bootstrap.py +0 -0
  91. {rucio-37.7.1.data → rucio-38.0.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
  92. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-abacus-account +0 -0
  93. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-abacus-collection-replica +0 -0
  94. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-abacus-rse +0 -0
  95. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-admin +0 -0
  96. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-atropos +0 -0
  97. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-auditor +0 -0
  98. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-automatix +0 -0
  99. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-bb8 +0 -0
  100. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-cache-client +0 -0
  101. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-cache-consumer +0 -0
  102. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-conveyor-finisher +0 -0
  103. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-conveyor-poller +0 -0
  104. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-conveyor-preparer +0 -0
  105. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-conveyor-receiver +0 -0
  106. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-conveyor-stager +0 -0
  107. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-conveyor-submitter +0 -0
  108. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-conveyor-throttler +0 -0
  109. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-dark-reaper +0 -0
  110. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-dumper +0 -0
  111. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-follower +0 -0
  112. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-hermes +0 -0
  113. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-judge-cleaner +0 -0
  114. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-judge-evaluator +0 -0
  115. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-judge-injector +0 -0
  116. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-judge-repairer +0 -0
  117. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-kronos +0 -0
  118. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-minos +0 -0
  119. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
  120. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-necromancer +0 -0
  121. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-oauth-manager +0 -0
  122. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-reaper +0 -0
  123. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-replica-recoverer +0 -0
  124. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-rse-decommissioner +0 -0
  125. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-storage-consistency-actions +0 -0
  126. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-transmogrifier +0 -0
  127. {rucio-37.7.1.data → rucio-38.0.0.data}/scripts/rucio-undertaker +0 -0
  128. {rucio-37.7.1.dist-info → rucio-38.0.0.dist-info}/WHEEL +0 -0
  129. {rucio-37.7.1.dist-info → rucio-38.0.0.dist-info}/licenses/LICENSE +0 -0
  130. {rucio-37.7.1.dist-info → rucio-38.0.0.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,16 @@ DEFAULT_ENDPOINTS = {
50
49
  'rules',
51
50
  'scopes',
52
51
  'subscriptions',
52
+ 'opendata',
53
+ 'opendata_public',
53
54
  }
54
55
 
55
56
 
56
57
  def apply_endpoints(app: Flask, modules: "Iterable[str]") -> None:
57
58
  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
59
  try:
63
60
  # searches for module names locally
64
- blueprint_module = importlib.import_module('.' + blueprint_module,
65
- package='rucio.web.rest.flaskapi.v1')
61
+ blueprint_module = importlib.import_module('.' + blueprint_module, package='rucio.web.rest.flaskapi.v1')
66
62
  except ImportError:
67
63
  raise ConfigurationError(f'Could not load "{blueprint_module}" provided in the endpoints configuration value')
68
64
 
@@ -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')