rucio 38.1.0__py3-none-any.whl → 38.3.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 (95) 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 +1 -1
  5. rucio/cli/download.py +1 -1
  6. rucio/cli/opendata.py +60 -9
  7. rucio/cli/utils.py +13 -1
  8. rucio/client/configclient.py +23 -0
  9. rucio/client/richclient.py +6 -0
  10. rucio/common/constants.py +5 -0
  11. rucio/common/exception.py +10 -0
  12. rucio/common/plugins.py +24 -8
  13. rucio/common/utils.py +3 -3
  14. rucio/core/config.py +8 -6
  15. rucio/core/did_meta_plugins/did_column_meta.py +226 -69
  16. rucio/core/replica.py +3 -4
  17. rucio/core/request.py +7 -2
  18. rucio/core/rule.py +6 -3
  19. rucio/core/rule_grouping.py +2 -2
  20. rucio/daemons/abacus/account.py +1 -1
  21. rucio/daemons/abacus/collection_replica.py +1 -1
  22. rucio/daemons/abacus/rse.py +1 -1
  23. rucio/daemons/common.py +1 -1
  24. rucio/daemons/hermes/hermes.py +14 -13
  25. rucio/daemons/judge/cleaner.py +2 -2
  26. rucio/daemons/judge/evaluator.py +2 -2
  27. rucio/daemons/judge/injector.py +5 -4
  28. rucio/daemons/judge/repairer.py +2 -2
  29. rucio/gateway/config.py +2 -37
  30. rucio/gateway/rule.py +2 -2
  31. rucio/rse/translation.py +3 -3
  32. rucio/transfertool/fts3_plugins.py +3 -3
  33. rucio/vcsversion.py +3 -3
  34. rucio/web/rest/flaskapi/v1/config.py +52 -14
  35. {rucio-38.1.0.dist-info → rucio-38.3.0.dist-info}/METADATA +1 -1
  36. {rucio-38.1.0.dist-info → rucio-38.3.0.dist-info}/RECORD +95 -95
  37. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/alembic.ini.template +0 -0
  38. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
  39. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
  40. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
  41. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
  42. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
  43. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
  44. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
  45. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
  46. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
  47. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
  48. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
  49. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/rucio.cfg.template +0 -0
  50. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -0
  51. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/requirements.server.txt +0 -0
  52. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/tools/bootstrap.py +0 -0
  53. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
  54. {rucio-38.1.0.data → rucio-38.3.0.data}/data/rucio/tools/reset_database.py +0 -0
  55. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio +0 -0
  56. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-abacus-account +0 -0
  57. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-abacus-collection-replica +0 -0
  58. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-abacus-rse +0 -0
  59. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-admin +0 -0
  60. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-atropos +0 -0
  61. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-auditor +0 -0
  62. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-automatix +0 -0
  63. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-bb8 +0 -0
  64. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-cache-client +0 -0
  65. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-cache-consumer +0 -0
  66. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-finisher +0 -0
  67. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-poller +0 -0
  68. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-preparer +0 -0
  69. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-receiver +0 -0
  70. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-stager +0 -0
  71. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-submitter +0 -0
  72. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-conveyor-throttler +0 -0
  73. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-dark-reaper +0 -0
  74. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-dumper +0 -0
  75. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-follower +0 -0
  76. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-hermes +0 -0
  77. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-judge-cleaner +0 -0
  78. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-judge-evaluator +0 -0
  79. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-judge-injector +0 -0
  80. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-judge-repairer +0 -0
  81. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-kronos +0 -0
  82. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-minos +0 -0
  83. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
  84. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-necromancer +0 -0
  85. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-oauth-manager +0 -0
  86. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-reaper +0 -0
  87. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-replica-recoverer +0 -0
  88. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-rse-decommissioner +0 -0
  89. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-storage-consistency-actions +0 -0
  90. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-transmogrifier +0 -0
  91. {rucio-38.1.0.data → rucio-38.3.0.data}/scripts/rucio-undertaker +0 -0
  92. {rucio-38.1.0.dist-info → rucio-38.3.0.dist-info}/WHEEL +0 -0
  93. {rucio-38.1.0.dist-info → rucio-38.3.0.dist-info}/licenses/AUTHORS.rst +0 -0
  94. {rucio-38.1.0.dist-info → rucio-38.3.0.dist-info}/licenses/LICENSE +0 -0
  95. {rucio-38.1.0.dist-info → rucio-38.3.0.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,7 @@
14
14
 
15
15
  import operator
16
16
  from datetime import datetime, timedelta
17
- from typing import TYPE_CHECKING
17
+ from typing import TYPE_CHECKING, Any
18
18
 
19
19
  from sqlalchemy import inspect, update
20
20
  from sqlalchemy.exc import CompileError, InvalidRequestError, NoResultFound
@@ -30,46 +30,95 @@ from rucio.db.sqla.constants import DIDType
30
30
  from rucio.db.sqla.session import read_session, stream_session, transactional_session
31
31
 
32
32
  if TYPE_CHECKING:
33
- from typing import Optional
33
+ from collections.abc import Iterator
34
+ from typing import Literal, Optional, Union
34
35
 
35
36
  from sqlalchemy.orm import Session
36
37
 
38
+ from rucio.common.types import InternalScope
39
+
37
40
 
38
41
  class DidColumnMeta(DidMetaPlugin):
39
42
  """
40
43
  A metadata plugin to interact with the base DID table metadata.
41
44
  """
42
- def __init__(self):
45
+
46
+ def __init__(self) -> None:
47
+ """Initialize the DID column metadata plugin."""
43
48
  super(DidColumnMeta, self).__init__()
44
49
  self.plugin_name = "DID_COLUMN"
45
50
 
46
51
  @read_session
47
- def get_metadata(self, scope, name, *, session: "Session"):
52
+ def get_metadata(
53
+ self,
54
+ scope: "InternalScope",
55
+ name: str,
56
+ *,
57
+ session: "Session",
58
+ ) -> dict[str, Any]:
48
59
  """
49
- Get data identifier metadata.
60
+ Get all the metadata of some data identifier.
50
61
 
51
- :param scope: The scope name.
52
- :param name: The data identifier name.
62
+ :param scope: The scope of the DID.
63
+ :param name: The name of the DID.
53
64
  :param session: The database session in use.
65
+ :returns: DID metadata as a dictionary.
54
66
  """
55
67
  try:
56
- row = session.query(models.DataIdentifier).filter_by(scope=scope, name=name).\
68
+ row = session.query(models.DataIdentifier).filter_by(scope=scope, name=name). \
57
69
  with_hint(models.DataIdentifier, "INDEX(DIDS DIDS_PK)", 'oracle').one()
58
70
  return row.to_dict()
59
71
  except NoResultFound:
60
72
  raise exception.DataIdentifierNotFound(f"Data identifier '{scope}:{name}' not found")
61
73
 
62
74
  @transactional_session
63
- def set_metadata(self, scope, name, key, value, recursive=False, *, session: "Session"):
75
+ def set_metadata(
76
+ self,
77
+ scope: "InternalScope",
78
+ name: str,
79
+ key: str,
80
+ value: Any,
81
+ recursive: bool = False,
82
+ *,
83
+ session: "Session",
84
+ ) -> None:
85
+ """
86
+ Add a single key-value metadata pair to a data identifier.
87
+
88
+ :param scope: The scope of the DID.
89
+ :param name: The name of the DID.
90
+ :param key: The metadata key.
91
+ :param value: The metadata value.
92
+ :param recursive: Option to propagate the metadata updates to child content.
93
+ :param session: The database session in use.
94
+ """
64
95
  self.set_metadata_bulk(scope=scope, name=name, metadata={key: value}, recursive=recursive, session=session)
65
96
 
66
97
  @transactional_session
67
- def set_metadata_bulk(self, scope, name, metadata, recursive=False, *, session: "Session"):
68
- did_query = session.query(models.DataIdentifier).with_hint(models.DataIdentifier, "INDEX(DIDS DIDS_PK)", 'oracle').filter_by(scope=scope, name=name)
98
+ def set_metadata_bulk(
99
+ self,
100
+ scope: "InternalScope",
101
+ name: str,
102
+ metadata: dict[str, Any],
103
+ recursive: bool = False,
104
+ *,
105
+ session: "Session",
106
+ ) -> None:
107
+ """
108
+ Add multiple key-value metadata pairs to a data identifier.
109
+
110
+ :param scope: The scope of the DID.
111
+ :param name: The name of the DID.
112
+ :param metadata: All key-value metadata pairs to set.
113
+ :param recursive: Option to propagate the metadata updates to child content.
114
+ :param session: The database session in use.
115
+ """
116
+ did_query = session.query(models.DataIdentifier).with_hint(models.DataIdentifier, "INDEX(DIDS DIDS_PK)",
117
+ 'oracle').filter_by(scope=scope, name=name)
69
118
  if did_query.one_or_none() is None:
70
119
  raise exception.DataIdentifierNotFound("Data identifier '%s:%s' not found" % (scope, name))
71
120
 
72
- remainder = {}
121
+ remainder: dict[Any, Any] = {}
73
122
  for key, value in metadata.items():
74
123
  if key == 'eol_at' and isinstance(value, str):
75
124
  try:
@@ -92,51 +141,106 @@ class DidColumnMeta(DidMetaPlugin):
92
141
  # check for DID presence
93
142
  raise exception.UnsupportedOperation('%s for %s:%s cannot be updated' % (key, scope, name))
94
143
  elif key in ['guid', 'events']:
95
- rowcount = did_query.filter_by(did_type=DIDType.FILE).update({key: value}, synchronize_session=False)
144
+ rowcount = did_query \
145
+ .filter_by(did_type=DIDType.FILE) \
146
+ .update({key: value}, synchronize_session=False)
96
147
  if not rowcount:
97
148
  # check for DID presence
98
149
  raise exception.UnsupportedOperation('%s for %s:%s cannot be updated' % (key, scope, name))
99
150
 
100
- session.query(models.DataIdentifierAssociation).filter_by(child_scope=scope, child_name=name, child_type=DIDType.FILE).update({key: value}, synchronize_session=False)
151
+ session.query(models.DataIdentifierAssociation) \
152
+ .filter_by(child_scope=scope, child_name=name, child_type=DIDType.FILE) \
153
+ .update({key: value}, synchronize_session=False)
101
154
  if key == 'events':
102
- for parent_scope, parent_name in session.query(models.DataIdentifierAssociation.scope, models.DataIdentifierAssociation.name).filter_by(child_scope=scope, child_name=name):
103
- events = session.query(func.sum(models.DataIdentifierAssociation.events)).filter_by(scope=parent_scope, name=parent_name).one()[0]
104
- session.query(models.DataIdentifier).filter_by(scope=parent_scope, name=parent_name).update({'events': events}, synchronize_session=False)
155
+ for parent_scope, parent_name \
156
+ in session.query(models.DataIdentifierAssociation.scope,
157
+ models.DataIdentifierAssociation.name
158
+ ).filter_by(child_scope=scope, child_name=name):
159
+ events = session.query(func.sum(models.DataIdentifierAssociation.events)) \
160
+ .filter_by(scope=parent_scope, name=parent_name).one()[0]
161
+ session.query(models.DataIdentifier) \
162
+ .filter_by(scope=parent_scope, name=parent_name) \
163
+ .update({'events': events}, synchronize_session=False)
105
164
  elif key == 'adler32':
106
- rowcount = did_query.filter_by(did_type=DIDType.FILE).update({key: value}, synchronize_session=False)
165
+ rowcount = did_query \
166
+ .filter_by(did_type=DIDType.FILE) \
167
+ .update({key: value}, synchronize_session=False)
107
168
  if not rowcount:
108
169
  # check for DID presence
109
170
  raise exception.UnsupportedOperation('%s for %s:%s cannot be updated' % (key, scope, name))
110
171
 
111
- session.query(models.DataIdentifierAssociation).filter_by(child_scope=scope, child_name=name, child_type=DIDType.FILE).update({key: value}, synchronize_session=False)
112
- session.query(models.Request).filter_by(scope=scope, name=name).update({key: value}, synchronize_session=False)
113
- session.query(models.RSEFileAssociation).filter_by(scope=scope, name=name).update({key: value}, synchronize_session=False)
172
+ session.query(models.DataIdentifierAssociation) \
173
+ .filter_by(child_scope=scope, child_name=name, child_type=DIDType.FILE) \
174
+ .update({key: value}, synchronize_session=False)
175
+ session.query(models.Request) \
176
+ .filter_by(scope=scope, name=name) \
177
+ .update({key: value}, synchronize_session=False)
178
+ session.query(models.RSEFileAssociation) \
179
+ .filter_by(scope=scope, name=name) \
180
+ .update({key: value}, synchronize_session=False)
114
181
  elif key == 'bytes':
115
- rowcount = did_query.filter_by(did_type=DIDType.FILE).update({key: value}, synchronize_session=False)
182
+ rowcount = did_query \
183
+ .filter_by(did_type=DIDType.FILE) \
184
+ .update({key: value}, synchronize_session=False)
116
185
  if not rowcount:
117
186
  # check for DID presence
118
187
  raise exception.UnsupportedOperation('%s for %s:%s cannot be updated' % (key, scope, name))
119
188
 
120
- session.query(models.DataIdentifierAssociation).filter_by(child_scope=scope, child_name=name, child_type=DIDType.FILE).update({key: value}, synchronize_session=False)
121
- session.query(models.Request).filter_by(scope=scope, name=name).update({key: value}, synchronize_session=False)
122
-
123
- for account, bytes_, rse_id, rule_id in session.query(models.ReplicaLock.account, models.ReplicaLock.bytes, models.ReplicaLock.rse_id, models.ReplicaLock.rule_id).filter_by(scope=scope, name=name):
124
- session.query(models.ReplicaLock).filter_by(scope=scope, name=name, rule_id=rule_id, rse_id=rse_id).update({key: value}, synchronize_session=False)
189
+ session.query(models.DataIdentifierAssociation) \
190
+ .filter_by(child_scope=scope, child_name=name, child_type=DIDType.FILE) \
191
+ .update({key: value}, synchronize_session=False)
192
+ session.query(models.Request) \
193
+ .filter_by(scope=scope, name=name) \
194
+ .update({key: value}, synchronize_session=False)
195
+
196
+ for account, bytes_, rse_id, rule_id \
197
+ in session.query(models.ReplicaLock.account,
198
+ models.ReplicaLock.bytes,
199
+ models.ReplicaLock.rse_id,
200
+ models.ReplicaLock.rule_id
201
+ ).filter_by(scope=scope, name=name):
202
+ session.query(models.ReplicaLock) \
203
+ .filter_by(scope=scope, name=name, rule_id=rule_id, rse_id=rse_id) \
204
+ .update({key: value}, synchronize_session=False)
125
205
  account_counter.decrease(rse_id=rse_id, account=account, files=1, bytes_=bytes_, session=session)
126
206
  account_counter.increase(rse_id=rse_id, account=account, files=1, bytes_=value, session=session)
127
207
 
128
- for bytes_, rse_id in session.query(models.RSEFileAssociation.bytes, models.RSEFileAssociation.rse_id).filter_by(scope=scope, name=name):
129
- session.query(models.RSEFileAssociation).filter_by(scope=scope, name=name, rse_id=rse_id).update({key: value}, synchronize_session=False)
208
+ for bytes_, rse_id \
209
+ in session.query(models.RSEFileAssociation.bytes,
210
+ models.RSEFileAssociation.rse_id
211
+ ).filter_by(scope=scope, name=name):
212
+ session.query(models.RSEFileAssociation) \
213
+ .filter_by(scope=scope, name=name, rse_id=rse_id) \
214
+ .update({key: value}, synchronize_session=False)
130
215
  rse_counter.decrease(rse_id=rse_id, files=1, bytes_=bytes_, session=session)
131
216
  rse_counter.increase(rse_id=rse_id, files=1, bytes_=value, session=session)
132
217
 
133
- for parent_scope, parent_name in session.query(models.DataIdentifierAssociation.scope, models.DataIdentifierAssociation.name).filter_by(child_scope=scope, child_name=name):
134
- values = {}
135
- values['length'], values['bytes'], values['events'] = session.query(func.count(models.DataIdentifierAssociation.scope),
136
- func.sum(models.DataIdentifierAssociation.bytes),
137
- func.sum(models.DataIdentifierAssociation.events)).filter_by(scope=parent_scope, name=parent_name).one()
138
- session.query(models.DataIdentifier).filter_by(scope=parent_scope, name=parent_name).update(values, synchronize_session=False)
139
- session.query(models.DatasetLock).filter_by(scope=parent_scope, name=parent_name).update({'length': values['length'], 'bytes': values['bytes']}, synchronize_session=False)
218
+ for parent_scope, parent_name \
219
+ in session.query(models.DataIdentifierAssociation.scope,
220
+ models.DataIdentifierAssociation.name
221
+ ).filter_by(child_scope=scope, child_name=name):
222
+ values: dict[Any, Any] = {
223
+ 'length': (session
224
+ .query(func.count(models.DataIdentifierAssociation.scope),
225
+ func.sum(models.DataIdentifierAssociation.bytes),
226
+ func.sum(models.DataIdentifierAssociation.events))
227
+ .filter_by(scope=parent_scope, name=parent_name).one())[0],
228
+ 'bytes': (session
229
+ .query(func.count(models.DataIdentifierAssociation.scope),
230
+ func.sum(models.DataIdentifierAssociation.bytes),
231
+ func.sum(models.DataIdentifierAssociation.events))
232
+ .filter_by(scope=parent_scope, name=parent_name).one())[1],
233
+ 'events': (session
234
+ .query(func.count(models.DataIdentifierAssociation.scope),
235
+ func.sum(models.DataIdentifierAssociation.bytes),
236
+ func.sum(models.DataIdentifierAssociation.events))
237
+ .filter_by(scope=parent_scope, name=parent_name).one())[2]}
238
+ session.query(models.DataIdentifier) \
239
+ .filter_by(scope=parent_scope, name=parent_name) \
240
+ .update(values, synchronize_session=False)
241
+ session.query(models.DatasetLock) \
242
+ .filter_by(scope=parent_scope, name=parent_name) \
243
+ .update({'length': values['length'], 'bytes': values['bytes']}, synchronize_session=False)
140
244
  else:
141
245
  remainder[key] = value
142
246
 
@@ -148,42 +252,63 @@ class DidColumnMeta(DidMetaPlugin):
148
252
  except InvalidRequestError:
149
253
  raise exception.InvalidMetadata("Some of the keys are not accepted: " + str(list(remainder.keys())))
150
254
  if not rowcount:
151
- raise exception.UnsupportedOperation('Some of the keys for %s:%s cannot be updated: %s' % (scope, name, str(list(remainder.keys()))))
255
+ raise exception.UnsupportedOperation(
256
+ 'Some of the keys for %s:%s cannot be updated: %s' % (scope, name, str(list(remainder.keys()))))
152
257
 
153
258
  # propagate metadata updates to child content
154
259
  if recursive:
155
- content_query = session.query(models.DataIdentifierAssociation.child_scope, models.DataIdentifierAssociation.child_name)
156
- content_query = content_query.with_hint(models.DataIdentifierAssociation, "INDEX(CONTENTS CONTENTS_PK)", 'oracle').filter_by(scope=scope, name=name)
260
+ content_query = session.query(models.DataIdentifierAssociation.child_scope,
261
+ models.DataIdentifierAssociation.child_name)
262
+ content_query = content_query.with_hint(models.DataIdentifierAssociation, "INDEX(CONTENTS CONTENTS_PK)",
263
+ 'oracle').filter_by(scope=scope, name=name)
157
264
 
158
265
  for child_scope, child_name in content_query:
159
266
  try:
160
- stmt = update(models.DataIdentifier)\
161
- .prefix_with("/*+ INDEX(DIDS DIDS_PK) */", dialect='oracle')\
162
- .filter_by(scope=child_scope, name=child_name)\
163
- .execution_options(synchronize_session='fetch')\
267
+ stmt = update(models.DataIdentifier) \
268
+ .prefix_with("/*+ INDEX(DIDS DIDS_PK) */", dialect='oracle') \
269
+ .filter_by(scope=child_scope, name=child_name) \
270
+ .execution_options(synchronize_session='fetch') \
164
271
  .values(remainder)
165
272
  session.execute(stmt)
166
273
  except CompileError as error:
167
274
  raise exception.InvalidMetadata(error)
168
275
  except InvalidRequestError:
169
- raise exception.InvalidMetadata("Some of the keys are not accepted recursively: " + str(list(remainder.keys())))
276
+ raise exception.InvalidMetadata(
277
+ "Some of the keys are not accepted recursively: " + str(list(remainder.keys())))
170
278
 
171
279
  @stream_session
172
- def list_dids(self, scope, filters, did_type='collection', ignore_case=False, limit=None,
173
- offset=None, long=False, recursive=False, ignore_dids=None, *, session: "Session"):
280
+ def list_dids(
281
+ self,
282
+ scope: "InternalScope",
283
+ filters: "Union[dict[str, Any], list[dict[str, Any]]]",
284
+ did_type: "Literal['all', 'collection', 'dataset', 'container', 'file']" = 'collection',
285
+ ignore_case: bool = False,
286
+ limit: "Optional[int]" = None,
287
+ offset: "Optional[int]" = None,
288
+ long: bool = False,
289
+ recursive: bool = False,
290
+ ignore_dids: "Optional[set[str]]" = None,
291
+ *,
292
+ session: "Session",
293
+ ) -> "Iterator[Union[str, dict[str, Any]]]":
174
294
  """
175
295
  Search data identifiers.
176
296
 
177
- :param scope: the scope name.
178
- :param filters: dictionary of attributes by which the results should be filtered.
179
- :param did_type: the type of the DID: all(container, dataset, file), collection(dataset or container), dataset, container, file.
180
- :param ignore_case: ignore case distinctions.
181
- :param limit: limit number.
182
- :param offset: offset number.
183
- :param long: Long format option to display more information for each DID.
297
+ :param scope: The scope of the DIDs to list.
298
+ :param filters: A single dict or a list of dicts representing OR groups (disjunction).
299
+ Each group can include a semantic 'type' expanded into did_type filters.
300
+ :param did_type: Option to filter by a specific DID type:
301
+ all(container, dataset, file), collection(dataset or container), dataset, container, file.
302
+ :param ignore_case: Has no effect.
303
+ :param limit: Option to limit the number of returned results.
304
+ :param offset: Has no effect.
305
+ :param long: Option to display more information for each DID.
306
+ :param recursive: Option to recursively list child-DIDs content.
307
+ :param ignore_dids: A set of 'scope:name' strings to de-duplicate results across OR groups and recursion.
184
308
  :param session: The database session in use.
185
- :param recursive: Recursively list DIDs content.
186
- :param ignore_dids: List of DIDs to refrain from yielding.
309
+ :yields:
310
+ - If long is False: DID names (str).
311
+ - If long is True: dicts with keys: {'scope', 'name', 'did_type', 'bytes', 'length'}.
187
312
  """
188
313
  if not ignore_dids:
189
314
  ignore_dids = set()
@@ -202,7 +327,8 @@ class DidColumnMeta(DidMetaPlugin):
202
327
  filters = [filters]
203
328
 
204
329
  # for each or_group, make sure there is a mapped "did_type" filter.
205
- # if type maps to many DIDTypes, the corresponding or_group will be copied the required number of times to satisfy all the logical possibilities.
330
+ # if type maps to many DIDTypes, the corresponding or_group will be copied the
331
+ # required number of times to satisfy all the logical possibilities.
206
332
  filters_tmp = []
207
333
  for or_group in filters:
208
334
  if 'type' not in or_group:
@@ -210,7 +336,8 @@ class DidColumnMeta(DidMetaPlugin):
210
336
  else:
211
337
  or_group_type = or_group.pop('type').lower()
212
338
  if or_group_type not in type_to_did_type_mapping.keys():
213
- raise exception.UnsupportedOperation('{} is not a valid type. Valid types are {}'.format(or_group_type, type_to_did_type_mapping.keys()))
339
+ raise exception.UnsupportedOperation(
340
+ '{} is not a valid type. Valid types are {}'.format(or_group_type, type_to_did_type_mapping.keys()))
214
341
 
215
342
  for mapped_did_type in type_to_did_type_mapping[or_group_type]:
216
343
  or_group['did_type'] = mapped_did_type
@@ -245,24 +372,32 @@ class DidColumnMeta(DidMetaPlugin):
245
372
  if recursive:
246
373
  from rucio.core.did import list_content
247
374
 
248
- # Get attached DIDs and save in list because query has to be finished before starting a new one in the recursion
375
+ # Get attached DIDs and save in a list because the query has to be finished before starting a new one in the recursion
249
376
  collections_content = []
250
377
  for did in session.execute(stmt).yield_per(100):
251
- if (did.did_type == DIDType.CONTAINER or did.did_type == DIDType.DATASET):
378
+ if did.did_type == DIDType.CONTAINER or did.did_type == DIDType.DATASET:
252
379
  collections_content += [d for d in list_content(scope=did.scope, name=did.name)]
253
380
 
254
381
  # Replace any name filtering with recursed DID names.
255
382
  for did in collections_content:
256
383
  for or_group in filters:
257
384
  or_group['name'] = did['name']
258
- for result in self.list_dids(scope=did['scope'], filters=filters, recursive=True, did_type=did_type, limit=limit, offset=offset,
259
- long=long, ignore_dids=ignore_dids, session=session):
385
+ for result in self.list_dids(scope=did['scope'],
386
+ filters=filters,
387
+ recursive=True,
388
+ did_type=did_type,
389
+ limit=limit,
390
+ offset=offset,
391
+ long=long,
392
+ ignore_dids=ignore_dids,
393
+ session=session):
260
394
  yield result
261
395
 
262
- for did in session.execute(stmt).yield_per(5): # don't unpack this as it makes it dependent on query return order!
396
+ for did in session.execute(stmt).yield_per(
397
+ 5): # don't unpack this as it makes it dependent on query return order!
263
398
  if long:
264
399
  did_full = "{}:{}".format(did.scope, did.name)
265
- if did_full not in ignore_dids: # concatenating results of OR clauses may contain duplicate DIDs if query result sets not mutually exclusive.
400
+ if did_full not in ignore_dids: # concatenating results of OR clauses may contain duplicate DIDs if the query result sets not mutually exclusive.
266
401
  ignore_dids.add(did_full)
267
402
  yield {
268
403
  'scope': did.scope,
@@ -273,21 +408,41 @@ class DidColumnMeta(DidMetaPlugin):
273
408
  }
274
409
  else:
275
410
  did_full = "{}:{}".format(did.scope, did.name)
276
- if did_full not in ignore_dids: # concatenating results of OR clauses may contain duplicate DIDs if query result sets not mutually exclusive.
411
+ if did_full not in ignore_dids: # concatenating results of OR clauses may contain duplicate DIDs if the query result sets not mutually exclusive.
277
412
  ignore_dids.add(did_full)
278
413
  yield did.name
279
414
 
280
- def delete_metadata(self, scope, name, key, *, session: "Optional[Session]" = None):
415
+ def delete_metadata(
416
+ self,
417
+ scope: "InternalScope",
418
+ name: str,
419
+ key: str,
420
+ *,
421
+ session: "Optional[Session]" = None,
422
+ ) -> None:
281
423
  """
282
- Deletes the metadata stored for the given key.
424
+ Deletes the metadata stored for the given key. (Currently not implemented)
283
425
 
284
426
  :param scope: The scope of the DID.
285
427
  :param name: The name of the DID.
286
428
  :param key: Key of the metadata.
429
+ :param session: The database session in use.
287
430
  """
288
431
  raise NotImplementedError('The DidColumnMeta plugin does not currently support deleting metadata.')
289
432
 
290
- def manages_key(self, key, *, session: "Optional[Session]" = None):
433
+ def manages_key(
434
+ self,
435
+ key: str,
436
+ *,
437
+ session: "Optional[Session]" = None,
438
+ ) -> bool:
439
+ """
440
+ Return whether a metadata key is managed by this plugin.
441
+
442
+ :param key: Key of the metadata.
443
+ :param session: Unused; accepted for interface compatibility.
444
+ :returns: ``True`` if the key is managed by this plugin, else ``False``.
445
+ """
291
446
  # Build list of which keys are managed by this plugin.
292
447
  #
293
448
  all_did_table_columns = []
@@ -323,9 +478,11 @@ class DidColumnMeta(DidMetaPlugin):
323
478
 
324
479
  return key in hardcoded_keys
325
480
 
326
- def get_plugin_name(self):
481
+ def get_plugin_name(
482
+ self
483
+ ) -> str:
327
484
  """
328
- Returns a unique identifier for this plugin. This can be later used for filtering down results to this plugin only.
485
+ Return a unique identifier for this plugin.
329
486
  :returns: The name of the plugin.
330
487
  """
331
488
  return self.plugin_name
rucio/core/replica.py CHANGED
@@ -22,7 +22,6 @@ from curses.ascii import isprint
22
22
  from datetime import datetime, timedelta
23
23
  from hashlib import sha256
24
24
  from itertools import groupby
25
- from json import dumps
26
25
  from re import match
27
26
  from struct import unpack
28
27
  from traceback import format_exc
@@ -2304,9 +2303,9 @@ def __cleanup_after_replica_deletion(
2304
2303
  for scope, name, did_type in session.execute(stmt):
2305
2304
  if did_type == DIDType.DATASET:
2306
2305
  messages.append({'event_type': 'ERASE',
2307
- 'payload': dumps({'scope': scope.external,
2308
- 'name': name,
2309
- 'account': 'root'})})
2306
+ 'payload': {'scope': scope.external,
2307
+ 'name': name,
2308
+ 'account': 'root'}})
2310
2309
  dids_to_delete.add(ScopeName(scope=scope, name=name))
