rucio-clients 36.3.0__tar.gz → 36.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rucio-clients might be problematic. Click here for more details.

Files changed (203) hide show
  1. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/AUTHORS.rst +2 -0
  2. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/PKG-INFO +1 -1
  3. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/bin/rucio +15 -11
  4. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/account.py +1 -0
  5. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/bin_legacy/rucio.py +7 -2
  6. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/bin_legacy/rucio_admin.py +8 -2
  7. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/rse.py +3 -1
  8. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/rule.py +1 -1
  9. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/richclient.py +6 -13
  10. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/config.py +2 -2
  11. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/gfal.py +7 -6
  12. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/webdav.py +60 -20
  13. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/vcsversion.py +3 -3
  14. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_bin_rucio.py +37 -1
  15. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_cli_client_structure.py +30 -0
  16. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/ChangeLog +0 -0
  17. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/LICENSE +0 -0
  18. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/MANIFEST.in +0 -0
  19. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/README.md +0 -0
  20. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/bin/rucio-admin +0 -0
  21. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/etc/rse-accounts.cfg.template +0 -0
  22. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/etc/rucio.cfg.atlas.client.template +0 -0
  23. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/etc/rucio.cfg.template +0 -0
  24. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/__init__.py +0 -0
  25. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/alembicrevision.py +0 -0
  26. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/__init__.py +0 -0
  27. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/accountclient.py +0 -0
  28. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/accountlimitclient.py +0 -0
  29. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/baseclient.py +0 -0
  30. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/client.py +0 -0
  31. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/__init__.py +0 -0
  32. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/bin_legacy/__init__.py +0 -0
  33. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/command.py +0 -0
  34. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/command_base.py +0 -0
  35. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/config.py +0 -0
  36. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/did.py +0 -0
  37. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/download.py +0 -0
  38. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/lifetime_exception.py +0 -0
  39. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/replica.py +0 -0
  40. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/scope.py +0 -0
  41. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/subscription.py +0 -0
  42. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/upload.py +0 -0
  43. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/commands/utils.py +0 -0
  44. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/configclient.py +0 -0
  45. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/credentialclient.py +0 -0
  46. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/didclient.py +0 -0
  47. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/diracclient.py +0 -0
  48. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/downloadclient.py +0 -0
  49. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/exportclient.py +0 -0
  50. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/fileclient.py +0 -0
  51. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/importclient.py +0 -0
  52. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/lifetimeclient.py +0 -0
  53. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/lockclient.py +0 -0
  54. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/metaconventionsclient.py +0 -0
  55. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/pingclient.py +0 -0
  56. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/replicaclient.py +0 -0
  57. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/requestclient.py +0 -0
  58. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/rseclient.py +0 -0
  59. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/ruleclient.py +0 -0
  60. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/scopeclient.py +0 -0
  61. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/subscriptionclient.py +0 -0
  62. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/touchclient.py +0 -0
  63. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/client/uploadclient.py +0 -0
  64. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/__init__.py +0 -0
  65. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/bittorrent.py +0 -0
  66. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/cache.py +0 -0
  67. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/checksum.py +0 -0
  68. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/client.py +0 -0
  69. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/constants.py +0 -0
  70. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/constraints.py +0 -0
  71. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/didtype.py +0 -0
  72. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/exception.py +0 -0
  73. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/extra.py +0 -0
  74. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/logging.py +0 -0
  75. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/pcache.py +0 -0
  76. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/plugins.py +0 -0
  77. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/policy.py +0 -0
  78. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/schema/__init__.py +0 -0
  79. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/schema/generic.py +0 -0
  80. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/schema/generic_multi_vo.py +0 -0
  81. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/stomp_utils.py +0 -0
  82. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/stopwatch.py +0 -0
  83. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/test_rucio_server.py +0 -0
  84. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/types.py +0 -0
  85. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/common/utils.py +0 -0
  86. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/__init__.py +0 -0
  87. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/__init__.py +0 -0
  88. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/bittorrent.py +0 -0
  89. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/cache.py +0 -0
  90. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/dummy.py +0 -0
  91. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/globus.py +0 -0
  92. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/gsiftp.py +0 -0
  93. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/http_cache.py +0 -0
  94. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/mock.py +0 -0
  95. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/ngarc.py +0 -0
  96. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/posix.py +0 -0
  97. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/protocol.py +0 -0
  98. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/rclone.py +0 -0
  99. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/rfio.py +0 -0
  100. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/srm.py +0 -0
  101. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/ssh.py +0 -0
  102. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/storm.py +0 -0
  103. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/protocols/xrootd.py +0 -0
  104. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/rsemanager.py +0 -0
  105. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/rse/translation.py +0 -0
  106. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio/version.py +0 -0
  107. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/lib/rucio_clients.egg-info/SOURCES.txt +0 -0
  108. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/pylintrc +0 -0
  109. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/pyproject.toml +0 -0
  110. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/requirements/requirements.client.txt +0 -0
  111. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/setup.cfg +0 -0
  112. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/setup.py +0 -0
  113. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/setuputil.py +0 -0
  114. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_abacus_account.py +0 -0
  115. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_abacus_collection_replica.py +0 -0
  116. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_abacus_rse.py +0 -0
  117. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_account.py +0 -0
  118. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_account_limits.py +0 -0
  119. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_archive.py +0 -0
  120. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_auditor.py +0 -0
  121. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_auditor_hdfs.py +0 -0
  122. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_auditor_srmdumps.py +0 -0
  123. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_authentication.py +0 -0
  124. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_automatix.py +0 -0
  125. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_bad_replica.py +0 -0
  126. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_bb8.py +0 -0
  127. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_belleii.py +0 -0
  128. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_boolean.py +0 -0
  129. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_clients.py +0 -0
  130. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_config.py +0 -0
  131. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_conveyor.py +0 -0
  132. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_conveyor_submitter.py +0 -0
  133. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_counter.py +0 -0
  134. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_credential.py +0 -0
  135. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_curl.py +0 -0
  136. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_daemons.py +0 -0
  137. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_dataset_replicas.py +0 -0
  138. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_db.py +0 -0
  139. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_did.py +0 -0
  140. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_did_meta_plugins.py +0 -0
  141. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_download.py +0 -0
  142. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_dumper.py +0 -0
  143. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_filter_engine.py +0 -0
  144. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_gateway_external_representation.py +0 -0
  145. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_heartbeat.py +0 -0
  146. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_hermes.py +0 -0
  147. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_identity.py +0 -0
  148. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_impl_upload_download.py +0 -0
  149. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_import_export.py +0 -0
  150. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_judge_cleaner.py +0 -0
  151. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_judge_evaluator.py +0 -0
  152. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_judge_injector.py +0 -0
  153. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_judge_repairer.py +0 -0
  154. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_lifetime.py +0 -0
  155. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_message.py +0 -0
  156. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_meta_conventions.py +0 -0
  157. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_meta_did.py +0 -0
  158. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_module_import.py +0 -0
  159. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_monitor.py +0 -0
  160. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_multi_vo.py +0 -0
  161. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_naming_convention.py +0 -0
  162. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_oauthmanager.py +0 -0
  163. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_oidc.py +0 -0
  164. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_permission.py +0 -0
  165. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_pfns.py +0 -0
  166. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_ping.py +0 -0
  167. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_policy_package.py +0 -0
  168. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_preparer.py +0 -0
  169. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_qos.py +0 -0
  170. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_quarantined_replica.py +0 -0
  171. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_reaper.py +0 -0
  172. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_redirect.py +0 -0
  173. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_replica.py +0 -0
  174. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_replica_recoverer.py +0 -0
  175. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_replica_sorting.py +0 -0
  176. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_request.py +0 -0
  177. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_root_proxy.py +0 -0
  178. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse.py +0 -0
  179. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_expression_parser.py +0 -0
  180. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_lfn2path.py +0 -0
  181. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_protocol_gfal2.py +0 -0
  182. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_protocol_gfal2_impl.py +0 -0
  183. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_protocol_posix.py +0 -0
  184. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_protocol_rclone.py +0 -0
  185. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_protocol_rsync.py +0 -0
  186. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_protocol_srm.py +0 -0
  187. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_protocol_ssh.py +0 -0
  188. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_protocol_webdav.py +0 -0
  189. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_protocol_xrootd.py +0 -0
  190. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rse_selector.py +0 -0
  191. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rucio_server.py +0 -0
  192. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_rule.py +0 -0
  193. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_scope.py +0 -0
  194. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_subscription.py +0 -0
  195. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_throttler.py +0 -0
  196. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_tpc.py +0 -0
  197. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_trace.py +0 -0
  198. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_transfer.py +0 -0
  199. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_transfer_plugins.py +0 -0
  200. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_undertaker.py +0 -0
  201. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_upload.py +0 -0
  202. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tests/test_utils.py +0 -0
  203. {rucio_clients-36.3.0 → rucio_clients-36.5.0}/tools/merge_rucio_configs.py +0 -0
