rucio 38.2.0__py3-none-any.whl → 38.4.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 (142) hide show
  1. rucio/cli/bin_legacy/rucio.py +26 -23
  2. rucio/cli/command.py +36 -26
  3. rucio/cli/config.py +22 -7
  4. rucio/cli/did.py +2 -2
  5. rucio/cli/download.py +1 -1
  6. rucio/cli/opendata.py +78 -10
  7. rucio/cli/utils.py +13 -1
  8. rucio/client/accountclient.py +20 -19
  9. rucio/client/accountlimitclient.py +5 -4
  10. rucio/client/baseclient.py +25 -25
  11. rucio/client/configclient.py +29 -5
  12. rucio/client/credentialclient.py +2 -1
  13. rucio/client/didclient.py +33 -32
  14. rucio/client/diracclient.py +2 -1
  15. rucio/client/exportclient.py +2 -1
  16. rucio/client/importclient.py +2 -1
  17. rucio/client/lifetimeclient.py +3 -2
  18. rucio/client/lockclient.py +4 -3
  19. rucio/client/metaconventionsclient.py +5 -4
  20. rucio/client/opendataclient.py +8 -7
  21. rucio/client/pingclient.py +2 -1
  22. rucio/client/replicaclient.py +27 -26
  23. rucio/client/requestclient.py +8 -8
  24. rucio/client/richclient.py +6 -0
  25. rucio/client/rseclient.py +31 -28
  26. rucio/client/ruleclient.py +13 -12
  27. rucio/client/scopeclient.py +4 -3
  28. rucio/client/subscriptionclient.py +6 -5
  29. rucio/common/constants.py +23 -0
  30. rucio/common/exception.py +30 -0
  31. rucio/common/plugins.py +33 -15
  32. rucio/common/utils.py +3 -3
  33. rucio/core/config.py +8 -6
  34. rucio/core/credential.py +19 -26
  35. rucio/core/did.py +1 -1
  36. rucio/core/did_meta_plugins/did_column_meta.py +226 -69
  37. rucio/core/opendata.py +150 -8
  38. rucio/core/replica.py +3 -4
  39. rucio/core/request.py +1 -1
  40. rucio/core/rule.py +6 -3
  41. rucio/core/rule_grouping.py +5 -5
  42. rucio/gateway/account.py +8 -7
  43. rucio/gateway/config.py +2 -37
  44. rucio/gateway/opendata.py +2 -2
  45. rucio/gateway/request.py +2 -117
  46. rucio/gateway/rule.py +2 -2
  47. rucio/rse/protocols/webdav.py +5 -2
  48. rucio/rse/translation.py +3 -3
  49. rucio/transfertool/fts3.py +0 -19
  50. rucio/transfertool/fts3_plugins.py +3 -3
  51. rucio/vcsversion.py +3 -3
  52. rucio/web/rest/flaskapi/v1/accountlimits.py +4 -3
  53. rucio/web/rest/flaskapi/v1/accounts.py +26 -25
  54. rucio/web/rest/flaskapi/v1/archives.py +2 -2
  55. rucio/web/rest/flaskapi/v1/auth.py +15 -14
  56. rucio/web/rest/flaskapi/v1/common.py +4 -4
  57. rucio/web/rest/flaskapi/v1/config.py +57 -17
  58. rucio/web/rest/flaskapi/v1/credentials.py +3 -3
  59. rucio/web/rest/flaskapi/v1/dids.py +25 -24
  60. rucio/web/rest/flaskapi/v1/dirac.py +3 -2
  61. rucio/web/rest/flaskapi/v1/export.py +4 -2
  62. rucio/web/rest/flaskapi/v1/heartbeats.py +2 -1
  63. rucio/web/rest/flaskapi/v1/identities.py +5 -4
  64. rucio/web/rest/flaskapi/v1/import.py +3 -2
  65. rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +3 -2
  66. rucio/web/rest/flaskapi/v1/locks.py +4 -3
  67. rucio/web/rest/flaskapi/v1/meta_conventions.py +4 -3
  68. rucio/web/rest/flaskapi/v1/metrics.py +2 -1
  69. rucio/web/rest/flaskapi/v1/nongrid_traces.py +2 -1
  70. rucio/web/rest/flaskapi/v1/opendata.py +7 -6
  71. rucio/web/rest/flaskapi/v1/opendata_public.py +6 -5
  72. rucio/web/rest/flaskapi/v1/ping.py +3 -2
  73. rucio/web/rest/flaskapi/v1/redirect.py +4 -3
  74. rucio/web/rest/flaskapi/v1/replicas.py +31 -31
  75. rucio/web/rest/flaskapi/v1/requests.py +7 -7
  76. rucio/web/rest/flaskapi/v1/rses.py +23 -16
  77. rucio/web/rest/flaskapi/v1/rules.py +9 -8
  78. rucio/web/rest/flaskapi/v1/scopes.py +4 -3
  79. rucio/web/rest/flaskapi/v1/subscriptions.py +9 -8
  80. rucio/web/rest/flaskapi/v1/traces.py +2 -1
  81. rucio/web/rest/flaskapi/v1/vos.py +4 -3
  82. {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/METADATA +1 -1
  83. {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/RECORD +142 -142
  84. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/alembic.ini.template +0 -0
  85. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
  86. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
  87. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
  88. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
  89. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
  90. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
  91. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
  92. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
  93. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
  94. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
  95. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
  96. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/rucio.cfg.template +0 -0
  97. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -0
  98. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/requirements.server.txt +0 -0
  99. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/tools/bootstrap.py +0 -0
  100. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
  101. {rucio-38.2.0.data → rucio-38.4.0.data}/data/rucio/tools/reset_database.py +0 -0
  102. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio +0 -0
  103. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-abacus-account +0 -0
  104. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-abacus-collection-replica +0 -0
  105. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-abacus-rse +0 -0
  106. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-admin +0 -0
  107. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-atropos +0 -0
  108. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-auditor +0 -0
  109. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-automatix +0 -0
  110. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-bb8 +0 -0
  111. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-cache-client +0 -0
  112. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-cache-consumer +0 -0
  113. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-finisher +0 -0
  114. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-poller +0 -0
  115. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-preparer +0 -0
  116. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-receiver +0 -0
  117. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-stager +0 -0
  118. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-submitter +0 -0
  119. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-conveyor-throttler +0 -0
  120. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-dark-reaper +0 -0
  121. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-dumper +0 -0
  122. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-follower +0 -0
  123. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-hermes +0 -0
  124. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-judge-cleaner +0 -0
  125. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-judge-evaluator +0 -0
  126. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-judge-injector +0 -0
  127. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-judge-repairer +0 -0
  128. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-kronos +0 -0
  129. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-minos +0 -0
  130. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
  131. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-necromancer +0 -0
  132. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-oauth-manager +0 -0
  133. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-reaper +0 -0
  134. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-replica-recoverer +0 -0
  135. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-rse-decommissioner +0 -0
  136. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-storage-consistency-actions +0 -0
  137. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-transmogrifier +0 -0
  138. {rucio-38.2.0.data → rucio-38.4.0.data}/scripts/rucio-undertaker +0 -0
  139. {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/WHEEL +0 -0
  140. {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
  141. {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/licenses/LICENSE +0 -0
  142. {rucio-38.2.0.dist-info → rucio-38.4.0.dist-info}/top_level.txt +0 -0
rucio/gateway/rule.py CHANGED
@@ -15,7 +15,7 @@
15
15
  from typing import TYPE_CHECKING, Any, Literal, Optional
16
16
 
17
17
  from rucio.common.config import config_get_bool
18
- from rucio.common.constants import DEFAULT_VO
18
+ from rucio.common.constants import DEFAULT_ACTIVITY, DEFAULT_VO
19
19
  from rucio.common.exception import AccessDenied
20
20
  from rucio.common.schema import validate_schema
21
21
  from rucio.common.types import InternalAccount, InternalScope
@@ -97,7 +97,7 @@ def add_replication_rule(
97
97
  account = issuer
98
98
 
99
99
  if activity is None:
100
- activity = 'User Subscriptions'
100
+ activity = DEFAULT_ACTIVITY
101
101
 
102
102
  kwargs = {'dids': dids, 'copies': copies, 'rse_expression': rse_expression, 'weight': weight, 'lifetime': lifetime,
103
103
  'grouping': grouping, 'account': account, 'locked': locked, 'subscription_id': subscription_id,
@@ -25,6 +25,7 @@ from requests.adapters import HTTPAdapter
25
25
  from urllib3.poolmanager import PoolManager
26
26
 
27
27
  from rucio.common import exception
28
+ from rucio.common.constants import HTTPMethod
28
29
  from rucio.rse.protocols import protocol
29
30
 
30
31
 
@@ -259,9 +260,11 @@ class Default(protocol.RSEProtocol):
259
260
  try:
260
261
  # use GET instead of HEAD for presigned urls
261
262
  if not using_presigned_urls:
262
- result = self.session.request('HEAD', path, verify=False, timeout=self.timeout, cert=self.cert)
263
+ result = self.session.request(HTTPMethod.HEAD.value, path, verify=False, timeout=self.timeout,
264
+ cert=self.cert)
263
265
  else:
264
- result = self.session.request('GET', path, verify=False, timeout=self.timeout, cert=self.cert)
266
+ result = self.session.request(HTTPMethod.GET.value, path, verify=False, timeout=self.timeout,
267
+ cert=self.cert)
265
268
  if result.status_code == 200:
266
269
  return True
267
270
  elif result.status_code in [401, ]:
rucio/rse/translation.py CHANGED
@@ -18,7 +18,7 @@ from configparser import NoOptionError, NoSectionError
18
18
  from typing import TYPE_CHECKING, Any, Optional
19
19
 
20
20
  from rucio.common import config
21
- from rucio.common.constants import DEFAULT_VO, RseAttr
21
+ from rucio.common.constants import DEFAULT_VO, POLICY_ALGORITHM_TYPES_LITERAL, RseAttr
22
22
  from rucio.common.exception import ConfigNotFound
23
23
  from rucio.common.plugins import PolicyPackageAlgorithms
24
24
 
@@ -33,7 +33,7 @@ class RSEDeterministicScopeTranslation(PolicyPackageAlgorithms):
33
33
  Translates a pfn dictionary into a scope and name
34
34
  """
35
35
 
36
- _algorithm_type = "pfn2lfn"
36
+ _algorithm_type: POLICY_ALGORITHM_TYPES_LITERAL = "pfn2lfn"
37
37
 
38
38
  def __init__(self, vo: str = DEFAULT_VO):
39
39
  super().__init__()
@@ -111,7 +111,7 @@ class RSEDeterministicTranslation(PolicyPackageAlgorithms):
111
111
  """
112
112
 
113
113
  _DEFAULT_LFN2PFN = "hash"
114
- _algorithm_type = "lfn2pfn"
114
+ _algorithm_type: POLICY_ALGORITHM_TYPES_LITERAL = "lfn2pfn"
115
115
 
116
116
  def __init__(
117
117
  self,
@@ -76,7 +76,6 @@ BULK_QUERY_COUNTER = METRICS.counter(name='{host}.bulk_query.{state}',
76
76
  QUERY_DETAILS_COUNTER = METRICS.counter(name='{host}.query_details.{state}',
77
77
  documentation='Number of detailed status queries', labelnames=('state', 'host'))
78
78
 
79
- REWRITE_HTTPS_TO_DAVS = config_get_bool('transfers', 'rewrite_https_to_davs', default=False)
80
79
  VO_CERTS_PATH = config_get('conveyor', 'vo_certs_path', False, None)
81
80
 
82
81
  # https://fts3-docs.web.cern.ch/fts3-docs/docs/state_machine.html
@@ -1073,24 +1072,6 @@ class FTS3Transfertool(Transfertool):
1073
1072
  if not transfer_file['sources'] or transfer_file['sources'] == []:
1074
1073
  raise Exception('No sources defined')
1075
1074
 
1076
- # TODO: remove the following logic in rucio 1.31
1077
- if REWRITE_HTTPS_TO_DAVS:
1078
- new_src_urls = []
1079
- new_dst_urls = []
1080
- for url in transfer_file['sources']:
1081
- if url.startswith('https'):
1082
- new_src_urls.append(':'.join(['davs'] + url.split(':')[1:]))
1083
- else:
1084
- new_src_urls.append(url)
1085
- for url in transfer_file['destinations']:
1086
- if url.startswith('https'):
1087
- new_dst_urls.append(':'.join(['davs'] + url.split(':')[1:]))
1088
- else:
1089
- new_dst_urls.append(url)
1090
-
1091
- transfer_file['sources'] = new_src_urls
1092
- transfer_file['destinations'] = new_dst_urls
1093
-
1094
1075
  transfer_id = None
1095
1076
  expected_transfer_id = None
1096
1077
  if self.deterministic_id:
@@ -17,7 +17,7 @@ import sys
17
17
  from typing import TYPE_CHECKING, Any, Optional, TypeVar
18
18
 
19
19
  from rucio.common.config import config_get_int
20
- from rucio.common.constants import DEFAULT_VO
20
+ from rucio.common.constants import DEFAULT_VO, POLICY_ALGORITHM_TYPES_LITERAL
21
21
  from rucio.common.exception import InvalidRequest
22
22
  from rucio.common.plugins import PolicyPackageAlgorithms
23
23
 
@@ -33,8 +33,8 @@ class FTS3TapeMetadataPlugin(PolicyPackageAlgorithms):
33
33
  Plugins are registered during initialization and called during a transfer with FTS3
34
34
  """
35
35
 
36
- ALGORITHM_NAME = "fts3_tape_metadata_plugins"
37
- _INIT_FUNC_NAME = "fts3_plugins_init"
36
+ ALGORITHM_NAME: POLICY_ALGORITHM_TYPES_LITERAL = "fts3_tape_metadata_plugins"
37
+ _INIT_FUNC_NAME: POLICY_ALGORITHM_TYPES_LITERAL = "fts3_plugins_init"
38
38
  DEFAULT = "def"
39
39
 
40
40
  def __init__(self, policy_algorithm: str) -> None:
rucio/vcsversion.py CHANGED
@@ -4,8 +4,8 @@ This file is automatically generated; Do not edit it. :)
4
4
  '''
5
5
  VERSION_INFO = {
6
6
  'final': True,
7
- 'version': '38.2.0',
7
+ 'version': '38.4.0',
8
8
  'branch_nick': 'release-38-LTS',
9
- 'revision_id': 'af0cedfc38c83fa06e4b82991d0c04a1129440a9',
10
- 'revno': 13963
9
+ 'revision_id': '945ab71be90243fe96148bb3bd13c1c3ae410765',
10
+ 'revno': 14030
11
11
  }
@@ -16,6 +16,7 @@ from typing import TYPE_CHECKING
16
16
 
17
17
  from flask import Flask, request
18
18
 
19
+ from rucio.common.constants import HTTPMethod
19
20
  from rucio.common.exception import AccessDenied, AccountNotFound, RSENotFound
20
21
  from rucio.gateway.account_limit import delete_global_account_limit, delete_local_account_limit, set_global_account_limit, set_local_account_limit
21
22
  from rucio.web.rest.flaskapi.authenticated_bp import AuthenticatedBlueprint
@@ -219,11 +220,11 @@ def blueprint(with_doc: bool = False) -> AuthenticatedBlueprint:
219
220
  bp = AuthenticatedBlueprint('accountlimits', __name__, url_prefix='/accountlimits')
220
221
 
221
222
  local_account_limit_view = LocalAccountLimit.as_view('local_account_limit')
222
- bp.add_url_rule('/local/<account>/<rse>', view_func=local_account_limit_view, methods=['post', 'delete'])
223
+ bp.add_url_rule('/local/<account>/<rse>', view_func=local_account_limit_view, methods=[HTTPMethod.POST.value, HTTPMethod.DELETE.value])
223
224
  if not with_doc:
224
- bp.add_url_rule('/<account>/<rse>', view_func=local_account_limit_view, methods=['post', 'delete'])
225
+ bp.add_url_rule('/<account>/<rse>', view_func=local_account_limit_view, methods=[HTTPMethod.POST.value, HTTPMethod.DELETE.value])
225
226
  global_account_limit_view = GlobalAccountLimit.as_view('global_account_limit')
226
- bp.add_url_rule('/global/<account>/<rse_expression>', view_func=global_account_limit_view, methods=['post', 'delete'])
227
+ bp.add_url_rule('/global/<account>/<rse_expression>', view_func=global_account_limit_view, methods=[HTTPMethod.POST.value, HTTPMethod.DELETE.value])
227
228
 
228
229
  bp.after_request(response_headers)
229
230
  return bp
@@ -18,7 +18,8 @@ from typing import TYPE_CHECKING, Any, Literal, Optional, Union
18
18
 
19
19
  from flask import Flask, Response, jsonify, redirect, request
20
20
 
21
- from rucio.common.exception import AccessDenied, AccountNotFound, CounterNotFound, Duplicate, IdentityError, InvalidObject, RSENotFound, RuleNotFound, ScopeNotFound
21
+ from rucio.common.constants import HTTPMethod
22
+ from rucio.common.exception import AccessDenied, AccountNotFound, CounterNotFound, Duplicate, IdentityError, InvalidAccountType, InvalidObject, RSENotFound, RuleNotFound, ScopeNotFound
22
23
  from rucio.common.utils import APIEncoder, render_json
23
24
  from rucio.gateway.account import add_account, add_account_attribute, del_account, del_account_attribute, get_account_info, get_usage_history, list_account_attributes, list_accounts, list_identities, update_account
24
25
  from rucio.gateway.account_limit import get_global_account_limit, get_global_account_usage, get_local_account_limit, get_local_account_usage
@@ -447,7 +448,7 @@ class AccountParameter(ErrorHandlingMethodView):
447
448
  return generate_http_error_flask(409, error)
448
449
  except AccessDenied as error:
449
450
  return generate_http_error_flask(401, error)
450
- except InvalidObject as error:
451
+ except (InvalidObject, InvalidAccountType) as error:
451
452
  return generate_http_error_flask(400, error)
452
453
 
453
454
  return 'Created', 201
@@ -1047,44 +1048,44 @@ def blueprint(with_doc: bool = False) -> AuthenticatedBlueprint:
1047
1048
  bp = AuthenticatedBlueprint('accounts', __name__, url_prefix='/accounts')
1048
1049
 
1049
1050
  attributes_view = Attributes.as_view('attributes')
1050
- bp.add_url_rule('/<account>/attr/', view_func=attributes_view, methods=['get', ])
1051
- bp.add_url_rule('/<account>/attr/<key>', view_func=attributes_view, methods=['post', 'delete'])
1051
+ bp.add_url_rule('/<account>/attr/', view_func=attributes_view, methods=[HTTPMethod.GET.value])
1052
+ bp.add_url_rule('/<account>/attr/<key>', view_func=attributes_view, methods=[HTTPMethod.POST.value, HTTPMethod.DELETE.value])
1052
1053
  scopes_view = Scopes.as_view('scopes')
1053
- bp.add_url_rule('/<account>/scopes/', view_func=scopes_view, methods=['get', ])
1054
- bp.add_url_rule('/<account>/scopes/<scope>', view_func=scopes_view, methods=['post', ])
1054
+ bp.add_url_rule('/<account>/scopes/', view_func=scopes_view, methods=[HTTPMethod.GET.value])
1055
+ bp.add_url_rule('/<account>/scopes/<scope>', view_func=scopes_view, methods=[HTTPMethod.POST.value])
1055
1056
  local_account_limits_view = LocalAccountLimits.as_view('local_account_limit')
1056
- bp.add_url_rule('/<account>/limits/local', view_func=local_account_limits_view, methods=['get', ])
1057
- bp.add_url_rule('/<account>/limits', view_func=local_account_limits_view, methods=['get', ])
1058
- bp.add_url_rule('/<account>/limits/local/<rse>', view_func=local_account_limits_view, methods=['get', ])
1059
- bp.add_url_rule('/<account>/limits/<rse>', view_func=local_account_limits_view, methods=['get', ])
1057
+ bp.add_url_rule('/<account>/limits/local', view_func=local_account_limits_view, methods=[HTTPMethod.GET.value])
1058
+ bp.add_url_rule('/<account>/limits', view_func=local_account_limits_view, methods=[HTTPMethod.GET.value])
1059
+ bp.add_url_rule('/<account>/limits/local/<rse>', view_func=local_account_limits_view, methods=[HTTPMethod.GET.value])
1060
+ bp.add_url_rule('/<account>/limits/<rse>', view_func=local_account_limits_view, methods=[HTTPMethod.GET.value])
1060
1061
  global_account_limits_view = GlobalAccountLimits.as_view('global_account_limit')
1061
- bp.add_url_rule('/<account>/limits/global', view_func=global_account_limits_view, methods=['get', ])
1062
- bp.add_url_rule('/<account>/limits/global/<rse_expression>', view_func=global_account_limits_view, methods=['get', ])
1062
+ bp.add_url_rule('/<account>/limits/global', view_func=global_account_limits_view, methods=[HTTPMethod.GET.value])
1063
+ bp.add_url_rule('/<account>/limits/global/<rse_expression>', view_func=global_account_limits_view, methods=[HTTPMethod.GET.value])
1063
1064
  identities_view = Identities.as_view('identities')
1064
- bp.add_url_rule('/<account>/identities', view_func=identities_view, methods=['get', 'post', 'delete'])
1065
+ bp.add_url_rule('/<account>/identities', view_func=identities_view, methods=[HTTPMethod.GET.value, HTTPMethod.POST.value, HTTPMethod.DELETE.value])
1065
1066
  rules_view = Rules.as_view('rules')
1066
- bp.add_url_rule('/<account>/rules', view_func=rules_view, methods=['get', ])
1067
+ bp.add_url_rule('/<account>/rules', view_func=rules_view, methods=[HTTPMethod.GET.value])
1067
1068
  usagehistory_view = UsageHistory.as_view('usagehistory')
1068
- bp.add_url_rule('/<account>/usage/history/<rse>', view_func=usagehistory_view, methods=['get', ])
1069
+ bp.add_url_rule('/<account>/usage/history/<rse>', view_func=usagehistory_view, methods=[HTTPMethod.GET.value])
1069
1070
  usage_view = LocalUsage.as_view('usage')
1070
- bp.add_url_rule('/<account>/usage/local', view_func=usage_view, methods=['get', ])
1071
- bp.add_url_rule('/<account>/usage', view_func=usage_view, methods=['get', ])
1071
+ bp.add_url_rule('/<account>/usage/local', view_func=usage_view, methods=[HTTPMethod.GET.value])
1072
+ bp.add_url_rule('/<account>/usage', view_func=usage_view, methods=[HTTPMethod.GET.value])
1072
1073
  if not with_doc:
1073
1074
  # for backwards-compatibility
1074
1075
  # rule without trailing slash needs to be added before rule with trailing slash
1075
- bp.add_url_rule('/<account>/usage/', view_func=usage_view, methods=['get', ])
1076
- bp.add_url_rule('/<account>/usage/local/<rse>', view_func=usage_view, methods=['get', ])
1077
- bp.add_url_rule('/<account>/usage/<rse>', view_func=usage_view, methods=['get', ])
1076
+ bp.add_url_rule('/<account>/usage/', view_func=usage_view, methods=[HTTPMethod.GET.value])
1077
+ bp.add_url_rule('/<account>/usage/local/<rse>', view_func=usage_view, methods=[HTTPMethod.GET.value])
1078
+ bp.add_url_rule('/<account>/usage/<rse>', view_func=usage_view, methods=[HTTPMethod.GET.value])
1078
1079
  global_usage_view = GlobalUsage.as_view('global_usage')
1079
- bp.add_url_rule('/<account>/usage/global', view_func=global_usage_view, methods=['get', ])
1080
- bp.add_url_rule('/<account>/usage/global/<rse_expression>', view_func=global_usage_view, methods=['get', ])
1080
+ bp.add_url_rule('/<account>/usage/global', view_func=global_usage_view, methods=[HTTPMethod.GET.value])
1081
+ bp.add_url_rule('/<account>/usage/global/<rse_expression>', view_func=global_usage_view, methods=[HTTPMethod.GET.value])
1081
1082
  account_parameter_view = AccountParameter.as_view('account_parameter')
1082
- bp.add_url_rule('/<account>', view_func=account_parameter_view, methods=['get', 'put', 'post', 'delete'])
1083
+ bp.add_url_rule('/<account>', view_func=account_parameter_view, methods=[HTTPMethod.GET.value, HTTPMethod.PUT.value, HTTPMethod.POST.value, HTTPMethod.DELETE.value])
1083
1084
  account_view = Account.as_view('account')
1084
1085
  if not with_doc:
1085
1086
  # rule without trailing slash needs to be added before rule with trailing slash
1086
- bp.add_url_rule('', view_func=account_view, methods=['get', ])
1087
- bp.add_url_rule('/', view_func=account_view, methods=['get', ])
1087
+ bp.add_url_rule('', view_func=account_view, methods=[HTTPMethod.GET.value])
1088
+ bp.add_url_rule('/', view_func=account_view, methods=[HTTPMethod.GET.value])
1088
1089
 
1089
1090
  bp.after_request(response_headers)
1090
1091
  return bp
@@ -17,7 +17,7 @@ from typing import TYPE_CHECKING
17
17
 
18
18
  from flask import Flask, Response, request
19
19
 
20
- from rucio.common.constants import DEFAULT_VO
20
+ from rucio.common.constants import DEFAULT_VO, HTTPMethod
21
21
  from rucio.gateway.did import list_archive_content
22
22
  from rucio.web.rest.flaskapi.authenticated_bp import AuthenticatedBlueprint
23
23
  from rucio.web.rest.flaskapi.v1.common import ErrorHandlingMethodView, check_accept_header_wrapper_flask, generate_http_error_flask, parse_scope_name, response_headers, try_stream
@@ -90,7 +90,7 @@ def blueprint() -> AuthenticatedBlueprint:
90
90
  bp = AuthenticatedBlueprint('archives', __name__, url_prefix='/archives')
91
91
 
92
92
  archive_view = Archive.as_view('archive')
93
- bp.add_url_rule('/<path:scope_name>/files', view_func=archive_view, methods=['get', ])
93
+ bp.add_url_rule('/<path:scope_name>/files', view_func=archive_view, methods=[HTTPMethod.GET.value])
94
94
 
95
95
  bp.after_request(response_headers)
96
96
  return bp
@@ -23,6 +23,7 @@ from jinja2.exceptions import TemplateNotFound
23
23
  from werkzeug.datastructures import Headers
24
24
 
25
25
  from rucio.common.config import config_get
26
+ from rucio.common.constants import HTTPMethod
26
27
  from rucio.common.exception import AccessDenied, CannotAuthenticate, CannotAuthorize, ConfigurationError, IdentityError, IdentityNotFound, InvalidRequest
27
28
  from rucio.common.extra import import_extras
28
29
  from rucio.common.utils import date_to_str
@@ -1632,31 +1633,31 @@ def blueprint() -> Blueprint:
1632
1633
  bp = Blueprint('auth', __name__, url_prefix='/auth')
1633
1634
 
1634
1635
  user_pass_view = UserPass.as_view('user_pass')
1635
- bp.add_url_rule('/userpass', view_func=user_pass_view, methods=['get', 'options'])
1636
+ bp.add_url_rule('/userpass', view_func=user_pass_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1636
1637
  gss_view = GSS.as_view('gss')
1637
- bp.add_url_rule('/gss', view_func=gss_view, methods=['get', 'options'])
1638
+ bp.add_url_rule('/gss', view_func=gss_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1638
1639
  x509_view = x509.as_view('x509')
1639
- bp.add_url_rule('/x509', view_func=x509_view, methods=['get', 'options'])
1640
- bp.add_url_rule('/x509/webui', view_func=x509_view, methods=['get', 'options'])
1641
- bp.add_url_rule('/x509_proxy', view_func=x509_view, methods=['get', 'options'])
1640
+ bp.add_url_rule('/x509', view_func=x509_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1641
+ bp.add_url_rule('/x509/webui', view_func=x509_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1642
+ bp.add_url_rule('/x509_proxy', view_func=x509_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1642
1643
  ssh_view = SSH.as_view('ssh')
1643
- bp.add_url_rule('/ssh', view_func=ssh_view, methods=['get', 'options'])
1644
+ bp.add_url_rule('/ssh', view_func=ssh_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1644
1645
  ssh_challenge_token_view = SSHChallengeToken.as_view('ssh_challenge_token')
1645
- bp.add_url_rule('/ssh_challenge_token', view_func=ssh_challenge_token_view, methods=['get', 'options'])
1646
+ bp.add_url_rule('/ssh_challenge_token', view_func=ssh_challenge_token_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1646
1647
  saml_view = SAML.as_view('saml')
1647
- bp.add_url_rule('/saml', view_func=saml_view, methods=['get', 'post', 'options'])
1648
+ bp.add_url_rule('/saml', view_func=saml_view, methods=[HTTPMethod.GET.value, HTTPMethod.POST.value, HTTPMethod.OPTIONS.value])
1648
1649
  validate_view = Validate.as_view('validate')
1649
- bp.add_url_rule('/validate', view_func=validate_view, methods=['get', 'options'])
1650
+ bp.add_url_rule('/validate', view_func=validate_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1650
1651
  oidc_view = OIDC.as_view('oidc_view')
1651
- bp.add_url_rule('/oidc', view_func=oidc_view, methods=['get', 'options'])
1652
+ bp.add_url_rule('/oidc', view_func=oidc_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1652
1653
  token_oidc_view = TokenOIDC.as_view('token_oidc_view')
1653
- bp.add_url_rule('/oidc_token', view_func=token_oidc_view, methods=['get', 'options'])
1654
+ bp.add_url_rule('/oidc_token', view_func=token_oidc_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1654
1655
  code_oidc_view = CodeOIDC.as_view('code_oidc_view')
1655
- bp.add_url_rule('/oidc_code', view_func=code_oidc_view, methods=['get', 'options'])
1656
+ bp.add_url_rule('/oidc_code', view_func=code_oidc_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1656
1657
  redirect_oidc_view = RedirectOIDC.as_view('redirect_oidc_view')
1657
- bp.add_url_rule('/oidc_redirect', view_func=redirect_oidc_view, methods=['get', 'options'])
1658
+ bp.add_url_rule('/oidc_redirect', view_func=redirect_oidc_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1658
1659
  refresh_oidc_view = RefreshOIDC.as_view('refresh_oidc_view')
1659
- bp.add_url_rule('/oidc_refresh', view_func=refresh_oidc_view, methods=['get', 'options'])
1660
+ bp.add_url_rule('/oidc_refresh', view_func=refresh_oidc_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
1660
1661
 
1661
1662
  return bp
1662
1663
 
@@ -31,7 +31,7 @@ from werkzeug.exceptions import HTTPException
31
31
  from werkzeug.wrappers import Request, Response
32
32
 
33
33
  from rucio.common import config
34
- from rucio.common.constants import DEFAULT_VO
34
+ from rucio.common.constants import DEFAULT_VO, HTTPMethod
35
35
  from rucio.common.exception import CannotAuthenticate, DatabaseException, IdentityError, RucioException, UnsupportedRequestedContentType
36
36
  from rucio.common.schema import get_schema_value
37
37
  from rucio.common.utils import generate_uuid, render_json
@@ -66,7 +66,7 @@ class CORSMiddleware:
66
66
  def __call__(self, environ: 'WSGIEnvironment', start_response: 'StartResponse') -> 'Iterable[bytes]':
67
67
  request: Request = Request(environ)
68
68
 
69
- if request.environ.get('REQUEST_METHOD') == 'OPTIONS':
69
+ if request.environ.get('REQUEST_METHOD') == HTTPMethod.OPTIONS.value:
70
70
  try:
71
71
  webui_urls = config.config_get_list('webui', 'urls')
72
72
  except (NoOptionError, NoSectionError, RuntimeError) as error:
@@ -145,7 +145,7 @@ class ErrorHandlingMethodView(MethodView):
145
145
 
146
146
 
147
147
  def request_auth_env() -> Optional['ResponseReturnValue']:
148
- if flask.request.environ.get('REQUEST_METHOD') == 'OPTIONS':
148
+ if flask.request.environ.get('REQUEST_METHOD') == HTTPMethod.OPTIONS.value:
149
149
  return '', 200
150
150
 
151
151
  auth_token = flask.request.headers.get('X-Rucio-Auth-Token', default=None)
@@ -176,7 +176,7 @@ def response_headers(response: ResponseTypeVar) -> ResponseTypeVar:
176
176
  response.headers['Access-Control-Allow-Methods'] = '*'
177
177
  response.headers['Access-Control-Allow-Credentials'] = 'true'
178
178
 
179
- if flask.request.environ.get('REQUEST_METHOD') == 'GET':
179
+ if flask.request.environ.get('REQUEST_METHOD') == HTTPMethod.GET.value:
180
180
  response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
181
181
  response.headers['Cache-Control'] = 'post-check=0, pre-check=0'
182
182
  response.headers['Pragma'] = 'no-cache'
@@ -12,20 +12,26 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from typing import TYPE_CHECKING
16
+
15
17
  from flask import Flask, jsonify
16
18
  from flask import request as request
17
19
 
20
+ from rucio.common.constants import HTTPMethod
18
21
  from rucio.common.exception import AccessDenied, ConfigNotFound, ConfigurationError
19
22
  from rucio.gateway import config
20
23
  from rucio.web.rest.flaskapi.authenticated_bp import AuthenticatedBlueprint
21
24
  from rucio.web.rest.flaskapi.v1.common import ErrorHandlingMethodView, check_accept_header_wrapper_flask, generate_http_error_flask, json_parameters, response_headers
22
25
 
26
+ if TYPE_CHECKING:
27
+ from flask.typing import ResponseReturnValue
28
+
23
29
 
24
30
  class Config(ErrorHandlingMethodView):
25
31
  """ REST API for full configuration. """
26
32
 
27
33
  @check_accept_header_wrapper_flask(['application/json'])
28
- def get(self):
34
+ def get(self) -> 'ResponseReturnValue':
29
35
  """
30
36
  ---
31
37
  summary: List
@@ -53,7 +59,7 @@ class Config(ErrorHandlingMethodView):
53
59
 
54
60
  return jsonify(res), 200
55
61
 
56
- def post(self):
62
+ def post(self) -> 'ResponseReturnValue':
57
63
  """
58
64
  ---
59
65
  summary: Create
@@ -97,7 +103,7 @@ class Section(ErrorHandlingMethodView):
97
103
  """ REST API for the sections in the configuration. """
98
104
 
99
105
  @check_accept_header_wrapper_flask(['application/json'])
100
- def get(self, section):
106
+ def get(self, section: str) -> 'ResponseReturnValue':
101
107
  """
102
108
  ---
103
109
  summary: List Sections
@@ -136,25 +142,55 @@ class Section(ErrorHandlingMethodView):
136
142
  406:
137
143
  description: "Not acceptable"
138
144
  """
139
- res = {}
140
- for item in config.items(section, issuer=request.environ['issuer'], vo=request.environ['vo']):
141
- res[item[0]] = item[1]
145
+ if config.has_section(section, issuer=request.environ['issuer'], vo=request.environ['vo']):
146
+ res = {}
147
+ for item in config.items(section, issuer=request.environ['issuer'], vo=request.environ['vo']):
148
+ res[item[0]] = item[1]
149
+ return jsonify(res), 200
142
150
 
143
- if res == {}:
151
+ else:
144
152
  return generate_http_error_flask(
145
153
  status_code=404,
146
154
  exc=ConfigNotFound.__name__,
147
155
  exc_msg=f"No configuration found for section '{section}'"
148
156
  )
149
157
 
150
- return jsonify(res), 200
158
+ def delete(self, section: str) -> 'ResponseReturnValue':
159
+ """
160
+ ---
161
+ summary: Remove an existing section
162
+ tags:
163
+ - Config
164
+ parameters:
165
+ - name: section
166
+ in: path
167
+ description: "The section."
168
+ responses:
169
+ 200:
170
+ description: "OK"
171
+ 401:
172
+ description: "Invalid Auth Token"
173
+ 404:
174
+ description: "Config not found"
175
+ 406:
176
+ description: "Not acceptable"
177
+ """
178
+ if config.has_section(section, issuer=request.environ['issuer'], vo=request.environ['vo']):
179
+ config.remove_section(section, issuer=request.environ['issuer'], vo=request.environ['vo'])
180
+ return '', 200
181
+ else:
182
+ return generate_http_error_flask(
183
+ status_code=404,
184
+ exc=ConfigNotFound.__name__,
185
+ exc_msg=f"No configuration found for section '{section}'"
186
+ )
151
187
 
152
188
 
153
189
  class OptionGetDel(ErrorHandlingMethodView):
154
190
  """ REST API for reading or deleting the options in the configuration. """
155
191
 
156
192
  @check_accept_header_wrapper_flask(['application/json'])
157
- def get(self, section, option):
193
+ def get(self, section: str, option: str) -> 'ResponseReturnValue':
158
194
  """
159
195
  ---
160
196
  summary: Get option
@@ -197,7 +233,7 @@ class OptionGetDel(ErrorHandlingMethodView):
197
233
  except ConfigNotFound as error:
198
234
  return generate_http_error_flask(404, error, f"No configuration found for section '{section}' option '{option}'")
199
235
 
200
- def delete(self, section, option):
236
+ def delete(self, section: str, option: str) -> 'ResponseReturnValue':
201
237
  """
202
238
  ---
203
239
  summary: Delete option
@@ -223,14 +259,17 @@ class OptionGetDel(ErrorHandlingMethodView):
223
259
  401:
224
260
  description: "Invalid Auth Token"
225
261
  """
226
- config.remove_option(section=section, option=option, issuer=request.environ['issuer'], vo=request.environ['vo'])
227
- return '', 200
262
+ if config.has_option(section, option, issuer=request.environ['issuer'], vo=request.environ['vo']):
263
+ config.remove_option(section=section, option=option, issuer=request.environ['issuer'], vo=request.environ['vo'])
264
+ return '', 200
265
+ else:
266
+ return generate_http_error_flask(404, ConfigNotFound.__name__, f"No configuration found for section '{section}' option '{option}'")
228
267
 
229
268
 
230
269
  class OptionSet(ErrorHandlingMethodView):
231
270
  """ REST API for setting the options in the configuration. """
232
271
 
233
- def put(self, section, option, value):
272
+ def put(self, section: str, option: str, value: str) -> 'ResponseReturnValue':
234
273
  """
235
274
  ---
236
275
  summary: Create value
@@ -285,13 +324,14 @@ def blueprint() -> AuthenticatedBlueprint:
285
324
  bp = AuthenticatedBlueprint('config', __name__, url_prefix='/config')
286
325
 
287
326
  option_set_view = OptionSet.as_view('option_set')
288
- bp.add_url_rule('/<section>/<option>/<value>', view_func=option_set_view, methods=['put', ])
327
+ bp.add_url_rule('/<section>/<option>/<value>', view_func=option_set_view, methods=[HTTPMethod.PUT.value])
289
328
  option_get_del_view = OptionGetDel.as_view('option_get_del')
290
- bp.add_url_rule('/<section>/<option>', view_func=option_get_del_view, methods=['get', 'delete'])
329
+ bp.add_url_rule('/<section>/<option>', view_func=option_get_del_view, methods=[HTTPMethod.GET.value, HTTPMethod.DELETE.value])
291
330
  section_view = Section.as_view('section')
292
- bp.add_url_rule('/<section>', view_func=section_view, methods=['get', ])
331
+
332
+ bp.add_url_rule('/<section>', view_func=section_view, methods=[HTTPMethod.GET.value, HTTPMethod.DELETE.value])
293
333
  config_view = Config.as_view('config')
294
- bp.add_url_rule('', view_func=config_view, methods=['get', 'post'])
334
+ bp.add_url_rule('', view_func=config_view, methods=[HTTPMethod.GET.value, HTTPMethod.POST.value])
295
335
 
296
336
  bp.after_request(response_headers)
297
337
  return bp
@@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, cast
17
17
  from flask import Flask, request
18
18
  from werkzeug.datastructures import Headers
19
19
 
20
- from rucio.common.constants import RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS, RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL, SUPPORTED_SIGN_URL_SERVICES, SUPPORTED_SIGN_URL_SERVICES_LITERAL
20
+ from rucio.common.constants import RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS, RSE_BASE_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL, SUPPORTED_SIGN_URL_SERVICES, SUPPORTED_SIGN_URL_SERVICES_LITERAL, HTTPMethod
21
21
  from rucio.common.exception import CannotAuthenticate
22
22
  from rucio.gateway.credential import get_signed_url
23
23
  from rucio.web.rest.flaskapi.authenticated_bp import AuthenticatedBlueprint
@@ -203,10 +203,10 @@ def blueprint(with_doc=False):
203
203
  bp = AuthenticatedBlueprint('credentials', __name__, url_prefix='/credentials')
204
204
 
205
205
  signurl_view = SignURL.as_view('signurl')
206
- bp.add_url_rule('/signurl', view_func=signurl_view, methods=['get', 'options'])
206
+ bp.add_url_rule('/signurl', view_func=signurl_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
207
207
  if not with_doc:
208
208
  # yes, /signur ~= '/signurl?$'
209
- bp.add_url_rule('/signur', view_func=signurl_view, methods=['get', 'options'])
209
+ bp.add_url_rule('/signur', view_func=signurl_view, methods=[HTTPMethod.GET.value, HTTPMethod.OPTIONS.value])
210
210
 
211
211
  bp.after_request(response_headers)
212
212