diff --git a/readthedocs/projects/constants.py b/readthedocs/projects/constants.py index d203e5a8711..87cf6d59ef8 100644 --- a/readthedocs/projects/constants.py +++ b/readthedocs/projects/constants.py @@ -423,3 +423,26 @@ _("Single version without translations (/)"), ), ) + + +ADDONS_FLYOUT_SORTING_ALPHABETICALLY = "alphabetically" +# Compatibility to keep the behavior of the old flyout. +# This isn't a good algorithm, but it's a way to keep the old behavior in case we need it. +ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE = "semver-readthedocs-compatible" +# https://pypi.org/project/packaging/ +ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING = "python-packaging" +ADDONS_FLYOUT_SORTING_CALVER = "calver" +# Let the user to define a custom pattern and use BumpVer to parse and sort the versions. +# https://github.com/mbarkhau/bumpver#pattern-examples +ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN = "custom-pattern" + +ADDONS_FLYOUT_SORTING_CHOICES = ( + (ADDONS_FLYOUT_SORTING_ALPHABETICALLY, _("Alphabetically")), + (ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE, _("SemVer (Read the Docs)")), + ( + ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING, + _("Python Packaging (PEP 440 and PEP 425)"), + ), + (ADDONS_FLYOUT_SORTING_CALVER, _("CalVer (YYYY.0M.0M)")), + (ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, _("Define your own pattern")), +) diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py index 574128a434f..bc616b3cbbe 100644 --- a/readthedocs/projects/forms.py +++ b/readthedocs/projects/forms.py @@ -22,6 +22,7 @@ from readthedocs.invitations.models import Invitation from readthedocs.oauth.models import RemoteRepository from readthedocs.organizations.models import Team +from readthedocs.projects.constants import ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN from readthedocs.projects.models import ( AddonsConfig, Domain, @@ -588,6 +589,9 @@ class Meta: "doc_diff_enabled", "external_version_warning_enabled", "flyout_enabled", + "flyout_sorting", + "flyout_sorting_latest_stable_at_beginning", + "flyout_sorting_custom_pattern", "hotkeys_enabled", "search_enabled", "stable_latest_version_warning_enabled", @@ -612,6 +616,18 @@ def __init__(self, *args, **kwargs): kwargs["instance"] = addons super().__init__(*args, **kwargs) + def clean(self): + if ( + self.cleaned_data["flyout_sorting"] == ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN + and not self.cleaned_data["flyout_sorting_custom_pattern"] + ): + raise forms.ValidationError( + _( + "The flyout sorting custom pattern is required when selecting a custom pattern." + ), + ) + return super().clean() + def clean_project(self): return self.project diff --git a/readthedocs/projects/migrations/0118_addons_flyout_sorting.py b/readthedocs/projects/migrations/0118_addons_flyout_sorting.py new file mode 100644 index 00000000000..408bc82074a --- /dev/null +++ b/readthedocs/projects/migrations/0118_addons_flyout_sorting.py @@ -0,0 +1,83 @@ +# Generated by Django 4.2.10 on 2024-03-04 13:32 + +from django.db import migrations, models +from django_safemigrate import Safe + + +class Migration(migrations.Migration): + safe = Safe.before_deploy + + dependencies = [ + ("projects", "0117_remove_old_fields"), + ] + + operations = [ + migrations.AddField( + model_name="addonsconfig", + name="flyout_sorting", + field=models.CharField( + choices=[ + ("alphabetically", "Alphabetically"), + ("semver-readthedocs-compatible", "SemVer (Read the Docs)"), + ("python-packaging", "Python Packaging (PEP 440 and PEP 425)"), + ("calver", "CalVer (YYYY.0M.0M)"), + ("custom-pattern", "Define your own pattern"), + ], + default="alphabetically", + max_length=64, + ), + ), + migrations.AddField( + model_name="addonsconfig", + name="flyout_sorting_custom_pattern", + field=models.CharField( + blank=True, + default=None, + help_text='Sorting pattern supported by BumpVer (See examples', + max_length=32, + null=True, + ), + ), + migrations.AddField( + model_name="addonsconfig", + name="flyout_sorting_latest_stable_at_beginning", + field=models.BooleanField( + default=True, + help_text="Show latest and stable at the beginning", + ), + ), + migrations.AddField( + model_name="historicaladdonsconfig", + name="flyout_sorting", + field=models.CharField( + choices=[ + ("alphabetically", "Alphabetically"), + ("semver-readthedocs-compatible", "SemVer (Read the Docs)"), + ("python-packaging", "Python Packaging (PEP 440 and PEP 425)"), + ("calver", "CalVer (YYYY.0M.0M)"), + ("custom-pattern", "Define your own pattern"), + ], + default="alphabetically", + max_length=64, + ), + ), + migrations.AddField( + model_name="historicaladdonsconfig", + name="flyout_sorting_custom_pattern", + field=models.CharField( + blank=True, + default=None, + help_text='Sorting pattern supported by BumpVer (See examples', + max_length=32, + null=True, + ), + ), + migrations.AddField( + model_name="historicaladdonsconfig", + name="flyout_sorting_latest_stable_at_beginning", + field=models.BooleanField( + default=True, + help_text="Show latest and stable at the beginning", + ), + ), + ] diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py index 008fd256ad4..a448cf36475 100644 --- a/readthedocs/projects/models.py +++ b/readthedocs/projects/models.py @@ -61,6 +61,8 @@ from readthedocs.vcs_support.backends import backend_cls from .constants import ( + ADDONS_FLYOUT_SORTING_ALPHABETICALLY, + ADDONS_FLYOUT_SORTING_CHOICES, DOWNLOADABLE_MEDIA_TYPES, MEDIA_TYPES, MULTIPLE_VERSIONS_WITH_TRANSLATIONS, @@ -181,6 +183,23 @@ class AddonsConfig(TimeStampedModel): # Flyout flyout_enabled = models.BooleanField(default=True) + flyout_sorting = models.CharField( + choices=ADDONS_FLYOUT_SORTING_CHOICES, + default=ADDONS_FLYOUT_SORTING_ALPHABETICALLY, + max_length=64, + ) + flyout_sorting_custom_pattern = models.CharField( + max_length=32, + default=None, + null=True, + blank=True, + help_text="Sorting pattern supported by BumpVer " + '(See examples)', + ) + flyout_sorting_latest_stable_at_beginning = models.BooleanField( + default=True, + help_text="Show latest and stable at the beginning", + ) # Hotkeys hotkeys_enabled = models.BooleanField(default=True) diff --git a/readthedocs/projects/tests/test_version_handling.py b/readthedocs/projects/tests/test_version_handling.py new file mode 100644 index 00000000000..a83d24e47b2 --- /dev/null +++ b/readthedocs/projects/tests/test_version_handling.py @@ -0,0 +1,207 @@ +import django_dynamic_fixture as fixture +import pytest + +from readthedocs.builds.models import Build, Version +from readthedocs.projects.models import AddonsConfig, Project +from readthedocs.projects.version_handling import ( + sort_versions_calver, + sort_versions_custom_pattern, + sort_versions_python_packaging, +) + + +@pytest.mark.django_db(databases="__all__") +class TestVersionHandling: + @pytest.fixture(autouse=True) + def setup(self, requests_mock): + # Save the reference to query it from inside the test + self.requests_mock = requests_mock + + self.project = fixture.get(Project, slug="project") + self.addons = fixture.get(AddonsConfig, project=self.project) + self.version = self.project.versions.get(slug="latest") + self.build = fixture.get( + Build, + version=self.version, + commit="a1b2c3", + ) + + def test_sort_versions_python_packaging(self): + slugs = [ + "v1.0", + "1.1", + "invalid", + "2.5.3", + "1.1.0", + "another-invalid", + ] + + expected = [ + # `latest` and `stable` are at the beginning + "latest", + "2.5.3", + "1.1", + "1.1.0", + "v1.0", + # Invalid versions are at the end sorted alphabetically. + "another-invalid", + "invalid", + ] + + for slug in slugs: + fixture.get( + Version, + slug=slug, + project=self.project, + ) + + sorted_versions = sort_versions_python_packaging( + self.project.versions.all(), + latest_stable_at_beginning=True, + ) + assert expected == [version.slug for version in sorted_versions] + + def test_sort_versions_python_packaging_latest_stable_not_at_beginning(self): + slugs = [ + "v1.0", + "1.1", + "invalid", + "2.5.3", + "1.1.0", + "another-invalid", + ] + + expected = [ + "2.5.3", + "1.1", + "1.1.0", + "v1.0", + # Invalid versions are at the end sorted alphabetically. + "another-invalid", + "invalid", + "latest", + ] + + for slug in slugs: + fixture.get( + Version, + slug=slug, + project=self.project, + ) + + sorted_versions = sort_versions_python_packaging( + self.project.versions.all(), + latest_stable_at_beginning=False, + ) + assert expected == [version.slug for version in sorted_versions] + + def test_sort_versions_calver(self): + slugs = [ + "2022.01.22", + "2023.04.22", + "2021.01.22", + "2022.05.02", + # invalid ones + "2001.16.32", + "2001.02.2", + "2001-02-27", + "1.1", + "invalid", + "2.5.3", + "1.1.0", + "another-invalid", + ] + + expected = [ + # `latest` and `stable` are at the beginning + "latest", + "stable", + "2023.04.22", + "2022.05.02", + "2022.01.22", + "2021.01.22", + # invalid ones (alphabetically) + "1.1", + "1.1.0", + "2.5.3", + "2001-02-27", + "2001.02.2", + "2001.16.32", + "another-invalid", + "invalid", + ] + + for slug in slugs: + fixture.get( + Version, + slug=slug, + project=self.project, + ) + + fixture.get( + Version, + slug="stable", + machine=True, + project=self.project, + ) + + sorted_versions = sort_versions_calver( + self.project.versions.all(), + latest_stable_at_beginning=True, + ) + + assert expected == [version.slug for version in sorted_versions] + + def test_sort_versions_custom_pattern(self): + slugs = [ + "v1.0", + "v1.1", + "v2.3", + # invalid ones + "v1.1.0", + "v2.3rc1", + "invalid", + "2.5.3", + "2022.01.22", + "1.1", + "another-invalid", + ] + + expected = [ + # `latest` and `stable` are at the beginning + "latest", + "stable", + "v2.3", + "v1.1", + "v1.0", + # invalid ones (alphabetically) + "1.1", + "2.5.3", + "2022.01.22", + "another-invalid", + "invalid", + "v1.1.0", + "v2.3rc1", + ] + + for slug in slugs: + fixture.get( + Version, + slug=slug, + project=self.project, + ) + + fixture.get( + Version, + slug="stable", + machine=True, + project=self.project, + ) + + sorted_versions = sort_versions_custom_pattern( + self.project.versions.all(), + raw_pattern="vMAJOR.MINOR", + latest_stable_at_beginning=True, + ) + + assert expected == [version.slug for version in sorted_versions] diff --git a/readthedocs/projects/version_handling.py b/readthedocs/projects/version_handling.py index 6efbd38effe..2059e2a818a 100644 --- a/readthedocs/projects/version_handling.py +++ b/readthedocs/projects/version_handling.py @@ -1,9 +1,18 @@ """Project version handling.""" +import operator import unicodedata +from bumpver.v2version import parse_version_info +from bumpver.version import PatternError from packaging.version import InvalidVersion, Version -from readthedocs.builds.constants import LATEST_VERBOSE_NAME, STABLE_VERBOSE_NAME, TAG +from readthedocs.builds.constants import ( + LATEST, + LATEST_VERBOSE_NAME, + STABLE, + STABLE_VERBOSE_NAME, + TAG, +) from readthedocs.vcs_support.backends import backend_cls @@ -158,3 +167,106 @@ def determine_stable_version(version_list): version_obj, comparable = versions[0] return version_obj return None + + +def sort_versions_python_packaging(version_list, latest_stable_at_beginning): + """ + Sort Read the Docs versions list using ``packaging`` algorithm. + + All the invalid version (raise ``InvalidVersion``) are added at the end + sorted alphabetically. + + https://pypi.org/project/packaging/ + https://packaging.python.org/en/latest/specifications/version-specifiers/ + """ + alphabetically_sorted_version_list = sorted( + version_list, + key=operator.attrgetter("slug"), + ) + + initial_versions = [] + + valid_versions = [] + invalid_versions = [] + for i, version in enumerate(alphabetically_sorted_version_list): + if latest_stable_at_beginning: + if version.slug in (STABLE, LATEST): + # It relies on the version list sorted alphabetically first ("l" comes first than "s") + initial_versions.append((version, version.slug)) + continue + + try: + valid_versions.append((version, Version(version.slug))) + except InvalidVersion: + # When the version is invalid, we put it at the end while keeping + # the alphabetically sorting between the invalid ones. + invalid_versions.append((version, None)) + + all_versions = ( + initial_versions + + sorted(valid_versions, key=operator.itemgetter(1), reverse=True) + + invalid_versions + ) + + return [item[0] for item in all_versions if item[0] is not None] + + +def sort_versions_calver(version_list, latest_stable_at_beginning): + """ + Sort Read the Docs versions using CalVer pattern: ``YYYY.0M.0M``. + + All the invalid version are added at the end sorted alphabetically. + """ + raw_pattern = "YYYY.0M.0D" + return sort_versions_custom_pattern( + version_list, raw_pattern, latest_stable_at_beginning + ) + + +def sort_versions_custom_pattern(version_list, raw_pattern, latest_stable_at_beginning): + """ + Sort Read the Docs versions using a custom pattern. + + All the invalid version (raise ``PatternError``) are added at the end + sorted alphabetically. + + It uses ``Bumpver`` behinds the scenes for the parsing and sorting. + https://github.com/mbarkhau/bumpver + """ + alphabetically_sorted_version_list = sorted( + version_list, + key=operator.attrgetter("slug"), + ) + + initial_versions = [] + valid_versions = [] + invalid_versions = [] + for i, version in enumerate(alphabetically_sorted_version_list): + if latest_stable_at_beginning: + if version.slug in (STABLE, LATEST): + # It relies on the version list sorted alphabetically first ("l" comes first than "s") + initial_versions.append((version, version.slug)) + continue + + try: + valid_versions.append( + ( + version, + parse_version_info( + version.slug, + raw_pattern=raw_pattern, + ), + ) + ) + except PatternError: + # When the version is invalid, we put it at the end while keeping + # the alphabetically sorting between the invalid ones. + invalid_versions.append((version, None)) + + all_versions = ( + initial_versions + + sorted(valid_versions, key=operator.itemgetter(1), reverse=True) + + invalid_versions + ) + + return [item[0] for item in all_versions if item[0] is not None] diff --git a/readthedocs/proxito/tests/test_hosting.py b/readthedocs/proxito/tests/test_hosting.py index 59824761486..1ebc43b2371 100644 --- a/readthedocs/proxito/tests/test_hosting.py +++ b/readthedocs/proxito/tests/test_hosting.py @@ -701,7 +701,7 @@ def test_number_of_queries_project_version_slug(self): active=True, ) - with self.assertNumQueries(21): + with self.assertNumQueries(20): r = self.client.get( reverse("proxito_readthedocs_docs_addons"), { @@ -730,7 +730,7 @@ def test_number_of_queries_url(self): active=True, ) - with self.assertNumQueries(21): + with self.assertNumQueries(20): r = self.client.get( reverse("proxito_readthedocs_docs_addons"), { @@ -766,7 +766,7 @@ def test_number_of_queries_url_subproject(self): active=True, ) - with self.assertNumQueries(25): + with self.assertNumQueries(24): r = self.client.get( reverse("proxito_readthedocs_docs_addons"), { @@ -792,7 +792,7 @@ def test_number_of_queries_url_translations(self): language=language, ) - with self.assertNumQueries(25): + with self.assertNumQueries(24): r = self.client.get( reverse("proxito_readthedocs_docs_addons"), { diff --git a/readthedocs/proxito/views/hosting.py b/readthedocs/proxito/views/hosting.py index 8500715f505..1bdad2fc9a8 100644 --- a/readthedocs/proxito/views/hosting.py +++ b/readthedocs/proxito/views/hosting.py @@ -23,7 +23,19 @@ from readthedocs.core.resolver import Resolver from readthedocs.core.unresolver import UnresolverError, unresolver from readthedocs.core.utils.extend import SettingsOverrideObject +from readthedocs.projects.constants import ( + ADDONS_FLYOUT_SORTING_CALVER, + ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, + ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING, + ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE, +) from readthedocs.projects.models import AddonsConfig, Project +from readthedocs.projects.version_handling import ( + comparable_version, + sort_versions_calver, + sort_versions_custom_pattern, + sort_versions_python_packaging, +) log = structlog.get_logger(__name__) # noqa @@ -256,6 +268,10 @@ def _v0(self, project, version, build, filename, url, user): version_downloads = [] versions_active_built_not_hidden = Version.objects.none() + # Automatically create an AddonsConfig with the default values for + # projects that don't have one already + AddonsConfig.objects.get_or_create(project=project) + if project.supports_multiple_versions: versions_active_built_not_hidden = ( Version.internal.public( @@ -269,6 +285,36 @@ def _v0(self, project, version, build, filename, url, user): .order_by("slug") ) + if ( + project.addons.flyout_sorting + == ADDONS_FLYOUT_SORTING_SEMVER_READTHEDOCS_COMPATIBLE + ): + versions_active_built_not_hidden = sorted( + versions_active_built_not_hidden, + key=lambda version: comparable_version( + version.verbose_name, + repo_type=project.repo_type, + ), + ) + elif ( + project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_PYTHON_PACKAGING + ): + versions_active_built_not_hidden = sort_versions_python_packaging( + versions_active_built_not_hidden, + project.addons.flyout_sorting_latest_stable_at_beginning, + ) + elif project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_CALVER: + versions_active_built_not_hidden = sort_versions_calver( + versions_active_built_not_hidden, + project.addons.flyout_sorting_latest_stable_at_beginning, + ) + elif project.addons.flyout_sorting == ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN: + versions_active_built_not_hidden = sort_versions_custom_pattern( + versions_active_built_not_hidden, + project.addons.flyout_sorting_custom_pattern, + project.addons.flyout_sorting_latest_stable_at_beginning, + ) + if version: version_downloads = version.get_downloads(pretty=True).items() @@ -283,10 +329,6 @@ def _v0(self, project, version, build, filename, url, user): # en (original), es, ru project_translations = itertools.chain([main_project], project_translations) - # Automatically create an AddonsConfig with the default values for - # projects that don't have one already - AddonsConfig.objects.get_or_create(project=project) - data = { "api_version": "0", "comment": ( @@ -335,9 +377,9 @@ def _v0(self, project, version, build, filename, url, user): # NOTE: I think we are moving away from these selectors # since we are doing floating noticications now. # "query_selector": "[role=main]", - "versions": list( - versions_active_built_not_hidden.values_list("slug", flat=True) - ), + "versions": [ + version_.slug for version_ in versions_active_built_not_hidden + ], }, "flyout": { "enabled": project.addons.flyout_enabled, diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py index c3b28a14985..a59f01d9b78 100644 --- a/readthedocs/rtd_tests/tests/test_project_forms.py +++ b/readthedocs/rtd_tests/tests/test_project_forms.py @@ -12,6 +12,8 @@ from readthedocs.core.forms import RichValidationError from readthedocs.organizations.models import Organization, Team from readthedocs.projects.constants import ( + ADDONS_FLYOUT_SORTING_CALVER, + ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, MULTIPLE_VERSIONS_WITH_TRANSLATIONS, MULTIPLE_VERSIONS_WITHOUT_TRANSLATIONS, PRIVATE, @@ -22,6 +24,7 @@ SPHINX, ) from readthedocs.projects.forms import ( + AddonsConfigForm, EmailHookForm, EnvironmentVariableForm, ProjectAutomaticForm, @@ -1084,3 +1087,68 @@ def test_create(self): EnvironmentVariable.objects.latest().value, r"'string escaped here: #$\1[]{}\|'", ) + + +class TestAddonsConfigForm(TestCase): + def setUp(self): + self.project = get(Project) + + def test_addonsconfig_form(self): + data = { + "enabled": True, + "analytics_enabled": False, + "doc_diff_enabled": False, + "external_version_warning_enabled": True, + "flyout_enabled": True, + "flyout_sorting": ADDONS_FLYOUT_SORTING_CALVER, + "flyout_sorting_latest_stable_at_beginning": True, + "flyout_sorting_custom_pattern": None, + "hotkeys_enabled": False, + "search_enabled": False, + "stable_latest_version_warning_enabled": True, + } + form = AddonsConfigForm(data=data, project=self.project) + self.assertTrue(form.is_valid()) + form.save() + + self.assertEqual(self.project.addons.enabled, True) + self.assertEqual(self.project.addons.analytics_enabled, False) + self.assertEqual(self.project.addons.doc_diff_enabled, False) + self.assertEqual(self.project.addons.external_version_warning_enabled, True) + self.assertEqual(self.project.addons.flyout_enabled, True) + self.assertEqual( + self.project.addons.flyout_sorting, + ADDONS_FLYOUT_SORTING_CALVER, + ) + self.assertEqual( + self.project.addons.flyout_sorting_latest_stable_at_beginning, + True, + ) + self.assertEqual(self.project.addons.flyout_sorting_custom_pattern, None) + self.assertEqual(self.project.addons.hotkeys_enabled, False) + self.assertEqual(self.project.addons.search_enabled, False) + self.assertEqual( + self.project.addons.stable_latest_version_warning_enabled, + True, + ) + + def test_addonsconfig_form_invalid_sorting_custom_pattern(self): + data = { + "enabled": True, + "analytics_enabled": False, + "doc_diff_enabled": False, + "external_version_warning_enabled": True, + "flyout_enabled": True, + "flyout_sorting": ADDONS_FLYOUT_SORTING_CUSTOM_PATTERN, + "flyout_sorting_latest_stable_at_beginning": True, + "flyout_sorting_custom_pattern": None, + "hotkeys_enabled": False, + "search_enabled": False, + "stable_latest_version_warning_enabled": True, + } + form = AddonsConfigForm(data=data, project=self.project) + self.assertFalse(form.is_valid()) + self.assertEqual( + "The flyout sorting custom pattern is required when selecting a custom pattern.", + form.errors["__all__"][0], + ) diff --git a/requirements/deploy.txt b/requirements/deploy.txt index 235ade9fb9c..46d917d6912 100644 --- a/requirements/deploy.txt +++ b/requirements/deploy.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/deploy.txt requirements/deploy.in +# pip-compile --output-file=requirements/deploy.txt --resolver=backtracking requirements/deploy.in # amqp==5.2.0 # via @@ -32,6 +32,8 @@ botocore==1.34.59 # -r requirements/pip.txt # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.txt celery==5.2.7 # via # -r requirements/pip.txt @@ -53,6 +55,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/pip.txt + # bumpver # celery # click-didyoumean # click-plugins @@ -69,6 +72,10 @@ click-repl==0.3.0 # via # -r requirements/pip.txt # celery +colorama==0.4.6 + # via + # -r requirements/pip.txt + # bumpver cron-descriptor==1.4.3 # via # -r requirements/pip.txt @@ -233,6 +240,14 @@ kombu==5.3.5 # via # -r requirements/pip.txt # celery +lexid==2021.1006 + # via + # -r requirements/pip.txt + # bumpver +looseversion==1.3.0 + # via + # -r requirements/pip.txt + # bumpver lxml==5.1.0 # via # -r requirements/pip.txt @@ -369,6 +384,10 @@ structlog==23.2.0 # structlog-sentry structlog-sentry==2.0.3 # via -r requirements/deploy.in +toml==0.10.2 + # via + # -r requirements/pip.txt + # bumpver tomli==2.0.1 # via # -r requirements/pip.txt diff --git a/requirements/docker.txt b/requirements/docker.txt index 63e0a7f431c..90c19db9b35 100644 --- a/requirements/docker.txt +++ b/requirements/docker.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/docker.txt requirements/docker.in +# pip-compile --output-file=requirements/docker.txt --resolver=backtracking requirements/docker.in # amqp==5.2.0 # via @@ -34,6 +34,8 @@ botocore==1.34.59 # -r requirements/pip.txt # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.txt cachetools==5.3.3 # via tox celery==5.2.7 @@ -58,6 +60,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/pip.txt + # bumpver # celery # click-didyoumean # click-plugins @@ -75,7 +78,10 @@ click-repl==0.3.0 # -r requirements/pip.txt # celery colorama==0.4.6 - # via tox + # via + # -r requirements/pip.txt + # bumpver + # tox cron-descriptor==1.4.3 # via # -r requirements/pip.txt @@ -249,6 +255,14 @@ kombu==5.3.5 # via # -r requirements/pip.txt # celery +lexid==2021.1006 + # via + # -r requirements/pip.txt + # bumpver +looseversion==1.3.0 + # via + # -r requirements/pip.txt + # bumpver lxml==5.1.0 # via # -r requirements/pip.txt @@ -399,6 +413,10 @@ structlog==23.2.0 # via # -r requirements/pip.txt # django-structlog +toml==0.10.2 + # via + # -r requirements/pip.txt + # bumpver tomli==2.0.1 # via # -r requirements/pip.txt diff --git a/requirements/docs.txt b/requirements/docs.txt index d1825bb285c..5cfd5404129 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/docs.txt requirements/docs.in +# pip-compile --output-file=requirements/docs.txt --resolver=backtracking requirements/docs.in # alabaster==0.7.16 # via sphinx diff --git a/requirements/pip.in b/requirements/pip.in index ce480229257..c941273f692 100644 --- a/requirements/pip.in +++ b/requirements/pip.in @@ -177,3 +177,6 @@ dparse gunicorn django-cacheops + +# Used by Addons for sorting patterns +bumpver diff --git a/requirements/pip.txt b/requirements/pip.txt index 2b2091ac265..1a894ef887f 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/pip.txt requirements/pip.in +# pip-compile --output-file=requirements/pip.txt --resolver=backtracking requirements/pip.in # amqp==5.2.0 # via kombu @@ -20,6 +20,8 @@ botocore==1.34.59 # via # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.in celery==5.2.7 # via # -r requirements/pip.in @@ -34,6 +36,7 @@ charset-normalizer==3.3.2 # via requests click==8.1.7 # via + # bumpver # celery # click-didyoumean # click-plugins @@ -44,6 +47,8 @@ click-plugins==1.1.1 # via celery click-repl==0.3.0 # via celery +colorama==0.4.6 + # via bumpver cron-descriptor==1.4.3 # via django-celery-beat cryptography==42.0.5 @@ -175,6 +180,10 @@ jsonfield==3.1.0 # dj-stripe kombu==5.3.5 # via celery +lexid==2021.1006 + # via bumpver +looseversion==1.3.0 + # via bumpver lxml==5.1.0 # via pyquery markdown==3.5.2 @@ -268,6 +277,8 @@ structlog==23.2.0 # via # -r requirements/pip.in # django-structlog +toml==0.10.2 + # via bumpver tomli==2.0.1 # via dparse typing-extensions==4.10.0 diff --git a/requirements/testing.txt b/requirements/testing.txt index a3f049f1b67..46887ca663f 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --output-file=requirements/testing.txt requirements/testing.in +# pip-compile --output-file=requirements/testing.txt --resolver=backtracking requirements/testing.in # alabaster==0.7.16 # via sphinx @@ -34,6 +34,8 @@ botocore==1.34.59 # -r requirements/pip.txt # boto3 # s3transfer +bumpver==2023.1129 + # via -r requirements/pip.txt celery==5.2.7 # via # -r requirements/pip.txt @@ -54,6 +56,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via # -r requirements/pip.txt + # bumpver # celery # click-didyoumean # click-plugins @@ -70,6 +73,10 @@ click-repl==0.3.0 # via # -r requirements/pip.txt # celery +colorama==0.4.6 + # via + # -r requirements/pip.txt + # bumpver coverage[toml]==7.4.3 # via pytest-cov cron-descriptor==1.4.3 @@ -238,6 +245,14 @@ kombu==5.3.5 # via # -r requirements/pip.txt # celery +lexid==2021.1006 + # via + # -r requirements/pip.txt + # bumpver +looseversion==1.3.0 + # via + # -r requirements/pip.txt + # bumpver lxml==5.1.0 # via # -r requirements/pip.txt @@ -399,6 +414,10 @@ structlog==23.2.0 # via # -r requirements/pip.txt # django-structlog +toml==0.10.2 + # via + # -r requirements/pip.txt + # bumpver tomli==2.0.1 # via # -r requirements/pip.txt