@@ -68,6 +68,7 @@ Individual contributors to the source code
68
68
  - Elena Gazzarrini <gazzarrini.elena@gmail.com>, 2022-2023
69
69
  - Maximilian Linhoff, <maximilian.linhoff@tu-dortmund.de>, 2024
70
70
  - Eric Banzuzi, <eric.banzuzi@gmail.com>, 2024
71
+ - Paul Millar, <paul.millar@desy.de>, 2025
71
72
 
72
73
  Organisations employing contributors
73
74
  ------------------------------------
@@ -96,3 +97,4 @@ Organisations employing contributors
96
97
  - University of Chicago (USA)
97
98
  - Purdue University (USA)
98
99
  - TU Dortmund University (Germany)
100
+ - Deutsches Elektronen-Synchrotron DESY (Germany)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: rucio-clients
3
- Version: 36.3.0
3
+ Version: 36.5.0
4
4
  Summary: Rucio Client Lite Package
5
5
  Home-page: https://rucio.cern.ch/
6
6
  Author: Rucio
@@ -90,29 +90,33 @@ def map_legacy_command() -> Optional[list[str]]:
90
90
  "touch": ["did", "update", "--touch"],
91
91
  "add-lifetime-exception": ["lifetime-exception", "add"],
92
92
  }
93
- try:
94
- new_command = command_map[command]
95
- except KeyError:
96
- pass
93
+ new_command = command_map.get(command)
97
94
 
98
95
  return new_command
99
96
 
100
97
 
101
98
  if __name__ == "__main__":
102
- commands = ("-h", "--help", "--version", "account", "config", "did", "replica", "rse", "rule", "scope", "subscription", "ping", "whoami", "test-server", "lifetime-exception", "upload", "download")
103
- logger = setup_logger(module_name=__name__)
104
- first_command = _get_first_command(sys.argv[1:])
99
+ commands = ("account", "config", "did", "replica", "rse", "rule", "scope", "subscription", "ping", "whoami", "test-server", "lifetime-exception", "upload", "download")
105
100
 
106
- is_legacy = '--legacy' in sys.argv
101
+ parser = argparse.ArgumentParser(add_help=False)
102
+ # Check for legacy flag
103
+ parser.add_argument("--legacy", action="store_true")
104
+ # Check for commands in the new command list
105
+ parser.add_argument("-h", "--help", action="store_true")
106
+ parser.add_argument("--version", action="store_true")
107
107
 
108
- if (first_command in commands) and not is_legacy:
109
- main()
108
+ args, _ = parser.parse_known_args()
110
109
 
111
- elif is_legacy:
110
+ logger = setup_logger(module_name=__name__)
111
+
112
+ if args.legacy:
112
113
  make_warning(logger)
113
114
  sys.argv.pop(sys.argv.index('--legacy'))
114
115
  main_legacy()
115
116
 
117
+ elif (any(arg in commands for arg in sys.argv)) or args.help or args.version:
118
+ main()
119
+
116
120
  else:
117
121
  make_warning(logger)
118
122
  try:
@@ -59,6 +59,7 @@ class Account(CommandBase):
59
59
  parser.add_argument("-a", "--account", dest="account", help="Account name")
60
60
  parser.add_argument("--id", dest="identity", action="store", help="Identity (e.g. DN)")
61
61
  parser.add_argument("--filters", dest="filters", action="store", help="Filter arguments in form `key=value,another_key=next_value`")
62
+ parser.add_argument("--csv", action='store_true', help="Output list of accounts as a csv")
62
63
 
63
64
  def add_namespace(self, parser: "ArgumentParser") -> None:
64
65
  parser.add_argument("--type", dest="account_type", help="Account Type", choices={"USER", "GROUP", "SERVICE"}, required=True)
@@ -1671,7 +1671,9 @@ def list_rses(args, client, logger, console, spinner):
1671
1671
  spinner.start()
1672
1672
 
1673
1673
  rses = client.list_rses(args.rses)
1674
- if cli_config == 'rich':
1674
+ if args.csv:
1675
+ print(*(rse['rse'] for rse in rses), sep='\n')
1676
+ elif cli_config == 'rich':
1675
1677
  table = generate_table([[rse['rse']] for rse in sorted(rses, key=lambda elem: elem['rse'])], headers=['RSE'], col_alignments=['left'])
1676
1678
  spinner.stop()
1677
1679
  print_output(table, console=console, no_pager=args.no_pager)
@@ -1985,7 +1987,8 @@ def add_lifetime_exception(args, client, logger, console, spinner):
1985
1987
  logger.error('inputfile is mandatory')
1986
1988
  return FAILURE
1987
1989
  with open(args.inputfile) as infile:
1988
- dids = list(set(line.strip() for line in infile))
1990
+ # Deduplicate the content of the input file and ignore empty lines.
1991
+ dids = set(did for line in infile if (did := line.strip()))
1989
1992
 
1990
1993
  dids_list = []
1991
1994
  containers = []
@@ -2040,6 +2043,7 @@ def add_lifetime_exception(args, client, logger, console, spinner):
2040
2043
  else:
2041
2044
  logger.info('%s:%s will be declared' % (scope, name))
2042
2045
  datasets.append({'scope': scope, 'name': name})
2046
+ summary[4] += 1
2043
2047
 
2044
2048
  if not datasets:
2045
2049
  logger.error('Nothing to submit')
@@ -2713,6 +2717,7 @@ You can filter by account::
2713
2717
  list_rses_parser.set_defaults(function=list_rses)
2714
2718
  list_rses_parser.add_argument('--rses', dest='rses', action='store', help='The RSE filter expression. A comprehensive help about RSE expressions \
2715
2719
  can be found in ' + Color.BOLD + 'https://rucio.cern.ch/documentation/started/concepts/rse_expressions' + Color.END)
2720
+ list_rses_parser.add_argument("--csv", action='store_true', help='Output a list of RSEs as a csv')
2716
2721
 
2717
2722
  # The list-suspicious-replicas command
2718
2723
  list_suspicious_replicas_parser = subparsers.add_parser('list-suspicious-replicas', help='Show the list of all replicas marked "suspicious".')
@@ -151,7 +151,9 @@ def list_accounts(args, client, logger, console, spinner):
151
151
  for key, value in [(_.split('=')[0], _.split('=')[1]) for _ in args.filters.split(',')]:
152
152
  filters[key] = value
153
153
  accounts = client.list_accounts(identity=args.identity, account_type=args.account_type, filters=filters)
154
- if cli_config == 'rich':
154
+ if args.csv:
155
+ print(*(account['account'] for account in accounts), sep=',')
156
+ elif cli_config == 'rich':
155
157
  table = generate_table([[account['account']] for account in accounts], headers=['ACCOUNT'], col_alignments=['left'])
156
158
  spinner.stop()
157
159
  print_output(table, console=console, no_pager=args.no_pager)
@@ -327,7 +329,9 @@ def list_rses(args, client, logger, console, spinner):
327
329
  spinner.start()
328
330
 
329
331
  rses = client.list_rses()
330
- if cli_config == 'rich':
332
+ if args.csv:
333
+ print(*(rse['rse'] for rse in rses), sep='\n')
334
+ elif cli_config == 'rich':
331
335
  table_data = [[rse['rse']] for rse in sorted(rses, key=lambda elem: elem['rse'])]
332
336
  table = generate_table(table_data, headers=['RSE'], col_alignments=['left'])
333
337
  spinner.stop()
@@ -1455,6 +1459,7 @@ def get_parser():
1455
1459
  list_account_parser.add_argument('--type', dest='account_type', action='store', help='Account Type (USER, GROUP, SERVICE)')
1456
1460
  list_account_parser.add_argument('--id', dest='identity', action='store', help='Identity (e.g. DN)')
1457
1461
  list_account_parser.add_argument('--filters', dest='filters', action='store', help='Filter arguments in form `key=value,another_key=next_value`')
1462
+ list_account_parser.add_argument("--csv", action='store_true', help='List result as a csv')
1458
1463
  list_account_parser.set_defaults(which='list_accounts')
1459
1464
 
1460
1465
  # The list_account_attributes command
@@ -1753,6 +1758,7 @@ def get_parser():
1753
1758
  '\n'
1754
1759
  ' $ rucio list-rses --rses \"tier=2&type=DATADISK\"\n'
1755
1760
  '\n')
1761
+ list_rse_parser.add_argument("--csv", action='store_true', help='Output a list of RSEs as a csv')
1756
1762
  list_rse_parser.set_defaults(which='list_rses')
1757
1763
 
1758
1764
  # The add_rse command
@@ -83,9 +83,11 @@ class RSE(CommandBase):
83
83
 
84
84
  def list_namespace(self, parser: "ArgumentParser") -> None:
85
85
  parser.add_argument("--rses", "--rse-exp", dest="rses", help="RSE name or expression")
86
+ parser.add_argument("--csv", action='store_true', help="Output list of RSEs as a csv")
86
87
 
87
88
  def show_namespace(self, parser: "ArgumentParser") -> None:
88
89
  parser.add_argument("--rse", "--rse-name", help="RSE name", required=True)
90
+ parser.add_argument("--csv", action='store_true', help="Output list of RSE property key and values as a csv")
89
91
 
90
92
  def list_(self):
91
93
  list_rses(self.args, self.client, self.logger, self.console, self.spinner)
@@ -218,7 +220,7 @@ class Protocol(RSE):
218
220
 
219
221
  def usage_example(self) -> list[str]:
220
222
  return [
221
- "$ rucio rse protocol --hostname jdoes.test.org --scheme gsiftp --prefix '/atlasdatadisk/rucio/' --port 8443 --rse JDOE_DATADISK # Add a new protocol on jdoe.test.org that uses gsiftp",
223
+ "$ rucio rse protocol --host jdoes.test.org --scheme gsiftp --prefix '/atlasdatadisk/rucio/' --port 8443 --rse JDOE_DATADISK # Add a new protocol on jdoe.test.org that uses gsiftp",
222
224
  "$ rucio rse protocol remove --scheme gsiftp --rse JDOE_DATADISK # Remove the existing gsiftp protocol",
223
225
  ]
224
226
 
@@ -51,7 +51,7 @@ class Rule(CommandBase):
51
51
  parser.add_argument("-a", "--account", dest="rule_account", action="store", help="The account of the rule")
52
52
  parser.add_argument("--activity", action="store", help="Activity to be used (e.g. User, Data Consolidation)") # TODO More info on this
53
53
  parser.add_argument("--rule-id", help="The rule ID, for accessing an existing rule.")
54
- parser.add_argument("--lifetime", dest="lifetime", action="store", type=int, help="Rule lifetime (in seconds)")
54
+ parser.add_argument("--lifetime", dest="lifetime", type=str, help="Rule lifetime (in seconds). Use 'None' for no set lifetime")
55
55
  parser.add_argument("--locked", action="store_true", help="Set the rule to locked") # TODO More info on this
56
56
  parser.add_argument("--source-replica-expression", help="RSE Expression for RSEs to be considered for source replicas")
57
57
  parser.add_argument("--comment", dest="comment", action="store", help="Comment about the replication rule")
@@ -15,7 +15,7 @@
15
15
  import logging
16
16
  import pydoc
17
17
  import re
18
- import subprocess
18
+ import shutil
19
19
  import sys
20
20
  from datetime import datetime
21
21
  from typing import TYPE_CHECKING, Any, Optional, Union
@@ -309,16 +309,9 @@ def get_pager() -> 'Callable[[str], None]':
309
309
  :returns: pager
310
310
  """
311
311
  default_pager = 'less'
312
- try:
313
- result = subprocess.run([default_pager], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
314
- less_available = result.returncode == 0
315
- except Exception:
316
- less_available = False
317
-
318
- def less_pager_function(text: str) -> None:
319
- """Use the 'less' pager with -FRSXKM options."""
320
- pydoc.pipepager(text, f'{default_pager} -FRSXKM')
321
-
322
- if less_available:
323
- return less_pager_function
312
+ # Attempt to use the default pager if available.
313
+ if shutil.which(default_pager) is not None:
314
+ return lambda text: pydoc.pipepager(text, f'{default_pager} -FRSXKM')
315
+
316
+ # Fall back to pydoc.pager if the default pager is not available.
324
317
  return pydoc.pager
@@ -547,12 +547,12 @@ def config_get_list(
547
547
  section: str,
548
548
  option: str,
549
549
  *,
550
- default: list[str] = ...,
550
+ default: _T = ...,
551
551
  check_config_table: bool = ...,
552
552
  session: "Optional[Session]" = ...,
553
553
  use_cache: bool = ...,
554
554
  expiration_time: int = ...,
555
- ) -> list[str]:
555
+ ) -> Union[list[str], _T]:
556
556
  ...
557
557
 
558
558
 
@@ -186,6 +186,7 @@ class Default(protocol.RSEProtocol):
186
186
  self.__ctx.set_opt_string_list("SRM PLUGIN", "TURL_PROTOCOLS", ["gsiftp", "rfio", "gsidcap", "dcap", "kdcap"])
187
187
  self.__ctx.set_opt_string("XROOTD PLUGIN", "XRD.WANTPROT", "gsi,unix")
188
188
  self.__ctx.set_opt_boolean("XROOTD PLUGIN", "NORMALIZE_PATH", False)
189
+ self.__ctx.set_opt_boolean("HTTP PLUGIN", "RETRIEVE_BEARER_TOKEN", False)
189
190
  auth_configured = False
190
191
  if self.auth_token:
191
192
  self.__ctx.set_opt_string("BEARER", "TOKEN", self.auth_token)
@@ -488,12 +489,12 @@ class Default(protocol.RSEProtocol):
488
489
 
489
490
  try:
490
491
  for path in paths:
491
- if self.__gfal2_exist(path) == 0:
492
- ret = ctx.unlink(str(path))
493
- if ret:
494
- return ret
495
- else:
496
- raise exception.SourceNotFound
492
+ # GFAL does a PROPFIND request before DELETE when the scheme is
493
+ # davs://, which is wasteful.
494
+ path = re.sub('^davs://', 'https://', str(path))
495
+ ret = ctx.unlink(path)
496
+ if ret:
497
+ return ret
497
498
  return ret
498
499
  except gfal2.GError as error: # pylint: disable=no-member
499
500
  if error.code == errno.ENOENT or 'No such file' in str(error):
@@ -12,6 +12,7 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import logging
15
16
  import os
16
17
  import sys
17
18
  from dataclasses import dataclass
@@ -161,6 +162,7 @@ class Default(protocol.RSEProtocol):
161
162
  :raises RSEAccessDenied
162
163
  """
163
164
  credentials = credentials or {}
165
+ using_presigned_urls = self.rse['sign_url'] is not None
164
166
  try:
165
167
  parse_url = urlparse(self.path2pfn(''))
166
168
  self.server = f'{parse_url.scheme}://{parse_url.netloc}'
@@ -177,23 +179,29 @@ class Default(protocol.RSEProtocol):
177
179
  except KeyError:
178
180
  self.auth_type = 'cert'
179
181
 
180
- try:
181
- self.cert = credentials['cert']
182
- except KeyError:
183
- x509 = os.getenv('X509_USER_PROXY')
184
- if not x509:
185
- # Trying to get the proxy from the default location
186
- proxy_path = '/tmp/x509up_u%s' % os.geteuid()
187
- if os.path.isfile(proxy_path):
188
- self.cert = (proxy_path, proxy_path)
189
- elif self.auth_token:
190
- # If no proxy is found, we set the cert to None and use the auth_token
191
- self.cert = None
192
- pass
182
+ if using_presigned_urls:
183
+ # Suppress all authentication, otherwise S3 servers will reject
184
+ # requests.
185
+ self.cert = None
186
+ self.auth_token = None
187
+ else:
188
+ try:
189
+ self.cert = credentials['cert']
190
+ except KeyError:
191
+ x509 = os.getenv('X509_USER_PROXY')
192
+ if not x509:
193
+ # Trying to get the proxy from the default location
194
+ proxy_path = '/tmp/x509up_u%s' % os.geteuid()
195
+ if os.path.isfile(proxy_path):
196
+ self.cert = (proxy_path, proxy_path)
197
+ elif self.auth_token:
198
+ # If no proxy is found, we set the cert to None and use the auth_token
199
+ self.cert = None
200
+ pass
201
+ else:
202
+ raise exception.RSEAccessDenied('X509_USER_PROXY is not set')
193
203
  else:
194
- raise exception.RSEAccessDenied('X509_USER_PROXY is not set')
195
- else:
196
- self.cert = (x509, x509)
204
+ self.cert = (x509, x509)
197
205
 
198
206
  try:
199
207
  self.timeout = credentials['timeout']
@@ -205,11 +213,16 @@ class Default(protocol.RSEProtocol):
205
213
  self.session.headers.update({'Authorization': 'Bearer ' + self.auth_token})
206
214
  # "ping" to see if the server is available
207
215
  try:
208
- res = self.session.request('HEAD', self.path2pfn(''), verify=False, timeout=self.timeout, cert=self.cert)
209
- if res.status_code != 200:
210
- raise exception.ServiceUnavailable('Problem to connect %s : %s' % (self.path2pfn(''), res.text))
216
+ test_url = self.path2pfn('')
217
+ res = self.session.request('HEAD', test_url, verify=False, timeout=self.timeout, cert=self.cert)
218
+ # REVISIT: this test checks some URL that doesn't correspond to
219
+ # any valid Rucio file. Although this works for normal WebDAV
220
+ # endpoints, it fails for endpoints using presigned URLs. As a
221
+ # work-around, accept 4xx status codes when using presigned URLs.
222
+ if res.status_code != 200 and not (using_presigned_urls and res.status_code < 500):
223
+ raise exception.ServiceUnavailable('Bad status code %s %s : %s' % (res.status_code, test_url, res.text))
211
224
  except requests.exceptions.ConnectionError as error:
212
- raise exception.ServiceUnavailable('Problem to connect %s : %s' % (self.path2pfn(''), error))
225
+ raise exception.ServiceUnavailable('Problem to connect %s : %s' % (test_url, error))
213
226
  except requests.exceptions.ReadTimeout as error:
214
227
  raise exception.ServiceUnavailable(error)
215
228
 
@@ -552,3 +565,30 @@ class Default(protocol.RSEProtocol):
552
565
  return totalsize, unusedsize
553
566
  except Exception as error:
554
567
  raise exception.ServiceUnavailable(error)
568
+
569
+
570
+ class NoRename(Default):
571
+ """ Implementing access to RSEs using the WebDAV protocol but without
572
+ renaming files on upload/download. Necessary for some storage endpoints.
573
+ """
574
+
575
+ def __init__(self, protocol_attr, rse_settings, logger=logging.log):
576
+ """ Initializes the object with information about the referred RSE.
577
+
578
+ :param protocol_attr: Properties of the requested protocol.
579
+ :param rse_settings: The RSE settings.
580
+ :param logger: Optional decorated logger that can be passed from the calling daemons or servers.
581
+ """
582
+ super(NoRename, self).__init__(protocol_attr, rse_settings, logger=logger)
583
+ self.renaming = False
584
+ self.attributes.pop('determinism_type', None)
585
+
586
+ def rename(self, pfn, new_pfn):
587
+ """ Allows to rename a file stored inside the connected RSE.
588
+
589
+ :param pfn: Current physical file name
590
+ :param new_pfn New physical file name
591
+
592
+ :raises DestinationNotAccessible, ServiceUnavailable, SourceNotFound
593
+ """
594
+ raise NotImplementedError
@@ -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': '36.3.0',
7
+ 'version': '36.5.0',
8
8
  'branch_nick': 'release-36',
9
- 'revision_id': '681dff60333b2f0b7d3f0b1eb7bf42ccc5f15053',
10
- 'revno': 13473
9
+ 'revision_id': 'd435b5bdf4f37f698d65d0a1ef4a7ecd21305b35',
10
+ 'revno': 13494
11
11
  }
@@ -123,6 +123,28 @@ class TestBinRucio:
123
123
  print(out, )
124
124
  assert 'Added new account: %s\n' % tmp_val in out
125
125
 
