pulpcore 3.77.1__py3-none-any.whl → 3.79.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 pulpcore might be problematic. Click here for more details.

@@ -6,6 +6,6 @@ class PulpCertGuardPluginAppConfig(PulpPluginAppConfig):
6
6
 
7
7
  name = "pulp_certguard.app"
8
8
  label = "certguard"
9
- version = "3.77.1"
9
+ version = "3.79.0"
10
10
  python_package_name = "pulpcore"
11
11
  domain_compatible = True
pulp_file/app/__init__.py CHANGED
@@ -8,6 +8,6 @@ class PulpFilePluginAppConfig(PulpPluginAppConfig):
8
8
 
9
9
  name = "pulp_file.app"
10
10
  label = "file"
11
- version = "3.77.1"
11
+ version = "3.79.0"
12
12
  python_package_name = "pulpcore"
13
13
  domain_compatible = True
pulpcore/app/apps.py CHANGED
@@ -247,7 +247,7 @@ class PulpAppConfig(PulpPluginAppConfig):
247
247
  label = "core"
248
248
 
249
249
  # The version of this app
250
- version = "3.77.1"
250
+ version = "3.79.0"
251
251
 
252
252
  # The python package name providing this app
253
253
  python_package_name = "pulpcore"
@@ -48,7 +48,7 @@ class PulpApiWorker(SyncWorker):
48
48
  from pulpcore.app.models import ApiAppStatus
49
49
 
50
50
  if settings.API_APP_TTL < 2 * self.timeout:
51
- logger.warn(
51
+ logger.warning(
52
52
  "API_APP_TTL (%s) is smaller than double the gunicorn timeout (%s). "
53
53
  "You may experience workers wrongly reporting as missing",
54
54
  settings.API_APP_TTL,
@@ -0,0 +1,52 @@
1
+ from gettext import gettext as _
2
+
3
+ from django.core.management import BaseCommand
4
+ from django.db.models import F
5
+
6
+ from pulpcore.app.models import ProgressReport
7
+ from pulpcore.constants import TASK_STATES
8
+
9
+
10
+ class Command(BaseCommand):
11
+ """
12
+ Django management command for repairing progress-reports in inconsistent states.
13
+ """
14
+
15
+ help = (
16
+ "Repairs issue #3609. Long-running tasks that utilize ProgressReports, which "
17
+ "fail or are cancelled, can leave their associated reports in state 'running'. "
18
+ "This script finds the ProgressReports marked as 'running', whose owning task "
19
+ "is in either 'cancelled or 'failed', and moves the state of the ProgressReport "
20
+ "to match that of the task."
21
+ )
22
+
23
+ def add_arguments(self, parser):
24
+ """Set up arguments."""
25
+ parser.add_argument(
26
+ "--dry-run",
27
+ action="store_true",
28
+ help=_(
29
+ "Don't modify anything, just collect results on how many ProgressReports "
30
+ "are impacted."
31
+ ),
32
+ )
33
+
34
+ def handle(self, *args, **options):
35
+ dry_run = options["dry_run"]
36
+ for state in [TASK_STATES.CANCELED, TASK_STATES.FAILED]:
37
+ if dry_run:
38
+ to_be_updated = ProgressReport.objects.filter(
39
+ task__state__ne=F("state"), state=TASK_STATES.RUNNING, task__state=state
40
+ ).count()
41
+ print(
42
+ _("Number of ProgressReports in inconsistent state for {} tasks: {}").format(
43
+ state, to_be_updated
44
+ )
45
+ )
46
+ else:
47
+ updated = ProgressReport.objects.filter(
48
+ task__state__ne=F("state"), state=TASK_STATES.RUNNING, task__state=state
49
+ ).update(state=state)
50
+ print(
51
+ _("Number of ProgressReports updated for {} tasks: {}").format(state, updated)
52
+ )
@@ -109,7 +109,7 @@ class Command(BaseCommand):
109
109
  repo_path = os.path.join(options["dest"], distribution.base_path)
110
110
  to_export.append((repo_path, publication))
111
111
  except ObjectDoesNotExist:
112
- logging.warn(
112
+ logging.warning(
113
113
  "No publication found for the repo published at '{}': skipping".format(
114
114
  distribution.base_path
115
115
  )
@@ -4,7 +4,6 @@ Django models related to the Tasking system
4
4
 
5
5
  import logging
6
6
  import traceback
7
- from contextlib import suppress
8
7
  from datetime import timedelta
9
8
  from gettext import gettext as _
10
9
 
@@ -76,6 +75,21 @@ class Task(BaseModel, AutoAddObjPermsMixin):
76
75
  """
77
76
  Represents a task
78
77
 
78
+ The Tasks state machine works like a finite automaton without loops:
79
+ The initial state is WAITING.
80
+ Final states are COMPLETED, FAILED and CANCELED.
81
+ The possible transitions are:
82
+ WAITING -> RUNNING
83
+ WAITING ->* CANCELING
84
+ RUNNING -> COMPLETED
85
+ RUNNING -> FAILED
86
+ RUNNING ->* CANCELING
87
+ CANCELING -> CANCELED
88
+
89
+ The transitions to CANCELING (marked with *) are the only ones allowed to happen without
90
+ holding the tasks advisory lock. Canceling is meant to be initiated asyncronously by a sparate
91
+ process before signalling the worker via Postgres LISTEN.
92
+
79
93
  Fields:
80
94
 
81
95
  state (models.TextField): The state of the task
@@ -180,24 +194,26 @@ class Task(BaseModel, AutoAddObjPermsMixin):
180
194
  """Set the "core.task_user_dispatcher" role for the current user after creation."""
181
195
  self.add_roles_for_object_creator("core.task_user_dispatcher")
182
196
 
197
+ def _cleanup_progress_reports(self, state):
198
+ """Find any running progress-reports and set their states to the specified end-state."""
199
+ self.progress_reports.filter(state=TASK_STATES.RUNNING).update(state=state)
200
+
183
201
  def set_running(self):
184
202
  """
185
203
  Set this Task to the running state, save it, and log output in warning cases.
186
204
 
187
205
  This updates the :attr:`started_at` and sets the :attr:`state` to :attr:`RUNNING`.
188
206
  """
207
+ started_at = timezone.now()
189
208
  rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.WAITING).update(
190
- state=TASK_STATES.RUNNING, started_at=timezone.now()
209
+ state=TASK_STATES.RUNNING,
210
+ started_at=started_at,
191
211
  )
192
- with suppress(AttributeError):
193
- del self.state
194
- with suppress(AttributeError):
195
- del self.started_at
196
- with suppress(AttributeError):
197
- del self.finished_at
198
- with suppress(AttributeError):
199
- del self.error
200
- if rows != 1:
212
+ if rows == 1:
213
+ self.state = TASK_STATES.RUNNING
214
+ self.started_at = started_at
215
+ else:
216
+ self.refresh_from_db()
201
217
  raise RuntimeError(
202
218
  _("Attempt to set not waiting task {} to running from '{}'.").format(
203
219
  self.pk, self.state
@@ -212,18 +228,16 @@ class Task(BaseModel, AutoAddObjPermsMixin):
212
228
  """
213
229
  # Only set the state to finished if it's running. This is important for when the task has
214
230
  # been canceled, so we don't move the task from canceled to finished.
231
+ finished_at = timezone.now()
215
232
  rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.RUNNING).update(
216
- state=TASK_STATES.COMPLETED, finished_at=timezone.now()
233
+ state=TASK_STATES.COMPLETED,
234
+ finished_at=finished_at,
217
235
  )
218
- with suppress(AttributeError):
219
- del self.state
220
- with suppress(AttributeError):
221
- del self.started_at
222
- with suppress(AttributeError):
223
- del self.finished_at
224
- with suppress(AttributeError):
225
- del self.error
226
- if rows != 1:
236
+ if rows == 1:
237
+ self.state = TASK_STATES.COMPLETED
238
+ self.finished_at = finished_at
239
+ else:
240
+ self.refresh_from_db()
227
241
  # If the user requested to cancel this task while the worker finished it, we leave it
228
242
  # as it is, but accept this is not an error condition.
229
243
  if self.state != TASK_STATES.CANCELING:
@@ -232,6 +246,7 @@ class Task(BaseModel, AutoAddObjPermsMixin):
232
246
  self.pk, self.state
233
247
  )
234
248
  )
249
+ self._cleanup_progress_reports(TASK_STATES.COMPLETED)
235
250
 
236
251
  def set_failed(self, exc, tb):
237
252
  """
@@ -244,26 +259,26 @@ class Task(BaseModel, AutoAddObjPermsMixin):
244
259
  exc (Exception): The exception raised by the task.
245
260
  tb (traceback): Traceback instance for the current exception.
246
261
  """
262
+ finished_at = timezone.now()
247
263
  tb_str = "".join(traceback.format_tb(tb))
264
+ error = exception_to_dict(exc, tb_str)
248
265
  rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.RUNNING).update(
249
266
  state=TASK_STATES.FAILED,
250
- finished_at=timezone.now(),
251
- error=exception_to_dict(exc, tb_str),
267
+ finished_at=finished_at,
268
+ error=error,
252
269
  )
253
- with suppress(AttributeError):
254
- del self.state
255
- with suppress(AttributeError):
256
- del self.started_at
257
- with suppress(AttributeError):
258
- del self.finished_at
259
- with suppress(AttributeError):
260
- del self.error
261
- if rows != 1:
270
+ if rows == 1:
271
+ self.state = TASK_STATES.FAILED
272
+ self.finished_at = finished_at
273
+ self.error = error
274
+ else:
275
+ self.refresh_from_db()
262
276
  raise RuntimeError(
263
277
  _("Attempt to set not running task {} to failed from '{}'.").format(
264
278
  self.pk, self.state
265
279
  )
266
280
  )