2311
2310
 
2312
2311
  # Remove Archive Constituents
rucio/core/request.py CHANGED
@@ -22,6 +22,7 @@ import threading
22
22
  import traceback
23
23
  from abc import ABCMeta, abstractmethod
24
24
  from collections import defaultdict, namedtuple
25
+ from collections.abc import Sized
25
26
  from dataclasses import dataclass
26
27
  from typing import TYPE_CHECKING, Any, Optional, Union
27
28
 
@@ -310,7 +311,11 @@ def queue_requests(
310
311
  :param logger: Optional decorated logger that can be passed from the calling daemons or servers.
311
312
  :returns: List of Request-IDs as 32 character hex strings.
312
313
  """
313
- logger(logging.DEBUG, "queue requests")
314
+
315
+ if isinstance(requests, Sized):
316
+ logger(logging.DEBUG, "Queuing %d requests", len(requests))
317
+ else:
318
+ logger(logging.DEBUG, "Queuing requests")
314
319
 
315
320
  request_clause = []
316
321
  rses = {}
@@ -1508,7 +1513,7 @@ class TransferStatsManager:
1508
1513
  def __enter__(self) -> "TransferStatsManager":
1509
1514
  self.record_stats = config_get_bool('transfers', 'stats_enabled', default=self.record_stats)
1510
1515
  downsample_period = config_get_int('transfers', 'stats_downsample_period', default=self.downsample_period)
1511
- # Introduce some voluntary jitter to reduce the likely-hood of performing this database
1516
+ # Introduce some voluntary jitter to reduce the likelihood of performing this database
1512
1517
  # operation multiple times in parallel.
1513
1518
  self.downsample_period = random.randint(downsample_period * 3 // 4, math.ceil(downsample_period * 5 / 4)) # noqa: S311
1514
1519
  if self.record_stats:
rucio/core/rule.py CHANGED
@@ -37,7 +37,7 @@ import rucio.core.lock # import get_replica_locks, get_files_and_replica_locks_
37
37
  import rucio.core.replica # import get_and_lock_file_replicas, get_and_lock_file_replicas_for_dataset
38
38
  from rucio.common.cache import MemcacheRegion
39
39
  from rucio.common.config import config_get
40
- from rucio.common.constants import DEFAULT_VO, RseAttr
40
+ from rucio.common.constants import DEFAULT_ACTIVITY, DEFAULT_VO, POLICY_ALGORITHM_TYPES_LITERAL, RseAttr
41
41
  from rucio.common.exception import (
42
42
  DataIdentifierNotFound,
43
43
  DuplicateRule,
@@ -98,7 +98,7 @@ class AutoApprove(PolicyPackageAlgorithms):
98
98
  Handle automatic approval algorithms for replication rules
99
99
  """
100
100
 
101
- _algorithm_type = 'auto_approve'
101
+ _algorithm_type: POLICY_ALGORITHM_TYPES_LITERAL = 'auto_approve'
102
102
 
103
103
  def __init__(self, rule: models.ReplicationRule, did: models.DataIdentifier, session: 'Session', vo: str = DEFAULT_VO) -> None:
104
104
  super().__init__()
@@ -180,7 +180,7 @@ def add_rule(
180
180
  locked: bool,
181
181
  subscription_id: Optional[str],
182
182
  source_replica_expression: Optional[str] = None,
183
- activity: str = 'User Subscriptions',
183
+ activity: Optional[str] = None,
184
184
  notify: Optional[Literal['Y', 'N', 'C', 'P']] = None,
185
185
  purge_replicas: bool = False,
186
186
  ignore_availability: bool = False,
@@ -232,6 +232,9 @@ def add_rule(
232
232
  if copies <= 0:
233
233
  raise InvalidValueForKey("The number of copies for a replication rule should be greater than 0.")
234
234
 
235
+ if not activity:
236
+ activity = DEFAULT_ACTIVITY
237
+
235
238
  rule_ids = []
236
239
 
237
240
  grouping_value = {'ALL': RuleGrouping.ALL, 'NONE': RuleGrouping.NONE}.get(grouping, RuleGrouping.DATASET)
@@ -921,8 +921,8 @@ def __is_retry_required(lock, activity):
921
921
  :param activity: The activity of the rule.
922
922
  """
923
923
 
924
- created_at_diff = (datetime.utcnow() - lock.created_at).days * 24 * 3600 + (datetime.utcnow() - lock.created_at).seconds
925
- updated_at_diff = (datetime.utcnow() - lock.updated_at).days * 24 * 3600 + (datetime.utcnow() - lock.updated_at).seconds
924
+ created_at_diff = (datetime.utcnow() - lock.created_at).total_seconds()
925
+ updated_at_diff = (datetime.utcnow() - lock.updated_at).total_seconds()
926
926
 
927
927
  if activity == 'Express':
928
928
  if updated_at_diff > 3600 * 2:
@@ -66,7 +66,7 @@ def run_once(
66
66
 
67
67
  # If the list is empty, sent the worker to sleep
68
68
  if not updated_account_counters:
69
- logger(logging.INFO, 'did not get any work')
69
+ logger(logging.INFO, 'Did not get any work')
70
70
  return
71
71
 
72
72
  for account_counter in updated_account_counters:
@@ -71,7 +71,7 @@ def run_once(
71
71
  logger(logging.DEBUG, 'Index query time %f size=%d' % (time.time() - start, len(replicas)))
72
72
  # If the list is empty, sent the worker to sleep
73
73
  if not replicas:
74
- logger(logging.INFO, 'did not get any work')
74
+ logger(logging.INFO, 'Did not get any work')
75
75
  must_sleep = True
76
76
  return must_sleep
77
77
 
@@ -67,7 +67,7 @@ def run_once(
67
67
 
68
68
  # If the list is empty, sent the worker to sleep
69
69
  if not rse_ids:
70
- logger(logging.INFO, 'did not get any work')
70
+ logger(logging.INFO, 'Did not get any work')
71
71
  return
72
72
 
73
73
  for rse_id in rse_ids:
rucio/daemons/common.py CHANGED
@@ -200,7 +200,7 @@ def db_workqueue(
200
200
 
201
201
  with HeartbeatHandler(executable=executable, renewal_interval=sleep_time - 1) as heartbeat_handler:
202
202
  logger = heartbeat_handler.logger
203
- logger(logging.INFO, 'started')
203
+ logger(logging.INFO, 'Daemon started')
204
204
 
205
205
  if partition_wait_time:
206
206
  graceful_stop.wait(partition_wait_time)
@@ -799,19 +799,20 @@ def run_once(heartbeat_handler: "HeartbeatHandler", bulk: int, **_kwargs) -> boo
799
799
  except Exception as error:
800
800
  logger(logging.ERROR, "Error sending to ActiveMQ : %s", str(error))
801
801
 
802
- logger(logging.INFO, "Deleting %s messages", len(to_delete))
803
- to_delete = [
804
- {
805
- "id": message["id"],
806
- "created_at": message["created_at"],
807
- "updated_at": message["created_at"],
808
- "payload": str(message["payload"]),
809
- "event_type": message["event_type"],
810
- "services": message["services"]
811
- }
812
- for message in to_delete
813
- ]
814
- delete_messages(messages=to_delete)
802
+ logger(logging.INFO, "Deleting %s messages", len(to_delete))
803
+ to_delete = [
804
+ {
805
+ "id": message["id"],
806
+ "created_at": message["created_at"],
807
+ "updated_at": message["created_at"],
808
+ "payload": str(message["payload"]),
809
+ "event_type": message["event_type"],
810
+ "services": message["services"]
811
+ }
812
+ for message in to_delete
813
+ ]
814
+ delete_messages(messages=to_delete)
815
+
815
816
  must_sleep = True
816
817
  return must_sleep
817
818
 
@@ -85,10 +85,10 @@ def run_once(
85
85
  worker_number=worker_number,
86
86
  limit=200,
87
87
  blocked_rules=[key for key in paused_rules])
88
- logger(logging.DEBUG, 'index query time %f fetch size is %d' % (time.time() - start, len(rules)))
88
+ logger(logging.DEBUG, 'Index query time %f fetch size is %d' % (time.time() - start, len(rules)))
89
89
 
90
90
  if not rules:
91
- logger(logging.DEBUG, 'did not get any work (paused_rules=%s)' % str(len(paused_rules)))
91
+ logger(logging.DEBUG, 'Did not get any work (paused_rules=%s)' % str(len(paused_rules)))
92
92
  return
93
93
 
94
94
  for rule in rules: