aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar mbaruh <[email protected]>2022-10-07 16:14:11 +0300
committerGravatar mbaruh <[email protected]>2022-10-07 16:14:11 +0300
commita663969c8d94b6174dd872501a32e4a428fa77d4 (patch)
tree9a414ff80233047ef4d20d1d7cb17c82b45e49a4
parentBring back enabled categories (diff)
parentMerge pull request #776 from python-discord/dependabot/pip/sentry-sdk-1.9.10 (diff)
Merge branch 'main' into new-filter-schema
-rw-r--r--.coveragerc1
-rw-r--r--.github/dependabot.yml6
-rw-r--r--.github/workflows/lint-test.yaml76
-rw-r--r--.gitignore4
-rw-r--r--.pre-commit-config.yaml1
-rw-r--r--Dockerfile17
-rw-r--r--docker-compose.yml6
-rwxr-xr-xmanage.py30
-rw-r--r--poetry.lock1024
-rw-r--r--pydis_site/apps/api/__init__.py1
-rw-r--r--pydis_site/apps/api/github_utils.py209
-rw-r--r--pydis_site/apps/api/migrations/0013_specialsnake_image.py3
-rw-r--r--pydis_site/apps/api/migrations/0084_infraction_last_applied.py26
-rw-r--r--pydis_site/apps/api/migrations/0085_new_filter_schema.py (renamed from pydis_site/apps/api/migrations/0084_new_filter_schema.py)2
-rw-r--r--pydis_site/apps/api/migrations/0086_unique_constraint_filters.py (renamed from pydis_site/apps/api/migrations/0085_unique_constraint_filters.py)2
-rw-r--r--pydis_site/apps/api/models/bot/infraction.py6
-rw-r--r--pydis_site/apps/api/models/bot/message.py11
-rw-r--r--pydis_site/apps/api/serializers.py1
-rw-r--r--pydis_site/apps/api/tests/test_github_utils.py285
-rw-r--r--pydis_site/apps/api/tests/test_infractions.py15
-rw-r--r--pydis_site/apps/api/urls.py9
-rw-r--r--pydis_site/apps/api/views.py96
-rw-r--r--pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py2
-rw-r--r--pydis_site/apps/api/viewsets/bot/aoc_link.py2
-rw-r--r--pydis_site/apps/api/viewsets/bot/infraction.py19
-rw-r--r--pydis_site/apps/api/viewsets/bot/nomination.py2
-rw-r--r--pydis_site/apps/api/viewsets/bot/reminder.py2
-rw-r--r--pydis_site/apps/api/viewsets/bot/user.py2
-rw-r--r--pydis_site/apps/content/resources/guides/pydis-guides/contributing.md2
-rw-r--r--pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md2
-rw-r--r--pydis_site/apps/content/resources/guides/pydis-guides/contributing/site.md2
-rw-r--r--pydis_site/apps/content/resources/guides/pydis-guides/contributing/style-guide.md3
-rw-r--r--pydis_site/apps/content/tests/test_views.py2
-rw-r--r--pydis_site/apps/home/tests/test_repodata_helpers.py6
-rw-r--r--pydis_site/apps/redirect/urls.py5
-rw-r--r--pydis_site/settings.py68
-rw-r--r--pydis_site/templates/events/index.html4
-rw-r--r--pydis_site/templates/events/pages/code-jams/9/_index.html28
-rw-r--r--pydis_site/templates/events/pages/code-jams/9/frameworks.html40
-rw-r--r--pydis_site/templates/events/pages/code-jams/9/rules.html13
-rw-r--r--pydis_site/templates/events/pages/code-jams/_index.html6
-rw-r--r--pydis_site/templates/events/sidebar/code-jams/previous-code-jams.html1
-rw-r--r--pydis_site/templates/home/index.html4
-rw-r--r--pyproject.toml67
-rw-r--r--static-builds/README.md24
-rw-r--r--static-builds/netlify_build.py133
46 files changed, 1527 insertions, 743 deletions
diff --git a/.coveragerc b/.coveragerc
index b4a9bbe4..039654db 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -2,7 +2,6 @@
branch = true
source =
pydis_site
- pydis_site/apps/admin
pydis_site/apps/api
pydis_site/apps/home
pydis_site/apps/staff
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..b38df29f
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "daily"
diff --git a/.github/workflows/lint-test.yaml b/.github/workflows/lint-test.yaml
index f97cd758..f82e1d4f 100644
--- a/.github/workflows/lint-test.yaml
+++ b/.github/workflows/lint-test.yaml
@@ -10,76 +10,26 @@ on:
jobs:
lint-test:
runs-on: ubuntu-latest
- env:
- # Configure pip to cache dependencies and do a user install
- PIP_NO_CACHE_DIR: false
- PIP_USER: 1
-
- # Make sure package manager does not use virtualenv
- POETRY_VIRTUALENVS_CREATE: false
-
- # Specify explicit paths for python dependencies and the pre-commit
- # environment so we know which directories to cache
- POETRY_CACHE_DIR: ${{ github.workspace }}/.cache/py-user-base
- PYTHONUSERBASE: ${{ github.workspace }}/.cache/py-user-base
- PRE_COMMIT_HOME: ${{ github.workspace }}/.cache/pre-commit-cache
steps:
- - name: Add custom PYTHONUSERBASE to PATH
- run: echo '${{ env.PYTHONUSERBASE }}/bin/' >> $GITHUB_PATH
-
- name: Checkout repository
uses: actions/checkout@v2
- - name: Setup python
- id: python
- uses: actions/setup-python@v2
+ - name: Install Python Dependencies
+ uses: HassanAbouelela/actions/setup-python@setup-python_v1.3.1
with:
- python-version: '3.9'
+ dev: true
+ python_version: '3.10'
# Start the database early to give it a chance to get ready before
# we start running tests.
- name: Run database using docker-compose
run: docker-compose run -d -p 7777:5432 --name pydis_web postgres
- # This step caches our Python dependencies. To make sure we
- # only restore a cache when the dependencies, the python version,
- # the runner operating system, and the dependency location haven't
- # changed, we create a cache key that is a composite of those states.
- #
- # Only when the context is exactly the same, we will restore the cache.
- - name: Python Dependency Caching
- uses: actions/cache@v2
- id: python_cache
- with:
- path: ${{ env.PYTHONUSERBASE }}
- key: "python-0-${{ runner.os }}-${{ env.PYTHONUSERBASE }}-\
- ${{ steps.python.outputs.python-version }}-\
- ${{ hashFiles('./pyproject.toml', './poetry.lock') }}"
-
- # Install our dependencies if we did not restore a dependency cache
- - name: Install dependencies using poetry
- if: steps.python_cache.outputs.cache-hit != 'true'
- run: |
- pip install poetry
- poetry install
-
- # This step caches our pre-commit environment. To make sure we
- # do create a new environment when our pre-commit setup changes,
- # we create a cache key based on relevant factors.
- - name: Pre-commit Environment Caching
- uses: actions/cache@v2
- with:
- path: ${{ env.PRE_COMMIT_HOME }}
- key: "precommit-0-${{ runner.os }}-${{ env.PRE_COMMIT_HOME }}-\
- ${{ steps.python.outputs.python-version }}-\
- ${{ hashFiles('./.pre-commit-config.yaml') }}"
-
# We will not run `flake8` here, as we will use a separate flake8
- # action. As pre-commit does not support user installs, we set
- # PIP_USER=0 to not do a user install.
+ # action.
- name: Run pre-commit hooks
- run: export PIP_USER=0; SKIP=flake8 pre-commit run --all-files
+ run: SKIP=flake8 pre-commit run --all-files
# Run flake8 and have it format the linting errors in the format of
# the GitHub Workflow command to register error annotations. This
@@ -97,20 +47,22 @@ jobs:
- name: Migrations and run tests with coverage.py
run: |
python manage.py makemigrations --check
- python manage.py migrate
coverage run manage.py test --no-input
coverage report -m
+ coverage lcov
env:
CI: True
DATABASE_URL: postgres://pysite:pysite@localhost:7777/pysite
METRICITY_DB_URL: postgres://pysite:pysite@localhost:7777/metricity
+ PYTHONWARNINGS: error
# This step will publish the coverage reports coveralls.io and
- # print a "job" link in the output of the GitHub Action
- - name: Publish coverage report to coveralls.io
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: coveralls
+ # link the report to the commit
+ - name: Publish Coverage Report
+ uses: coverallsapp/[email protected]
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ path-to-lcov: ./coverage.lcov
- name: Tear down docker-compose containers
run: docker-compose stop
diff --git a/.gitignore b/.gitignore
index 45073da5..911acdcf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@ pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
+*.lcov
htmlcov/
.tox/
.coverage
@@ -132,3 +133,6 @@ log.*
# Mac/OSX
.DS_Store
+
+# Private keys
+*.pem
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 25781752..b2a03559 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -22,4 +22,3 @@ repos:
entry: poetry run flake8
language: system
types: [python]
- require_serial: true
diff --git a/Dockerfile b/Dockerfile
index 2b039fab..454e58d5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,21 +1,14 @@
-FROM --platform=linux/amd64 python:3.9-slim-buster
+FROM ghcr.io/chrislovering/python-poetry-base:3.10-slim
# Allow service to handle stops gracefully
STOPSIGNAL SIGQUIT
-# Set pip to have cleaner logs and no saved cache
-ENV PIP_NO_CACHE_DIR=false \
- POETRY_VIRTUALENVS_CREATE=false
-
-# Install poetry
-RUN pip install -U poetry
-
# Copy the project files into working directory
WORKDIR /app
# Install project dependencies
COPY pyproject.toml poetry.lock ./
-RUN poetry install --no-dev
+RUN poetry install --without dev
# Set Git SHA environment variable
ARG git_sha="development"
@@ -34,14 +27,14 @@ RUN \
SECRET_KEY=dummy_value \
DATABASE_URL=postgres://localhost \
METRICITY_DB_URL=postgres://localhost \
- python manage.py collectstatic --noinput --clear
+ poetry run python manage.py collectstatic --noinput --clear
# Build static files if we are doing a static build
ARG STATIC_BUILD=false
RUN if [ $STATIC_BUILD = "TRUE" ] ; \
- then SECRET_KEY=dummy_value python manage.py distill-local build --traceback --force ; \
+ then SECRET_KEY=dummy_value poetry run python manage.py distill-local build --traceback --force ; \
fi
# Run web server through custom manager
-ENTRYPOINT ["python", "manage.py"]
+ENTRYPOINT ["poetry", "run", "python", "manage.py"]
CMD ["run"]
diff --git a/docker-compose.yml b/docker-compose.yml
index eb987624..a6f4fd18 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,12 +8,12 @@
# and additionally use the Django development server which is
# unsuitable for production.
-version: "3.6"
+version: "3.8"
services:
postgres:
image: postgres:13-alpine
ports:
- - "127.0.0.1:7777:5432"
+ - "7777:5432"
environment:
POSTGRES_DB: pysite
POSTGRES_PASSWORD: pysite
@@ -38,7 +38,7 @@ services:
- admin.web
- staff.web
ports:
- - "127.0.0.1:8000:8000"
+ - "8000:8000"
depends_on:
postgres:
condition: service_healthy
diff --git a/manage.py b/manage.py
index 90912da3..37fb141f 100755
--- a/manage.py
+++ b/manage.py
@@ -7,6 +7,7 @@ from pathlib import Path
import django
from django.contrib.auth import get_user_model
from django.core.management import call_command, execute_from_command_line
+from django.test.utils import ignore_warnings
DEFAULT_ENVS = {
"DJANGO_SETTINGS_MODULE": "pydis_site.settings",
@@ -95,13 +96,15 @@ class SiteManager:
name="pythondiscord.local:8000"
)
- def prepare_server(self) -> None:
- """Perform preparation tasks before running the server."""
+ def prepare_environment(self) -> None:
+ """Perform common preparation tasks."""
django.setup()
print("Applying migrations.")
call_command("migrate", verbosity=self.verbosity)
+ def prepare_server(self) -> None:
+ """Preform runserver-specific preparation tasks."""
if self.debug:
# In Production, collectstatic is ran in the Docker image
print("Collecting static files.")
@@ -121,6 +124,7 @@ class SiteManager:
# Prevent preparing twice when in dev mode due to reloader
if not self.debug or in_reloader:
+ self.prepare_environment()
self.prepare_server()
print("Starting server.")
@@ -148,6 +152,20 @@ class SiteManager:
# Run gunicorn for the production server.
gunicorn.app.wsgiapp.run()
+ def run_tests(self) -> None:
+ """Prepare and run the test suite."""
+ self.prepare_environment()
+ # The whitenoise package expects a staticfiles directory to exist during startup,
+ # else it raises a warning. This is fine under normal application, but during
+ # tests, staticfiles are not, and do not need to be generated.
+ # The following line suppresses the warning.
+ # Reference: https://github.com/evansd/whitenoise/issues/215
+ with ignore_warnings(
+ message=r"No directory at: .*staticfiles",
+ module="whitenoise.base",
+ ):
+ call_command(*sys.argv[1:])
+
def clean_up_static_files(build_folder: Path) -> None:
"""Recursively loop over the build directory and fix links."""
@@ -168,8 +186,12 @@ def clean_up_static_files(build_folder: Path) -> None:
def main() -> None:
"""Entry point for Django management script."""
# Use the custom site manager for launching the server
- if len(sys.argv) > 1 and sys.argv[1] == "run":
- SiteManager(sys.argv).run_server()
+ if len(sys.argv) > 1 and sys.argv[1] in ("run", "test"):
+ manager = SiteManager(sys.argv)
+ if sys.argv[1] == "run":
+ manager.run_server()
+ elif sys.argv[1] == "test":
+ manager.run_tests()
# Pass any others directly to standard management commands
else:
diff --git a/poetry.lock b/poetry.lock
index f6576fba..9b09a5de 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -11,8 +11,8 @@ idna = ">=2.8"
sniffio = ">=1.1"
[package.extras]
-doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
-test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
+doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
+test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
trio = ["trio (>=0.16)"]
[[package]]
@@ -24,21 +24,21 @@ optional = false
python-versions = ">=3.7"
[package.extras]
-tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
+tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
[[package]]
name = "attrs"
-version = "21.4.0"
+version = "22.1.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.5"
[package.extras]
-dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
-docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
-tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
-tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
+dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
+docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
+tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
+tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
[[package]]
name = "bandit"
@@ -55,19 +55,30 @@ PyYAML = ">=5.3.1"
stevedore = ">=1.20.0"
[package.extras]
-test = ["coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml", "beautifulsoup4 (>=4.8.0)", "pylint (==1.9.4)"]
+test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml"]
toml = ["toml"]
-yaml = ["pyyaml"]
+yaml = ["PyYAML"]
[[package]]
name = "certifi"
-version = "2022.6.15"
+version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
+name = "cffi"
+version = "1.15.1"
+description = "Foreign Function Interface for Python calling C code."
+category = "main"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
name = "cfgv"
version = "3.3.1"
description = "Validate configuration and produce human readable error messages."
@@ -77,7 +88,7 @@ python-versions = ">=3.6.1"
[[package]]
name = "charset-normalizer"
-version = "2.1.0"
+version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
@@ -96,49 +107,52 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "coverage"
-version = "5.5"
+version = "6.5.0"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
+python-versions = ">=3.7"
[package.extras]
-toml = ["toml"]
+toml = ["tomli"]
[[package]]
-name = "coveralls"
-version = "2.2.0"
-description = "Show coverage stats online via coveralls.io"
-category = "dev"
+name = "cryptography"
+version = "38.0.1"
+description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
+category = "main"
optional = false
-python-versions = ">= 3.5"
+python-versions = ">=3.6"
[package.dependencies]
-coverage = ">=4.1,<6.0"
-docopt = ">=0.6.1"
-requests = ">=1.0.0"
+cffi = ">=1.12"
[package.extras]
-yaml = ["PyYAML (>=3.10)"]
+docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
+docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
+pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
+sdist = ["setuptools-rust (>=0.11.4)"]
+ssh = ["bcrypt (>=3.1.5)"]
+test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
[[package]]
name = "distlib"
-version = "0.3.4"
+version = "0.3.6"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"
[[package]]
-name = "django"
-version = "4.0.6"
+name = "Django"
+version = "4.1.1"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
python-versions = ">=3.8"
[package.dependencies]
-asgiref = ">=3.4.1,<4"
+asgiref = ">=3.5.2,<4"
sqlparse = ">=0.2.2"
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
@@ -148,7 +162,7 @@ bcrypt = ["bcrypt"]
[[package]]
name = "django-distill"
-version = "2.9.2"
+version = "3.0.1"
description = "Static site renderer and publisher for Django."
category = "main"
optional = false
@@ -160,22 +174,27 @@ requests = "*"
[[package]]
name = "django-environ"
-version = "0.4.5"
-description = "Django-environ allows you to utilize 12factor inspired environment variables to configure your Django application."
+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 = "*"
+python-versions = ">=3.4,<4"
+
+[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"]
+testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)"]
[[package]]
name = "django-filter"
-version = "21.1"
+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.6"
+python-versions = ">=3.7"
[package.dependencies]
-Django = ">=2.2"
+Django = ">=3.2"
[[package]]
name = "django-prometheus"
@@ -201,68 +220,60 @@ Django = ">=2.0"
libsass = ">=0.19,<1.0"
[package.extras]
-dev = ["flake8 (>=3.8,<4.0)", "flake8-annotations (>=2.0,<3.0)", "flake8-bugbear (>=20.1,<21.0)", "flake8-docstrings (>=1.4,<2.0)", "flake8-import-order (>=0.18,<1.0)", "flake8-tidy-imports (>=4.0,<5.0)", "flake8-todo (>=0.7,<1.0)", "flake8-string-format (>=0.3,<1.0)", "pdoc (>=0.3,<1.0)", "pep8-naming (>=0.9,<1.0)", "pre-commit (>=2.1,<3.0)", "PyGithub (>=1.43,<2.0)", "wheel (>=0.33,<1.0)"]
+dev = ["PyGithub (>=1.43,<2.0)", "flake8 (>=3.8,<4.0)", "flake8-annotations (>=2.0,<3.0)", "flake8-bugbear (>=20.1,<21.0)", "flake8-docstrings (>=1.4,<2.0)", "flake8-import-order (>=0.18,<1.0)", "flake8-string-format (>=0.3,<1.0)", "flake8-tidy-imports (>=4.0,<5.0)", "flake8-todo (>=0.7,<1.0)", "pdoc (>=0.3,<1.0)", "pep8-naming (>=0.9,<1.0)", "pre-commit (>=2.1,<3.0)", "wheel (>=0.33,<1.0)"]
[[package]]
name = "djangorestframework"
-version = "3.13.1"
+version = "3.14.0"
description = "Web APIs for Django, made easy."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
-django = ">=2.2"
+django = ">=3.0"
pytz = "*"
[[package]]
-name = "docopt"
-version = "0.6.2"
-description = "Pythonic argument parser, that will make you smile"
-category = "dev"
-optional = false
-python-versions = "*"
-
-[[package]]
name = "filelock"
-version = "3.7.1"
+version = "3.8.0"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
-docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
-testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
+docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
+testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "flake8"
-version = "3.9.2"
+version = "5.0.4"
description = "the modular source code checker: pep8 pyflakes and co"
category = "dev"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+python-versions = ">=3.6.1"
[package.dependencies]
-mccabe = ">=0.6.0,<0.7.0"
-pycodestyle = ">=2.7.0,<2.8.0"
-pyflakes = ">=2.3.0,<2.4.0"
+mccabe = ">=0.7.0,<0.8.0"
+pycodestyle = ">=2.9.0,<2.10.0"
+pyflakes = ">=2.5.0,<2.6.0"
[[package]]
name = "flake8-annotations"
-version = "2.9.0"
+version = "2.9.1"
description = "Flake8 Type Annotation Checks"
category = "dev"
optional = false
python-versions = ">=3.7,<4.0"
[package.dependencies]
-attrs = ">=21.4,<22.0"
+attrs = ">=21.4"
flake8 = ">=3.7"
[[package]]
name = "flake8-bandit"
-version = "3.0.0"
+version = "4.1.1"
description = "Automated security testing with bandit and flake8."
category = "dev"
optional = false
@@ -270,13 +281,11 @@ python-versions = ">=3.6"
[package.dependencies]
bandit = ">=1.7.3"
-flake8 = "*"
-flake8-polyfill = "*"
-pycodestyle = "*"
+flake8 = ">=5.0.0"
[[package]]
name = "flake8-bugbear"
-version = "20.11.1"
+version = "22.9.23"
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
@@ -287,7 +296,7 @@ attrs = ">=19.2.0"
flake8 = ">=3.0.0"
[package.extras]
-dev = ["coverage", "black", "hypothesis", "hypothesmith"]
+dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"]
[[package]]
name = "flake8-docstrings"
@@ -311,17 +320,7 @@ python-versions = "*"
[package.dependencies]
pycodestyle = "*"
-
-[[package]]
-name = "flake8-polyfill"
-version = "1.0.2"
-description = "Polyfill package for Flake8 plugins"
-category = "dev"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-flake8 = "*"
+setuptools = "*"
[[package]]
name = "flake8-string-format"
@@ -368,7 +367,7 @@ python-versions = ">=3.6"
smmap = ">=3.0.1,<6"
[[package]]
-name = "gitpython"
+name = "GitPython"
version = "3.1.27"
description = "GitPython is a python library used to interact with Git repositories"
category = "dev"
@@ -380,15 +379,18 @@ gitdb = ">=4.0.1,<5"
[[package]]
name = "gunicorn"
-version = "20.0.4"
+version = "20.1.0"
description = "WSGI HTTP Server for UNIX"
category = "main"
optional = false
-python-versions = ">=3.4"
+python-versions = ">=3.5"
+
+[package.dependencies]
+setuptools = ">=3.0"
[package.extras]
-eventlet = ["eventlet (>=0.9.7)"]
-gevent = ["gevent (>=0.13)"]
+eventlet = ["eventlet (>=0.24.1)"]
+gevent = ["gevent (>=1.4.0)"]
setproctitle = ["setproctitle"]
tornado = ["tornado (>=0.2)"]
@@ -433,14 +435,14 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*"
[package.extras]
-brotli = ["brotlicffi", "brotli"]
-cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
+brotli = ["brotli", "brotlicffi"]
+cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]]
name = "identify"
-version = "2.5.1"
+version = "2.5.5"
description = "File identification library for Python"
category = "dev"
optional = false
@@ -451,29 +453,13 @@ license = ["ukkonen"]
[[package]]
name = "idna"
-version = "3.3"
+version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
-name = "importlib-metadata"
-version = "4.12.0"
-description = "Read metadata from Python packages"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-
-[package.dependencies]
-zipp = ">=0.5"
-
-[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
-perf = ["ipython"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
-
-[[package]]
name = "libsass"
version = "0.21.0"
description = "Sass for Python: A straightforward binding of libsass for Python."
@@ -485,26 +471,23 @@ python-versions = "*"
six = "*"
[[package]]
-name = "markdown"
-version = "3.3.7"
+name = "Markdown"
+version = "3.4.1"
description = "Python implementation of Markdown."
category = "main"
optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
+python-versions = ">=3.7"
[package.extras]
testing = ["coverage", "pyyaml"]
[[package]]
name = "mccabe"
-version = "0.6.1"
+version = "0.7.0"
description = "McCabe checker, plugin for flake8"
category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=3.6"
[[package]]
name = "mslex"
@@ -522,9 +505,12 @@ category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
+[package.dependencies]
+setuptools = "*"
+
[[package]]
name = "pbr"
-version = "5.9.0"
+version = "5.10.0"
description = "Python Build Reasonableness"
category = "dev"
optional = false
@@ -532,11 +518,11 @@ python-versions = ">=2.6"
[[package]]
name = "pep8-naming"
-version = "0.13.0"
+version = "0.13.2"
description = "Check PEP-8 naming conventions, plugin for flake8"
category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
[package.dependencies]
flake8 = ">=3.9.1"
@@ -550,8 +536,8 @@ optional = false
python-versions = ">=3.7"
[package.extras]
-docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
-test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
+docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
+test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
[[package]]
name = "pre-commit"
@@ -582,29 +568,37 @@ twisted = ["twisted"]
[[package]]
name = "psutil"
-version = "5.9.1"
+version = "5.9.2"
description = "Cross-platform lib for process and system monitoring in Python."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.extras]
-test = ["ipaddress", "mock", "enum34", "pywin32", "wmi"]
+test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
[[package]]
name = "psycopg2-binary"
-version = "2.8.6"
+version = "2.9.3"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
category = "main"
optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+python-versions = ">=3.6"
[[package]]
name = "pycodestyle"
-version = "2.7.0"
+version = "2.9.1"
description = "Python style guide checker"
category = "dev"
optional = false
+python-versions = ">=3.6"
+
+[[package]]
+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.*"
[[package]]
@@ -623,27 +617,45 @@ toml = ["toml"]
[[package]]
name = "pyfakefs"
-version = "4.5.6"
+version = "4.7.0"
description = "pyfakefs implements a fake file system that mocks the Python file system modules."
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[[package]]
name = "pyflakes"
-version = "2.3.1"
+version = "2.5.0"
description = "passive checker of Python programs"
category = "dev"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.6"
+
+[[package]]
+name = "PyJWT"
+version = "2.5.0"
+description = "JSON Web Token implementation in Python"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+cryptography = {version = ">=3.3.1", optional = true, markers = "extra == \"crypto\""}
+types-cryptography = {version = ">=3.3.21", optional = true, markers = "extra == \"crypto\""}
+
+[package.extras]
+crypto = ["cryptography (>=3.3.1)", "types-cryptography (>=3.3.21)"]
+dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.3.1)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "types-cryptography (>=3.3.21)", "zope.interface"]
+docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
+tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
[[package]]
name = "python-dotenv"
-version = "0.17.1"
+version = "0.21.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
[package.extras]
cli = ["click (>=5.0)"]
@@ -661,23 +673,23 @@ PyYAML = "*"
[package.extras]
docs = ["sphinx"]
-test = ["pytest", "toml", "pyaml"]
+test = ["pyaml", "pytest", "toml"]
[[package]]
name = "pytz"
-version = "2022.1"
+version = "2022.2.1"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
python-versions = "*"
[[package]]
-name = "pyyaml"
-version = "5.4.1"
+name = "PyYAML"
+version = "6.0"
description = "YAML parser and emitter for Python"
category = "main"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+python-versions = ">=3.6"
[[package]]
name = "requests"
@@ -713,7 +725,7 @@ idna2008 = ["idna"]
[[package]]
name = "sentry-sdk"
-version = "0.20.3"
+version = "1.9.10"
description = "Python client for Sentry (https://sentry.io)"
category = "main"
optional = false
@@ -721,7 +733,7 @@ python-versions = "*"
[package.dependencies]
certifi = "*"
-urllib3 = ">=1.10.0"
+urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""}
[package.extras]
aiohttp = ["aiohttp (>=3.5)"]
@@ -731,15 +743,32 @@ celery = ["celery (>=3)"]
chalice = ["chalice (>=1.16.0)"]
django = ["django (>=1.8)"]
falcon = ["falcon (>=1.4)"]
-flask = ["flask (>=0.11)", "blinker (>=1.1)"]
-pure_eval = ["pure-eval", "executing", "asttokens"]
+fastapi = ["fastapi (>=0.79.0)"]
+flask = ["blinker (>=1.1)", "flask (>=0.11)"]
+httpx = ["httpx (>=0.16.0)"]
+pure_eval = ["asttokens", "executing", "pure-eval"]
pyspark = ["pyspark (>=2.4.4)"]
+quart = ["blinker (>=1.1)", "quart (>=0.16.1)"]
rq = ["rq (>=0.6)"]
sanic = ["sanic (>=0.8)"]
sqlalchemy = ["sqlalchemy (>=1.2)"]
+starlette = ["starlette (>=0.19.1)"]
tornado = ["tornado (>=5)"]
[[package]]
+name = "setuptools"
+version = "65.4.1"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+
+[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
@@ -757,11 +786,11 @@ python-versions = ">=3.6"
[[package]]
name = "sniffio"
-version = "1.2.0"
+version = "1.3.0"
description = "Sniff out which async library your code is running under"
category = "main"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
[[package]]
name = "snowballstemmer"
@@ -773,7 +802,7 @@ python-versions = "*"
[[package]]
name = "sqlparse"
-version = "0.4.2"
+version = "0.4.3"
description = "A non-validating SQL parser."
category = "main"
optional = false
@@ -781,27 +810,28 @@ python-versions = ">=3.5"
[[package]]
name = "stevedore"
-version = "3.5.0"
+version = "4.0.0"
description = "Manage dynamic plugins for Python applications"
category = "dev"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.8"
[package.dependencies]
pbr = ">=2.0.0,<2.1.0 || >2.1.0"
[[package]]
name = "taskipy"
-version = "1.7.0"
+version = "1.10.3"
description = "tasks runner for python projects"
category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
[package.dependencies]
-mslex = ">=0.3.0,<0.4.0"
+colorama = ">=0.4.4,<0.5.0"
+mslex = {version = ">=0.3.0,<0.4.0", markers = "sys_platform == \"win32\""}
psutil = ">=5.7.2,<6.0.0"
-toml = ">=0.10.0,<0.11.0"
+tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
[[package]]
name = "toml"
@@ -812,8 +842,24 @@ optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+category = "dev"
+optional = false
+python-versions = ">=3.7"
+
+[[package]]
+name = "types-cryptography"
+version = "3.3.23"
+description = "Typing stubs for cryptography"
+category = "main"
+optional = false
+python-versions = "*"
+
+[[package]]
name = "tzdata"
-version = "2022.1"
+version = "2022.4"
description = "Provider of IANA time zone data"
category = "main"
optional = false
@@ -821,62 +867,49 @@ python-versions = ">=2"
[[package]]
name = "urllib3"
-version = "1.26.10"
+version = "1.26.12"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
[package.extras]
-brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
-secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "virtualenv"
-version = "20.15.1"
+version = "20.16.5"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+python-versions = ">=3.6"
[package.dependencies]
-distlib = ">=0.3.1,<1"
-filelock = ">=3.2,<4"
-platformdirs = ">=2,<3"
-six = ">=1.9.0,<2"
+distlib = ">=0.3.5,<1"
+filelock = ">=3.4.1,<4"
+platformdirs = ">=2.4,<3"
[package.extras]
-docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"]
-testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"]
+docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
+testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "whitenoise"
-version = "5.3.0"
+version = "6.2.0"
description = "Radically simplified static file serving for WSGI applications"
category = "main"
optional = false
-python-versions = ">=3.5, <4"
-
-[package.extras]
-brotli = ["brotli"]
-
-[[package]]
-name = "zipp"
-version = "3.8.0"
-description = "Backport of pathlib-compatible object wrapper for zip files"
-category = "main"
-optional = false
python-versions = ">=3.7"
[package.extras]
-docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
-testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
+brotli = ["Brotli"]
[metadata]
lock-version = "1.1"
-python-versions = "3.9.*"
-content-hash = "e71d10c3d478c5d99e842f4c449a093caa1d4b2d255eb0dfb19843c5265d4aca"
+python-versions = "3.10.*"
+content-hash = "75f384f1b764918d349b8927ce8ff61f7f170dc72b422e5bfe77b31d9e9d64f7"
[metadata.files]
anyio = [
@@ -888,119 +921,225 @@ asgiref = [
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
]
attrs = [
- {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
- {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
+ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
+ {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
+]
+bandit = [
+ {file = "bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"},
+ {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"},
]
-bandit = []
certifi = [
- {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
- {file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
+ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+ {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
+]
+cffi = [
+ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
+ {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
+ {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
+ {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
+ {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
+ {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
+ {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
+ {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
+ {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
+ {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
+ {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
+ {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
+ {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
+ {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
+ {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
+ {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
+ {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
+ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
+ {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
]
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
-charset-normalizer = []
+charset-normalizer = [
+ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
+ {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
+]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
coverage = [
- {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"},
- {file = "coverage-5.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b"},
- {file = "coverage-5.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669"},
- {file = "coverage-5.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90"},
- {file = "coverage-5.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c"},
- {file = "coverage-5.5-cp27-cp27m-win32.whl", hash = "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a"},
- {file = "coverage-5.5-cp27-cp27m-win_amd64.whl", hash = "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82"},
- {file = "coverage-5.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905"},
- {file = "coverage-5.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083"},
- {file = "coverage-5.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5"},
- {file = "coverage-5.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81"},
- {file = "coverage-5.5-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6"},
- {file = "coverage-5.5-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0"},
- {file = "coverage-5.5-cp310-cp310-win_amd64.whl", hash = "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae"},
- {file = "coverage-5.5-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb"},
- {file = "coverage-5.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160"},
- {file = "coverage-5.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"},
- {file = "coverage-5.5-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701"},
- {file = "coverage-5.5-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793"},
- {file = "coverage-5.5-cp35-cp35m-win32.whl", hash = "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e"},
- {file = "coverage-5.5-cp35-cp35m-win_amd64.whl", hash = "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3"},
- {file = "coverage-5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066"},
- {file = "coverage-5.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a"},
- {file = "coverage-5.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465"},
- {file = "coverage-5.5-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb"},
- {file = "coverage-5.5-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821"},
- {file = "coverage-5.5-cp36-cp36m-win32.whl", hash = "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45"},
- {file = "coverage-5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184"},
- {file = "coverage-5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a"},
- {file = "coverage-5.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53"},
- {file = "coverage-5.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d"},
- {file = "coverage-5.5-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638"},
- {file = "coverage-5.5-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3"},
- {file = "coverage-5.5-cp37-cp37m-win32.whl", hash = "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a"},
- {file = "coverage-5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a"},
- {file = "coverage-5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6"},
- {file = "coverage-5.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2"},
- {file = "coverage-5.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759"},
- {file = "coverage-5.5-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873"},
- {file = "coverage-5.5-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a"},
- {file = "coverage-5.5-cp38-cp38-win32.whl", hash = "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6"},
- {file = "coverage-5.5-cp38-cp38-win_amd64.whl", hash = "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502"},
- {file = "coverage-5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b"},
- {file = "coverage-5.5-cp39-cp39-manylinux1_i686.whl", hash = "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529"},
- {file = "coverage-5.5-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b"},
- {file = "coverage-5.5-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff"},
- {file = "coverage-5.5-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b"},
- {file = "coverage-5.5-cp39-cp39-win32.whl", hash = "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6"},
- {file = "coverage-5.5-cp39-cp39-win_amd64.whl", hash = "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03"},
- {file = "coverage-5.5-pp36-none-any.whl", hash = "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079"},
- {file = "coverage-5.5-pp37-none-any.whl", hash = "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4"},
- {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"},
-]
-coveralls = [
- {file = "coveralls-2.2.0-py2.py3-none-any.whl", hash = "sha256:2301a19500b06649d2ec4f2858f9c69638d7699a4c63027c5d53daba666147cc"},
- {file = "coveralls-2.2.0.tar.gz", hash = "sha256:b990ba1f7bc4288e63340be0433698c1efe8217f78c689d254c2540af3d38617"},
+ {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"},
+ {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"},
+ {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"},
+ {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"},
+ {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"},
+ {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"},
+ {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"},
+ {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"},
+ {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"},
+ {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"},
+ {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"},
+ {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"},
+ {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"},
+ {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"},
+ {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"},
+ {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"},
+ {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"},
+ {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"},
+ {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"},
+ {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"},
+ {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"},
+ {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"},
+ {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"},
+ {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"},
+ {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"},
+ {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"},
+ {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"},
+ {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"},
+ {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"},
+ {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"},
+ {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"},
+ {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"},
+ {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"},
+ {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"},
+ {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"},
+ {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"},
+ {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"},
+ {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"},
+ {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"},
+ {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"},
+ {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"},
+ {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"},
+ {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"},
+ {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"},
+ {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"},
+ {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"},
+ {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"},
+ {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"},
+ {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"},
+ {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"},
+]
+cryptography = [
+ {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"},
+ {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"},
+ {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"},
+ {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"},
+ {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"},
+ {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"},
+ {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"},
+ {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"},
+ {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"},
+ {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"},
+ {file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"},
+ {file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"},
+ {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"},
+ {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"},
+ {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"},
+ {file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"},
+ {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"},
+ {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"},
+ {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"},
+ {file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"},
+ {file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"},
+ {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac"},
+ {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb"},
+ {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a"},
+ {file = "cryptography-38.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b"},
+ {file = "cryptography-38.0.1.tar.gz", hash = "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7"},
]
distlib = [
- {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"},
- {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"},
+ {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
+ {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
+]
+Django = [
+ {file = "Django-4.1.1-py3-none-any.whl", hash = "sha256:acb21fac9275f9972d81c7caf5761a89ec3ea25fe74545dd26b8a48cb3a0203e"},
+ {file = "Django-4.1.1.tar.gz", hash = "sha256:a153ffd5143bf26a877bfae2f4ec736ebd8924a46600ca089ad96b54a1d4e28e"},
]
-django = []
django-distill = [
- {file = "django-distill-2.9.2.tar.gz", hash = "sha256:91d5f45c2ff78b8efd4805ff5ec17df4ba815bbf51ca12a2cea65727d2f1d42e"},
+ {file = "django-distill-3.0.1.tar.gz", hash = "sha256:8bbac5e45d2afc61cc718d587c6026267c985305f5e599465f2ebc4b0cba9ebf"},
]
django-environ = [
- {file = "django-environ-0.4.5.tar.gz", hash = "sha256:6c9d87660142608f63ec7d5ce5564c49b603ea8ff25da595fd6098f6dc82afde"},
- {file = "django_environ-0.4.5-py2.py3-none-any.whl", hash = "sha256:c57b3c11ec1f319d9474e3e5a79134f40174b17c7cc024bbb2fad84646b120c4"},
+ {file = "django-environ-0.9.0.tar.gz", hash = "sha256:bff5381533056328c9ac02f71790bd5bf1cea81b1beeb648f28b81c9e83e0a21"},
+ {file = "django_environ-0.9.0-py2.py3-none-any.whl", hash = "sha256:f21a5ef8cc603da1870bbf9a09b7e5577ab5f6da451b843dbcc721a7bca6b3d9"},
]
django-filter = [
- {file = "django-filter-21.1.tar.gz", hash = "sha256:632a251fa8f1aadb4b8cceff932bb52fe2f826dd7dfe7f3eac40e5c463d6836e"},
- {file = "django_filter-21.1-py3-none-any.whl", hash = "sha256:f4a6737a30104c98d2e2a5fb93043f36dd7978e0c7ddc92f5998e85433ea5063"},
+ {file = "django-filter-22.1.tar.gz", hash = "sha256:ed473b76e84f7e83b2511bb2050c3efb36d135207d0128dfe3ae4b36e3594ba5"},
+ {file = "django_filter-22.1-py3-none-any.whl", hash = "sha256:ed429e34760127e3520a67f415bec4c905d4649fbe45d0d6da37e6ff5e0287eb"},
]
django-prometheus = [
{file = "django-prometheus-2.2.0.tar.gz", hash = "sha256:240378a1307c408bd5fc85614a3a57f1ce633d4a222c9e291e2bbf325173b801"},
{file = "django_prometheus-2.2.0-py2.py3-none-any.whl", hash = "sha256:e6616770d8820b8834762764bf1b76ec08e1b98e72a6f359d488a2e15fe3537c"},
]
-django-simple-bulma = []
-djangorestframework = []
-docopt = [
- {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
+django-simple-bulma = [
+ {file = "django-simple-bulma-2.5.0.tar.gz", hash = "sha256:d4e9f6ea857954a9bdc7a4f16453834a578cd04da5c3a96b2a3241bfcfabead2"},
+ {file = "django_simple_bulma-2.5.0-py3-none-any.whl", hash = "sha256:c413b031494d80f674068a782440c6ec5f20a12501ee7464d6f781a5777fa89c"},
+]
+djangorestframework = [
+ {file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"},
+ {file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"},
+]
+filelock = [
+ {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"},
+ {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"},
]
-filelock = []
flake8 = [
- {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
- {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
+ {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
+ {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
]
flake8-annotations = [
- {file = "flake8-annotations-2.9.0.tar.gz", hash = "sha256:63fb3f538970b6a8dfd84125cf5af16f7b22e52d5032acb3b7eb23645ecbda9b"},
- {file = "flake8_annotations-2.9.0-py3-none-any.whl", hash = "sha256:84f46de2964cb18fccea968d9eafce7cf857e34d913d515120795b9af6498d56"},
+ {file = "flake8-annotations-2.9.1.tar.gz", hash = "sha256:11f09efb99ae63c8f9d6b492b75fe147fbc323179fddfe00b2e56eefeca42f57"},
+ {file = "flake8_annotations-2.9.1-py3-none-any.whl", hash = "sha256:a4385158a7a9fc8af1d8820a2f4c8d03387997006a83f5f8bfe5bc6085bdf88a"},
+]
+flake8-bandit = [
+ {file = "flake8_bandit-4.1.1-py3-none-any.whl", hash = "sha256:4c8a53eb48f23d4ef1e59293657181a3c989d0077c9952717e98a0eace43e06d"},
+ {file = "flake8_bandit-4.1.1.tar.gz", hash = "sha256:068e09287189cbfd7f986e92605adea2067630b75380c6b5733dab7d87f9a84e"},
]
-flake8-bandit = []
flake8-bugbear = [
- {file = "flake8-bugbear-20.11.1.tar.gz", hash = "sha256:528020129fea2dea33a466b9d64ab650aa3e5f9ffc788b70ea4bc6cf18283538"},
- {file = "flake8_bugbear-20.11.1-py36.py37.py38-none-any.whl", hash = "sha256:f35b8135ece7a014bc0aee5b5d485334ac30a6da48494998cc1fabf7ec70d703"},
+ {file = "flake8-bugbear-22.9.23.tar.gz", hash = "sha256:17b9623325e6e0dcdcc80ed9e4aa811287fcc81d7e03313b8736ea5733759937"},
+ {file = "flake8_bugbear-22.9.23-py3-none-any.whl", hash = "sha256:cd2779b2b7ada212d7a322814a1e5651f1868ab0d3f24cc9da66169ab8fda474"},
]
flake8-docstrings = [
{file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"},
@@ -1010,10 +1149,6 @@ flake8-import-order = [
{file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"},
{file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"},
]
-flake8-polyfill = [
- {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
- {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
-]
flake8-string-format = [
{file = "flake8-string-format-0.3.0.tar.gz", hash = "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2"},
{file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"},
@@ -1029,29 +1164,34 @@ gitdb = [
{file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"},
{file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"},
]
-gitpython = [
+GitPython = [
{file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"},
{file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"},
]
gunicorn = [
- {file = "gunicorn-20.0.4-py2.py3-none-any.whl", hash = "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"},
- {file = "gunicorn-20.0.4.tar.gz", hash = "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626"},
+ {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
+ {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
]
h11 = [
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
]
-httpcore = []
-httpx = []
+httpcore = [
+ {file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
+ {file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
+]
+httpx = [
+ {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
+ {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
+]
identify = [
- {file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"},
- {file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"},
+ {file = "identify-2.5.5-py2.py3-none-any.whl", hash = "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"},
+ {file = "identify-2.5.5.tar.gz", hash = "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6"},
]
idna = [
- {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
- {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
+ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
-importlib-metadata = []
libsass = [
{file = "libsass-0.21.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb"},
{file = "libsass-0.21.0-cp27-cp27m-win32.whl", hash = "sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb"},
@@ -1064,159 +1204,230 @@ libsass = [
{file = "libsass-0.21.0-cp38-abi3-macosx_12_0_arm64.whl", hash = "sha256:c9ec490609752c1d81ff6290da33485aa7cb6d7365ac665b74464c1b7d97f7da"},
{file = "libsass-0.21.0.tar.gz", hash = "sha256:d5ba529d9ce668be9380563279f3ffe988f27bc5b299c5a28453df2e0b0fbaf2"},
]
-markdown = []
+Markdown = [
+ {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"},
+ {file = "Markdown-3.4.1.tar.gz", hash = "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff"},
+]
mccabe = [
- {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
- {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
+ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
mslex = [
{file = "mslex-0.3.0-py2.py3-none-any.whl", hash = "sha256:380cb14abf8fabf40e56df5c8b21a6d533dc5cbdcfe42406bbf08dda8f42e42a"},
{file = "mslex-0.3.0.tar.gz", hash = "sha256:4a1ac3f25025cad78ad2fe499dd16d42759f7a3801645399cce5c404415daa97"},
]
-nodeenv = []
-pbr = []
-pep8-naming = []
+nodeenv = [
+ {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
+ {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
+]
+pbr = [
+ {file = "pbr-5.10.0-py2.py3-none-any.whl", hash = "sha256:da3e18aac0a3c003e9eea1a81bd23e5a3a75d745670dcf736317b7d966887fdf"},
+ {file = "pbr-5.10.0.tar.gz", hash = "sha256:cfcc4ff8e698256fc17ea3ff796478b050852585aa5bae79ecd05b2ab7b39b9a"},
+]
+pep8-naming = [
+ {file = "pep8-naming-0.13.2.tar.gz", hash = "sha256:93eef62f525fd12a6f8c98f4dcc17fa70baae2f37fa1f73bec00e3e44392fa48"},
+ {file = "pep8_naming-0.13.2-py3-none-any.whl", hash = "sha256:59e29e55c478db69cffbe14ab24b5bd2cd615c0413edf790d47d3fb7ba9a4e23"},
+]
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
]
-pre-commit = []
-prometheus-client = []
+pre-commit = [
+ {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"},
+ {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"},
+]
+prometheus-client = [
+ {file = "prometheus_client-0.14.1-py3-none-any.whl", hash = "sha256:522fded625282822a89e2773452f42df14b5a8e84a86433e3f8a189c1d54dc01"},
+ {file = "prometheus_client-0.14.1.tar.gz", hash = "sha256:5459c427624961076277fdc6dc50540e2bacb98eebde99886e59ec55ed92093a"},
+]
psutil = [
- {file = "psutil-5.9.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87"},
- {file = "psutil-5.9.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af"},
- {file = "psutil-5.9.1-cp27-cp27m-win32.whl", hash = "sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc"},
- {file = "psutil-5.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2"},
- {file = "psutil-5.9.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0"},
- {file = "psutil-5.9.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22"},
- {file = "psutil-5.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9"},
- {file = "psutil-5.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8"},
- {file = "psutil-5.9.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de"},
- {file = "psutil-5.9.1-cp310-cp310-win32.whl", hash = "sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329"},
- {file = "psutil-5.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021"},
- {file = "psutil-5.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237"},
- {file = "psutil-5.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453"},
- {file = "psutil-5.9.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685"},
- {file = "psutil-5.9.1-cp36-cp36m-win32.whl", hash = "sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36"},
- {file = "psutil-5.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d"},
- {file = "psutil-5.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc"},
- {file = "psutil-5.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676"},
- {file = "psutil-5.9.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4"},
- {file = "psutil-5.9.1-cp37-cp37m-win32.whl", hash = "sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b"},
- {file = "psutil-5.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680"},
- {file = "psutil-5.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1"},
- {file = "psutil-5.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4"},
- {file = "psutil-5.9.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b"},
- {file = "psutil-5.9.1-cp38-cp38-win32.whl", hash = "sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689"},
- {file = "psutil-5.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0"},
- {file = "psutil-5.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81"},
- {file = "psutil-5.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e"},
- {file = "psutil-5.9.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537"},
- {file = "psutil-5.9.1-cp39-cp39-win32.whl", hash = "sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574"},
- {file = "psutil-5.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5"},
- {file = "psutil-5.9.1.tar.gz", hash = "sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954"},
+ {file = "psutil-5.9.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c"},
+ {file = "psutil-5.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb"},
+ {file = "psutil-5.9.2-cp27-cp27m-win32.whl", hash = "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab"},
+ {file = "psutil-5.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf"},
+ {file = "psutil-5.9.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339"},
+ {file = "psutil-5.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84"},
+ {file = "psutil-5.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9"},
+ {file = "psutil-5.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969"},
+ {file = "psutil-5.9.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34"},
+ {file = "psutil-5.9.2-cp310-cp310-win32.whl", hash = "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85"},
+ {file = "psutil-5.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1"},
+ {file = "psutil-5.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d"},
+ {file = "psutil-5.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8"},
+ {file = "psutil-5.9.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec"},
+ {file = "psutil-5.9.2-cp36-cp36m-win32.whl", hash = "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9"},
+ {file = "psutil-5.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444"},
+ {file = "psutil-5.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32"},
+ {file = "psutil-5.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d"},
+ {file = "psutil-5.9.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727"},
+ {file = "psutil-5.9.2-cp37-cp37m-win32.whl", hash = "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f"},
+ {file = "psutil-5.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c"},
+ {file = "psutil-5.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5"},
+ {file = "psutil-5.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b"},
+ {file = "psutil-5.9.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d"},
+ {file = "psutil-5.9.2-cp38-cp38-win32.whl", hash = "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06"},
+ {file = "psutil-5.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea"},
+ {file = "psutil-5.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8"},
+ {file = "psutil-5.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97"},
+ {file = "psutil-5.9.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12"},
+ {file = "psutil-5.9.2-cp39-cp39-win32.whl", hash = "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1"},
+ {file = "psutil-5.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8"},
+ {file = "psutil-5.9.2.tar.gz", hash = "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c"},
]
psycopg2-binary = [
- {file = "psycopg2-binary-2.8.6.tar.gz", hash = "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0"},
- {file = "psycopg2_binary-2.8.6-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4"},
- {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db"},
- {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"},
- {file = "psycopg2_binary-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25"},
- {file = "psycopg2_binary-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c"},
- {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c"},
- {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1"},
- {file = "psycopg2_binary-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2"},
- {file = "psycopg2_binary-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152"},
- {file = "psycopg2_binary-2.8.6-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449"},
- {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859"},
- {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550"},
- {file = "psycopg2_binary-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd"},
- {file = "psycopg2_binary-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71"},
- {file = "psycopg2_binary-2.8.6-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4"},
- {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb"},
- {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da"},
- {file = "psycopg2_binary-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2"},
- {file = "psycopg2_binary-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a"},
- {file = "psycopg2_binary-2.8.6-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679"},
- {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf"},
- {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b"},
- {file = "psycopg2_binary-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67"},
- {file = "psycopg2_binary-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66"},
- {file = "psycopg2_binary-2.8.6-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f"},
- {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77"},
- {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"},
- {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"},
- {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"},
- {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"},
- {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"},
- {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"},
- {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"},
- {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"},
+ {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f2534ab7dc7e776a263b463a16e189eb30e85ec9bbe1bff9e78dae802608932"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-win32.whl", hash = "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e6aa71ae45f952a2205377773e76f4e3f27951df38e69a4c95440c779e013560"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b3a24a1982ae56461cc24f6680604fffa2c1b818e9dc55680da038792e004d18"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"},
]
pycodestyle = [
- {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
- {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
+ {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
+ {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
+]
+pycparser = [
+ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
+ {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
pydocstyle = [
{file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
{file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"},
]
-pyfakefs = []
+pyfakefs = [
+ {file = "pyfakefs-4.7.0-py3-none-any.whl", hash = "sha256:29203a7482b25406dd3ea41c8740be2697c6058b0f6577485c3ae9cd4c5e96cd"},
+ {file = "pyfakefs-4.7.0.tar.gz", hash = "sha256:f22d30d93d2989bf2d2c67b606a14cbab2df0be912c09dcdb590ea4931073ade"},
+]
pyflakes = [
- {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
- {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
+ {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
+ {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
+]
+PyJWT = [
+ {file = "PyJWT-2.5.0-py3-none-any.whl", hash = "sha256:8d82e7087868e94dd8d7d418e5088ce64f7daab4b36db654cbaedb46f9d1ca80"},
+ {file = "PyJWT-2.5.0.tar.gz", hash = "sha256:e77ab89480905d86998442ac5788f35333fa85f65047a534adc38edf3c88fc3b"},
]
python-dotenv = [
- {file = "python-dotenv-0.17.1.tar.gz", hash = "sha256:b1ae5e9643d5ed987fc57cc2583021e38db531946518130777734f9589b3141f"},
- {file = "python_dotenv-0.17.1-py2.py3-none-any.whl", hash = "sha256:00aa34e92d992e9f8383730816359647f358f4a3be1ba45e5a5cefd27ee91544"},
+ {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
+ {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
]
python-frontmatter = [
{file = "python-frontmatter-1.0.0.tar.gz", hash = "sha256:e98152e977225ddafea6f01f40b4b0f1de175766322004c826ca99842d19a7cd"},
{file = "python_frontmatter-1.0.0-py3-none-any.whl", hash = "sha256:766ae75f1b301ffc5fe3494339147e0fd80bc3deff3d7590a93991978b579b08"},
]
pytz = [
- {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
- {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
-]
-pyyaml = [
- {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
- {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
- {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
- {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"},
- {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"},
- {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"},
- {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347"},
- {file = "PyYAML-5.4.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541"},
- {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"},
- {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"},
- {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"},
- {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"},
- {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa"},
- {file = "PyYAML-5.4.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"},
- {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"},
- {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"},
- {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"},
- {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"},
- {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247"},
- {file = "PyYAML-5.4.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc"},
- {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"},
- {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"},
- {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"},
- {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"},
- {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122"},
- {file = "PyYAML-5.4.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6"},
- {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"},
- {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
- {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
-]
-requests = []
+ {file = "pytz-2022.2.1-py2.py3-none-any.whl", hash = "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197"},
+ {file = "pytz-2022.2.1.tar.gz", hash = "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"},
+]
+PyYAML = [
+ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
+ {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
+ {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
+ {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
+ {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
+ {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
+ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
+ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
+ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
+ {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
+ {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
+ {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
+ {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
+ {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
+ {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
+ {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
+ {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
+ {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
+ {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
+ {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
+ {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
+ {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
+ {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
+ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
+ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
+]
+requests = [
+ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
+ {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
+]
rfc3986 = [
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
]
sentry-sdk = [
- {file = "sentry-sdk-0.20.3.tar.gz", hash = "sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237"},
- {file = "sentry_sdk-0.20.3-py2.py3-none-any.whl", hash = "sha256:e75c8c58932bda8cd293ea8e4b242527129e1caaec91433d21b8b2f20fee030b"},
+ {file = "sentry-sdk-1.9.10.tar.gz", hash = "sha256:4fbace9a763285b608c06f01a807b51acb35f6059da6a01236654e08b0ee81ff"},
+ {file = "sentry_sdk-1.9.10-py2.py3-none-any.whl", hash = "sha256:2469240f6190aaebcb453033519eae69cfe8cc602065b4667e18ee14fc1e35dc"},
+]
+setuptools = [
+ {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"},
+ {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
@@ -1227,37 +1438,50 @@ smmap = [
{file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"},
]
sniffio = [
- {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
- {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
+ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
]
snowballstemmer = [
{file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
{file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
]
sqlparse = [
- {file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
- {file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
+ {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"},
+ {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"},
]
stevedore = [
- {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"},
- {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"},
+ {file = "stevedore-4.0.0-py3-none-any.whl", hash = "sha256:87e4d27fe96d0d7e4fc24f0cbe3463baae4ec51e81d95fbe60d2474636e0c7d8"},
+ {file = "stevedore-4.0.0.tar.gz", hash = "sha256:f82cc99a1ff552310d19c379827c2c64dd9f85a38bcd5559db2470161867b786"},
]
taskipy = [
- {file = "taskipy-1.7.0-py3-none-any.whl", hash = "sha256:9e284c10898e9dee01a3e72220b94b192b1daa0f560271503a6df1da53d03844"},
- {file = "taskipy-1.7.0.tar.gz", hash = "sha256:960e480b1004971e76454ecd1a0484e640744a30073a1069894a311467f85ed8"},
+ {file = "taskipy-1.10.3-py3-none-any.whl", hash = "sha256:4c0070ca53868d97989f7ab5c6f237525d52ee184f9b967576e8fe427ed9d0b8"},
+ {file = "taskipy-1.10.3.tar.gz", hash = "sha256:112beaf21e3d5569950b99162a1de003fa885fabee9e450757a6b874be914877"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
-tzdata = []
-urllib3 = []
-virtualenv = []
-whitenoise = [
- {file = "whitenoise-5.3.0-py2.py3-none-any.whl", hash = "sha256:d963ef25639d1417e8a247be36e6aedd8c7c6f0a08adcb5a89146980a96b577c"},
- {file = "whitenoise-5.3.0.tar.gz", hash = "sha256:d234b871b52271ae7ed6d9da47ffe857c76568f11dd30e28e18c5869dbd11e12"},
+tomli = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+types-cryptography = [
+ {file = "types-cryptography-3.3.23.tar.gz", hash = "sha256:b85c45fd4d3d92e8b18e9a5ee2da84517e8fff658e3ef5755c885b1c2a27c1fe"},
+ {file = "types_cryptography-3.3.23-py3-none-any.whl", hash = "sha256:913b3e66a502edbf4bfc3bb45e33ab476040c56942164a7ff37bd1f0ef8ef783"},
]
-zipp = [
- {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"},
- {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"},
+tzdata = [
+ {file = "tzdata-2022.4-py2.py3-none-any.whl", hash = "sha256:74da81ecf2b3887c94e53fc1d466d4362aaf8b26fc87cda18f22004544694583"},
+ {file = "tzdata-2022.4.tar.gz", hash = "sha256:ada9133fbd561e6ec3d1674d3fba50251636e918aa97bd59d63735bef5a513bb"},
+]
+urllib3 = [
+ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
+ {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
+]
+virtualenv = [
+ {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"},
+ {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"},
+]
+whitenoise = [
+ {file = "whitenoise-6.2.0-py3-none-any.whl", hash = "sha256:8e9c600a5c18bd17655ef668ad55b5edf6c24ce9bdca5bf607649ca4b1e8e2c2"},
+ {file = "whitenoise-6.2.0.tar.gz", hash = "sha256:8fa943c6d4cd9e27673b70c21a07b0aa120873901e099cd46cab40f7cc96d567"},
]
diff --git a/pydis_site/apps/api/__init__.py b/pydis_site/apps/api/__init__.py
index afa5b4d5..e69de29b 100644
--- a/pydis_site/apps/api/__init__.py
+++ b/pydis_site/apps/api/__init__.py
@@ -1 +0,0 @@
-default_app_config = 'pydis_site.apps.api.apps.ApiConfig'
diff --git a/pydis_site/apps/api/github_utils.py b/pydis_site/apps/api/github_utils.py
new file mode 100644
index 00000000..986c64e1
--- /dev/null
+++ b/pydis_site/apps/api/github_utils.py
@@ -0,0 +1,209 @@
+"""Utilities for working with the GitHub API."""
+import dataclasses
+import datetime
+import math
+import typing
+
+import httpx
+import jwt
+
+from pydis_site import settings
+
+MAX_RUN_TIME = datetime.timedelta(minutes=10)
+"""The maximum time allowed before an action is declared timed out."""
+ISO_FORMAT_STRING = "%Y-%m-%dT%H:%M:%SZ"
+"""The datetime string format GitHub uses."""
+
+
+class ArtifactProcessingError(Exception):
+ """Base exception for other errors related to processing a GitHub artifact."""
+
+ status: int
+
+
+class UnauthorizedError(ArtifactProcessingError):
+ """The application does not have permission to access the requested repo."""
+
+ status = 401
+
+
+class NotFoundError(ArtifactProcessingError):
+ """The requested resource could not be found."""
+
+ status = 404
+
+
+class ActionFailedError(ArtifactProcessingError):
+ """The requested workflow did not conclude successfully."""
+
+ status = 400
+
+
+class RunTimeoutError(ArtifactProcessingError):
+ """The requested workflow run was not ready in time."""
+
+ status = 408
+
+
+class RunPendingError(ArtifactProcessingError):
+ """The requested workflow run is still pending, try again later."""
+
+ status = 202
+
+
[email protected](frozen=True)
+class WorkflowRun:
+ """
+ A workflow run from the GitHub API.
+
+ https://docs.github.com/en/rest/actions/workflow-runs#get-a-workflow-run
+ """
+
+ name: str
+ head_sha: str
+ created_at: str
+ status: str
+ conclusion: str
+ artifacts_url: str
+
+ @classmethod
+ def from_raw(cls, data: dict[str, typing.Any]):
+ """Create an instance using the raw data from the API, discarding unused fields."""
+ return cls(**{
+ key.name: data[key.name] for key in dataclasses.fields(cls)
+ })
+
+
+def generate_token() -> str:
+ """
+ Generate a JWT token to access the GitHub API.
+
+ The token is valid for roughly 10 minutes after generation, before the API starts
+ returning 401s.
+
+ Refer to:
+ https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#authenticating-as-a-github-app
+ """
+ now = datetime.datetime.now()
+ return jwt.encode(
+ {
+ "iat": math.floor((now - datetime.timedelta(seconds=60)).timestamp()), # Issued at
+ "exp": math.floor((now + datetime.timedelta(minutes=9)).timestamp()), # Expires at
+ "iss": settings.GITHUB_APP_ID,
+ },
+ settings.GITHUB_APP_KEY,
+ algorithm="RS256"
+ )
+
+
+def authorize(owner: str, repo: str) -> httpx.Client:
+ """
+ Get an access token for the requested repository.
+
+ The process is roughly:
+ - GET app/installations to get a list of all app installations
+ - POST <app_access_token> to get a token to access the given app
+ - GET installation/repositories and check if the requested one is part of those
+ """
+ client = httpx.Client(
+ base_url=settings.GITHUB_API,
+ headers={"Authorization": f"bearer {generate_token()}"},
+ timeout=10,
+ )
+
+ try:
+ # Get a list of app installations we have access to
+ apps = client.get("app/installations")
+ apps.raise_for_status()
+
+ for app in apps.json():
+ # Look for an installation with the right owner
+ if app["account"]["login"] != owner:
+ continue
+
+ # Get the repositories of the specified owner
+ app_token = client.post(app["access_tokens_url"])
+ app_token.raise_for_status()
+ client.headers["Authorization"] = f"bearer {app_token.json()['token']}"
+
+ repos = client.get("installation/repositories")
+ repos.raise_for_status()
+
+ # Search for the request repository
+ for accessible_repo in repos.json()["repositories"]:
+ if accessible_repo["name"] == repo:
+ # We've found the correct repository, and it's accessible with the current auth
+ return client
+
+ raise NotFoundError(
+ "Could not find the requested repository. Make sure the application can access it."
+ )
+
+ except BaseException as e:
+ # Close the client if we encountered an unexpected exception
+ client.close()
+ raise e
+
+
+def check_run_status(run: WorkflowRun) -> str:
+ """Check if the provided run has been completed, otherwise raise an exception."""
+ created_at = datetime.datetime.strptime(run.created_at, ISO_FORMAT_STRING)
+ run_time = datetime.datetime.utcnow() - created_at
+
+ if run.status != "completed":
+ if run_time <= MAX_RUN_TIME:
+ raise RunPendingError(
+ f"The requested run is still pending. It was created "
+ f"{run_time.seconds // 60}:{run_time.seconds % 60 :>02} minutes ago."
+ )
+ else:
+ raise RunTimeoutError("The requested workflow was not ready in time.")
+
+ if run.conclusion != "success":
+ # The action failed, or did not run
+ raise ActionFailedError(f"The requested workflow ended with: {run.conclusion}")
+
+ # The requested action is ready
+ return run.artifacts_url
+
+
+def get_artifact(owner: str, repo: str, sha: str, action_name: str, artifact_name: str) -> str:
+ """Get a download URL for a build artifact."""
+ client = authorize(owner, repo)
+
+ try:
+ # Get the workflow runs for this repository
+ runs = client.get(f"/repos/{owner}/{repo}/actions/runs", params={"per_page": 100})
+ runs.raise_for_status()
+ runs = runs.json()
+
+ # Filter the runs for the one associated with the given SHA
+ for run in runs["workflow_runs"]:
+ run = WorkflowRun.from_raw(run)
+ if run.name == action_name and sha == run.head_sha:
+ break
+ else:
+ raise NotFoundError(
+ "Could not find a run matching the provided settings in the previous hundred runs."
+ )
+
+ # Check the workflow status
+ url = check_run_status(run)
+
+ # Filter the artifacts, and return the download URL
+ artifacts = client.get(url)
+ artifacts.raise_for_status()
+
+ for artifact in artifacts.json()["artifacts"]:
+ if artifact["name"] == artifact_name:
+ data = client.get(artifact["archive_download_url"])
+ if data.status_code == 302:
+ return str(data.next_request.url)
+
+ # The following line is left untested since it should in theory be impossible
+ data.raise_for_status() # pragma: no cover
+
+ raise NotFoundError("Could not find an artifact matching the provided name.")
+
+ finally:
+ client.close()
diff --git a/pydis_site/apps/api/migrations/0013_specialsnake_image.py b/pydis_site/apps/api/migrations/0013_specialsnake_image.py
index a0d0d318..8ba3432f 100644
--- a/pydis_site/apps/api/migrations/0013_specialsnake_image.py
+++ b/pydis_site/apps/api/migrations/0013_specialsnake_image.py
@@ -2,7 +2,6 @@
import datetime
from django.db import migrations, models
-from django.utils.timezone import utc
class Migration(migrations.Migration):
@@ -15,7 +14,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='specialsnake',
name='image',
- field=models.URLField(default=datetime.datetime(2018, 10, 23, 11, 51, 23, 703868, tzinfo=utc)),
+ field=models.URLField(default=datetime.datetime(2018, 10, 23, 11, 51, 23, 703868, tzinfo=datetime.timezone.utc)),
preserve_default=False,
),
]
diff --git a/pydis_site/apps/api/migrations/0084_infraction_last_applied.py b/pydis_site/apps/api/migrations/0084_infraction_last_applied.py
new file mode 100644
index 00000000..7704ddb8
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0084_infraction_last_applied.py
@@ -0,0 +1,26 @@
+# Generated by Django 4.0.6 on 2022-07-27 20:32
+
+import django.utils.timezone
+from django.db import migrations, models
+from django.apps.registry import Apps
+
+
+def set_last_applied_to_inserted_at(apps: Apps, schema_editor):
+ Infractions = apps.get_model("api", "infraction")
+ Infractions.objects.all().update(last_applied=models.F("inserted_at"))
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0083_remove_embed_validation'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='infraction',
+ name='last_applied',
+ field=models.DateTimeField(default=django.utils.timezone.now, help_text='The date and time of when this infraction was last applied.'),
+ ),
+ migrations.RunPython(set_last_applied_to_inserted_at)
+ ]
diff --git a/pydis_site/apps/api/migrations/0084_new_filter_schema.py b/pydis_site/apps/api/migrations/0085_new_filter_schema.py
index 74e1f009..d16c26ac 100644
--- a/pydis_site/apps/api/migrations/0084_new_filter_schema.py
+++ b/pydis_site/apps/api/migrations/0085_new_filter_schema.py
@@ -84,7 +84,7 @@ def forward(apps: Apps, schema_editor: BaseDatabaseSchemaEditor) -> None:
class Migration(migrations.Migration):
dependencies = [
- ('api', '0083_remove_embed_validation'),
+ ('api', '0084_infraction_last_applied'),
]
operations = [
diff --git a/pydis_site/apps/api/migrations/0085_unique_constraint_filters.py b/pydis_site/apps/api/migrations/0086_unique_constraint_filters.py
index 55ede901..8072ed2e 100644
--- a/pydis_site/apps/api/migrations/0085_unique_constraint_filters.py
+++ b/pydis_site/apps/api/migrations/0086_unique_constraint_filters.py
@@ -6,7 +6,7 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('api', '0084_new_filter_schema'),
+ ('api', '0085_new_filter_schema'),
]
operations = [
diff --git a/pydis_site/apps/api/models/bot/infraction.py b/pydis_site/apps/api/models/bot/infraction.py
index c9303024..218ee5ec 100644
--- a/pydis_site/apps/api/models/bot/infraction.py
+++ b/pydis_site/apps/api/models/bot/infraction.py
@@ -23,6 +23,12 @@ class Infraction(ModelReprMixin, models.Model):
default=timezone.now,
help_text="The date and time of the creation of this infraction."
)
+ last_applied = models.DateTimeField(
+ # This default is for backwards compatibility with bot versions
+ # that don't explicitly give a value.
+ default=timezone.now,
+ help_text="The date and time of when this infraction was last applied."
+ )
expires_at = models.DateTimeField(
null=True,
help_text=(
diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py
index bfa54721..89ae27e4 100644
--- a/pydis_site/apps/api/models/bot/message.py
+++ b/pydis_site/apps/api/models/bot/message.py
@@ -1,9 +1,8 @@
-from datetime import datetime
+import datetime
from django.contrib.postgres import fields as pgfields
from django.core.validators import MinValueValidator
from django.db import models
-from django.utils import timezone
from pydis_site.apps.api.models.bot.user import User
from pydis_site.apps.api.models.mixins import ModelReprMixin
@@ -60,11 +59,11 @@ class Message(ModelReprMixin, models.Model):
)
@property
- def timestamp(self) -> datetime:
+ def timestamp(self) -> datetime.datetime:
"""Attribute that represents the message timestamp as derived from the snowflake id."""
- tz_naive_datetime = datetime.utcfromtimestamp(((self.id >> 22) + 1420070400000) / 1000)
- tz_aware_datetime = timezone.make_aware(tz_naive_datetime, timezone=timezone.utc)
- return tz_aware_datetime
+ return datetime.datetime.utcfromtimestamp(
+ ((self.id >> 22) + 1420070400000) / 1000
+ ).replace(tzinfo=datetime.timezone.utc)
class Meta:
"""Metadata provided for Django's ORM."""
diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py
index 26bda035..7c1c107a 100644
--- a/pydis_site/apps/api/serializers.py
+++ b/pydis_site/apps/api/serializers.py
@@ -363,6 +363,7 @@ class InfractionSerializer(ModelSerializer):
fields = (
'id',
'inserted_at',
+ 'last_applied',
'expires_at',
'active',
'user',
diff --git a/pydis_site/apps/api/tests/test_github_utils.py b/pydis_site/apps/api/tests/test_github_utils.py
new file mode 100644
index 00000000..2eaf48d9
--- /dev/null
+++ b/pydis_site/apps/api/tests/test_github_utils.py
@@ -0,0 +1,285 @@
+import dataclasses
+import datetime
+import typing
+import unittest
+from unittest import mock
+
+import django.test
+import httpx
+import jwt
+import rest_framework.response
+import rest_framework.test
+from django.urls import reverse
+
+from .. import github_utils
+
+
+class GeneralUtilityTests(unittest.TestCase):
+ """Test the utility methods which do not fit in another class."""
+
+ def test_token_generation(self):
+ """Test that the a valid JWT token is generated."""
+ def encode(payload: dict, _: str, algorithm: str, *args, **kwargs) -> str:
+ """
+ Intercept the encode method.
+
+ The result is encoded with an algorithm which does not require a PEM key, as it may
+ not be available in testing environments.
+ """
+ self.assertEqual("RS256", algorithm, "The GitHub App JWT must be signed using RS256.")
+ return original_encode(
+ payload, "secret-encoding-key", *args, algorithm="HS256", **kwargs
+ )
+
+ original_encode = jwt.encode
+ with mock.patch("jwt.encode", new=encode):
+ token = github_utils.generate_token()
+ decoded = jwt.decode(token, "secret-encoding-key", algorithms=["HS256"])
+
+ delta = datetime.timedelta(minutes=10)
+ self.assertAlmostEqual(decoded["exp"] - decoded["iat"], delta.total_seconds())
+ self.assertLess(decoded["exp"], (datetime.datetime.now() + delta).timestamp())
+
+
+class CheckRunTests(unittest.TestCase):
+ """Tests the check_run_status utility."""
+
+ run_kwargs: typing.Mapping = {
+ "name": "run_name",
+ "head_sha": "sha",
+ "status": "completed",
+ "conclusion": "success",
+ "created_at": datetime.datetime.utcnow().strftime(github_utils.ISO_FORMAT_STRING),
+ "artifacts_url": "url",
+ }
+
+ def test_completed_run(self):
+ """Test that an already completed run returns the correct URL."""
+ final_url = "some_url_string_1234"
+
+ kwargs = dict(self.run_kwargs, artifacts_url=final_url)
+ result = github_utils.check_run_status(github_utils.WorkflowRun(**kwargs))
+ self.assertEqual(final_url, result)
+
+ def test_pending_run(self):
+ """Test that a pending run raises the proper exception."""
+ kwargs = dict(self.run_kwargs, status="pending")
+ with self.assertRaises(github_utils.RunPendingError):
+ github_utils.check_run_status(github_utils.WorkflowRun(**kwargs))
+
+ def test_timeout_error(self):
+ """Test that a timeout is declared after a certain duration."""
+ kwargs = dict(self.run_kwargs, status="pending")
+ # Set the creation time to well before the MAX_RUN_TIME
+ # to guarantee the right conclusion
+ kwargs["created_at"] = (
+ datetime.datetime.utcnow() - github_utils.MAX_RUN_TIME - datetime.timedelta(minutes=10)
+ ).strftime(github_utils.ISO_FORMAT_STRING)
+
+ with self.assertRaises(github_utils.RunTimeoutError):
+ github_utils.check_run_status(github_utils.WorkflowRun(**kwargs))
+
+ def test_failed_run(self):
+ """Test that a failed run raises the proper exception."""
+ kwargs = dict(self.run_kwargs, conclusion="failed")
+ with self.assertRaises(github_utils.ActionFailedError):
+ github_utils.check_run_status(github_utils.WorkflowRun(**kwargs))
+
+
+def get_response_authorize(_: httpx.Client, request: httpx.Request, **__) -> httpx.Response:
+ """
+ Helper method for the authorize tests.
+
+ Requests are intercepted before being sent out, and the appropriate responses are returned.
+ """
+ path = request.url.path
+ auth = request.headers.get("Authorization")
+
+ if request.method == "GET":
+ if path == "/app/installations":
+ if auth == "bearer JWT initial token":
+ return httpx.Response(200, request=request, json=[{
+ "account": {"login": "VALID_OWNER"},
+ "access_tokens_url": "https://example.com/ACCESS_TOKEN_URL"
+ }])
+ else:
+ return httpx.Response(
+ 401, json={"error": "auth app/installations"}, request=request
+ )
+
+ elif path == "/installation/repositories":
+ if auth == "bearer app access token":
+ return httpx.Response(200, request=request, json={
+ "repositories": [{
+ "name": "VALID_REPO"
+ }]
+ })
+ else: # pragma: no cover
+ return httpx.Response(
+ 401, json={"error": "auth installation/repositories"}, request=request
+ )
+
+ elif request.method == "POST":
+ if path == "/ACCESS_TOKEN_URL":
+ if auth == "bearer JWT initial token":
+ return httpx.Response(200, request=request, json={"token": "app access token"})
+ else: # pragma: no cover
+ return httpx.Response(401, json={"error": "auth access_token"}, request=request)
+
+ # Reaching this point means something has gone wrong
+ return httpx.Response(500, request=request) # pragma: no cover
+
+
[email protected]("httpx.Client.send", new=get_response_authorize)
[email protected](github_utils, "generate_token", new=mock.Mock(return_value="JWT initial token"))
+class AuthorizeTests(unittest.TestCase):
+ """Test the authorize utility."""
+
+ def test_invalid_apps_auth(self):
+ """Test that an exception is raised if authorization was attempted with an invalid token."""
+ with mock.patch.object(github_utils, "generate_token", return_value="Invalid token"):
+ with self.assertRaises(httpx.HTTPStatusError) as error:
+ github_utils.authorize("VALID_OWNER", "VALID_REPO")
+
+ exception: httpx.HTTPStatusError = error.exception
+ self.assertEqual(401, exception.response.status_code)
+ self.assertEqual("auth app/installations", exception.response.json()["error"])
+
+ def test_missing_repo(self):
+ """Test that an exception is raised when the selected owner or repo are not available."""
+ with self.assertRaises(github_utils.NotFoundError):
+ github_utils.authorize("INVALID_OWNER", "VALID_REPO")
+ with self.assertRaises(github_utils.NotFoundError):
+ github_utils.authorize("VALID_OWNER", "INVALID_REPO")
+
+ def test_valid_authorization(self):
+ """Test that an accessible repository can be accessed."""
+ client = github_utils.authorize("VALID_OWNER", "VALID_REPO")
+ self.assertEqual("bearer app access token", client.headers.get("Authorization"))
+
+
+class ArtifactFetcherTests(unittest.TestCase):
+ """Test the get_artifact utility."""
+
+ @staticmethod
+ def get_response_get_artifact(request: httpx.Request, **_) -> httpx.Response:
+ """
+ Helper method for the get_artifact tests.
+
+ Requests are intercepted before being sent out, and the appropriate responses are returned.
+ """
+ path = request.url.path
+
+ if "force_error" in path:
+ return httpx.Response(404, request=request)
+
+ if request.method == "GET":
+ if path == "/repos/owner/repo/actions/runs":
+ run = github_utils.WorkflowRun(
+ name="action_name",
+ head_sha="action_sha",
+ created_at=datetime.datetime.now().strftime(github_utils.ISO_FORMAT_STRING),
+ status="completed",
+ conclusion="success",
+ artifacts_url="artifacts_url"
+ )
+ return httpx.Response(
+ 200, request=request, json={"workflow_runs": [dataclasses.asdict(run)]}
+ )
+ elif path == "/artifact_url":
+ return httpx.Response(
+ 200, request=request, json={"artifacts": [{
+ "name": "artifact_name",
+ "archive_download_url": "artifact_download_url"
+ }]}
+ )
+ elif path == "/artifact_download_url":
+ response = httpx.Response(302, request=request)
+ response.next_request = httpx.Request(
+ "GET",
+ httpx.URL("https://final_download.url")
+ )
+ return response
+
+ # Reaching this point means something has gone wrong
+ return httpx.Response(500, request=request) # pragma: no cover
+
+ def setUp(self) -> None:
+ self.call_args = ["owner", "repo", "action_sha", "action_name", "artifact_name"]
+ self.client = httpx.Client(base_url="https://example.com")
+
+ self.patchers = [
+ mock.patch.object(self.client, "send", new=self.get_response_get_artifact),
+ mock.patch.object(github_utils, "authorize", return_value=self.client),
+ mock.patch.object(github_utils, "check_run_status", return_value="artifact_url"),
+ ]
+
+ for patcher in self.patchers:
+ patcher.start()
+
+ def tearDown(self) -> None:
+ for patcher in self.patchers:
+ patcher.stop()
+
+ def test_client_closed_on_errors(self):
+ """Test that the client is terminated even if an error occurs at some point."""
+ self.call_args[0] = "force_error"
+ with self.assertRaises(httpx.HTTPStatusError):
+ github_utils.get_artifact(*self.call_args)
+ self.assertTrue(self.client.is_closed)
+
+ def test_missing(self):
+ """Test that an exception is raised if the requested artifact was not found."""
+ cases = (
+ "invalid sha",
+ "invalid action name",
+ "invalid artifact name",
+ )
+ for i, name in enumerate(cases, 2):
+ with self.subTest(f"Test {name} raises an error"):
+ new_args = self.call_args.copy()
+ new_args[i] = name
+
+ with self.assertRaises(github_utils.NotFoundError):
+ github_utils.get_artifact(*new_args)
+
+ def test_valid(self):
+ """Test that the correct download URL is returned for valid requests."""
+ url = github_utils.get_artifact(*self.call_args)
+ self.assertEqual("https://final_download.url", url)
+ self.assertTrue(self.client.is_closed)
+
+
[email protected](github_utils, "get_artifact")
+class GitHubArtifactViewTests(django.test.TestCase):
+ """Test the GitHub artifact fetch API view."""
+
+ def setUp(self):
+ self.kwargs = {
+ "owner": "test_owner",
+ "repo": "test_repo",
+ "sha": "test_sha",
+ "action_name": "test_action",
+ "artifact_name": "test_artifact",
+ }
+ self.url = reverse("api:github-artifacts", kwargs=self.kwargs)
+
+ def test_correct_artifact(self, artifact_mock: mock.Mock):
+ """Test a proper response is returned with proper input."""
+ artifact_mock.return_value = "final download url"
+ result = self.client.get(self.url)
+
+ self.assertIsInstance(result, rest_framework.response.Response)
+ self.assertEqual({"url": artifact_mock.return_value}, result.data)
+
+ def test_failed_fetch(self, artifact_mock: mock.Mock):
+ """Test that a proper error is returned when the request fails."""
+ artifact_mock.side_effect = github_utils.NotFoundError("Test error message")
+ result = self.client.get(self.url)
+
+ self.assertIsInstance(result, rest_framework.response.Response)
+ self.assertEqual({
+ "error_type": github_utils.NotFoundError.__name__,
+ "error": "Test error message",
+ "requested_resource": "/".join(self.kwargs.values())
+ }, result.data)
diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py
index f1107734..89ee4e23 100644
--- a/pydis_site/apps/api/tests/test_infractions.py
+++ b/pydis_site/apps/api/tests/test_infractions.py
@@ -56,15 +56,17 @@ class InfractionTests(AuthenticatedAPITestCase):
type='ban',
reason='He terk my jerb!',
hidden=True,
+ inserted_at=dt(2020, 10, 10, 0, 0, 0, tzinfo=timezone.utc),
expires_at=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc),
- active=True
+ active=True,
)
cls.ban_inactive = Infraction.objects.create(
user_id=cls.user.id,
actor_id=cls.user.id,
type='ban',
reason='James is an ass, and we won\'t be working with him again.',
- active=False
+ active=False,
+ inserted_at=dt(2020, 10, 10, 0, 1, 0, tzinfo=timezone.utc),
)
cls.mute_permanent = Infraction.objects.create(
user_id=cls.user.id,
@@ -72,7 +74,8 @@ class InfractionTests(AuthenticatedAPITestCase):
type='mute',
reason='He has a filthy mouth and I am his soap.',
active=True,
- expires_at=None
+ inserted_at=dt(2020, 10, 10, 0, 2, 0, tzinfo=timezone.utc),
+ expires_at=None,
)
cls.superstar_expires_soon = Infraction.objects.create(
user_id=cls.user.id,
@@ -80,7 +83,8 @@ class InfractionTests(AuthenticatedAPITestCase):
type='superstar',
reason='This one doesn\'t matter anymore.',
active=True,
- expires_at=dt.now(timezone.utc) + datetime.timedelta(hours=5)
+ inserted_at=dt(2020, 10, 10, 0, 3, 0, tzinfo=timezone.utc),
+ expires_at=dt.now(timezone.utc) + datetime.timedelta(hours=5),
)
cls.voiceban_expires_later = Infraction.objects.create(
user_id=cls.user.id,
@@ -88,7 +92,8 @@ class InfractionTests(AuthenticatedAPITestCase):
type='voice_ban',
reason='Jet engine mic',
active=True,
- expires_at=dt.now(timezone.utc) + datetime.timedelta(days=5)
+ inserted_at=dt(2020, 10, 10, 0, 4, 0, tzinfo=timezone.utc),
+ expires_at=dt.now(timezone.utc) + datetime.timedelta(days=5),
)
def test_list_all(self):
diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py
index d5f6bc56..f872ba92 100644
--- a/pydis_site/apps/api/urls.py
+++ b/pydis_site/apps/api/urls.py
@@ -1,7 +1,7 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
-from .views import HealthcheckView, RulesView
+from .views import GitHubArtifactsView, HealthcheckView, RulesView
from .viewsets import (
AocAccountLinkViewSet,
AocCompletionistBlockViewSet,
@@ -95,5 +95,10 @@ urlpatterns = (
# from django_hosts.resolvers import reverse
path('bot/', include((bot_router.urls, 'api'), namespace='bot')),
path('healthcheck', HealthcheckView.as_view(), name='healthcheck'),
- path('rules', RulesView.as_view(), name='rules')
+ path('rules', RulesView.as_view(), name='rules'),
+ path(
+ 'github/artifact/<str:owner>/<str:repo>/<str:sha>/<str:action_name>/<str:artifact_name>',
+ GitHubArtifactsView.as_view(),
+ name="github-artifacts"
+ ),
)
diff --git a/pydis_site/apps/api/views.py b/pydis_site/apps/api/views.py
index 816463f6..34167a38 100644
--- a/pydis_site/apps/api/views.py
+++ b/pydis_site/apps/api/views.py
@@ -1,7 +1,10 @@
from rest_framework.exceptions import ParseError
+from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView
+from . import github_utils
+
class HealthcheckView(APIView):
"""
@@ -34,12 +37,14 @@ class RulesView(APIView):
## Routes
### GET /rules
- Returns a JSON array containing the server's rules:
+ Returns a JSON array containing the server's rules
+ and keywords relating to each rule.
+ Example response:
>>> [
- ... "Eat candy.",
- ... "Wake up at 4 AM.",
- ... "Take your medicine."
+ ... ["Eat candy.", ["candy", "sweets"]],
+ ... ["Wake up at 4 AM.", ["wake_up", "early", "early_bird"]],
+ ... ["Take your medicine.", ["medicine", "health"]]
... ]
Since some of the the rules require links, this view
@@ -97,6 +102,12 @@ class RulesView(APIView):
# `format` here is the result format, we have a link format here instead.
def get(self, request, format=None): # noqa: D102,ANN001,ANN201
+ """
+ Returns a list of our community rules coupled with their keywords.
+
+ Each item in the returned list is a tuple with the rule as first item
+ and a list of keywords that match that rules as second item.
+ """
link_format = request.query_params.get('link_format', 'md')
if link_format not in ('html', 'md'):
raise ParseError(
@@ -121,34 +132,93 @@ class RulesView(APIView):
return Response([
(
- f"Follow the {pydis_coc}."
+ f"Follow the {pydis_coc}.",
+ ["coc", "conduct", "code"]
),
(
- f"Follow the {discord_community_guidelines} and {discord_tos}."
+ f"Follow the {discord_community_guidelines} and {discord_tos}.",
+ ["discord", "guidelines", "discord_tos"]
),
(
- "Respect staff members and listen to their instructions."
+ "Respect staff members and listen to their instructions.",
+ ["respect", "staff", "instructions"]
),
(
"Use English to the best of your ability. "
- "Be polite if someone speaks English imperfectly."
+ "Be polite if someone speaks English imperfectly.",
+ ["english", "language"]
),
(
"Do not provide or request help on projects that may break laws, "
- "breach terms of services, or are malicious or inappropriate."
+ "breach terms of services, or are malicious or inappropriate.",
+ ["infraction", "tos", "breach", "malicious", "inappropriate"]
),
(
- "Do not post unapproved advertising."
+ "Do not post unapproved advertising.",
+ ["ad", "ads", "advert", "advertising"]
),
(
"Keep discussions relevant to the channel topic. "
- "Each channel's description tells you the topic."
+ "Each channel's description tells you the topic.",
+ ["off-topic", "topic", "relevance"]
),
(
"Do not help with ongoing exams. When helping with homework, "
- "help people learn how to do the assignment without doing it for them."
+ "help people learn how to do the assignment without doing it for them.",
+ ["exam", "exams", "assignment", "assignments", "homework"]
),
(
- "Do not offer or ask for paid work of any kind."
+ "Do not offer or ask for paid work of any kind.",
+ ["paid", "work", "money"]
),
])
+
+
+class GitHubArtifactsView(APIView):
+ """
+ Provides utilities for interacting with the GitHub API and obtaining action artifacts.
+
+ ## Routes
+ ### GET /github/artifacts
+ Returns a download URL for the artifact requested.
+
+ {
+ 'url': 'https://pipelines.actions.githubusercontent.com/...'
+ }
+
+ ### Exceptions
+ In case of an error, the following body will be returned:
+
+ {
+ "error_type": "<error class name>",
+ "error": "<error description>",
+ "requested_resource": "<owner>/<repo>/<sha>/<artifact_name>"
+ }
+
+ ## Authentication
+ Does not require any authentication nor permissions.
+ """
+
+ authentication_classes = ()
+ permission_classes = ()
+
+ def get(
+ self,
+ request: Request,
+ *,
+ owner: str,
+ repo: str,
+ sha: str,
+ action_name: str,
+ artifact_name: str
+ ) -> Response:
+ """Return a download URL for the requested artifact."""
+ try:
+ url = github_utils.get_artifact(owner, repo, sha, action_name, artifact_name)
+ return Response({"url": url})
+ except github_utils.ArtifactProcessingError as e:
+ return Response({
+ "error_type": e.__class__.__name__,
+ "error": str(e),
+ "requested_resource": f"{owner}/{repo}/{sha}/{action_name}/{artifact_name}"
+ }, status=e.status)
diff --git a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py
index 3a4cec60..97efb63c 100644
--- a/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py
+++ b/pydis_site/apps/api/viewsets/bot/aoc_completionist_block.py
@@ -70,4 +70,4 @@ class AocCompletionistBlockViewSet(
serializer_class = AocCompletionistBlockSerializer
queryset = AocCompletionistBlock.objects.all()
filter_backends = (DjangoFilterBackend,)
- filter_fields = ("user__id", "is_blocked")
+ filterset_fields = ("user__id", "is_blocked")
diff --git a/pydis_site/apps/api/viewsets/bot/aoc_link.py b/pydis_site/apps/api/viewsets/bot/aoc_link.py
index c7a96629..3cdc342d 100644
--- a/pydis_site/apps/api/viewsets/bot/aoc_link.py
+++ b/pydis_site/apps/api/viewsets/bot/aoc_link.py
@@ -68,4 +68,4 @@ class AocAccountLinkViewSet(
serializer_class = AocAccountLinkSerializer
queryset = AocAccountLink.objects.all()
filter_backends = (DjangoFilterBackend,)
- filter_fields = ("user__id", "aoc_username")
+ filterset_fields = ("user__id", "aoc_username")
diff --git a/pydis_site/apps/api/viewsets/bot/infraction.py b/pydis_site/apps/api/viewsets/bot/infraction.py
index 7f31292f..93d29391 100644
--- a/pydis_site/apps/api/viewsets/bot/infraction.py
+++ b/pydis_site/apps/api/viewsets/bot/infraction.py
@@ -1,9 +1,8 @@
-from datetime import datetime
+import datetime
from django.db import IntegrityError
from django.db.models import QuerySet
from django.http.request import HttpRequest
-from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
@@ -154,7 +153,7 @@ class InfractionViewSet(
queryset = Infraction.objects.all()
pagination_class = LimitOffsetPaginationExtended
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
- filter_fields = ('user__id', 'actor__id', 'active', 'hidden', 'type')
+ filterset_fields = ('user__id', 'actor__id', 'active', 'hidden', 'type')
search_fields = ('$reason',)
frozen_fields = ('id', 'inserted_at', 'type', 'user', 'actor', 'hidden')
@@ -185,23 +184,21 @@ class InfractionViewSet(
filter_expires_after = self.request.query_params.get('expires_after')
if filter_expires_after:
try:
- expires_after_parsed = datetime.fromisoformat(filter_expires_after)
+ expires_after_parsed = datetime.datetime.fromisoformat(filter_expires_after)
except ValueError:
raise ValidationError({'expires_after': ['failed to convert to datetime']})
- additional_filters['expires_at__gte'] = timezone.make_aware(
- expires_after_parsed,
- timezone=timezone.utc,
+ additional_filters['expires_at__gte'] = expires_after_parsed.replace(
+ tzinfo=datetime.timezone.utc
)
filter_expires_before = self.request.query_params.get('expires_before')
if filter_expires_before:
try:
- expires_before_parsed = datetime.fromisoformat(filter_expires_before)
+ expires_before_parsed = datetime.datetime.fromisoformat(filter_expires_before)
except ValueError:
raise ValidationError({'expires_before': ['failed to convert to datetime']})
- additional_filters['expires_at__lte'] = timezone.make_aware(
- expires_before_parsed,
- timezone=timezone.utc,
+ additional_filters['expires_at__lte'] = expires_before_parsed.replace(
+ tzinfo=datetime.timezone.utc
)
if 'expires_at__lte' in additional_filters and 'expires_at__gte' in additional_filters:
diff --git a/pydis_site/apps/api/viewsets/bot/nomination.py b/pydis_site/apps/api/viewsets/bot/nomination.py
index 144daab0..6af42bcb 100644
--- a/pydis_site/apps/api/viewsets/bot/nomination.py
+++ b/pydis_site/apps/api/viewsets/bot/nomination.py
@@ -172,7 +172,7 @@ class NominationViewSet(CreateModelMixin, RetrieveModelMixin, ListModelMixin, Ge
serializer_class = NominationSerializer
queryset = Nomination.objects.all()
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
- filter_fields = ('user__id', 'active')
+ filterset_fields = ('user__id', 'active')
frozen_fields = ('id', 'inserted_at', 'user', 'ended_at')
frozen_on_create = ('ended_at', 'end_reason', 'active', 'inserted_at', 'reviewed')
diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py
index 78d7cb3b..5f997052 100644
--- a/pydis_site/apps/api/viewsets/bot/reminder.py
+++ b/pydis_site/apps/api/viewsets/bot/reminder.py
@@ -125,4 +125,4 @@ class ReminderViewSet(
serializer_class = ReminderSerializer
queryset = Reminder.objects.prefetch_related('author')
filter_backends = (DjangoFilterBackend, SearchFilter)
- filter_fields = ('active', 'author__id')
+ filterset_fields = ('active', 'author__id')
diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py
index 3318b2b9..ba1bcd9d 100644
--- a/pydis_site/apps/api/viewsets/bot/user.py
+++ b/pydis_site/apps/api/viewsets/bot/user.py
@@ -237,7 +237,7 @@ class UserViewSet(ModelViewSet):
queryset = User.objects.all().order_by("id")
pagination_class = UserListPagination
filter_backends = (DjangoFilterBackend,)
- filter_fields = ('name', 'discriminator')
+ filterset_fields = ('name', 'discriminator')
def get_serializer(self, *args, **kwargs) -> ModelSerializer:
"""Set Serializer many attribute to True if request body contains a list."""
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing.md
index 6231fe87..2822d046 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing.md
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing.md
@@ -119,7 +119,7 @@ As mentioned in the Contributing Guidelines, we have a simple style guide for ou
[**Style Guide**](./style-guide/)
### 4. Create an issue
-The first step to any new contribution is an issue describing a problem with the current codebase or proposing a new feature. All the open issues are viewable on the GitHub repositories, for instance here is the [issues page for Sir Lancebot](https://github.com/python-discord/sir-lancebot/issues). If you have something that you want to implement open a new issue to present your idea. Otherwise you can browse the unassigned issues and ask to be assigned to one that you're interested in, either in the comments on the issue or in the [`#dev-contrib`](https://discord.gg/2h3qBv8Xaa) channel on Discord.
+The first step to any new contribution is an issue describing a problem with the current codebase or proposing a new feature. All the open issues are viewable on the GitHub repositories, for instance here is the [issues page for Sir Lancebot](https://github.com/python-discord/sir-lancebot/issues). If you have something that you want to implement open a new issue to present your idea. Otherwise, you can browse the unassigned issues and ask to be assigned to one that you're interested in, either in the comments on the issue or in the [`#dev-contrib`](https://discord.gg/2h3qBv8Xaa) channel on Discord.
[**How to write a good issue**](./issues/)
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md
index ad446cc8..633289f2 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/bot.md
@@ -507,7 +507,7 @@ You can start several services together: `docker-compose up web snekbox redis`.
##### Setting Up a Development Environment
The bot's code is Python code like any other. To run it locally, you will need the right version of Python with the necessary packages installed:
-1. Make sure you have [Python 3.9](https://www.python.org/downloads/) installed. It helps if it is your system's default Python version.
+1. Make sure you have [Python 3.10](https://www.python.org/downloads/) installed. It helps if it is your system's default Python version.
2. [Install Poetry](https://github.com/python-poetry/poetry#installation).
3. [Install the dependencies](../installing-project-dependencies).
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/site.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/site.md
index 520e41ad..9786698b 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/site.md
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/site.md
@@ -9,7 +9,7 @@ You should have already forked the [`site`](https://github.com/python-discord/si
### Requirements
-- [Python 3.9](https://www.python.org/downloads/)
+- [Python 3.10](https://www.python.org/downloads/)
- [Poetry](https://python-poetry.org/docs/#installation)
- `pip install poetry`
- [Git](https://git-scm.com/downloads)
diff --git a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/style-guide.md b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/style-guide.md
index 4dba45c8..b26c467c 100644
--- a/pydis_site/apps/content/resources/guides/pydis-guides/contributing/style-guide.md
+++ b/pydis_site/apps/content/resources/guides/pydis-guides/contributing/style-guide.md
@@ -202,6 +202,3 @@ def foo(input_1: int, input_2: dict[str, int]) -> bool:
This tells us that `foo` accepts an `int` and a `dict`, with `str` keys and `int` values, and returns a `bool`.
In previous examples, we have purposely omitted annotations to keep focus on the specific points they represent.
-
-> **Note:** if the project is running Python 3.8 or below you have to use `typing.Dict` instead of `dict`, but our three main projects are all >=3.9.
-> See [PEP 585](https://www.python.org/dev/peps/pep-0585/) for more information.
diff --git a/pydis_site/apps/content/tests/test_views.py b/pydis_site/apps/content/tests/test_views.py
index eadad7e3..a09d22d8 100644
--- a/pydis_site/apps/content/tests/test_views.py
+++ b/pydis_site/apps/content/tests/test_views.py
@@ -172,7 +172,7 @@ class PageOrCategoryViewTests(MockPagesTestCase, SimpleTestCase, TestCase):
for item in context["breadcrumb_items"]:
item["path"] = Path(item["path"])
- self.assertEquals(
+ self.assertEqual(
context["breadcrumb_items"],
[
{"name": PARSED_CATEGORY_INFO["title"], "path": Path(".")},
diff --git a/pydis_site/apps/home/tests/test_repodata_helpers.py b/pydis_site/apps/home/tests/test_repodata_helpers.py
index 4007eded..a963f733 100644
--- a/pydis_site/apps/home/tests/test_repodata_helpers.py
+++ b/pydis_site/apps/home/tests/test_repodata_helpers.py
@@ -42,7 +42,7 @@ class TestRepositoryMetadataHelpers(TestCase):
metadata = self.home_view._get_repo_data()
self.assertIsInstance(metadata[0], RepositoryMetadata)
- self.assertEquals(len(metadata), len(self.home_view.repos))
+ self.assertEqual(len(metadata), len(self.home_view.repos))
def test_returns_cached_metadata(self):
"""Test if the _get_repo_data helper returns cached data when available."""
@@ -82,7 +82,7 @@ class TestRepositoryMetadataHelpers(TestCase):
repo = self.home_view.repos[0]
self.assertIsInstance(api_data, dict)
- self.assertEquals(len(api_data), len(self.home_view.repos))
+ self.assertEqual(len(api_data), len(self.home_view.repos))
self.assertIn(repo, api_data.keys())
self.assertIn("stargazers_count", api_data[repo])
@@ -126,7 +126,7 @@ class TestRepositoryMetadataHelpers(TestCase):
with self.assertLogs():
metadata = self.home_view._get_repo_data()
- self.assertEquals(len(metadata), 0)
+ self.assertEqual(len(metadata), 0)
def test_cleans_up_stale_metadata(self):
"""Tests that we clean up stale metadata when we start the HomeView."""
diff --git a/pydis_site/apps/redirect/urls.py b/pydis_site/apps/redirect/urls.py
index f7ddf45b..f86fe665 100644
--- a/pydis_site/apps/redirect/urls.py
+++ b/pydis_site/apps/redirect/urls.py
@@ -3,6 +3,7 @@ import re
import yaml
from django import conf
+from django.http import HttpResponse
from django.urls import URLPattern, path
from django_distill import distill_path
@@ -53,7 +54,7 @@ def map_redirect(name: str, data: Redirect) -> list[URLPattern]:
class RedirectFunc:
def __init__(self, new_url: str, _name: str):
- self.result = REDIRECT_TEMPLATE.format(url=new_url)
+ self.result = HttpResponse(REDIRECT_TEMPLATE.format(url=new_url))
self.__qualname__ = _name
def __call__(self, *args, **kwargs):
@@ -95,7 +96,7 @@ def map_redirect(name: str, data: Redirect) -> list[URLPattern]:
return [distill_path(
data.original_path,
- lambda *args: REDIRECT_TEMPLATE.format(url=new_redirect),
+ lambda *args: HttpResponse(REDIRECT_TEMPLATE.format(url=new_redirect)),
name=name,
)]
diff --git a/pydis_site/settings.py b/pydis_site/settings.py
index 03c16f4b..cb05956b 100644
--- a/pydis_site/settings.py
+++ b/pydis_site/settings.py
@@ -21,7 +21,6 @@ import environ
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
-
env = environ.Env(
DEBUG=(bool, False),
SITE_DSN=(str, ""),
@@ -30,17 +29,27 @@ env = environ.Env(
GIT_SHA=(str, 'development'),
TIMEOUT_PERIOD=(int, 5),
GITHUB_TOKEN=(str, None),
+ GITHUB_APP_ID=(str, None),
+ GITHUB_APP_KEY=(str, None),
)
GIT_SHA = env("GIT_SHA")
+GITHUB_API = "https://api.github.com"
GITHUB_TOKEN = env("GITHUB_TOKEN")
-
-sentry_sdk.init(
- dsn=env('SITE_DSN'),
- integrations=[DjangoIntegration()],
- send_default_pii=True,
- release=f"site@{GIT_SHA}"
-)
+GITHUB_APP_ID = env("GITHUB_APP_ID")
+GITHUB_APP_KEY = env("GITHUB_APP_KEY")
+
+if GITHUB_APP_KEY and (key_file := Path(GITHUB_APP_KEY)).is_file():
+ # Allow the OAuth key to be loaded from a file
+ GITHUB_APP_KEY = key_file.read_text(encoding="utf-8")
+
+if not env("STATIC_BUILD"):
+ sentry_sdk.init(
+ dsn=env('SITE_DSN'),
+ integrations=[DjangoIntegration()],
+ send_default_pii=True,
+ release=f"site@{GIT_SHA}"
+ )
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -120,25 +129,29 @@ INSTALLED_APPS = [
if not env("BUILDING_DOCKER"):
INSTALLED_APPS.append("django_prometheus")
-NON_STATIC_MIDDLEWARE = [
- 'django_prometheus.middleware.PrometheusBeforeMiddleware',
-] if not env("STATIC_BUILD") else []
-
-# Ensure that Prometheus middlewares are first and last here.
-MIDDLEWARE = [
- *NON_STATIC_MIDDLEWARE,
-
- 'django.middleware.security.SecurityMiddleware',
- 'whitenoise.middleware.WhiteNoiseMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
-
- 'django_prometheus.middleware.PrometheusAfterMiddleware'
-]
+if env("STATIC_BUILD"):
+ # The only middleware required during static builds
+ MIDDLEWARE = [
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ ]
+else:
+ # Ensure that Prometheus middlewares are first and last here.
+ MIDDLEWARE = [
+ 'django_prometheus.middleware.PrometheusBeforeMiddleware',
+
+ 'django.middleware.security.SecurityMiddleware',
+ 'whitenoise.middleware.WhiteNoiseMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+
+ 'django_prometheus.middleware.PrometheusAfterMiddleware'
+ ]
ROOT_URLCONF = 'pydis_site.urls'
@@ -192,7 +205,6 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
-USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
diff --git a/pydis_site/templates/events/index.html b/pydis_site/templates/events/index.html
index db3e32f7..640682d0 100644
--- a/pydis_site/templates/events/index.html
+++ b/pydis_site/templates/events/index.html
@@ -10,8 +10,8 @@
<div class="box">
<h2 class="title is-4"><a href="{% url "events:page" path="code-jams" %}">Code Jams</a></h2>
<div class="notification is-success">
- The <b>2022 Summer Code Jam</b> is currently underway and you can still enter! <b>The qualifier is open until July 13</b>; check out the details <a href="{% url "events:page" path="code-jams/9" %}">here</a>.
- </div>
+ <a href="{% url "events:page" path="code-jams/9" %}">The <b>2022 Summer Code Jam</b> is underway!</a>.
+ </div>
<p>Every year we hold a community-wide Summer Code Jam. For this event, members of our community are assigned to teams to collaborate and create something amazing using a technology we picked for them. One such technology that was picked for the Summer 2021 Code Jam was text user interfaces (TUIs), where teams could pick from a pre-approved list of frameworks.</p>
<p>To help fuel the creative process, we provide a specific theme, like <strong>Think Inside the Box</strong> or <strong>Early Internet</strong>. At the end of the Code Jam, the projects are judged by Python Discord server staff members and guest judges from the larger Python community. The judges will consider creativity, code quality, teamwork, and adherence to the theme.</p>
<p>If you want to read more about Code Jams, visit our <a href="{% url "events:page" path="code-jams" %}">Code Jam info page</a> or watch this video showcasing the best projects created during the <strong>Winter Code Jam 2020: Ancient Technology</strong>:</p>
diff --git a/pydis_site/templates/events/pages/code-jams/9/_index.html b/pydis_site/templates/events/pages/code-jams/9/_index.html
index 7c57b799..ca7c4f90 100644
--- a/pydis_site/templates/events/pages/code-jams/9/_index.html
+++ b/pydis_site/templates/events/pages/code-jams/9/_index.html
@@ -24,18 +24,28 @@
<ul>
<li><strike>Saturday, June 18 - Form to submit theme suggestions opens</strike></li>
<li><strike>Wednesday, June 29 - The Qualifier is released</strike></li>
- <li>Wednesday, July 6 - Voting for the theme opens</li>
- <li>Wednesday, July 13 - The Qualifier closes</li>
- <li>Thursday, July 21 - Code Jam Begins</li>
- <li>Sunday, July 31 - Coding portion of the jam ends</li>
- <li>Sunday, August 4 - Code Jam submissions are closed</li>
+ <li><strike>Wednesday, July 6 - Voting for the theme opens</strike></li>
+ <li><strike>Wednesday, July 13 - The Qualifier closes</strike></li>
+ <li><strike>Thursday, July 21 - Code Jam Begins</strike></li>
+ <li><strike>Sunday, July 31 - Coding portion of the jam ends</strike></li>
+ <li><strike>Sunday, August 4 - Code Jam submissions are closed</strike></li>
</ul>
- <h3 id="how-to-join"><a href="#how-to-join">How to Join</a></h3>
+
+ <h3 id="qualifier"><a href="#how-to-join">The Qualifier</a></h3>
+ <p>
+ The qualifier is a coding challenge that you are required to complete before registering for the code jam.
+ This is meant as a basic assessment of your skills to ensure you have enough python knowledge to effectively contribute in a team environment.
+ </p>
+ <p class="has-text-centered"><a class="button is-link" href="https://github.com/python-discord/code-jam-qualifier-9/" target="_blank" rel="noopener">View the Qualifier</a></p>
<p>
- Before being able to join the code jam, you must complete a qualifier which tests your knowledge in Python.
- The qualifier can be found <a href="https://github.com/python-discord/code-jam-qualifier-9/" title="Code Jam 9 qualifier repository" target="_blank" rel="noopener">on our GitHub</a>
- and once completed you should submit your solution using the <a href="https://forms.pythondiscord.com/form/cj9-qualifier" target="_blank" rel="noopener">sign-up form</a>.
+ Please note the requirements for the qualifier.
+ <ul>
+ <li>The qualifier must be completed using Python 3.10</li>
+ <li>No external modules are allowed, only those available through the standard library.</li>
+ <li>The Qualifier must be submitted through the Code Jam sign-up form.</li>
+ </ul>
</p>
+
<h3 id="technology"><a href="#technology">Technology</a></h3>
<p>
The chosen technology/tech stack for this year is <strong>WebSockets</strong>.
diff --git a/pydis_site/templates/events/pages/code-jams/9/frameworks.html b/pydis_site/templates/events/pages/code-jams/9/frameworks.html
index 355bf9c3..b462c733 100644
--- a/pydis_site/templates/events/pages/code-jams/9/frameworks.html
+++ b/pydis_site/templates/events/pages/code-jams/9/frameworks.html
@@ -71,70 +71,70 @@
<div class="card mb-4">
<div class="card-content">
<div class="content">
- <p class="subtitle">Django Channels</p>
- <p>Django Channels adds WebSocket-support to Django - built on ASGI like other web frameworks.
+ <p class="subtitle">aiohttp</p>
+ <p>aiohttp provides both a client and server WebSocket implementation, while avoiding callback-hell.
</p>
</div>
</div>
<div class="card-footer">
- <a href="https://channels.readthedocs.io/en/stable" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
- <a href="https://github.com/django/channels" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
+ <a href="https://docs.aiohttp.org/en/stable/client_quickstart.html#websockets" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
+ <a href="https://github.com/aio-libs/aiohttp" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
</div>
</div>
<div class="card mb-4">
<div class="card-content">
<div class="content">
- <p class="subtitle">Flask-SocketIO</p>
- <p>Flask-SocketIO gives Flask applications access to low latency bi-directional communications between the clients and the server.
+ <p class="subtitle">Django Channels</p>
+ <p>Django Channels adds WebSocket-support to Django - built on ASGI like other web frameworks.
</p>
</div>
</div>
<div class="card-footer">
- <a href="https://flask-socketio.readthedocs.io/en/latest" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
- <a href="https://github.com/miguelgrinberg/flask-socketio" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
+ <a href="https://channels.readthedocs.io/en/stable" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
+ <a href="https://github.com/django/channels" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
</div>
</div>
<div class="card mb-4">
<div class="card-content">
<div class="content">
- <p class="subtitle">wsproto</p>
- <p>wsproto is a pure-Python WebSocket protocol stack written to be as flexible as possible by having the user build the bridge to the I/O.
+ <p class="subtitle">Starlite</p>
+ <p>Starlite is a light and flexible ASGI API framework, using Starlette and Pydantic as foundations.
</p>
</div>
</div>
<div class="card-footer">
- <a href="https://python-hyper.org/projects/wsproto/en/stable" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
- <a href="https://github.com/python-hyper/wsproto" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
+ <a href="https://starlite-api.github.io/starlite" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
+ <a href="https://github.com/starlite-api/starlite" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
</div>
</div>
<div class="card mb-4">
<div class="card-content">
<div class="content">
- <p class="subtitle">Starlite</p>
- <p>Starlite is a light and flexible ASGI API framework, using Starlette and Pydantic as foundations.
+ <p class="subtitle">Sanic</p>
+ <p>Sanic is an ASGI compliant web framework designed for speed and simplicity.
</p>
</div>
</div>
<div class="card-footer">
- <a href="https://starlite-api.github.io/starlite" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
- <a href="https://github.com/starlite-api/starlite" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
+ <a href="https://sanic.dev/en/guide/advanced/websockets.html" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
+ <a href="https://github.com/sanic-org/sanic" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
</div>
</div>
<div class="card mb-4">
<div class="card-content">
<div class="content">
- <p class="subtitle">Sanic</p>
- <p>Sanic is an ASGI compliant web framework designed for speed and simplicity.
+ <p class="subtitle">wsproto</p>
+ <p>wsproto is a pure-Python WebSocket protocol stack written to be as flexible as possible by having the user build the bridge to the I/O.
</p>
</div>
</div>
<div class="card-footer">
- <a href="https://sanic.dev/en/guide/advanced/websockets.html" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
- <a href="https://github.com/sanic-org/sanic" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
+ <a href="https://python-hyper.org/projects/wsproto/en/stable" class="card-footer-item"><i class="fas fa-book"></i>&ensp;Documentation</a>
+ <a href="https://github.com/python-hyper/wsproto" class="card-footer-item"><i class="fab fa-github"></i>&ensp;GitHub</a>
</div>
</div>
diff --git a/pydis_site/templates/events/pages/code-jams/9/rules.html b/pydis_site/templates/events/pages/code-jams/9/rules.html
index 72c0372e..9a28852f 100644
--- a/pydis_site/templates/events/pages/code-jams/9/rules.html
+++ b/pydis_site/templates/events/pages/code-jams/9/rules.html
@@ -11,7 +11,18 @@
{% block event_content %}
<ol>
- <li><p>Your solution must use one of the approved frameworks (a list will be released soon). It is not permitted to circumvent this rule by e.g. using the approved framework as a wrapper for another framework.</p></li>
+ <li><p>Your solution must use one of the approved frameworks. It is not permitted to circumvent this rule by e.g. using the approved framework as a wrapper for another framework.</p></li>
+ <li>
+ <p>
+ <strong>The core of your project must use WebSockets as its communication protocol.</strong>.
+ This means that you are allowed to use other methods of communication where WebSockets cannot be implemented, however, that should be a non-significant portion of your project.
+ For example, serving static files for a website cannot be done over WebSockets and it does not pose as a significant portion of a project, therefore it is allowed.
+ </p>
+
+ <p>This rule does not apply to databases and files when used for <i>storage purposes</i> even though that may be a significant portion of your project. Working with subprocesses (through stdin/stdout or <code>multiprocessing.Pool()</code>/<code>concurrent.futures.ProcessPoolExecutor()</code>) is also exempt from this rule.</p>
+
+ <p>If you're unsure about your use of non-WebSocket communication, please reach out to the events team.</p>
+ </li>
<li><p>Your solution should be platform agnostic. For example, if you use filepaths in your submission, use <code>pathlib</code> to create platform agnostic Path objects instead of hardcoding the paths.</p></li>
<li>
<p>
diff --git a/pydis_site/templates/events/pages/code-jams/_index.html b/pydis_site/templates/events/pages/code-jams/_index.html
index 74efcfaa..c7975679 100644
--- a/pydis_site/templates/events/pages/code-jams/_index.html
+++ b/pydis_site/templates/events/pages/code-jams/_index.html
@@ -8,12 +8,6 @@
{% block title %}Code Jams{% endblock %}
{% block event_content %}
- <div class="block">
- <div class="notification is-success">
- The <b>2022 Summer Code Jam</b> is currently underway and you can still enter! <b>The qualifier is open until July 13</b>; check out the details <a href="{% url "events:page" path="code-jams/9" %}">here</a>.
- </div>
- </div>
-
<p>
If you've been around the server for a while, or you just happened to join at the right time,
you may have heard of something known as a Code Jam.
diff --git a/pydis_site/templates/events/sidebar/code-jams/previous-code-jams.html b/pydis_site/templates/events/sidebar/code-jams/previous-code-jams.html
index 21b2ccb4..28412c53 100644
--- a/pydis_site/templates/events/sidebar/code-jams/previous-code-jams.html
+++ b/pydis_site/templates/events/sidebar/code-jams/previous-code-jams.html
@@ -1,6 +1,7 @@
<div class="box">
<p class="menu-label">Previous Code Jams</p>
<ul class="menu-list">
+ <li><a class="has-text-link" href="{% url "events:page" path="code-jams/9" %}">Code Jam 9: It's Not A Bug, It's A Feature</a></li>
<li><a class="has-text-link" href="{% url "events:page" path="code-jams/8" %}">Code Jam 8: Think Inside the Box</a></li>
<li><a class="has-text-link" href="{% url "events:page" path="code-jams/7" %}">Code Jam 7: Early Internet</a></li>
<li><a class="has-text-link" href="{% url "events:page" path="code-jams/6" %}">Code Jam 6: Ancient Technology</a></li>
diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html
index cdbac830..cf6ff8cd 100644
--- a/pydis_site/templates/home/index.html
+++ b/pydis_site/templates/home/index.html
@@ -12,7 +12,7 @@
<!-- Mobile-only Code Jam Banner -->
<section id="mobile-notice" class="is-primary is-hidden-tablet">
<a href="/events/code-jams/9/">
- <img src="{% static "images/events/summer_code_jam_2022/front_page_banners/sign_up.png" %}" alt="Summer Code Jam 2022">
+ <img src="{% static "images/events/summer_code_jam_2022/site_banner.png" %}" alt="Summer Code Jam 2022">
</a>
</section>
@@ -48,7 +48,7 @@
{# Code Jam Banner #}
<div id="wave-hero-right" class="column is-half">
<a href="/events/code-jams/9/">
- <img src="{% static "images/events/summer_code_jam_2022/front_page_banners/sign_up.png" %}" alt="Summer Code Jam 2022">
+ <img src="{% static "images/events/summer_code_jam_2022/site_banner.png" %}" alt="Summer Code Jam 2022">
</a>
</div>
</div>
diff --git a/pyproject.toml b/pyproject.toml
index 467fc8bc..50738cfe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,41 +6,40 @@ authors = ["Python Discord <[email protected]>"]
license = "MIT"
[tool.poetry.dependencies]
-python = "3.9.*"
-django = "~=4.0"
-django-environ = "~=0.4.5"
-django-filter = "~=21.1"
-djangorestframework = "~=3.13"
-psycopg2-binary = "~=2.8.0"
-django-simple-bulma = "~=2.4"
-whitenoise = "~=5.0"
-httpx = "~=0.23.0"
-pyyaml = "~=5.1"
-gunicorn = "~=20.0.4"
-sentry-sdk = "~=0.19"
-markdown = "~=3.3.4"
-python-frontmatter = "~=1.0"
-django-prometheus = "~=2.1"
-django-distill = "~=2.9.0"
+python = "3.10.*"
+django = "4.1.1"
+django-environ = "0.9.0"
+django-filter = "22.1"
+djangorestframework = "3.14.0"
+psycopg2-binary = "2.9.3"
+django-simple-bulma = "2.5.0"
+whitenoise = "6.2.0"
+httpx = "0.23.0"
+pyyaml = "6.0"
+gunicorn = "20.1.0"
+sentry-sdk = "1.9.10"
+markdown = "3.4.1"
+python-frontmatter = "1.0.0"
+django-prometheus = "2.2.0"
+django-distill = "3.0.1"
+PyJWT = {version = "2.5.0", extras = ["crypto"]}
[tool.poetry.dev-dependencies]
-coverage = "~=5.0"
-flake8 = "~=3.7"
-flake8-annotations = "~=2.0"
-flake8-bandit = "~=3.0"
-flake8-bugbear = "~=20.1"
-flake8-docstrings = "~=1.5"
-flake8-import-order = "~=0.18"
-flake8-string-format = "~=0.3"
-flake8-tidy-imports = "~=4.0"
-flake8-todo = "~=0.7"
-mccabe = "~=0.6.1"
-pep8-naming = "~=0.9"
-pre-commit = "~=2.1"
-pyfakefs = "~=4.5"
-coveralls = "~=2.1"
-taskipy = "~=1.7.0"
-python-dotenv = "~=0.17.1"
+coverage = "6.5.0"
+flake8 = "5.0.4"
+flake8-annotations = "2.9.1"
+flake8-bandit = "4.1.1"
+flake8-bugbear = "22.9.23"
+flake8-docstrings = "1.6.0"
+flake8-import-order = "0.18.1"
+flake8-tidy-imports = "4.8.0"
+flake8-string-format = "0.3.0"
+flake8-todo = "0.7"
+pep8-naming = "0.13.2"
+pre-commit = "2.20.0"
+pyfakefs = "4.7.0"
+taskipy = "1.10.3"
+python-dotenv = "0.21.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
@@ -51,7 +50,7 @@ start = "python manage.py run --debug"
makemigrations = "python manage.py makemigrations"
django_shell = "python manage.py shell"
test = "coverage run manage.py test"
-coverage = "coverage run manage.py test --no-input; coverage report -m"
+coverage = "coverage run manage.py test --no-input && coverage report -m"
report = "coverage report -m"
lint = "pre-commit run --all-files"
precommit = "pre-commit install"
diff --git a/static-builds/README.md b/static-builds/README.md
index 9b86ed08..a3c7962b 100644
--- a/static-builds/README.md
+++ b/static-builds/README.md
@@ -27,16 +27,29 @@ Alternatively, you can use the [Dockerfile](/Dockerfile) and extract the build.
Both output their builds to a `build/` directory.
### Deploying To Netlify
-To deploy to netlify, link your site GitHub repository to a netlify site, and use the following settings:
+To deploy to netlify, link your site GitHub repository to a netlify site, and use the settings below.
+The netlify build script uses the site API to fetch and download the artifact, using a GitHub app that
+can access the repo. The app must have the `actions` and `artifacts` scopes enabled.
+### Netlify Settings
Build Command:
-`python -m pip install httpx==0.19.0 && python static-builds/netlify_build.py`
+`python -m pip install httpx==0.23.0 && python static-builds/netlify_build.py`
Publish Directory:
`build`
-Environment Variables:
-- PYTHON_VERSION: 3.8
+**Environment Variables**
+
+| Name | Value | Description |
+|----------------|--------------------------------|-------------------------------------------------------------------------------------------|
+| PYTHON_VERSION | 3.8 | The python version. Supported options are defined by netlify [here][netlify build image]. |
+| API_URL | https://pythondiscord.com/ | The link to the API, which will be used to fetch the build artifacts. |
+| ACTION_NAME | Build & Publish Static Preview | The name of the workflow which will be used to find the artifact. |
+| ARTIFACT_NAME | static-build | The name of the artifact to download. |
+
+
+[netlify build image]: https://github.com/netlify/build-image/tree/focal
+
Note that at this time, if you are deploying to netlify yourself, you won't have access to the
@@ -45,6 +58,3 @@ You can either update the pack to one which will work on your domain, or you'll
> Warning: If you are modifying the [build script](./netlify_build.py), make sure it is compatible with Python 3.8.
-
-Note: The build script uses [nightly.link](https://github.com/oprypin/nightly.link)
-to fetch the artifact with no authentication.
diff --git a/static-builds/netlify_build.py b/static-builds/netlify_build.py
index 4e1e6106..36520c28 100644
--- a/static-builds/netlify_build.py
+++ b/static-builds/netlify_build.py
@@ -4,106 +4,60 @@
# This script performs all the actions required to build and deploy our project on netlify
# It depends on the following packages, which are set in the netlify UI:
-# httpx == 0.19.0
+# httpx == 0.23.0
+import json
import os
import time
-import typing
import zipfile
from pathlib import Path
from urllib import parse
import httpx
-API_URL = "https://api.github.com"
-NIGHTLY_URL = "https://nightly.link"
-OWNER, REPO = parse.urlparse(os.getenv("REPOSITORY_URL")).path.lstrip("/").split("/")[0:2]
+def raise_response(response: httpx.Response) -> None:
+ """Raise an exception from a response if necessary."""
+ if response.status_code // 100 != 2:
+ try:
+ print(response.json())
+ except json.JSONDecodeError:
+ pass
-def get_build_artifact() -> typing.Tuple[int, str]:
- """
- Search for a build artifact, and return the result.
+ response.raise_for_status()
- The return is a tuple of the check suite ID, and the URL to the artifacts.
- """
- print("Fetching build URL.")
- if os.getenv("PULL_REQUEST").lower() == "true":
- print(f"Fetching data for PR #{os.getenv('REVIEW_ID')}")
-
- pull_url = f"{API_URL}/repos/{OWNER}/{REPO}/pulls/{os.getenv('REVIEW_ID')}"
- pull_request = httpx.get(pull_url)
- pull_request.raise_for_status()
-
- commit_sha = pull_request.json()["head"]["sha"]
-
- workflows_params = parse.urlencode({
- "event": "pull_request",
- "per_page": 100
- })
-
- else:
- commit_sha = os.getenv("COMMIT_REF")
-
- workflows_params = parse.urlencode({
- "event": "push",
- "per_page": 100
- })
-
- print(f"Fetching action data for commit {commit_sha}")
-
- workflows = httpx.get(f"{API_URL}/repos/{OWNER}/{REPO}/actions/runs?{workflows_params}")
- workflows.raise_for_status()
-
- for run in workflows.json()["workflow_runs"]:
- if run["name"] == "Build & Publish Static Preview" and commit_sha == run["head_sha"]:
- print(f"Found action for this commit: {run['id']}\n{run['html_url']}")
- break
- else:
- raise Exception("Could not find the workflow run for this event.")
-
- polls = 0
- while polls <= 20:
- if run["status"] != "completed":
- print("Action isn't ready, sleeping for 10 seconds.")
- polls += 1
- time.sleep(10)
-
- elif run["conclusion"] != "success":
- print("Aborting build due to a failure in a previous CI step.")
- exit(0)
-
- else:
- print(f"Found artifact URL:\n{run['artifacts_url']}")
- return run["check_suite_id"], run["artifacts_url"]
-
- _run = httpx.get(run["url"])
- _run.raise_for_status()
- run = _run.json()
-
- raise Exception("Polled for the artifact workflow, but it was not ready in time.")
-
-
-def download_artifact(suite_id: int, url: str) -> None:
- """Download a build artifact from `url`, and unzip the content."""
- print("Fetching artifact data.")
-
- artifacts = httpx.get(url)
- artifacts.raise_for_status()
- artifacts = artifacts.json()
-
- if artifacts["total_count"] == "0":
- raise Exception(f"No artifacts were found for this build, aborting.\n{url}")
-
- for artifact in artifacts["artifacts"]:
- if artifact["name"] == "static-build":
- print("Found artifact with build.")
- break
- else:
- raise Exception("Could not find an artifact with the expected name.")
-
- artifact_url = f"{NIGHTLY_URL}/{OWNER}/{REPO}/suites/{suite_id}/artifacts/{artifact['id']}"
- zipped_content = httpx.get(artifact_url)
+if __name__ == "__main__":
+ client = httpx.Client(
+ follow_redirects=True,
+ timeout=3 * 60,
+ )
+
+ owner, repo = parse.urlparse(os.getenv("REPOSITORY_URL")).path.lstrip("/").split("/")[0:2]
+
+ download_url = "/".join([
+ os.getenv("API_URL").rstrip("/"),
+ "api/github/artifact",
+ owner,
+ repo,
+ os.getenv("COMMIT_REF"),
+ parse.quote(os.getenv("ACTION_NAME")),
+ os.getenv("ARTIFACT_NAME"),
+ ])
+ print(f"Fetching download URL from {download_url}")
+ response = client.get(download_url)
+ raise_response(response)
+
+ # The workflow is still pending, retry in a bit
+ while response.status_code == 202:
+ print(f"{response.json()['error']}. Retrying in 10 seconds.")
+ time.sleep(10)
+ response = client.get(download_url)
+
+ raise_response(response)
+ url = response.json()["url"]
+ print(f"Downloading build from {url}")
+ zipped_content = client.get(url)
zipped_content.raise_for_status()
zip_file = Path("temp.zip")
@@ -115,8 +69,3 @@ def download_artifact(suite_id: int, url: str) -> None:
zip_file.unlink(missing_ok=True)
print("Wrote artifact content to target directory.")
-
-
-if __name__ == "__main__":
- print("Build started")
- download_artifact(*get_build_artifact())