281
+ self._cleanup_progress_reports(TASK_STATES.FAILED)
267
282
 
268
283
  def set_canceling(self):
269
284
  """
@@ -274,15 +289,10 @@ class Task(BaseModel, AutoAddObjPermsMixin):
274
289
  rows = Task.objects.filter(pk=self.pk, state__in=TASK_INCOMPLETE_STATES).update(
275
290
  state=TASK_STATES.CANCELING,
276
291
  )
277
- with suppress(AttributeError):
278
- del self.state
279
- with suppress(AttributeError):
280
- del self.started_at
281
- with suppress(AttributeError):
282
- del self.finished_at
283
- with suppress(AttributeError):
284
- del self.error
285
- if rows != 1:
292
+ if rows == 1:
293
+ self.state = TASK_STATES.CANCELING
294
+ else:
295
+ self.refresh_from_db()
286
296
  raise RuntimeError(
287
297
  _("Attempt to set not incomplete task {} to canceling from '{}'.").format(
288
298
  self.pk, self.state
@@ -295,50 +305,40 @@ class Task(BaseModel, AutoAddObjPermsMixin):
295
305
  """
296
306
  # Make sure this function was called with a proper final state
297
307
  assert final_state in [TASK_STATES.CANCELED, TASK_STATES.FAILED]
308
+ finished_at = timezone.now()
298
309
  task_data = {}
299
310
  if reason:
300
311
  task_data["error"] = {"reason": reason}
301
312
  rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.CANCELING).update(
302
313
  state=final_state,
303
- finished_at=timezone.now(),
314
+ finished_at=finished_at,
304
315
  **task_data,
305
316
  )
306
- with suppress(AttributeError):
307
- del self.state
308
- with suppress(AttributeError):
309
- del self.started_at
310
- with suppress(AttributeError):
311
- del self.finished_at
312
- with suppress(AttributeError):
313
- del self.error
314
- if rows != 1:
317
+ if rows == 1:
318
+ self.state = final_state
319
+ self.finished_at = finished_at
320
+ if reason:
321
+ self.error = task_data["error"]
322
+ else:
323
+ self.refresh_from_db()
315
324
  raise RuntimeError(
316
325
  _("Attempt to set not canceling task {} to canceled from '{}'.").format(
317
326
  self.pk, self.state
318
327
  )
319
328
  )
329
+ self._cleanup_progress_reports(final_state)
320
330
 
321
331
  def unblock(self):
322
332
  # This should be safe to be called without holding the lock.
323
- Task.objects.filter(pk=self.pk).update(unblocked_at=timezone.now())
324
- with suppress(AttributeError):
325
- del self.unblocked_at
326
-
327
- # Example taken from here:
328
- # https://docs.djangoproject.com/en/3.2/ref/models/instances/#refreshing-objects-from-database
329
- def refresh_from_db(self, using=None, fields=None, **kwargs):
330
- # fields contains the name of the deferred field to be
331
- # loaded.
332
- if fields is not None:
333
- fields = set(fields)
334
- deferred_fields = {
335
- field for field in self.get_deferred_fields() if not field.startswith("enc_")
336
- }
337
- # If any state related deferred field is going to be loaded
338
- if fields.intersection(deferred_fields):
339
- # then load all of them
340
- fields = fields.union(deferred_fields)
341
- super().refresh_from_db(using, fields, **kwargs)
333
+ unblocked_at = timezone.now()
334
+ rows = Task.objects.filter(pk=self.pk).update(unblocked_at=unblocked_at)
335
+ if rows == 1:
336
+ self.unblocked_at = unblocked_at
337
+ else:
338
+ self.refresh_from_db()
339
+ raise RuntimeError(
340
+ _("Falied to set task {} unblocked in state '{}'.").format(self.pk, self.state)
341
+ )
342
342
 
343
343
  class Meta:
