rucio 37.2.0__py3-none-any.whl → 37.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 (127) hide show
  1. rucio/cli/rule.py +1 -1
  2. rucio/client/accountclient.py +205 -60
  3. rucio/client/accountlimitclient.py +84 -25
  4. rucio/client/baseclient.py +85 -48
  5. rucio/client/client.py +49 -41
  6. rucio/client/configclient.py +36 -13
  7. rucio/client/credentialclient.py +16 -6
  8. rucio/client/didclient.py +321 -133
  9. rucio/client/diracclient.py +13 -6
  10. rucio/client/downloadclient.py +435 -165
  11. rucio/client/exportclient.py +8 -2
  12. rucio/client/fileclient.py +10 -3
  13. rucio/client/importclient.py +4 -1
  14. rucio/client/lifetimeclient.py +48 -31
  15. rucio/client/lockclient.py +22 -7
  16. rucio/client/metaconventionsclient.py +59 -21
  17. rucio/client/pingclient.py +3 -1
  18. rucio/client/replicaclient.py +213 -96
  19. rucio/client/requestclient.py +123 -16
  20. rucio/client/rseclient.py +385 -160
  21. rucio/client/ruleclient.py +147 -51
  22. rucio/client/scopeclient.py +35 -10
  23. rucio/client/subscriptionclient.py +60 -27
  24. rucio/client/touchclient.py +16 -7
  25. rucio/common/plugins.py +1 -1
  26. rucio/core/did.py +2 -3
  27. rucio/core/permission/generic.py +37 -1
  28. rucio/core/replica.py +6 -6
  29. rucio/core/rule.py +5 -3
  30. rucio/daemons/judge/evaluator.py +1 -1
  31. rucio/db/sqla/util.py +1 -1
  32. rucio/gateway/authentication.py +58 -88
  33. rucio/gateway/config.py +63 -75
  34. rucio/gateway/did.py +245 -329
  35. rucio/gateway/dirac.py +33 -34
  36. rucio/gateway/exporter.py +27 -30
  37. rucio/gateway/importer.py +12 -14
  38. rucio/gateway/lifetime_exception.py +16 -24
  39. rucio/gateway/lock.py +27 -40
  40. rucio/gateway/replica.py +334 -249
  41. rucio/gateway/request.py +176 -103
  42. rucio/gateway/rse.py +191 -218
  43. rucio/gateway/rule.py +115 -146
  44. rucio/gateway/scope.py +18 -25
  45. rucio/gateway/subscription.py +90 -108
  46. rucio/gateway/trace.py +48 -0
  47. rucio/vcsversion.py +3 -3
  48. rucio/web/rest/flaskapi/v1/accounts.py +2 -2
  49. rucio/web/rest/flaskapi/v1/auth.py +15 -0
  50. rucio/web/rest/flaskapi/v1/common.py +3 -0
  51. rucio/web/rest/flaskapi/v1/config.py +7 -7
  52. rucio/web/rest/flaskapi/v1/dids.py +55 -55
  53. rucio/web/rest/flaskapi/v1/dirac.py +2 -2
  54. rucio/web/rest/flaskapi/v1/export.py +1 -1
  55. rucio/web/rest/flaskapi/v1/import.py +1 -1
  56. rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +5 -5
  57. rucio/web/rest/flaskapi/v1/locks.py +4 -4
  58. rucio/web/rest/flaskapi/v1/main.py +17 -10
  59. rucio/web/rest/flaskapi/v1/redirect.py +1 -1
  60. rucio/web/rest/flaskapi/v1/replicas.py +30 -29
  61. rucio/web/rest/flaskapi/v1/requests.py +211 -20
  62. rucio/web/rest/flaskapi/v1/rses.py +37 -37
  63. rucio/web/rest/flaskapi/v1/rules.py +15 -15
  64. rucio/web/rest/flaskapi/v1/scopes.py +3 -3
  65. rucio/web/rest/flaskapi/v1/subscriptions.py +9 -9
  66. rucio/web/rest/flaskapi/v1/traces.py +75 -77
  67. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio.cfg.template +0 -1
  68. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -1
  69. {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/METADATA +1 -1
  70. {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/RECORD +127 -126
  71. {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/WHEEL +1 -1
  72. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/alembic.ini.template +0 -0
  73. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
  74. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
  75. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
  76. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
  77. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
  78. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
  79. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
  80. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
  81. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
  82. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
  83. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
  84. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/requirements.server.txt +0 -0
  85. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/tools/bootstrap.py +0 -0
  86. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
  87. {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/tools/reset_database.py +0 -0
  88. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio +0 -0
  89. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-account +0 -0
  90. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-collection-replica +0 -0
  91. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-rse +0 -0
  92. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-admin +0 -0
  93. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-atropos +0 -0
  94. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-auditor +0 -0
  95. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-automatix +0 -0
  96. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-bb8 +0 -0
  97. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-cache-client +0 -0
  98. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-cache-consumer +0 -0
  99. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-finisher +0 -0
  100. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-poller +0 -0
  101. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-preparer +0 -0
  102. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-receiver +0 -0
  103. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-stager +0 -0
  104. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-submitter +0 -0
  105. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-throttler +0 -0
  106. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-dark-reaper +0 -0
  107. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-dumper +0 -0
  108. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-follower +0 -0
  109. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-hermes +0 -0
  110. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-cleaner +0 -0
  111. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-evaluator +0 -0
  112. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-injector +0 -0
  113. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-repairer +0 -0
  114. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-kronos +0 -0
  115. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-minos +0 -0
  116. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
  117. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-necromancer +0 -0
  118. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-oauth-manager +0 -0
  119. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-reaper +0 -0
  120. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-replica-recoverer +0 -0
  121. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-rse-decommissioner +0 -0
  122. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-storage-consistency-actions +0 -0
  123. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-transmogrifier +0 -0
  124. {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-undertaker +0 -0
  125. {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
  126. {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/licenses/LICENSE +0 -0
  127. {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/top_level.txt +0 -0
@@ -150,7 +150,7 @@ class Scope(ErrorHandlingMethodView):
150
150
  generate(
151
151
  name=request.args.get('name', default=None),
152
152
  recursive=recursive,
153
- vo=request.environ.get('vo')
153
+ vo=request.environ['vo']
154
154
  )
155
155
  )
156
156
  except DataIdentifierNotFound as error:
@@ -268,15 +268,15 @@ class Search(ErrorHandlingMethodView):
268
268
  filters[arg] = value
269
269
  filters = [filters]
270
270
 
271
- did_type = request.args.get('type', default=None)
272
- limit = request.args.get('limit', default=None)
271
+ did_type = request.args.get('type', default='collection')
272
+ limit = request.args.get('limit', type=int, default=None)
273
273
  long = request.args.get('long', type=['True', '1'].__contains__, default=False)
274
274
  recursive = request.args.get('recursive', type='True'.__eq__, default=False)
275
275
  try:
276
276
  def generate(vo):
277
277
  for did in list_dids(scope=scope, filters=filters, did_type=did_type, limit=limit, long=long, recursive=recursive, vo=vo):
278
278
  yield dumps(did) + '\n'
279
- return try_stream(generate(vo=request.environ.get('vo')))
279
+ return try_stream(generate(vo=request.environ['vo']))
280
280
  except UnsupportedOperation as error:
281
281
  return generate_http_error_flask(409, error)
282
282
  except KeyNotFound as error:
@@ -339,7 +339,7 @@ class BulkDIDS(ErrorHandlingMethodView):
339
339
  """
340
340
  dids = json_list()
341
341
  try:
342
- add_dids(dids=dids, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
342
+ add_dids(dids=dids, issuer=request.environ['issuer'], vo=request.environ['vo'])
343
343
  except DataIdentifierNotFound as error:
344
344
  return generate_http_error_flask(404, error)
345
345
  except (DuplicateContent, DataIdentifierAlreadyExists, UnsupportedOperation) as error:
@@ -462,7 +462,7 @@ class Attachments(ErrorHandlingMethodView):
462
462
  return generate_http_error_flask(406, exc="Invalid attachment format.")
463
463
 
464
464
  try:
465
- attach_dids_to_dids(attachments=attachments, ignore_duplicate=ignore_duplicate, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
465
+ attach_dids_to_dids(attachments=attachments, ignore_duplicate=ignore_duplicate, issuer=request.environ['issuer'], vo=request.environ['vo'])
466
466
  except DataIdentifierNotFound as error:
467
467
  return generate_http_error_flask(404, error)
468
468
  except (DuplicateContent, DataIdentifierAlreadyExists, UnsupportedOperation, FileAlreadyExists) as error:
@@ -577,7 +577,7 @@ class DIDs(ErrorHandlingMethodView):
577
577
  description: Not acceptable
578
578
  """
579
579
  try:
580
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
580
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
581
581
  dynamic_depth = None
582
582
  if 'dynamic_depth' in request.args:
583
583
  orig = request.args['dynamic_depth'].upper()
@@ -589,7 +589,7 @@ class DIDs(ErrorHandlingMethodView):
589
589
  dynamic_depth = None
590
590
  elif 'dynamic' in request.args:
591
591
  dynamic_depth = DIDType.FILE
592
- did = get_did(scope=scope, name=name, dynamic_depth=dynamic_depth, vo=request.environ.get('vo'))
592
+ did = get_did(scope=scope, name=name, dynamic_depth=dynamic_depth, vo=request.environ['vo'])
593
593
  return Response(render_json(**did), content_type='application/json')
594
594
  except ValueError as error:
595
595
  return generate_http_error_flask(400, error)
@@ -668,7 +668,7 @@ class DIDs(ErrorHandlingMethodView):
668
668
  description: Did already exists
669
669
  """
670
670
  try:
671
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
671
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
672
672
  except ValueError as error:
673
673
  return generate_http_error_flask(400, error)
674
674
 
@@ -686,8 +686,8 @@ class DIDs(ErrorHandlingMethodView):
686
686
  lifetime=param_get(parameters, 'lifetime', default=None),
687
687
  dids=param_get(parameters, 'dids', default=[]),
688
688
  rse=param_get(parameters, 'rse', default=None),
689
- issuer=request.environ.get('issuer'),
690
- vo=request.environ.get('vo'),
689
+ issuer=request.environ['issuer'],
690
+ vo=request.environ['vo'],
691
691
  )
692
692
  except (InvalidObject, InvalidPath) as error:
693
693
  return generate_http_error_flask(400, error)
@@ -743,14 +743,14 @@ class DIDs(ErrorHandlingMethodView):
743
743
  description: Wrong status
744
744
  """
745
745
  try:
746
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
746
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
747
747
  except ValueError as error:
748
748
  return generate_http_error_flask(400, error)
749
749
 
750
750
  parameters = json_parameters()
751
751
 
752
752
  try:
753
- set_status(scope=scope, name=name, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'), **parameters)
753
+ set_status(scope=scope, name=name, issuer=request.environ['issuer'], vo=request.environ['vo'], **parameters)
754
754
  except DataIdentifierNotFound as error:
755
755
  return generate_http_error_flask(404, error)
756
756
  except (UnsupportedStatus, UnsupportedOperation) as error:
@@ -822,13 +822,13 @@ class Attachment(ErrorHandlingMethodView):
822
822
  description: Not acceptable
823
823
  """
824
824
  try:
825
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
825
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
826
826
 
827
827
  def generate(vo):
828
828
  for did in list_content(scope=scope, name=name, vo=vo):
829
829
  yield render_json(**did) + '\n'
830
830
 
831
- return try_stream(generate(vo=request.environ.get('vo')))
831
+ return try_stream(generate(vo=request.environ['vo']))
832
832
  except ValueError as error:
833
833
  return generate_http_error_flask(400, error)
834
834
  except DataIdentifierNotFound as error:
@@ -893,14 +893,14 @@ class Attachment(ErrorHandlingMethodView):
893
893
  description: Already attached
894
894
  """
895
895
  try:
896
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
896
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
897
897
  except ValueError as error:
898
898
  return generate_http_error_flask(400, error)
899
899
 
900
900
  attachments = json_parameters()
901
901
 
902
902
  try:
903
- attach_dids(scope=scope, name=name, attachment=attachments, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
903
+ attach_dids(scope=scope, name=name, attachment=attachments, issuer=request.environ['issuer'], vo=request.environ['vo'])
904
904
  except InvalidPath as error:
905
905
  return generate_http_error_flask(400, error)
906
906
  except (DataIdentifierNotFound, RSENotFound) as error:
@@ -955,7 +955,7 @@ class Attachment(ErrorHandlingMethodView):
955
955
  description: Did not found
956
956
  """
957
957
  try:
958
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
958
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
959
959
  except ValueError as error:
960
960
  return generate_http_error_flask(400, error)
961
961
 
@@ -963,7 +963,7 @@ class Attachment(ErrorHandlingMethodView):
963
963
  dids = param_get(parameters, 'dids')
964
964
 
965
965
  try:
966
- detach_dids(scope=scope, name=name, dids=dids, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
966
+ detach_dids(scope=scope, name=name, dids=dids, issuer=request.environ['issuer'], vo=request.environ['vo'])
967
967
  except UnsupportedOperation as error:
968
968
  return generate_http_error_flask(409, error)
969
969
  except DataIdentifierNotFound as error:
@@ -1038,13 +1038,13 @@ class AttachmentHistory(ErrorHandlingMethodView):
1038
1038
  description: Not acceptable
1039
1039
  """
1040
1040
  try:
1041
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
1041
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
1042
1042
 
1043
1043
  def generate(vo):
1044
1044
  for did in list_content_history(scope=scope, name=name, vo=vo):
1045
1045
  yield render_json(**did) + '\n'
1046
1046
 
1047
- return try_stream(generate(vo=request.environ.get('vo')))
1047
+ return try_stream(generate(vo=request.environ['vo']))
1048
1048
  except ValueError as error:
1049
1049
  return generate_http_error_flask(400, error)
1050
1050
  except DataIdentifierNotFound as error:
@@ -1140,13 +1140,13 @@ class Files(ErrorHandlingMethodView):
1140
1140
  long = 'long' in request.args
1141
1141
 
1142
1142
  try:
1143
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
1143
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
1144
1144
 
1145
1145
  def generate(vo):
1146
1146
  for file in list_files(scope=scope, name=name, long=long, vo=vo):
1147
1147
  yield dumps(file) + '\n'
1148
1148
 
1149
- return try_stream(generate(vo=request.environ.get('vo')))
1149
+ return try_stream(generate(vo=request.environ['vo']))
1150
1150
  except ValueError as error:
1151
1151
  return generate_http_error_flask(400, error)
1152
1152
  except DataIdentifierNotFound as error:
@@ -1227,7 +1227,7 @@ class BulkFiles(ErrorHandlingMethodView):
1227
1227
  for did in bulk_list_files(dids=dids, vo=vo):
1228
1228
  yield render_json(**did) + '\n'
1229
1229
 
1230
- return try_stream(generate(vo=request.environ.get('vo')))
1230
+ return try_stream(generate(vo=request.environ['vo']))
1231
1231
  except AccessDenied as error:
1232
1232
  return generate_http_error_flask(401, error)
1233
1233
  return 'Created', 201
@@ -1279,13 +1279,13 @@ class Parents(ErrorHandlingMethodView):
1279
1279
  description: Not acceptable
1280
1280
  """
1281
1281
  try:
1282
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
1282
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
1283
1283
 
1284
1284
  def generate(vo):
1285
1285
  for dataset in list_parent_dids(scope=scope, name=name, vo=vo):
1286
1286
  yield render_json(**dataset) + "\n"
1287
1287
 
1288
- return try_stream(generate(vo=request.environ.get('vo')))
1288
+ return try_stream(generate(vo=request.environ['vo']))
1289
1289
  except ValueError as error:
1290
1290
  return generate_http_error_flask(400, error)
1291
1291
  except DataIdentifierNotFound as error:
@@ -1333,13 +1333,13 @@ class Meta(ErrorHandlingMethodView):
1333
1333
  description: Not acceptable
1334
1334
  """
1335
1335
  try:
1336
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
1336
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
1337
1337
  except ValueError as error:
1338
1338
  return generate_http_error_flask(400, error)
1339
1339
 
1340
1340
  try:
1341
1341
  plugin = request.args.get('plugin', default='DID_COLUMN')
1342
- meta = get_metadata(scope=scope, name=name, plugin=plugin, vo=request.environ.get('vo'))
1342
+ meta = get_metadata(scope=scope, name=name, plugin=plugin, vo=request.environ['vo'])
1343
1343
  return Response(render_json(**meta), content_type='application/json')
1344
1344
  except DataIdentifierNotFound as error:
1345
1345
  return generate_http_error_flask(404, error)
@@ -1391,7 +1391,7 @@ class Meta(ErrorHandlingMethodView):
1391
1391
  description: Not acceptable
1392
1392
  """
1393
1393
  try:
1394
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
1394
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
1395
1395
  except ValueError as error:
1396
1396
  return generate_http_error_flask(400, error)
1397
1397
 
@@ -1403,9 +1403,9 @@ class Meta(ErrorHandlingMethodView):
1403
1403
  scope=scope,
1404
1404
  name=name,
1405
1405
  meta=meta,
1406
- issuer=request.environ.get('issuer'),
1406
+ issuer=request.environ['issuer'],
1407
1407
  recursive=param_get(parameters, 'recursive', default=False),
1408
- vo=request.environ.get('vo'),
1408
+ vo=request.environ['vo'],
1409
1409
  )
1410
1410
  except DataIdentifierNotFound as error:
1411
1411
  return generate_http_error_flask(404, error)
@@ -1450,7 +1450,7 @@ class Meta(ErrorHandlingMethodView):
1450
1450
  description: Feature is not in current database.
1451
1451
  """
1452
1452
  try:
1453
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
1453
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
1454
1454
  except ValueError as error:
1455
1455
  return generate_http_error_flask(400, error)
1456
1456
 
@@ -1460,7 +1460,7 @@ class Meta(ErrorHandlingMethodView):
1460
1460
  return generate_http_error_flask(404, KeyNotFound.__name__, 'No key provided to remove')
1461
1461
 
1462
1462
  try:
1463
- delete_metadata(scope=scope, name=name, key=key, vo=request.environ.get('vo'))
1463
+ delete_metadata(scope=scope, name=name, key=key, vo=request.environ['vo'])
1464
1464
  except (KeyNotFound, DataIdentifierNotFound) as error:
1465
1465
  return generate_http_error_flask(404, error)
1466
1466
  except NotImplementedError as error:
@@ -1521,7 +1521,7 @@ class SingleMeta(ErrorHandlingMethodView):
1521
1521
  description: Invalid key or value
1522
1522
  """
1523
1523
  try:
1524
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
1524
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
1525
1525
  except ValueError as error:
1526
1526
  return generate_http_error_flask(400, error)
1527
1527
 
@@ -1534,9 +1534,9 @@ class SingleMeta(ErrorHandlingMethodView):
1534
1534
  name=name,
1535
1535
  key=key,
1536
1536
  value=value,
1537
- issuer=request.environ.get('issuer'),
1537
+ issuer=request.environ['issuer'],
1538
1538
  recursive=param_get(parameters, 'recursive', default=False),
1539
- vo=request.environ.get('vo'),
1539
+ vo=request.environ['vo'],
1540
1540
  )
1541
1541
  except DataIdentifierNotFound as error:
1542
1542
  return generate_http_error_flask(404, error)
@@ -1602,7 +1602,7 @@ class BulkDIDsMeta(ErrorHandlingMethodView):
1602
1602
  dids = param_get(parameters, 'dids')
1603
1603
 
1604
1604
  try:
1605
- set_dids_metadata_bulk(dids=dids, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
1605
+ set_dids_metadata_bulk(dids=dids, issuer=request.environ['issuer'], vo=request.environ['vo'])
1606
1606
  except DataIdentifierNotFound as error:
1607
1607
  return generate_http_error_flask(404, error)
1608
1608
  except UnsupportedOperation as error:
@@ -1649,14 +1649,14 @@ class Rules(ErrorHandlingMethodView):
1649
1649
  description: Not acceptable
1650
1650
  """
1651
1651
  try:
1652
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
1652
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
1653
1653
 
1654
1654
  def generate(vo):
1655
1655
  get_did(scope=scope, name=name, vo=vo)
1656
1656
  for rule in list_replication_rules({'scope': scope, 'name': name}, vo=vo):
1657
1657
  yield dumps(rule, cls=APIEncoder) + '\n'
1658
1658
 
1659
- return try_stream(generate(vo=request.environ.get('vo')))
1659
+ return try_stream(generate(vo=request.environ['vo']))
1660
1660
  except ValueError as error:
1661
1661
  return generate_http_error_flask(400, error)
1662
1662
  except RuleNotFound as error:
@@ -1734,7 +1734,7 @@ class BulkMeta(ErrorHandlingMethodView):
1734
1734
  for meta in get_metadata_bulk(dids, inherit=inherit, plugin=plugin, vo=vo):
1735
1735
  yield render_json(**meta) + '\n'
1736
1736
 
1737
- return try_stream(generate(vo=request.environ.get('vo')))
1737
+ return try_stream(generate(vo=request.environ['vo']))
1738
1738
  except ValueError as error:
1739
1739
  return generate_http_error_flask(400, error, 'Cannot decode json parameter list')
1740
1740
  except DataIdentifierNotFound as error:
@@ -1799,13 +1799,13 @@ class AssociatedRules(ErrorHandlingMethodView):
1799
1799
  description: Not acceptable
1800
1800
  """
1801
1801
  try:
1802
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
1802
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
1803
1803
 
1804
1804
  def generate(vo):
1805
1805
  for rule in list_associated_replication_rules_for_file(scope=scope, name=name, vo=vo):
1806
1806
  yield dumps(rule, cls=APIEncoder) + '\n'
1807
1807
 
1808
- return try_stream(generate(vo=request.environ.get('vo')))
1808
+ return try_stream(generate(vo=request.environ['vo']))
1809
1809
  except ValueError as error:
1810
1810
  return generate_http_error_flask(400, error)
1811
1811
  except DataIdentifierNotFound as error:
@@ -1859,7 +1859,7 @@ class GUIDLookup(ErrorHandlingMethodView):
1859
1859
  for dataset in get_dataset_by_guid(guid, vo=vo):
1860
1860
  yield dumps(dataset, cls=APIEncoder) + '\n'
1861
1861
 
1862
- return try_stream(generate(vo=request.environ.get('vo')))
1862
+ return try_stream(generate(vo=request.environ['vo']))
1863
1863
  except DataIdentifierNotFound as error:
1864
1864
  return generate_http_error_flask(404, error)
1865
1865
 
@@ -1927,9 +1927,9 @@ class SampleLegacy(ErrorHandlingMethodView):
1927
1927
  input_name=input_name,
1928
1928
  output_scope=output_scope,
1929
1929
  output_name=output_name,
1930
- issuer=request.environ.get('issuer'),
1930
+ issuer=request.environ['issuer'],
1931
1931
  nbfiles=nbfiles,
1932
- vo=request.environ.get('vo'),
1932
+ vo=request.environ['vo'],
1933
1933
  )
1934
1934
  except DataIdentifierNotFound as error:
1935
1935
  return generate_http_error_flask(404, error)
@@ -2002,9 +2002,9 @@ class Sample(ErrorHandlingMethodView):
2002
2002
  input_name=parameters['input_name'],
2003
2003
  output_scope=parameters['output_scope'],
2004
2004
  output_name=parameters['output_name'],
2005
- issuer=request.environ.get('issuer'),
2005
+ issuer=request.environ['issuer'],
2006
2006
  nbfiles=parameters['nbfiles'],
2007
- vo=request.environ.get('vo'),
2007
+ vo=request.environ['vo'],
2008
2008
  )
2009
2009
  except DataIdentifierNotFound as error:
2010
2010
  return generate_http_error_flask(404, error)
@@ -2065,7 +2065,7 @@ class NewDIDs(ErrorHandlingMethodView):
2065
2065
 
2066
2066
  type_param = request.args.get('type', default=None)
2067
2067
 
2068
- return try_stream(generate(_type=type_param, vo=request.environ.get('vo')))
2068
+ return try_stream(generate(_type=type_param, vo=request.environ['vo']))
2069
2069
 
2070
2070
 
2071
2071
  class Resurrect(ErrorHandlingMethodView):
@@ -2113,7 +2113,7 @@ class Resurrect(ErrorHandlingMethodView):
2113
2113
  dids = json_list()
2114
2114
 
2115
2115
  try:
2116
- resurrect(dids=dids, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
2116
+ resurrect(dids=dids, issuer=request.environ['issuer'], vo=request.environ['vo'])
2117
2117
  except DataIdentifierNotFound as error:
2118
2118
  return generate_http_error_flask(404, error)
2119
2119
  except (DuplicateContent, DataIdentifierAlreadyExists, UnsupportedOperation) as error:
@@ -2165,13 +2165,13 @@ class Follow(ErrorHandlingMethodView):
2165
2165
  description: Not acceptable
2166
2166
  """
2167
2167
  try:
2168
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
2168
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
2169
2169
 
2170
2170
  def generate(vo):
2171
2171
  for user in get_users_following_did(scope=scope, name=name, vo=vo):
2172
2172
  yield render_json(**user) + '\n'
2173
2173
 
2174
- return try_stream(generate(vo=request.environ.get('vo')), content_type='application/json')
2174
+ return try_stream(generate(vo=request.environ['vo']), content_type='application/json')
2175
2175
  except ValueError as error:
2176
2176
  return generate_http_error_flask(400, error)
2177
2177
  except DataIdentifierNotFound as error:
@@ -2215,7 +2215,7 @@ class Follow(ErrorHandlingMethodView):
2215
2215
  description: Internal error
2216
2216
  """
2217
2217
  try:
2218
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
2218
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
2219
2219
  except ValueError as error:
2220
2220
  return generate_http_error_flask(400, error)
2221
2221
 
@@ -2223,7 +2223,7 @@ class Follow(ErrorHandlingMethodView):
2223
2223
  account = param_get(parameters, 'account')
2224
2224
 
2225
2225
  try:
2226
- add_did_to_followed(scope=scope, name=name, account=account, vo=request.environ.get('vo'))
2226
+ add_did_to_followed(scope=scope, name=name, account=account, vo=request.environ['vo'])
2227
2227
  except DataIdentifierNotFound as error:
2228
2228
  return generate_http_error_flask(404, error)
2229
2229
  except AccessDenied as error:
@@ -2265,7 +2265,7 @@ class Follow(ErrorHandlingMethodView):
2265
2265
  description: Internal error
2266
2266
  """
2267
2267
  try:
2268
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
2268
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
2269
2269
  except ValueError as error:
2270
2270
  return generate_http_error_flask(400, error)
2271
2271
 
@@ -2273,7 +2273,7 @@ class Follow(ErrorHandlingMethodView):
2273
2273
  account = param_get(parameters, 'account')
2274
2274
 
2275
2275
  try:
2276
- remove_did_from_followed(scope=scope, name=name, account=account, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
2276
+ remove_did_from_followed(scope=scope, name=name, account=account, issuer=request.environ['issuer'], vo=request.environ['vo'])
2277
2277
  except DataIdentifierNotFound as error:
2278
2278
  return generate_http_error_flask(404, error)
2279
2279
 
@@ -80,8 +80,8 @@ class AddFiles(ErrorHandlingMethodView):
80
80
  ignore_availability = param_get(parameters, 'ignore_availability', default=False)
81
81
  parents_metadata = param_get(parameters, 'parents_metadata', default=None)
82
82
  try:
83
- add_files(lfns=lfns, issuer=request.environ.get('issuer'), ignore_availability=ignore_availability,
84
- parents_metadata=parents_metadata, vo=request.environ.get('vo'))
83
+ add_files(lfns=lfns, issuer=request.environ['issuer'], ignore_availability=ignore_availability,
84
+ parents_metadata=parents_metadata, vo=request.environ['vo'])
85
85
  except InvalidPath as error:
86
86
  return generate_http_error_flask(400, error)
87
87
  except AccessDenied as error:
@@ -52,7 +52,7 @@ class Export(ErrorHandlingMethodView):
52
52
  description: Not acceptable
53
53
  """
54
54
  distance = request.args.get('distance', default='True') == 'True'
55
- return Response(render_json(**export_data(issuer=request.environ.get('issuer'), distance=distance, vo=request.environ.get('vo'))), content_type='application/json')
55
+ return Response(render_json(**export_data(issuer=request.environ['issuer'], distance=distance, vo=request.environ['vo'])), content_type='application/json')
56
56
 
57
57
 
58
58
  def blueprint(with_doc=False):
@@ -108,7 +108,7 @@ class Import(ErrorHandlingMethodView):
108
108
  description: Invalid Auth Token
109
109
  """
110
110
  data = json_parameters(parse_response)
111
- import_data(data=data, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
111
+ import_data(data=data, issuer=request.environ['issuer'], vo=request.environ['vo'])
112
112
  return 'Created', 201
113
113
 
114
114
 
@@ -90,7 +90,7 @@ class LifetimeException(ErrorHandlingMethodView):
90
90
  for exception in list_exceptions(vo=vo):
91
91
  yield dumps(exception, cls=APIEncoder) + '\n'
92
92
 
93
- return try_stream(generate(vo=request.environ.get('vo')))
93
+ return try_stream(generate(vo=request.environ['vo']))
94
94
  except LifetimeExceptionNotFound as error:
95
95
  return generate_http_error_flask(404, error)
96
96
 
@@ -145,8 +145,8 @@ class LifetimeException(ErrorHandlingMethodView):
145
145
  try:
146
146
  exception_id = add_exception(
147
147
  dids=param_get(parameters, 'dids', default=[]),
148
- account=request.environ.get('issuer'),
149
- vo=request.environ.get('vo'),
148
+ account=request.environ['issuer'],
149
+ vo=request.environ['vo'],
150
150
  pattern=param_get(parameters, 'pattern', default=None),
151
151
  comments=param_get(parameters, 'comments', default=None),
152
152
  expires_at=param_get(parameters, 'expires_at', default=None),
@@ -235,7 +235,7 @@ class LifetimeExceptionId(ErrorHandlingMethodView):
235
235
  for exception in list_exceptions(exception_id, vo=vo):
236
236
  yield dumps(exception, cls=APIEncoder) + '\n'
237
237
 
238
- return try_stream(generate(vo=request.environ.get('vo')))
238
+ return try_stream(generate(vo=request.environ['vo']))
239
239
  except LifetimeExceptionNotFound as error:
240
240
  return generate_http_error_flask(404, error)
241
241
 
@@ -282,7 +282,7 @@ class LifetimeExceptionId(ErrorHandlingMethodView):
282
282
  state = param_get(parameters, 'state', default=None)
283
283
 
284
284
  try:
285
- update_exception(exception_id=exception_id, state=state, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
285
+ update_exception(exception_id=exception_id, state=state, issuer=request.environ['issuer'], vo=request.environ['vo'])
286
286
  except UnsupportedOperation as error:
287
287
  return generate_http_error_flask(400, error)
288
288
  except AccessDenied as error:
@@ -113,7 +113,7 @@ class LockByRSE(ErrorHandlingMethodView):
113
113
  for lock in get_dataset_locks_by_rse(rse, vo=vo):
114
114
  yield render_json(**lock) + '\n'
115
115
 
116
- return try_stream(generate(vo=request.environ.get('vo')))
116
+ return try_stream(generate(vo=request.environ['vo']))
117
117
  except RSENotFound as error:
118
118
  return generate_http_error_flask(404, error)
119
119
 
@@ -206,13 +206,13 @@ class LocksByScopeName(ErrorHandlingMethodView):
206
206
  return 'Wrong did_type specified', 500
207
207
 
208
208
  try:
209
- scope, name = parse_scope_name(scope_name, request.environ.get('vo'))
209
+ scope, name = parse_scope_name(scope_name, request.environ['vo'])
210
210
 
211
211
  def generate(vo):
212
212
  for lock in get_dataset_locks(scope, name, vo=vo):
213
213
  yield render_json(**lock) + '\n'
214
214
 
215
- return try_stream(generate(vo=request.environ.get('vo')))
215
+ return try_stream(generate(vo=request.environ['vo']))
216
216
  except ValueError as error:
217
217
  return generate_http_error_flask(400, error)
218
218
 
@@ -321,7 +321,7 @@ class DatasetLocksForDids(ErrorHandlingMethodView):
321
321
  dids = data["dids"]
322
322
  except KeyError:
323
323
  return 'Can not find the list of DIDs in the data. Use "dids" keyword.', 400
324
- vo = request.environ.get('vo')
324
+ vo = request.environ['vo']
325
325
  try:
326
326
  locks = get_dataset_locks_bulk(dids, vo) # removes duplicates
327
327
 
@@ -15,15 +15,19 @@
15
15
 
16
16
  import importlib
17
17
  import logging
18
+ from typing import TYPE_CHECKING
18
19
 
19
20
  from flask import Flask
20
21
 
21
- from rucio.common.config import config_get
22
+ from rucio.common.config import config_get_list
22
23
  from rucio.common.exception import ConfigurationError
23
24
  from rucio.common.logging import setup_logging
24
25
  from rucio.web.rest.flaskapi.v1.common import CORSMiddleware
25
26
 
26
- DEFAULT_ENDPOINTS = [
27
+ if TYPE_CHECKING:
28
+ from collections.abc import Iterable
29
+
30
+ DEFAULT_ENDPOINTS = {
27
31
  'accountlimits',
28
32
  'accounts',
29
33
  'auth',
@@ -46,10 +50,10 @@ DEFAULT_ENDPOINTS = [
46
50
  'rules',
47
51
  'scopes',
48
52
  'subscriptions',
49
- ]
53
+ }
50
54
 
51
55
 
52
- def apply_endpoints(app, modules):
56
+ def apply_endpoints(app: Flask, modules: "Iterable[str]") -> None:
53
57
  for blueprint_module in modules:
54
58
  # Legacy patch - TODO Remove in 38.0.0
55
59
  if blueprint_module == "meta":
@@ -71,15 +75,18 @@ def apply_endpoints(app, modules):
71
75
  else:
72
76
  raise ConfigurationError(f'"{blueprint_module}" from the endpoints configuration value did not have a blueprint')
73
77
 
78
+ endpoints = set(config_get_list('api', 'endpoints', raise_exception=False, default=[]))
79
+ endpoints_add = set(config_get_list('api', 'endpoints_add', raise_exception=False, default=[]))
80
+ endpoints_remove = set(config_get_list('api', 'endpoints_remove', raise_exception=False, default=[]))
81
+
82
+ if endpoints and (endpoints_add or endpoints_remove):
83
+ raise ConfigurationError("Endpoints cannot be set in both 'endpoints' and 'endpoints_add'/'endpoints_remove'")
74
84
 
75
- try:
76
- endpoints = config_get('api', 'endpoints', raise_exception=False, default='')
77
- endpoints = list(filter(bool, map(str.strip, endpoints.split(sep=','))))
78
- except RuntimeError:
79
- endpoints = None
85
+ if endpoints_add.intersection(endpoints_remove):
86
+ raise ConfigurationError("Endpoints cannot be in both 'endpoints_add' and 'endpoints_remove'")
80
87
 
81
88
  if not endpoints:
82
- endpoints = DEFAULT_ENDPOINTS
89
+ endpoints = DEFAULT_ENDPOINTS - endpoints_remove | endpoints_add
83
90
 
84
91
  application = Flask(__name__)
85
92
  application.wsgi_app = CORSMiddleware(application.wsgi_app)
@@ -296,7 +296,7 @@ class HeaderRedirector(ErrorHandlingMethodView):
296
296
  vo = extract_vo(request.headers)
297
297
 
298
298
  replicas = list(
299
- list_replicas( # type: ignore (session parameter missing)
299
+ list_replicas(
300
300
  dids=[{'scope': scope, 'name': name, 'type': 'FILE'}],
301
301
  schemes=schemes,
302
302
  client_location=client_location,