From 9a00f76d66f101dbf6edfccd04259b3f46fb2726 Mon Sep 17 00:00:00 2001 From: Arthur Deierlein Date: Tue, 11 Jul 2023 13:53:06 +0200 Subject: [PATCH] feat: added mail_parser, notifier and notify command --- api/outdated/email-templates/base.html | 17 ++ .../email-templates/notification.html | 28 +++ api/outdated/email_parser.py | 50 ++++++ .../outdated/management/commands/notify.py | 13 ++ api/outdated/outdated/models.py | 23 +++ api/outdated/outdated/notifier.py | 44 +++++ api/outdated/settings.py | 4 +- api/poetry.lock | 162 +++++++++--------- api/pyproject.toml | 1 + 9 files changed, 258 insertions(+), 84 deletions(-) create mode 100644 api/outdated/email-templates/base.html create mode 100644 api/outdated/email-templates/notification.html create mode 100644 api/outdated/email_parser.py create mode 100644 api/outdated/outdated/management/commands/notify.py create mode 100644 api/outdated/outdated/notifier.py diff --git a/api/outdated/email-templates/base.html b/api/outdated/email-templates/base.html new file mode 100644 index 00000000..ffebbd0f --- /dev/null +++ b/api/outdated/email-templates/base.html @@ -0,0 +1,17 @@ + + + + + + + + + {% block content %}{% endblock %} + + + \ No newline at end of file diff --git a/api/outdated/email-templates/notification.html b/api/outdated/email-templates/notification.html new file mode 100644 index 00000000..13f506ab --- /dev/null +++ b/api/outdated/email-templates/notification.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% block style %} +th, +td { +width: 25% text-align: center !important +} +{% endblock %} + +{% block content %} +Project: {{ project.name }} +
+Repo: {{ project.repo }} +
+{% if is_outdated %} +Your project is using some packages that are EOL. This means that they are no longer stable or supported and may have +security vulnerabilities or compatibility problems. You should update them to the next LTS version immediately to fix +these issues. Here is a list of the packages: +{% else %} +Your project is using some packages that will be EOL soon. You should consider updating them to the next LTS version as +soon as possible to avoid potential issues. Here is a list of the packages that need updating: +{% endif %} +
+{% for vd in versioned_dependencies %} +
+{{ vd.dependency }}: current LTS version {{ + vd.version }}, EOL {{vd.since_in_string}} days +{% endfor %} +{% endblock %} \ No newline at end of file diff --git a/api/outdated/email_parser.py b/api/outdated/email_parser.py new file mode 100644 index 00000000..e563ada7 --- /dev/null +++ b/api/outdated/email_parser.py @@ -0,0 +1,50 @@ +import re +from typing import Any, Union + +from django.core.mail import EmailMultiAlternatives +from django.core.mail.message import EmailMessage +from django.utils.html import strip_tags +from jinja2 import Environment, FileSystemLoader, Template + + +class EmailParser: + """Parse mail from template.""" + + def __init__( + self, + mail_kwargs: dict[str, Any], + template: Union[str, Template], + context: dict[str, Any], + ) -> None: + self.mail_kwargs = mail_kwargs + self.template = template + self.context = context + + @staticmethod + def _template_to_text(parsed_template: str) -> str: + """Parse the parsed templates html to raw text.""" + + cleaned_template = parsed_template.replace("\n", "").replace("
", "\n") + + text = strip_tags(cleaned_template) + + return re.sub( + r"[/][*] ignore [*][/].*[/][*] ignore [*][/]", + "", + text, + flags=re.DOTALL, + ).strip() + + def parse_mail(self) -> EmailMessage: + """Parse a MailMessage from the template.""" + + env = Environment(loader=FileSystemLoader("outdated/email-templates")) + template = env.get_template(self.template) + if not template: + raise ValueError("Mail template does not exist!") + body = template.render(**self.context) + message = EmailMultiAlternatives( + body=self._template_to_text(body), **self.mail_kwargs + ) + message.attach_alternative(body, "text/html") + return message diff --git a/api/outdated/outdated/management/commands/notify.py b/api/outdated/outdated/management/commands/notify.py new file mode 100644 index 00000000..b2faea49 --- /dev/null +++ b/api/outdated/outdated/management/commands/notify.py @@ -0,0 +1,13 @@ +from django.core.management.base import BaseCommand + +from outdated.outdated.models import Project +from outdated.outdated.notifier import Notifier + + +class Command(BaseCommand): + help = "Sends email notification to maintainers when project outdated/warning" + + def handle(self, *args, **options): + for project in Project.objects.all(): + Notifier(project).send() + print("Finished") diff --git a/api/outdated/outdated/models.py b/api/outdated/outdated/models.py index 3ca66708..d100d4c1 100644 --- a/api/outdated/outdated/models.py +++ b/api/outdated/outdated/models.py @@ -90,6 +90,29 @@ def __str__(self): def version(self): return f"{self.release_version.version}.{self.patch_version}" + @property + def end_of_life(self): + return self.release_version.end_of_life + + @property + def dependency(self): + return self.release_version.dependency + + @property + def status(self): + return self.release_version.status + + @property + def is_EOL(self): + return self.status == STATUS_OPTIONS["outdated"] + + @property + def since_in_string(self): + values = [(self.end_of_life, "in"), (date.today(), "since")] + if self.is_EOL: + values = values[: -1 if self.is_EOL else 1] + return f"{values[0][1]} {(values[0][0] - values[1][0]).days}" + class Project(UUIDModel): name = models.CharField(max_length=100, db_index=True) diff --git a/api/outdated/outdated/notifier.py b/api/outdated/outdated/notifier.py new file mode 100644 index 00000000..4f679be8 --- /dev/null +++ b/api/outdated/outdated/notifier.py @@ -0,0 +1,44 @@ +from datetime import date + +from outdated.email_parser import EmailParser +from outdated.outdated import models + + +class Notifier: + """Check project and send an informative mail if the status is outdated/warning.""" + + def __init__(self, project: models.Project) -> None: + self.project = project + + def send(self): + # maybe sync project before checking + if not self.project.maintainers: + return + if self.project.status in ["UP_TO_DATE", "UNDEFINED"]: + return + + dependencies = [ + version + for version in self.project.versioned_dependencies.all() + if version.status in ["OUTDATED", "WARNING"] + ] + is_outdated = self.project.status == "OUTDATED" + + subject = f"{'Your project is out of date!' if is_outdated else 'Your project will be out of date soon!'}" + context = dict( + versioned_dependencies=dependencies, + project=self.project, + is_outdated=is_outdated, + today=date.today(), + ) + email_kwargs = dict( + subject=subject, + to=[self.project.maintainers.get(is_primary=True).user.email], + cc=[ + maintainer.user.email + for maintainer in self.project.maintainers.filter(is_primary=False) + if is_outdated + ], + ) + email = EmailParser(email_kwargs, "notification.html", context).parse_mail() + return email.send() diff --git a/api/outdated/settings.py b/api/outdated/settings.py index 9a97f42d..5214102f 100644 --- a/api/outdated/settings.py +++ b/api/outdated/settings.py @@ -177,10 +177,10 @@ def default(default_dev=env.NOTSET, default_prod=env.NOTSET): SERVER_EMAIL = env.str("SERVER_EMAIL", EMAIL_HOST_USER) EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD", "") EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", False) + from_name = env.str("MAILING_FROM_NAME", "outdated") from_mail = env.str("MAILING_FROM_MAIL", "noreply@outdated.adfinis.com") -MAILING = {"from_email": from_mail, "from_name": from_name} -MAILING_SENDER = f"{from_name} <{from_mail}>" +DEFAULT_FROM_EMAIL = f"{from_name} <{from_mail}>" # Syncproject settings RELEVANT_DEPENDENCIES = [ diff --git a/api/poetry.lock b/api/poetry.lock index 7e965d0a..a27156ea 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiodns" version = "3.0.0" description = "Simple DNS resolver for asyncio" -category = "main" optional = false python-versions = "*" files = [ @@ -19,7 +18,6 @@ pycares = ">=4.0.0" name = "aiohttp" version = "3.8.4" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -130,7 +128,6 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -145,7 +142,6 @@ frozenlist = ">=1.1.0" name = "asgiref" version = "3.7.2" description = "ASGI specs, helper code, and adapters" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -160,7 +156,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "async-timeout" version = "4.0.2" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -172,7 +167,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -191,7 +185,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "black" version = "22.12.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -225,7 +218,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "brotli" version = "1.0.9" description = "Python bindings for the Brotli compression library" -category = "main" optional = false python-versions = "*" files = [ @@ -317,7 +309,6 @@ files = [ name = "certifi" version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -329,7 +320,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -406,7 +396,6 @@ pycparser = "*" name = "charset-normalizer" version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -491,7 +480,6 @@ files = [ name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -506,7 +494,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -518,7 +505,6 @@ files = [ name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -591,7 +577,6 @@ toml = ["tomli"] name = "cryptography" version = "41.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -633,7 +618,6 @@ test-randomorder = ["pytest-randomly"] name = "django" version = "4.1.9" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -654,7 +638,6 @@ bcrypt = ["bcrypt"] name = "django-environ" version = "0.9.0" description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application." -category = "main" optional = false python-versions = ">=3.4,<4" files = [ @@ -663,15 +646,14 @@ files = [ ] [package.extras] -develop = ["coverage[toml] (>=5.0a4)", "furo (>=2021.8.17b43,<2021.9.0)", "pytest (>=4.6.11)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] -docs = ["furo (>=2021.8.17b43,<2021.9.0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] +develop = ["coverage[toml] (>=5.0a4)", "furo (>=2021.8.17b43,<2021.9.dev0)", "pytest (>=4.6.11)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] +docs = ["furo (>=2021.8.17b43,<2021.9.dev0)", "sphinx (>=3.5.0)", "sphinx-notfound-page"] testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)"] [[package]] name = "django-filter" version = "22.1" description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -686,7 +668,6 @@ Django = ">=3.2" name = "djangorestframework" version = "3.14.0" description = "Web APIs for Django, made easy." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -702,7 +683,6 @@ pytz = "*" name = "djangorestframework-jsonapi" version = "6.0.0" description = "A Django REST framework API adapter for the JSON:API spec." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -724,7 +704,6 @@ openapi = ["pyyaml (>=5.4)", "uritemplate (>=3.0.1)"] name = "factory-boy" version = "3.2.1" description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -743,7 +722,6 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] name = "faker" version = "18.10.1" description = "Faker is a Python package that generates fake data for you." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -758,7 +736,6 @@ python-dateutil = ">=2.4" name = "fancycompleter" version = "0.9.1" description = "colorful TAB completion for Python prompt" -category = "dev" optional = false python-versions = "*" files = [ @@ -774,7 +751,6 @@ pyrepl = ">=0.8.2" name = "flake8" version = "5.0.0" description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -791,7 +767,6 @@ pyflakes = ">=2.5.0,<2.6.0" name = "flake8-bugbear" version = "23.1.20" description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -810,7 +785,6 @@ dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "pytest", name = "flake8-debugger" version = "4.1.2" description = "ipdb/pdb statement checker plugin for flake8" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -826,7 +800,6 @@ pycodestyle = "*" name = "flake8-docstrings" version = "1.6.0" description = "Extension for flake8 which uses pydocstyle to check docstrings" -category = "dev" optional = false python-versions = "*" files = [ @@ -842,7 +815,6 @@ pydocstyle = ">=2.1" name = "flake8-isort" version = "5.0.3" description = "flake8 plugin that integrates isort ." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -861,7 +833,6 @@ test = ["pytest"] name = "flake8-string-format" version = "0.3.0" description = "string format checker, plugin for flake8" -category = "dev" optional = false python-versions = "*" files = [ @@ -876,7 +847,6 @@ flake8 = "*" name = "flake8-tuple" version = "0.4.1" description = "Check code for 1 element tuple." -category = "dev" optional = false python-versions = "*" files = [ @@ -892,7 +862,6 @@ six = "*" name = "frozenlist" version = "1.3.3" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -976,7 +945,6 @@ files = [ name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -997,7 +965,6 @@ tornado = ["tornado (>=0.2)"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1009,7 +976,6 @@ files = [ name = "inflection" version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1021,7 +987,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1033,7 +998,6 @@ files = [ name = "isort" version = "5.11.4" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -1047,11 +1011,27 @@ pipfile-deprecated-finder = ["pipreqs", "requirementslib"] plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] name = "josepy" version = "1.13.0" description = "JOSE protocol implementation in Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1069,11 +1049,69 @@ dev = ["pytest", "tox"] docs = ["Sphinx (>=1.0)", "sphinx-rtd-theme (>=1.0)"] tests = ["coverage (>=4.0)", "flake8 (<4)", "isort", "mypy", "pytest (>=2.8.0)", "pytest-cov", "pytest-flake8 (>=0.5)", "types-pyOpenSSL", "types-pyRFC3339", "types-requests", "types-setuptools"] +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + [[package]] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1085,7 +1123,6 @@ files = [ name = "mozilla-django-oidc" version = "3.0.0" description = "A lightweight authentication and access management library for integration with OpenID Connect enabled authentication services." -category = "main" optional = false python-versions = "*" files = [ @@ -1103,7 +1140,6 @@ requests = "*" name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1187,7 +1223,6 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1199,7 +1234,6 @@ files = [ name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1211,7 +1245,6 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1223,7 +1256,6 @@ files = [ name = "pdbpp" version = "0.10.3" description = "pdb++, a drop-in replacement for pdb" -category = "dev" optional = false python-versions = "*" files = [ @@ -1244,7 +1276,6 @@ testing = ["funcsigs", "pytest"] name = "platformdirs" version = "3.5.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1260,7 +1291,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1276,7 +1306,6 @@ testing = ["pytest", "pytest-benchmark"] name = "psycopg2-binary" version = "2.9.6" description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1348,7 +1377,6 @@ files = [ name = "pycares" version = "4.3.0" description = "Python interface for c-ares" -category = "main" optional = false python-versions = "*" files = [ @@ -1416,7 +1444,6 @@ idna = ["idna (>=2.1)"] name = "pycodestyle" version = "2.9.1" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1428,7 +1455,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1440,7 +1466,6 @@ files = [ name = "pydocstyle" version = "6.3.0" description = "Python docstring style checker" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1458,7 +1483,6 @@ toml = ["tomli (>=1.2.3)"] name = "pyflakes" version = "2.5.0" description = "passive checker of Python programs" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1470,7 +1494,6 @@ files = [ name = "pygments" version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1485,7 +1508,6 @@ plugins = ["importlib-metadata"] name = "pyopenssl" version = "23.2.0" description = "Python wrapper module around the OpenSSL library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1504,7 +1526,6 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "pyreadline" version = "2.1" description = "A python implmementation of GNU readline." -category = "dev" optional = false python-versions = "*" files = [ @@ -1515,7 +1536,6 @@ files = [ name = "pyrepl" version = "0.9.0" description = "A library for building flexible command line interfaces" -category = "dev" optional = false python-versions = "*" files = [ @@ -1526,7 +1546,6 @@ files = [ name = "pytest" version = "7.3.1" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1547,7 +1566,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1566,7 +1584,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-django" version = "4.5.2" description = "A Django plugin for pytest." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1585,7 +1602,6 @@ testing = ["Django", "django-configurations (>=2.0)"] name = "pytest-factoryboy" version = "2.5.1" description = "Factory Boy support for pytest." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1603,7 +1619,6 @@ typing_extensions = "*" name = "pytest-vcr" version = "1.0.2" description = "Plugin for managing VCR.py cassettes" -category = "dev" optional = false python-versions = "*" files = [ @@ -1619,7 +1634,6 @@ vcrpy = "*" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1634,7 +1648,6 @@ six = ">=1.5" name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -1646,7 +1659,6 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1696,7 +1708,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1718,7 +1729,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "requests-mock" version = "1.10.0" description = "Mock out responses from the requests package" -category = "dev" optional = false python-versions = "*" files = [ @@ -1738,7 +1748,6 @@ test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "tes name = "semver" version = "3.0.0" description = "Python helper for Semantic Versioning (https://semver.org)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1750,7 +1759,6 @@ files = [ name = "setuptools" version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1767,7 +1775,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1779,7 +1786,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -1791,7 +1797,6 @@ files = [ name = "sqlparse" version = "0.4.4" description = "A non-validating SQL parser." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1808,7 +1813,6 @@ test = ["pytest", "pytest-cov"] name = "typing-extensions" version = "4.6.3" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1820,7 +1824,6 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -1832,7 +1835,6 @@ files = [ name = "urllib3" version = "2.0.3" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1850,7 +1852,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "vcrpy" version = "4.3.1" description = "Automatically mock your HTTP interactions to simplify and speed up testing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1868,7 +1869,6 @@ yarl = "*" name = "wmctrl" version = "0.4" description = "A tool to programmatically control windows inside X" -category = "dev" optional = false python-versions = "*" files = [ @@ -1879,7 +1879,6 @@ files = [ name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -1964,7 +1963,6 @@ files = [ name = "yarl" version = "1.9.2" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2051,4 +2049,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "465bbe5cb514b921a005849fd3a1b5f9f0b9ba54d9a2f9fbe0f2b1b809b0c3bd" +content-hash = "950cea0c2ac9452b963b3b436057080266cf809d1911ea6100948e65d5f5a71a" diff --git a/api/pyproject.toml b/api/pyproject.toml index 9b3e3b8a..d39eaa27 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -19,6 +19,7 @@ semver = "^3.0.0" aiohttp = {extras = ["speedups"], version = "^3.8.4"} # pyaml = "^21.10.1" mozilla-django-oidc = "^3.0.0" +jinja2 = "^3.1.2" [tool.poetry.group.dev.dependencies]