344
344
  indexes = [
pulpcore/app/openpgp.py CHANGED
@@ -289,15 +289,15 @@ def analyze_signature(data, pubkey, signed_packet_type, signed_packet):
289
289
  if version == 4:
290
290
  hash_payload = b"\x99" + len(signed_packet).to_bytes(2, "big") + signed_packet
291
291
  else: # version == 5
292
- hash_payload = b"\x9A" + len(signed_packet).to_bytes(4, "big") + signed_packet
292
+ hash_payload = b"\x9a" + len(signed_packet).to_bytes(4, "big") + signed_packet
293
293
  elif signature_type in [0x10, 0x11, 0x12, 0x13, 0x16, 0x30]:
294
294
  # 0x10 - 0x13 Certification of a user id or attribute
295
295
  # 0x16 Attested Key Signature
296
296
  # 0x30 Certification Revocation Signature
297
297
  if signed_packet_type == 13:
298
- hash_payload = b"\xB4" + len(signed_packet).to_bytes(4, "big") + signed_packet
298
+ hash_payload = b"\xb4" + len(signed_packet).to_bytes(4, "big") + signed_packet
299
299
  elif signed_packet_type == 17:
300
- hash_payload = b"\xD1" + len(signed_packet).to_bytes(4, "big") + signed_packet
300
+ hash_payload = b"\xd1" + len(signed_packet).to_bytes(4, "big") + signed_packet
301
301
  else:
302
302
  raise ValueError("Out of band user ID or attribute signature.")
303
303
  elif signature_type in [0x1F, 0x20, 0x30]:
@@ -318,18 +318,18 @@ def analyze_signature(data, pubkey, signed_packet_type, signed_packet):
318
318
  if version == 4:
319
319
  h.update(b"\x99" + len(pubkey).to_bytes(2, "big") + pubkey)
320
320
  else: # version == 5
321
- h.update(b"\x9A" + len(pubkey).to_bytes(4, "big") + pubkey)
321
+ h.update(b"\x9a" + len(pubkey).to_bytes(4, "big") + pubkey)
322
322
  h.update(hash_payload)
323
323
  if version == 4:
324
324
  h.update(
325
325
  data[: 6 + hashed_size]
326
- + b"\x04\xFF"
326
+ + b"\x04\xff"
327
327
  + ((6 + hashed_size) % (1 << 32)).to_bytes(4, "big")
328
328
  )
329
329
  else: # version == 5
330
330
  h.update(
331
331
  data[: 6 + hashed_size]
332
- + b"\x05\xFF"
332
+ + b"\x05\xff"
333
333
  + ((6 + hashed_size) % (1 << 64)).to_bytes(8, "big")
334
334
  )
335
335
  if not h.digest().startswith(canary):
pulpcore/app/replica.py CHANGED
@@ -27,7 +27,7 @@ class ReplicaContext(PulpContext):
27
27
  if err:
28
28
  self.err_buf += message
29
29
  if nl:
30
- _logger.warn("{}", self.err_buf)
30
+ _logger.warning("{}", self.err_buf)
31
31
  self.err_buf = ""
32
32
  else:
33
33
  self.out_buf += message
@@ -112,7 +112,7 @@ class _DetailFieldMixin(HrefPrnFieldMixin):
112
112
  if view_name_pattern:
113
113
  view_name = _MatchingRegexViewName(view_name_pattern)
114
114
  else:
115
- log.warn(
115
+ log.warning(
116
116
  _(
117
117
  "Please provide either 'view_name' or 'view_name_pattern' for {} on {}."
118
118
  ).format(self.__class__.__name__, traceback.extract_stack()[-4][2])
@@ -24,8 +24,10 @@ from pulpcore.app.serializers import (
24
24
  PRNField,
25
25
  )
26
26
  from pulpcore.app.util import (
27
- get_viewset_for_model,
28
27
  get_request_without_query_params,
28
+ get_url,
29
+ get_prn,
30
+ resolve_prn,
29
31
  )
30
32
 
31
33
  User = get_user_model()
@@ -60,27 +62,42 @@ class PermissionField(serializers.RelatedField):
60
62
  class ContentObjectField(serializers.CharField):
61
63
  """Content object field"""
62
64
 
63
- def to_representation(self, obj):
64
- content_object = getattr(obj, "content_object", None)
65
- if content_object:
66
- viewset = get_viewset_for_model(obj.content_object)
67
-
65
+ def to_representation(self, value):
66
+ if value is None:
67
+ return None
68
+ else:
68
69
  request = get_request_without_query_params(self.context)
70
+ return get_url(value, request=request)
69
71
 
70
- serializer = viewset.serializer_class(obj.content_object, context={"request": request})
71
- return serializer.data.get("pulp_href")
72
-
73
- def to_internal_value(self, data):
72
+ def to_internal_value(self, value):
74
73
  # ... circular import ...
75
74
  from pulpcore.app.viewsets.base import NamedModelViewSet
76
75
 
77
- if data is None:
78
- return {"content_object": None}
79
- try:
80
- obj = NamedModelViewSet.get_resource(data)
81
- except serializers.ValidationError:
82
- raise serializers.ValidationError(_("Invalid value: {}.").format(data))
83
- return {"content_object": obj}
76
+ if value is None:
77
+ return None
78
+ else:
79
+ try:
80
+ obj = NamedModelViewSet.get_resource(value)
81
+ except serializers.ValidationError:
82
+ raise serializers.ValidationError(_("Invalid value: {}.").format(value))
83
+ return obj
84
+
85
+
86
+ class ContentObjectPRNField(serializers.CharField):
87
+ """Content object PRN field"""
88
+
89
+ def to_representation(self, value):
90
+ if value is None:
91
+ return None
92
+ else:
93
+ return get_prn(value)
94
+
95
+ def to_internal_value(self, value):
96
+ if value is None:
97
+ return None
98
+ else:
99
+ model, pk = resolve_prn(value)
100
+ return model.objects.get(pk=pk)
84
101
 
85
102
 
86
103
  class UserGroupSerializer(serializers.ModelSerializer):
@@ -247,6 +264,13 @@ class ValidateRoleMixin:
247
264
  and checks if the user/group already has the role. Does not set any value
248
265
  in data or return anything.
249
266
  """
267
+ if "content_object" not in data:
268
+ raise serializers.ValidationError(
269
+ _(
270
+ "Either 'content_object' or 'content_object_prn' needs to be specified."
271
+ " Use 'null' for global or domain level access."
272
+ )
273
+ )
250
274
  natural_key_args = {
251
275
  f"{role_type}_id": data[role_type].pk,
252
276
  "role_id": data["role"].pk,
@@ -308,8 +332,18 @@ class UserRoleSerializer(ValidateRoleMixin, ModelSerializer, NestedHyperlinkedMo
308
332
  "pulp_href of the object for which role permissions should be asserted. "
309
333
  "If set to 'null', permissions will act on either domain or model-level."
310
334
  ),
311
- source="*",
312
335
  allow_null=True,
336
+ required=False,
337
+ )
338
+
339
+ content_object_prn = ContentObjectPRNField(
340
+ help_text=_(
341
+ "prn of the object for which role permissions should be asserted. "
342
+ "If set to 'null', permissions will act on either domain or model-level."
343
+ ),
344
+ source="content_object",
345
+ allow_null=True,
346
+ required=False,
313
347
  )
314
348
 
315
349
  domain = RelatedField(
@@ -343,6 +377,7 @@ class UserRoleSerializer(ValidateRoleMixin, ModelSerializer, NestedHyperlinkedMo
343
377
  fields = ModelSerializer.Meta.fields + (
344
378
  "role",
345
379
  "content_object",
380
+ "content_object_prn",
346
381
  "description",
347
382
  "permissions",
348
383
  "domain",
@@ -366,8 +401,18 @@ class GroupRoleSerializer(ValidateRoleMixin, ModelSerializer, NestedHyperlinkedM
366
401
  "pulp_href of the object for which role permissions should be asserted. "
367
402
  "If set to 'null', permissions will act on the model-level."
368
403
  ),
369
- source="*",
370
404
  allow_null=True,
405
+ required=False,
406
+ )
407
+
408
+ content_object_prn = ContentObjectPRNField(
409
+ help_text=_(
410
+ "prn of the object for which role permissions should be asserted. "
411
+ "If set to 'null', permissions will act on either domain or model-level."
412
+ ),
413
+ source="content_object",
414
+ allow_null=True,
415
+ required=False,
371
416
  )
372
417
 
373
418
  domain = RelatedField(
@@ -403,6 +448,7 @@ class GroupRoleSerializer(ValidateRoleMixin, ModelSerializer, NestedHyperlinkedM
403
448
  fields = ModelSerializer.Meta.fields + (
404
449
  "role",
405
450
  "content_object",
451
+ "content_object_prn",
406
452
  "description",
407
453
  "permissions",
408
454
  "domain",
pulpcore/app/settings.py CHANGED
@@ -342,9 +342,7 @@ CACHE_SETTINGS = {
342
342
  "EXPIRES_TTL": 600, # 10 minutes
343
343
  }
344
344
 
345
- # The time a RemoteArtifact will be ignored after failure.
346
- # In on-demand, if a fetching content from a remote failed due to corrupt data,
347
- # the corresponding RemoteArtifact will be ignored for that time (seconds).
345
+ # The time in seconds a RemoteArtifact will be ignored after failure.
348
346
  REMOTE_CONTENT_FETCH_FAILURE_COOLDOWN = 5 * 60 # 5 minutes
349
347
 
350
348
  SPECTACULAR_SETTINGS = {
@@ -425,6 +423,9 @@ KAFKA_SASL_PASSWORD = None
425
423
  # opentelemetry settings
426
424
  OTEL_ENABLED = False
427
425
 
426
+ # Replaces asyncio event loop with uvloop
427
+ UVLOOP_ENABLED = False
428
+
428
429
  # HERE STARTS DYNACONF EXTENSION LOAD (Keep at the very bottom of settings.py)
429
430
  # Read more at https://www.dynaconf.com/django/
430
431
 
@@ -79,7 +79,7 @@ async def _repair_ca(content_artifact, repaired=None):
79
79
  try:
80
80
  dl_result = await downloader.run()
81
81
  except Exception as e:
82
- log.warn(_("Redownload failed from '{}': {}.").format(remote_artifact.url, str(e)))
82
+ log.warning(_("Redownload failed from '{}': {}.").format(remote_artifact.url, str(e)))
83
83
  else:
84
84
  if dl_result.artifact_attributes["sha256"] == content_artifact.artifact.sha256:
85
85
  with open(dl_result.path, "rb") as src:
@@ -138,7 +138,7 @@ async def _repair_artifacts_for_content(subset=None, verify_checksums=True):
138
138
  valid = await loop.run_in_executor(None, storage.exists, artifact.file.name)
139
139
  if not valid:
140
140
  await missing.aincrement()
141
- log.warn(_("Missing file for {}").format(artifact))
141
+ log.warning(_("Missing file for {}").format(artifact))
142
142
  elif verify_checksums:
143
143
  # default ThreadPoolExecutor uses num cores x 5 threads. Since we're doing
144
144
  # such long and sequential reads, using too many threads might hurt more
@@ -151,7 +151,7 @@ async def _repair_artifacts_for_content(subset=None, verify_checksums=True):
151
151
  )
152
152
  if not valid:
153
153
  await corrupted.aincrement()
154
- log.warn(_("Digest mismatch for {}").format(artifact))
154
+ log.warning(_("Digest mismatch for {}").format(artifact))
155
155
 
156
156
  if not valid:
157
157
  if len(pending) >= 5: # Limit the number of concurrent repair tasks
pulpcore/app/util.py CHANGED
@@ -66,18 +66,19 @@ def get_url(model, domain=None, request=None):
66
66
  str: The path component of the resource url
67
67
  """
68
68
  kwargs = {}
69
- view_action = "list"
70
69
  if settings.DOMAIN_ENABLED:
71
70
  kwargs["pulp_domain"] = "default"
72
- if not domain and hasattr(model, "pulp_domain") and isinstance(model, model._meta.model):
71
+ if not domain and hasattr(model, "pulp_domain") and isinstance(model, Model):
73
72
  kwargs["pulp_domain"] = model.pulp_domain.name
74
73
  elif isinstance(domain, models.Domain):
75
74
  kwargs["pulp_domain"] = domain.name
76
75
  elif isinstance(domain, str):
77
76
  kwargs["pulp_domain"] = domain
78
- if isinstance(model, model._meta.model):
77
+ if isinstance(model, Model):
79
78
  view_action = "detail"
80
79
  kwargs["pk"] = model.pk
80
+ else:
81
+ view_action = "list"
81
82
 
82
83
  return reverse(get_view_name_for_model(model, view_action), kwargs=kwargs, request=request)
83
84
 
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  from contextlib import suppress
3
3
  from importlib import import_module
4
+ from importlib.util import find_spec
4
5
  import logging
5
6
  import os
6
7
 
@@ -35,6 +36,13 @@ if settings.OTEL_ENABLED:
35
36
  else:
36
37
  app = web.Application(middlewares=[guid, authenticate])
37
38
 
39
+
40
+ if settings.UVLOOP_ENABLED:
41
+ if not find_spec("uvloop"):
42
+ raise RuntimeError("The library 'uvloop' must be installed if UVLOOP_ENABLED is true.")
43
+ log.info("Using uvloop as the asyncio event loop.")
44
+
45
+
38
46
  CONTENT_MODULE_NAME = "content"
39
47
 
40
48
 
@@ -1,11 +1,17 @@
1
1
  import click
2
2
  from pulpcore.app.pulpcore_gunicorn_application import PulpcoreGunicornApplication
3
+ from django.conf import settings
3
4
 
4
5
 
5
6
  class PulpcoreContentApplication(PulpcoreGunicornApplication):
6
7
  def load_app_specific_config(self):
8
+ worker_class = (
9
+ "aiohttp.GunicornUVLoopWebWorker"
10
+ if settings.UVLOOP_ENABLED
11
+ else "aiohttp.GunicornWebWorker"
12
+ )
7
13
  self.set_option("default_proc_name", "pulpcore-content", enforced=True)
8
- self.set_option("worker_class", "aiohttp.GunicornWebWorker", enforced=True)
14
+ self.set_option("worker_class", worker_class, enforced=True)
9
15
 
10
16
  def load(self):
11
17
  import pulpcore.content
@@ -1314,10 +1314,9 @@ class Handler:
1314
1314
  except DigestValidationError:
1315
1315
  remote_artifact.failed_at = timezone.now()
1316
1316
  await remote_artifact.asave()
1317
- await downloader.session.close()
1318
1317
  close_tcp_connection(request.transport._sock)
1319
1318
  REMOTE_CONTENT_FETCH_FAILURE_COOLDOWN = settings.REMOTE_CONTENT_FETCH_FAILURE_COOLDOWN
1320
- raise RuntimeError(
1319
+ log.error(
1321
1320
  f"Pulp tried streaming {remote_artifact.url!r} to "
1322
1321
  "the client, but it failed checksum validation.\n\n"
1323
1322
  "We can't recover from wrong data already sent so we are:\n"
@@ -1327,8 +1326,10 @@ class Handler:
1327
1326
  "If the Remote is known to be fixed, try resyncing the associated repository.\n"
1328
1327
  "If the Remote is known to be permanently corrupted, try removing "
1329
1328
  "affected Pulp Remote, adding a good one and resyncing.\n"
1330
- "If the problem persists, please contact the Pulp team."
1329
+ "Learn more on <https://pulpproject.org/pulpcore/docs/user/learn/"
1330
+ "on-demand-downloading/#on-demand-and-streamed-limitations>"
1331
1331
  )
1332
+ return response
1332
1333
 
1333
1334
  if save_artifact and remote.policy != Remote.STREAMED:
1334
1335
  content_artifacts = await asyncio.shield(
pulpcore/tasking/tasks.py CHANGED
@@ -127,7 +127,18 @@ def _execute_task(task):
127
127
  send_task_notification(task)
128
128
  else:
129
129
  task.set_completed()
130
- _logger.info("Task completed %s in domain: %s", task.pk, domain.name)
130
+ execution_time = task.finished_at - task.started_at
131
+ execution_time_us = int(execution_time.total_seconds() * 1_000_000) # μs
132
+ _logger.info(
133
+ "Task completed %s in domain:"
134
+ " %s, task_type: %s, immediate: %s, deferred: %s, execution_time: %s μs",
135
+ task.pk,
136
+ domain.name,
137
+ task.name,
138
+ str(task.immediate),
139
+ str(task.deferred),
140
+ execution_time_us,
141
+ )
131
142
  send_task_notification(task)
132
143
 
133
144
 
@@ -309,7 +309,7 @@ class PulpcoreWorker:
309
309
  # But during at least one specific upgrade this situation can emerge.
310
310
  # In this case, we can assume that the old algorithm was employed to identify the
311
311
  # task as unblocked, and we just rectify the situation here.
312
- _logger.warn(
312
+ _logger.warning(
313
313
  "Running task %s was not previously marked unblocked. Fixing.", task.pk
314
314
  )
315
315
  task.unblock()
@@ -117,6 +117,7 @@ def test_remote_content_changed_with_on_demand(
117
117
  file_bindings,
118
118
  monitor_task,
119
119
  file_distribution_factory,
120
+ tmp_path,
120
121
  ):
121
122
  """
122
123
  GIVEN a remote synced on demand with fileA (e.g, digest=123),
@@ -124,6 +125,7 @@ def test_remote_content_changed_with_on_demand(
124
125
 
125
126
  WHEN the client first requests that content
126
127
  THEN the content app will start a response but close the connection before finishing
128
+ AND no file will be present in the filesystem
127
129
 
128
130
  WHEN the client requests that content again (within the RA cooldown interval)
129
131
  THEN the content app will return a 404
@@ -143,9 +145,12 @@ def test_remote_content_changed_with_on_demand(
143
145
  get_url = urljoin(distribution_base_url(distribution.base_url), expected_file_list[0][0])
144
146
 
145
147
  # WHEN (first request)
146
- result = subprocess.run(["curl", "-v", get_url], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
148
+ output_file = tmp_path / "out.rpm"
149
+ cmd = ["curl", "-v", get_url, "-o", str(output_file)]
150
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
147
151
 
148
152
  # THEN
153
+ assert not output_file.exists()
149
154
  assert result.returncode == 18
150
155
  assert b"* Closing connection 0" in result.stderr
151
156
  assert b"curl: (18) transfer closed with outstanding read data remaining" in result.stderr
@@ -211,10 +216,6 @@ def test_handling_remote_artifact_on_demand_streaming_failure(
211
216
  distribution = file_distribution_factory(repository=repo.pulp_href)
212
217
  return distribution
213
218
 
214
- def refresh_acs(acs):
215
- monitor_task_group(file_bindings.AcsFileApi.refresh(acs.pulp_href).task_group)
216
- return acs
217
-
218
219
  def get_original_content_info(remote):
219
220
  expected_files = get_files_in_manifest(remote.url)
220
221
  content_unit = list(expected_files)[0]
@@ -231,8 +232,7 @@ def test_handling_remote_artifact_on_demand_streaming_failure(
231
232
  acs_manifest_path = write_3_iso_file_fixture_data_factory("acs", seed=123)
232
233
  remote = create_simple_remote(basic_manifest_path)
233
234
  distribution = sync_publish_and_distribute(remote)
234
- acs = create_acs_remote(acs_manifest_path)
235
- refresh_acs(acs)
235
+ create_acs_remote(acs_manifest_path)
236
236
  write_3_iso_file_fixture_data_factory("acs", overwrite=True) # corrupt
237
237
 
238
238
  # WHEN/THEN (first request)
@@ -9,8 +9,6 @@ from collections import namedtuple
9
9
  from urllib.parse import urljoin
10
10
  from uuid import uuid4
11
11
 
12
- from .pulpperf import reporting
13
-
14
12
  Args = namedtuple("Arguments", "limit processes repositories")
15
13
 
16
14
 
@@ -92,7 +90,7 @@ def test_performance(
92
90
  responses.append(response)
93
91
 
94
92
  results = [monitor_task(response.task) for response in responses]
95
- reporting.report_tasks_stats("Sync tasks", results)
93
+ report_tasks_stats("Sync tasks", results)
96
94
 
97
95
  """Measure time of resynchronization."""
98
96
  responses = []
@@ -102,7 +100,7 @@ def test_performance(
102
100
  responses.append(response)
103
101
 
104
102
  results = [monitor_task(response.task) for response in responses]
105
- reporting.report_tasks_stats("Resync tasks", results)
103
+ report_tasks_stats("Resync tasks", results)
106
104
 
107
105
  """Measure time of repository publishing."""
108
106
  responses = []
@@ -111,7 +109,7 @@ def test_performance(
111
109
  responses.append(response)
112
110
 
113
111
  results = [monitor_task(response.task) for response in responses]
114
- reporting.report_tasks_stats("Publication tasks", results)
112
+ report_tasks_stats("Publication tasks", results)
115
113
 
116
114
  for i in range(len(results)):
117
115
  data[i]["publication_href"] = results[i].created_resources[0]
@@ -132,7 +130,7 @@ def test_performance(
132
130
  responses.append(response)
133
131
 
134
132
  results = [monitor_task(response.task) for response in responses]
135
- reporting.report_tasks_stats("Distribution tasks", results)
133
+ report_tasks_stats("Distribution tasks", results)
136
134
 
137
135
  for i in range(len(results)):
138
136
  data[i]["distribution_href"] = results[i].created_resources[0]
@@ -152,7 +150,7 @@ def test_performance(
152
150
  pool.starmap(download, params)
153
151
 
154
152
  after = datetime.datetime.utcnow()
155
- reporting.print_fmt_experiment_time("Repository download", before, after)
153
+ print_fmt_experiment_time("Repository download", before, after)
156
154
 
157
155
  """Measure time of inspecting the repository content."""
158
156
  before = datetime.datetime.utcnow()
@@ -172,7 +170,7 @@ def test_performance(
172
170
  with multiprocessing.Pool(processes=args.processes) as pool:
173
171
  pool.starmap(measureit, params)
174
172
  after = datetime.datetime.utcnow()
175
- reporting.print_fmt_experiment_time("Content inspection", before, after)
173
+ print_fmt_experiment_time("Content inspection", before, after)
176
174
 
177
175
  """Measure time of repository cloning."""
178
176
  for r in data:
@@ -192,7 +190,7 @@ def test_performance(
192
190
  responses.append(response)
193
191
 
194
192
  results = [monitor_task(response.task) for response in responses]
195
- reporting.report_tasks_stats("Version clone with base_version tasks", results)
193
+ report_tasks_stats("Version clone with base_version tasks", results)
196
194
 
197
195
  hrefs = [
198
196
  i["pulp_href"]
@@ -206,7 +204,7 @@ def test_performance(
206
204
  responses.append(response)
207
205
 
208
206
  results = [monitor_task(response.task) for response in responses]
209
- reporting.report_tasks_stats("Version clone with add_content_units tasks", results)
207
+ report_tasks_stats("Version clone with add_content_units tasks", results)
210
208
 
211
209
 
212
210
  def download(base_url, file_name, file_size):
@@ -0,0 +1,44 @@
1
+ import pytest
2
+ import sys
3
+ from pulpcore.app.models import Task, ProgressReport
4
+ from pulpcore.constants import TASK_STATES
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ "to_state,use_canceled",
9
+ [
10
+ (TASK_STATES.FAILED, False),
11
+ (TASK_STATES.CANCELED, False),
12
+ (TASK_STATES.CANCELED, True),
13
+ ],
14
+ )
15
+ @pytest.mark.django_db
16
+ def test_report_state_changes(to_state, use_canceled):
17
+ task = Task.objects.create(name="test", state=TASK_STATES.RUNNING)
18
+ reports = {}
19
+ for state in vars(TASK_STATES):
20
+ report = ProgressReport(message="test", code="test", state=state, task=task)
21
+ report.save()
22
+ reports[state] = report
23
+
24
+ if TASK_STATES.FAILED == to_state:
25
+ # Two ways to fail a task - set_failed and set_canceled("failed")
26
+ if use_canceled:
27
+ task.set_cancelling()
28
+ task.set_canceled(TASK_STATES.FAILED)
29
+ else:
30
+ try:
31
+ raise ValueError("test")
32
+ except ValueError:
33
+ exc_type, exc, tb = sys.exc_info()
34
+ task.set_failed(exc, tb)
35
+ elif TASK_STATES.CANCELED == to_state:
36
+ task.set_canceling()
37
+ task.set_canceled()
38
+
39
+ for state in vars(TASK_STATES):
40
+ report = ProgressReport.objects.get(pk=reports[state].pulp_id)
41
+ if TASK_STATES.RUNNING == state: # report *was* running, should be changed
42
+ assert to_state == report.state
43
+ else:
44
+ assert state == report.state
@@ -0,0 +1,86 @@
1
+ import pytest
2
+ from unittest.mock import Mock
3
+
4
+ from django.contrib.auth import get_user_model
5
+ from django.contrib.auth.models import Group
6
+ from rest_framework.exceptions import ValidationError
7
+
8
+ from pulpcore.app.util import get_url, get_prn
9
+ from pulpcore.app.serializers import UserRoleSerializer, GroupRoleSerializer
10
+ from pulp_file.app.models import FileRepository
11
+
12
+
13
+ pytestmark = [pytest.mark.django_db]
14
+
15
+
16
+ @pytest.fixture(params=[UserRoleSerializer, GroupRoleSerializer])
17
+ def serializer_class(request):
18
+ return request.param
19
+
20
+
21
+ @pytest.fixture
22
+ def context(db, serializer_class):
23
+ request = Mock()
24
+ if serializer_class == UserRoleSerializer:
25
+ User = get_user_model()
26
+ user = User.objects.create()
27
+ request.resolver_match.kwargs = {"user_pk": user.pk}
28
+ elif serializer_class == GroupRoleSerializer:
29
+ group = Group.objects.create()
30
+ request.resolver_match.kwargs = {"group_pk": group.pk}
31
+ else:
32
+ pytest.fail("This fixture received an unknown serializer class.")
33
+ return {"request": request}
34
+
35
+
36
+ @pytest.fixture
37
+ def repository(db):
38
+ return FileRepository.objects.create(name="test1")
39
+
40
+
41
+ @pytest.mark.parametrize(
42
+ "field",
43
+ [
44
+ "role",
45
+ "content_object",
46
+ "content_object_prn",
47
+ ],
48
+ )
49
+ def test_nested_role_serializer_has_certain_fields(serializer_class, field):
50
+ serializer = serializer_class()
51
+ assert field in serializer.fields
52
+
53
+
54
+ def test_nested_role_serializer_fails_without_content(serializer_class, context):
55
+ data = {"role": "file.filerepository_owner"}
56
+ serializer = serializer_class(data=data, context=context)
57
+ with pytest.raises(ValidationError):
58
+ serializer.is_valid(raise_exception=True)
59
+
60
+
61
+ def test_nested_role_serializer_with_null_content(serializer_class, context):
62
+ data = {"role": "file.filerepository_owner", "content_object": None}
63
+ serializer = serializer_class(data=data, context=context)
64
+ assert serializer.is_valid(raise_exception=True)
65
+ assert serializer.validated_data["content_object"] is None
66
+
67
+
68
+ def test_nested_role_serializer_with_null_content_prn(serializer_class, context):
69
+ data = {"role": "file.filerepository_owner", "content_object_prn": None}
70
+ serializer = serializer_class(data=data, context=context)
71
+ assert serializer.is_valid(raise_exception=True)
72
+ assert serializer.validated_data["content_object"] is None
73
+
74
+
75
+ def test_nested_role_serializer_allows_href(serializer_class, context, repository):
76
+ data = {"role": "file.filerepository_owner", "content_object": get_url(repository)}
77
+ serializer = serializer_class(data=data, context=context)
78
+ assert serializer.is_valid(raise_exception=True)
79
+ assert serializer.validated_data["content_object"] == repository
80
+
81
+
82
+ def test_nested_role_serializer_allows_prn(serializer_class, context, repository):
83
+ data = {"role": "file.filerepository_owner", "content_object_prn": get_prn(repository)}
84
+ serializer = serializer_class(data=data, context=context)
85
+ assert serializer.is_valid(raise_exception=True)
86
+ assert serializer.validated_data["content_object"] == repository
@@ -33,7 +33,7 @@ class TestGetResource(TestCase):
33
33
  "{api_root}repositories/file/file/{pk}/".format(api_root=API_ROOT, pk=repo.pk),
34
34
  models.FileRepository,
35
35
  )
36
- self.assertEquals(repo, resource)
36
+ assert repo == resource
37
37
 
38
38
  def test_multiple_matches(self):
39
39
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulpcore
3
- Version: 3.77.1
3
+ Version: 3.79.0
4
4
  Summary: Pulp Django Application and Related Modules
5
5
  Author-email: Pulp Team <pulp-list@redhat.com>
6
6
  Project-URL: Homepage, https://pulpproject.org
@@ -21,14 +21,14 @@ Classifier: Programming Language :: Python :: 3.12
21
21
  Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
- Requires-Dist: aiodns<=3.3.0,>=3.0
24
+ Requires-Dist: aiodns<=3.4.0,>=3.0
25
25
  Requires-Dist: aiofiles<24.2.0,>=22.1
26
26
  Requires-Dist: aiohttp<3.12,>=3.8.1
27
27
  Requires-Dist: asyncio-throttle<=1.0.2,>=1.0
28
28
  Requires-Dist: async-timeout<4.0.4,>=4.0.3; python_version < "3.11"
29
29
  Requires-Dist: backoff<2.2.2,>=2.1.2
30
30
  Requires-Dist: click<=8.1.8,>=8.1.0
31
- Requires-Dist: cryptography<44.0.4,>=38.0.1
31
+ Requires-Dist: cryptography<45.0.3,>=38.0.1
32
32
  Requires-Dist: Django~=4.2.0
33
33
  Requires-Dist: django-filter<=25.1,>=23.1
34
34
  Requires-Dist: django-guid<=3.5.1,>=3.3
@@ -37,7 +37,7 @@ Requires-Dist: django-lifecycle<=1.2.4,>=1.0
37
37
  Requires-Dist: djangorestframework<=3.16.0,>=3.14.0
38
38
  Requires-Dist: djangorestframework-queryfields<=1.1.0,>=1.0
39
39
  Requires-Dist: drf-access-policy<1.5.1,>=1.1.2
40
- Requires-Dist: drf-nested-routers<=0.94.1,>=0.93.4
40
+ Requires-Dist: drf-nested-routers<=0.94.2,>=0.93.4
41
41
  Requires-Dist: drf-spectacular==0.27.2
42
42
  Requires-Dist: dynaconf<3.3.0,>=3.2.5
43
43
  Requires-Dist: gunicorn<23.1.0,>=20.1
@@ -46,13 +46,13 @@ Requires-Dist: jinja2<=3.1.6,>=3.1
46
46
  Requires-Dist: json_stream<2.4,>=2.3.2
47
47
  Requires-Dist: jq<1.9.0,>=1.6.0
48
48
  Requires-Dist: PyOpenSSL<26.0
49
- Requires-Dist: opentelemetry-api<1.33,>=1.27.0
50
- Requires-Dist: opentelemetry-sdk<1.33,>=1.27.0
51
- Requires-Dist: opentelemetry-exporter-otlp-proto-http<1.33,>=1.27.0
49
+ Requires-Dist: opentelemetry-api<1.34,>=1.27.0
50
+ Requires-Dist: opentelemetry-sdk<1.34,>=1.27.0
51
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http<1.34,>=1.27.0
52
52
  Requires-Dist: protobuf<6.0,>=4.21.1
53
53
  Requires-Dist: pulp-glue<0.33,>=0.18.0
54
54
  Requires-Dist: pygtrie<=2.5.0,>=2.5
55
- Requires-Dist: psycopg[binary]<=3.2.7,>=3.1.8
55
+ Requires-Dist: psycopg[binary]<=3.2.9,>=3.1.8
56
56
  Requires-Dist: pyparsing<=3.2.3,>=3.1.0
57
57
  Requires-Dist: python-gnupg<=0.5.4,>=0.5
58
58
  Requires-Dist: PyYAML<=6.0.2,>=5.1.1
@@ -78,6 +78,8 @@ Requires-Dist: confluent-kafka<2.10.0,>=2.4.0; extra == "kafka"
78
78
  Provides-Extra: diagnostics
79
79
  Requires-Dist: pyinstrument~=5.0; extra == "diagnostics"
80
80
  Requires-Dist: memray~=1.17; extra == "diagnostics"
81
+ Provides-Extra: uvloop
82
+ Requires-Dist: uvloop<0.22,>=0.20; extra == "uvloop"
81
83
  Dynamic: license-file
82
84
 
83
85
 
@@ -1,6 +1,6 @@
1
1
  pulp_certguard/__init__.py,sha256=llnEd00PrsAretsgAOHiNKFbmvIdXe3iDVPmSaKz7gU,71
2
2
  pulp_certguard/pytest_plugin.py,sha256=qhRbChzqN2PROtD-65KuoTfKr5k9T3GPsz9daFgpqpM,852
3
- pulp_certguard/app/__init__.py,sha256=yrfk5jSZdckh14GlycVlp7yj-aqEz9RhG4NbxWcr-tg,297
3
+ pulp_certguard/app/__init__.py,sha256=37fy6yEmamVjBNpYjBDR0PLwmJVcJLBDKJJecOne2Js,297
4
4
  pulp_certguard/app/models.py,sha256=xy5IWxf0LQxayIDmQw25Y2YhB_NrlTGvuvdY-YW7QBU,8119
5
5
  pulp_certguard/app/serializers.py,sha256=3jxWu82vU3xA578Qbyz-G4Q9Zlh3MFLGRHzX62M0RF8,1826
6
6
  pulp_certguard/app/utils.py,sha256=O6T1Npdb8fu3XqIkDJd8PQdEFJWPUeQ-i_aHXBl7MEc,816
@@ -49,7 +49,7 @@ pulp_certguard/tests/unit/test_models.py,sha256=TBI0yKsrdbnJSPeBFfxSqhXK7zaNvR6q
49
49
  pulp_file/__init__.py,sha256=0vOCXofR6Eyxkg4y66esnOGPeESCe23C1cNBHj56w44,61
50
50
  pulp_file/manifest.py,sha256=1WwIOJrPSkFcmkRm7CkWifVOCoZvo_nnANgce6uuG7U,3796
51
51
  pulp_file/pytest_plugin.py,sha256=Fi_p-Vle_I-VYUSe4Zlg7esb_Ul5fpB8Rx9UGLK5UNQ,13281
52
- pulp_file/app/__init__.py,sha256=VYwb2aF8lkSmk9tLh7bZi3s7FK82uIttiD6z8Setxaw,292
52
+ pulp_file/app/__init__.py,sha256=H_oQBsMT3QLmqf01BA-pgV9rZGJ3o9eZA4qZX2qbFkY,292
53
53
  pulp_file/app/modelresource.py,sha256=v-m-_bBEsfr8wG0TI5ffx1TuKUy2-PsirhuQz4XXF-0,1063
54
54
  pulp_file/app/models.py,sha256=QsrVg_2uKqnR89sLN2Y7Zy260_nLIcUfa94uZowlmFw,4571
55
55
  pulp_file/app/replica.py,sha256=OtNWVmdFUgNTYhPttftVNQnSrnvx2_hnrJgtW_G0Vrg,1894
@@ -110,10 +110,10 @@ pulpcore/pytest_plugin.py,sha256=Tq_xlO8Z2iyjFtbnaKHbWQogq6jxcRpjji9XbKrs5_U,377
110
110
  pulpcore/responses.py,sha256=mIGKmdCfTSoZxbFu4yIH1xbdLx1u5gqt3D99LTamcJg,6125
111
111
  pulpcore/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
112
  pulpcore/app/access_policy.py,sha256=5vCKy6WoHtIt1_-eS5vMaZ7CmR4G-CIpsrB8yT-d88Q,6079
113
- pulpcore/app/apps.py,sha256=x8Vf5rLOz4SXECRMlgYd6X18xDdnymVmdFS7c701DHo,17860
113
+ pulpcore/app/apps.py,sha256=JlQDeDMMwjANi3Td8hHSxAJc8YXQRGyE2q-AVEWW54E,17860
114
114
  pulpcore/app/authentication.py,sha256=1LIJW6HIQQlZrliHy__jdzkDEh6Oj7xKgd0V-vRcDus,2855
115
115
  pulpcore/app/checks.py,sha256=jbfTF7nmftBbky4AQXHigpyCaGydKasvRUXsd72JZVg,1946
116
- pulpcore/app/entrypoint.py,sha256=HRfaHDkveSIfcTOtWEWYqg1poTmTo0J9hzzmj0yDcEM,4885
116
+ pulpcore/app/entrypoint.py,sha256=m9kwANkh9OkhyAcWqPbrZg21IMQibLlB8_k1tkVgedg,4888
117
117
  pulpcore/app/files.py,sha256=uPodXYTVh7Ud-lQn8F58viSdom7TMh2X1SpoDt6XRKw,5797
118
118
  pulpcore/app/global_access_conditions.py,sha256=Jezc1Hf0bQFaZvZFEDPpBrJmK0EvIa6zHRHHZYkez2Y,30398
119
119
  pulpcore/app/importexport.py,sha256=x1gGrHgirfMLsv92GEwBIQe12aItJJW9JH8TPij-rms,8795
@@ -121,24 +121,25 @@ pulpcore/app/loggers.py,sha256=7tteVBBIf4W7jk4tB7QNpFGjCZueDDrPAavHj46LdJI,79
121
121
  pulpcore/app/manage.py,sha256=5YGD5ly3dJcLjX4jKIo3BBPFi_aqEPqi-CCoogV8lm8,286
122
122
  pulpcore/app/mime_types.py,sha256=xQh9gd5jHKxS0RrYqUjg98pST-Cyfcrk_Et1l2eby_w,6706
123
123
  pulpcore/app/modelresource.py,sha256=SlnKD_K-ZFtR5Gt8hpoF5odEX2y-1v2bb6UkJcOqnd8,4847
124
- pulpcore/app/openpgp.py,sha256=jyvOBlu_wGDH_aem3TN8hw51Dx2dd3NyLzCw4Q9Xlf0,17246
124
+ pulpcore/app/openpgp.py,sha256=MYwCTGz7J9-Zr5aSBrz7KGWWWNC1EI-aItGb5dr7XPE,17246
125
125
  pulpcore/app/pulp_hashlib.py,sha256=NoVCO8duLz9rggPcilg0smi6fTDnsn-zS9dXgO831Pg,1327
126
126
  pulpcore/app/pulpcore_gunicorn_application.py,sha256=caqbDg9dhzECbx9Ss76biuEARhquj9gQaSL6v3XLy2w,2612
127
127
  pulpcore/app/redis_connection.py,sha256=VTdG0ulXuyESjYV6SJdG_jLzkLZH-MlLcD6pielwRSk,952
128
- pulpcore/app/replica.py,sha256=b6r-QF4H4G94N5HoaV3PGHeOD4-BqVb7YVsRNHx0h9Y,11675
128
+ pulpcore/app/replica.py,sha256=6WU-K8olrOoO4q8gwJ2bKc_qmvw8wCOzRZdNZrr895g,11678
129
129
  pulpcore/app/response.py,sha256=hYH_jSBrxmRsBr2bknmXE1qfs2g8JjDTXYcQ5ZWlF_c,1950
130
130
  pulpcore/app/role_util.py,sha256=84HSt8_9fxB--dtfSyg_TumVgOdyBbyP6rBaiAfTpOU,22393
131
- pulpcore/app/settings.py,sha256=34pHnUfM1AzF1nKNtt31nthuFyQBd006fcH_Hq4ZmxI,22509
131
+ pulpcore/app/settings.py,sha256=f2LXOVLd58iG2z1Whm_XlLop129uKT5-GYxWiBq-ja8,22430
132
132
  pulpcore/app/urls.py,sha256=0gdI74CAdycJStXSw1gknviDGe3J3k0UhS4J8RYa5dg,8120
133
- pulpcore/app/util.py,sha256=kenzRmvDl1obKFb806ETlEE2qs8h3Y1KcCe-Q8AtYGY,24442
133
+ pulpcore/app/util.py,sha256=nYF6nZXgqVk4U1QeZEpWYX-wqitGSGAJip6W78IfXUk,24432
134
134
  pulpcore/app/wsgi.py,sha256=7rpZ_1NHEN_UfeNZCj8206bas1WeqRkHnGdxpd7rdDI,492
135
135
  pulpcore/app/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
136
136
  pulpcore/app/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
137
137
  pulpcore/app/management/commands/add-signing-service.py,sha256=jzUHopJoQ0fKkC23ybmd9WjX4R-UtvEHvvBxI_QkJF0,3232
138
138
  pulpcore/app/management/commands/analyze-publication.py,sha256=imcHSBYHI20vaT6ZgGJkJ4lVzMgQDVlhAe9mGu07MIk,3124
139
+ pulpcore/app/management/commands/clean-up-progress-reports.py,sha256=3LLB1MJyyq_eHfuVn-XwzojufqphEaNTRIbXNbjgiCM,1971
139
140
  pulpcore/app/management/commands/datarepair-2327.py,sha256=HCw3XQcEEFbgYPd7H1bBjboApDapCNplsAxd9ua8f6M,4677
140
141
  pulpcore/app/management/commands/dump-permissions.py,sha256=hrwDbEMBpEGM6UmgZtCHR9vpEiVQP8KLPybVmqSMxmA,8662
141
- pulpcore/app/management/commands/dump-publications-to-fs.py,sha256=0rIt7fJX_q-h_5sbys5T4SClp6Q326ABOWu9ZZfUPdA,7037
142
+ pulpcore/app/management/commands/dump-publications-to-fs.py,sha256=lkLwxPi4GXzcLZpLvQbPnqYCALxag-G5xjr309gWGwo,7040
142
143
  pulpcore/app/management/commands/handle-artifact-checksums.py,sha256=gblm6CkuyXrf9TsiTtts6iIgk4nyZnsJShozGxyALV8,8728
143
144
  pulpcore/app/management/commands/migrationstat.py,sha256=Gy19UMSyUeXYT13ERQ-P1PdgnmNX9veJteEOgMMG6QY,1517
144
145
  pulpcore/app/management/commands/openapi.py,sha256=p-aPuVfbnFQYIU7BMnipxe9nId-f2agNiviSIy43y9Q,3634
@@ -302,14 +303,14 @@ pulpcore/app/models/repository.py,sha256=xBMKsryirkpZyrQHnFbwolNbvyX1jHljcqC1ofv
302
303
  pulpcore/app/models/role.py,sha256=dZklNd2VeAw4cT6dyJ7SyTBt9sZvdqakY86wXGAY3vU,3287
303
304
  pulpcore/app/models/status.py,sha256=72oUOJ7BnCAw3uDbc-XuI72oAyP2llCoBic4zb2JP78,3683
304
305
  pulpcore/app/models/storage.py,sha256=2b-DQWaO31NqjV6FiISALegND-sQZAU7BVAsduUvm3o,6780
305
- pulpcore/app/models/task.py,sha256=7T9MZkcORTO6qm-vHEA_b0CvgMWJ9peWCFAVuIF--_M,14646
306
+ pulpcore/app/models/task.py,sha256=gcK-ou-ppS5Xdqx-6HHE01_lGeZn16H9IzqEZ_P07MY,14652
306
307
  pulpcore/app/models/upload.py,sha256=3njXT2rrVJwBjEDegvqcLD9_7cPnnl974lhbAhikEp8,3004
307
308
  pulpcore/app/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
308
309
  pulpcore/app/protobuf/analytics_pb2.py,sha256=-4CkbSW8JUAEIjZJBTPAJ5QezFJOdCPiDhx8_KA1bMU,2168
309
310
  pulpcore/app/serializers/__init__.py,sha256=M1g5Si6hRi1flhQ-F1n6G-uFEzIZ7BUqHZ8iUE-bRuM,3466
310
311
  pulpcore/app/serializers/access_policy.py,sha256=NNtuzDW5H4RGfy5LbRFEHWDTDzXdL-Kihe9uquXnxBU,2818
311
312
  pulpcore/app/serializers/acs.py,sha256=wBbGE5YHR7dJWuicbDtiPQUJ7xEgz1zKHGkJEaMfpDU,5834
312
- pulpcore/app/serializers/base.py,sha256=6mC5ajrv4bVm3K-mWigDNJKOE4OTAdLKmoJPTSc8rr0,21415
313
+ pulpcore/app/serializers/base.py,sha256=ojWmsr2U2Mx8qpSFxqHLNQyfU2Z9q7hY1NUwVs9s3HE,21418
313
314
  pulpcore/app/serializers/content.py,sha256=lqfSah9Kg2i6dV-x2MGwQ-1q87GB9VOtjkQdjQKC5tQ,11967
314
315
  pulpcore/app/serializers/domain.py,sha256=-xRJS_Olb1s2bqFzKamV0d_QnYO-e2iIyBJw-39uqMI,22688
315
316
  pulpcore/app/serializers/exporter.py,sha256=TxAgHDt34YUGPusawn7B8HL3bBymp46__6CnfhXSgGs,11179
@@ -327,7 +328,7 @@ pulpcore/app/serializers/repository.py,sha256=fSM92qJTjQIGXgnmA4xr62jRG9atYj6tkI
327
328
  pulpcore/app/serializers/status.py,sha256=nIrQl-MlOzUIvV2DrkgC19gqGmRVNKvWVN4pIBELgcQ,3815
328
329
  pulpcore/app/serializers/task.py,sha256=IGJGoSEC_wKS8t77JGnZWRqK-Mk5-4rXSj8j0Ha6nRA,10026
329
330
  pulpcore/app/serializers/upload.py,sha256=4r6iBegbYHmgFYjBYPcqB8J7eSxXgY4ukayMxJZNh_M,2402
330
- pulpcore/app/serializers/user.py,sha256=gw-Qju1akkDtjxiehNM10jnoeaoApY1MATNfpboAFoY,17122
331
+ pulpcore/app/serializers/user.py,sha256=QBEnUCfq2my3Lq_pohj7hphDE8wqU6g6fnYuEXl8VtI,18413
331
332
  pulpcore/app/tasks/__init__.py,sha256=6fhLD0Z9LMluzqyBwQkatId71qI_2U7-o2-ZI1JH1Ls,576
332
333
  pulpcore/app/tasks/analytics.py,sha256=eB3p-sdocH5yyNoe0OG5rUzwiVOfayOfHNzkohAfx-U,4722
333
334
  pulpcore/app/tasks/base.py,sha256=4I88Bn5SttqEvvVlNJmIwkPv2IWe7OhpM-kbQiQ9T_U,5929
@@ -338,7 +339,7 @@ pulpcore/app/tasks/orphan.py,sha256=4rTZLZ549niJ7mGMh_7suy-czIcj06oCTxPYnsPN8mU,
338
339
  pulpcore/app/tasks/purge.py,sha256=yrnlvQKtg2usjK-75JoDvg4RvvEKipMpI8p4fh69A3o,7472
339
340
  pulpcore/app/tasks/reclaim_space.py,sha256=FZ7KFasbScPAU7A6lzK98pdylmqgThssgnNMecG5bEw,3803
340
341
  pulpcore/app/tasks/replica.py,sha256=x-Yjd8Z4EUhrhuF1DCX5jCa6F7FTAE3th-161lnLN2g,4509
341
- pulpcore/app/tasks/repository.py,sha256=tPQ8KvsAUgX8Gd_hXsSc64uCmhVRQqHh36bykHlUPIw,9631
342
+ pulpcore/app/tasks/repository.py,sha256=v-CDXp03YV6S6Lf-rKklPw7PwpfeoQe_Gw3ZyMH6SFQ,9640
342
343
  pulpcore/app/tasks/telemetry.py,sha256=QXOcYi7VIx_TBPCfs2BfcaiiVzRCiTFPZCN8MlC-hw8,1338
343
344
  pulpcore/app/tasks/test.py,sha256=_3BdJzdtEGdyL7qnzl0apwAoGeleYPeZTr7IJ5COFyo,912
344
345
  pulpcore/app/tasks/upload.py,sha256=3YJa32XYUFgqkEEWoERRPB9Q6ph9a6ashMtMi24R15k,1413
@@ -370,10 +371,10 @@ pulpcore/app/viewsets/upload.py,sha256=Mfy9Vcm5KcqARooH4iExzoXVkL6boDddEqAnGWDWz
370
371
  pulpcore/app/viewsets/user.py,sha256=86eMawpaVrvp6ilQmb1C4j7SKpesPB5HgMovYL9rY3Q,13813
371
372
  pulpcore/cache/__init__.py,sha256=GkYD4PgIMaVL83ywfAsLBC9JNNDUpmTtbitW9zZSslk,131
372
373
  pulpcore/cache/cache.py,sha256=d8GMlvjeGG9MOMdi5_9029WpGCKH8Y5q9b2lt3wSREo,17371
373
- pulpcore/content/__init__.py,sha256=iDCr_SjoC8Y58sfSzx1-zU1l44fVrxOpekVALohezQM,3758
374
+ pulpcore/content/__init__.py,sha256=CVrhM5Ep2NFZBWOPxuyXXz7xL0bdZsmpBaOLPeA14SI,4010
374
375
  pulpcore/content/authentication.py,sha256=lEZBkXBBBkIdtFMCSpHDD7583M0bO-zsZNYXTmpr4k8,3235
375
- pulpcore/content/entrypoint.py,sha256=fVqligooWVaW6ZZvNoj6TpCbb3AO5jtG9WXQL2kPXsU,1865
376
- pulpcore/content/handler.py,sha256=_hPNpfbqMucXk3uGrb_PzzlWvZ6ptLhNj7XyOJxdxgY,56651
376
+ pulpcore/content/entrypoint.py,sha256=svs6pEYa5bEGhWAAHpZt-uqlTuVXQ2UdW4U_LRlRHhY,2048
377
+ pulpcore/content/handler.py,sha256=J5LDpT0uJn86HESsX37eIEIiakl84OKXdV934gpP-JI,56712
377
378
  pulpcore/content/instrumentation.py,sha256=H0N0GWzvOPGGjFi6eIbGW3mcvagfnAfazccTh-BZVmE,1426
378
379
  pulpcore/download/__init__.py,sha256=s3Wh2GKdsmbUooVIR6wSvhYVIhpaTbtfR3Ar1OJhC7s,154
379
380
  pulpcore/download/base.py,sha256=G8jgyowvVEFCGA_KTr0CtHn0qHWXKsnv4Xpi0KSMglM,12821
@@ -426,8 +427,8 @@ pulpcore/tasking/_util.py,sha256=giR8f8fNvsjsTiuJOU9X21Dyb14fFntSYU7xXGwQZzo,970
426
427
  pulpcore/tasking/entrypoint.py,sha256=Npnn41e39soGvJ7CTaZXT5MjIhOO7UtQmpmNaZtfKYg,1120
427
428
  pulpcore/tasking/kafka.py,sha256=76z4DzeXM1WL5uu1HlKnduWeLO3-b-czvGBXdWR6054,3845
428
429
  pulpcore/tasking/storage.py,sha256=zQkwlpC_FDQtmZGZ8vKwHqxvD6CLO_gAS4Q7wijZE-k,3106
429
- pulpcore/tasking/tasks.py,sha256=b0YzBoxXiBXC8piZlzV63ehRHsw8V3uq6t4v8Ec8OIg,13935
430
- pulpcore/tasking/worker.py,sha256=NpBACe6z_xFYOEeJYRne0hlwQDVrOAYGwpNCzSL-3C4,23819
430
+ pulpcore/tasking/tasks.py,sha256=Y1tvG2hHREtpzVnQtu-_QYXD6mKtuvyCethOopWUJAI,14327
431
+ pulpcore/tasking/worker.py,sha256=c9RgSYg4J_Jn_q70MVF_2egDeASFgXlLrP00lqWKtnQ,23822
431
432
  pulpcore/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
432
433
  pulpcore/tests/functional/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
433
434
  pulpcore/tests/functional/content_with_coverage.py,sha256=gQK8himy32s9O9vpXdgoM6-_z2KySaXm5rTga9z0jGI,260
@@ -461,7 +462,7 @@ pulpcore/tests/functional/api/using_plugin/__init__.py,sha256=QyyfzgjLOi4n32G3o9
461
462
  pulpcore/tests/functional/api/using_plugin/test_checkpoint.py,sha256=gx1oiHOVUH5QZfF33k_DXSw-AVbYQp39uKii1D96BoI,7965
462
463
  pulpcore/tests/functional/api/using_plugin/test_content_access.py,sha256=Ym800bU-M48RCDfQMkVa1UQt_sfgy5ciU0FxorCk9Ds,2551
463
464
  pulpcore/tests/functional/api/using_plugin/test_content_cache.py,sha256=OB3gDbPDptQBjyYnr_jHyU9bcI_-ANAoUp9EDiskwug,7312
464
- pulpcore/tests/functional/api/using_plugin/test_content_delivery.py,sha256=sDYRqwZvOmDX6mc_UeVWI05CWm7nyNMPgeWf2r913i8,10549
465
+ pulpcore/tests/functional/api/using_plugin/test_content_delivery.py,sha256=BNinSe7eUGWzJ_PVlzlDykAV02xV_EPHxunQnVjndgo,10566
465
466
  pulpcore/tests/functional/api/using_plugin/test_content_directory.py,sha256=w4uY258etnP8-LbrbZ_EZTolciYTt7cY1HJK9Ll7mS0,1931
466
467
  pulpcore/tests/functional/api/using_plugin/test_content_path.py,sha256=fvqeptqo-mrUAiKIjlypuvHG1XsFeKKP81ocTmo4hv0,3334
467
468
  pulpcore/tests/functional/api/using_plugin/test_content_promotion.py,sha256=Co4ytrfpzklwgDdEthv45dsmrceRpqIQfLJlZWM6EBY,2388
@@ -483,7 +484,7 @@ pulpcore/tests/functional/api/using_plugin/test_tasks.py,sha256=wIQr2J8DD2isx_0t
483
484
  pulpcore/tests/functional/api/using_plugin/test_unlinking_repo.py,sha256=rGJP2qcDarJALSpjzEsoO-ewQ0J2kWhFvN3Y1Z9fvzA,1085
484
485
  pulpcore/tests/functional/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
485
486
  pulpcore/tests/performance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
486
- pulpcore/tests/performance/test_performance.py,sha256=MBjybXeG7T72XCTFT1ozbvs2BBdTXKWCjUFWbj7Y-WY,8615
487
+ pulpcore/tests/performance/test_performance.py,sha256=LIUSbDG6voX8IX4y7UXKDNcg4Ih2M0tVcvgB7vfClBc,8502
487
488
  pulpcore/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
488
489
  pulpcore/tests/unit/conftest.py,sha256=GfVjybp2b8c3E75rgq-kXVQ1_Tc3owuG3NK7Z0eGrsY,277
489
490
  pulpcore/tests/unit/test_cache.py,sha256=sDNc7W31JLHS9r7ZiIUHz6iB7UkqPCKjLlfVmyTd8t4,3259
@@ -494,7 +495,7 @@ pulpcore/tests/unit/test_import_checks.py,sha256=Pu78yWFm68e1IfBbJIbzgwgKfDsjkpH
494
495
  pulpcore/tests/unit/test_pulp_urls.py,sha256=sMRZowo7ej4HIrGEOY_OsDfXI1J4I8k_VXBvRDSCvYA,3416
495
496
  pulpcore/tests/unit/test_settings.py,sha256=-yRXxmK-OdgC6mHqS83XgkD-PkZYvY1wZ37gRrGEoYc,2067
496
497
  pulpcore/tests/unit/test_util.py,sha256=hgioXXC5-tufFpk6zrmMEHtWPG7UbmMHqeF5CiXOLqQ,884
497
- pulpcore/tests/unit/test_viewsets.py,sha256=yMiNisRqwt7xcfvmx2YDT1qg9htVGFfrhwqeDVHHGPs,3218
498
+ pulpcore/tests/unit/test_viewsets.py,sha256=6rek28Rr0kEuYjQZ0_kTSnKsTvmMmD3l-WV_GVb48YQ,3208
498
499
  pulpcore/tests/unit/content/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
499
500
  pulpcore/tests/unit/content/test_handler.py,sha256=f4F9RAqCP60PUanYdeLW_A955UjRt8eCTrRuh0mChDU,19774
500
501
  pulpcore/tests/unit/content/test_heartbeat.py,sha256=pHmSNy7KjlU9LpRcejBr8lttaJXrE4k9XQ5e2rDZmT0,1036
@@ -512,6 +513,7 @@ pulpcore/tests/unit/models/test_base.py,sha256=77hnxOFBJYMNbI1YGEaR5yj8VCapNGmEg
512
513
  pulpcore/tests/unit/models/test_content.py,sha256=heU0vJKucPIp6py2Ww-eXLvhFopvmK8QjFgzt1jGnYQ,5599
513
514
  pulpcore/tests/unit/models/test_remote.py,sha256=KxXwHdA-wj7D-ZpuVi33cLX43wkEeIzeqF9uMsJGt-k,2354
514
515
  pulpcore/tests/unit/models/test_repository.py,sha256=rnBw1VOsi2Lv3zez2pV2RDXGk_z70KiaACOtyyXugJM,10379
516
+ pulpcore/tests/unit/models/test_task.py,sha256=rjxeYe383Zsjk8Ck4inMBBTzR4osCrgTeZNWwmHfbjk,1457
515
517
  pulpcore/tests/unit/roles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
516
518
  pulpcore/tests/unit/roles/test_roles.py,sha256=TkPPCLEHMaxfafsRf_3pc4Z3w8BPTyteY7rFkVo65GM,4973
517
519
  pulpcore/tests/unit/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -521,14 +523,15 @@ pulpcore/tests/unit/serializers/test_fields.py,sha256=ba25xHl6aezQ3K5o2mPdw-pK9k
521
523
  pulpcore/tests/unit/serializers/test_orphans_cleanup.py,sha256=z3yWWioSl3A1ZlQR0RGzkzPExp7AggyxHZBsE5cMjl4,617
522
524
  pulpcore/tests/unit/serializers/test_pulpexport.py,sha256=gXn7E13X-SP0rFM0bUv8PwpdLI614txrPW-JKHZ4pQE,3010
523
525
  pulpcore/tests/unit/serializers/test_repository.py,sha256=eknsHlbHz1K0nqntDntltFLU2EunrSlXCgg3HrV9PTI,9288
526
+ pulpcore/tests/unit/serializers/test_user.py,sha256=lemDxBIDWKrfFmazl9DMW7-k3lQyWtD8uQCxNktHI3Q,3094
524
527
  pulpcore/tests/unit/stages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
525
528
  pulpcore/tests/unit/stages/test_artifactdownloader.py,sha256=qB1ANdFmNtUnljg8fCdLHTiAakrO3KtX-w9RA5fPSOQ,12480
526
529
  pulpcore/tests/unit/stages/test_stages.py,sha256=H1a2BQLjdZlZvcb_qULp62huZ1xy6ItTcthktVyGU0w,4735
527
530
  pulpcore/tests/unit/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
528
531
  pulpcore/tests/unit/viewsets/test_viewset_base.py,sha256=W9o3V6758bZctR6krMPPQytb0xJuF-jb4uBWTNDoD_U,4837
529
- pulpcore-3.77.1.dist-info/licenses/LICENSE,sha256=dhnHU8rJXUdAIgIjveSKAyYG_KzN5eVG-bxETIGrNW0,17988
530
- pulpcore-3.77.1.dist-info/METADATA,sha256=5PbwCkEF0X97zbdXnPwJmL1f2cIxS-bfJCQ5WuG__k4,4260
531
- pulpcore-3.77.1.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
532
- pulpcore-3.77.1.dist-info/entry_points.txt,sha256=OZven4wzXzQA5b5q9MpP4HUpIPPQCSvIOvkKtNInrK0,452
533
- pulpcore-3.77.1.dist-info/top_level.txt,sha256=6h-Lm3FKQSaT_nL1KSxu_hBnzKE15bcvf_BoU-ea4CI,34
534
- pulpcore-3.77.1.dist-info/RECORD,,
532
+ pulpcore-3.79.0.dist-info/licenses/LICENSE,sha256=dhnHU8rJXUdAIgIjveSKAyYG_KzN5eVG-bxETIGrNW0,17988
533
+ pulpcore-3.79.0.dist-info/METADATA,sha256=oza7Ge3RemPRjXrvpfPh3Rc-vgp7wq6ZOutyPjWKjH4,4336
534
+ pulpcore-3.79.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
535
+ pulpcore-3.79.0.dist-info/entry_points.txt,sha256=OZven4wzXzQA5b5q9MpP4HUpIPPQCSvIOvkKtNInrK0,452
536
+ pulpcore-3.79.0.dist-info/top_level.txt,sha256=6h-Lm3FKQSaT_nL1KSxu_hBnzKE15bcvf_BoU-ea4CI,34
537
+ pulpcore-3.79.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5