pulpcore 3.76.1__py3-none-any.whl → 3.77.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.76.1"
9
+ version = "3.77.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.76.1"
11
+ version = "3.77.0"
12
12
  python_package_name = "pulpcore"
13
13
  domain_compatible = True
@@ -1,10 +1,89 @@
1
- import uuid
2
-
3
1
  import pytest
2
+ import uuid
4
3
 
5
4
  from pulpcore.client.pulp_file import (
6
5
  RepositorySyncURL,
7
6
  )
7
+ from pulpcore.client.pulp_file.exceptions import BadRequestException
8
+
9
+ GOOD_CERT = """-----BEGIN CERTIFICATE-----
10
+ MIICoDCCAYgCCQC2c2uY34HNlzANBgkqhkiG9w0BAQUFADASMRAwDgYDVQQDDAdn
11
+ b3ZlZ2FuMB4XDTE5MDMxMzIxMDMzMFoXDTM4MDYxNjIxMDMzMFowEjEQMA4GA1UE
12
+ AwwHZ292ZWdhbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANEatWsZ
13
+ 1iwGmTxD02dxMI4ci+Au4FzvmWLBWD07H5GGTVFwnqmNOKhP6DHs1EsMZevkUvaG
14
+ CRxZlPYhjNFLZr2c2FnoDZ5nBXlSW6sodXURbMfyT187nDeBXVYFuh4T2eNCatnm
15
+ t3vgdi+pWsF0LbOgpu7GJI2sh5K1imxyB77tJ7PFTDZCSohkK+A+0nDCnJqDUNXD
16
+ 5CK8iaBciCbnzp3nRKuM2EmgXno9Repy/HYxIgB7ZodPwDvYNjMGfvs0s9mJIKmc
17
+ CKgkPXVO9y9gaRrrytICcPOs+YoU/PN4Ttg6wzxaWvJgw44vsR8wM/0i4HlXfBdl
18
+ 9br+cgn8jukDOgECAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAyNHV6NA+0GfUrvBq
19
+ AHXHNnBE3nzMhGPhF/0B/dO4o0n6pgGZyzRxaUaoo6+5oQnBf/2NmDyLWdalFWX7
20
+ D1WBaxkhK+FU922+qwQKhABlwMxGCnfZ8F+rlk4lNotm3fP4wHbnO1SGIDvvZFt/
21
+ mpMgkhwL4lShUFv57YylXr+D2vSFcAryKiVGk1X3sHMXlFAMLHUm3d97fJnmb1qQ
22
+ wC43BlJCBQF98wKtYNwTUG/9gblfk8lCB2DL1hwmPy3q9KbSDOdUK3HW6a75ZzCD
23
+ 6mXc/Y0bJcwweDsywbPBYP13hYUcpw4htcU6hg6DsoAjLNkSrlY+GGo7htx+L9HH
24
+ IwtfRg==
25
+ -----END CERTIFICATE-----
26
+ """
27
+
28
+ GOOD_CERT_WITH_COMMENT = """saydas Intermédiaire CA
29
+ -----BEGIN CERTIFICATE-----
30
+ MIICoDCCAYgCCQC2c2uY34HNlzANBgkqhkiG9w0BAQUFADASMRAwDgYDVQQDDAdn
31
+ b3ZlZ2FuMB4XDTE5MDMxMzIxMDMzMFoXDTM4MDYxNjIxMDMzMFowEjEQMA4GA1UE
32
+ AwwHZ292ZWdhbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANEatWsZ
33
+ 1iwGmTxD02dxMI4ci+Au4FzvmWLBWD07H5GGTVFwnqmNOKhP6DHs1EsMZevkUvaG
34
+ CRxZlPYhjNFLZr2c2FnoDZ5nBXlSW6sodXURbMfyT187nDeBXVYFuh4T2eNCatnm
35
+ t3vgdi+pWsF0LbOgpu7GJI2sh5K1imxyB77tJ7PFTDZCSohkK+A+0nDCnJqDUNXD
36
+ 5CK8iaBciCbnzp3nRKuM2EmgXno9Repy/HYxIgB7ZodPwDvYNjMGfvs0s9mJIKmc
37
+ CKgkPXVO9y9gaRrrytICcPOs+YoU/PN4Ttg6wzxaWvJgw44vsR8wM/0i4HlXfBdl
38
+ 9br+cgn8jukDOgECAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAyNHV6NA+0GfUrvBq
39
+ AHXHNnBE3nzMhGPhF/0B/dO4o0n6pgGZyzRxaUaoo6+5oQnBf/2NmDyLWdalFWX7
40
+ D1WBaxkhK+FU922+qwQKhABlwMxGCnfZ8F+rlk4lNotm3fP4wHbnO1SGIDvvZFt/
41
+ mpMgkhwL4lShUFv57YylXr+D2vSFcAryKiVGk1X3sHMXlFAMLHUm3d97fJnmb1qQ
42
+ wC43BlJCBQF98wKtYNwTUG/9gblfk8lCB2DL1hwmPy3q9KbSDOdUK3HW6a75ZzCD
43
+ 6mXc/Y0bJcwweDsywbPBYP13hYUcpw4htcU6hg6DsoAjLNkSrlY+GGo7htx+L9HH
44
+ IwtfRg==
45
+ -----END CERTIFICATE-----
46
+ """
47
+
48
+ GOOD_CERT_TWO = """-----BEGIN CERTIFICATE-----
49
+ MIICqjCCAZICAgtCMA0GCSqGSIb3DQEBBQUAMBIxEDAOBgNVBAMMB2dvdmVnYW4w
50
+ HhcNMTkwMzEzMjEwMzMwWhcNMjkwMzEwMjEwMzMwWjAjMRAwDgYDVQQDDAdnb3Zl
51
+ Z2FuMQ8wDQYDVQQKDAZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
52
+ AoIBAQCxJWx5t25jY4womWtKxGqv2LHg9YnU0b2VCECLhu5JjoAzFPja5VHB0Maz
53
+ G8m5c0+N2ubrPcBC+KdoGMd2MqrrGyzKOiwbVDW0YOgnFqh58p796iKtVboWx41y
54
+ Gzn289PzYccxH6mhhPmRVD25KyV1TenqvGIHJTepF7mgIemGDTv+j7+mYPT/3r6I
55
+ pnwTkEVPr+Q4iW0l3fNESlFFRt2b7yhz9f0E4SMhmIRnSIGOLO1zE02IJ1hTuGkx
56
+ /MZ1AqQdVSdm4jenTIMp91R1kYylI66yMcpU6w6x4j8qvJ8nBZ4r4DqOHcOobyHp
57
+ qlaJjv/K5SGJxV2k0EFk7b483lbrAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBALYh
58
+ SHLGJCxVL8ePFLs294fhTq4pTQsvHm8q3SyJD9DaB+HKTceCFErNv18Dsl/QwBis
59
+ WPHWKpDN0EUcuuE/8oUaGjjzByJ8bPafMicFCHSSefcJw+IOOqKBkWDT+4YGkvfs
60
+ RpwxSLqLOhEt7aSkiPcMvD20v8cvj0O36c5G3Vv0E8WmPWOEqjyPFoU9X1vACr+h
61
+ DdIKvxFbvRU9ObektFxOYHuvP010IBv2dGyw3G5W5fh9A5OSXHAShWSwkRU36oft
62
+ ugB47fIIlb7zLm4GBmxGG0yBwAf4otBlUXVNqNx15bbUuVgKbGMFfItQgEo9AQcz
63
+ gGsetwDOs/NgZ95oH40=
64
+ -----END CERTIFICATE-----
65
+ """
66
+
67
+ BAD_CERT = """-----BEGIN CERTIFICATE-----\nBOGUS==\n-----END CERTIFICATE-----
68
+ """
69
+
70
+ NO_CERT = """
71
+ MIICoDCCAYgCCQC2c2uY34HNlzANBgkqhkiG9w0BAQUFADASMRAwDgYDVQQDDAdn
72
+ b3ZlZ2FuMB4XDTE5MDMxMzIxMDMzMFoXDTM4MDYxNjIxMDMzMFowEjEQMA4GA1UE
73
+ AwwHZ292ZWdhbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANEatWsZ
74
+ 1iwGmTxD02dxMI4ci+Au4FzvmWLBWD07H5GGTVFwnqmNOKhP6DHs1EsMZevkUvaG
75
+ CRxZlPYhjNFLZr2c2FnoDZ5nBXlSW6sodXURbMfyT187nDeBXVYFuh4T2eNCatnm
76
+ t3vgdi+pWsF0LbOgpu7GJI2sh5K1imxyB77tJ7PFTDZCSohkK+A+0nDCnJqDUNXD
77
+ 5CK8iaBciCbnzp3nRKuM2EmgXno9Repy/HYxIgB7ZodPwDvYNjMGfvs0s9mJIKmc
78
+ CKgkPXVO9y9gaRrrytICcPOs+YoU/PN4Ttg6wzxaWvJgw44vsR8wM/0i4HlXfBdl
79
+ 9br+cgn8jukDOgECAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAyNHV6NA+0GfUrvBq
80
+ AHXHNnBE3nzMhGPhF/0B/dO4o0n6pgGZyzRxaUaoo6+5oQnBf/2NmDyLWdalFWX7
81
+ D1WBaxkhK+FU922+qwQKhABlwMxGCnfZ8F+rlk4lNotm3fP4wHbnO1SGIDvvZFt/
82
+ mpMgkhwL4lShUFv57YylXr+D2vSFcAryKiVGk1X3sHMXlFAMLHUm3d97fJnmb1qQ
83
+ wC43BlJCBQF98wKtYNwTUG/9gblfk8lCB2DL1hwmPy3q9KbSDOdUK3HW6a75ZzCD
84
+ 6mXc/Y0bJcwweDsywbPBYP13hYUcpw4htcU6hg6DsoAjLNkSrlY+GGo7htx+L9HH
85
+ IwtfRg==
86
+ """
8
87
 
9
88
 
10
89
  def _run_basic_sync_and_assert(file_bindings, remote, file_repo, monitor_task):
@@ -211,3 +290,55 @@ def test_header_for_sync(
211
290
  assert requests_record[0].path == "/basic/PULP_MANIFEST"
212
291
  assert header_name in requests_record[0].headers
213
292
  assert header_value == requests_record[0].headers[header_name]
293
+
294
+
295
+ @pytest.mark.parallel
296
+ def test_certificate_clean(file_remote_factory):
297
+ # Check that a good cert validates
298
+ a_remote = file_remote_factory(url="http://example.com/", ca_cert=GOOD_CERT)
299
+ assert a_remote.ca_cert == GOOD_CERT
300
+ a_remote = file_remote_factory(url="http://example.com/", client_cert=GOOD_CERT)
301
+ assert a_remote.client_cert == GOOD_CERT
302
+
303
+ # Check that a good-cert-with-comments validates and strips the comments
304
+ a_remote = file_remote_factory(url="http://example.com/", ca_cert=GOOD_CERT_WITH_COMMENT)
305
+ assert a_remote.ca_cert == GOOD_CERT
306
+ a_remote = file_remote_factory(url="http://example.com/", client_cert=GOOD_CERT_WITH_COMMENT)
307
+ assert a_remote.client_cert == GOOD_CERT
308
+
309
+ # Check that a bad-cert gets rejected
310
+ with pytest.raises(BadRequestException):
311
+ a_remote = file_remote_factory(url="http://example.com/", ca_cert=BAD_CERT)
312
+ with pytest.raises(BadRequestException):
313
+ a_remote = file_remote_factory(url="http://example.com/", client_cert=BAD_CERT)
314
+
315
+ # Check that a no-cert string returns the expected error
316
+ with pytest.raises(BadRequestException):
317
+ a_remote = file_remote_factory(url="http://example.com/", ca_cert=NO_CERT)
318
+ with pytest.raises(BadRequestException):
319
+ a_remote = file_remote_factory(url="http://example.com/", client_cert=NO_CERT)
320
+
321
+
322
+ @pytest.mark.parallel
323
+ def test_multi_certificate_clean(file_remote_factory):
324
+ multi_cert = GOOD_CERT + GOOD_CERT_TWO
325
+
326
+ # Check that a good multi-cert PEM validates
327
+ a_remote = file_remote_factory(url="http://example.com/", ca_cert=multi_cert)
328
+ assert a_remote.ca_cert == multi_cert
329
+ a_remote = file_remote_factory(url="http://example.com/", client_cert=multi_cert)
330
+ assert a_remote.client_cert == multi_cert
331
+
332
+ # Check that a good-cert-with-comments validates and strips the comments
333
+ multi_cert_with_comment = GOOD_CERT_WITH_COMMENT + GOOD_CERT_TWO
334
+ a_remote = file_remote_factory(url="http://example.com/", ca_cert=multi_cert_with_comment)
335
+ assert a_remote.ca_cert == multi_cert
336
+ a_remote = file_remote_factory(url="http://example.com/", client_cert=multi_cert_with_comment)
337
+ assert a_remote.client_cert == multi_cert
338
+
339
+ # Check that multi-with-bad is rejected
340
+ multi_bad = GOOD_CERT + BAD_CERT
341
+ with pytest.raises(BadRequestException):
342
+ a_remote = file_remote_factory(url="http://example.com/", ca_cert=multi_bad)
343
+ with pytest.raises(BadRequestException):
344
+ a_remote = file_remote_factory(url="http://example.com/", client_cert=multi_bad)
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.76.1"
250
+ version = "3.77.0"
251
251
 
252
252
  # The python package name providing this app
253
253
  python_package_name = "pulpcore"
pulpcore/app/replica.py CHANGED
@@ -8,7 +8,7 @@ from pulp_glue.common.context import PulpContext
8
8
  from pulpcore.app.models import UpstreamPulp
9
9
  from pulpcore.tasking.tasks import dispatch
10
10
  from pulpcore.app.tasks.base import (
11
- general_update,
11
+ ageneral_update,
12
12
  general_create,
13
13
  general_multi_delete,
14
14
  )
@@ -133,7 +133,7 @@ class Replicator:
133
133
  needs_update = self.needs_update(remote_fields_dict, remote)
134
134
  if needs_update:
135
135
  dispatch(
136
- general_update,
136
+ ageneral_update,
137
137
  task_group=self.task_group,
138
138
  shared_resources=[self.server],
139
139
  exclusive_resources=[remote],
@@ -162,7 +162,7 @@ class Replicator:
162
162
  needs_update = self.needs_update(repo_fields_dict, repository)
163
163
  if needs_update:
164
164
  dispatch(
165
- general_update,
165
+ ageneral_update,
166
166
  task_group=self.task_group,
167
167
  shared_resources=[self.server],
168
168
  exclusive_resources=[repository],
@@ -197,7 +197,7 @@ class Replicator:
197
197
  if needs_update:
198
198
  # Update the distribution
199
199
  dispatch(
200
- general_update,
200
+ ageneral_update,
201
201
  task_group=self.task_group,
202
202
  shared_resources=[repository, self.server],
203
203
  exclusive_resources=self.distros_uris,
@@ -407,14 +407,34 @@ def validate_repo_and_remote(repository, field, **kwargs):
407
407
 
408
408
  detail_repo = repository.cast()
409
409
  if detail_repo.remote and type(detail_repo.remote.cast()) not in detail_repo.REMOTE_TYPES:
410
- msg = (
411
- f"Type for Remote '{get_prn(repository.remote)}' "
412
- f"does not match Repository '{get_prn(repository)}'"
413
- )
414
- if repository_version := kwargs.pop("repository_version", None):
415
- msg += f" from RepositoryVersion '{get_prn(repository_version)}'"
416
- if publication := kwargs.pop("publication", None):
417
- msg += f" from Publication '{get_prn(publication)}'"
418
- msg += "."
410
+ repository_version = kwargs.pop("repository_version", None)
411
+ publication = kwargs.pop("publication", None)
412
+
413
+ if repository_version and publication:
414
+ msg_template = _(
415
+ "Type for Remote '{remote}' does not match Repository '{repository}' "
416
+ "from RepositoryVersion '{repository_version}' from Publication '{publication}'."
417
+ )
418
+ elif repository_version:
419
+ msg_template = _(
420
+ "Type for Remote '{remote}' does not match Repository '{repository}' "
421
+ "from RepositoryVersion '{repository_version}'."
422
+ )
423
+ elif publication:
424
+ msg_template = _(
425
+ "Type for Remote '{remote}' does not match Repository '{repository}' "
426
+ "from Publication '{publication}'."
427
+ )
428
+ else:
429
+ msg_template = _("Type for Remote '{remote}' does not match Repository '{repository}'.")
419
430
 
420
- raise serializers.ValidationError({field: _(msg)})
431
+ raise serializers.ValidationError(
432
+ {
433
+ field: msg_template.format(
434
+ remote=get_prn(repository.remote),
435
+ repository=get_prn(repository),
436
+ repository_version=get_prn(repository_version) if repository_version else "",
437
+ publication=get_prn(publication) if publication else "",
438
+ )
439
+ }
440
+ )
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from cryptography.x509 import load_pem_x509_certificate
2
3
  from gettext import gettext as _
3
4
  from urllib.parse import urlparse
4
5
 
@@ -72,6 +73,46 @@ class RepositorySerializer(ModelSerializer):
72
73
  )
73
74
 
74
75
 
76
+ def validate_certificate(which_cert, value):
77
+ """
78
+ Validate and return *just* the certs and not any commentary that came along with them.
79
+
80
+ Args:
81
+ which_cert: The attribute-name whose cert we're validating (only used for error-message).
82
+ value: The string being proposed as a certificate-containing PEM.
83
+
84
+ Raises:
85
+ ValidationError: When the provided value has no or an invalid certificate.
86
+
87
+ Returns:
88
+ The pem-string with *just* the validated BEGIN/END CERTIFICATE segments.
89
+ """
90
+ if value:
91
+ try:
92
+ # Find any/all CERTIFICATE entries in the proposed PEM and let crypto validate them.
93
+ # NOTE: crypto/39 includes load_certificates(), which will let us remove this whole
94
+ # loop. But we want to fix the current problem on older supported branches that
95
+ # allow 38, so we do it ourselves for now
96
+ certs = list()
97
+ a_cert = ""
98
+ for line in value.split("\n"):
99
+ if "-----BEGIN CERTIFICATE-----" in line or a_cert:
100
+ a_cert += line + "\n"
101
+ if "-----END CERTIFICATE-----" in line:
102
+ load_pem_x509_certificate(bytes(a_cert, "ASCII"))
103
+ certs.append(a_cert.strip())
104
+ a_cert = ""
105
+ if not certs:
106
+ raise serializers.ValidationError(
107
+ "No {} specified in string {}".format(which_cert, value)
108
+ )
109
+ return "\n".join(certs) + "\n"
110
+ except ValueError as e:
111
+ raise serializers.ValidationError(
112
+ "Invalid {} specified, error '{}'".format(which_cert, e.args)
113
+ )
114
+
115
+
75
116
  class RemoteSerializer(ModelSerializer, HiddenFieldsMixin):
76
117
  """
77
118
  Every remote defined by a plugin should have a Remote serializer that inherits from this
@@ -271,6 +312,12 @@ class RemoteSerializer(ModelSerializer, HiddenFieldsMixin):
271
312
  raise serializers.ValidationError(_("proxy_url must not contain credentials"))
272
313
  return value
273
314
 
315
+ def validate_ca_cert(self, value):
316
+ return validate_certificate("ca_cert", value)
317
+
318
+ def validate_client_cert(self, value):
319
+ return validate_certificate("client_cert", value)
320
+
274
321
  def validate(self, data):
275
322
  """
276
323
  Check, that proxy credentials are only provided completely and if a proxy is configured.
@@ -365,9 +412,8 @@ class RepositorySyncURLSerializer(ValidateFieldsMixin, serializers.Serializer):
365
412
  if repository and type(remote.cast()) not in repository.cast().REMOTE_TYPES:
366
413
  raise serializers.ValidationError(
367
414
  {
368
- "remote": _(
369
- f"Type for Remote '{get_prn(remote)}' "
370
- f"does not match Repository '{get_prn(repository)}'."
415
+ "remote": _("Type for Remote '{}' does not match Repository '{}'.").format(
416
+ get_prn(remote), get_prn(repository)
371
417
  )
372
418
  }
373
419
  )
pulpcore/app/settings.py CHANGED
@@ -382,7 +382,19 @@ ALLOWED_CONTENT_CHECKSUMS = ["sha224", "sha256", "sha384", "sha512"]
382
382
 
383
383
  DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
384
384
 
385
- TASK_DIAGNOSTICS = False
385
+ # Possible diagnostics:
386
+ # * "memory"
387
+ # Logs the task process RSS every couple of seconds
388
+ # * "pyinstrument"
389
+ # Dumps an HTML profile report produced by pyinstrument, showing time spent in various
390
+ # callstacks. This adds ~10% overhead to the task process and consumes extra memory.
391
+ # Tweaking code might be warranted for some advanced settings.
392
+ # * "memray" - Dumps a report produced by memray which logs how much memory was allocated by which
393
+ # lines and functions, at the time of peak RSS of the task process. This adds significant
394
+ # runtime overhead to the task process, 20-40%. Tweaking code might be warranted for
395
+ # some advanced settings.
396
+ # NOTE: "memray" and "pyinstrument" require additional packages to be installed on the system.
397
+ TASK_DIAGNOSTICS = [] # ["memory", "pyinstrument", "memray"]
386
398
 
387
399
  ANALYTICS = True
388
400
 
@@ -1,11 +1,11 @@
1
1
  from pulpcore.app.tasks import base, repository, upload
2
2
 
3
3
  from .base import (
4
+ ageneral_update,
4
5
  general_create,
5
6
  general_create_from_temp_file,
6
7
  general_delete,
7
8
  general_multi_delete,
8
- general_update,
9
9
  )
10
10
 
11
11
  from .export import fs_publication_export, fs_repo_version_export
@@ -2,8 +2,11 @@ from django.db import transaction
2
2
 
3
3
  from pulpcore.app.apps import get_plugin_config
4
4
  from pulpcore.app.models import CreatedResource
5
+ from pulpcore.app.loggers import deprecation_logger
5
6
  from pulpcore.plugin.models import MasterModel
6
7
 
8
+ from asgiref.sync import sync_to_async
9
+
7
10
 
8
11
  def general_create_from_temp_file(app_label, serializer_name, temp_file_pk, *args, **kwargs):
9
12
  """
@@ -63,6 +66,10 @@ def general_update(instance_id, app_label, serializer_name, *args, **kwargs):
63
66
  due to validation error. This theoretically should never occur since validation is
64
67
  performed before the task is dispatched.
65
68
  """
69
+ deprecation_logger.warning(
70
+ "`pulpcore.app.tasks.base.general_update` is deprecated and will be removed in Pulp 4. "
71
+ "Use `pulpcore.app.tasks.base.ageneral_update` instead."
72
+ )
66
73
  data = kwargs.pop("data", None)
67
74
  partial = kwargs.pop("partial", False)
68
75
  serializer_class = get_plugin_config(app_label).named_serializers[serializer_name]
@@ -85,6 +92,10 @@ def general_delete(instance_id, app_label, serializer_name):
85
92
  app_label (str): the Django app label of the plugin that provides the model
86
93
  serializer_name (str): name of the serializer class for the model
87
94
  """
95
+ deprecation_logger.warning(
96
+ "`pulpcore.app.tasks.base.general_delete` is deprecated and will be removed in Pulp 4. "
97
+ "Use `pulpcore.app.tasks.base.ageneral_delete` instead."
98
+ )
88
99
  serializer_class = get_plugin_config(app_label).named_serializers[serializer_name]
89
100
  instance = serializer_class.Meta.model.objects.get(pk=instance_id)
90
101
  if isinstance(instance, MasterModel):
@@ -111,3 +122,29 @@ def general_multi_delete(instance_ids):
111
122
  with transaction.atomic():
112
123
  for instance in instances:
113
124
  instance.delete()
125
+
126
+
127
+ async def ageneral_update(instance_id, app_label, serializer_name, *args, **kwargs):
128
+ """
129
+ Async version of [pulpcore.app.tasks.base.general_update][].
130
+ """
131
+ data = kwargs.pop("data", None)
132
+ partial = kwargs.pop("partial", False)
133
+ serializer_class = get_plugin_config(app_label).named_serializers[serializer_name]
134
+ instance = await serializer_class.Meta.model.objects.aget(pk=instance_id)
135
+ if isinstance(instance, MasterModel):
136
+ instance = await instance.acast()
137
+ serializer = serializer_class(instance, data=data, partial=partial)
138
+ await sync_to_async(serializer.is_valid)(raise_exception=True)
139
+ await sync_to_async(serializer.save)()
140
+
141
+
142
+ async def ageneral_delete(instance_id, app_label, serializer_name):
143
+ """
144
+ Async version of [pulpcore.app.tasks.base.general_delete][].
145
+ """
146
+ serializer_class = get_plugin_config(app_label).named_serializers[serializer_name]
147
+ instance = await serializer_class.Meta.model.objects.aget(pk=instance_id)
148
+ if isinstance(instance, MasterModel):
149
+ instance = await instance.acast()
150
+ await instance.adelete()
@@ -1,4 +1,6 @@
1
+ import asyncio
1
2
  import backoff
3
+ import time
2
4
  from pulpcore.app.models import TaskGroup
3
5
  from pulpcore.tasking.tasks import dispatch
4
6
 
@@ -9,17 +11,18 @@ def dummy_task():
9
11
 
10
12
 
11
13
  def sleep(interval):
12
- from time import sleep
14
+ time.sleep(interval)
13
15
 
14
- sleep(interval)
16
+
17
+ async def asleep(interval):
18
+ """Async function that sleeps."""
19
+ await asyncio.sleep(interval)
15
20
 
16
21
 
17
22
  @backoff.on_exception(backoff.expo, BaseException)
18
23
  def gooey_task(interval):
19
24
  """A sleep task that tries to avoid being killed by ignoring all exceptions."""
20
- from time import sleep
21
-
22
- sleep(interval)
25
+ time.sleep(interval)
23
26
 
24
27
 
25
28
  def dummy_group_task(inbetween=3, intervals=None):
@@ -28,5 +31,5 @@ def dummy_group_task(inbetween=3, intervals=None):
28
31
  task_group = TaskGroup.current()
29
32
  for interval in intervals:
30
33
  dispatch(sleep, args=(interval,), task_group=task_group)
31
- sleep(inbetween)
34
+ time.sleep(inbetween)
32
35
  task_group.finish()
@@ -492,7 +492,7 @@ class AsyncUpdateMixin(AsyncReservedObjectMixin):
492
492
  serializer.is_valid(raise_exception=True)
493
493
  app_label = instance._meta.app_label
494
494
  task = dispatch(
495
- tasks.base.general_update,
495
+ tasks.base.ageneral_update,
496
496
  exclusive_resources=self.async_reserved_resources(instance),
497
497
  args=(pk, app_label, serializer.__class__.__name__),
498
498
  kwargs={"data": request.data, "partial": partial},
@@ -528,7 +528,7 @@ class AsyncRemoveMixin(AsyncReservedObjectMixin):
528
528
  serializer = self.get_serializer(instance)
529
529
  app_label = instance._meta.app_label
530
530
  task = dispatch(
531
- tasks.base.general_delete,
531
+ tasks.base.ageneral_delete,
532
532
  exclusive_resources=self.async_reserved_resources(instance),
533
533
  args=(pk, app_label, serializer.__class__.__name__),
534
534
  immediate=self.ALLOW_NON_BLOCKING_DELETE,
pulpcore/constants.py CHANGED
@@ -45,6 +45,8 @@ TASK_FINAL_STATES = (
45
45
  #: Tasks in an incomplete state have not finished their work yet.
46
46
  TASK_INCOMPLETE_STATES = (TASK_STATES.WAITING, TASK_STATES.RUNNING, TASK_STATES.CANCELING)
47
47
 
48
+ #: Timeout for immediate tasks in seconds
49
+ IMMEDIATE_TIMEOUT = 5
48
50
 
49
51
  SYNC_MODES = SimpleNamespace(ADDITIVE="additive", MIRROR="mirror")
50
52
  SYNC_CHOICES = (
@@ -2,13 +2,13 @@
2
2
  from pulpcore.tasking.tasks import dispatch
3
3
 
4
4
  from pulpcore.app.tasks import (
5
+ ageneral_update,
5
6
  fs_publication_export,
6
7
  fs_repo_version_export,
7
8
  general_create,
8
9
  general_create_from_temp_file,
9
10
  general_delete,
10
11
  general_multi_delete,
11
- general_update,
12
12
  orphan_cleanup,
13
13
  reclaim_space,
14
14
  )
@@ -16,6 +16,7 @@ from pulpcore.app.tasks.repository import add_and_remove
16
16
 
17
17
 
18
18
  __all__ = [
19
+ "ageneral_update",
19
20
  "dispatch",
20
21
  "fs_publication_export",
21
22
  "fs_repo_version_export",
@@ -23,7 +24,6 @@ __all__ = [
23
24
  "general_create_from_temp_file",
24
25
  "general_delete",
25
26
  "general_multi_delete",
26
- "general_update",
27
27
  "orphan_cleanup",
28
28
  "reclaim_space",
29
29
  "add_and_remove",
pulpcore/pytest_plugin.py CHANGED
@@ -9,6 +9,7 @@ import shutil
9
9
  import socket
10
10
  import ssl
11
11
  import subprocess
12
+ import sys
12
13
  import threading
13
14
  import uuid
14
15
 
@@ -1017,6 +1018,7 @@ def dispatch_task(pulpcore_bindings):
1017
1018
 
1018
1019
  assert process.returncode == 0
1019
1020
  task_href = process.stdout.decode().strip()
1021
+ print(process.stderr.decode(), file=sys.stderr)
1020
1022
  return task_href
1021
1023
 
1022
1024
  return _dispatch_task
pulpcore/tasking/_util.py CHANGED
@@ -71,8 +71,8 @@ def write_memory_usage(stop_event, path):
71
71
  current_mb_in_use = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024
72
72
  file.write(f"{seconds}\t{current_mb_in_use:.2f}\n")
73
73
  file.flush()
74
- time.sleep(5)
75
- seconds += 5
74
+ time.sleep(2)
75
+ seconds += 2
76
76
 
77
77
 
78
78
  def child_signal_handler(sig, frame):
@@ -135,15 +135,21 @@ def perform_task(task_pk, task_working_dir_rel_path):
135
135
 
136
136
  def _execute_task_and_profile(task):
137
137
  with tempfile.TemporaryDirectory(dir=settings.WORKING_DIRECTORY) as temp_dir:
138
- pyinstrument_func = _pyinstrument_diagnostic_decorator(temp_dir, execute_task)
139
- memory_func = _memory_diagnostic_decorator(temp_dir, pyinstrument_func)
138
+ _execute_task = execute_task
140
139
 
141
- memory_func(task)
140
+ if settings.TASK_DIAGNOSTICS is True or "memory" in settings.TASK_DIAGNOSTICS:
141
+ _execute_task = _memory_diagnostic_decorator(temp_dir, _execute_task)
142
+ if settings.TASK_DIAGNOSTICS is True or "pyinstrument" in settings.TASK_DIAGNOSTICS:
143
+ _execute_task = _pyinstrument_diagnostic_decorator(temp_dir, _execute_task)
144
+ if settings.TASK_DIAGNOSTICS is True or "memray" in settings.TASK_DIAGNOSTICS:
145
+ _execute_task = _memray_diagnostic_decorator(temp_dir, _execute_task)
146
+
147
+ _execute_task(task)
142
148
 
143
149
 
144
150
  def _memory_diagnostic_decorator(temp_dir, func):
145
151
  def __memory_diagnostic_decorator(task):
146
- mem_diagnostics_file_path = os.path.join(temp_dir, "memory.datum")
152
+ mem_diagnostics_file_path = os.path.join(temp_dir, "memory_profile.datum")
147
153
  # It would be better to have this recording happen in the parent process instead of here
148
154
  # https://github.com/pulp/pulpcore/issues/2337
149
155
  stop_event = threading.Event()
@@ -158,10 +164,10 @@ def _memory_diagnostic_decorator(temp_dir, func):
158
164
  artifact = Artifact.init_and_validate(mem_diagnostics_file_path)
159
165
  with suppress(IntegrityError):
160
166
  artifact.save()
161
-
162
- ProfileArtifact.objects.get_or_create(artifact=artifact, name="memory_profile", task=task)
163
-
164
- _logger.info("Created memory diagnostic data.")
167
+ ProfileArtifact.objects.get_or_create(
168
+ artifact=artifact, name="memory_profile", task=task
169
+ )
170
+ _logger.info("Created memory diagnostic data.")
165
171
 
166
172
  return __memory_diagnostic_decorator
167
173
 
@@ -171,7 +177,7 @@ def _pyinstrument_diagnostic_decorator(temp_dir, func):
171
177
  if importlib.util.find_spec("pyinstrument") is not None:
172
178
  from pyinstrument import Profiler
173
179
 
174
- with Profiler() as profiler:
180
+ with Profiler(interval=0.002) as profiler:
175
181
  func(task)
176
182
 
177
183
  profile_file_path = os.path.join(temp_dir, "pyinstrument.html")
@@ -182,16 +188,40 @@ def _pyinstrument_diagnostic_decorator(temp_dir, func):
182
188
  artifact = Artifact.init_and_validate(str(profile_file_path))
183
189
  with suppress(IntegrityError):
184
190
  artifact.save()
191
+ ProfileArtifact.objects.get_or_create(
192
+ artifact=artifact, name="pyinstrument_profile", task=task
193
+ )
194
+ _logger.info("Created pyinstrument profile data.")
195
+ else:
196
+ func(task)
185
197
 
186
- ProfileArtifact.objects.get_or_create(
187
- artifact=artifact, name="pyinstrument_data", task=task
188
- )
198
+ return __pyinstrument_diagnostic_decorator
199
+
200
+
201
+ def _memray_diagnostic_decorator(temp_dir, func):
202
+ def __memray_diagnostic_decorator(task):
203
+ if importlib.util.find_spec("memray") is not None:
204
+ import memray
205
+
206
+ profile_file_path = os.path.join(temp_dir, "memray_profile.bin")
207
+ with memray.Tracker(
208
+ profile_file_path,
209
+ native_traces=False,
210
+ file_format=memray.FileFormat.AGGREGATED_ALLOCATIONS,
211
+ ):
212
+ func(task)
189
213
 
190
- _logger.info("Created pyinstrument profile data.")
214
+ artifact = Artifact.init_and_validate(str(profile_file_path))
215
+ with suppress(IntegrityError):
216
+ artifact.save()
217
+ ProfileArtifact.objects.get_or_create(
218
+ artifact=artifact, name="memray_profile", task=task
219
+ )
220
+ _logger.info("Created memray memory profile data.")
191
221
  else:
192
222
  func(task)
193
223
 
194
- return __pyinstrument_diagnostic_decorator
224
+ return __memray_diagnostic_decorator
195
225
 
196
226
 
197
227
  def dispatch_scheduled_tasks():
pulpcore/tasking/tasks.py CHANGED
@@ -7,6 +7,8 @@ import os
7
7
  import sys
8
8
  import traceback
9
9
  import tempfile
10
+ import threading
11
+ from asgiref.sync import sync_to_async
10
12
  from datetime import timedelta
11
13
  from gettext import gettext as _
12
14
 
@@ -16,12 +18,13 @@ from django.db.models import Model, Max
16
18
  from django_guid import get_guid
17
19
  from pulpcore.app.apps import MODULE_PLUGIN_VERSIONS
18
20
  from pulpcore.app.models import Task, TaskGroup
19
- from pulpcore.app.util import current_task, get_domain, get_prn
21
+ from pulpcore.app.util import current_task, get_domain, get_prn, deprecation_logger
20
22
  from pulpcore.constants import (
21
23
  TASK_FINAL_STATES,
22
24
  TASK_INCOMPLETE_STATES,
23
25
  TASK_STATES,
24
26
  TASK_DISPATCH_LOCK,
27
+ IMMEDIATE_TIMEOUT,
25
28
  )
26
29
  from pulpcore.tasking.kafka import send_task_notification
27
30
 
@@ -75,11 +78,38 @@ def _execute_task(task):
75
78
  func = getattr(module, function_name)
76
79
  args = task.enc_args or ()
77
80
  kwargs = task.enc_kwargs or {}
78
- result = func(*args, **kwargs)
79
- if asyncio.iscoroutine(result):
81
+ immediate = task.immediate
82
+ is_coroutine_fn = asyncio.iscoroutinefunction(func)
83
+
84
+ if not is_coroutine_fn:
85
+ if immediate:
86
+ deprecation_logger.warning(
87
+ "Immediate tasks must be coroutine functions. "
88
+ "Support for non-coroutine immediate tasks will be dropped "
89
+ "in pulpcore 3.85."
90
+ )
91
+ func = sync_to_async(func)
92
+ is_coroutine_fn = True
93
+ else:
94
+ func(*args, **kwargs)
95
+
96
+ if is_coroutine_fn:
80
97
  _logger.debug("Task is coroutine %s", task.pk)
98
+ coro = func(*args, **kwargs)
99
+ if immediate:
100
+ coro = asyncio.wait_for(coro, timeout=IMMEDIATE_TIMEOUT)
81
101
  loop = asyncio.get_event_loop()
82
- loop.run_until_complete(result)
102
+ try:
103
+ loop.run_until_complete(coro)
104
+ except asyncio.TimeoutError:
105
+ _logger.info(
106
+ "Immediate task %s timed out after %s seconds.", task.pk, IMMEDIATE_TIMEOUT
107
+ )
108
+ raise RuntimeError(
109
+ "Immediate task timed out after {timeout} seconds.".format(
110
+ timeout=IMMEDIATE_TIMEOUT,
111
+ )
112
+ )
83
113
 
84
114
  except Exception:
85
115
  exc_type, exc, tb = sys.exc_info()
@@ -101,6 +131,14 @@ def _execute_task(task):
101
131
  send_task_notification(task)
102
132
 
103
133
 
134
+ def running_from_thread_pool() -> bool:
135
+ # TODO: this needs an alternative approach ASAP!
136
+ # Currently we rely on the weak fact that ThreadPoolExecutor names threads like:
137
+ # "ThreadPoolExecutor-0_0"
138
+ thread_name = threading.current_thread().name
139
+ return "ThreadPoolExecutor" in thread_name
140
+
141
+
104
142
  def dispatch(
105
143
  func,
106
144
  args=None,
@@ -148,6 +186,8 @@ def dispatch(
148
186
  ValueError: When `resources` is an unsupported type.
149
187
  """
150
188
 
189
+ # Can't run short tasks immediately if running from thread pool
190
+ immediate = immediate and not running_from_thread_pool()
151
191
  assert deferred or immediate, "A task must be at least `deferred` or `immediate`."
152
192
 
153
193
  if callable(func):
@@ -10,8 +10,10 @@ from urllib.parse import urljoin
10
10
  from uuid import uuid4
11
11
 
12
12
  from pulpcore.client.pulpcore import ApiException
13
+ from contextlib import contextmanager
13
14
 
14
- from pulpcore.tests.functional.utils import download_file
15
+ from pulpcore.tests.functional.utils import download_file, PulpTaskError
16
+ from pulpcore.constants import IMMEDIATE_TIMEOUT
15
17
 
16
18
 
17
19
  @pytest.fixture(scope="module")
@@ -445,3 +447,151 @@ def test_cancel_task_group(pulpcore_bindings, dispatch_task_group, gen_user):
445
447
 
446
448
  with gen_user(model_roles=["core.task_owner"]):
447
449
  pulpcore_bindings.TaskGroupsApi.task_groups_cancel(tgroup_href, {"state": "canceled"})
450
+
451
+
452
+ LT_TIMEOUT = IMMEDIATE_TIMEOUT / 2
453
+ GT_TIMEOUT = IMMEDIATE_TIMEOUT * 2
454
+
455
+
456
+ class TestImmediateTaskWithNoResource:
457
+
458
+ @pytest.mark.parallel
459
+ def test_succeeds_on_api_worker(self, pulpcore_bindings, dispatch_task):
460
+ """
461
+ GIVEN a task with no resource requirements
462
+ AND the task IS an async function
463
+ WHEN dispatching a task as immediate
464
+ THEN the task completes with no associated worker
465
+ """
466
+ task_href = dispatch_task(
467
+ "pulpcore.app.tasks.test.asleep", args=(LT_TIMEOUT,), immediate=True
468
+ )
469
+ task = pulpcore_bindings.TasksApi.read(task_href)
470
+ assert task.state == "completed"
471
+ assert task.worker is None
472
+
473
+ @pytest.mark.parallel
474
+ def test_executes_on_api_worker_when_no_async(self, pulpcore_bindings, dispatch_task, capsys):
475
+ """
476
+ GIVEN a task with no resource requirements
477
+ AND the task IS NOT an async function
478
+ WHEN dispatching a task as immediate
479
+ THEN the task completes with no associated worker
480
+ """
481
+ # TODO: on 3.85 this should throw an error
482
+ task_href = dispatch_task(
483
+ "pulpcore.app.tasks.test.sleep", args=(LT_TIMEOUT,), immediate=True
484
+ )
485
+ stderr_content = capsys.readouterr().err
486
+ task = pulpcore_bindings.TasksApi.read(task_href)
487
+ assert task.state == "completed"
488
+ assert task.worker is None
489
+ assert "Support for non-coroutine immediate tasks will be dropped" in stderr_content
490
+
491
+ @pytest.mark.parallel
492
+ def test_timeouts_on_api_worker(self, pulpcore_bindings, dispatch_task):
493
+ """
494
+ GIVEN a task with no resource requirements
495
+ AND the task is an async function
496
+ WHEN dispatching a task as immediate
497
+ AND it takes longer than timeout
498
+ THEN the task fails with a timeout error message
499
+ """
500
+ task_href = dispatch_task(
501
+ "pulpcore.app.tasks.test.asleep", args=(GT_TIMEOUT,), immediate=True
502
+ )
503
+ task = pulpcore_bindings.TasksApi.read(task_href)
504
+ assert task.worker is None
505
+ assert "task timed out after" in task.error["description"]
506
+
507
+
508
+ @pytest.fixture
509
+ def resource_blocker(pulpcore_bindings, dispatch_task):
510
+
511
+ @contextmanager
512
+ def _resource_blocker(exclusive_resources: list[str], duration=20):
513
+ task_href = dispatch_task(
514
+ "pulpcore.app.tasks.test.sleep",
515
+ args=(duration,),
516
+ exclusive_resources=exclusive_resources,
517
+ )
518
+ yield
519
+ # Trying to cancel a finished task will return a 409 code.
520
+ # We can ignore if that's the case, because all we want here is to cut time down.
521
+ # Otherwise it might be a real error.
522
+ try:
523
+ pulpcore_bindings.TasksApi.tasks_cancel(task_href, {"state": "canceled"})
524
+ except ApiException as e:
525
+ if e.status != 409:
526
+ raise
527
+
528
+ return _resource_blocker
529
+
530
+
531
+ class TestImmediateTaskWithBlockedResource:
532
+
533
+ @pytest.mark.parallel
534
+ def test_executes_in_task_worker(
535
+ self, resource_blocker, dispatch_task, monitor_task, pulpcore_bindings
536
+ ):
537
+ """
538
+ GIVEN an async task requiring busy resources
539
+ WHEN dispatching a task as immediate
540
+ THEN the task completes with a worker
541
+ """
542
+ COMMON_RESOURCE = str(uuid4())
543
+ with resource_blocker(exclusive_resources=[COMMON_RESOURCE]):
544
+ task_href = dispatch_task(
545
+ "pulpcore.app.tasks.test.asleep",
546
+ args=(LT_TIMEOUT,),
547
+ immediate=True,
548
+ exclusive_resources=[COMMON_RESOURCE],
549
+ )
550
+ task = monitor_task(task_href)
551
+ assert task.state == "completed"
552
+ assert task.worker is not None
553
+
554
+ @pytest.mark.parallel
555
+ def test_throws_when_non_deferrable(
556
+ self, resource_blocker, pulpcore_bindings, dispatch_task, monitor_task
557
+ ):
558
+ """
559
+ GIVEN an async task requiring busy resources
560
+ WHEN dispatching as immediate and not deferrable
561
+ THEN an error is raised
562
+ """
563
+ COMMON_RESOURCE = str(uuid4())
564
+ with resource_blocker(exclusive_resources=[COMMON_RESOURCE]):
565
+ task_href = dispatch_task(
566
+ "pulpcore.app.tasks.test.asleep",
567
+ args=(0,),
568
+ immediate=True,
569
+ deferred=False,
570
+ exclusive_resources=[COMMON_RESOURCE],
571
+ )
572
+ task = pulpcore_bindings.TasksApi.read(task_href)
573
+ assert task.state == "canceled"
574
+ assert task.worker is None
575
+ assert "Resources temporarily unavailable." in task.error["reason"]
576
+
577
+ @pytest.mark.parallel
578
+ def test_times_out_on_task_worker(
579
+ self, resource_blocker, pulpcore_bindings, dispatch_task, monitor_task
580
+ ):
581
+ """
582
+ GIVEN an async task requiring busy resources
583
+ WHEN dispatching a task as immediate
584
+ AND it takes longer than timeout
585
+ THEN an error is raised
586
+ """
587
+ COMMON_RESOURCE = str(uuid4())
588
+ with pytest.raises(PulpTaskError) as ctx:
589
+ with resource_blocker(exclusive_resources=[COMMON_RESOURCE]):
590
+ task_href = dispatch_task(
591
+ "pulpcore.app.tasks.test.asleep",
592
+ args=(GT_TIMEOUT,),
593
+ immediate=True,
594
+ exclusive_resources=[COMMON_RESOURCE],
595
+ )
596
+ monitor_task(task_href)
597
+ assert "task timed out after" in ctx.value.task.error["description"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pulpcore
3
- Version: 3.76.1
3
+ Version: 3.77.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.2.0,>=3.0
24
+ Requires-Dist: aiodns<=3.3.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.3,>=38.0.1
31
+ Requires-Dist: cryptography<44.0.4,>=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
@@ -52,13 +52,13 @@ Requires-Dist: opentelemetry-exporter-otlp-proto-http<1.33,>=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.6,>=3.1.8
55
+ Requires-Dist: psycopg[binary]<=3.2.7,>=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
59
59
  Requires-Dist: redis<5.2.2,>=4.3
60
60
  Requires-Dist: tablib<3.6.0
61
- Requires-Dist: url-normalize<=2.2.0,>=1.4.3
61
+ Requires-Dist: url-normalize<=2.2.1,>=1.4.3
62
62
  Requires-Dist: uuid6<=2024.7.10,>=2023.5.2
63
63
  Requires-Dist: whitenoise<6.10.0,>=5.0
64
64
  Requires-Dist: yarl<1.20.1,>=1.8
@@ -75,6 +75,9 @@ Requires-Dist: django-prometheus; extra == "prometheus"
75
75
  Provides-Extra: kafka
76
76
  Requires-Dist: cloudevents==1.11.0; extra == "kafka"
77
77
  Requires-Dist: confluent-kafka<2.10.0,>=2.4.0; extra == "kafka"
78
+ Provides-Extra: diagnostics
79
+ Requires-Dist: pyinstrument~=5.0; extra == "diagnostics"
80
+ Requires-Dist: memray~=1.17; extra == "diagnostics"
78
81
  Dynamic: license-file
79
82
 
80
83
 
@@ -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=ZJG_hpr0DY5FK-rulaYcw81s2Vz9_tsPykOohd-fyDk,297
3
+ pulp_certguard/app/__init__.py,sha256=Ri9QtsoSI0bmv_5qz0wCvWhDbrGkCr8nUXdTtLNS8QM,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=Uj8lL9og-flRYyE8dXQGqmcwcyki0SyG4Qswk-CRTRo,292
52
+ pulp_file/app/__init__.py,sha256=jbAB2BH6-V4jKXVeEoaxYLmvFY-CRmc2LSOL3mBWyEw,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
@@ -94,23 +94,23 @@ pulp_file/tests/functional/api/test_mime_types.py,sha256=ZTZVpEnYXcXsDVo1PaaWEXY
94
94
  pulp_file/tests/functional/api/test_publish.py,sha256=Bjb-IHknmlXS0gY15OUDvQFNJJQJqWaXU7uFmORBOOQ,5844
95
95
  pulp_file/tests/functional/api/test_pulp_export.py,sha256=j8MJs1H4-fdRZecxroYpKHEu0aqcsPw21Fxx6YC3b10,10275
96
96
  pulp_file/tests/functional/api/test_rbac.py,sha256=8smU2gy94Bj03CiKC_8nIbB-AdZxoLbJT3G2rzGENgk,12423
97
- pulp_file/tests/functional/api/test_remote_settings.py,sha256=g36B9HobAc7c_pRhUArRs0_7B6ZQyi-WS35Fh6jxui8,5362
97
+ pulp_file/tests/functional/api/test_remote_settings.py,sha256=gpIgzuPCR2y_fJF-lOIA5SFFMcVEWfFxF6S0PiHAl7s,12034
98
98
  pulp_file/tests/functional/api/test_sync.py,sha256=jN87UoibkoJkqBV36ctvB9v72JKQHFnciRxFA-_1VfA,5673
99
99
  pulp_file/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
100
  pulp_file/tests/unit/test_safe_paths.py,sha256=CRJX3-MdIZF_4-hVK-7brH9LSK2i97GdIYdqpe3Ao84,3796
101
101
  pulp_file/tests/unit/test_serializers.py,sha256=reDGIZrAaPHITwiv-LSCJ85JK-aCcNh5cavmAaba8vw,2143
102
102
  pulpcore/__init__.py,sha256=9L859gHcVX5TxrTP0Ef7GWv8oa7tsvIs_8XDkyZIu2g,107
103
103
  pulpcore/backends.py,sha256=Ax_MJpbvtNDg_rhkHaiQRm39DBSS2dH8UpMRJN2T0oE,4482
104
- pulpcore/constants.py,sha256=hhAt5-yKVdVpTxmK5U06ncMMEoHLx6W0imCURLVz2aI,4609
104
+ pulpcore/constants.py,sha256=06lih8sVRzHCrBXGmoT3Q-9ZkKZUEEYZ8EoeFfxEq04,4673
105
105
  pulpcore/filters.py,sha256=dD5oRRkWg65s3LoObr-ipRvRsxZK_3Zr0lKMNr9Sg5o,16682
106
106
  pulpcore/metrics.py,sha256=Mfq-nnRjRf3vBHFO-ux-4d1I3yE7TgeptwgiSgGz4rA,2230
107
107
  pulpcore/middleware.py,sha256=tJB2rGnulvNBN1fZ6lyN4zd60ja5QUNYD4l5dLHHaoY,5079
108
108
  pulpcore/migrations.py,sha256=gzUTxcXnbYkM0_vN8Rfr_XWuwsGWedVZAwNy9JqJ8sQ,2476
109
- pulpcore/pytest_plugin.py,sha256=wI6pXJ1minJh_dza-gOMLp9pWeuhKNyLc3PaZxpYkDI,37713
109
+ pulpcore/pytest_plugin.py,sha256=Tq_xlO8Z2iyjFtbnaKHbWQogq6jxcRpjji9XbKrs5_U,37780
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=FbhBemzuCmMHD96tM274zjH2MPm9skxiWtpXU9S392Y,17860
113
+ pulpcore/app/apps.py,sha256=Dyk1l_Cch5VavYZp-bbWsJA1SP9RcqQN9dPjHEKmtRE,17860
114
114
  pulpcore/app/authentication.py,sha256=1LIJW6HIQQlZrliHy__jdzkDEh6Oj7xKgd0V-vRcDus,2855
115
115
  pulpcore/app/checks.py,sha256=jbfTF7nmftBbky4AQXHigpyCaGydKasvRUXsd72JZVg,1946
116
116
  pulpcore/app/entrypoint.py,sha256=HRfaHDkveSIfcTOtWEWYqg1poTmTo0J9hzzmj0yDcEM,4885
@@ -125,10 +125,10 @@ pulpcore/app/openpgp.py,sha256=jyvOBlu_wGDH_aem3TN8hw51Dx2dd3NyLzCw4Q9Xlf0,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=V7Xk4NoKv8JtCe0YYicDw1GwAfCifvWIGvfon2vrv-0,11671
128
+ pulpcore/app/replica.py,sha256=b6r-QF4H4G94N5HoaV3PGHeOD4-BqVb7YVsRNHx0h9Y,11675
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=0541xz9CH1WFRsrjQ8PvqAkMos871upg4X4oIf3I9go,21713
131
+ pulpcore/app/settings.py,sha256=34pHnUfM1AzF1nKNtt31nthuFyQBd006fcH_Hq4ZmxI,22509
132
132
  pulpcore/app/urls.py,sha256=0gdI74CAdycJStXSw1gknviDGe3J3k0UhS4J8RYa5dg,8120
133
133
  pulpcore/app/util.py,sha256=kenzRmvDl1obKFb806ETlEE2qs8h3Y1KcCe-Q8AtYGY,24442
134
134
  pulpcore/app/wsgi.py,sha256=7rpZ_1NHEN_UfeNZCj8206bas1WeqRkHnGdxpd7rdDI,492
@@ -318,19 +318,19 @@ pulpcore/app/serializers/importer.py,sha256=PVSNs5U0dfAm-XlRKpMqOXK0VmUErxJauNJC
318
318
  pulpcore/app/serializers/openpgp.py,sha256=3Svxskj_-HmOVbjay7QI82zXnKTsbtaSlZZ03CoT-MQ,8966
319
319
  pulpcore/app/serializers/orphans.py,sha256=Vhyaj0fqYT4pkiYoNjgmsy1u5BiR_aHwZm2y7rk9cbk,1967
320
320
  pulpcore/app/serializers/progress.py,sha256=j4IQDLb_XOrLzTud4Fq8T-8kkOqLewReMVkbS5uCEIg,2575
321
- pulpcore/app/serializers/publication.py,sha256=66k9ZCdxK69IMLHeoldIHtRUFKZqRdS3AJGmWjmItqc,15569
321
+ pulpcore/app/serializers/publication.py,sha256=9Lzvn5e04TQ8EVMR_aR5NWK-P3X_4PYSUaWAfD2rHQg,16439
322
322
  pulpcore/app/serializers/purge.py,sha256=CnjKWUvkuI207QMbqwmNs7FqMdOMUh1cujagby3vVM0,779
323
323
  pulpcore/app/serializers/reclaim.py,sha256=-ewdNqu-Ck1B_IUWJHG0pvN5zCMMEK9RiWI45g7D0ro,1710
324
324
  pulpcore/app/serializers/repair.py,sha256=uKrxTnhoarxyyGCixPRn9pmG19gRRVUTM7nPwCVp6_8,554
325
325
  pulpcore/app/serializers/replica.py,sha256=E3jwn1vfBqT4Y4s9pWsTrUEJKPO9pO0q2ZmwcpIDVh4,4044
326
- pulpcore/app/serializers/repository.py,sha256=ak75nibokkVL841Ulau6W77csn1w9MsIS9e5fHmu25A,18299
326
+ pulpcore/app/serializers/repository.py,sha256=fSM92qJTjQIGXgnmA4xr62jRG9atYj6tkI_ZM6VFaM0,20224
327
327
  pulpcore/app/serializers/status.py,sha256=nIrQl-MlOzUIvV2DrkgC19gqGmRVNKvWVN4pIBELgcQ,3815
328
328
  pulpcore/app/serializers/task.py,sha256=IGJGoSEC_wKS8t77JGnZWRqK-Mk5-4rXSj8j0Ha6nRA,10026
329
329
  pulpcore/app/serializers/upload.py,sha256=4r6iBegbYHmgFYjBYPcqB8J7eSxXgY4ukayMxJZNh_M,2402
330
330
  pulpcore/app/serializers/user.py,sha256=gw-Qju1akkDtjxiehNM10jnoeaoApY1MATNfpboAFoY,17122
331
- pulpcore/app/tasks/__init__.py,sha256=5yb1gSzThCPXej80a8MMhQ3ykllxkYTjHLcciS5peZ4,575
331
+ pulpcore/app/tasks/__init__.py,sha256=6fhLD0Z9LMluzqyBwQkatId71qI_2U7-o2-ZI1JH1Ls,576
332
332
  pulpcore/app/tasks/analytics.py,sha256=eB3p-sdocH5yyNoe0OG5rUzwiVOfayOfHNzkohAfx-U,4722
333
- pulpcore/app/tasks/base.py,sha256=1_sn4pSVV3-UB1ZX3zu9pD3tYAop4VFFIQUCEDm8yRU,4330
333
+ pulpcore/app/tasks/base.py,sha256=4I88Bn5SttqEvvVlNJmIwkPv2IWe7OhpM-kbQiQ9T_U,5929
334
334
  pulpcore/app/tasks/export.py,sha256=dRg-KcnM7HumXUx8mjgJ-EVMcz2RbzSOPmMkzVtJEnI,19320
335
335
  pulpcore/app/tasks/importer.py,sha256=tQXCQZslaMySeN6BS6hLZtiGvq-XE-bwjsEjF7fAJNc,23247
336
336
  pulpcore/app/tasks/migrate.py,sha256=wCjGskoF-XWzbINEyC_crgcigFZlC8EHqZTbjkLQykg,2452
@@ -340,7 +340,7 @@ pulpcore/app/tasks/reclaim_space.py,sha256=FZ7KFasbScPAU7A6lzK98pdylmqgThssgnNMe
340
340
  pulpcore/app/tasks/replica.py,sha256=x-Yjd8Z4EUhrhuF1DCX5jCa6F7FTAE3th-161lnLN2g,4509
341
341
  pulpcore/app/tasks/repository.py,sha256=tPQ8KvsAUgX8Gd_hXsSc64uCmhVRQqHh36bykHlUPIw,9631
342
342
  pulpcore/app/tasks/telemetry.py,sha256=QXOcYi7VIx_TBPCfs2BfcaiiVzRCiTFPZCN8MlC-hw8,1338
343
- pulpcore/app/tasks/test.py,sha256=yC1_zinIIvmn1yAAdvJZGXG0h4Bh08M5bALA3y2niUQ,824
343
+ pulpcore/app/tasks/test.py,sha256=_3BdJzdtEGdyL7qnzl0apwAoGeleYPeZTr7IJ5COFyo,912
344
344
  pulpcore/app/tasks/upload.py,sha256=3YJa32XYUFgqkEEWoERRPB9Q6ph9a6ashMtMi24R15k,1413
345
345
  pulpcore/app/templates/rest_framework/api.html,sha256=n2ymhyLN7tQ5dYc5uceRSqS5L8fsPnSHIfCBsFMshnM,246
346
346
  pulpcore/app/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -353,7 +353,7 @@ pulpcore/app/views/status.py,sha256=snoAyU7KCfaxe04Tc9UF9Go8A8-zCxpKoZ6nxFfmaVU,
353
353
  pulpcore/app/viewsets/__init__.py,sha256=iaDmZbjJalbU5BQ8q9sTKRX-64ToAnnyuHiLiFoyE-Y,2225
354
354
  pulpcore/app/viewsets/access_policy.py,sha256=Tjo443gpPfR9gRAgfQfTuV15eMWylZa7lPLucTAIKhQ,2447
355
355
  pulpcore/app/viewsets/acs.py,sha256=JabJntgGAMELbarKlTXDWrgMSYdMPgv6evJxoRdj66Y,2078
356
- pulpcore/app/viewsets/base.py,sha256=dqR5FbxtwlUUc1ERgZok1cT7_imc-aNauo1rg5hktmc,27135
356
+ pulpcore/app/viewsets/base.py,sha256=7FgUMLl3ZOIflnzCEcrXr6spKRLNXyfffAHnuE3gQA8,27137
357
357
  pulpcore/app/viewsets/content.py,sha256=PLKdY6l_D5_5i3aFOYdVLAz5zpjKEJmXKrEFJOrcOFg,7919
358
358
  pulpcore/app/viewsets/custom_filters.py,sha256=_O3HPCbZ_we4ZO4gxCMQwWvDjk8JMisGIRwpkk2rkSs,14465
359
359
  pulpcore/app/viewsets/domain.py,sha256=HLdd3AvAa4gp64JPGQa0iYveSqVjFdsEIVvBksl7LCk,6059
@@ -404,7 +404,7 @@ pulpcore/plugin/repo_version_utils.py,sha256=nuo55r5rQgDoFfdaobuyvqINW1B8k5xM618
404
404
  pulpcore/plugin/responses.py,sha256=VcgJtVaTzCPjdIGz_AS4nQwO4ooh4O5T9KBEa1FGA1w,62
405
405
  pulpcore/plugin/storage.py,sha256=CMoWFPfZG82-SOmuuaf8aPDYerK7mvxlER_cgWdJzQA,82
406
406
  pulpcore/plugin/sync.py,sha256=IwAUZ_7vVYuba2Uhm0ndMyEnNZTkOhX0kOVWtb8SgxE,1002
407
- pulpcore/plugin/tasking.py,sha256=JCndCrbI-t9ZR458NB4eWftZZ8e2x9JlT67ze2H6rS0,675
407
+ pulpcore/plugin/tasking.py,sha256=Ij9Zqamzn566Qf-8uvcm2q1AD4nZ6Mqi7FZnJow8Q2Y,677
408
408
  pulpcore/plugin/util.py,sha256=Az0DDhdC6fEIPApC5ffdvM4oMwbqteISJ-b1yEcFRBE,1470
409
409
  pulpcore/plugin/authentication/__init__.py,sha256=0qx9U7Poy96Ql3zu7DZwmzsA8Cx_yd1zHeeGKLeLXHg,69
410
410
  pulpcore/plugin/cache/__init__.py,sha256=0P1OqLmYxJdAaxhND6flNZzYa-korgIrzS1eOiTT_kc,74
@@ -422,11 +422,11 @@ pulpcore/plugin/stages/models.py,sha256=0b7xs9d64WTG2yHog1Zo_Z_-pomFAe-gC4u9wksY
422
422
  pulpcore/plugin/viewsets/__init__.py,sha256=G2zE-NRWz6PFYp8OMZrv01RYBQELFWfW702bRvxXs3k,2281
423
423
  pulpcore/plugin/viewsets/content.py,sha256=MHvmLOxsKSQfk6y5t1s9CVxkce9YNeU-dYb1Ldyf83I,6432
424
424
  pulpcore/tasking/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
425
- pulpcore/tasking/_util.py,sha256=AuS6HZ0ADCOpW75NDHuB4B-U3-Sw85QzxqYKequYSwE,8319
425
+ pulpcore/tasking/_util.py,sha256=giR8f8fNvsjsTiuJOU9X21Dyb14fFntSYU7xXGwQZzo,9705
426
426
  pulpcore/tasking/entrypoint.py,sha256=Npnn41e39soGvJ7CTaZXT5MjIhOO7UtQmpmNaZtfKYg,1120
427
427
  pulpcore/tasking/kafka.py,sha256=76z4DzeXM1WL5uu1HlKnduWeLO3-b-czvGBXdWR6054,3845
428
428
  pulpcore/tasking/storage.py,sha256=zQkwlpC_FDQtmZGZ8vKwHqxvD6CLO_gAS4Q7wijZE-k,3106
429
- pulpcore/tasking/tasks.py,sha256=VpLaaDPUIHSSXi2KM0RXlY5u5LE98mNss7EMsD3t03A,12355
429
+ pulpcore/tasking/tasks.py,sha256=b0YzBoxXiBXC8piZlzV63ehRHsw8V3uq6t4v8Ec8OIg,13935
430
430
  pulpcore/tasking/worker.py,sha256=NpBACe6z_xFYOEeJYRne0hlwQDVrOAYGwpNCzSL-3C4,23819
431
431
  pulpcore/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
432
432
  pulpcore/tests/functional/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -453,7 +453,7 @@ pulpcore/tests/functional/api/test_scoping.py,sha256=uiLOsx5_7puRMcvrpPKEYQziqlu
453
453
  pulpcore/tests/functional/api/test_signing_service.py,sha256=yr1HXBrNoliBHJNAGAN4PAN0eBKPIvAQP-uMoMSrO_I,222
454
454
  pulpcore/tests/functional/api/test_status.py,sha256=NYOoYIiIifv0jO74EvPZlqws7Hob6bE8Pk2ro9tvlyU,5456
455
455
  pulpcore/tests/functional/api/test_task_purge.py,sha256=Av4DrUdCqf-JegfoP1pkY4B-teoUzYd1LBZKAhDa-08,7273
456
- pulpcore/tests/functional/api/test_tasking.py,sha256=JdgBCHrDrD_4swZGtzAp2vsqpBbuZnz38uPRqC7QaUc,15946
456
+ pulpcore/tests/functional/api/test_tasking.py,sha256=LPJtsRK9Ggh7CUsVuATcLYUnhdclbbS2zPUwAJGo2fg,21566
457
457
  pulpcore/tests/functional/api/test_upload.py,sha256=oLP1ZmQgPzgK5jAQwGeXS8uLFHgAzVeLW0GfANMWexI,6794
458
458
  pulpcore/tests/functional/api/test_users_groups.py,sha256=YFG0xtyJuIRraczR7ERl_UNS7dlJfKd2eUmXgD1lLBU,2926
459
459
  pulpcore/tests/functional/api/test_workers.py,sha256=u3oQnErjf6qPCg08XMRZzecGetLLDWmvHvoZIk-AUAA,4659
@@ -526,9 +526,9 @@ pulpcore/tests/unit/stages/test_artifactdownloader.py,sha256=qB1ANdFmNtUnljg8fCd
526
526
  pulpcore/tests/unit/stages/test_stages.py,sha256=H1a2BQLjdZlZvcb_qULp62huZ1xy6ItTcthktVyGU0w,4735
527
527
  pulpcore/tests/unit/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
528
528
  pulpcore/tests/unit/viewsets/test_viewset_base.py,sha256=W9o3V6758bZctR6krMPPQytb0xJuF-jb4uBWTNDoD_U,4837
529
- pulpcore-3.76.1.dist-info/licenses/LICENSE,sha256=dhnHU8rJXUdAIgIjveSKAyYG_KzN5eVG-bxETIGrNW0,17988
530
- pulpcore-3.76.1.dist-info/METADATA,sha256=kQOn6PhS5rHJr0MiGG3cX_1TXbOG7SKL9VOO34-cldE,4123
531
- pulpcore-3.76.1.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
532
- pulpcore-3.76.1.dist-info/entry_points.txt,sha256=OZven4wzXzQA5b5q9MpP4HUpIPPQCSvIOvkKtNInrK0,452
533
- pulpcore-3.76.1.dist-info/top_level.txt,sha256=6h-Lm3FKQSaT_nL1KSxu_hBnzKE15bcvf_BoU-ea4CI,34
534
- pulpcore-3.76.1.dist-info/RECORD,,
529
+ pulpcore-3.77.0.dist-info/licenses/LICENSE,sha256=dhnHU8rJXUdAIgIjveSKAyYG_KzN5eVG-bxETIGrNW0,17988
530
+ pulpcore-3.77.0.dist-info/METADATA,sha256=KwDSPdPh-YRNIQvRh9sEpF7cyW7j3UiWEKTJaq5Fo_k,4260
531
+ pulpcore-3.77.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
532
+ pulpcore-3.77.0.dist-info/entry_points.txt,sha256=OZven4wzXzQA5b5q9MpP4HUpIPPQCSvIOvkKtNInrK0,452
533
+ pulpcore-3.77.0.dist-info/top_level.txt,sha256=6h-Lm3FKQSaT_nL1KSxu_hBnzKE15bcvf_BoU-ea4CI,34
534
+ pulpcore-3.77.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.1)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5