diff --git a/aleksis/core/__init__.py b/aleksis/core/__init__.py index 21a49f0785f3977d9a265059a32ec957c9e1eb3c..0cce3caca0b209dd4cd9bc26de1ae50ad9482a2d 100644 --- a/aleksis/core/__init__.py +++ b/aleksis/core/__init__.py @@ -7,7 +7,7 @@ except ModuleNotFoundError: celery_app = None try: - __version__ = pkg_resources.get_distribution("AlekSIS").version + __version__ = pkg_resources.get_distribution("AlekSIS-Core").version except Exception: __version__ = "unknown" diff --git a/aleksis/core/util/manage.py b/aleksis/core/__main__.py similarity index 73% rename from aleksis/core/util/manage.py rename to aleksis/core/__main__.py index 64441f62dfdadbb589b973dcd65633e100bf647b..ef9e0064f91121aaf2e6238945f11115198178ab 100644 --- a/aleksis/core/util/manage.py +++ b/aleksis/core/__main__.py @@ -3,10 +3,16 @@ import os import sys +import django.__main__ from django.core.management import execute_from_command_line def aleksis_cmd(): """Run django-admin command with correct settings path.""" os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aleksis.core.settings") + sys.argv[0] = django.__main__.__file__ execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + sys.exit(aleksis_cmd()) diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py index 3d3f86933acb3b86e87c644bfc92db37832c27cb..42edc4bceaa61a55de310f59cea74dc270a3cf0b 100644 --- a/aleksis/core/apps.py +++ b/aleksis/core/apps.py @@ -21,6 +21,7 @@ from .util.sass_helpers import clean_scss class CoreConfig(AppConfig): name = "aleksis.core" verbose_name = "AlekSIS — The Free School Information System" + dist_name = "AlekSIS-Core" urls = { "Repository": "https://edugit.org/AlekSIS/official/AlekSIS/", @@ -52,9 +53,17 @@ class CoreConfig(AppConfig): self._load_data_checks() - from .health_checks import DataChecksHealthCheckBackend + from .health_checks import ( + BackupJobHealthCheck, + DataChecksHealthCheckBackend, + DbBackupAgeHealthCheck, + MediaBackupAgeHealthCheck, + ) plugin_dir.register(DataChecksHealthCheckBackend) + plugin_dir.register(DbBackupAgeHealthCheck) + plugin_dir.register(MediaBackupAgeHealthCheck) + plugin_dir.register(BackupJobHealthCheck) @classmethod def _load_data_checks(cls): diff --git a/aleksis/core/checks.py b/aleksis/core/checks.py index db3199b6081d77e9bd8e2c559db07f7924484599..fc1c54e88e60a76728f2b347339737b761cc081b 100644 --- a/aleksis/core/checks.py +++ b/aleksis/core/checks.py @@ -3,7 +3,7 @@ from typing import Optional import django.apps from django.core.checks import Tags, Warning, register -from .mixins import ExtensibleModel, PureDjangoModel +from .mixins import ExtensibleModel, GlobalPermissionModel, PureDjangoModel from .util.apps import AppConfig @@ -39,7 +39,10 @@ def check_app_configs_base_class( def check_app_models_base_class( app_configs: Optional[django.apps.registry.Apps] = None, **kwargs ) -> list: - """Check whether all app models derive from AlekSIS's base ExtensibleModel.""" + """Check whether all app models derive from AlekSIS's allowed base models. + + Does only allow ExtensibleModel, GlobalPermissionModel and PureDjangoModel. + """ results = [] if app_configs is None: @@ -47,14 +50,19 @@ def check_app_models_base_class( for app_config in filter(lambda c: c.name.startswith("aleksis."), app_configs): for model in app_config.get_models(): - if ExtensibleModel not in model.__mro__ and PureDjangoModel not in model.__mro__: + if not ( + set(model.__mro__) & set((ExtensibleModel, PureDjangoModel, GlobalPermissionModel)) + ): results.append( Warning( - f"Model {model._meta.object_name} in app config {app_config.name} does" - "not derive from aleksis.core.mixins.ExtensibleModel.", + f"Model {model._meta.object_name} in app config {app_config.name} does " + "not derive from aleksis.core.mixins.ExtensibleModel " + "or aleksis.core.mixins.GlobalPermissionModel.", hint=( - "Ensure all models in AlekSIS use ExtensibleModel as base." - "If your deviation is intentional, you can add the PureDjangoModel" + "Ensure all models in AlekSIS use ExtensibleModel (or " + "GlobalPermissionModel, if you want to define global permissions) " + "as base. " + "If your deviation is intentional, you can add the PureDjangoModel " "mixin instead to silence this warning." ), obj=model, diff --git a/aleksis/core/data_checks.py b/aleksis/core/data_checks.py index 1279822f8d1f28bb40e040d9eaf0bb8126540940..f7152686f61fb63997eb9e0a6af9e23b47237f66 100644 --- a/aleksis/core/data_checks.py +++ b/aleksis/core/data_checks.py @@ -9,7 +9,8 @@ import reversion from reversion import set_comment from templated_email import send_templated_mail -from .util.core_helpers import celery_optional, get_site_preferences +from .celery import app +from .util.core_helpers import get_site_preferences class SolveOption: @@ -207,7 +208,7 @@ class DataCheckRegistry: return [(check.name, check.verbose_name) for check in cls.data_checks] -@celery_optional +@app.task def check_data(): """Execute all registered data checks and send email if activated.""" for check in DataCheckRegistry.data_checks: diff --git a/aleksis/core/health_checks.py b/aleksis/core/health_checks.py index e3239087a17e5fed6c59441e9c6a219e86deb006..49677765f6a2fcc52eafe227027ee381ff6da4c9 100644 --- a/aleksis/core/health_checks.py +++ b/aleksis/core/health_checks.py @@ -1,5 +1,11 @@ +from datetime import datetime + +from django.conf import settings from django.utils.translation import gettext as _ +from dbbackup import utils as dbbackup_utils +from dbbackup.storage import get_storage +from django_celery_results.models import TaskResult from health_check.backends import BaseHealthCheckBackend from aleksis.core.models import DataCheckResult @@ -16,3 +22,54 @@ class DataChecksHealthCheckBackend(BaseHealthCheckBackend): def identifier(self): return self.__class__.__name__ + + +class BaseBackupHealthCheck(BaseHealthCheckBackend): + """Common base class for backup age checks.""" + + critical_service = False + content_type = None + configured_seconds = None + + def check_status(self): + storage = get_storage() + backups = storage.list_backups(content_type=self.content_type) + if backups: + last_backup = backups[-1] + last_backup_time = dbbackup_utils.filename_to_date(last_backup) + time_gone_since_backup = last_backup_time - datetime.now() + + # Check if backup is older than configured time + if time_gone_since_backup.seconds > self.configured_seconds: + self.add_error(_(f"Last backup {time_gone_since_backup}!")) + else: + self.add_error(_("No backup found!")) + + +class DbBackupAgeHealthCheck(BaseBackupHealthCheck): + """Checks if last backup file is less than configured seconds ago.""" + + content_type = "db" + configured_seconds = settings.DBBACKUP_CHECK_SECONDS + + +class MediaBackupAgeHealthCheck(BaseBackupHealthCheck): + """Checks if last backup file is less than configured seconds ago.""" + + content_type = "media" + configured_seconds = settings.MEDIABACKUP_CHECK_SECONDS + + +class BackupJobHealthCheck(BaseHealthCheckBackend): + """Checks if last backup file is less than configured seconds ago.""" + + critical_service = False + + def check_status(self): + task = TaskResult.objects.filter(task_name="aleksis.core.tasks.backup_data").last() + + # Check if state is success + if not task: + self.add_error(_("No backup result found!")) + elif task and task.status != "SUCCESS": + self.add_error(_(f"{task.status} - {task.result}")) diff --git a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po index f44814ae73c4edaaa27294f5468eb3cb5eb9e44f..c26e99b1bc38619b7b131cb67718516e4b6da5e1 100644 --- a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po +++ b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: AlekSIS (School Information System) 0.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-01-11 21:30+0100\n" -"PO-Revision-Date: 2021-01-11 20:42+0000\n" +"PO-Revision-Date: 2021-02-17 12:13+0000\n" "Last-Translator: Jonathan Weth <teckids@jonathanweth.de>\n" "Language-Team: German <https://translate.edugit.org/projects/aleksis/aleksis/" "de/>\n" @@ -130,7 +130,7 @@ msgstr "Sie benötigen mindestens einen Empfänger." #: health_checks.py:15 msgid "There are unresolved data problems." -msgstr "Es gibt keine ungelösten Datenprobleme." +msgstr "Es gibt ungelöste Datenprobleme." #: menus.py:7 templates/two_factor/core/login.html:6 #: templates/two_factor/core/login.html:10 diff --git a/aleksis/core/migrations/0001_initial.py b/aleksis/core/migrations/0001_initial.py index 9999e1a6615138251c1745d4e33b3d9d404d0c7b..07e4bf433df4d8aec43814493d7dde6527d1f8f1 100644 --- a/aleksis/core/migrations/0001_initial.py +++ b/aleksis/core/migrations/0001_initial.py @@ -26,15 +26,12 @@ class Migration(migrations.Migration): name='GlobalPermissions', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('extended_data', django.contrib.postgres.fields.jsonb.JSONField(default=dict, editable=False)), ], options={ - 'permissions': (('view_system_status', 'Can view system status'), ('link_persons_accounts', 'Can link persons to accounts'), ('manage_data', 'Can manage data'), ('impersonate', 'Can impersonate'), ('search', 'Can use search'), ('change_site_preferences', 'Can change site preferences'), ('change_person_preferences', 'Can change person preferences'), ('change_group_preferences', 'Can change group preferences')), + 'default_permissions': (), + 'permissions': (('view_system_status', 'Can view system status'), ('link_persons_accounts', 'Can link persos to accounts'), ('manage_data', 'Can manage data'), ('impersonate', 'Can impersonate'), ('search', 'Can use search'), ('change_site_preferences', 'Can change site preferences'), ('change_person_preferences', 'Can change person preferences'), ('change_group_preferences', 'Can change group preferences')), 'managed': False, }, - managers=[ - ('objects', django.contrib.sites.managers.CurrentSiteManager()), - ], ), migrations.CreateModel( name='AdditionalField', diff --git a/aleksis/core/mixins.py b/aleksis/core/mixins.py index 7b7b0b2b466e2c4f35311eb9253d6af144b5660d..2d06ba0dc84bf79edf74cbdbbb25b7d50da33da0 100644 --- a/aleksis/core/mixins.py +++ b/aleksis/core/mixins.py @@ -154,8 +154,6 @@ class ExtensibleModel(models.Model, metaclass=_ExtensibleModelBase): extended_data = JSONField(default=dict, editable=False) - extended_data = JSONField(default=dict, editable=False) - @classmethod def _safe_add(cls, obj: Any, name: Optional[str]) -> None: # Decide the name for the attribute @@ -354,6 +352,17 @@ class PureDjangoModel(object): pass +class GlobalPermissionModel(models.Model): + """Base model for global permissions. + + This base model ensures that global permissions are not managed.""" + + class Meta: + default_permissions = () + abstract = True + managed = False + + class _ExtensibleFormMetaclass(ModelFormMetaclass): def __new__(cls, name, bases, dct): x = super().__new__(cls, name, bases, dct) diff --git a/aleksis/core/models.py b/aleksis/core/models.py index 24b6c8ca1c5f18889679d9c81b972eaac6cd72c4..93ad1ff096d5f2db93d0953a439352386b837fe4 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator from django.db import models, transaction from django.db.models import QuerySet +from django.dispatch import receiver from django.forms.widgets import Media from django.urls import reverse from django.utils import timezone @@ -22,6 +23,7 @@ from django.utils.translation import gettext_lazy as _ import jsonstore from cache_memoize import cache_memoize from dynamic_preferences.models import PerInstancePreferenceModel +from model_utils import FieldTracker from model_utils.models import TimeStampedModel from phonenumber_field.modelfields import PhoneNumberField from polymorphic.models import PolymorphicModel @@ -34,7 +36,12 @@ from .managers import ( GroupQuerySet, SchoolTermQuerySet, ) -from .mixins import ExtensibleModel, PureDjangoModel, SchoolTermRelatedExtensibleModel +from .mixins import ( + ExtensibleModel, + GlobalPermissionModel, + PureDjangoModel, + SchoolTermRelatedExtensibleModel, +) from .tasks import send_notification from .util.core_helpers import get_site_preferences, now_tomorrow from .util.model_helpers import ICONS @@ -255,20 +262,21 @@ class Person(ExtensibleModel): """Return the count of unread notifications for this person.""" return self.unread_notifications.count() + user_info_tracker = FieldTracker(fields=("first_name", "last_name", "email")) + def save(self, *args, **kwargs): + # Determine all fields that were changed since last load + dirty = bool(self.user_info_tracker.changed()) + super().save(*args, **kwargs) - # Synchronise user fields to linked User object to keep it up to date - if self.user: + if self.user and dirty: + # Synchronise user fields to linked User object to keep it up to date self.user.first_name = self.first_name self.user.last_name = self.last_name self.user.email = self.email self.user.save() - # Save all related groups once to keep synchronisation with Django - for group in self.member_of.union(self.owner_of.all()).all(): - group.save() - # Select a primary group if none is set self.auto_select_primary_group() @@ -428,19 +436,26 @@ class Group(SchoolTermRelatedExtensibleModel): else: return f"{self.name} ({self.short_name})" - def save(self, *args, **kwargs): + group_info_tracker = FieldTracker(fields=("name", "short_name", "members", "owners")) + + def save(self, force: bool = False, *args, **kwargs): + # Determine state of object in relation to database + created = self.pk is None + dirty = bool(self.group_info_tracker.changed()) + super().save(*args, **kwargs) - # Synchronise group to Django group with same name - dj_group, _ = DjangoGroup.objects.get_or_create(name=self.name) - dj_group.user_set.set( - list( - self.members.filter(user__isnull=False) - .values_list("user", flat=True) - .union(self.owners.filter(user__isnull=False).values_list("user", flat=True)) + if force or created or dirty: + # Synchronise group to Django group with same name + dj_group, _ = DjangoGroup.objects.get_or_create(name=self.name) + dj_group.user_set.set( + list( + self.members.filter(user__isnull=False) + .values_list("user", flat=True) + .union(self.owners.filter(user__isnull=False).values_list("user", flat=True)) + ) ) - ) - dj_group.save() + dj_group.save() class PersonGroupThrough(ExtensibleModel): @@ -463,6 +478,40 @@ class PersonGroupThrough(ExtensibleModel): setattr(self, field_name, field_instance) +@receiver(models.signals.m2m_changed, sender=PersonGroupThrough) +def save_group_on_m2m_changed( + sender: PersonGroupThrough, + instance: models.Model, + action: str, + reverse: bool, + model: models.Model, + pk_set: Optional[set], + **kwargs, +) -> None: + """Ensure user and group data is synced to Django's models. + + AlekSIS maintains personal information and group meta-data / membership + in its Person and Group models. As third-party libraries have no knowledge + about this, we need to keep django.contrib.auth in sync. + + This signal handler triggers a save of group objects whenever a membership + changes. The save() code will decide whether to update the Django objects + or not. + """ + if action not in ("post_add", "post_remove", "post_clear"): + # Only trigger once, after the change was applied to the database + return + + if reverse: + # Relationship was changed on the Person side, saving all groups + # that have been touched there + for group in model.objects.filter(pk__in=pk_set): + group.save(force=True) + else: + # Relationship was changed on the Group side + instance.save(force=True) + + class Activity(ExtensibleModel, TimeStampedModel): """Activity of a user to trace some actions done in AlekSIS in displayable form.""" @@ -830,11 +879,10 @@ class GroupType(ExtensibleModel): verbose_name_plural = _("Group types") -class GlobalPermissions(ExtensibleModel): +class GlobalPermissions(GlobalPermissionModel): """Container for global permissions.""" - class Meta: - managed = False + class Meta(GlobalPermissionModel.Meta): permissions = ( ("view_system_status", _("Can view system status")), ("link_persons_accounts", _("Can link persons to accounts")), diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index 22f1ede64a7a1cd47316601691a85776b116ea9f..d65a5a08e79c28656abe344406cf3b8272311630 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -57,7 +57,6 @@ DEBUG_TOOLBAR_PANELS = [ "debug_toolbar.panels.templates.TemplatesPanel", "debug_toolbar.panels.signals.SignalsPanel", "debug_toolbar.panels.logging.LoggingPanel", - "debug_toolbar.panels.redirects.RedirectsPanel", "debug_toolbar.panels.profiling.ProfilingPanel", ] @@ -74,12 +73,19 @@ INSTALLED_APPS = [ "django.contrib.sites", "django.contrib.staticfiles", "django.contrib.humanize", + "django_extensions", "guardian", "rules.apps.AutodiscoverRulesConfig", "haystack", "polymorphic", "django_global_request", "dbbackup", + "django_celery_beat", + "django_celery_results", + "celery_progress", + "health_check.contrib.celery", + "djcelery_email", + "celery_haystack", "settings_context_processor", "sass_processor", "django_any_js", @@ -189,7 +195,6 @@ DATABASES = { "PASSWORD": _settings.get("database.password", None), "HOST": _settings.get("database.host", "127.0.0.1"), "PORT": _settings.get("database.port", "5432"), - "ATOMIC_REQUESTS": True, "CONN_MAX_AGE": _settings.get("database.conn_max_age", None), } } @@ -485,21 +490,13 @@ if _settings.get("twilio.sid", None): TWILIO_TOKEN = _settings.get("twilio.token") TWILIO_CALLER_ID = _settings.get("twilio.callerid") -if _settings.get("celery.enabled", False): - INSTALLED_APPS += ( - "django_celery_beat", - "django_celery_results", - "celery_progress", - "health_check.contrib.celery", - ) - CELERY_BROKER_URL = _settings.get("celery.broker", "redis://localhost") - CELERY_RESULT_BACKEND = "django-db" - CELERY_CACHE_BACKEND = "django-cache" - CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" +CELERY_BROKER_URL = _settings.get("celery.broker", "redis://localhost") +CELERY_RESULT_BACKEND = "django-db" +CELERY_CACHE_BACKEND = "django-cache" +CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" - if _settings.get("celery.email", False): - INSTALLED_APPS += ("djcelery_email",) - EMAIL_BACKEND = "djcelery_email.backends.CeleryEmailBackend" +if _settings.get("celery.email", False): + EMAIL_BACKEND = "djcelery_email.backends.CeleryEmailBackend" PWA_APP_NAME = lazy_preference("general", "title") PWA_APP_DESCRIPTION = lazy_preference("general", "description") @@ -722,11 +719,8 @@ elif HAYSTACK_BACKEND_SHORT == "whoosh": }, } -if _settings.get("celery.enabled", False) and _settings.get("search.celery", True): - INSTALLED_APPS.append("celery_haystack") - HAYSTACK_SIGNAL_PROCESSOR = "celery_haystack.signals.CelerySignalProcessor" -else: - HAYSTACK_SIGNAL_PROCESSOR = "haystack.signals.RealtimeSignalProcessor" +HAYSTACK_SIGNAL_PROCESSOR = "celery_haystack.signals.CelerySignalProcessor" +CELERY_HAYSTACK_IGNORE_RESULT = True HAYSTACK_SEARCH_RESULTS_PER_PAGE = 10 @@ -737,4 +731,7 @@ HEALTH_CHECK = { "MEMORY_MIN": _settings.get("health.memory_min_mb", 500), } +DBBACKUP_CHECK_SECONDS = _settings.get("backup.database.check_seconds", 7200) +MEDIABACKUP_CHECK_SECONDS = _settings.get("backup.media.check_seconds", 7200) + PROMETHEUS_EXPORT_MIGRATIONS = False diff --git a/aleksis/core/static/icons/favicon_16.png b/aleksis/core/static/icons/favicon_16.png index 4af3f9529ea0e3a19de31643cb782e1c08b46d5e..f1d02a7a8a34825726d20eeb1f6a63922d1a848e 100644 Binary files a/aleksis/core/static/icons/favicon_16.png and b/aleksis/core/static/icons/favicon_16.png differ diff --git a/aleksis/core/static/icons/favicon_32.png b/aleksis/core/static/icons/favicon_32.png index 3b9a9c2d025fbbb137a1b84f805716fffe1859c8..edc72d4556cce0fde923a0ac3db4d0f94bb74412 100644 Binary files a/aleksis/core/static/icons/favicon_32.png and b/aleksis/core/static/icons/favicon_32.png differ diff --git a/aleksis/core/static/icons/favicon_48.png b/aleksis/core/static/icons/favicon_48.png index 766a8bb798fc70665aa241e058b0462e688cd309..a057802b4623b08e81328f16acafe8e35dd9621b 100644 Binary files a/aleksis/core/static/icons/favicon_48.png and b/aleksis/core/static/icons/favicon_48.png differ diff --git a/aleksis/core/static/img/aleksis-icon.svg b/aleksis/core/static/img/aleksis-icon.svg index e1ef059e37be7bf5bad544496e45eef08c98b6ce..db4a3a2abe7bed163a9f84d903619883734c4e2a 100644 --- a/aleksis/core/static/img/aleksis-icon.svg +++ b/aleksis/core/static/img/aleksis-icon.svg @@ -49,4 +49,7 @@ <path id="puzzle-ul" d="m31.48 20.23c0.9818-0.9818 0.9818-2.563 0-3.544l-10.54-10.54c-0.9818-0.9819-2.563-0.9819-3.545 0l-10.57 10.57c-0.9818 0.9818-0.9818 2.563 6e-6 3.544l10.54 10.54c0.9818 0.9819 2.563 0.9819 3.545 0l3.261-3.261-1.562-1.562 0.0064-0.0069c-0.03229-0.02696-0.06563-0.05145-0.09668-0.08057-1.118-1.118-1.118-2.93 0-4.048 1.118-1.118 2.93-1.118 4.048 0 0.02514 0.0363 0.04485 0.07241 0.0681 0.109l0.0196-0.01948 1.562 1.562zm-23.06 8.969c-1.118-1.118-1.118-2.93-6.1e-6 -4.048 2.984-2.057 5.759 2.228 4.048 4.048-1.118 1.118-2.93 1.118-4.048 0zm5.697-1.652-1.562 1.562-4.045-4.045 1.562-1.562z"/> </g> </g> + <g id="favicon-bag" transform="translate(-46.809258,-7.6499461)" display="none"> + <use xlink:href="#schoolbag" transform="matrix(2.25,0,0,2.25,-30.62,0)" fill="#ffffff" x="0" y="0" width="100%" height="100%" /> + </g> </svg> diff --git a/aleksis/core/tasks.py b/aleksis/core/tasks.py index d085f027fad09e875bf612d8352d83151e77d880..731c4356c20bba915bd5ad0811307c3877810631 100644 --- a/aleksis/core/tasks.py +++ b/aleksis/core/tasks.py @@ -1,11 +1,11 @@ from django.conf import settings from django.core import management -from .util.core_helpers import celery_optional +from .celery import app from .util.notifications import send_notification as _send_notification -@celery_optional +@app.task def send_notification(notification: int, resend: bool = False) -> None: """Send a notification object to its recipient. @@ -15,7 +15,7 @@ def send_notification(notification: int, resend: bool = False) -> None: _send_notification(notification, resend) -@celery_optional +@app.task def backup_data() -> None: """Backup database and media using django-dbbackup.""" # Assemble command-line options for dbbackup management command diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index eb5c6c41ac26173ebe81df366b19acf80d6ed234..8183fba0d6da1ea956eb1da94b720b49e02363ac 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -15,7 +15,6 @@ from rules.contrib.views import permission_required from two_factor.urls import urlpatterns as tf_urls from . import views -from .util.core_helpers import is_celery_enabled urlpatterns = [ path("", include("django_prometheus.urls")), @@ -25,6 +24,7 @@ urlpatterns = [ path("data_management/", views.data_management, name="data_management"), path("status/", views.SystemStatus.as_view(), name="system_status"), path("", include(tf_urls)), + path("celery_progress/", include("celery_progress.urls")), path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"), path("school_terms/", views.SchoolTermListView.as_view(), name="school_terms"), path("school_terms/create/", views.SchoolTermCreateView.as_view(), name="create_school_term"), @@ -211,9 +211,6 @@ if hasattr(settings, "TWILIO_ACCOUNT_SID"): urlpatterns += [path("", include(tf_twilio_urls))] -if is_celery_enabled(): - urlpatterns.append(path("celery_progress/", include("celery_progress.urls"))) - # Serve javascript-common if in development if settings.DEBUG: urlpatterns.append(path("__debug__/", include(debug_toolbar.urls))) diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py index 79548a79dfbbb94a36a40bfad54fef5a24d3653c..d7539c22616abd2d76013546acb15b592862ceb4 100644 --- a/aleksis/core/util/apps.py +++ b/aleksis/core/util/apps.py @@ -5,6 +5,7 @@ from django.contrib.auth.signals import user_logged_in, user_logged_out from django.db.models.signals import post_migrate, pre_migrate from django.http import HttpRequest +import pkg_resources from dynamic_preferences.signals import preference_updated from license_expression import Licensing from spdx_license_list import LICENSES @@ -28,21 +29,46 @@ class AppConfig(django.apps.AppConfig): # Getting an app ready means it should look at its config once self.preference_updated(self) - @classmethod - def get_name(cls): + def get_distribution_name(self): + """Get pkg_resources distribution name of application package.""" + if hasattr(self, "dist_name"): + return self.dist_name + elif self.name.lower().startswith("aleksis.apps."): + return self.name.lower().replace("aleksis.apps.", "AlekSIS-App-") + + return None + + def get_distribution(self): + """Get pkg_resources distribution of application package.""" + dist_name = self.get_distribution_name() + if dist_name: + try: + dist = pkg_resources.get_distribution(dist_name) + except pkg_resources.DistributionNotFound: + return None + + return dist + + def get_name(self): """Get name of application package.""" - return getattr(cls, "verbose_name", cls.name) - # TODO Try getting from distribution if not set + if hasattr(self, "verbose_name"): + return self.verbose_name + else: + dist_name = self.get_distribution_name() + if dist_name: + return dist_name + return self.name - @classmethod - def get_version(cls): + def get_version(self): """Get version of application package.""" - try: - from .. import __version__ # noqa - except ImportError: - __version__ = None - - return getattr(cls, "version", __version__) + if hasattr(self, "version"): + return self.version + else: + dist = self.get_distribution() + if dist: + return dist.version + else: + return "unknown" @classmethod def get_licence(cls) -> Tuple: diff --git a/aleksis/core/util/celery_progress.py b/aleksis/core/util/celery_progress.py index 9de760eeb56b8ce535a0cd8e88f7380c5411f78d..e957dd4f938291b11a85d00e115d302f512e609a 100644 --- a/aleksis/core/util/celery_progress.py +++ b/aleksis/core/util/celery_progress.py @@ -1,34 +1,162 @@ -from decimal import Decimal -from typing import Union +from functools import wraps +from numbers import Number +from typing import Callable, Generator, Iterable, Optional, Sequence, Union + +from django.contrib import messages from celery_progress.backend import PROGRESS_STATE, AbstractProgressRecorder +from ..celery import app + class ProgressRecorder(AbstractProgressRecorder): + """Track the progress of a Celery task and give data to the frontend. + + This recorder provides the functions `set_progress` and `add_message` + which can be used to track the status of a Celery task. + + How to use + ---------- + 1. Write a function and include tracking methods + + :: + + from django.contrib import messages + + from aleksis.core.util.celery_progress import recorded_task + + @recorded_task + def do_something(foo, bar, recorder, baz=None): + # ... + recorder.set_progress(total=len(list_with_data)) + + for i, item in enumerate(list_with_data): + # ... + recorder.set_progress(i+1) + # ... + + recorder.add_message(messages.SUCCESS, "All data were imported successfully.") + + You can also use `recorder.iterate` to simplify iterating and counting. + + 2. Track progress in view: + + :: + + def my_view(request): + context = {} + # ... + result = do_something.delay(foo, bar, baz=baz) + + context = { + "title": _("Progress: Import data"), + "back_url": reverse("index"), + "progress": { + "task_id": result.task_id, + "title": _("Import objects …"), + "success": _("The import was done successfully."), + "error": _("There was a problem while importing data."), + }, + } + + # Render progress view + return render(request, "core/progress.html", context) + """ + def __init__(self, task): self.task = task - self.messages = [] - self.total = 100 - self.current = 0 + self._messages = [] + self._current = 0 + self._total = 100 - def set_progress(self, current: Union[int, float], **kwargs): - self.current = current + def iterate(self, data: Union[Iterable, Sequence], total: Optional[int] = None) -> Generator: + """Iterate over a sequence or iterable, updating progress on the move. + + :: + + @recorded_task + def do_something(long_list, recorder): + for item in recorder.iterate(long_list): + do_something_with(item) + + :param data: A sequence (tuple, list, set,...) or an iterable + :param total: Total number of items, in case data does not support len() + """ + if total is None and hasattr(data, "__len__"): + total = len(data) + else: + raise TypeError("No total value passed, and data does not support len()") + + for current, item in enumerate(data): + self.set_progress(current, total) + yield item + + def set_progress( + self, + current: Optional[Number] = None, + total: Optional[Number] = None, + description: Optional[str] = None, + level: int = messages.INFO, + ): + """Set the current progress in the frontend. + + The progress percentage is automatically calculated in relation to self.total. + + :param current: The number of processed items; relative to total, default unchanged + :param total: The total number of items (or 100 if using a percentage), default unchanged + :param description: A textual description, routed to the frontend as an INFO message + """ + if current is not None: + self._current = current + if total is not None: + self._total = total percent = 0 - if self.total > 0: - percent = (Decimal(current) / Decimal(self.total)) * Decimal(100) - percent = float(round(percent, 2)) + if self._total > 0: + percent = self._current / self._total * 100 + + if description is not None: + self._messages.append((level, description)) self.task.update_state( state=PROGRESS_STATE, meta={ - "current": current, - "total": self.total, + "current": self._current, + "total": self._total, "percent": percent, - "messages": self.messages, + "messages": self._messages, }, ) - def add_message(self, level: int, message: str, **kwargs): - self.messages.append((level, message)) - self.set_progress(self.current) + def add_message(self, level: int, message: str) -> None: + """Show a message in the progress frontend. + + This method is a shortcut for set_progress with no new progress arguments, + passing only the message and level as description. + + :param level: The message level (default levels from django.contrib.messages) + :param message: The actual message (should be translated) + """ + self.set_progress(description=message, level=level) + + +def recorded_task(orig: Optional[Callable] = None, **kwargs) -> Union[Callable, app.Task]: + """Create a Celery task that receives a ProgressRecorder. + + Returns a Task object with a wrapper that passes the recorder instance + as the recorder keyword argument. + """ + + def _real_decorator(orig: Callable) -> app.Task: + @wraps(orig) + def _inject_recorder(task, *args, **kwargs): + recorder = ProgressRecorder(task) + return orig(*args, **kwargs, recorder=recorder) + + # Force bind to True because _inject_recorder needs the Task object + kwargs["bind"] = True + return app.task(_inject_recorder, **kwargs) + + if orig and not kwargs: + return _real_decorator(orig) + return _real_decorator diff --git a/aleksis/core/util/core_helpers.py b/aleksis/core/util/core_helpers.py index 9e9a90faf5547b71f3d556d89d6f2459b743c04b..676894d28d62f32ee9c08de874edf6857f94a629 100644 --- a/aleksis/core/util/core_helpers.py +++ b/aleksis/core/util/core_helpers.py @@ -1,6 +1,5 @@ import os import sys -import time from datetime import datetime, timedelta from importlib import import_module from itertools import groupby @@ -14,7 +13,6 @@ else: import importlib_metadata as metadata from django.conf import settings -from django.db import transaction from django.db.models import Model, QuerySet from django.http import HttpRequest from django.shortcuts import get_object_or_404 @@ -22,9 +20,6 @@ from django.utils import timezone from django.utils.functional import lazy from cache_memoize import cache_memoize -from django_global_request.middleware import get_request - -from aleksis.core.util import messages def copyright_years(years: Sequence[int], seperator: str = ", ", joiner: str = "–") -> str: @@ -188,140 +183,6 @@ def has_person(obj: Union[HttpRequest, Model]) -> bool: return True -def is_celery_enabled(): - """Check whether celery support is enabled.""" - return hasattr(settings, "CELERY_RESULT_BACKEND") - - -def celery_optional(orig: Callable) -> Callable: - """Add a decorator that makes Celery optional for a function. - - If Celery is configured and available, it wraps the function in a Task - and calls its delay method when invoked; if not, it leaves it untouched - and it is executed synchronously. - - The wrapped function returns a tuple with either - the return value of the task's delay method and False - if the method has been executed asynchronously - or the return value of the executed method and True - if the method has been executed synchronously. - """ - if is_celery_enabled(): - from ..celery import app # noqa - - task = app.task(orig) - - def wrapped(*args, **kwargs): - if is_celery_enabled(): - return transaction.on_commit(lambda: task.delay(*args, **kwargs)), False - else: - return orig(*args, **kwargs), True - - return wrapped - - -class DummyRecorder: - def set_progress(self, *args, **kwargs): - pass - - def add_message(self, level: int, message: str, **kwargs) -> Optional[Any]: - request = get_request() - return messages.add_message(request, level, message, **kwargs) - - -def celery_optional_progress(orig: Callable) -> Callable: - """Add a decorator that makes Celery with progress bar support optional for a function. - - If Celery is configured and available, it wraps the function in a Task - and calls its delay method when invoked; if not, it leaves it untouched - and it is executed synchronously. - - Additionally, it adds a recorder class as first argument - (`ProgressRecorder` if Celery is enabled, else `DummyRecoder`). - - This recorder provides the functions `set_progress` and `add_message` - which can be used to track the status of the task. - For further information, see the respective recorder classes. - - How to use - ---------- - 1. Write a function and include tracking methods - - :: - - from django.contrib import messages - - from aleksis.core.util.core_helpers import celery_optional_progress - - @celery_optional_progress - def do_something(recorder: Union[ProgressRecorder, DummyRecorder], foo, bar, baz=None): - # ... - recorder.total = len(list_with_data) - - for i, item in list_with_data: - # ... - recorder.set_progress(i + 1) - # ... - - recorder.add_message(messages.SUCCESS, "All data were imported successfully.") - - 2. Track process in view: - - :: - - def my_view(request): - context = {} - # ... - result = do_something(foo, bar, baz=baz) - - if result: - context = { - "title": _("Progress: Import data"), - "back_url": reverse("index"), - "progress": { - "task_id": result.task_id, - "title": _("Import objects …"), - "success": _("The import was done successfully."), - "error": _("There was a problem while importing data."), - }, - } - - # Render progress view - return render(request, "core/progress.html", context) - - # Render other view if Celery isn't enabled - return render(request, "my-app/other-view.html", context) - """ - - def recorder_func(self, *args, **kwargs): - if is_celery_enabled(): - from .celery_progress import ProgressRecorder # noqa - - recorder = ProgressRecorder(self) - else: - recorder = DummyRecorder() - orig(recorder, *args, **kwargs) - - # Needed to ensure that all messages are displayed by frontend - time.sleep(0.7) - - var_name = f"{orig.__module__}.{orig.__name__}" - - if is_celery_enabled(): - from ..celery import app # noqa - - task = app.task(recorder_func, bind=True, name=var_name) - - def wrapped(*args, **kwargs): - if is_celery_enabled(): - return task.delay(*args, **kwargs) - else: - recorder_func(None, *args, **kwargs) - return None - - return wrapped - - def path_and_rename(instance, filename: str, upload_to: str = "files") -> str: """Update path of an uploaded file and renames it to a random UUID in Django FileField.""" _, ext = os.path.splitext(filename) diff --git a/aleksis/core/util/search.py b/aleksis/core/util/search.py index 5c8af9f860df94649d46f1fc0dd0741e35c6ad07..34c736f9e13b55d6ac277d41b76a374efa2b43d1 100644 --- a/aleksis/core/util/search.py +++ b/aleksis/core/util/search.py @@ -1,15 +1,9 @@ -from django.conf import settings - +from celery_haystack.indexes import CelerySearchIndex as BaseSearchIndex from haystack import indexes # Not used here, but simplifies imports for apps Indexable = indexes.Indexable # noqa -if settings.HAYSTACK_SIGNAL_PROCESSOR == "celery_haystack.signals.CelerySignalProcessor": - from celery_haystack.indexes import CelerySearchIndex as BaseSearchIndex -else: - from haystack.indexes import SearchIndex as BaseSearchIndex - class SearchIndex(BaseSearchIndex): """Base class for search indexes on AlekSIS models. diff --git a/aleksis/core/views.py b/aleksis/core/views.py index d110b30f63c8f6748198c3fc8268e81bb0caab9b..f1bc04013f92ed2bbde449c47cf44473be70ece0 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -1,7 +1,6 @@ from typing import Any, Dict, Optional, Type from django.apps import apps -from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator @@ -18,6 +17,7 @@ from django.views.generic.detail import DetailView from django.views.generic.list import ListView import reversion +from django_celery_results.models import TaskResult from django_tables2 import RequestConfig, SingleTableView from dynamic_preferences.forms import preference_form_builder from guardian.shortcuts import get_objects_for_user @@ -31,6 +31,7 @@ from rules.contrib.views import PermissionRequiredMixin, permission_required from aleksis.core.data_checks import DataCheckRegistry, check_data +from .celery import app from .filters import GroupFilter, PersonFilter from .forms import ( AnnouncementForm, @@ -411,17 +412,12 @@ class SystemStatus(PermissionRequiredMixin, MainView): status_code = 500 if self.errors else 200 task_results = [] - if "django_celery_results" in settings.INSTALLED_APPS: - from django_celery_results.models import TaskResult # noqa - - from .celery import app # noqa - - if app.control.inspect().registered_tasks(): - job_list = list(app.control.inspect().registered_tasks().values())[0] - for job in job_list: - task_results.append( - TaskResult.objects.filter(task_name=job).order_by("date_done").last() - ) + if app.control.inspect().registered_tasks(): + job_list = list(app.control.inspect().registered_tasks().values())[0] + for job in job_list: + task_results.append( + TaskResult.objects.filter(task_name=job).order_by("date_done").last() + ) context = {"plugins": self.plugins, "status_code": status_code, "tasks": task_results} return self.render_to_response(context, status=status_code) diff --git a/poetry.lock b/poetry.lock index b0e78f3ef62a53847f39f72f4804963c3d936b2f..697d09b19fd5db3dc309338bff5a534491407b85 100644 --- a/poetry.lock +++ b/poetry.lock @@ -46,10 +46,10 @@ reference = "gitlab" [[package]] name = "amqp" -version = "5.0.3" +version = "5.0.5" description = "Low-level AMQP client for Python (fork of amqplib)." category = "main" -optional = true +optional = false python-versions = ">=3.6" [package.dependencies] @@ -63,6 +63,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "appnope" +version = "0.1.2" +description = "Disable App Nap on macOS >= 10.9" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "asgiref" version = "3.3.1" @@ -107,6 +115,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pytz = ">=2015.7" +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "bandit" version = "1.7.0" @@ -142,7 +158,7 @@ name = "billiard" version = "3.6.3.0" description = "Python multiprocessing fork with improvements and bugfixes" category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -167,7 +183,7 @@ d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] name = "bleach" -version = "3.2.2" +version = "3.3.0" description = "An easy safelist-based HTML-sanitizing tool." category = "main" optional = false @@ -213,7 +229,7 @@ name = "celery" version = "5.0.5" description = "Distributed Task Queue." category = "main" -optional = true +optional = false python-versions = ">=3.6," [package.dependencies] @@ -263,15 +279,17 @@ zookeeper = ["kazoo (>=1.3.1)"] zstd = ["zstandard"] [[package]] -name = "celery-haystack" -version = "0.10" -description = "An app for integrating Celery with Haystack." +name = "celery-haystack-ng" +version = "0.20.post2" +description = "An app for integrating Celery with Haystack" category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] +celery = ">=4.0" django-appconf = ">=0.4.1" +django-haystack = ">=2.0" [[package]] name = "celery-progress" @@ -315,7 +333,7 @@ name = "click-didyoumean" version = "0.0.3" description = "Enable git-like did-you-mean feature in click." category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -326,7 +344,7 @@ name = "click-plugins" version = "1.1.1" description = "An extension module for click to enable registering CLI commands via setuptools entry-points." category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -340,7 +358,7 @@ name = "click-repl" version = "0.1.6" description = "REPL plugin for Click" category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -380,7 +398,7 @@ six = "*" [[package]] name = "coverage" -version = "5.3.1" +version = "5.4" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -389,6 +407,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] toml = ["toml"] +[[package]] +name = "decorator" +version = "4.4.2" +description = "Decorators for Humans" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" + [[package]] name = "dj-database-url" version = "0.5.0" @@ -399,7 +425,7 @@ python-versions = "*" [[package]] name = "django" -version = "3.1.5" +version = "3.1.6" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." category = "main" optional = false @@ -416,7 +442,7 @@ bcrypt = ["bcrypt"] [[package]] name = "django-any-js" -version = "1.0.3.post0" +version = "1.0.3.post1" description = "Include JavaScript libraries with readable template tags" category = "main" optional = false @@ -438,14 +464,14 @@ django = "*" [[package]] name = "django-auth-ldap" -version = "2.2.0" +version = "2.3.0" description = "Django LDAP authentication backend." category = "main" optional = true -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -Django = ">=1.11" +Django = ">=2.2" python-ldap = ">=3.1" [[package]] @@ -498,7 +524,7 @@ name = "django-celery-beat" version = "2.2.0" description = "Database-backed Periodic Tasks." category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -512,7 +538,7 @@ name = "django-celery-email" version = "3.0.0" description = "An async Django email backend using celery" category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -525,7 +551,7 @@ name = "django-celery-results" version = "2.0.1" description = "Celery result backends for Django." category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -588,6 +614,14 @@ django = ">=1.11" persisting-theory = ">=0.2.1" six = "*" +[[package]] +name = "django-extensions" +version = "3.1.1" +description = "Extensions for Django" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "django-favicon-plus-reloaded" version = "1.0.4" @@ -672,7 +706,7 @@ django = ">=1.11" [[package]] name = "django-impersonate" -version = "1.7.2" +version = "1.7.3" description = "Django app to allow superusers to impersonate other users." category = "main" optional = false @@ -727,7 +761,7 @@ python-versions = "*" [[package]] name = "django-material" -version = "1.7.4" +version = "1.7.5" description = "Material design for django forms and admin" category = "main" optional = false @@ -943,7 +977,7 @@ name = "django-timezone-field" version = "4.1.1" description = "A Django app providing database and form fields for pytz timezone objects." category = "main" -optional = true +optional = false python-versions = ">=3.5" [package.dependencies] @@ -1046,7 +1080,7 @@ yaml = ["ruamel.yaml"] [[package]] name = "faker" -version = "5.7.0" +version = "6.1.1" description = "Faker is a Python package that generates fake data for you." category = "main" optional = false @@ -1206,7 +1240,7 @@ smmap = ">=3.0.1,<4" [[package]] name = "gitpython" -version = "3.1.12" +version = "3.1.13" description = "Python Git Library" category = "dev" optional = false @@ -1263,6 +1297,45 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "ipython" +version = "7.20.0" +description = "IPython: Productive Interactive Computing" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["notebook", "ipywidgets"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "isort" version = "5.7.0" @@ -1276,9 +1349,24 @@ pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +[[package]] +name = "jedi" +version = "0.18.0" +description = "An autocompletion tool for Python that can be used for text editors." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] + [[package]] name = "jinja2" -version = "2.11.2" +version = "2.11.3" description = "A very fast and expressive template engine." category = "dev" optional = false @@ -1295,7 +1383,7 @@ name = "kombu" version = "5.0.2" description = "Messaging library for Python." category = "main" -optional = true +optional = false python-versions = ">=3.6" [package.dependencies] @@ -1382,7 +1470,7 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.8" +version = "20.9" description = "Core utilities for Python packages" category = "main" optional = false @@ -1391,6 +1479,18 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" +[[package]] +name = "parso" +version = "0.8.1" +description = "A Python Parser" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + [[package]] name = "pathspec" version = "0.8.1" @@ -1415,9 +1515,20 @@ category = "main" optional = false python-versions = "*" +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +ptyprocess = ">=0.5" + [[package]] name = "pg8000" -version = "1.16.6" +version = "1.17.0" description = "PostgreSQL interface library" category = "dev" optional = false @@ -1428,12 +1539,20 @@ scramp = "1.2.0" [[package]] name = "phonenumbers" -version = "8.12.16" +version = "8.12.18" description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers." category = "main" optional = false python-versions = "*" +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "pillow" version = "8.1.0" @@ -1469,10 +1588,10 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.14" +version = "3.0.16" description = "Library for building powerful interactive command lines in Python" category = "main" -optional = true +optional = false python-versions = ">=3.6.1" [package.dependencies] @@ -1497,6 +1616,14 @@ category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "py" version = "1.10.0" @@ -1534,11 +1661,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pycryptodome" -version = "3.9.9" +version = "3.10.1" description = "Cryptographic library for Python" category = "main" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pydocstyle" @@ -1561,9 +1688,9 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.7.4" +version = "2.8.0" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -1670,7 +1797,7 @@ name = "python-crontab" version = "2.5.1" description = "Python Crontab API" category = "main" -optional = true +optional = false python-versions = "*" [package.dependencies] @@ -1716,7 +1843,7 @@ six = ">=1.4.0" [[package]] name = "pytz" -version = "2020.5" +version = "2021.1" description = "World timezone definitions, modern and historical" category = "main" optional = false @@ -1753,7 +1880,7 @@ name = "redis" version = "3.5.3" description = "Python client for Redis key-value store" category = "main" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] @@ -1886,11 +2013,11 @@ python-versions = "*" [[package]] name = "soupsieve" -version = "2.1" +version = "2.2" description = "A modern CSS selector implementation for Beautiful Soup." category = "main" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [[package]] name = "spdx-license-list" @@ -1902,7 +2029,7 @@ python-versions = "*" [[package]] name = "sphinx" -version = "3.4.3" +version = "3.5.1" description = "Python documentation generator" category = "dev" optional = false @@ -1928,7 +2055,7 @@ sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.790)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] @@ -2110,7 +2237,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tqdm" -version = "4.56.0" +version = "4.56.2" description = "Fast, Extensible Progress Meter" category = "main" optional = false @@ -2120,9 +2247,23 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" dev = ["py-make (>=0.1.0)", "twine", "wheel"] telegram = ["requests"] +[[package]] +name = "traitlets" +version = "5.0.5" +description = "Traitlets Python configuration system" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +ipython-genutils = "*" + +[package.extras] +test = ["pytest"] + [[package]] name = "twilio" -version = "6.51.0" +version = "6.52.0" description = "Twilio API client and TwiML generator" category = "main" optional = false @@ -2152,7 +2293,7 @@ python-versions = "*" [[package]] name = "urllib3" -version = "1.26.2" +version = "1.26.3" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false @@ -2168,7 +2309,7 @@ name = "vine" version = "5.0.0" description = "Promises, promises, promises." category = "main" -optional = true +optional = false python-versions = ">=3.6" [[package]] @@ -2176,7 +2317,7 @@ name = "wcwidth" version = "0.2.5" description = "Measures the displayed width of unicode strings in a terminal" category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -2211,13 +2352,12 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] -celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celery-email", "celery-haystack"] ldap = ["django-auth-ldap"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "b8b44ac64723c99f10cbeaa085976eb30a503e29379801c21495d362933e4aa0" +content-hash = "70b2b90f0d23ad6037d01bcadd9002a442a8e9e16bc1d09f148c9e74a30f3a4c" [metadata.files] alabaster = [ @@ -2228,13 +2368,17 @@ aleksis-builddeps = [ {file = "AlekSIS-Builddeps-1.tar.gz", hash = "sha256:97a19597f422593cbdc438aabf17f95748126c8951df6ac7db7991fc99c108c4"}, ] amqp = [ - {file = "amqp-5.0.3-py3-none-any.whl", hash = "sha256:2c58528a05dcbf2ae080f3141b6a5bf467949fad9234edd8b9085b8db2e325fe"}, - {file = "amqp-5.0.3.tar.gz", hash = "sha256:1733ebf713050504fd9d2ebc661f1fc95b3588f99ee87d2e39c84c27bfd815dc"}, + {file = "amqp-5.0.5-py3-none-any.whl", hash = "sha256:1e759a7f202d910939de6eca45c23a107f6b71111f41d1282c648e9ac3d21901"}, + {file = "amqp-5.0.5.tar.gz", hash = "sha256:affdd263d8b8eb3c98170b78bf83867cdb6a14901d586e00ddb65bfe2f0c4e60"}, ] appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, ] +appnope = [ + {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, + {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, +] asgiref = [ {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"}, {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"}, @@ -2251,6 +2395,10 @@ babel = [ {file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"}, {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"}, ] +backcall = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] bandit = [ {file = "bandit-1.7.0-py3-none-any.whl", hash = "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07"}, {file = "bandit-1.7.0.tar.gz", hash = "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608"}, @@ -2269,8 +2417,8 @@ black = [ {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, ] bleach = [ - {file = "bleach-3.2.2-py2.py3-none-any.whl", hash = "sha256:a690ccc41a10d806a7c0a9130767750925e4863e332f7e4ea93da1bc12a24300"}, - {file = "bleach-3.2.2.tar.gz", hash = "sha256:ce6270dd0ae56cd810495b8d994551ae16b41f2b4043cf50064f298985afdb3c"}, + {file = "bleach-3.3.0-py2.py3-none-any.whl", hash = "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125"}, + {file = "bleach-3.3.0.tar.gz", hash = "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433"}, ] "boolean.py" = [ {file = "boolean.py-3.8-py2.py3-none-any.whl", hash = "sha256:d75da0fd0354425fa64f6bbc6cec6ae1485d0eec3447b73187ff8cbf9b572e26"}, @@ -2287,9 +2435,9 @@ celery = [ {file = "celery-5.0.5-py3-none-any.whl", hash = "sha256:5e8d364e058554e83bbb116e8377d90c79be254785f357cb2cec026e79febe13"}, {file = "celery-5.0.5.tar.gz", hash = "sha256:f4efebe6f8629b0da2b8e529424de376494f5b7a743c321c8a2ddc2b1414921c"}, ] -celery-haystack = [ - {file = "celery-haystack-0.10.tar.gz", hash = "sha256:b6e2a3c70071bef0838ca1a7d9f14fae6c2ecf385704092e59b82147a1ee552e"}, - {file = "celery_haystack-0.10-py2.py3-none-any.whl", hash = "sha256:ec1f39050661e033f554de99cb9393c2e94427667ff5401f16393b2a68f888fc"}, +celery-haystack-ng = [ + {file = "celery-haystack-ng-0.20.post2.tar.gz", hash = "sha256:d2e077851f13dddc36fc86134c7c8a937e46ae75e576eb8e77e03b03977fc7bb"}, + {file = "celery_haystack_ng-0.20.post2-py2.py3-none-any.whl", hash = "sha256:a13e00f2c29411b06c6cdf59ad6a90b6c158e3384e7ec6d6d64f6a69e8ff299a"}, ] celery-progress = [ {file = "celery-progress-0.0.14.tar.gz", hash = "sha256:002ead0d3fa3602bd74cf328206b8e2352994ab599711dc20058a5cf2b4db2d1"}, @@ -2330,74 +2478,78 @@ configobj = [ {file = "configobj-5.0.6.tar.gz", hash = "sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902"}, ] coverage = [ - {file = "coverage-5.3.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fabeeb121735d47d8eab8671b6b031ce08514c86b7ad8f7d5490a7b6dcd6267d"}, - {file = "coverage-5.3.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:7e4d159021c2029b958b2363abec4a11db0ce8cd43abb0d9ce44284cb97217e7"}, - {file = "coverage-5.3.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:378ac77af41350a8c6b8801a66021b52da8a05fd77e578b7380e876c0ce4f528"}, - {file = "coverage-5.3.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e448f56cfeae7b1b3b5bcd99bb377cde7c4eb1970a525c770720a352bc4c8044"}, - {file = "coverage-5.3.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:cc44e3545d908ecf3e5773266c487ad1877be718d9dc65fc7eb6e7d14960985b"}, - {file = "coverage-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:08b3ba72bd981531fd557f67beee376d6700fba183b167857038997ba30dd297"}, - {file = "coverage-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:8dacc4073c359f40fcf73aede8428c35f84639baad7e1b46fce5ab7a8a7be4bb"}, - {file = "coverage-5.3.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ee2f1d1c223c3d2c24e3afbb2dd38be3f03b1a8d6a83ee3d9eb8c36a52bee899"}, - {file = "coverage-5.3.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9a9d4ff06804920388aab69c5ea8a77525cf165356db70131616acd269e19b36"}, - {file = "coverage-5.3.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:782a5c7df9f91979a7a21792e09b34a658058896628217ae6362088b123c8500"}, - {file = "coverage-5.3.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:fda29412a66099af6d6de0baa6bd7c52674de177ec2ad2630ca264142d69c6c7"}, - {file = "coverage-5.3.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:f2c6888eada180814b8583c3e793f3f343a692fc802546eed45f40a001b1169f"}, - {file = "coverage-5.3.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8f33d1156241c43755137288dea619105477961cfa7e47f48dbf96bc2c30720b"}, - {file = "coverage-5.3.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b239711e774c8eb910e9b1ac719f02f5ae4bf35fa0420f438cdc3a7e4e7dd6ec"}, - {file = "coverage-5.3.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:f54de00baf200b4539a5a092a759f000b5f45fd226d6d25a76b0dff71177a714"}, - {file = "coverage-5.3.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:be0416074d7f253865bb67630cf7210cbc14eb05f4099cc0f82430135aaa7a3b"}, - {file = "coverage-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:c46643970dff9f5c976c6512fd35768c4a3819f01f61169d8cdac3f9290903b7"}, - {file = "coverage-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9a4f66259bdd6964d8cf26142733c81fb562252db74ea367d9beb4f815478e72"}, - {file = "coverage-5.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c6e5174f8ca585755988bc278c8bb5d02d9dc2e971591ef4a1baabdf2d99589b"}, - {file = "coverage-5.3.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3911c2ef96e5ddc748a3c8b4702c61986628bb719b8378bf1e4a6184bbd48fe4"}, - {file = "coverage-5.3.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c5ec71fd4a43b6d84ddb88c1df94572479d9a26ef3f150cef3dacefecf888105"}, - {file = "coverage-5.3.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f51dbba78d68a44e99d484ca8c8f604f17e957c1ca09c3ebc2c7e3bbd9ba0448"}, - {file = "coverage-5.3.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a2070c5affdb3a5e751f24208c5c4f3d5f008fa04d28731416e023c93b275277"}, - {file = "coverage-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:535dc1e6e68fad5355f9984d5637c33badbdc987b0c0d303ee95a6c979c9516f"}, - {file = "coverage-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a4857f7e2bc6921dbd487c5c88b84f5633de3e7d416c4dc0bb70256775551a6c"}, - {file = "coverage-5.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fac3c432851038b3e6afe086f777732bcf7f6ebbfd90951fa04ee53db6d0bcdd"}, - {file = "coverage-5.3.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cd556c79ad665faeae28020a0ab3bda6cd47d94bec48e36970719b0b86e4dcf4"}, - {file = "coverage-5.3.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a66ca3bdf21c653e47f726ca57f46ba7fc1f260ad99ba783acc3e58e3ebdb9ff"}, - {file = "coverage-5.3.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ab110c48bc3d97b4d19af41865e14531f300b482da21783fdaacd159251890e8"}, - {file = "coverage-5.3.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e52d3d95df81c8f6b2a1685aabffadf2d2d9ad97203a40f8d61e51b70f191e4e"}, - {file = "coverage-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:fa10fee7e32213f5c7b0d6428ea92e3a3fdd6d725590238a3f92c0de1c78b9d2"}, - {file = "coverage-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ce6f3a147b4b1a8b09aae48517ae91139b1b010c5f36423fa2b866a8b23df879"}, - {file = "coverage-5.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:93a280c9eb736a0dcca19296f3c30c720cb41a71b1f9e617f341f0a8e791a69b"}, - {file = "coverage-5.3.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3102bb2c206700a7d28181dbe04d66b30780cde1d1c02c5f3c165cf3d2489497"}, - {file = "coverage-5.3.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8ffd4b204d7de77b5dd558cdff986a8274796a1e57813ed005b33fd97e29f059"}, - {file = "coverage-5.3.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a607ae05b6c96057ba86c811d9c43423f35e03874ffb03fbdcd45e0637e8b631"}, - {file = "coverage-5.3.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:3a3c3f8863255f3c31db3889f8055989527173ef6192a283eb6f4db3c579d830"}, - {file = "coverage-5.3.1-cp38-cp38-win32.whl", hash = "sha256:ff1330e8bc996570221b450e2d539134baa9465f5cb98aff0e0f73f34172e0ae"}, - {file = "coverage-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:3498b27d8236057def41de3585f317abae235dd3a11d33e01736ffedb2ef8606"}, - {file = "coverage-5.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ceb499d2b3d1d7b7ba23abe8bf26df5f06ba8c71127f188333dddcf356b4b63f"}, - {file = "coverage-5.3.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:3b14b1da110ea50c8bcbadc3b82c3933974dbeea1832e814aab93ca1163cd4c1"}, - {file = "coverage-5.3.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:76b2775dda7e78680d688daabcb485dc87cf5e3184a0b3e012e1d40e38527cc8"}, - {file = "coverage-5.3.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:cef06fb382557f66d81d804230c11ab292d94b840b3cb7bf4450778377b592f4"}, - {file = "coverage-5.3.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f61319e33222591f885c598e3e24f6a4be3533c1d70c19e0dc59e83a71ce27d"}, - {file = "coverage-5.3.1-cp39-cp39-win32.whl", hash = "sha256:cc6f8246e74dd210d7e2b56c76ceaba1cc52b025cd75dbe96eb48791e0250e98"}, - {file = "coverage-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:2757fa64e11ec12220968f65d086b7a29b6583d16e9a544c889b22ba98555ef1"}, - {file = "coverage-5.3.1-pp36-none-any.whl", hash = "sha256:723d22d324e7997a651478e9c5a3120a0ecbc9a7e94071f7e1954562a8806cf3"}, - {file = "coverage-5.3.1-pp37-none-any.whl", hash = "sha256:c89b558f8a9a5a6f2cfc923c304d49f0ce629c3bd85cb442ca258ec20366394c"}, - {file = "coverage-5.3.1.tar.gz", hash = "sha256:38f16b1317b8dd82df67ed5daa5f5e7c959e46579840d77a67a4ceb9cef0a50b"}, + {file = "coverage-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135"}, + {file = "coverage-5.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c"}, + {file = "coverage-5.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44"}, + {file = "coverage-5.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3"}, + {file = "coverage-5.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9"}, + {file = "coverage-5.4-cp27-cp27m-win32.whl", hash = "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1"}, + {file = "coverage-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19"}, + {file = "coverage-5.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247"}, + {file = "coverage-5.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339"}, + {file = "coverage-5.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337"}, + {file = "coverage-5.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3"}, + {file = "coverage-5.4-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4"}, + {file = "coverage-5.4-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c"}, + {file = "coverage-5.4-cp35-cp35m-win32.whl", hash = "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f"}, + {file = "coverage-5.4-cp35-cp35m-win_amd64.whl", hash = "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66"}, + {file = "coverage-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d"}, + {file = "coverage-5.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b"}, + {file = "coverage-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9"}, + {file = "coverage-5.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af"}, + {file = "coverage-5.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5"}, + {file = "coverage-5.4-cp36-cp36m-win32.whl", hash = "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec"}, + {file = "coverage-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9"}, + {file = "coverage-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90"}, + {file = "coverage-5.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc"}, + {file = "coverage-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37"}, + {file = "coverage-5.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409"}, + {file = "coverage-5.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb"}, + {file = "coverage-5.4-cp37-cp37m-win32.whl", hash = "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a"}, + {file = "coverage-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22"}, + {file = "coverage-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f"}, + {file = "coverage-5.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3"}, + {file = "coverage-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"}, + {file = "coverage-5.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c"}, + {file = "coverage-5.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994"}, + {file = "coverage-5.4-cp38-cp38-win32.whl", hash = "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39"}, + {file = "coverage-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7"}, + {file = "coverage-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c"}, + {file = "coverage-5.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3"}, + {file = "coverage-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde"}, + {file = "coverage-5.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f"}, + {file = "coverage-5.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f"}, + {file = "coverage-5.4-cp39-cp39-win32.whl", hash = "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880"}, + {file = "coverage-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345"}, + {file = "coverage-5.4-pp36-none-any.whl", hash = "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f"}, + {file = "coverage-5.4-pp37-none-any.whl", hash = "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b"}, + {file = "coverage-5.4.tar.gz", hash = "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca"}, +] +decorator = [ + {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, + {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, ] dj-database-url = [ {file = "dj-database-url-0.5.0.tar.gz", hash = "sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163"}, {file = "dj_database_url-0.5.0-py2.py3-none-any.whl", hash = "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9"}, ] django = [ - {file = "Django-3.1.5-py3-none-any.whl", hash = "sha256:efa2ab96b33b20c2182db93147a0c3cd7769d418926f9e9f140a60dca7c64ca9"}, - {file = "Django-3.1.5.tar.gz", hash = "sha256:2d78425ba74c7a1a74b196058b261b9733a8570782f4e2828974777ccca7edf7"}, + {file = "Django-3.1.6-py3-none-any.whl", hash = "sha256:169e2e7b4839a7910b393eec127fd7cbae62e80fa55f89c6510426abf673fe5f"}, + {file = "Django-3.1.6.tar.gz", hash = "sha256:c6c0462b8b361f8691171af1fb87eceb4442da28477e12200c40420176206ba7"}, ] django-any-js = [ - {file = "django-any-js-1.0.3.post0.tar.gz", hash = "sha256:1da88b44b861b0f54f6b8ea0eb4c7c4fa1a5772e9a4320532cd4e0871a4e23f7"}, + {file = "django-any-js-1.0.3.post1.tar.gz", hash = "sha256:32306643d4989b3cdbbf6a87bb43ca4d5ca35863c96ad96a8bc0d50bcf9d4ab4"}, ] django-appconf = [ {file = "django-appconf-1.0.4.tar.gz", hash = "sha256:be58deb54a43d77d2e1621fe59f787681376d3cd0b8bd8e4758ef6c3a6453380"}, {file = "django_appconf-1.0.4-py2.py3-none-any.whl", hash = "sha256:1b1d0e1069c843ebe8ae5aa48ec52403b1440402b320c3e3a206a0907e97bb06"}, ] django-auth-ldap = [ - {file = "django-auth-ldap-2.2.0.tar.gz", hash = "sha256:11af1773b08613339d2c3a0cec1308a4d563518f17b1719c3759994d0b4d04bf"}, - {file = "django_auth_ldap-2.2.0-py3-none-any.whl", hash = "sha256:0ed2d88d81c39be915a9ab53b97ec0a33a3d16055518ab4c9bcffe8236d40370"}, + {file = "django-auth-ldap-2.3.0.tar.gz", hash = "sha256:5894317122a086c9955ed366562869a81459cf6b663636b152857bb5d3a0a3b7"}, + {file = "django_auth_ldap-2.3.0-py3-none-any.whl", hash = "sha256:cbbb476eff2504b5ab4fdf1fa92d93d2d3408fd9c8bc0c426169d987d0733153"}, ] django-bleach = [ {file = "django-bleach-0.6.1.tar.gz", hash = "sha256:674709c26040618aff0741ce8261fd151e5ead405bd50568c2034662d69daac3"}, @@ -2446,6 +2598,10 @@ django-dynamic-preferences = [ {file = "django-dynamic-preferences-1.10.1.tar.gz", hash = "sha256:e4b2bb7b2563c5064ba56dd76441c77e06b850ff1466a386a1cd308909a6c7de"}, {file = "django_dynamic_preferences-1.10.1-py2.py3-none-any.whl", hash = "sha256:9419fa925fd2cbb665269ae72059eb3058bf080913d853419b827e4e7a141902"}, ] +django-extensions = [ + {file = "django-extensions-3.1.1.tar.gz", hash = "sha256:674ad4c3b1587a884881824f40212d51829e662e52f85b012cd83d83fe1271d9"}, + {file = "django_extensions-3.1.1-py3-none-any.whl", hash = "sha256:9507f8761ee760748938fd8af766d0608fb2738cf368adfa1b2451f61c15ae35"}, +] django-favicon-plus-reloaded = [ {file = "django-favicon-plus-reloaded-1.0.4.tar.gz", hash = "sha256:90c761c636a338e6e9fb1d086649d82095085f92cff816c9cf074607f28c85a5"}, {file = "django_favicon_plus_reloaded-1.0.4-py3-none-any.whl", hash = "sha256:26e4316d41328a61ced52c7fc0ead795f0eb194d6a30311c34a9833c6fe30a7c"}, @@ -2475,7 +2631,7 @@ django-health-check = [ {file = "django_health_check-3.16.2-py2.py3-none-any.whl", hash = "sha256:aec4dd5cfb8333071f535c3611ef805ec2012af9568cf984be351ff1bd4020be"}, ] django-impersonate = [ - {file = "django-impersonate-1.7.2.tar.gz", hash = "sha256:ef1f9fa3180f4d95db0abbca3403f389e901e0beb781afd0db0edface72d148d"}, + {file = "django-impersonate-1.7.3.tar.gz", hash = "sha256:282003957577c7143fe31e5861f8fffdf6fe0c25557aedb28fcf8b11474eaa23"}, ] django-ipware = [ {file = "django-ipware-3.0.2.tar.gz", hash = "sha256:c7df8e1410a8e5d6b1fbae58728402ea59950f043c3582e033e866f0f0cf5e94"}, @@ -2497,8 +2653,8 @@ django-maintenance-mode = [ {file = "django_maintenance_mode-0.15.1-py3-none-any.whl", hash = "sha256:8c45b400253076655562c99a2ffb88f8353fc1c84496c1b9de812cc8132aea6f"}, ] django-material = [ - {file = "django-material-1.7.4.tar.gz", hash = "sha256:93af86e740b6db15a3b9df913c343217b198d7342a083db694acb319b49cb2dd"}, - {file = "django_material-1.7.4-py2.py3-none-any.whl", hash = "sha256:70dcaa34b35dbc31fbdb7454c7a376358586d0f166abe15870e07e468d729425"}, + {file = "django-material-1.7.5.tar.gz", hash = "sha256:d0df25b1d3ff629a4dfe2bc869550b25289f556940b45fd6d7c4897859446491"}, + {file = "django_material-1.7.5-py2.py3-none-any.whl", hash = "sha256:141bdd1b3ded91be8c77f6de687523d63df986a559ec3eb82cd33f4af7d5983b"}, ] django-menu-generator-ng = [ {file = "django-menu-generator-ng-1.2.1.tar.gz", hash = "sha256:06097f6611913a0770d633b6fc02cc83af1d427cc42a4048ceefe5f3a0f9d3ab"}, @@ -2592,8 +2748,8 @@ dynaconf = [ {file = "dynaconf-3.1.2.tar.gz", hash = "sha256:9b34ab2f811a81755f5eb4beac77a69e1e0887528c7e37fc4bc83fed52dcf502"}, ] faker = [ - {file = "Faker-5.7.0-py3-none-any.whl", hash = "sha256:dd98418eceb374c475e85bd49ae826a9b304dabcd794640f35a1d185f4767dd9"}, - {file = "Faker-5.7.0.tar.gz", hash = "sha256:1a40c9696830342e67d30057753323478b848e89334e97e0a64b442b18ae7ece"}, + {file = "Faker-6.1.1-py3-none-any.whl", hash = "sha256:bf2a9b3f8d00a5dada61fc4a3f80fe0d6795c7f02a138a7d2ef2db5817c7d017"}, + {file = "Faker-6.1.1.tar.gz", hash = "sha256:d4aecdb877519d06c2fdc01ffc5ecf70658981acf5e13cd07ded9892994ef5c6"}, ] flake8 = [ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, @@ -2641,8 +2797,8 @@ gitdb = [ {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"}, ] gitpython = [ - {file = "GitPython-3.1.12-py3-none-any.whl", hash = "sha256:867ec3dfb126aac0f8296b19fb63b8c4a399f32b4b6fafe84c4b10af5fa9f7b5"}, - {file = "GitPython-3.1.12.tar.gz", hash = "sha256:42dbefd8d9e2576c496ed0059f3103dcef7125b9ce16f9d5f9c834aed44a1dac"}, + {file = "GitPython-3.1.13-py3-none-any.whl", hash = "sha256:c5347c81d232d9b8e7f47b68a83e5dc92e7952127133c5f2df9133f2c75a1b29"}, + {file = "GitPython-3.1.13.tar.gz", hash = "sha256:8621a7e777e276a5ec838b59280ba5272dd144a18169c36c903d8b38b99f750a"}, ] html2text = [ {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"}, @@ -2664,13 +2820,25 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] +ipython = [ + {file = "ipython-7.20.0-py3-none-any.whl", hash = "sha256:1918dea4bfdc5d1a830fcfce9a710d1d809cbed123e85eab0539259cb0f56640"}, + {file = "ipython-7.20.0.tar.gz", hash = "sha256:1923af00820a8cf58e91d56b89efc59780a6e81363b94464a0f17c039dffff9e"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] isort = [ {file = "isort-5.7.0-py3-none-any.whl", hash = "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"}, {file = "isort-5.7.0.tar.gz", hash = "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e"}, ] +jedi = [ + {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, + {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, +] jinja2 = [ - {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, - {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, + {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, + {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] kombu = [ {file = "kombu-5.0.2-py2.py3-none-any.whl", hash = "sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006"}, @@ -2763,8 +2931,12 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, - {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, + {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, + {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, +] +parso = [ + {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, + {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, ] pathspec = [ {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, @@ -2777,37 +2949,41 @@ pbr = [ persisting-theory = [ {file = "persisting-theory-0.2.1.tar.gz", hash = "sha256:00ff7dcc8f481ff75c770ca5797d968e8725b6df1f77fe0cf7d20fa1e5790c0a"}, ] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] pg8000 = [ - {file = "pg8000-1.16.6-py3-none-any.whl", hash = "sha256:66fa16a402f38f8ba664206b4ba4040f24ea9641c4205b2b96a1ff3a613de3be"}, - {file = "pg8000-1.16.6.tar.gz", hash = "sha256:8fc1e6a62ccb7c9830f1e7e9288e2d20eaf373cc8875b5c55b7d5d9b7717be91"}, + {file = "pg8000-1.17.0-py3-none-any.whl", hash = "sha256:3276fe9cf38fee4fd4006c64d50fa621841b550f0f068d88b4694ee423188a5f"}, + {file = "pg8000-1.17.0.tar.gz", hash = "sha256:14198c5afeb289106e40ee6e5e4c0529c5369939f6ca588a028b371a75fe20dd"}, ] phonenumbers = [ - {file = "phonenumbers-8.12.16-py2.py3-none-any.whl", hash = "sha256:56ad29025b8f885945506350b06d77afbc506c5463141d77a5df767280a7ee0b"}, - {file = "phonenumbers-8.12.16.tar.gz", hash = "sha256:a820ab08c980ef24a2d2a1ead4f8d7016fdf008e484d1aecf7ff0b32cc475e16"}, + {file = "phonenumbers-8.12.18-py2.py3-none-any.whl", hash = "sha256:f60b1cc7b424cdadf5c291ed839d1c623a46ca1e4d0a04d3e85d1fdf754c1a26"}, + {file = "phonenumbers-8.12.18.tar.gz", hash = "sha256:0aa0f5e1382d292a7ff2f8bc08673126521461c7f908e0220756449a734d8fef"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] pillow = [ {file = "Pillow-8.1.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:d355502dce85ade85a2511b40b4c61a128902f246504f7de29bbeec1ae27933a"}, {file = "Pillow-8.1.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:93a473b53cc6e0b3ce6bf51b1b95b7b1e7e6084be3a07e40f79b42e83503fbf2"}, {file = "Pillow-8.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2353834b2c49b95e1313fb34edf18fca4d57446675d05298bb694bca4b194174"}, - {file = "Pillow-8.1.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:1d208e670abfeb41b6143537a681299ef86e92d2a3dac299d3cd6830d5c7bded"}, {file = "Pillow-8.1.0-cp36-cp36m-win32.whl", hash = "sha256:dd9eef866c70d2cbbea1ae58134eaffda0d4bfea403025f4db6859724b18ab3d"}, {file = "Pillow-8.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:b09e10ec453de97f9a23a5aa5e30b334195e8d2ddd1ce76cc32e52ba63c8b31d"}, {file = "Pillow-8.1.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:b02a0b9f332086657852b1f7cb380f6a42403a6d9c42a4c34a561aa4530d5234"}, {file = "Pillow-8.1.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ca20739e303254287138234485579b28cb0d524401f83d5129b5ff9d606cb0a8"}, {file = "Pillow-8.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:604815c55fd92e735f9738f65dabf4edc3e79f88541c221d292faec1904a4b17"}, - {file = "Pillow-8.1.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cf6e33d92b1526190a1de904df21663c46a456758c0424e4f947ae9aa6088bf7"}, {file = "Pillow-8.1.0-cp37-cp37m-win32.whl", hash = "sha256:47c0d93ee9c8b181f353dbead6530b26980fe4f5485aa18be8f1fd3c3cbc685e"}, {file = "Pillow-8.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:96d4dc103d1a0fa6d47c6c55a47de5f5dafd5ef0114fa10c85a1fd8e0216284b"}, {file = "Pillow-8.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:7916cbc94f1c6b1301ac04510d0881b9e9feb20ae34094d3615a8a7c3db0dcc0"}, {file = "Pillow-8.1.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:3de6b2ee4f78c6b3d89d184ade5d8fa68af0848f9b6b6da2b9ab7943ec46971a"}, {file = "Pillow-8.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cdbbe7dff4a677fb555a54f9bc0450f2a21a93c5ba2b44e09e54fcb72d2bd13d"}, - {file = "Pillow-8.1.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f50e7a98b0453f39000619d845be8b06e611e56ee6e8186f7f60c3b1e2f0feae"}, {file = "Pillow-8.1.0-cp38-cp38-win32.whl", hash = "sha256:cb192176b477d49b0a327b2a5a4979552b7a58cd42037034316b8018ac3ebb59"}, {file = "Pillow-8.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6c5275bd82711cd3dcd0af8ce0bb99113ae8911fc2952805f1d012de7d600a4c"}, {file = "Pillow-8.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:165c88bc9d8dba670110c689e3cc5c71dbe4bfb984ffa7cbebf1fac9554071d6"}, {file = "Pillow-8.1.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:5e2fe3bb2363b862671eba632537cd3a823847db4d98be95690b7e382f3d6378"}, {file = "Pillow-8.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7612520e5e1a371d77e1d1ca3a3ee6227eef00d0a9cddb4ef7ecb0b7396eddf7"}, - {file = "Pillow-8.1.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d673c4990acd016229a5c1c4ee8a9e6d8f481b27ade5fc3d95938697fa443ce0"}, {file = "Pillow-8.1.0-cp39-cp39-win32.whl", hash = "sha256:dc577f4cfdda354db3ae37a572428a90ffdbe4e51eda7849bf442fb803f09c9b"}, {file = "Pillow-8.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:22d070ca2e60c99929ef274cfced04294d2368193e935c5d6febfd8b601bf865"}, {file = "Pillow-8.1.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:a3d3e086474ef12ef13d42e5f9b7bbf09d39cf6bd4940f982263d6954b13f6a9"}, @@ -2828,8 +3004,8 @@ prometheus-client = [ {file = "prometheus_client-0.9.0.tar.gz", hash = "sha256:9da7b32f02439d8c04f7777021c304ed51d9ec180604700c1ba72a4d44dceb03"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.14-py3-none-any.whl", hash = "sha256:c96b30925025a7635471dc083ffb6af0cc67482a00611bd81aeaeeeb7e5a5e12"}, - {file = "prompt_toolkit-3.0.14.tar.gz", hash = "sha256:7e966747c18ececaec785699626b771c1ba8344c8d31759a1915d6b12fad6525"}, + {file = "prompt_toolkit-3.0.16-py3-none-any.whl", hash = "sha256:62c811e46bd09130fb11ab759012a4ae385ce4fb2073442d1898867a824183bd"}, + {file = "prompt_toolkit-3.0.16.tar.gz", hash = "sha256:0fa02fa80363844a4ab4b8d6891f62dd0645ba672723130423ca4037b80c1974"}, ] psutil = [ {file = "psutil-5.8.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64"}, @@ -2874,10 +3050,12 @@ psycopg2 = [ {file = "psycopg2-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:56fee7f818d032f802b8eed81ef0c1232b8b42390df189cab9cfa87573fe52c5"}, {file = "psycopg2-2.8.6-cp38-cp38-win32.whl", hash = "sha256:ad2fe8a37be669082e61fb001c185ffb58867fdbb3e7a6b0b0d2ffe232353a3e"}, {file = "psycopg2-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:56007a226b8e95aa980ada7abdea6b40b75ce62a433bd27cec7a8178d57f4051"}, - {file = "psycopg2-2.8.6-cp39-cp39-win32.whl", hash = "sha256:2c93d4d16933fea5bbacbe1aaf8fa8c1348740b2e50b3735d1b0bf8154cbf0f3"}, - {file = "psycopg2-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:d5062ae50b222da28253059880a871dc87e099c25cb68acf613d9d227413d6f7"}, {file = "psycopg2-2.8.6.tar.gz", hash = "sha256:fb23f6c71107c37fd667cb4ea363ddeb936b348bbd6449278eb92c189699f543"}, ] +ptyprocess = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] py = [ {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, @@ -2917,41 +3095,36 @@ pycodestyle = [ {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, ] pycryptodome = [ - {file = "pycryptodome-3.9.9-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:5598dc6c9dbfe882904e54584322893eff185b98960bbe2cdaaa20e8a437b6e5"}, - {file = "pycryptodome-3.9.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1cfdb92dca388e27e732caa72a1cc624520fe93752a665c3b6cd8f1a91b34916"}, - {file = "pycryptodome-3.9.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f19e6ef750f677d924d9c7141f54bade3cd56695bbfd8a9ef15d0378557dfe4"}, - {file = "pycryptodome-3.9.9-cp27-cp27m-win32.whl", hash = "sha256:a3d8a9efa213be8232c59cdc6b65600276508e375e0a119d710826248fd18d37"}, - {file = "pycryptodome-3.9.9-cp27-cp27m-win_amd64.whl", hash = "sha256:50826b49fbca348a61529693b0031cdb782c39060fb9dca5ac5dff858159dc5a"}, - {file = "pycryptodome-3.9.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:19cb674df6c74a14b8b408aa30ba8a89bd1c01e23505100fb45f930fbf0ed0d9"}, - {file = "pycryptodome-3.9.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:28f75e58d02019a7edc7d4135203d2501dfc47256d175c72c9798f9a129a49a7"}, - {file = "pycryptodome-3.9.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:6d3baaf82681cfb1a842f1c8f77beac791ceedd99af911e4f5fabec32bae2259"}, - {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:946399d15eccebafc8ce0257fc4caffe383c75e6b0633509bd011e357368306c"}, - {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:eb01f9997e4d6a8ec8a1ad1f676ba5a362781ff64e8189fe2985258ba9cb9706"}, - {file = "pycryptodome-3.9.9-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:411745c6dce4eff918906eebcde78771d44795d747e194462abb120d2e537cd9"}, - {file = "pycryptodome-3.9.9-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:8f9f84059039b672a5a705b3c5aa21747867bacc30a72e28bf0d147cc8ef85ed"}, - {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:7798e73225a699651888489fbb1dbc565e03a509942a8ce6194bbe6fb582a41f"}, - {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:46e96aeb8a9ca8b1edf9b1fd0af4bf6afcf3f1ca7fa35529f5d60b98f3e4e959"}, - {file = "pycryptodome-3.9.9-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:843e5f10ecdf9d307032b8b91afe9da1d6ed5bb89d0bbec5c8dcb4ba44008e11"}, - {file = "pycryptodome-3.9.9-cp36-cp36m-win32.whl", hash = "sha256:b68794fba45bdb367eeb71249c26d23e61167510a1d0c3d6cf0f2f14636e62ee"}, - {file = "pycryptodome-3.9.9-cp36-cp36m-win_amd64.whl", hash = "sha256:60febcf5baf70c566d9d9351c47fbd8321da9a4edf2eff45c4c31c86164ca794"}, - {file = "pycryptodome-3.9.9-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:4ed27951b0a17afd287299e2206a339b5b6d12de9321e1a1575261ef9c4a851b"}, - {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9000877383e2189dafd1b2fc68c6c726eca9a3cfb6d68148fbb72ccf651959b6"}, - {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:faa682c404c218e8788c3126c9a4b8fbcc54dc245b5b6e8ea5b46f3b63bd0c84"}, - {file = "pycryptodome-3.9.9-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:62c488a21c253dadc9f731a32f0ac61e4e436d81a1ea6f7d1d9146ed4d20d6bd"}, - {file = "pycryptodome-3.9.9-cp37-cp37m-win32.whl", hash = "sha256:834b790bbb6bd18956f625af4004d9c15eed12d5186d8e57851454ae76d52215"}, - {file = "pycryptodome-3.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:70d807d11d508433daf96244ec1c64e55039e8a35931fc5ea9eee94dbe3cb6b5"}, - {file = "pycryptodome-3.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:27397aee992af69d07502126561d851ba3845aa808f0e55c71ad0efa264dd7d4"}, - {file = "pycryptodome-3.9.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d7ec2bd8f57c559dd24e71891c51c25266a8deb66fc5f02cc97c7fb593d1780a"}, - {file = "pycryptodome-3.9.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e15bde67ccb7d4417f627dd16ffe2f5a4c2941ce5278444e884cb26d73ecbc61"}, - {file = "pycryptodome-3.9.9-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:5c3c4865730dfb0263f822b966d6d58429d8b1e560d1ddae37685fd9e7c63161"}, - {file = "pycryptodome-3.9.9-cp38-cp38-win32.whl", hash = "sha256:76b1a34d74bb2c91bce460cdc74d1347592045627a955e9a252554481c17c52f"}, - {file = "pycryptodome-3.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:6e4227849e4231a3f5b35ea5bdedf9a82b3883500e5624f00a19156e9a9ef861"}, - {file = "pycryptodome-3.9.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2a68df525b387201a43b27b879ce8c08948a430e883a756d6c9e3acdaa7d7bd8"}, - {file = "pycryptodome-3.9.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:a4599c0ca0fc027c780c1c45ed996d5bef03e571470b7b1c7171ec1e1a90914c"}, - {file = "pycryptodome-3.9.9-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b4e6b269a8ddaede774e5c3adbef6bf452ee144e6db8a716d23694953348cd86"}, - {file = "pycryptodome-3.9.9-cp39-cp39-win32.whl", hash = "sha256:a199e9ca46fc6e999e5f47fce342af4b56c7de85fae893c69ab6aa17531fb1e1"}, - {file = "pycryptodome-3.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:6e89bb3826e6f84501e8e3b205c22595d0c5492c2f271cbb9ee1c48eb1866645"}, - {file = "pycryptodome-3.9.9.tar.gz", hash = "sha256:910e202a557e1131b1c1b3f17a63914d57aac55cf9fb9b51644962841c3995c4"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-win32.whl", hash = "sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9"}, + {file = "pycryptodome-3.10.1-cp27-cp27m-win_amd64.whl", hash = "sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8"}, + {file = "pycryptodome-3.10.1-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427"}, + {file = "pycryptodome-3.10.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_i686.whl", hash = "sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6"}, + {file = "pycryptodome-3.10.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7"}, + {file = "pycryptodome-3.10.1-cp35-abi3-win32.whl", hash = "sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6"}, + {file = "pycryptodome-3.10.1-cp35-abi3-win_amd64.whl", hash = "sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa"}, + {file = "pycryptodome-3.10.1-pp27-pypy_73-win32.whl", hash = "sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d"}, + {file = "pycryptodome-3.10.1-pp36-pypy36_pp73-win32.whl", hash = "sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713"}, + {file = "pycryptodome-3.10.1.tar.gz", hash = "sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673"}, ] pydocstyle = [ {file = "pydocstyle-5.1.1-py3-none-any.whl", hash = "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"}, @@ -2962,8 +3135,8 @@ pyflakes = [ {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, ] pygments = [ - {file = "Pygments-2.7.4-py3-none-any.whl", hash = "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435"}, - {file = "Pygments-2.7.4.tar.gz", hash = "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"}, + {file = "Pygments-2.8.0-py3-none-any.whl", hash = "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"}, + {file = "Pygments-2.8.0.tar.gz", hash = "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0"}, ] pyjwt = [ {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, @@ -3007,8 +3180,8 @@ python-memcached = [ {file = "python_memcached-1.59-py2.py3-none-any.whl", hash = "sha256:4dac64916871bd3550263323fc2ce18e1e439080a2d5670c594cf3118d99b594"}, ] pytz = [ - {file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"}, - {file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"}, + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, @@ -3103,29 +3276,22 @@ restructuredtext-lint = [ {file = "ruamel.yaml.clib-0.2.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b"}, - {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win32.whl", hash = "sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f"}, {file = "ruamel.yaml.clib-0.2.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988"}, - {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win32.whl", hash = "sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2"}, {file = "ruamel.yaml.clib-0.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e"}, - {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win32.whl", hash = "sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6"}, {file = "ruamel.yaml.clib-0.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99"}, - {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win32.whl", hash = "sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1"}, {file = "ruamel.yaml.clib-0.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b"}, {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a"}, {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win32.whl", hash = "sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd"}, - {file = "ruamel.yaml.clib-0.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb"}, {file = "ruamel.yaml.clib-0.2.2.tar.gz", hash = "sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7"}, ] rules = [ @@ -3156,16 +3322,16 @@ snowballstemmer = [ {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, ] soupsieve = [ - {file = "soupsieve-2.1-py3-none-any.whl", hash = "sha256:4bb21a6ee4707bf43b61230e80740e71bfe56e55d1f1f50924b087bb2975c851"}, - {file = "soupsieve-2.1.tar.gz", hash = "sha256:6dc52924dc0bc710a5d16794e6b3480b2c7c08b07729505feab2b2c16661ff6e"}, + {file = "soupsieve-2.2-py3-none-any.whl", hash = "sha256:d3a5ea5b350423f47d07639f74475afedad48cf41c0ad7a82ca13a3928af34f6"}, + {file = "soupsieve-2.2.tar.gz", hash = "sha256:407fa1e8eb3458d1b5614df51d9651a1180ea5fedf07feb46e45d7e25e6d6cdd"}, ] spdx-license-list = [ {file = "spdx_license_list-0.5.2-py3-none-any.whl", hash = "sha256:1b338470c7b403dbecceca563a316382c7977516128ca6c1e8f7078e3ed6e7b0"}, {file = "spdx_license_list-0.5.2.tar.gz", hash = "sha256:952996f72ab807972dc2278bb9b91e5294767211e51f09aad9c0e2ff5b82a31b"}, ] sphinx = [ - {file = "Sphinx-3.4.3-py3-none-any.whl", hash = "sha256:c314c857e7cd47c856d2c5adff514ac2e6495f8b8e0f886a8a37e9305dfea0d8"}, - {file = "Sphinx-3.4.3.tar.gz", hash = "sha256:41cad293f954f7d37f803d97eb184158cfd90f51195131e94875bc07cd08b93c"}, + {file = "Sphinx-3.5.1-py3-none-any.whl", hash = "sha256:e90161222e4d80ce5fc811ace7c6787a226b4f5951545f7f42acf97277bfc35c"}, + {file = "Sphinx-3.5.1.tar.gz", hash = "sha256:11d521e787d9372c289472513d807277caafb1684b33eb4f08f7574c405893a9"}, ] sphinx-autodoc-typehints = [ {file = "sphinx-autodoc-typehints-1.11.1.tar.gz", hash = "sha256:244ba6d3e2fdb854622f643c7763d6f95b6886eba24bec28e86edf205e4ddb20"}, @@ -3231,11 +3397,15 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tqdm = [ - {file = "tqdm-4.56.0-py2.py3-none-any.whl", hash = "sha256:4621f6823bab46a9cc33d48105753ccbea671b68bab2c50a9f0be23d4065cb5a"}, - {file = "tqdm-4.56.0.tar.gz", hash = "sha256:fe3d08dd00a526850568d542ff9de9bbc2a09a791da3c334f3213d8d0bbbca65"}, + {file = "tqdm-4.56.2-py2.py3-none-any.whl", hash = "sha256:a89be573bfddb81bb0b395a416d5e55e3ecc73ce95a368a4f6360bedea33195e"}, + {file = "tqdm-4.56.2.tar.gz", hash = "sha256:11d544652edbdfc9cc41aa4c8a5c166513e279f3f2d9f1a9e1c89935b51de6ff"}, +] +traitlets = [ + {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, + {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, ] twilio = [ - {file = "twilio-6.51.0.tar.gz", hash = "sha256:de98a05858e6efdf87bfa4c8f7e773adf1885cfcbb6531356840bcd17dc4c444"}, + {file = "twilio-6.52.0.tar.gz", hash = "sha256:3fbe4aa6b57f41237e3cf5b16e247ffeb1c79419137156088e377d5baf5a7bf6"}, ] typed-ast = [ {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, @@ -3275,8 +3445,8 @@ typing-extensions = [ {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, ] urllib3 = [ - {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, - {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, + {file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"}, + {file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"}, ] vine = [ {file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"}, diff --git a/pyproject.toml b/pyproject.toml index 9a388a12d9da6e4e195a175072832dc8ce7a59fc..cc3e3fe7c6b0b84438e3870728a38c4d15c2ec6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "AlekSIS-Core" -version = "2.0a4.dev0" +version = "2.0a5.dev0" packages = [ { include = "aleksis" } ] @@ -68,10 +68,10 @@ html2text = "^2020.0.0" django-ckeditor = "^6.0.0" django-js-reverse = "^0.9.1" calendarweek = "^0.4.3" -Celery = {version="^5.0.0", optional=true, extras=["django", "redis"]} -django-celery-results = {version="^2.0.0", optional=true} -django-celery-beat = {version="^2.0.0", optional=true} -django-celery-email = {version="^3.0.0", optional=true} +Celery = {version="^5.0.0", extras=["django", "redis"]} +django-celery-results = "^2.0.1" +django-celery-beat = "^2.2.0" +django-celery-email = "^3.0.0" django-jsonstore = "^0.5.0" django-polymorphic = "^3.0.0" django-colorfield = "^0.3.0" @@ -80,7 +80,7 @@ django-guardian = "^2.2.0" rules = "^2.2" django-cache-memoize = "^0.1.6" django-haystack = {version="3.0b1", allow-prereleases = true} -celery-haystack = {version="^0.10.0", optional=true} +celery-haystack-ng = "^0.20" django-dbbackup = "^3.3.0" spdx-license-list = "^0.5.0" license-expression = "^1.2" @@ -94,16 +94,17 @@ django-prometheus = "^2.1.0" importlib-metadata = {version = "^3.0.0", python = "<3.9"} django-model-utils = "^4.0.0" bs4 = "^0.0.1" +django-extensions = "^3.1.1" +ipython = "^7.20.0" [tool.poetry.extras] ldap = ["django-auth-ldap"] -celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celery-email", "celery-haystack"] [tool.poetry.dev-dependencies] aleksis-builddeps = "*" [tool.poetry.scripts] -aleksis-admin = 'aleksis.core.util.manage:aleksis_cmd' +aleksis-admin = 'aleksis.core.__main__:aleksis_cmd' [tool.black] line-length = 100 diff --git a/tox.ini b/tox.ini index f09d243fd2ddcd42c0eae1a6c298c08eb9557a13..2aed3362cf7e62c15839c37ec5fb191dcd1a2f4d 100644 --- a/tox.ini +++ b/tox.ini @@ -35,6 +35,7 @@ commands = [testenv:build] commands_pre = + poetry install poetry run sh -c "cd aleksis; aleksis-admin compilemessages" commands = poetry build