126
+ @pytest.mark.dirty
127
+ def test_list_account(self):
128
+ """CLIENT(ADMIN): List accounts"""
129
+ n_accounts = 5
130
+ tmp_accounts = [account_name_generator() for _ in range(n_accounts)]
131
+ for account in tmp_accounts:
132
+ execute(f'rucio-admin account add {account}')
133
+ execute(f'rucio-admin account add-attribute {account} --key test_list_account --value true')
134
+
135
+ cmd = 'rucio-admin account list'
136
+ exitcode, out, err = execute(cmd)
137
+ assert tmp_accounts[0] in out
138
+ assert tmp_accounts[-1] in out # Test by induction
139
+
140
+ cmd = "rucio-admin account list --filter test_list_account=true"
141
+ exitcode, out, err = execute(cmd)
142
+ assert set([o for o in out.split("\n") if o != '']) == set(tmp_accounts) # There's a little '' printed after
143
+
144
+ cmd = "rucio-admin account list --filter test_list_account=true --csv"
145
+ exitcode, out, err = execute(cmd)
146
+ assert set(o.rstrip('\n') for o in out.split(',')) == set(tmp_accounts) # Last obj in list has a `\n` included
147
+
126
148
  def test_whoami(self):
127
149
  """CLIENT(USER): Rucio whoami"""
128
150
  cmd = 'rucio whoami'
@@ -224,7 +246,8 @@ class TestBinRucio:
224
246
  assert 'Added new non-deterministic RSE: %s\n' % tmp_val in out
225
247
 
226
248
  def test_list_rses(self):
227
- """CLIENT(ADMIN): List RSEs"""
249
+ """CLIENT(USER/ADMIN): List RSEs"""
250
+ # TODO Test filter
228
251
  tmp_val = rse_name_generator()
229
252
  cmd = 'rucio-admin rse add %s' % tmp_val
230
253
  exitcode, out, err = execute(cmd)
@@ -234,6 +257,19 @@ class TestBinRucio:
234
257
  print(out, )
235
258
  assert tmp_val in out
236
259
 
260
+ # Expected output is a new RSE on each line
261
+ cmd = 'rucio-admin rse list --csv'
262
+ _, out, _ = execute(cmd)
263
+ assert tmp_val in out.split('\n')
264
+
265
+ cmd = 'rucio list-rses'
266
+ _, out, _ = execute(cmd)
267
+ assert tmp_val in out
268
+
269
+ cmd = 'rucio list-rses --csv'
270
+ _, out, _ = execute(cmd)
271
+ assert tmp_val in out.split('\n')
272
+
237
273
  def test_rse_add_distance(self):
238
274
  """CLIENT (ADMIN): Add distance to RSE"""
239
275
  # add RSEs
@@ -21,6 +21,22 @@ from rucio.common.utils import generate_uuid
21
21
  from rucio.tests.common import account_name_generator, execute, file_generator, rse_name_generator, scope_name_generator
22
22
 
23
23
 
24
+ def test_main_args():
25
+ specify_account = "rucio --account root --auth-strategy userpass whoami"
26
+ exitcode, out, err = execute(specify_account)
27
+
28
+ assert exitcode == 0
29
+ assert "This method is being deprecated" not in err
30
+ assert "root" in out
31
+
32
+ legacy_arg = "rucio --legacy --account root --auth-strategy userpass whoami"
33
+ exitcode, out, err = execute(legacy_arg)
34
+
35
+ assert exitcode == 0
36
+ assert "This method is being deprecated" in err
37
+ assert "root" in out
38
+
39
+
24
40
  def test_account(rucio_client):
25
41
  new_account = account_name_generator()
26
42
  command = f"rucio account add --type USER --account {new_account}"
@@ -613,6 +629,20 @@ def test_rule(rucio_client, mock_scope):
613
629
  assert exitcode == 0
614
630
  assert "ERROR" not in err
615
631
 
632
+ # Testing the two different lifetime type options
633
+ cmd = f"rucio rule update --rule-id {rule_id} --lifetime 10"
634
+ exitcode, out, err = execute(cmd)
635
+ assert exitcode == 0
636
+ assert "ERROR" not in err
637
+ rule_info = rucio_client.get_replication_rule(rule_id)
638
+ assert abs(rule_info["updated_at"] - rule_info["expires_at"]).seconds == 10
639
+
640
+ cmd = f"rucio rule update --rule-id {rule_id} --lifetime None"
641
+ exitcode, out, err = execute(cmd)
642
+ assert exitcode == 0
643
+ assert "ERROR" not in err
644
+ assert rucio_client.get_replication_rule(rule_id)["expires_at"] is None
645
+
616
646
  # Do one without a child rule so i can delete it
617
647
  new_name = generate_uuid()
618
648
  rucio_client.add_replica(mock_rse, scope, new_name, 4, "deadbeef")
File without changes
File without changes
File without changes