aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml13
-rw-r--r--CHANGELOG.rst24
-rw-r--r--asset-sources/theme.scss53
-rwxr-xr-xci/run_tests.sh48
-rw-r--r--doc/administration/installation.rst31
-rw-r--r--fietsboek/__init__.py21
-rw-r--r--fietsboek/actions.py13
-rw-r--r--fietsboek/alembic/versions/20220808_d085998b49ca.py11
-rw-r--r--fietsboek/alembic/versions/20230203_3149aa2d0114.py2
-rw-r--r--fietsboek/alembic/versions/20250607_2ebe1bf66430.py46
-rw-r--r--fietsboek/config.py22
-rw-r--r--fietsboek/convert.py18
-rw-r--r--fietsboek/data.py56
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.mobin16196 -> 17892 bytes
-rw-r--r--fietsboek/locale/de/LC_MESSAGES/messages.po292
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.mobin15148 -> 16808 bytes
-rw-r--r--fietsboek/locale/en/LC_MESSAGES/messages.po292
-rw-r--r--fietsboek/locale/fietslog.pot288
-rw-r--r--fietsboek/models/track.py23
-rw-r--r--fietsboek/routes.py14
-rw-r--r--fietsboek/scripts/fietscron.py39
-rw-r--r--fietsboek/static/DeadEnd.svg44
-rw-r--r--fietsboek/static/NoEntry.svg37
-rw-r--r--fietsboek/static/theme.css49
-rw-r--r--fietsboek/static/theme.css.map2
-rw-r--r--fietsboek/templates/403.jinja216
-rw-r--r--fietsboek/templates/404.jinja214
-rw-r--r--fietsboek/templates/admin.jinja249
-rw-r--r--fietsboek/templates/admin_badges.jinja245
-rw-r--r--fietsboek/templates/admin_overview.jinja293
-rw-r--r--fietsboek/templates/browse.jinja2135
-rw-r--r--fietsboek/templates/edit.jinja24
-rw-r--r--fietsboek/templates/profile_overview.jinja289
-rw-r--r--fietsboek/trackmap.py134
-rw-r--r--fietsboek/updater/scripts/upd_20250618_v0.11.0.py27
-rw-r--r--fietsboek/util.py14
-rw-r--r--fietsboek/views/admin.py125
-rw-r--r--fietsboek/views/browse.py82
-rw-r--r--fietsboek/views/detail.py55
-rw-r--r--fietsboek/views/edit.py33
-rw-r--r--fietsboek/views/errors.py32
-rw-r--r--fietsboek/views/notfound.py19
-rw-r--r--fietsboek/views/tileproxy.py109
-rw-r--r--fietsboek/views/upload.py6
-rw-r--r--poetry.lock1722
-rw-r--r--pyproject.toml13
-rw-r--r--release-checklist.md2
-rw-r--r--testing.ini2
-rw-r--r--tests/bootstrap/test_new_instance.py90
-rw-r--r--tests/conftest.py1
-rw-r--r--tests/integration/test_browse.py90
-rw-r--r--tests/playwright/conftest.py11
-rw-r--r--tests/playwright/test_basic.py27
-rw-r--r--tests/playwright/test_profiles.py2
-rw-r--r--tests/playwright/test_share.py3
-rw-r--r--tests/playwright/test_tileproxy.py4
-rw-r--r--tox.ini4
57 files changed, 3161 insertions, 1329 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9263e91..20307c4 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,7 +4,7 @@ variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
default:
- image: python:bullseye
+ image: python:bookworm
# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/topics/caching/
#
@@ -17,16 +17,15 @@ default:
before_script:
- python --version # For debugging
+ - dpkg -s libsqlite3-0
- pip install tox
test:
+ parallel:
+ matrix:
+ - DB: ["sqlite", "postgres"]
script:
- - pip install poetry && pip install "playwright=="$(poetry show playwright | grep version | cut -f 2 -d ":" | tr -d " ")
- - playwright install firefox
- - playwright install-deps
- - apt install -y redis-server
- - redis-server >/dev/null 2>&1 &
- - tox -e python -- --browser firefox
+ - ci/run_tests.sh $DB
# test-pypy:
# image: pypy:3
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a49f7b6..4aa9643 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,6 +4,30 @@ Changelog
Unreleased
----------
+0.11.0 - 2025-06-18
+-------------------
+
+Added
+^^^^^
+
+- The admin overview with storage statistics and some other information.
+- Track preview images in the browse/profile views.
+- The ability to replace a track's GPX file with a new one.
+
+Changed
+^^^^^^^
+
+- Fietsboek now requires at least Python 3.11.
+- There is now a proper 404 (Not Found) page.
+- There is now a proper 403 (Forbidden) page.
+- The heat map is now preselected on the profile page.
+- The browse page now paginates its results.
+
+Fixed
+^^^^^
+
+- The alignment of image/comment counts on the home view has been fixed.
+
0.10.0 - 2025-02-05
-------------------
diff --git a/asset-sources/theme.scss b/asset-sources/theme.scss
index 7f89bf6..ba05782 100644
--- a/asset-sources/theme.scss
+++ b/asset-sources/theme.scss
@@ -280,10 +280,63 @@ strong {
width: 25%;
}
+.browse-track-card {
+ display: grid;
+ grid-template: 'preview data' / 300px auto;
+
+ @media (max-width: 768px) {
+ grid-template:
+ 'preview'
+ 'data';
+ }
+
+ &.card-body {
+ padding: 0px;
+ }
+
+ .browse-track-preview {
+ grid-area: preview;
+
+ @media (max-width: 768px) {
+ text-align: center;
+ }
+
+ img {
+ width: 300px;
+ height: 300px;
+ }
+ }
+
+ .browse-track-data {
+ grid-area: data;
+ padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x);
+ }
+}
+
.chart-title {
text-align: center;
}
+/* Admin view layout: We have an extra sidebar for the navigation */
+#adminContainer {
+ display: grid;
+ grid-template-areas: "sidebar main";
+ grid-template-columns: 1fr 5fr;
+ gap: 1rem;
+}
+
+#adminNavigation {
+ grid-area: sidebar;
+}
+
+#adminContent {
+ grid-area: main;
+}
+
+.admin-stat {
+ font-size: 120%;
+}
+
.list-group.list-group-root {
padding: 0;
overflow: hidden;
diff --git a/ci/run_tests.sh b/ci/run_tests.sh
new file mode 100755
index 0000000..200f794
--- /dev/null
+++ b/ci/run_tests.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+set -euxo pipefail
+
+DB=$1
+
+PPATH="/usr/lib/postgresql/15/bin/"
+
+setup_postgres() {
+ apt update
+ apt install -y postgresql postgresql-client sudo
+ echo -n "postgres" >/tmp/pw
+ mkdir /tmp/postgres-db
+ chown postgres:postgres /tmp/postgres-db
+ sudo -u postgres "$PPATH/initdb" --pwfile /tmp/pw -U postgres /tmp/postgres-db
+ sudo -u postgres "$PPATH/postgres" -D /tmp/postgres-db >/dev/null 2>&1 &
+}
+
+setup_redis() {
+ apt install -y redis-server
+ redis-server >/dev/null 2>&1 &
+}
+
+setup_playwright() {
+ pip install poetry
+ pip install "playwright=="$(poetry show playwright | grep version | cut -f 2 -d ":" | tr -d " ")
+ playwright install firefox
+ playwright install-deps
+}
+
+case "$DB" in
+ "sqlite")
+ ;;
+
+ "postgres")
+ setup_postgres
+ sed -i 's|^sqlalchemy.url = .*$|sqlalchemy.url = postgresql://postgres:postgres@localhost/postgres|' testing.ini
+ ;;
+
+ *)
+ echo "Unknown database: $DB"
+ exit 1
+ ;;
+esac
+
+pip install tox
+setup_playwright
+setup_redis
+tox -e python -- --browser firefox
diff --git a/doc/administration/installation.rst b/doc/administration/installation.rst
index b207784..13e03b5 100644
--- a/doc/administration/installation.rst
+++ b/doc/administration/installation.rst
@@ -6,14 +6,17 @@ This document will outline the installation process of Fietsboek, step-by-step.
Requirements
------------
-Fietsboek has the following requirements (apart from the Python modules, which
-will be installed by ``pip``):
+Fietsboek has the following requirements:
-* Python 3.10 or later
-* A `redis <https://redis.com/>`__ server, used for caching and temporary data
-* (Optionally) an SQL database server like `PostgreSQL
- <https://www.postgresql.org/>`__ or `MariaDB <https://mariadb.org/>`__ (if
- SQLite is not enough)
+* A Linux system
+* Python 3.11 or later
+* A `redis <https://redis.com/>`__ server
+* (Optionally) an SQL database server:
+
+ * `PostgreSQL <https://www.postgresql.org/>`__
+
+Other systems (such as BSD as operating system, or MariaDB as SQL server) might
+work, but are not tested.
In addition, if you run on a different interpreter than CPython, you might need
a working Rust toolchain (``rustc`` and ``cargo``) installed. This is because
@@ -97,6 +100,18 @@ parser to process the GPX files:
.. _issue #7: https://gitlab.com/dunj3/fietsboek/-/issues/7
+Optional: Install Database Drivers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you decide to use a database other than SQLite, you must install the
+required drivers:
+
+**PostgreSQL**:
+
+.. code:: bash
+
+ .venv/bin/pip install psycopg2
+
Configuring Fietsboek
---------------------
@@ -119,7 +134,7 @@ other update tasks. You can use it to set up the initial database schema:
instead of a specific version, you must also run ``.venv/bin/alembic -c
production.ini upgrade head``.
- See :doc:`../developer/updater` ("Furhter notes") for more information.
+ See :doc:`../developer/updater` ("Further notes") for more information.
Adding an Administrator User
----------------------------
diff --git a/fietsboek/__init__.py b/fietsboek/__init__.py
index 3d6c125..9cd58ed 100644
--- a/fietsboek/__init__.py
+++ b/fietsboek/__init__.py
@@ -20,6 +20,7 @@ from pathlib import Path
from typing import Callable, Optional
import redis
+import sqlalchemy
from pyramid.config import Configurator
from pyramid.csrf import CookieCSRFStoragePolicy
from pyramid.httpexceptions import HTTPServiceUnavailable
@@ -119,6 +120,22 @@ def check_update_state(config_uri: str):
LOGGER.warning("Could not determine version state of the data - check `fietsupdate status`")
+def check_db_engine(sqlalchemy_uri: str):
+ """Checks whether we "support" the given SQL engine.
+
+ :param sqlalchemy_uri: The configured SQLAlchemy URL.
+ """
+ engine = sqlalchemy.create_engine(sqlalchemy_uri)
+ match engine.name:
+ case "sqlite":
+ pass
+ case _:
+ LOGGER.warning(
+ "The configured SQL backend is not well tested in combination with fietsboek. "
+ "Use it at your own risk."
+ )
+
+
def main(global_config, **settings):
"""This function returns a Pyramid WSGI application."""
# Avoid a circular import by not importing at the top level
@@ -132,6 +149,8 @@ def main(global_config, **settings):
parsed_config = mod_config.parse(settings)
settings["jinja2.newstyle"] = True
+ check_db_engine(parsed_config.sqlalchemy_url)
+
def data_manager(request):
return DataManager(Path(request.config.data_dir))
@@ -174,7 +193,7 @@ def main(global_config, **settings):
config.add_request_method(redis_, name="redis", reify=True)
config.add_request_method(config_, name="config", reify=True)
- config.registry.registerUtility(TileRequester())
+ config.registry.registerUtility(TileRequester(redis.from_url(parsed_config.redis_url)))
jinja2_env = config.get_jinja2_environment()
jinja2_env.filters["format_decimal"] = mod_jinja2.filter_format_decimal
diff --git a/fietsboek/actions.py b/fietsboek/actions.py
index a20ca2e..3f14308 100644
--- a/fietsboek/actions.py
+++ b/fietsboek/actions.py
@@ -7,6 +7,7 @@ the test functions.
"""
import datetime
+import io
import logging
import re
from typing import Optional
@@ -18,12 +19,14 @@ from pyramid.request import Request
from sqlalchemy import select
from sqlalchemy.orm.session import Session
-from . import email, models
+from . import email, models, trackmap
from . import transformers as mod_transformers
from . import util
+from .config import TileLayerConfig
from .data import DataManager, TrackDataDir
from .models.track import TrackType, Visibility
from .models.user import TokenType
+from .views.tileproxy import TileRequester
LOGGER = logging.getLogger(__name__)
@@ -31,6 +34,8 @@ LOGGER = logging.getLogger(__name__)
def add_track(
dbsession: Session,
data_manager: DataManager,
+ tile_requester: TileRequester,
+ layer: TileLayerConfig,
owner: models.User,
title: str,
date: datetime.datetime,
@@ -105,6 +110,12 @@ def add_track(
track.ensure_cache(gpx)
dbsession.add(track.cache)
+ LOGGER.debug("Building preview image for %s", track.id)
+ preview_image = trackmap.render(gpx, layer, tile_requester)
+ image_io = io.BytesIO()
+ preview_image.save(image_io, "PNG")
+ manager.set_preview(image_io.getvalue())
+
manager.engrave_metadata(
title=track.title,
description=track.description,
diff --git a/fietsboek/alembic/versions/20220808_d085998b49ca.py b/fietsboek/alembic/versions/20220808_d085998b49ca.py
index d6353d2..2c5b71d 100644
--- a/fietsboek/alembic/versions/20220808_d085998b49ca.py
+++ b/fietsboek/alembic/versions/20220808_d085998b49ca.py
@@ -14,9 +14,18 @@ down_revision = '091ce24409fe'
branch_labels = None
depends_on = None
+is_postgres = op.get_bind().dialect.name == "postgresql"
+
def upgrade():
- op.add_column('tracks', sa.Column('type', sa.Enum('ORGANIC', 'SYNTHETIC', name='tracktype'), nullable=True))
+ if is_postgres:
+ tracktype = sa.dialects.postgresql.ENUM("ORGANIC", "SYNTHETIC", name="tracktype")
+ tracktype.create(op.get_bind())
+ op.add_column("tracks", sa.Column("type", tracktype, nullable=True))
+ else:
+ op.add_column('tracks', sa.Column('type', sa.Enum('ORGANIC', 'SYNTHETIC', name='tracktype'), nullable=True))
op.execute("UPDATE tracks SET type='ORGANIC';")
def downgrade():
op.drop_column('tracks', 'type')
+ if is_postgres:
+ op.execute("DROP TYPE tracktype;")
diff --git a/fietsboek/alembic/versions/20230203_3149aa2d0114.py b/fietsboek/alembic/versions/20230203_3149aa2d0114.py
index eb9ef78..ced8639 100644
--- a/fietsboek/alembic/versions/20230203_3149aa2d0114.py
+++ b/fietsboek/alembic/versions/20230203_3149aa2d0114.py
@@ -16,7 +16,7 @@ depends_on = None
def upgrade():
op.add_column('tracks', sa.Column('transformers', sa.JSON(), nullable=True))
- op.execute('UPDATE tracks SET transformers="[]";')
+ op.execute("UPDATE tracks SET transformers='[]';")
def downgrade():
op.drop_column('tracks', 'transformers')
diff --git a/fietsboek/alembic/versions/20250607_2ebe1bf66430.py b/fietsboek/alembic/versions/20250607_2ebe1bf66430.py
new file mode 100644
index 0000000..d7c811e
--- /dev/null
+++ b/fietsboek/alembic/versions/20250607_2ebe1bf66430.py
@@ -0,0 +1,46 @@
+"""switch transfomers from JSON to TEXT
+
+Revision ID: 2ebe1bf66430
+Revises: 4566843039d6
+Create Date: 2025-06-07 23:24:33.182649
+
+"""
+import logging
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '2ebe1bf66430'
+down_revision = '4566843039d6'
+branch_labels = None
+depends_on = None
+
+is_sqlite = op.get_bind().dialect.name == "sqlite"
+
+def upgrade():
+ if is_sqlite:
+ op.add_column('tracks', sa.Column('transformers_text', sa.Text, nullable=True))
+ op.execute('UPDATE tracks SET transformers_text=transformers;')
+ try:
+ op.drop_column('tracks', 'transformers')
+ except sa.exc.OperationalError as exc:
+ logging.getLogger(__name__).warning(
+ "Your SQLite version does not support dropping a column. "
+ "We're setting the content to NULL instead: %s",
+ exc,
+ )
+ op.execute("UPDATE tracks SET transformers = NULL;")
+ op.alter_column("tracks", "transformers", new_column_name="transformers_old_delete_this_column")
+ op.alter_column('tracks', 'transformers_text', new_column_name='transformers')
+ else:
+ op.alter_column('tracks', 'transformers', type_=sa.Text)
+
+def downgrade():
+ if is_sqlite:
+ op.add_column('tracks', sa.Column('transfomers_json', sa.JSON, nullable=True))
+ op.execute('UPDATE tracks SET transformers_json=transformers;')
+ op.drop_column('tracks', 'transformers')
+ op.alter_column('tracks', 'transformers_json', new_column_name='transformers')
+ else:
+ op.alter_column('tracks', 'transformers', type_=sa.JSON)
diff --git a/fietsboek/config.py b/fietsboek/config.py
index af946b4..beea820 100644
--- a/fietsboek/config.py
+++ b/fietsboek/config.py
@@ -19,6 +19,7 @@ import re
import typing
import urllib.parse
from enum import Enum
+from itertools import chain
import pydantic
from pydantic import (
@@ -277,6 +278,27 @@ class Config(BaseModel):
hasher.update(what_for.encode("utf-8"))
return hasher.hexdigest()
+ def public_tile_layers(self) -> list[TileLayerConfig]:
+ """Returns all tile layer configs that are public.
+
+ :return: A list of public :class:`TileLayerConfig`s.
+ """
+ # pylint: disable=import-outside-toplevel,cyclic-import
+ from .views.tileproxy import DEFAULT_TILE_LAYERS, extract_tile_layers
+
+ return [
+ source
+ for source in chain(
+ (
+ default_layer
+ for default_layer in DEFAULT_TILE_LAYERS
+ if default_layer.layer_id in self.default_tile_layers
+ ),
+ extract_tile_layers(self),
+ )
+ if source.access == LayerAccess.PUBLIC
+ ]
+
def parse(config: dict) -> Config:
"""Parses the configuration into a :class:`Config`.
diff --git a/fietsboek/convert.py b/fietsboek/convert.py
index 3c8208b..d3bfb22 100644
--- a/fietsboek/convert.py
+++ b/fietsboek/convert.py
@@ -49,4 +49,20 @@ def from_fit(data: bytes) -> GPX:
return gpx
-__all__ = ["from_fit"]
+def smart_convert(data: bytes) -> bytes:
+ """Tries to be smart in converting the input bytes.
+
+ This function automatically applies the correct conversion if possible.
+
+ Note that this function is not guaranteed to return valid GPX bytes. In the worst case,
+ invalid bytes are simply passed through.
+
+ :param data: The input bytes.
+ :return: The converted content.
+ """
+ if len(data) > 11 and data[9:12] == b"FIT":
+ return from_fit(data).to_xml().encode("utf-8")
+ return data
+
+
+__all__ = ["from_fit", "smart_convert"]
diff --git a/fietsboek/data.py b/fietsboek/data.py
index a7e9b19..9d5a133 100644
--- a/fietsboek/data.py
+++ b/fietsboek/data.py
@@ -9,7 +9,6 @@ the database itself. This module makes access to such data objects easier.
# pylint: disable=deprecated-argument
import datetime
import logging
-import os
import random
import shutil
import string
@@ -130,6 +129,27 @@ class DataManager:
raise FileNotFoundError(f"The path {path} is not a directory") from None
return UserDataDir(user_id, path)
+ def size(self) -> int:
+ """Returns the size of all data.
+
+ :return: The size of all data in bytes.
+ """
+ return util.recursive_size(self.data_dir)
+
+ def list_tracks(self) -> list[int]:
+ """Returns a list of all tracks.
+
+ :return: A list of all track IDs.
+ """
+ return [int(track.name) for track in self._track_data_dir(".").iterdir()]
+
+ def list_users(self) -> list[int]:
+ """Returns a list of all users.
+
+ :return: A list of all user IDs.
+ """
+ return [int(user.name) for user in self._user_data_dir(".").iterdir()]
+
class TrackDataDir:
"""Manager for a single track's data.
@@ -191,6 +211,12 @@ class TrackDataDir:
elif action == "delete_image":
path, data = rest
path.write_bytes(data)
+ elif action == "set_preview":
+ old_data = rest
+ if old_data is None:
+ self.preview_path().unlink()
+ else:
+ self.preview_path().write_bytes(old_data)
self.journal = None
@@ -218,6 +244,9 @@ class TrackDataDir:
elif action == "delete_image":
# Again, nothing to do here, we simply discard the in-memory image data
pass
+ elif action == "set_preview":
+ # Still nothing to do here
+ pass
self.journal = None
@@ -252,10 +281,7 @@ class TrackDataDir:
:return: The size of bytes that this track consumes.
"""
- size = 0
- for root, _, files in os.walk(self.path):
- size += sum(os.path.getsize(os.path.join(root, fname)) for fname in files)
- return size
+ return util.recursive_size(self.path)
def gpx_path(self) -> Path:
"""Returns the path of the GPX file.
@@ -406,6 +432,26 @@ class TrackDataDir:
path.unlink()
+ def preview_path(self) -> Path:
+ """Gets the path to the "preview image".
+
+ :return: The path to the preview image.
+ """
+ return self.path / "preview.png"
+
+ def set_preview(self, data: bytes):
+ """Sets the preview image to the given data.
+
+ :param data: The data of the preview image.
+ """
+ if self.journal is not None:
+ try:
+ previous_preview = self.preview_path().read_bytes()
+ except FileNotFoundError:
+ previous_preview = None
+ self.journal.append(("set_preview", previous_preview))
+ self.preview_path().write_bytes(data)
+
class UserDataDir:
"""Manager for a single user's data."""
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.mo b/fietsboek/locale/de/LC_MESSAGES/messages.mo
index 9de152c..6526f6f 100644
--- a/fietsboek/locale/de/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/de/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.po b/fietsboek/locale/de/LC_MESSAGES/messages.po
index ccb46a3..f306a46 100644
--- a/fietsboek/locale/de/LC_MESSAGES/messages.po
+++ b/fietsboek/locale/de/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-01-30 21:50+0100\n"
+"POT-Creation-Date: 2025-06-12 22:39+0200\n"
"PO-Revision-Date: 2022-07-02 17:35+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
@@ -16,87 +16,185 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.15.0\n"
+"Generated-By: Babel 2.17.0\n"
-#: fietsboek/actions.py:267
+#: fietsboek/actions.py:278
msgid "email.verify_mail.subject"
msgstr "Fietsboek Konto Bestätigung"
-#: fietsboek/actions.py:270
+#: fietsboek/actions.py:281
msgid "email.verify.text"
msgstr ""
"Um Dein Fietsboek-Konto zu bestätigen, nutze diesen Link: {}\n"
"\n"
"Falls Du kein Konto angelegt hast, ignoriere diese E-Mail."
-#: fietsboek/util.py:333
+#: fietsboek/util.py:334
msgid "password_constraint.mismatch"
msgstr "Passwörter stimmen nicht überein"
-#: fietsboek/util.py:335
+#: fietsboek/util.py:336
msgid "password_constraint.length"
msgstr "Passwort zu kurz"
-#: fietsboek/models/track.py:603
+#: fietsboek/models/track.py:622
msgid "tooltip.table.length"
msgstr "Länge"
-#: fietsboek/models/track.py:604
+#: fietsboek/models/track.py:623
msgid "tooltip.table.people"
msgstr "# Personen"
-#: fietsboek/models/track.py:605
+#: fietsboek/models/track.py:624
msgid "tooltip.table.uphill"
msgstr "Bergauf"
-#: fietsboek/models/track.py:606
+#: fietsboek/models/track.py:625
msgid "tooltip.table.downhill"
msgstr "Bergab"
-#: fietsboek/models/track.py:607 fietsboek/templates/home.jinja2:7
+#: fietsboek/models/track.py:626 fietsboek/templates/home.jinja2:7
msgid "tooltip.table.moving_time"
msgstr "Fahrzeit"
-#: fietsboek/models/track.py:608 fietsboek/templates/home.jinja2:8
+#: fietsboek/models/track.py:627 fietsboek/templates/home.jinja2:8
msgid "tooltip.table.stopped_time"
msgstr "Haltezeit"
-#: fietsboek/models/track.py:610
+#: fietsboek/models/track.py:629
msgid "tooltip.table.max_speed"
msgstr "Maximalgeschwindigkeit"
-#: fietsboek/models/track.py:614
+#: fietsboek/models/track.py:633
msgid "tooltip.table.avg_speed"
msgstr "Durchschnittsgeschwindigkeit"
+#: fietsboek/templates/403.jinja2:5
+msgid "403.title"
+msgstr "Zugang verboten"
+
+#: fietsboek/templates/403.jinja2:9
+msgid "403.no_access"
+msgstr "Du hast keinen Zugang zu dieser Ressource."
+
+#: fietsboek/templates/403.jinja2:12
+msgid "403.try_log_in"
+msgstr ""
+"Falls Du Zugang haben solltest, stelle sicher, dass du korrekt angemeldet"
+" bist."
+
+#: fietsboek/templates/404.jinja2:5
+msgid "404.title"
+msgstr "Sackgasse"
+
+#: fietsboek/templates/404.jinja2:9
+msgid "404.path_not_found"
+msgstr "Der gesuchte Weg wurde nicht gefunden."
+
+#: fietsboek/templates/404.jinja2:12
+msgid "404.choose_different"
+msgstr "Bitte such einen anderen Weg."
+
#: fietsboek/templates/admin.jinja2:5
msgid "page.admin.title"
msgstr "Administration"
-#: fietsboek/templates/admin.jinja2:7
+#: fietsboek/templates/admin.jinja2:10
+msgid "page.admin.nav.overview"
+msgstr "Übersicht"
+
+#: fietsboek/templates/admin.jinja2:11
+msgid "page.admin.nav.badges"
+msgstr "Wappen"
+
+#: fietsboek/templates/admin_badges.jinja2:5
msgid "page.admin.badges"
msgstr "Wappen"
-#: fietsboek/templates/admin.jinja2:23
+#: fietsboek/templates/admin_badges.jinja2:21
msgid "page.admin.badge.edit"
msgstr "Bearbeiten"
-#: fietsboek/templates/admin.jinja2:29
+#: fietsboek/templates/admin_badges.jinja2:22
msgid "page.admin.badge.delete_badge"
msgstr "Löschen"
-#: fietsboek/templates/admin.jinja2:37
+#: fietsboek/templates/admin_badges.jinja2:35
msgid "page.admin.badges.badge_title"
msgstr "Titel"
-#: fietsboek/templates/admin.jinja2:41
+#: fietsboek/templates/admin_badges.jinja2:39
msgid "page.admin.badges.badge_image"
msgstr "Bild"
-#: fietsboek/templates/admin.jinja2:45
+#: fietsboek/templates/admin_badges.jinja2:43
msgid "page.admin.badges.add_badge"
msgstr "Hinzufügen"
+#: fietsboek/templates/admin_overview.jinja2:5
+msgid "admin.overview.instance_has"
+msgstr "Diese Instanz hat"
+
+#: fietsboek/templates/admin_overview.jinja2:9
+msgid "admin.overview.stat.user"
+msgid_plural "admin.overview.stat.users"
+msgstr[0] "%(num)d Nutzer:in"
+msgstr[1] "%(num)d Nutzer:innen"
+
+#: fietsboek/templates/admin_overview.jinja2:13
+msgid "admin.overview.stat.track"
+msgid_plural "admin.overview.stat.tracks"
+msgstr[0] "%(num)d Strecke"
+msgstr[1] "%(num)d Strecken"
+
+#: fietsboek/templates/admin_overview.jinja2:17
+msgid "admin.overview.stats.mib"
+msgstr "MiB an Daten"
+
+#: fietsboek/templates/admin_overview.jinja2:24
+msgid "admin.overview.system_overview"
+msgstr "Systemübersicht"
+
+#: fietsboek/templates/admin_overview.jinja2:28
+msgid "admin.overview.fietsboek_version"
+msgstr "Fietsboek-Version"
+
+#: fietsboek/templates/admin_overview.jinja2:32
+msgid "admin.overview.python_version"
+msgstr "Python-Version"
+
+#: fietsboek/templates/admin_overview.jinja2:36
+msgid "admin.overview.kernel_version"
+msgstr "Kernel-Version"
+
+#: fietsboek/templates/admin_overview.jinja2:40
+msgid "admin.overview.distro_version"
+msgstr "Distribution"
+
+#: fietsboek/templates/admin_overview.jinja2:44
+msgid "admin.overview.last_cronjob"
+msgstr "Letzter Cronjob"
+
+#: fietsboek/templates/admin_overview.jinja2:55
+msgid "admin.overview.storage_graph.label.gpx"
+msgstr "GPX"
+
+#: fietsboek/templates/admin_overview.jinja2:56
+msgid "admin.overview.storage_graph.label.images"
+msgstr "Bilder"
+
+#: fietsboek/templates/admin_overview.jinja2:57
+msgid "admin.overview.storage_graph.label.previews"
+msgstr "Vorschaubilder"
+
+#: fietsboek/templates/admin_overview.jinja2:58
+msgid "admin.overview.storage_graph.label.user_maps"
+msgstr "Nutzerkarten"
+
+#: fietsboek/templates/admin_overview.jinja2:84
+msgid "admin.overview.storage_graph.title"
+msgstr "Speicherübersicht"
+
#: fietsboek/templates/browse.jinja2:4
msgid "page.browse.title"
msgstr "Stöbern"
@@ -193,73 +291,81 @@ msgstr "Dies ist eine Aufnahme einer Strecke"
msgid "page.browse.synthetic_tooltip"
msgstr "Dies ist eine geplante Strecke"
-#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:103
-#: fietsboek/templates/profile.jinja2:15
+#: fietsboek/templates/browse.jinja2:162 fietsboek/templates/details.jinja2:103
+#: fietsboek/templates/profile_overview.jinja2:20
msgid "page.details.date"
msgstr "Datum"
-#: fietsboek/templates/browse.jinja2:160 fietsboek/templates/details.jinja2:117
-#: fietsboek/templates/profile.jinja2:17
+#: fietsboek/templates/browse.jinja2:164 fietsboek/templates/details.jinja2:117
+#: fietsboek/templates/profile_overview.jinja2:22
msgid "page.details.length"
msgstr "Länge"
-#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:108
-#: fietsboek/templates/profile.jinja2:21
+#: fietsboek/templates/browse.jinja2:169 fietsboek/templates/details.jinja2:108
+#: fietsboek/templates/profile_overview.jinja2:26
msgid "page.details.start_time"
msgstr "Startzeit"
-#: fietsboek/templates/browse.jinja2:167 fietsboek/templates/details.jinja2:112
-#: fietsboek/templates/profile.jinja2:23
+#: fietsboek/templates/browse.jinja2:171 fietsboek/templates/details.jinja2:112
+#: fietsboek/templates/profile_overview.jinja2:28
msgid "page.details.end_time"
msgstr "Endzeit"
-#: fietsboek/templates/browse.jinja2:172 fietsboek/templates/details.jinja2:121
-#: fietsboek/templates/profile.jinja2:27
+#: fietsboek/templates/browse.jinja2:176 fietsboek/templates/details.jinja2:121
+#: fietsboek/templates/profile_overview.jinja2:32
msgid "page.details.uphill"
msgstr "Bergauf"
-#: fietsboek/templates/browse.jinja2:174 fietsboek/templates/details.jinja2:125
-#: fietsboek/templates/profile.jinja2:29
+#: fietsboek/templates/browse.jinja2:178 fietsboek/templates/details.jinja2:125
+#: fietsboek/templates/profile_overview.jinja2:34
msgid "page.details.downhill"
msgstr "Bergab"
-#: fietsboek/templates/browse.jinja2:179 fietsboek/templates/details.jinja2:130
-#: fietsboek/templates/profile.jinja2:33
+#: fietsboek/templates/browse.jinja2:183 fietsboek/templates/details.jinja2:130
+#: fietsboek/templates/profile_overview.jinja2:38
msgid "page.details.moving_time"
msgstr "Fahrzeit"
-#: fietsboek/templates/browse.jinja2:181 fietsboek/templates/details.jinja2:134
-#: fietsboek/templates/profile.jinja2:35
+#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:134
+#: fietsboek/templates/profile_overview.jinja2:40
msgid "page.details.stopped_time"
msgstr "Haltezeit"
-#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:138
-#: fietsboek/templates/profile.jinja2:39
+#: fietsboek/templates/browse.jinja2:189 fietsboek/templates/details.jinja2:138
+#: fietsboek/templates/profile_overview.jinja2:44
msgid "page.details.max_speed"
msgstr "maximale Geschwindigkeit"
-#: fietsboek/templates/browse.jinja2:187 fietsboek/templates/details.jinja2:142
-#: fietsboek/templates/profile.jinja2:41
+#: fietsboek/templates/browse.jinja2:191 fietsboek/templates/details.jinja2:142
+#: fietsboek/templates/profile_overview.jinja2:46
msgid "page.details.avg_speed"
msgstr "durchschnittliche Geschwindigkeit"
-#: fietsboek/templates/browse.jinja2:192
+#: fietsboek/templates/browse.jinja2:196
msgid "page.browse.card.comments"
msgstr "Kommentare"
-#: fietsboek/templates/browse.jinja2:194
+#: fietsboek/templates/browse.jinja2:198
msgid "page.browse.card.images"
msgstr "Bilder"
-#: fietsboek/templates/browse.jinja2:211
+#: fietsboek/templates/browse.jinja2:216
msgid "page.browse.download_multiple"
msgstr "ausgewählte Herunterladen"
-#: fietsboek/templates/browse.jinja2:213
+#: fietsboek/templates/browse.jinja2:222 fietsboek/templates/browse.jinja2:226
+msgid "pagination.previous"
+msgstr "Vorherige"
+
+#: fietsboek/templates/browse.jinja2:231 fietsboek/templates/browse.jinja2:235
+msgid "pagination.next"
+msgstr "Nächste"
+
+#: fietsboek/templates/browse.jinja2:242
msgid "page.browse.no_results"
msgstr "Es wurden keine Strecken gefunden, die den Filtern entsprechen."
-#: fietsboek/templates/browse.jinja2:215
+#: fietsboek/templates/browse.jinja2:244
msgid "page.browse.no_tracks"
msgstr ""
"Es wurden keine Strecken gefunden, auf die Du Zugriff hast. Versuche, "
@@ -396,11 +502,15 @@ msgstr "Absenden"
msgid "page.edit.title"
msgstr "Strecke Bearbeiten"
-#: fietsboek/templates/edit.jinja2:16
+#: fietsboek/templates/edit.jinja2:14
+msgid "page.edit.form.new_track"
+msgstr "Neue Streckendatei auswählen"
+
+#: fietsboek/templates/edit.jinja2:20
msgid "page.edit.form.submit"
msgstr "Speichern"
-#: fietsboek/templates/edit.jinja2:17
+#: fietsboek/templates/edit.jinja2:21
msgid "page.edit.form.cancel"
msgstr "Abbrechen"
@@ -544,13 +654,13 @@ msgstr ""
"Links, um sie fortzusetzen:"
#: fietsboek/templates/home.jinja2:44 fietsboek/templates/home.jinja2:53
-#: fietsboek/templates/home.jinja2:97
+#: fietsboek/templates/home.jinja2:99
msgid "page.home.summary.track"
msgid_plural "page.home.summary.tracks"
msgstr[0] "%(num)d Strecke"
msgstr[1] "%(num)d Strecken"
-#: fietsboek/templates/home.jinja2:97
+#: fietsboek/templates/home.jinja2:99
msgid "page.home.total"
msgstr "Gesamt"
@@ -654,91 +764,91 @@ msgstr "Passwörter stimmen nicht überein"
msgid "page.password_reset.reset"
msgstr "Zurücksetzen"
-#: fietsboek/templates/profile.jinja2:64
+#: fietsboek/templates/profile.jinja2:10
msgid "page.profile.tabbar.overview"
msgstr "Übersicht"
-#: fietsboek/templates/profile.jinja2:69
+#: fietsboek/templates/profile.jinja2:15
msgid "page.profile.tabbar.graphs"
msgstr "Diagramme"
-#: fietsboek/templates/profile.jinja2:74
+#: fietsboek/templates/profile.jinja2:20
msgid "page.profile.tabbar.calendar"
msgstr "Kalender"
-#: fietsboek/templates/profile.jinja2:88
+#: fietsboek/templates/profile_calendar.jinja2:9
+msgid "page.profile.calendar.previous"
+msgstr "Vorheriger Monat"
+
+#: fietsboek/templates/profile_calendar.jinja2:11
+msgid "page.profile.calendar.next"
+msgstr "Nächster Monat"
+
+#: fietsboek/templates/profile_graphs.jinja2:6
+msgid "page.profile.graph.km_per_month"
+msgstr "Kilometer pro Monat"
+
+#: fietsboek/templates/profile_overview.jinja2:71
msgid "page.profile.length"
msgstr "Länge"
-#: fietsboek/templates/profile.jinja2:92
+#: fietsboek/templates/profile_overview.jinja2:75
msgid "page.profile.avg_length"
msgstr "durchschnittliche Länge"
-#: fietsboek/templates/profile.jinja2:96
+#: fietsboek/templates/profile_overview.jinja2:79
msgid "page.profile.uphill"
msgstr "Bergauf"
-#: fietsboek/templates/profile.jinja2:100
+#: fietsboek/templates/profile_overview.jinja2:83
msgid "page.profile.downhill"
msgstr "Bergab"
-#: fietsboek/templates/profile.jinja2:104
+#: fietsboek/templates/profile_overview.jinja2:87
msgid "page.profile.moving_time"
msgstr "Fahrzeit"
-#: fietsboek/templates/profile.jinja2:108
+#: fietsboek/templates/profile_overview.jinja2:91
msgid "page.profile.stopped_time"
msgstr "Haltezeit"
-#: fietsboek/templates/profile.jinja2:112
+#: fietsboek/templates/profile_overview.jinja2:95
msgid "page.profile.avg_duration"
msgstr "durchschnittliche Dauer"
-#: fietsboek/templates/profile.jinja2:116
+#: fietsboek/templates/profile_overview.jinja2:99
msgid "page.profile.max_speed"
msgstr "maximale Geschwindigkeit"
-#: fietsboek/templates/profile.jinja2:120
+#: fietsboek/templates/profile_overview.jinja2:103
msgid "page.profile.avg_speed"
msgstr "durchschnittliche Geschwindigkeit"
-#: fietsboek/templates/profile.jinja2:124
+#: fietsboek/templates/profile_overview.jinja2:107
msgid "page.profile.number_of_tracks"
msgstr "Anzahl der Strecken"
-#: fietsboek/templates/profile.jinja2:130
+#: fietsboek/templates/profile_overview.jinja2:113
msgid "page.profile.longest_distance_track"
msgstr "Weiteste Strecke"
-#: fietsboek/templates/profile.jinja2:135
+#: fietsboek/templates/profile_overview.jinja2:118
msgid "page.profile.shortest_distance_track"
msgstr "Kürzeste Strecke"
-#: fietsboek/templates/profile.jinja2:140
+#: fietsboek/templates/profile_overview.jinja2:123
msgid "page.profile.longest_duration_track"
msgstr "Am Längsten Dauernde Strecke"
-#: fietsboek/templates/profile.jinja2:145
+#: fietsboek/templates/profile_overview.jinja2:128
msgid "page.profile.shortest_duration_track"
msgstr "Am Kürzesten Dauernde Strecke"
-#: fietsboek/templates/profile.jinja2:152
-msgid "page.profile.graph.km_per_month"
-msgstr "Kilometer pro Monat"
-
-#: fietsboek/templates/profile.jinja2:161
-msgid "page.profile.calendar.previous"
-msgstr "Vorheriger Monat"
-
-#: fietsboek/templates/profile.jinja2:163
-msgid "page.profile.calendar.next"
-msgstr "Nächster Monat"
-
-#: fietsboek/templates/profile.jinja2:218
+#: fietsboek/templates/profile_overview.jinja2:149
msgid "page.profile.heatmap"
msgstr "Heatmap"
-#: fietsboek/templates/profile.jinja2:223
+#: fietsboek/templates/profile_overview.jinja2:157
msgid "page.profile.tilehunt"
msgstr "Kacheljäger"
@@ -888,15 +998,15 @@ msgstr "Ungültige E-Mail-Adresse"
msgid "flash.a_confirmation_link_has_been_sent"
msgstr "Ein Bestätigungslink wurde versandt"
-#: fietsboek/views/admin.py:49
+#: fietsboek/views/admin.py:156
msgid "flash.badge_added"
msgstr "Wappen hinzugefügt"
-#: fietsboek/views/admin.py:73
+#: fietsboek/views/admin.py:180
msgid "flash.badge_modified"
msgstr "Wappen bearbeitet"
-#: fietsboek/views/admin.py:93
+#: fietsboek/views/admin.py:200
msgid "flash.badge_deleted"
msgstr "Wappen gelöscht"
@@ -956,23 +1066,23 @@ msgstr "E-Mail-Adresse bestätigt"
msgid "flash.password_updated"
msgstr "Passwort aktualisiert"
-#: fietsboek/views/detail.py:162
+#: fietsboek/views/detail.py:166
msgid "flash.track_deleted"
msgstr "Strecke gelöscht"
-#: fietsboek/views/upload.py:53
-msgid "flash.no_file_selected"
-msgstr "Keine Datei ausgewählt"
-
-#: fietsboek/views/upload.py:66
+#: fietsboek/views/edit.py:100 fietsboek/views/upload.py:66
msgid "flash.invalid_file"
msgstr "Ungültige GPX-Datei gesendet"
-#: fietsboek/views/upload.py:192
+#: fietsboek/views/upload.py:54
+msgid "flash.no_file_selected"
+msgstr "Keine Datei ausgewählt"
+
+#: fietsboek/views/upload.py:194
msgid "flash.upload_success"
msgstr "Hochladen erfolgreich"
-#: fietsboek/views/upload.py:211
+#: fietsboek/views/upload.py:213
msgid "flash.upload_cancelled"
msgstr "Hochladen abgebrochen"
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.mo b/fietsboek/locale/en/LC_MESSAGES/messages.mo
index 5f8edc6..18f473c 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.mo
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.po b/fietsboek/locale/en/LC_MESSAGES/messages.po
index 981d134..89e183d 100644
--- a/fietsboek/locale/en/LC_MESSAGES/messages.po
+++ b/fietsboek/locale/en/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-01-30 21:50+0100\n"
+"POT-Creation-Date: 2025-06-12 22:39+0200\n"
"PO-Revision-Date: 2023-04-03 20:42+0200\n"
"Last-Translator: \n"
"Language: en\n"
@@ -16,87 +16,185 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.15.0\n"
+"Generated-By: Babel 2.17.0\n"
-#: fietsboek/actions.py:267
+#: fietsboek/actions.py:278
msgid "email.verify_mail.subject"
msgstr "Fietsboek Account Verification"
-#: fietsboek/actions.py:270
+#: fietsboek/actions.py:281
msgid "email.verify.text"
msgstr ""
"To verify your Fietsboek account, please use this link: {}\n"
"\n"
"If you did not create an account, ignore this email."
-#: fietsboek/util.py:333
+#: fietsboek/util.py:334
msgid "password_constraint.mismatch"
msgstr "Passwords don't match"
-#: fietsboek/util.py:335
+#: fietsboek/util.py:336
msgid "password_constraint.length"
msgstr "Password not long enough"
-#: fietsboek/models/track.py:603
+#: fietsboek/models/track.py:622
msgid "tooltip.table.length"
msgstr "Length"
-#: fietsboek/models/track.py:604
+#: fietsboek/models/track.py:623
msgid "tooltip.table.people"
msgstr "# People"
-#: fietsboek/models/track.py:605
+#: fietsboek/models/track.py:624
msgid "tooltip.table.uphill"
msgstr "Uphill"
-#: fietsboek/models/track.py:606
+#: fietsboek/models/track.py:625
msgid "tooltip.table.downhill"
msgstr "Downhill"
-#: fietsboek/models/track.py:607 fietsboek/templates/home.jinja2:7
+#: fietsboek/models/track.py:626 fietsboek/templates/home.jinja2:7
msgid "tooltip.table.moving_time"
msgstr "Moving Time"
-#: fietsboek/models/track.py:608 fietsboek/templates/home.jinja2:8
+#: fietsboek/models/track.py:627 fietsboek/templates/home.jinja2:8
msgid "tooltip.table.stopped_time"
msgstr "Stopped Time"
-#: fietsboek/models/track.py:610
+#: fietsboek/models/track.py:629
msgid "tooltip.table.max_speed"
msgstr "Max Speed"
-#: fietsboek/models/track.py:614
+#: fietsboek/models/track.py:633
msgid "tooltip.table.avg_speed"
msgstr "Average Speed"
+#: fietsboek/templates/403.jinja2:5
+msgid "403.title"
+msgstr "No entry"
+
+#: fietsboek/templates/403.jinja2:9
+msgid "403.no_access"
+msgstr "You are not allowed to access this resource."
+
+#: fietsboek/templates/403.jinja2:12
+msgid "403.try_log_in"
+msgstr ""
+"If you should have access, make sure you are logged in with the right "
+"credentials."
+
+#: fietsboek/templates/404.jinja2:5
+msgid "404.title"
+msgstr "Dead end"
+
+#: fietsboek/templates/404.jinja2:9
+msgid "404.path_not_found"
+msgstr "The path you have chosen was not found."
+
+#: fietsboek/templates/404.jinja2:12
+msgid "404.choose_different"
+msgstr "Please choose a different path."
+
#: fietsboek/templates/admin.jinja2:5
msgid "page.admin.title"
msgstr "Administration"
-#: fietsboek/templates/admin.jinja2:7
+#: fietsboek/templates/admin.jinja2:10
+msgid "page.admin.nav.overview"
+msgstr "Overview"
+
+#: fietsboek/templates/admin.jinja2:11
+msgid "page.admin.nav.badges"
+msgstr "Badges"
+
+#: fietsboek/templates/admin_badges.jinja2:5
msgid "page.admin.badges"
msgstr "Badges"
-#: fietsboek/templates/admin.jinja2:23
+#: fietsboek/templates/admin_badges.jinja2:21
msgid "page.admin.badge.edit"
msgstr "Edit"
-#: fietsboek/templates/admin.jinja2:29
+#: fietsboek/templates/admin_badges.jinja2:22
msgid "page.admin.badge.delete_badge"
msgstr "Delete badge"
-#: fietsboek/templates/admin.jinja2:37
+#: fietsboek/templates/admin_badges.jinja2:35
msgid "page.admin.badges.badge_title"
msgstr "Badge Title"
-#: fietsboek/templates/admin.jinja2:41
+#: fietsboek/templates/admin_badges.jinja2:39
msgid "page.admin.badges.badge_image"
msgstr "Badge Image"
-#: fietsboek/templates/admin.jinja2:45
+#: fietsboek/templates/admin_badges.jinja2:43
msgid "page.admin.badges.add_badge"
msgstr "Add Badge"
+#: fietsboek/templates/admin_overview.jinja2:5
+msgid "admin.overview.instance_has"
+msgstr "This instance has"
+
+#: fietsboek/templates/admin_overview.jinja2:9
+msgid "admin.overview.stat.user"
+msgid_plural "admin.overview.stat.users"
+msgstr[0] "%(num)d user"
+msgstr[1] "%(num)d users"
+
+#: fietsboek/templates/admin_overview.jinja2:13
+msgid "admin.overview.stat.track"
+msgid_plural "admin.overview.stat.tracks"
+msgstr[0] "%(num)d track"
+msgstr[1] "%(num)d tracks"
+
+#: fietsboek/templates/admin_overview.jinja2:17
+msgid "admin.overview.stats.mib"
+msgstr "MiB of data"
+
+#: fietsboek/templates/admin_overview.jinja2:24
+msgid "admin.overview.system_overview"
+msgstr "System information"
+
+#: fietsboek/templates/admin_overview.jinja2:28
+msgid "admin.overview.fietsboek_version"
+msgstr "Fietsboek version"
+
+#: fietsboek/templates/admin_overview.jinja2:32
+msgid "admin.overview.python_version"
+msgstr "Python version"
+
+#: fietsboek/templates/admin_overview.jinja2:36
+msgid "admin.overview.kernel_version"
+msgstr "Linux version"
+
+#: fietsboek/templates/admin_overview.jinja2:40
+msgid "admin.overview.distro_version"
+msgstr "Distribution"
+
+#: fietsboek/templates/admin_overview.jinja2:44
+msgid "admin.overview.last_cronjob"
+msgstr "Last cronjob"
+
+#: fietsboek/templates/admin_overview.jinja2:55
+msgid "admin.overview.storage_graph.label.gpx"
+msgstr "GPX"
+
+#: fietsboek/templates/admin_overview.jinja2:56
+msgid "admin.overview.storage_graph.label.images"
+msgstr "Images"
+
+#: fietsboek/templates/admin_overview.jinja2:57
+msgid "admin.overview.storage_graph.label.previews"
+msgstr "Preview images"
+
+#: fietsboek/templates/admin_overview.jinja2:58
+msgid "admin.overview.storage_graph.label.user_maps"
+msgstr "User maps"
+
+#: fietsboek/templates/admin_overview.jinja2:84
+msgid "admin.overview.storage_graph.title"
+msgstr "Storage breakdown"
+
#: fietsboek/templates/browse.jinja2:4
msgid "page.browse.title"
msgstr "Browse"
@@ -193,73 +291,81 @@ msgstr "This is a recording of a track"
msgid "page.browse.synthetic_tooltip"
msgstr "This is a pre-planned track"
-#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:103
-#: fietsboek/templates/profile.jinja2:15
+#: fietsboek/templates/browse.jinja2:162 fietsboek/templates/details.jinja2:103
+#: fietsboek/templates/profile_overview.jinja2:20
msgid "page.details.date"
msgstr "Date"
-#: fietsboek/templates/browse.jinja2:160 fietsboek/templates/details.jinja2:117
-#: fietsboek/templates/profile.jinja2:17
+#: fietsboek/templates/browse.jinja2:164 fietsboek/templates/details.jinja2:117
+#: fietsboek/templates/profile_overview.jinja2:22
msgid "page.details.length"
msgstr "Length"
-#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:108
-#: fietsboek/templates/profile.jinja2:21
+#: fietsboek/templates/browse.jinja2:169 fietsboek/templates/details.jinja2:108
+#: fietsboek/templates/profile_overview.jinja2:26
msgid "page.details.start_time"
msgstr "Record Start"
-#: fietsboek/templates/browse.jinja2:167 fietsboek/templates/details.jinja2:112
-#: fietsboek/templates/profile.jinja2:23
+#: fietsboek/templates/browse.jinja2:171 fietsboek/templates/details.jinja2:112
+#: fietsboek/templates/profile_overview.jinja2:28
msgid "page.details.end_time"
msgstr "Record End"
-#: fietsboek/templates/browse.jinja2:172 fietsboek/templates/details.jinja2:121
-#: fietsboek/templates/profile.jinja2:27
+#: fietsboek/templates/browse.jinja2:176 fietsboek/templates/details.jinja2:121
+#: fietsboek/templates/profile_overview.jinja2:32
msgid "page.details.uphill"
msgstr "Uphill"
-#: fietsboek/templates/browse.jinja2:174 fietsboek/templates/details.jinja2:125
-#: fietsboek/templates/profile.jinja2:29
+#: fietsboek/templates/browse.jinja2:178 fietsboek/templates/details.jinja2:125
+#: fietsboek/templates/profile_overview.jinja2:34
msgid "page.details.downhill"
msgstr "Downhill"
-#: fietsboek/templates/browse.jinja2:179 fietsboek/templates/details.jinja2:130
-#: fietsboek/templates/profile.jinja2:33
+#: fietsboek/templates/browse.jinja2:183 fietsboek/templates/details.jinja2:130
+#: fietsboek/templates/profile_overview.jinja2:38
msgid "page.details.moving_time"
msgstr "Moving Time"
-#: fietsboek/templates/browse.jinja2:181 fietsboek/templates/details.jinja2:134
-#: fietsboek/templates/profile.jinja2:35
+#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:134
+#: fietsboek/templates/profile_overview.jinja2:40
msgid "page.details.stopped_time"
msgstr "Stopped Time"
-#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:138
-#: fietsboek/templates/profile.jinja2:39
+#: fietsboek/templates/browse.jinja2:189 fietsboek/templates/details.jinja2:138
+#: fietsboek/templates/profile_overview.jinja2:44
msgid "page.details.max_speed"
msgstr "Max Speed"
-#: fietsboek/templates/browse.jinja2:187 fietsboek/templates/details.jinja2:142
-#: fietsboek/templates/profile.jinja2:41
+#: fietsboek/templates/browse.jinja2:191 fietsboek/templates/details.jinja2:142
+#: fietsboek/templates/profile_overview.jinja2:46
msgid "page.details.avg_speed"
msgstr "Average Speed"
-#: fietsboek/templates/browse.jinja2:192
+#: fietsboek/templates/browse.jinja2:196
msgid "page.browse.card.comments"
msgstr "Comments"
-#: fietsboek/templates/browse.jinja2:194
+#: fietsboek/templates/browse.jinja2:198
msgid "page.browse.card.images"
msgstr "Images"
-#: fietsboek/templates/browse.jinja2:211
+#: fietsboek/templates/browse.jinja2:216
msgid "page.browse.download_multiple"
msgstr "Download selected"
-#: fietsboek/templates/browse.jinja2:213
+#: fietsboek/templates/browse.jinja2:222 fietsboek/templates/browse.jinja2:226
+msgid "pagination.previous"
+msgstr "Previous"
+
+#: fietsboek/templates/browse.jinja2:231 fietsboek/templates/browse.jinja2:235
+msgid "pagination.next"
+msgstr "Next"
+
+#: fietsboek/templates/browse.jinja2:242
msgid "page.browse.no_results"
msgstr "No results matching the filters were found."
-#: fietsboek/templates/browse.jinja2:215
+#: fietsboek/templates/browse.jinja2:244
msgid "page.browse.no_tracks"
msgstr "You currently do not have access to any tracks. Try logging in."
@@ -392,11 +498,15 @@ msgstr "Submit"
msgid "page.edit.title"
msgstr "Edit Track"
-#: fietsboek/templates/edit.jinja2:16
+#: fietsboek/templates/edit.jinja2:14
+msgid "page.edit.form.new_track"
+msgstr "New file for this track"
+
+#: fietsboek/templates/edit.jinja2:20
msgid "page.edit.form.submit"
msgstr "Save"
-#: fietsboek/templates/edit.jinja2:17
+#: fietsboek/templates/edit.jinja2:21
msgid "page.edit.form.cancel"
msgstr "Cancel"
@@ -538,13 +648,13 @@ msgid "page.home.unfinished_uploads"
msgstr "You have unfinished uploads. Click on the links below to resume them:"
#: fietsboek/templates/home.jinja2:44 fietsboek/templates/home.jinja2:53
-#: fietsboek/templates/home.jinja2:97
+#: fietsboek/templates/home.jinja2:99
msgid "page.home.summary.track"
msgid_plural "page.home.summary.tracks"
msgstr[0] "%(num)d track"
msgstr[1] "%(num)d tracks"
-#: fietsboek/templates/home.jinja2:97
+#: fietsboek/templates/home.jinja2:99
msgid "page.home.total"
msgstr "Total"
@@ -648,91 +758,91 @@ msgstr "Passwords must match"
msgid "page.password_reset.reset"
msgstr "Reset"
-#: fietsboek/templates/profile.jinja2:64
+#: fietsboek/templates/profile.jinja2:10
msgid "page.profile.tabbar.overview"
msgstr "Overview"
-#: fietsboek/templates/profile.jinja2:69
+#: fietsboek/templates/profile.jinja2:15
msgid "page.profile.tabbar.graphs"
msgstr "Graphs"
-#: fietsboek/templates/profile.jinja2:74
+#: fietsboek/templates/profile.jinja2:20
msgid "page.profile.tabbar.calendar"
msgstr "Calendar"
-#: fietsboek/templates/profile.jinja2:88
+#: fietsboek/templates/profile_calendar.jinja2:9
+msgid "page.profile.calendar.previous"
+msgstr "Previous month"
+
+#: fietsboek/templates/profile_calendar.jinja2:11
+msgid "page.profile.calendar.next"
+msgstr "Next month"
+
+#: fietsboek/templates/profile_graphs.jinja2:6
+msgid "page.profile.graph.km_per_month"
+msgstr "Kilometers per month"
+
+#: fietsboek/templates/profile_overview.jinja2:71
msgid "page.profile.length"
msgstr "Length"
-#: fietsboek/templates/profile.jinja2:92
+#: fietsboek/templates/profile_overview.jinja2:75
msgid "page.profile.avg_length"
msgstr "Average Length"
-#: fietsboek/templates/profile.jinja2:96
+#: fietsboek/templates/profile_overview.jinja2:79
msgid "page.profile.uphill"
msgstr "Uphill"
-#: fietsboek/templates/profile.jinja2:100
+#: fietsboek/templates/profile_overview.jinja2:83
msgid "page.profile.downhill"
msgstr "Downhill"
-#: fietsboek/templates/profile.jinja2:104
+#: fietsboek/templates/profile_overview.jinja2:87
msgid "page.profile.moving_time"
msgstr "Moving Time"
-#: fietsboek/templates/profile.jinja2:108
+#: fietsboek/templates/profile_overview.jinja2:91
msgid "page.profile.stopped_time"
msgstr "Stopped Time"
-#: fietsboek/templates/profile.jinja2:112
+#: fietsboek/templates/profile_overview.jinja2:95
msgid "page.profile.avg_duration"
msgstr "Average Duration"
-#: fietsboek/templates/profile.jinja2:116
+#: fietsboek/templates/profile_overview.jinja2:99
msgid "page.profile.max_speed"
msgstr "Max Speed"
-#: fietsboek/templates/profile.jinja2:120
+#: fietsboek/templates/profile_overview.jinja2:103
msgid "page.profile.avg_speed"
msgstr "Average Speed"
-#: fietsboek/templates/profile.jinja2:124
+#: fietsboek/templates/profile_overview.jinja2:107
msgid "page.profile.number_of_tracks"
msgstr "Number of tracks"
-#: fietsboek/templates/profile.jinja2:130
+#: fietsboek/templates/profile_overview.jinja2:113
msgid "page.profile.longest_distance_track"
msgstr "Longest Track"
-#: fietsboek/templates/profile.jinja2:135
+#: fietsboek/templates/profile_overview.jinja2:118
msgid "page.profile.shortest_distance_track"
msgstr "Shortest Track"
-#: fietsboek/templates/profile.jinja2:140
+#: fietsboek/templates/profile_overview.jinja2:123
msgid "page.profile.longest_duration_track"
msgstr "Most Time-Consuming Track"
-#: fietsboek/templates/profile.jinja2:145
+#: fietsboek/templates/profile_overview.jinja2:128
msgid "page.profile.shortest_duration_track"
msgstr "Quickest Track"
-#: fietsboek/templates/profile.jinja2:152
-msgid "page.profile.graph.km_per_month"
-msgstr "Kilometers per month"
-
-#: fietsboek/templates/profile.jinja2:161
-msgid "page.profile.calendar.previous"
-msgstr "Previous month"
-
-#: fietsboek/templates/profile.jinja2:163
-msgid "page.profile.calendar.next"
-msgstr "Next month"
-
-#: fietsboek/templates/profile.jinja2:218
+#: fietsboek/templates/profile_overview.jinja2:149
msgid "page.profile.heatmap"
msgstr "Heat Map"
-#: fietsboek/templates/profile.jinja2:223
+#: fietsboek/templates/profile_overview.jinja2:157
msgid "page.profile.tilehunt"
msgstr "Tilehunt"
@@ -878,15 +988,15 @@ msgstr "Invalid email"
msgid "flash.a_confirmation_link_has_been_sent"
msgstr "A confirmation link has been sent"
-#: fietsboek/views/admin.py:49
+#: fietsboek/views/admin.py:156
msgid "flash.badge_added"
msgstr "Badge has been added"
-#: fietsboek/views/admin.py:73
+#: fietsboek/views/admin.py:180
msgid "flash.badge_modified"
msgstr "Badge has been modified"
-#: fietsboek/views/admin.py:93
+#: fietsboek/views/admin.py:200
msgid "flash.badge_deleted"
msgstr "Badge has been deleted"
@@ -945,23 +1055,23 @@ msgstr "Your email address has been verified"
msgid "flash.password_updated"
msgstr "Password has been updated"
-#: fietsboek/views/detail.py:162
+#: fietsboek/views/detail.py:166
msgid "flash.track_deleted"
msgstr "Track has been deleted"
-#: fietsboek/views/upload.py:53
-msgid "flash.no_file_selected"
-msgstr "No file selected"
-
-#: fietsboek/views/upload.py:66
+#: fietsboek/views/edit.py:100 fietsboek/views/upload.py:66
msgid "flash.invalid_file"
msgstr "Invalid GPX file selected"
-#: fietsboek/views/upload.py:192
+#: fietsboek/views/upload.py:54
+msgid "flash.no_file_selected"
+msgstr "No file selected"
+
+#: fietsboek/views/upload.py:194
msgid "flash.upload_success"
msgstr "Upload successful"
-#: fietsboek/views/upload.py:211
+#: fietsboek/views/upload.py:213
msgid "flash.upload_cancelled"
msgstr "Upload cancelled"
diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot
index 60c77a5..6383760 100644
--- a/fietsboek/locale/fietslog.pot
+++ b/fietsboek/locale/fietslog.pot
@@ -8,91 +8,187 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-01-30 21:50+0100\n"
+"POT-Creation-Date: 2025-06-12 22:39+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.15.0\n"
+"Generated-By: Babel 2.17.0\n"
-#: fietsboek/actions.py:267
+#: fietsboek/actions.py:278
msgid "email.verify_mail.subject"
msgstr ""
-#: fietsboek/actions.py:270
+#: fietsboek/actions.py:281
msgid "email.verify.text"
msgstr ""
-#: fietsboek/util.py:333
+#: fietsboek/util.py:334
msgid "password_constraint.mismatch"
msgstr ""
-#: fietsboek/util.py:335
+#: fietsboek/util.py:336
msgid "password_constraint.length"
msgstr ""
-#: fietsboek/models/track.py:603
+#: fietsboek/models/track.py:622
msgid "tooltip.table.length"
msgstr ""
-#: fietsboek/models/track.py:604
+#: fietsboek/models/track.py:623
msgid "tooltip.table.people"
msgstr ""
-#: fietsboek/models/track.py:605
+#: fietsboek/models/track.py:624
msgid "tooltip.table.uphill"
msgstr ""
-#: fietsboek/models/track.py:606
+#: fietsboek/models/track.py:625
msgid "tooltip.table.downhill"
msgstr ""
-#: fietsboek/models/track.py:607 fietsboek/templates/home.jinja2:7
+#: fietsboek/models/track.py:626 fietsboek/templates/home.jinja2:7
msgid "tooltip.table.moving_time"
msgstr ""
-#: fietsboek/models/track.py:608 fietsboek/templates/home.jinja2:8
+#: fietsboek/models/track.py:627 fietsboek/templates/home.jinja2:8
msgid "tooltip.table.stopped_time"
msgstr ""
-#: fietsboek/models/track.py:610
+#: fietsboek/models/track.py:629
msgid "tooltip.table.max_speed"
msgstr ""
-#: fietsboek/models/track.py:614
+#: fietsboek/models/track.py:633
msgid "tooltip.table.avg_speed"
msgstr ""
+#: fietsboek/templates/403.jinja2:5
+msgid "403.title"
+msgstr ""
+
+#: fietsboek/templates/403.jinja2:9
+msgid "403.no_access"
+msgstr ""
+
+#: fietsboek/templates/403.jinja2:12
+msgid "403.try_log_in"
+msgstr ""
+
+#: fietsboek/templates/404.jinja2:5
+msgid "404.title"
+msgstr ""
+
+#: fietsboek/templates/404.jinja2:9
+msgid "404.path_not_found"
+msgstr ""
+
+#: fietsboek/templates/404.jinja2:12
+msgid "404.choose_different"
+msgstr ""
+
#: fietsboek/templates/admin.jinja2:5
msgid "page.admin.title"
msgstr ""
-#: fietsboek/templates/admin.jinja2:7
+#: fietsboek/templates/admin.jinja2:10
+msgid "page.admin.nav.overview"
+msgstr ""
+
+#: fietsboek/templates/admin.jinja2:11
+msgid "page.admin.nav.badges"
+msgstr ""
+
+#: fietsboek/templates/admin_badges.jinja2:5
msgid "page.admin.badges"
msgstr ""
-#: fietsboek/templates/admin.jinja2:23
+#: fietsboek/templates/admin_badges.jinja2:21
msgid "page.admin.badge.edit"
msgstr ""
-#: fietsboek/templates/admin.jinja2:29
+#: fietsboek/templates/admin_badges.jinja2:22
msgid "page.admin.badge.delete_badge"
msgstr ""
-#: fietsboek/templates/admin.jinja2:37
+#: fietsboek/templates/admin_badges.jinja2:35
msgid "page.admin.badges.badge_title"
msgstr ""
-#: fietsboek/templates/admin.jinja2:41
+#: fietsboek/templates/admin_badges.jinja2:39
msgid "page.admin.badges.badge_image"
msgstr ""
-#: fietsboek/templates/admin.jinja2:45
+#: fietsboek/templates/admin_badges.jinja2:43
msgid "page.admin.badges.add_badge"
msgstr ""
+#: fietsboek/templates/admin_overview.jinja2:5
+msgid "admin.overview.instance_has"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:9
+msgid "admin.overview.stat.user"
+msgid_plural "admin.overview.stat.users"
+msgstr[0] ""
+msgstr[1] ""
+
+#: fietsboek/templates/admin_overview.jinja2:13
+msgid "admin.overview.stat.track"
+msgid_plural "admin.overview.stat.tracks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: fietsboek/templates/admin_overview.jinja2:17
+msgid "admin.overview.stats.mib"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:24
+msgid "admin.overview.system_overview"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:28
+msgid "admin.overview.fietsboek_version"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:32
+msgid "admin.overview.python_version"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:36
+msgid "admin.overview.kernel_version"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:40
+msgid "admin.overview.distro_version"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:44
+msgid "admin.overview.last_cronjob"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:55
+msgid "admin.overview.storage_graph.label.gpx"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:56
+msgid "admin.overview.storage_graph.label.images"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:57
+msgid "admin.overview.storage_graph.label.previews"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:58
+msgid "admin.overview.storage_graph.label.user_maps"
+msgstr ""
+
+#: fietsboek/templates/admin_overview.jinja2:84
+msgid "admin.overview.storage_graph.title"
+msgstr ""
+
#: fietsboek/templates/browse.jinja2:4
msgid "page.browse.title"
msgstr ""
@@ -189,73 +285,81 @@ msgstr ""
msgid "page.browse.synthetic_tooltip"
msgstr ""
-#: fietsboek/templates/browse.jinja2:158 fietsboek/templates/details.jinja2:103
-#: fietsboek/templates/profile.jinja2:15
+#: fietsboek/templates/browse.jinja2:162 fietsboek/templates/details.jinja2:103
+#: fietsboek/templates/profile_overview.jinja2:20
msgid "page.details.date"
msgstr ""
-#: fietsboek/templates/browse.jinja2:160 fietsboek/templates/details.jinja2:117
-#: fietsboek/templates/profile.jinja2:17
+#: fietsboek/templates/browse.jinja2:164 fietsboek/templates/details.jinja2:117
+#: fietsboek/templates/profile_overview.jinja2:22
msgid "page.details.length"
msgstr ""
-#: fietsboek/templates/browse.jinja2:165 fietsboek/templates/details.jinja2:108
-#: fietsboek/templates/profile.jinja2:21
+#: fietsboek/templates/browse.jinja2:169 fietsboek/templates/details.jinja2:108
+#: fietsboek/templates/profile_overview.jinja2:26
msgid "page.details.start_time"
msgstr ""
-#: fietsboek/templates/browse.jinja2:167 fietsboek/templates/details.jinja2:112
-#: fietsboek/templates/profile.jinja2:23
+#: fietsboek/templates/browse.jinja2:171 fietsboek/templates/details.jinja2:112
+#: fietsboek/templates/profile_overview.jinja2:28
msgid "page.details.end_time"
msgstr ""
-#: fietsboek/templates/browse.jinja2:172 fietsboek/templates/details.jinja2:121
-#: fietsboek/templates/profile.jinja2:27
+#: fietsboek/templates/browse.jinja2:176 fietsboek/templates/details.jinja2:121
+#: fietsboek/templates/profile_overview.jinja2:32
msgid "page.details.uphill"
msgstr ""
-#: fietsboek/templates/browse.jinja2:174 fietsboek/templates/details.jinja2:125
-#: fietsboek/templates/profile.jinja2:29
+#: fietsboek/templates/browse.jinja2:178 fietsboek/templates/details.jinja2:125
+#: fietsboek/templates/profile_overview.jinja2:34
msgid "page.details.downhill"
msgstr ""
-#: fietsboek/templates/browse.jinja2:179 fietsboek/templates/details.jinja2:130
-#: fietsboek/templates/profile.jinja2:33
+#: fietsboek/templates/browse.jinja2:183 fietsboek/templates/details.jinja2:130
+#: fietsboek/templates/profile_overview.jinja2:38
msgid "page.details.moving_time"
msgstr ""
-#: fietsboek/templates/browse.jinja2:181 fietsboek/templates/details.jinja2:134
-#: fietsboek/templates/profile.jinja2:35
+#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:134
+#: fietsboek/templates/profile_overview.jinja2:40
msgid "page.details.stopped_time"
msgstr ""
-#: fietsboek/templates/browse.jinja2:185 fietsboek/templates/details.jinja2:138
-#: fietsboek/templates/profile.jinja2:39
+#: fietsboek/templates/browse.jinja2:189 fietsboek/templates/details.jinja2:138
+#: fietsboek/templates/profile_overview.jinja2:44
msgid "page.details.max_speed"
msgstr ""
-#: fietsboek/templates/browse.jinja2:187 fietsboek/templates/details.jinja2:142
-#: fietsboek/templates/profile.jinja2:41
+#: fietsboek/templates/browse.jinja2:191 fietsboek/templates/details.jinja2:142
+#: fietsboek/templates/profile_overview.jinja2:46
msgid "page.details.avg_speed"
msgstr ""
-#: fietsboek/templates/browse.jinja2:192
+#: fietsboek/templates/browse.jinja2:196
msgid "page.browse.card.comments"
msgstr ""
-#: fietsboek/templates/browse.jinja2:194
+#: fietsboek/templates/browse.jinja2:198
msgid "page.browse.card.images"
msgstr ""
-#: fietsboek/templates/browse.jinja2:211
+#: fietsboek/templates/browse.jinja2:216
msgid "page.browse.download_multiple"
msgstr ""
-#: fietsboek/templates/browse.jinja2:213
+#: fietsboek/templates/browse.jinja2:222 fietsboek/templates/browse.jinja2:226
+msgid "pagination.previous"
+msgstr ""
+
+#: fietsboek/templates/browse.jinja2:231 fietsboek/templates/browse.jinja2:235
+msgid "pagination.next"
+msgstr ""
+
+#: fietsboek/templates/browse.jinja2:242
msgid "page.browse.no_results"
msgstr ""
-#: fietsboek/templates/browse.jinja2:215
+#: fietsboek/templates/browse.jinja2:244
msgid "page.browse.no_tracks"
msgstr ""
@@ -388,11 +492,15 @@ msgstr ""
msgid "page.edit.title"
msgstr ""
-#: fietsboek/templates/edit.jinja2:16
+#: fietsboek/templates/edit.jinja2:14
+msgid "page.edit.form.new_track"
+msgstr ""
+
+#: fietsboek/templates/edit.jinja2:20
msgid "page.edit.form.submit"
msgstr ""
-#: fietsboek/templates/edit.jinja2:17
+#: fietsboek/templates/edit.jinja2:21
msgid "page.edit.form.cancel"
msgstr ""
@@ -532,13 +640,13 @@ msgid "page.home.unfinished_uploads"
msgstr ""
#: fietsboek/templates/home.jinja2:44 fietsboek/templates/home.jinja2:53
-#: fietsboek/templates/home.jinja2:97
+#: fietsboek/templates/home.jinja2:99
msgid "page.home.summary.track"
msgid_plural "page.home.summary.tracks"
msgstr[0] ""
msgstr[1] ""
-#: fietsboek/templates/home.jinja2:97
+#: fietsboek/templates/home.jinja2:99
msgid "page.home.total"
msgstr ""
@@ -642,91 +750,91 @@ msgstr ""
msgid "page.password_reset.reset"
msgstr ""
-#: fietsboek/templates/profile.jinja2:64
+#: fietsboek/templates/profile.jinja2:10
msgid "page.profile.tabbar.overview"
msgstr ""
-#: fietsboek/templates/profile.jinja2:69
+#: fietsboek/templates/profile.jinja2:15
msgid "page.profile.tabbar.graphs"
msgstr ""
-#: fietsboek/templates/profile.jinja2:74
+#: fietsboek/templates/profile.jinja2:20
msgid "page.profile.tabbar.calendar"
msgstr ""
-#: fietsboek/templates/profile.jinja2:88
+#: fietsboek/templates/profile_calendar.jinja2:9
+msgid "page.profile.calendar.previous"
+msgstr ""
+
+#: fietsboek/templates/profile_calendar.jinja2:11
+msgid "page.profile.calendar.next"
+msgstr ""
+
+#: fietsboek/templates/profile_graphs.jinja2:6
+msgid "page.profile.graph.km_per_month"
+msgstr ""
+
+#: fietsboek/templates/profile_overview.jinja2:71
msgid "page.profile.length"
msgstr ""
-#: fietsboek/templates/profile.jinja2:92
+#: fietsboek/templates/profile_overview.jinja2:75
msgid "page.profile.avg_length"
msgstr ""
-#: fietsboek/templates/profile.jinja2:96
+#: fietsboek/templates/profile_overview.jinja2:79
msgid "page.profile.uphill"
msgstr ""
-#: fietsboek/templates/profile.jinja2:100
+#: fietsboek/templates/profile_overview.jinja2:83
msgid "page.profile.downhill"
msgstr ""
-#: fietsboek/templates/profile.jinja2:104
+#: fietsboek/templates/profile_overview.jinja2:87
msgid "page.profile.moving_time"
msgstr ""
-#: fietsboek/templates/profile.jinja2:108
+#: fietsboek/templates/profile_overview.jinja2:91
msgid "page.profile.stopped_time"
msgstr ""
-#: fietsboek/templates/profile.jinja2:112
+#: fietsboek/templates/profile_overview.jinja2:95
msgid "page.profile.avg_duration"
msgstr ""
-#: fietsboek/templates/profile.jinja2:116
+#: fietsboek/templates/profile_overview.jinja2:99
msgid "page.profile.max_speed"
msgstr ""
-#: fietsboek/templates/profile.jinja2:120
+#: fietsboek/templates/profile_overview.jinja2:103
msgid "page.profile.avg_speed"
msgstr ""
-#: fietsboek/templates/profile.jinja2:124
+#: fietsboek/templates/profile_overview.jinja2:107
msgid "page.profile.number_of_tracks"
msgstr ""
-#: fietsboek/templates/profile.jinja2:130
+#: fietsboek/templates/profile_overview.jinja2:113
msgid "page.profile.longest_distance_track"
msgstr ""
-#: fietsboek/templates/profile.jinja2:135
+#: fietsboek/templates/profile_overview.jinja2:118
msgid "page.profile.shortest_distance_track"
msgstr ""
-#: fietsboek/templates/profile.jinja2:140
+#: fietsboek/templates/profile_overview.jinja2:123
msgid "page.profile.longest_duration_track"
msgstr ""
-#: fietsboek/templates/profile.jinja2:145
+#: fietsboek/templates/profile_overview.jinja2:128
msgid "page.profile.shortest_duration_track"
msgstr ""
-#: fietsboek/templates/profile.jinja2:152
-msgid "page.profile.graph.km_per_month"
-msgstr ""
-
-#: fietsboek/templates/profile.jinja2:161
-msgid "page.profile.calendar.previous"
-msgstr ""
-
-#: fietsboek/templates/profile.jinja2:163
-msgid "page.profile.calendar.next"
-msgstr ""
-
-#: fietsboek/templates/profile.jinja2:218
+#: fietsboek/templates/profile_overview.jinja2:149
msgid "page.profile.heatmap"
msgstr ""
-#: fietsboek/templates/profile.jinja2:223
+#: fietsboek/templates/profile_overview.jinja2:157
msgid "page.profile.tilehunt"
msgstr ""
@@ -866,15 +974,15 @@ msgstr ""
msgid "flash.a_confirmation_link_has_been_sent"
msgstr ""
-#: fietsboek/views/admin.py:49
+#: fietsboek/views/admin.py:156
msgid "flash.badge_added"
msgstr ""
-#: fietsboek/views/admin.py:73
+#: fietsboek/views/admin.py:180
msgid "flash.badge_modified"
msgstr ""
-#: fietsboek/views/admin.py:93
+#: fietsboek/views/admin.py:200
msgid "flash.badge_deleted"
msgstr ""
@@ -930,23 +1038,23 @@ msgstr ""
msgid "flash.password_updated"
msgstr ""
-#: fietsboek/views/detail.py:162
+#: fietsboek/views/detail.py:166
msgid "flash.track_deleted"
msgstr ""
-#: fietsboek/views/upload.py:53
-msgid "flash.no_file_selected"
+#: fietsboek/views/edit.py:100 fietsboek/views/upload.py:66
+msgid "flash.invalid_file"
msgstr ""
-#: fietsboek/views/upload.py:66
-msgid "flash.invalid_file"
+#: fietsboek/views/upload.py:54
+msgid "flash.no_file_selected"
msgstr ""
-#: fietsboek/views/upload.py:192
+#: fietsboek/views/upload.py:194
msgid "flash.upload_success"
msgstr ""
-#: fietsboek/views/upload.py:211
+#: fietsboek/views/upload.py:213
msgid "flash.upload_cancelled"
msgstr ""
diff --git a/fietsboek/models/track.py b/fietsboek/models/track.py
index 0737982..0921437 100644
--- a/fietsboek/models/track.py
+++ b/fietsboek/models/track.py
@@ -15,11 +15,13 @@ meta information.
import datetime
import enum
import gzip
+import json
import logging
from itertools import chain
from typing import TYPE_CHECKING, Optional, Union
import gpxpy
+import sqlalchemy.types
from babel.numbers import format_decimal
from markupsafe import Markup
from pyramid.authorization import (
@@ -34,7 +36,6 @@ from pyramid.httpexceptions import HTTPNotFound
from pyramid.i18n import Localizer
from pyramid.i18n import TranslationString as _
from sqlalchemy import (
- JSON,
Column,
DateTime,
Enum,
@@ -58,6 +59,24 @@ if TYPE_CHECKING:
LOGGER = logging.getLogger(__name__)
+class JsonText(sqlalchemy.types.TypeDecorator):
+ """Saves objects serialized as JSON but keeps the column as a Text."""
+
+ # This is straight from the SQLAlchemy documentation, so the non-overriden
+ # methods should be fine.
+ # pylint: disable=too-many-ancestors,abstract-method
+
+ impl = sqlalchemy.types.Text
+
+ cache_ok = True
+
+ def process_bind_param(self, value, dialect):
+ return json.dumps(value)
+
+ def process_result_value(self, value, dialect):
+ return json.loads(value)
+
+
class Tag(Base):
"""A tag is a single keyword associated with a track.
@@ -213,7 +232,7 @@ class Track(Base):
visibility = Column(Enum(Visibility))
link_secret = Column(Text)
type = Column(Enum(TrackType))
- transformers = Column(JSON)
+ transformers = Column(JsonText)
owner: Mapped["models.User"] = relationship("User", back_populates="tracks")
cache: Mapped[Optional["TrackCache"]] = relationship(
diff --git a/fietsboek/routes.py b/fietsboek/routes.py
index d5caef8..4286e92 100644
--- a/fietsboek/routes.py
+++ b/fietsboek/routes.py
@@ -46,13 +46,19 @@ def includeme(config):
config.add_route(
"image", "/track/{track_id}/images/{image_name}", factory="fietsboek.models.Track.factory"
)
+ config.add_route(
+ "track-map",
+ "/track/{track_id}/preview",
+ factory="fietsboek.models.Track.factory",
+ )
config.add_route("badge", "/badge/{badge_id}", factory="fietsboek.models.Badge.factory")
- config.add_route("admin", "/admin")
- config.add_route("admin-badge-add", "/admin/add-badge")
- config.add_route("admin-badge-edit", "/admin/edit-badge")
- config.add_route("admin-badge-delete", "/admin/delete-badge")
+ config.add_route("admin", "/admin/")
+ config.add_route("admin-badge", "/admin/badges/")
+ config.add_route("admin-badge-add", "/admin/badges/add")
+ config.add_route("admin-badge-edit", "/admin/badges/edit")
+ config.add_route("admin-badge-delete", "/admin/badges/delete")
config.add_route("user-data", "/me")
config.add_route("add-friend", "/me/send-friend-request")
diff --git a/fietsboek/scripts/fietscron.py b/fietsboek/scripts/fietscron.py
index 7687e12..a3f3f54 100644
--- a/fietsboek/scripts/fietscron.py
+++ b/fietsboek/scripts/fietscron.py
@@ -6,6 +6,7 @@ import logging.config
from pathlib import Path
import click
+import gpxpy
import pyramid.paster
import redis as mod_redis
from redis import Redis
@@ -14,10 +15,11 @@ from sqlalchemy.engine import Engine
from sqlalchemy.orm import Session
from .. import config as mod_config
-from .. import hittekaart, models
+from .. import hittekaart, models, trackmap
from ..config import Config
from ..data import DataManager
from ..models.user import TOKEN_LIFETIME
+from ..views.tileproxy import TileRequester
from . import config_option
LOGGER = logging.getLogger(__name__)
@@ -33,6 +35,7 @@ def cli(config):
\b
* Deletes pending uploads that are older than 24 hours.
* Rebuilds the cache for missing tracks.
+ * Builds preview images for tracks.
* (optional) Runs ``hittekaart`` to generate heatmaps
"""
logging.config.fileConfig(config)
@@ -47,16 +50,20 @@ def cli(config):
return
engine = create_engine(config.sqlalchemy_url)
+ redis = mod_redis.from_url(config.redis_url)
LOGGER.debug("Starting maintenance tasks")
remove_old_uploads(engine)
remove_old_tokens(engine)
rebuild_cache(engine, data_manager)
+ build_previews(engine, data_manager, redis, config)
+ redis = mod_redis.from_url(config.redis_url)
if config.hittekaart_autogenerate:
- redis = mod_redis.from_url(config.redis_url)
run_hittekaart(engine, data_manager, redis, config)
+ redis.set("last-cronjob", datetime.datetime.now(datetime.UTC).timestamp())
+
def remove_old_uploads(engine: Engine):
"""Removes old uploads from the database."""
@@ -95,6 +102,34 @@ def rebuild_cache(engine: Engine, data_manager: DataManager):
session.commit()
+def build_previews(
+ engine: Engine,
+ data_manager: DataManager,
+ redis: Redis,
+ config: mod_config.Config,
+):
+ """Builds track preview images if they are missing."""
+ LOGGER.info("Building track preview images")
+ session = Session(engine)
+ tile_requester = TileRequester(redis)
+ layer = config.public_tile_layers()[0]
+ tracks = select(models.Track)
+ for track in session.execute(tracks).scalars():
+ if track.id is None:
+ continue
+
+ track_dir = data_manager.open(track.id)
+ if track_dir.preview_path().exists():
+ continue
+
+ LOGGER.debug("Building preview for %s", track.id)
+ gpx = gpxpy.parse(track_dir.decompress_gpx())
+ preview = trackmap.render(gpx, layer, tile_requester)
+ with track_dir.lock():
+ with open(track_dir.preview_path(), "wb") as preview_file:
+ preview.save(preview_file, "PNG")
+
+
def run_hittekaart(engine: Engine, data_manager: DataManager, redis: Redis, config: Config):
"""Run outstanding hittekaart requests."""
# The logic here is as follows:
diff --git a/fietsboek/static/DeadEnd.svg b/fietsboek/static/DeadEnd.svg
new file mode 100644
index 0000000..b65f171
--- /dev/null
+++ b/fietsboek/static/DeadEnd.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 421.0267 630.72443"
+ height="630.72443"
+ width="421.0267"
+ xml:space="preserve"
+ id="svg5733"
+ version="1.1"><metadata
+ id="metadata5739"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs5737" /><g
+ transform="matrix(1.3333333,0,0,-1.3333333,0,630.72445)"
+ id="g5741"><g
+ id="g5743"><path
+ id="path5745"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="M 21.594,0.375 H 294.59 c 20.906,0 20.805,17.051 20.805,17.051 v 438.41 c 0,0 0.136,16.828 -17.977,16.828 H 18.945 c 0,0 -18.531,0.309 -18.57,-18.507 V 19.11 C 0.375,0.328 21.594,0.375 21.594,0.375 Z" /><path
+ id="path5747"
+ style="fill:#154889;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="m 18.422,8.723 h 279.019 c 0,0 10.153,0.027 10.153,10.27 V 454.86 c 0,0 -0.008,10.058 -11.235,10.058 H 19.504 c 0,0 -11.152,0 -11.152,-10.054 V 18.352 C 8.391,8.621 18.422,8.723 18.422,8.723 Z" /><path
+ id="path5749"
+ style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="m 18.945,473.043 c -0.007,0 -4.711,0.078 -9.433,-2.238 C 4.781,468.485 0.02,463.688 0,454.161 V 19.11 C 0,9.582 5.441,4.731 10.832,2.356 16.223,-0.019 21.594,0 21.594,0 H 294.59 c 10.555,0 15.918,4.348 18.562,8.707 2.645,4.356 2.618,8.715 2.618,8.715 v 438.414 c 0,0.004 0.035,4.293 -2.243,8.594 -2.277,4.297 -6.937,8.613 -16.109,8.613 z m 0,-0.379 h 278.473 c 18.113,0 17.977,-16.828 17.977,-16.828 V 17.422 c 0,0 0.101,-17.047 -20.805,-17.047 H 21.594 c 0,0 -21.219,-0.047 -21.219,18.731 v 435.051 c 0.039,18.816 18.57,18.507 18.57,18.507 z" /><path
+ id="path5751"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="M 53.969,418.61 V 265.614 h 96.164 v -34.368 h -66 V 181.43 H 137.16 V 6.571 h 41.59 l -0.066,174.859 h 53.027 l -0.066,49.816 h -66 v 34.368 h 96.156 V 418.61 Z" /><path
+ id="path5753"
+ style="fill:#cc0000;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="M 88.68,226.7 H 227.098 V 186.442 H 88.68 Z" /><path
+ id="path5755"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ d="m 165.359,390.379 c 0,-5.152 -4.175,-9.332 -9.332,-9.332 -5.152,0 -9.328,4.18 -9.328,9.332 0,5.153 4.176,9.332 9.328,9.332 5.157,0 9.332,-4.179 9.332,-9.332 z" /><path
+ id="path5757"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ d="m 128.367,336.723 c 25.278,39.441 22.879,40.543 32.328,40.824 5.5,-0.164 5.664,-1.664 14.996,-8.996 11.5,-8.449 10.664,-8.551 10.5,-17.832 -0.05,-11.164 0.715,-10.328 -8.168,-14.496 v 18.832 l -7.832,6.996 v -26.496 l -18.66,0.168 -0.168,20.996 c -15.332,-23.66 -14.332,-22.328 -22.996,-19.996 z" /><path
+ id="path5759"
+ style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ d="m 151.863,332.891 h 18.164 c 0,-4.168 1.332,-3.832 10.996,-21.996 10.165,-18.715 10.332,-17.445 4.5,-28.16 l -25.16,45.156 c -23.832,-46.105 -20.832,-44.707 -33.996,-43.824 l 13.164,24.66 c 11.282,20.383 11.883,20.449 12.332,24.164 z" /></g></g></svg> \ No newline at end of file
diff --git a/fietsboek/static/NoEntry.svg b/fietsboek/static/NoEntry.svg
new file mode 100644
index 0000000..1cc18a4
--- /dev/null
+++ b/fietsboek/static/NoEntry.svg
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" width="600.99628" height="600.99628">
+ <defs>
+ <path id="a" d="M23.809 456.512h.051v-.047h-.051v.047z"/>
+ <path id="b" d="M23.809 456.465v.047l.05-.047h-.05zm.05 0-.05.047v-.047h.05z"/>
+ <path id="c" d="M23.859 456.465h-.05.023v.047l.027-.047z"/>
+ <path id="d" d="M23.809 456.465v.047-.047l.05.047-.05-.047z"/>
+ <path id="e" d="M23.859 456.512v-.047h-.05l.05.047z"/>
+ </defs>
+ <g transform="matrix(1.25 0 0 -1.25 0 600.99628)">
+ <path fill="#fff" d="M480.398 240.399c0-132.551-107.449-240-240-240-132.55 0-240 107.449-240 240 0 132.55 107.45 240 240 240 132.551 0 240-107.45 240-240z"/>
+ <path fill="#c1121c" d="M240.402 472.402c-127.75 0-232-104.25-232-232s104.25-232 232-232c127.746 0 232 104.25 232 232s-104.254 232-232 232zm-208-196h416v-72h-416v72z"/>
+ <path d="M240.398 480.797C107.633 480.797 0 373.164 0 240.399 0 107.633 107.633 0 240.398 0c132.766 0 240.399 107.633 240.399 240.399 0 132.765-107.633 240.398-240.399 240.398zm0-.398c132.551 0 240-107.45 240-240 0-132.551-107.449-240-240-240-132.55 0-240 107.449-240 240 0 132.55 107.45 240 240 240zM23.832 456.512v-.024h-.023l.023.024z"/>
+ <path d="M23.809 456.512h.023v-.023h-.023v.023z"/>
+ <path d="M23.809 456.488v.024l.023-.024h-.023zm.023 0-.023.024v-.024h.023z"/>
+ <path d="M23.859 456.488h-.05.023v.024l.027-.024z"/>
+ <path d="M23.809 456.488v.024-.024l.023.024-.023-.024z"/>
+ <path d="M23.809 456.512h.023v-.023h-.023v.023zm.05 0v-.047h-.05l.05.047z"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#b"/>
+ <use xlink:href="#c"/>
+ <use xlink:href="#d"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#e"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#b"/>
+ <use xlink:href="#c"/>
+ <use xlink:href="#d"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#e"/>
+ <use xlink:href="#a"/>
+ <use xlink:href="#b"/>
+ <use xlink:href="#c"/>
+ <use xlink:href="#d"/>
+ <use xlink:href="#a"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/fietsboek/static/theme.css b/fietsboek/static/theme.css
index 2298b49..65e6881 100644
--- a/fietsboek/static/theme.css
+++ b/fietsboek/static/theme.css
@@ -268,10 +268,59 @@ strong {
width: 25%;
}
+.browse-track-card {
+ display: grid;
+ grid-template: "preview data"/300px auto;
+}
+@media (max-width: 768px) {
+ .browse-track-card {
+ grid-template: "preview" "data";
+ }
+}
+.browse-track-card.card-body {
+ padding: 0px;
+}
+.browse-track-card .browse-track-preview {
+ grid-area: preview;
+}
+@media (max-width: 768px) {
+ .browse-track-card .browse-track-preview {
+ text-align: center;
+ }
+}
+.browse-track-card .browse-track-preview img {
+ width: 300px;
+ height: 300px;
+}
+.browse-track-card .browse-track-data {
+ grid-area: data;
+ padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x);
+}
+
.chart-title {
text-align: center;
}
+/* Admin view layout: We have an extra sidebar for the navigation */
+#adminContainer {
+ display: grid;
+ grid-template-areas: "sidebar main";
+ grid-template-columns: 1fr 5fr;
+ gap: 1rem;
+}
+
+#adminNavigation {
+ grid-area: sidebar;
+}
+
+#adminContent {
+ grid-area: main;
+}
+
+.admin-stat {
+ font-size: 120%;
+}
+
.list-group.list-group-root {
padding: 0;
overflow: hidden;
diff --git a/fietsboek/static/theme.css.map b/fietsboek/static/theme.css.map
index 08ac64f..3ddb9bc 100644
--- a/fietsboek/static/theme.css.map
+++ b/fietsboek/static/theme.css.map
@@ -1 +1 @@
-{"version":3,"sourceRoot":"","sources":["../../asset-sources/theme.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EAqCE;EACA;EACA;EACA;EACA;EACA;;AAzCA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAWJ;EACI;;;AAGJ;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAEF;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;EACA;;AACA;EACE;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE","file":"theme.css"} \ No newline at end of file
+{"version":3,"sourceRoot":"","sources":["../../asset-sources/theme.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EAqCE;EACA;EACA;EACA;EACA;EACA;;AAzCA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAWJ;EACI;;;AAGJ;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;EACA;;;AAEF;EACE;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;;AAEF;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;;AACA;EACE;;AAGJ;EACE;EACA;;AACA;EACE;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;;;AAGF;EACE;EACA;;AAEA;EAJF;IAKI,eACE;;;AAIJ;EACE;;AAGF;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;;AAIJ;EACE;;;AAGF;AACA;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE","file":"theme.css"} \ No newline at end of file
diff --git a/fietsboek/templates/403.jinja2 b/fietsboek/templates/403.jinja2
new file mode 100644
index 0000000..9f4478e
--- /dev/null
+++ b/fietsboek/templates/403.jinja2
@@ -0,0 +1,16 @@
+{% extends "layout.jinja2" %}
+
+{% block content %}
+<div class="container">
+ <h1>{{ _("403.title") }}</h1>
+ <div style="text-align: center;">
+ <img src="{{ request.static_url('fietsboek:static/NoEntry.svg') }}" style="width: min(100%, 300px); margin: auto;">
+ <p>
+ {{ _("403.no_access") }}
+ </p>
+ <p>
+ {{ _("403.try_log_in") }}
+ </p>
+ </div>
+</div>
+{% endblock content %}
diff --git a/fietsboek/templates/404.jinja2 b/fietsboek/templates/404.jinja2
index aaf1241..9c7cc72 100644
--- a/fietsboek/templates/404.jinja2
+++ b/fietsboek/templates/404.jinja2
@@ -1,8 +1,16 @@
{% extends "layout.jinja2" %}
{% block content %}
-<div class="content">
- <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
- <p class="lead"><span class="font-semi-bold">404</span> Page Not Found</p>
+<div class="container">
+ <h1>{{ _("404.title") }}</h1>
+ <div style="text-align: center;">
+ <img src="{{ request.static_url('fietsboek:static/DeadEnd.svg') }}" style="width: min(100%, 300px); margin: auto;">
+ <p>
+ {{ _("404.path_not_found") }}
+ </p>
+ <p>
+ {{ _("404.choose_different") }}
+ </p>
+ </div>
</div>
{% endblock content %}
diff --git a/fietsboek/templates/admin.jinja2 b/fietsboek/templates/admin.jinja2
index 3201d8d..e05e9f0 100644
--- a/fietsboek/templates/admin.jinja2
+++ b/fietsboek/templates/admin.jinja2
@@ -4,45 +4,18 @@
<div class="container">
<h1>{{ _("page.admin.title") }}</h1>
- <h2>{{ _("page.admin.badges") }}</h2>
+ <div id="adminContainer">
+ <aside id="adminNavigation">
+ <nav class="nav nav-pills nav-fill flex-column">
+ <a class="nav-link{% if admin_index == 0 %} active text-bg-dark{% endif %}" href="{{ request.route_url('admin') }}">{{ _("page.admin.nav.overview") }}</a>
+ <a class="nav-link{% if admin_index == 1 %} active text-bg-dark{% endif %}" href="{{ request.route_url('admin-badge') }}">{{ _("page.admin.nav.badges") }}</a>
+ </nav>
+ </aside>
- <div class="list-group">
- {% for badge in badges %}
- <span href="#" class="list-group-item list-group-item-action d-flex admin-badge-list">
- {{ util.render_badge(badge) }}
- <form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-edit') }}">
- <input type="hidden" name="badge-edit-id" value="{{ badge.id }}">
- <div class="mb-3">
- <input type="text" class="form-control" name="badge-title" value="{{ badge.title }}">
- </div>
- <div class="mb-3">
- <input class="form-control" type="file" name="badge-image">
- </div>
- {{ util.hidden_csrf_input() }}
- <div class="mb-3">
- <button class="btn btn-primary">{{ _("page.admin.badge.edit") }}</button>
- </div>
- </form>
- <form method="POST" action="{{ request.route_path('admin-badge-delete') }}">
- <input type="hidden" name="badge-delete-id" value="{{ badge.id }}">
- {{ util.hidden_csrf_input() }}
- <button class="btn btn-danger"><i class="bi bi-trash"></i> {{ _("page.admin.badge.delete_badge") }}</button>
- </form>
- </span>
- {% endfor %}
- </div>
-
- <form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-add') }}">
- <div class="mb-3">
- <label for="badge-title" class="form-label">{{ _("page.admin.badges.badge_title") }}</label>
- <input type="text" class="form-control" id="badge-title" name="badge-title">
+ <div id="adminContent">
+ {% block admin_content %}
+ {% endblock %}
</div>
- <div class="mb-3">
- <label for="badge-image" class="form-label">{{ _("page.admin.badges.badge_image") }}</label>
- <input class="form-control" type="file" name="badge-image">
- </div>
- {{ util.hidden_csrf_input() }}
- <button type="submit" class="btn btn-primary">{{ _("page.admin.badges.add_badge") }}</button>
- </form>
+ </div>
</div>
{% endblock %}
diff --git a/fietsboek/templates/admin_badges.jinja2 b/fietsboek/templates/admin_badges.jinja2
new file mode 100644
index 0000000..efe8d2c
--- /dev/null
+++ b/fietsboek/templates/admin_badges.jinja2
@@ -0,0 +1,45 @@
+{% set admin_index = 1 %}
+{% extends "admin.jinja2" %}
+{% import "util.jinja2" as util with context %}
+{% block admin_content %}
+<h2>{{ _("page.admin.badges") }}</h2>
+
+<div class="list-group">
+ {% for badge in badges %}
+ <span href="#" class="list-group-item list-group-item-action d-flex admin-badge-list">
+ {{ util.render_badge(badge) }}
+ <form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-edit') }}">
+ <input type="hidden" name="badge-edit-id" value="{{ badge.id }}">
+ <div class="mb-3">
+ <input type="text" class="form-control" name="badge-title" value="{{ badge.title }}">
+ </div>
+ <div class="mb-3">
+ <input class="form-control" type="file" name="badge-image">
+ </div>
+ {{ util.hidden_csrf_input() }}
+ <div class="mb-3">
+ <button class="btn btn-success"><i class="bi bi-pencil"></i> {{ _("page.admin.badge.edit") }}</button>
+ <button class="btn btn-danger" form="deleteBadge{{ badge.id }}"><i class="bi bi-trash"></i> {{ _("page.admin.badge.delete_badge") }}</button>
+ </div>
+ </form>
+ <form method="POST" id="deleteBadge{{ badge.id }}" action="{{ request.route_path('admin-badge-delete') }}">
+ <input type="hidden" name="badge-delete-id" value="{{ badge.id }}">
+ {{ util.hidden_csrf_input() }}
+ </form>
+ </span>
+ {% endfor %}
+</div>
+
+<form method="POST" enctype="multipart/form-data" action="{{ request.route_path('admin-badge-add') }}">
+ <div class="mb-3">
+ <label for="badge-title" class="form-label">{{ _("page.admin.badges.badge_title") }}</label>
+ <input type="text" class="form-control" id="badge-title" name="badge-title">
+ </div>
+ <div class="mb-3">
+ <label for="badge-image" class="form-label">{{ _("page.admin.badges.badge_image") }}</label>
+ <input class="form-control" type="file" name="badge-image">
+ </div>
+ {{ util.hidden_csrf_input() }}
+ <button type="submit" class="btn btn-primary">{{ _("page.admin.badges.add_badge") }}</button>
+</form>
+{% endblock %}
diff --git a/fietsboek/templates/admin_overview.jinja2 b/fietsboek/templates/admin_overview.jinja2
new file mode 100644
index 0000000..18a7633
--- /dev/null
+++ b/fietsboek/templates/admin_overview.jinja2
@@ -0,0 +1,93 @@
+{% set admin_index = 0 %}
+{% extends "admin.jinja2" %}
+{% block admin_content %}
+<p class="admin-stat">
+ {{ _("admin.overview.instance_has") }}&hellip;
+</p>
+
+<p class="admin-stat">
+ &hellip; {{ ngettext("admin.overview.stat.user", "admin.overview.stat.users", user_count) }}
+</p>
+
+<p class="admin-stat">
+ &hellip; {{ ngettext("admin.overview.stat.track", "admin.overview.stat.tracks", track_count) }}
+</p>
+
+<p class="admin-stat">
+ &hellip; {{ (total_size / 1024 / 1024) | round(2) }} {{ _("admin.overview.stats.mib") }}
+</p>
+
+<div style="position: relative; height: 500px; margin: auto; width: 75%;">
+ <canvas id="graph-size-breakdown"></canvas>
+</div>
+
+<h2>{{ _("admin.overview.system_overview") }}</h2>
+
+<table class="table">
+ <tr>
+ <td>{{ _("admin.overview.fietsboek_version") }}</td>
+ <td>{{ versions["fietsboek"] }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("admin.overview.python_version") }}</td>
+ <td>{{ versions["python"] }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("admin.overview.kernel_version") }}</td>
+ <td>{{ versions["linux"] }}</td>
+ </tr>
+ <tr>
+ <td>{{ _("admin.overview.distro_version") }}</td>
+ <td>{{ versions["distro"] }}</td>
+ </tr>
+ <tr class="{% if cron_good %}table-success{% else %}table-warning{% endif %}">
+ <td>{{ _("admin.overview.last_cronjob") }} {% if not cron_good %}<i class="bi bi-exclamation-triangle-fill"></i>{% endif %}</td>
+ <td>{{ last_cronjob }}</td>
+ </tr>
+</table>
+{% endblock %}
+
+{% block latescripts %}
+<script>
+ (function() {
+ const data = {
+ labels: [
+ {{ _("admin.overview.storage_graph.label.gpx") | tojson }},
+ {{ _("admin.overview.storage_graph.label.images") | tojson }},
+ {{ _("admin.overview.storage_graph.label.previews") | tojson }},
+ {{ _("admin.overview.storage_graph.label.user_maps") | tojson }}
+ ],
+ datasets: [
+ {
+ label: "MiB",
+ data: [
+ {{ (size_breakdown.gpx_files / 1024 / 1024) | tojson }},
+ {{ (size_breakdown.image_files / 1024 / 1024) | tojson }},
+ {{ (size_breakdown.preview_files / 1024 / 1024) | tojson }},
+ {{ (size_breakdown.user_maps / 1024 / 1024) | tojson }}
+ ]
+ }
+ ]
+ };
+
+ const config = {
+ type: 'pie',
+ data: data,
+ options: {
+ responsive: true,
+ plugins: {
+ legend: {
+ position: 'top',
+ },
+ title: {
+ display: true,
+ text: {{ _("admin.overview.storage_graph.title") | tojson }}
+ }
+ }
+ }
+ };
+
+ new Chart("graph-size-breakdown", config);
+ })();
+</script>
+{% endblock %}
diff --git a/fietsboek/templates/browse.jinja2 b/fietsboek/templates/browse.jinja2
index 8877229..28693f6 100644
--- a/fietsboek/templates/browse.jinja2
+++ b/fietsboek/templates/browse.jinja2
@@ -151,64 +151,93 @@
{% endif %}
</span>
</h5>
- <div class="card-body">
- <table class="table table-hover table-sm browse-summary">
- <tbody>
- <tr>
- <th scope="row">{{ _("page.details.date") }}</th>
- <td>{{ track.date | format_datetime }}</td>
- <th scope="row">{{ _("page.details.length") }}</th>
- <td>{{ (track.length / 1000) | round(2) | format_decimal }} km</td>
- </tr>
- {% if track.show_organic_data() %}
- <tr>
- <th scope="row">{{ _("page.details.start_time") }}</th>
- <td>{{ track.start_time | format_datetime }}</td>
- <th scope="row">{{ _("page.details.end_time") }}</th>
- <td>{{ track.end_time | format_datetime }}</td>
- </tr>
- {% endif %}
- <tr>
- <th scope="row">{{ _("page.details.uphill") }}</th>
- <td>{{ track.uphill | round(2) | format_decimal }} m</td>
- <th scope="row">{{ _("page.details.downhill") }}</th>
- <td>{{ track.downhill | round(2) | format_decimal }} m</td>
- </tr>
- {% if track.show_organic_data() %}
- <tr>
- <th scope="row">{{ _("page.details.moving_time") }}</th>
- <td>{{ track.moving_time }}</td>
- <th scope="row">{{ _("page.details.stopped_time") }}</th>
- <td>{{ track.stopped_time }}</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.details.max_speed") }}</th>
- <td>{{ mps_to_kph(track.max_speed) | round(2) | format_decimal }} km/h</td>
- <th scope="row">{{ _("page.details.avg_speed") }}</th>
- <td>{{ mps_to_kph(track.avg_speed) | round(2) | format_decimal }} km/h</td>
- </tr>
- {% endif %}
- <tr>
- <th scope="row"><i class="bi bi-chat-left-text-fill"></i> {{ _("page.browse.card.comments") }}</th>
- <td>{{ track.comments | length }}</td>
- <th scope="row"><i class="bi bi-images"></i> {{ _("page.browse.card.images") }}</th>
- <td>{{ track.images | length }}</td>
- </tr>
- </tbody>
- </table>
+ <div class="card-body browse-track-card">
+ <div class="browse-track-preview">
+ <img src="{{ request.route_url('track-map', track_id=track.id) }}">
+ </div>
+ <div class="browse-track-data">
+ <table class="table table-hover table-sm browse-summary">
+ <tbody>
+ <tr>
+ <th scope="row">{{ _("page.details.date") }}</th>
+ <td>{{ track.date | format_datetime }}</td>
+ <th scope="row">{{ _("page.details.length") }}</th>
+ <td>{{ (track.length / 1000) | round(2) | format_decimal }} km</td>
+ </tr>
+ {% if track.show_organic_data() %}
+ <tr>
+ <th scope="row">{{ _("page.details.start_time") }}</th>
+ <td>{{ track.start_time | format_datetime }}</td>
+ <th scope="row">{{ _("page.details.end_time") }}</th>
+ <td>{{ track.end_time | format_datetime }}</td>
+ </tr>
+ {% endif %}
+ <tr>
+ <th scope="row">{{ _("page.details.uphill") }}</th>
+ <td>{{ track.uphill | round(2) | format_decimal }} m</td>
+ <th scope="row">{{ _("page.details.downhill") }}</th>
+ <td>{{ track.downhill | round(2) | format_decimal }} m</td>
+ </tr>
+ {% if track.show_organic_data() %}
+ <tr>
+ <th scope="row">{{ _("page.details.moving_time") }}</th>
+ <td>{{ track.moving_time }}</td>
+ <th scope="row">{{ _("page.details.stopped_time") }}</th>
+ <td>{{ track.stopped_time }}</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.details.max_speed") }}</th>
+ <td>{{ mps_to_kph(track.max_speed) | round(2) | format_decimal }} km/h</td>
+ <th scope="row">{{ _("page.details.avg_speed") }}</th>
+ <td>{{ mps_to_kph(track.avg_speed) | round(2) | format_decimal }} km/h</td>
+ </tr>
+ {% endif %}
+ <tr>
+ <th scope="row"><i class="bi bi-chat-left-text-fill"></i> {{ _("page.browse.card.comments") }}</th>
+ <td>{{ track.comments | length }}</td>
+ <th scope="row"><i class="bi bi-images"></i> {{ _("page.browse.card.images") }}</th>
+ <td>{{ track.images | length }}</td>
+ </tr>
+ </tbody>
+ </table>
- {% if track.show_organic_data() %}
- <ul>
- <li>{{ track.owner.name }}</li>
- {% for user in track.tagged_people %}
- <li>{{ user.name }}</li>
- {% endfor %}
- </ul>
- {% endif %}
+ {% if track.show_organic_data() %}
+ <ul>
+ <li>{{ track.owner.name }}</li>
+ {% for user in track.tagged_people %}
+ <li>{{ user.name }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ </div>
</div>
</div>
{% endfor %}
<button type="button" class="btn btn-primary ui-element" id="archiveDownloadButton" disabled><i class="bi bi-file-earmark-zip"></i> {{ _("page.browse.download_multiple") }}</button>
+
+ <nav aria-label="Page navigation">
+ <ul class="pagination justify-content-center">
+ {% if page_previous is none %}
+ <li class="page-item disabled">
+ <span class="page-link">{{ _("pagination.previous") }}</span>
+ </li>
+ {% else %}
+ <li class="page-item">
+ <a class="page-link" href="{{ page_previous | safe }}">{{ _("pagination.previous") }}</a>
+ </li>
+ {% endif %}
+ {% if page_next is none %}
+ <li class="page-item disabled">
+ <span class="page-link">{{ _("pagination.next") }}</span>
+ </li>
+ {% else %}
+ <li class="page-item">
+ <a class="page-link" href="{{ page_next | safe }}">{{ _("pagination.next") }}</a>
+ </li>
+ {% endif %}
+ </ul>
+ </nav>
+
{% elif used_filters %}
<p>{{ _("page.browse.no_results") }}</p>
{% else %}
diff --git a/fietsboek/templates/edit.jinja2 b/fietsboek/templates/edit.jinja2
index 6347ae6..26c520f 100644
--- a/fietsboek/templates/edit.jinja2
+++ b/fietsboek/templates/edit.jinja2
@@ -10,6 +10,10 @@
<noscript><p>{{ _("page.noscript") }}<p></noscript>
</div>
<form method="POST" enctype="multipart/form-data">
+ <div class="mb-3">
+ <label class="form-label ui-element" for="newGpx">{{ _("page.edit.form.new_track") }}</label>
+ <input id="newGpx" name="gpx" type="file" class="form-control ui-element">
+ </div>
{{ edit_form.edit_track(track.title, track.date_raw, track.date_tz or 0, track.visibility, track.type, track.description, track.text_tags(), badges, track.tagged_people, images) }}
{{ util.hidden_csrf_input() }}
<div class="btn-group" role="group">
diff --git a/fietsboek/templates/profile_overview.jinja2 b/fietsboek/templates/profile_overview.jinja2
index aa2333c..eaa10c6 100644
--- a/fietsboek/templates/profile_overview.jinja2
+++ b/fietsboek/templates/profile_overview.jinja2
@@ -9,48 +9,53 @@
{% for tag in track.tags %}<span class="badge bg-info text-dark">{{ tag.tag }}</span> {% endfor %}
{% endif %}
</h5>
- <div class="card-body">
- <table class="table table-hover table-sm browse-summary">
- <tbody>
- <tr>
- <th scope="row">{{ _("page.details.date") }}</th>
- <td>{{ track.date | format_datetime }}</td>
- <th scope="row">{{ _("page.details.length") }}</th>
- <td>{{ (track.length / 1000) | round(2) | format_decimal }} km</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.details.start_time") }}</th>
- <td>{{ track.start_time | format_datetime }}</td>
- <th scope="row">{{ _("page.details.end_time") }}</th>
- <td>{{ track.end_time | format_datetime }}</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.details.uphill") }}</th>
- <td>{{ track.uphill | round(2) | format_decimal }} m</td>
- <th scope="row">{{ _("page.details.downhill") }}</th>
- <td>{{ track.downhill | round(2) | format_decimal }} m</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.details.moving_time") }}</th>
- <td>{{ track.moving_time }}</td>
- <th scope="row">{{ _("page.details.stopped_time") }}</th>
- <td>{{ track.stopped_time }}</td>
- </tr>
- <tr>
- <th scope="row">{{ _("page.details.max_speed") }}</th>
- <td>{{ mps_to_kph(track.max_speed) | round(2) | format_decimal }} km/h</td>
- <th scope="row">{{ _("page.details.avg_speed") }}</th>
- <td>{{ mps_to_kph(track.avg_speed) | round(2) | format_decimal }} km/h</td>
- </tr>
- </tbody>
- </table>
-
- <ul>
- <li>{{ track.owner.name }}</li>
- {% for user in track.tagged_people %}
- <li>{{ user.name }}</li>
- {% endfor %}
- </ul>
+ <div class="card-body browse-track-card">
+ <div class="browse-track-preview">
+ <img src="{{ request.route_url('track-map', track_id=track.id) }}">
+ </div>
+ <div class="browse-track-data">
+ <table class="table table-hover table-sm browse-summary">
+ <tbody>
+ <tr>
+ <th scope="row">{{ _("page.details.date") }}</th>
+ <td>{{ track.date | format_datetime }}</td>
+ <th scope="row">{{ _("page.details.length") }}</th>
+ <td>{{ (track.length / 1000) | round(2) | format_decimal }} km</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.details.start_time") }}</th>
+ <td>{{ track.start_time | format_datetime }}</td>
+ <th scope="row">{{ _("page.details.end_time") }}</th>
+ <td>{{ track.end_time | format_datetime }}</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.details.uphill") }}</th>
+ <td>{{ track.uphill | round(2) | format_decimal }} m</td>
+ <th scope="row">{{ _("page.details.downhill") }}</th>
+ <td>{{ track.downhill | round(2) | format_decimal }} m</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.details.moving_time") }}</th>
+ <td>{{ track.moving_time }}</td>
+ <th scope="row">{{ _("page.details.stopped_time") }}</th>
+ <td>{{ track.stopped_time }}</td>
+ </tr>
+ <tr>
+ <th scope="row">{{ _("page.details.max_speed") }}</th>
+ <td>{{ mps_to_kph(track.max_speed) | round(2) | format_decimal }} km/h</td>
+ <th scope="row">{{ _("page.details.avg_speed") }}</th>
+ <td>{{ mps_to_kph(track.avg_speed) | round(2) | format_decimal }} km/h</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <ul>
+ <li>{{ track.owner.name }}</li>
+ {% for user in track.tagged_people %}
+ <li>{{ user.name }}</li>
+ {% endfor %}
+ </ul>
+ </div>
</div>
</div>
{% endmacro %}
diff --git a/fietsboek/trackmap.py b/fietsboek/trackmap.py
new file mode 100644
index 0000000..584bf72
--- /dev/null
+++ b/fietsboek/trackmap.py
@@ -0,0 +1,134 @@
+"""Module to render tracks to static images on OSM tiles."""
+
+import io
+import math
+
+from gpxpy.gpx import GPX
+from PIL import Image, ImageDraw
+
+from .config import TileLayerConfig
+from .views.tileproxy import TileRequester
+
+TILE_SIZE = 256
+
+
+def to_web_mercator(lat: float, lon: float, zoom: int) -> tuple[int, int]:
+ """Convert a pari of latitude/longitude coordinates to web mercator form.
+
+ :param lat: Latitude (in degrees).
+ :param lon: Longitude (in degrees).
+ :param zoom: Zoom level.
+ :return: The web mercator x/y coordinates. Both will be between 0 and
+ 2**zoom * 256.
+ """
+ width = height = TILE_SIZE
+
+ la = math.radians(lon)
+ phi = math.radians(lat)
+
+ x = float(2**zoom) / (2 * math.pi) * width * (la + math.pi)
+ y = (
+ float(2**zoom)
+ / (2 * math.pi)
+ * height
+ * (math.pi - math.log(math.tan((math.pi / 4 + phi / 2))))
+ )
+
+ return (int(math.floor(x)), int(math.floor(y)))
+
+
+class TrackMapRenderer:
+ """A renderer that renders GPX tracks onto small map excerpts."""
+
+ # pylint: disable=too-few-public-methods
+
+ def __init__(
+ self,
+ track: GPX,
+ requester: TileRequester,
+ size: tuple[int, int],
+ layer: TileLayerConfig,
+ ):
+ self.track = track
+ self.requester = requester
+ self.size = size
+ self.layer = layer
+ self.maxzoom = layer.zoom
+ self.color = (0, 0, 255)
+ self.line_width = 5
+
+ def render(self) -> Image.Image:
+ """Render the track.
+
+ :return: The image containing the rendered preview.
+ """
+ zoom, bbox = self._find_zoom()
+ image = Image.new("RGB", self.size)
+ start_x, start_y = self._draw_base(image, zoom, bbox)
+ self._draw_lines(image, zoom, start_x, start_y)
+ return image
+
+ def _find_zoom(self) -> tuple[int, tuple[int, int, int, int]]:
+ for zoom in range(self.maxzoom or 19, 0, -1):
+ min_x, max_x = 2**zoom * TILE_SIZE, 0
+ min_y, max_y = 2**zoom * TILE_SIZE, 0
+
+ for point in self.track.walk(only_points=True):
+ x, y = to_web_mercator(point.latitude, point.longitude, zoom)
+ min_x = min(min_x, x)
+ max_x = max(max_x, x)
+ min_y = min(min_y, y)
+ max_y = max(max_y, y)
+
+ if max_x - min_x > self.size[0] or max_y - min_y > self.size[1]:
+ break
+ else:
+ return zoom, (min_x, max_x, min_y, max_y)
+
+ return 1, (0, 512, 0, 512)
+
+ def _draw_base(self, image, zoom, bbox):
+ min_x, max_x, min_y, max_y = bbox
+ # We center the track by centering its bounding box
+ start_x = min_x - (self.size[0] - (max_x - min_x)) // 2
+ start_y = min_y - (self.size[1] - (max_y - min_y)) // 2
+
+ offset_x = start_x // TILE_SIZE * TILE_SIZE - start_x
+ offset_y = start_y // TILE_SIZE * TILE_SIZE - start_y
+
+ for i in range(int(math.ceil(self.size[0] / TILE_SIZE)) + 1):
+ for j in range(int(math.ceil(self.size[1] / TILE_SIZE)) + 1):
+ tile = self._load_tile(zoom, start_x // TILE_SIZE + i, start_y // TILE_SIZE + j)
+ image.paste(tile, (i * TILE_SIZE + offset_x, j * TILE_SIZE + offset_y))
+
+ return start_x, start_y
+
+ def _load_tile(self, zoom, x, y) -> Image.Image:
+ tile_data = self.requester.load_tile(self.layer, zoom, x, y)
+ if not tile_data:
+ return Image.new("RGB", (TILE_SIZE, TILE_SIZE))
+ return Image.open(io.BytesIO(tile_data))
+
+ def _draw_lines(self, image, zoom, start_x, start_y):
+ coords = (
+ to_web_mercator(point.latitude, point.longitude, zoom)
+ for point in self.track.walk(only_points=True)
+ )
+ coords = [(x - start_x, y - start_y) for x, y in coords]
+
+ draw = ImageDraw.Draw(image)
+ draw.line(coords, fill=self.color, width=self.line_width, joint="curve")
+
+
+def render(track: GPX, layer: TileLayerConfig, requester: TileRequester) -> Image.Image:
+ """Shorthand to construct a :class:`TrackMapRenderer` and render the preview.
+
+ :param track: Parsed track to render.
+ :param layer: The tile layer to take the map tiles from.
+ :param requester: The requester which will be used to request the tiles.
+ :return: The image containing the rendered preview.
+ """
+ return TrackMapRenderer(track, requester, (300, 300), layer).render()
+
+
+__all__ = ["to_web_mercator", "TrackMapRenderer", "render"]
diff --git a/fietsboek/updater/scripts/upd_20250618_v0.11.0.py b/fietsboek/updater/scripts/upd_20250618_v0.11.0.py
new file mode 100644
index 0000000..11837f7
--- /dev/null
+++ b/fietsboek/updater/scripts/upd_20250618_v0.11.0.py
@@ -0,0 +1,27 @@
+"""Revision upgrade script v0.11.0
+
+Date created: 2025-06-18 13:14:07.849714
+"""
+from fietsboek.updater.script import UpdateScript
+
+update_id = 'v0.11.0'
+previous = [
+ 'v0.10.0',
+]
+alembic_revision = '2ebe1bf66430'
+
+
+class Up(UpdateScript):
+ def pre_alembic(self, config):
+ pass
+
+ def post_alembic(self, config):
+ pass
+
+
+class Down(UpdateScript):
+ def pre_alembic(self, config):
+ pass
+
+ def post_alembic(self, config):
+ pass
diff --git a/fietsboek/util.py b/fietsboek/util.py
index 9284ce2..5611c51 100644
--- a/fietsboek/util.py
+++ b/fietsboek/util.py
@@ -7,6 +7,7 @@ import os
import re
import secrets
import unicodedata
+from pathlib import Path
from typing import Optional, TypeVar, Union
import babel
@@ -504,6 +505,18 @@ def secure_filename(filename: str) -> str:
return filename
+def recursive_size(path: Path) -> int:
+ """Recursively determines the size of the given directory.
+
+ :param path: The directory.
+ :return: The combined size, in bytes.
+ """
+ size = 0
+ for root, _folders, files in os.walk(path):
+ size += sum(os.path.getsize(os.path.join(root, fname)) for fname in files)
+ return size
+
+
__all__ = [
"ALLOWED_TAGS",
"ALLOWED_ATTRIBUTES",
@@ -529,4 +542,5 @@ __all__ = [
"tile_url",
"encode_gpx",
"secure_filename",
+ "recursive_size",
]
diff --git a/fietsboek/views/admin.py b/fietsboek/views/admin.py
index d078794..6948049 100644
--- a/fietsboek/views/admin.py
+++ b/fietsboek/views/admin.py
@@ -1,21 +1,128 @@
"""Admin views."""
+import datetime
+import platform
+import stat
+from dataclasses import dataclass
+from pathlib import Path
+
from pyramid.httpexceptions import HTTPFound
from pyramid.i18n import TranslationString as _
+from pyramid.request import Request
from pyramid.view import view_config
-from sqlalchemy import select
+from sqlalchemy import func, select
+
+from .. import models, util
+
+GOOD_CRON_THRESHOLD = datetime.timedelta(hours=1)
+
+
+def _safe_size(path: Path) -> int:
+ try:
+ res = path.stat()
+ if stat.S_ISDIR(res.st_mode):
+ return util.recursive_size(path)
+ if stat.S_ISREG(res.st_mode):
+ return res.st_size
+ return 0
+ except FileNotFoundError:
+ return 0
+
+
+@dataclass
+class SizeBreakdown:
+ """A breakdown of what objects take how much storage."""
+
+ gpx_files: int = 0
+ image_files: int = 0
+ preview_files: int = 0
+ user_maps: int = 0
-from .. import models
+
+def _get_size_breakdown(data_manager):
+ breakdown = SizeBreakdown()
+
+ for track_id in data_manager.list_tracks():
+ track = data_manager.open(track_id)
+ breakdown.gpx_files += _safe_size(track.gpx_path())
+ breakdown.preview_files += _safe_size(track.preview_path())
+ for image_id in track.images():
+ breakdown.image_files += _safe_size(track.image_path(image_id))
+
+ for user_id in data_manager.list_users():
+ user = data_manager.open_user(user_id)
+ breakdown.user_maps += _safe_size(user.heatmap_path())
+ breakdown.user_maps += _safe_size(user.tilehunt_path())
+
+ return breakdown
+
+
+def _get_fietsboek_version():
+ # pylint: disable=import-outside-toplevel
+ from fietsboek import __VERSION__
+
+ return __VERSION__
@view_config(
route_name="admin",
- renderer="fietsboek:templates/admin.jinja2",
+ renderer="fietsboek:templates/admin_overview.jinja2",
+ request_method="GET",
+ permission="admin",
+)
+def admin(request: Request):
+ """Renders the admin overview.
+
+ :param request: The Pyramid request.
+ :return: The HTTP response.
+ """
+ # False-positive with func.count()
+ # pylint: disable=not-callable
+ user_count = request.dbsession.execute(select(func.count()).select_from(models.User)).scalar()
+ track_count = request.dbsession.execute(select(func.count()).select_from(models.Track)).scalar()
+ size_total = request.data_manager.size()
+ size_breakdown = _get_size_breakdown(request.data_manager)
+
+ try:
+ distro = platform.freedesktop_os_release()["PRETTY_NAME"]
+ except OSError:
+ distro = None
+
+ try:
+ last_cronjob_timestamp = float(request.redis.get("last-cronjob"))
+ except (TypeError, ValueError):
+ last_cronjob = None
+ cron_good = False
+ else:
+ last_cronjob = datetime.datetime.fromtimestamp(last_cronjob_timestamp, datetime.UTC)
+ cron_good = (datetime.datetime.now(datetime.UTC) - last_cronjob) < GOOD_CRON_THRESHOLD
+
+ versions = {
+ "fietsboek": _get_fietsboek_version(),
+ "python": platform.python_version(),
+ "linux": platform.platform(),
+ "distro": distro,
+ }
+
+ return {
+ "user_count": user_count,
+ "track_count": track_count,
+ "total_size": size_total,
+ "size_breakdown": size_breakdown,
+ "versions": versions,
+ "last_cronjob": last_cronjob,
+ "cron_good": cron_good,
+ }
+
+
+@view_config(
+ route_name="admin-badge",
+ renderer="fietsboek:templates/admin_badges.jinja2",
request_method="GET",
permission="admin",
)
-def admin(request):
- """Renders the main admin overview.
+def admin_badges(request):
+ """Renders the badges editor.
:param request: The Pyramid request.
:type request: pyramid.request.Request
@@ -47,7 +154,7 @@ def do_badge_add(request):
request.dbsession.add(badge)
request.session.flash(request.localizer.translate(_("flash.badge_added")))
- return HTTPFound(request.route_url("admin"))
+ return HTTPFound(request.route_url("admin-badge"))
@view_config(route_name="admin-badge-edit", permission="admin", request_method="POST")
@@ -71,7 +178,7 @@ def do_badge_edit(request):
badge.title = request.params["badge-title"]
request.session.flash(request.localizer.translate(_("flash.badge_modified")))
- return HTTPFound(request.route_url("admin"))
+ return HTTPFound(request.route_url("admin-badge"))
@view_config(route_name="admin-badge-delete", permission="admin", request_method="POST")
@@ -91,7 +198,7 @@ def do_badge_delete(request):
request.dbsession.delete(badge)
request.session.flash(request.localizer.translate(_("flash.badge_deleted")))
- return HTTPFound(request.route_url("admin"))
+ return HTTPFound(request.route_url("admin-badge"))
-__all__ = ["admin", "do_badge_add", "do_badge_edit", "do_badge_delete"]
+__all__ = ["admin", "admin_badges", "do_badge_add", "do_badge_edit", "do_badge_delete"]
diff --git a/fietsboek/views/browse.py b/fietsboek/views/browse.py
index 97bee35..e8e3edf 100644
--- a/fietsboek/views/browse.py
+++ b/fietsboek/views/browse.py
@@ -1,10 +1,11 @@
"""Views for browsing all tracks."""
import datetime
+import urllib.parse
from collections.abc import Callable, Iterable
from enum import Enum
from io import RawIOBase
-from typing import TypeVar
+from typing import Iterator, TypeVar
from zipfile import ZIP_DEFLATED, ZipFile
from pyramid.httpexceptions import HTTPBadRequest, HTTPForbidden, HTTPNotFound
@@ -12,12 +13,14 @@ from pyramid.request import Request
from pyramid.response import Response
from pyramid.view import view_config
from sqlalchemy import func, not_, or_, select
-from sqlalchemy.orm import aliased
+from sqlalchemy.orm import Session, aliased
from sqlalchemy.sql import Select
from .. import models, util
+from ..data import DataManager
from ..models.track import TrackType, TrackWithMetadata
+TRACKS_PER_PAGE = 20
T = TypeVar("T", bound=Enum)
AliasedTrack = type[models.Track]
@@ -69,6 +72,14 @@ def _get_enum(enum: type[T], value: str) -> T:
raise HTTPBadRequest(f"Invalid enum value {value!r}") from exc
+def _with_page(url: str, num: int) -> str:
+ parsed = urllib.parse.urlparse(url)
+ query = urllib.parse.parse_qs(parsed.query)
+ query["page"] = [str(num)]
+ new_qs = urllib.parse.urlencode(query, doseq=True)
+ return urllib.parse.urlunparse(parsed._replace(query=new_qs))
+
+
class ResultOrder(Enum):
"""Enum representing the different ways in which the tracks can be sorted
in the result."""
@@ -415,6 +426,49 @@ def apply_order(query: Select, track: AliasedTrack, order: ResultOrder) -> Selec
return query
+def paginate(
+ dbsession: Session,
+ data_manager: DataManager,
+ query: Select,
+ filters: Filter,
+ start: int,
+ num: int,
+) -> Iterator[TrackWithMetadata]:
+ """Paginates a query.
+
+ Unlike a simple OFFSET/LIMIT solution, this generator will request more
+ elements if the filters end up throwing tracks out.
+
+ :param dbsession: The current database session.
+ :param data_manager: The current data manager.
+ :param query: The (filtered and ordered) query.
+ :param filters: The filters to apply after retrieving elements from the
+ database.
+ :param track: The aliased ``Track`` class.
+ :param start: The offset from which to start the pagination.
+ :param num: How many items to retrieve at maximum.
+ :return: An iterable over ``num`` tracks (or fewer).
+ """
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
+ num_retrieved = 0
+ offset = start
+ while num_retrieved < num:
+ # Best to try and get all at once
+ num_query = num - num_retrieved
+ this_query = query.offset(offset).limit(num_query)
+ offset += num_query
+
+ tracks = list(dbsession.execute(this_query).scalars())
+ if not tracks:
+ break
+
+ for track in tracks:
+ track = TrackWithMetadata(track, data_manager)
+ if filters.apply(track):
+ num_retrieved += 1
+ yield track
+
+
@view_config(
route_name="browse", renderer="fietsboek:templates/browse.jinja2", request_method="GET"
)
@@ -431,18 +485,34 @@ def browse(request: Request) -> Response:
query = select(track).join(models.TrackCache, isouter=True)
query = filters.compile(query, track)
+ if "page" in request.params:
+ page = _get_int(request, "page")
+ else:
+ page = 1
+
order = ResultOrder.DATE_DESC
if request.params.get("sort"):
order = _get_enum(ResultOrder, request.params.get("sort"))
query = apply_order(query, track, order)
- tracks = request.dbsession.execute(query).scalars()
- tracks = (TrackWithMetadata(track, request.data_manager) for track in tracks)
- tracks = [track for track in tracks if filters.apply(track)]
+ tracks = list(
+ paginate(
+ request.dbsession,
+ request.data_manager,
+ query,
+ filters,
+ (page - 1) * TRACKS_PER_PAGE,
+ # We request one more so we can tell easily if there is a next page
+ TRACKS_PER_PAGE + 1,
+ )
+ )
+
return {
- "tracks": tracks,
+ "tracks": tracks[:TRACKS_PER_PAGE],
"mps_to_kph": util.mps_to_kph,
"used_filters": bool(filters),
+ "page_previous": None if page == 1 else _with_page(request.url, page - 1),
+ "page_next": None if len(tracks) <= TRACKS_PER_PAGE else _with_page(request.url, page + 1),
}
diff --git a/fietsboek/views/detail.py b/fietsboek/views/detail.py
index 2bc5d9a..acc7bfe 100644
--- a/fietsboek/views/detail.py
+++ b/fietsboek/views/detail.py
@@ -2,8 +2,10 @@
import datetime
import gzip
+import io
import logging
+import gpxpy
from pyramid.httpexceptions import (
HTTPFound,
HTTPInternalServerError,
@@ -11,12 +13,14 @@ from pyramid.httpexceptions import (
HTTPNotFound,
)
from pyramid.i18n import TranslationString as _
+from pyramid.request import Request
from pyramid.response import FileResponse, Response
from pyramid.view import view_config
from sqlalchemy import select
-from .. import models, util
+from .. import models, trackmap, util
from ..models.track import Track, TrackWithMetadata
+from .tileproxy import ITileRequester
LOGGER = logging.getLogger(__name__)
@@ -222,4 +226,51 @@ def add_comment(request):
return HTTPFound(request.route_url("details", track_id=track.id))
-__all__ = ["details", "gpx", "invalidate_share", "delete_track", "badge", "image", "add_comment"]
+@view_config(route_name="track-map", http_cache=3600, permission="track.view")
+def track_map(request: Request):
+ """Endpoint to provide the track's preview image.
+
+ Will use the cached version if available. Otherwise, will create the
+ preview and cache it.
+
+ :param request: The pyramid request.
+ :return: The HTTP response.
+ """
+ track = request.context
+ manager = request.data_manager.open(track.id)
+ preview_path = manager.preview_path()
+ try:
+ response = Response(preview_path.read_bytes(), content_type="image/png")
+ response.md5_etag()
+ return response
+ except FileNotFoundError:
+ pass
+
+ loader: ITileRequester = request.registry.getUtility(ITileRequester)
+ layer = request.config.public_tile_layers()[0]
+
+ parsed_gpx = gpxpy.parse(manager.decompress_gpx())
+ track_image = trackmap.render(parsed_gpx, layer, loader)
+
+ imageio = io.BytesIO()
+ track_image.save(imageio, "png")
+ tile_data = imageio.getvalue()
+
+ with manager.lock():
+ manager.set_preview(tile_data)
+
+ response = Response(tile_data, content_type="image/png")
+ response.md5_etag()
+ return response
+
+
+__all__ = [
+ "details",
+ "gpx",
+ "invalidate_share",
+ "delete_track",
+ "badge",
+ "image",
+ "add_comment",
+ "track_map",
+]
diff --git a/fietsboek/views/edit.py b/fietsboek/views/edit.py
index c3a4fc5..3daaacd 100644
--- a/fietsboek/views/edit.py
+++ b/fietsboek/views/edit.py
@@ -4,11 +4,13 @@ import datetime
import logging
from collections import namedtuple
+import gpxpy
from pyramid.httpexceptions import HTTPBadRequest, HTTPFound
+from pyramid.i18n import TranslationString as _
from pyramid.view import view_config
from sqlalchemy import select
-from .. import actions, models, util
+from .. import actions, convert, models, util
from ..data import TrackDataDir
from ..models.track import TrackType, Visibility
@@ -68,7 +70,7 @@ def do_edit(request):
:return: The HTTP response.
:rtype: pyramid.response.Response
"""
- # pylint: disable=duplicate-code
+ # pylint: disable=duplicate-code,broad-exception-caught
track = request.context
user_friends = request.identity.get_friends()
@@ -84,6 +86,25 @@ def do_edit(request):
tz_offset = datetime.timedelta(minutes=int(request.params["date-tz"]))
date = datetime.datetime.fromisoformat(request.params["date"])
with data, data.lock():
+ redo_cache = False
+ try:
+ gpx_bytes = request.POST["gpx"].file.read()
+ except AttributeError:
+ pass
+ else:
+ LOGGER.info("Setting new track for %s", track.id)
+ gpx_bytes = convert.smart_convert(gpx_bytes)
+ try:
+ gpxpy.parse(gpx_bytes)
+ except Exception as exc:
+ request.session.flash(request.localizer.translate(_("flash.invalid_file")))
+ LOGGER.info("Could not parse updated gpx: %s", exc)
+ return HTTPFound(request.route_url("edit", track_id=track.id))
+ data.compress_gpx(gpx_bytes)
+ data.backup()
+ track.transformers = []
+ redo_cache = True
+
track.date = date.replace(tzinfo=datetime.timezone(tz_offset))
track.tagged_people = tagged_people
@@ -105,6 +126,14 @@ def do_edit(request):
gpx=gpx,
)
+ # actions.execute_transformers automatically rebuilds the cache, so we only need to do
+ # this if execute_transformers didn't do it
+ if redo_cache and gpx is None:
+ LOGGER.info("New file detected, rebuilding cache for %s", track.id)
+ track.cache = None
+ track.ensure_cache(gpx_bytes)
+ request.dbsession.add(track.cache)
+
return HTTPFound(request.route_url("details", track_id=track.id))
diff --git a/fietsboek/views/errors.py b/fietsboek/views/errors.py
new file mode 100644
index 0000000..39af14e
--- /dev/null
+++ b/fietsboek/views/errors.py
@@ -0,0 +1,32 @@
+"""Error views."""
+
+from pyramid.view import forbidden_view_config, notfound_view_config
+
+
+@notfound_view_config(renderer="fietsboek:templates/404.jinja2")
+def notfound_view(request):
+ """Renders the 404 response.
+
+ :param request: The Pyramid request.
+ :type request: pyramid.request.Request
+ :return: The HTTP response.
+ :rtype: pyramid.response.Response
+ """
+ request.response.status = 404
+ return {}
+
+
+@forbidden_view_config(renderer="fietsboek:templates/403.jinja2")
+def forbidden_view(request):
+ """Renders the 403 response.
+
+ :param request: The Pyramid request.
+ :type request: pyramid.request.Request
+ :return: The HTTP response.
+ :rtype: pyramid.response.Response
+ """
+ request.response.status = 403
+ return {}
+
+
+__all__ = ["notfound_view", "forbidden_view"]
diff --git a/fietsboek/views/notfound.py b/fietsboek/views/notfound.py
deleted file mode 100644
index 2ec6c6c..0000000
--- a/fietsboek/views/notfound.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""Error views."""
-
-from pyramid.view import notfound_view_config
-
-
-@notfound_view_config(renderer="fietsboek:templates/404.jinja2")
-def notfound_view(request):
- """Renders the 404 response.
-
- :param request: The Pyramid request.
- :type request: pyramid.request.Request
- :return: The HTTP response.
- :rtype: pyramid.response.Response
- """
- request.response.status = 404
- return {}
-
-
-__all__ = ["notfound_view"]
diff --git a/fietsboek/views/tileproxy.py b/fietsboek/views/tileproxy.py
index 0efe4de..f472d6d 100644
--- a/fietsboek/views/tileproxy.py
+++ b/fietsboek/views/tileproxy.py
@@ -242,18 +242,44 @@ Note that new requests reset the timeout.
class ITileRequester(Interface): # pylint: disable=inherit-non-class
"""An interface to define the tile requester."""
- def load_tile(self, url: str, headers: Optional[dict[str, str]] = None) -> requests.Response:
+ # pylint: disable=too-many-arguments
+
+ def load_url(self, url: str, headers: Optional[dict[str, str]] = None) -> requests.Response:
"""Loads a tile at the given URL.
+ This is a low-level version of :meth:`~ITileRequester.load_tile`.
+
:param url: The URL of the tile to load.
:param headers: Additional headers to send.
:return: The response.
"""
raise NotImplementedError()
+ def load_tile(
+ self,
+ layer: TileLayerConfig,
+ zoom: int,
+ x: int,
+ y: int,
+ *,
+ headers: Optional[dict[str, str]] = None,
+ use_cache: bool = True,
+ ) -> bytes:
+ """Loads a tile from the given layer.
+
+ :param layer: The configured tile layer.
+ :param zoom: The zoom level.
+ :param x: The tile's x coordinate.
+ :param y: The tile's y coordinate.
+ :param headers: Additional headers.
+ :param use_cache: Whether to use the cache (if available).
+ :return: The bytes of the tile.
+ """
+ raise NotImplementedError()
+
@implementer(ITileRequester)
-class TileRequester: # pylint: disable=too-few-public-methods
+class TileRequester:
"""Implementation of the tile requester using requests sessions.
The benefit of this over doing ``requests.get`` is that we can re-use
@@ -261,7 +287,9 @@ class TileRequester: # pylint: disable=too-few-public-methods
servers by not hammering them with too many connections.
"""
- def __init__(self):
+ # pylint: disable=too-many-arguments
+
+ def __init__(self, redis):
self.session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
pool_maxsize=MAX_CONCURRENT_CONNECTIONS,
@@ -271,14 +299,56 @@ class TileRequester: # pylint: disable=too-few-public-methods
self.session.mount("https://", adapter)
self.lock = threading.Lock()
self.closer = None
-
- def load_tile(self, url: str, headers: Optional[dict[str, str]] = None) -> requests.Response:
- """Implementation of :meth:`ITileRequester.load_tile`."""
+ self.redis = redis
+
+ def load_url(self, url: str, headers: Optional[dict[str, str]] = None) -> requests.Response:
+ """Implementation of :meth:`ITileRequester.load_url`."""
+ if headers is None:
+ headers = {}
+ if "user-agent" not in headers:
+ headers["user-agent"] = f"Fietsboek/{__VERSION__}"
response = self.session.get(url, headers=headers, timeout=TIMEOUT.total_seconds())
response.raise_for_status()
self._schedule_session_close()
return response
+ def load_tile(
+ self,
+ layer: TileLayerConfig,
+ zoom: int,
+ x: int,
+ y: int,
+ *,
+ headers: Optional[dict[str, str]] = None,
+ use_cache: bool = True,
+ ) -> bytes:
+ """Implementation of :meth:`ITileRequester.load_tile`."""
+ cache_key = f"tile:{layer.layer_id}-{x}-{y}-{zoom}"
+
+ if use_cache and self.redis is not None:
+ cached = self.redis.get(cache_key)
+ if cached is not None:
+ LOGGER.debug("Cache hit for %s/z:%s/x:%s/y:%s", layer.layer_id, zoom, x, y)
+ return cached
+
+ url = (
+ layer.url.unicode_string()
+ .replace(quote("{x}"), str(x))
+ .replace(quote("{y}"), str(y))
+ .replace(quote("{z}"), str(zoom))
+ )
+
+ # Avoid doing actual requests during tests
+ if url.startswith("http://localhost:0"):
+ LOGGER.debug("Skipping tile request for testing URL")
+ return b""
+
+ response = self.load_url(url, headers=headers)
+ tile_data = response.content
+ if use_cache and self.redis is not None:
+ self.redis.set(cache_key, tile_data, ex=TTL)
+ return tile_data
+
def _schedule_session_close(self):
with self.lock:
if self.closer:
@@ -332,29 +402,17 @@ def tile_proxy(request):
LOGGER.debug("Aborted attempt to contact %s due to previous timeouts", provider)
raise HTTPGatewayTimeout(f"Avoiding request to {provider}")
- url = (
- tile_sources[provider]
- .url.unicode_string()
- .replace(quote("{x}"), str(x))
- .replace(quote("{y}"), str(y))
- .replace(quote("{z}"), str(z))
- )
- # Avoid doing actual requests during tests
- if url.startswith("http://localhost:0"):
- LOGGER.debug("Skipping tile proxy request for testing URL")
- return Response(b"", content_type="image/png")
- headers = {
- "user-agent": f"Fietsboek-Tile-Proxy/{__VERSION__}",
- }
+ headers = {}
from_mail = request.config.email_from
if from_mail:
headers["from"] = from_mail
loader: ITileRequester = request.registry.getUtility(ITileRequester)
try:
- resp = loader.load_tile(url, headers=headers)
+ # We already tried the cache, so bypass it here
+ resp = loader.load_tile(tile_sources[provider], z, x, y, headers=headers, use_cache=False)
except ReadTimeout:
- LOGGER.debug("Proxy timeout when accessing %r", url)
+ LOGGER.debug("Proxy timeout when accessing z:%s/x:%s/y:%s from %s", z, x, y, provider)
request.redis.incr(timeout_tracker)
request.redis.expire(timeout_tracker, PUNISHMENT_TTL)
raise HTTPGatewayTimeout(f"No response in time from {provider}") from None
@@ -365,8 +423,8 @@ def tile_proxy(request):
status_code = exc.response.status_code
return Response(f"Failed to get tile from {provider}", status_code=status_code)
else:
- request.redis.set(cache_key, resp.content, ex=TTL)
- return Response(resp.content, content_type=resp.headers.get("Content-type", content_type))
+ request.redis.set(cache_key, resp, ex=TTL)
+ return Response(resp, content_type=content_type)
def sources_for(request: Request) -> list[TileLayerConfig]:
@@ -375,6 +433,9 @@ def sources_for(request: Request) -> list[TileLayerConfig]:
:param request: The Pyramid request.
:return: A list of tile sources.
"""
+ # This code is similar to Config.public_tile_layers. Maybe it's worth
+ # refactoring it when we refactor this module?
+ # pylint: disable=duplicate-code
return [
source
for source in chain(
diff --git a/fietsboek/views/upload.py b/fietsboek/views/upload.py
index c40319c..3eb1099 100644
--- a/fietsboek/views/upload.py
+++ b/fietsboek/views/upload.py
@@ -12,6 +12,7 @@ from sqlalchemy import select
from .. import actions, convert, models, transformers, util
from ..models.track import TrackType, Visibility
+from ..views.tileproxy import ITileRequester
LOGGER = logging.getLogger(__name__)
@@ -53,8 +54,7 @@ def do_upload(request):
request.session.flash(request.localizer.translate(_("flash.no_file_selected")))
return HTTPFound(request.route_url("upload"))
- if len(gpx) > 11 and gpx[9:12] == b"FIT":
- gpx = convert.from_fit(gpx).to_xml().encode("utf-8")
+ gpx = convert.smart_convert(gpx)
# Before we do anything, we check if we can parse the file.
# gpxpy might throw different exceptions, so we simply catch `Exception`
@@ -165,6 +165,8 @@ def do_finish_upload(request):
track = actions.add_track(
request.dbsession,
request.data_manager,
+ request.registry.getUtility(ITileRequester),
+ request.config.public_tile_layers()[0],
owner=request.identity,
title=request.params["title"],
visibility=Visibility[request.params["visibility"]],
diff --git a/poetry.lock b/poetry.lock
index c1347df..19c12d6 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
[[package]]
name = "alabaster"
@@ -6,6 +6,7 @@ version = "1.0.0"
description = "A light, configurable Sphinx theme"
optional = false
python-versions = ">=3.10"
+groups = ["docs"]
files = [
{file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"},
{file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"},
@@ -13,22 +14,23 @@ files = [
[[package]]
name = "alembic"
-version = "1.14.1"
+version = "1.16.2"
description = "A database migration tool for SQLAlchemy."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "alembic-1.14.1-py3-none-any.whl", hash = "sha256:1acdd7a3a478e208b0503cd73614d5e4c6efafa4e73518bb60e4f2846a37b1c5"},
- {file = "alembic-1.14.1.tar.gz", hash = "sha256:496e888245a53adf1498fcab31713a469c65836f8de76e01399aa1c3e90dd213"},
+ {file = "alembic-1.16.2-py3-none-any.whl", hash = "sha256:5f42e9bd0afdbd1d5e3ad856c01754530367debdebf21ed6894e34af52b3bb03"},
+ {file = "alembic-1.16.2.tar.gz", hash = "sha256:e53c38ff88dadb92eb22f8b150708367db731d58ad7e9d417c9168ab516cbed8"},
]
[package.dependencies]
Mako = "*"
-SQLAlchemy = ">=1.3.0"
-typing-extensions = ">=4"
+SQLAlchemy = ">=1.4.0"
+typing-extensions = ">=4.12"
[package.extras]
-tz = ["backports.zoneinfo", "tzdata"]
+tz = ["tzdata"]
[[package]]
name = "annotated-types"
@@ -36,6 +38,7 @@ version = "0.7.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
@@ -43,24 +46,24 @@ files = [
[[package]]
name = "astroid"
-version = "3.3.8"
+version = "3.3.10"
description = "An abstract syntax tree for Python with inference support."
optional = false
python-versions = ">=3.9.0"
+groups = ["linters"]
files = [
- {file = "astroid-3.3.8-py3-none-any.whl", hash = "sha256:187ccc0c248bfbba564826c26f070494f7bc964fd286b6d9fff4420e55de828c"},
- {file = "astroid-3.3.8.tar.gz", hash = "sha256:a88c7994f914a4ea8572fac479459f4955eeccc877be3f2d959a33273b0cf40b"},
+ {file = "astroid-3.3.10-py3-none-any.whl", hash = "sha256:104fb9cb9b27ea95e847a94c003be03a9e039334a8ebca5ee27dafaf5c5711eb"},
+ {file = "astroid-3.3.10.tar.gz", hash = "sha256:c332157953060c6deb9caa57303ae0d20b0fbdb2e59b4a4f2a6ba49d0a7961ce"},
]
-[package.dependencies]
-typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""}
-
[[package]]
name = "async-timeout"
version = "5.0.1"
description = "Timeout context manager for asyncio programs"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
+markers = "python_version == \"3.11\" and python_full_version < \"3.11.3\""
files = [
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
@@ -68,31 +71,34 @@ files = [
[[package]]
name = "babel"
-version = "2.16.0"
+version = "2.17.0"
description = "Internationalization utilities"
optional = false
python-versions = ">=3.8"
+groups = ["main", "docs"]
files = [
- {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"},
- {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"},
+ {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"},
+ {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"},
]
[package.extras]
-dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
+dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
[[package]]
name = "beautifulsoup4"
-version = "4.12.3"
+version = "4.13.4"
description = "Screen-scraping library"
optional = false
-python-versions = ">=3.6.0"
+python-versions = ">=3.7.0"
+groups = ["testing"]
files = [
- {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"},
- {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"},
+ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"},
+ {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"},
]
[package.dependencies]
soupsieve = ">1.2"
+typing-extensions = ">=4.0.0"
[package.extras]
cchardet = ["cchardet"]
@@ -107,6 +113,7 @@ version = "25.1.0"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.9"
+groups = ["linters"]
files = [
{file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"},
{file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"},
@@ -138,8 +145,6 @@ mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
@@ -153,6 +158,7 @@ version = "1.1.0"
description = "Python bindings for the Brotli compression library"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1140c64812cb9b06c922e77f1c26a75ec5e3f0fb2bf92cc8c58720dec276752"},
{file = "Brotli-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8fd5270e906eef71d4a8d19b7c6a43760c6abcfcc10c9101d14eb2357418de9"},
@@ -241,13 +247,14 @@ files = [
[[package]]
name = "certifi"
-version = "2024.12.14"
+version = "2025.6.15"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
+groups = ["main", "docs", "testing"]
files = [
- {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
- {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
+ {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"},
+ {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"},
]
[[package]]
@@ -256,6 +263,8 @@ version = "1.17.1"
description = "Foreign Function Interface for Python calling C code."
optional = false
python-versions = ">=3.8"
+groups = ["main", "types"]
+markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
@@ -331,114 +340,116 @@ pycparser = "*"
[[package]]
name = "charset-normalizer"
-version = "3.4.1"
+version = "3.4.2"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
optional = false
python-versions = ">=3.7"
-files = [
- {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"},
- {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"},
- {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"},
- {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"},
- {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"},
- {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"},
- {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"},
- {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"},
- {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"},
- {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"},
+groups = ["main", "docs", "testing"]
+files = [
+ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"},
+ {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"},
+ {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"},
+ {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"},
+ {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"},
+ {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"},
+ {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"},
+ {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"},
+ {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"},
+ {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"},
]
[[package]]
name = "click"
-version = "8.1.8"
+version = "8.2.1"
description = "Composable command line interface toolkit"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.10"
+groups = ["main", "linters"]
files = [
- {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
- {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
+ {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"},
+ {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"},
]
[package.dependencies]
@@ -446,22 +457,24 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "click-option-group"
-version = "0.5.6"
+version = "0.5.7"
description = "Option groups missing in Click"
optional = false
-python-versions = ">=3.6,<4"
+python-versions = ">=3.7"
+groups = ["main"]
files = [
- {file = "click-option-group-0.5.6.tar.gz", hash = "sha256:97d06703873518cc5038509443742b25069a3c7562d1ea72ff08bfadde1ce777"},
- {file = "click_option_group-0.5.6-py3-none-any.whl", hash = "sha256:38a26d963ee3ad93332ddf782f9259c5bdfe405e73408d943ef5e7d0c3767ec7"},
+ {file = "click_option_group-0.5.7-py3-none-any.whl", hash = "sha256:96b9f52f397ef4d916f81929bd6c1f85e89046c7a401a64e72a61ae74ad35c24"},
+ {file = "click_option_group-0.5.7.tar.gz", hash = "sha256:8dc780be038712fc12c9fecb3db4fe49e0d0723f9c171d7cda85c20369be693c"},
]
[package.dependencies]
-Click = ">=7.0,<9"
+click = ">=7.0"
[package.extras]
-docs = ["Pallets-Sphinx-Themes", "m2r2", "sphinx"]
-tests = ["pytest"]
-tests-cov = ["coverage", "coveralls", "pytest", "pytest-cov"]
+dev = ["pre-commit", "pytest"]
+docs = ["m2r2", "pallets-sphinx-themes", "sphinx"]
+test = ["pytest"]
+test-cov = ["pytest", "pytest-cov"]
[[package]]
name = "colorama"
@@ -469,146 +482,163 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main", "docs", "linters", "testing"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+markers = {main = "platform_system == \"Windows\"", docs = "sys_platform == \"win32\"", linters = "platform_system == \"Windows\" or sys_platform == \"win32\"", testing = "sys_platform == \"win32\""}
[[package]]
name = "coverage"
-version = "7.6.10"
+version = "7.9.1"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.9"
-files = [
- {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"},
- {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"},
- {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"},
- {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"},
- {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"},
- {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"},
- {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"},
- {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"},
- {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"},
- {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"},
- {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"},
- {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"},
- {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"},
- {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"},
- {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"},
- {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"},
- {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"},
- {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"},
- {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"},
- {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"},
- {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"},
- {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"},
- {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"},
- {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"},
- {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"},
- {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"},
- {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"},
- {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"},
- {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"},
- {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"},
- {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"},
- {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"},
- {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"},
- {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"},
- {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"},
- {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"},
- {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"},
- {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"},
- {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"},
- {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"},
- {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"},
- {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"},
- {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"},
- {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"},
- {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"},
- {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"},
- {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"},
- {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"},
- {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"},
- {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"},
- {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"},
- {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"},
- {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"},
- {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"},
- {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"},
- {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"},
- {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"},
- {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"},
- {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"},
- {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"},
- {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"},
- {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"},
+groups = ["testing"]
+files = [
+ {file = "coverage-7.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc94d7c5e8423920787c33d811c0be67b7be83c705f001f7180c7b186dcf10ca"},
+ {file = "coverage-7.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16aa0830d0c08a2c40c264cef801db8bc4fc0e1892782e45bcacbd5889270509"},
+ {file = "coverage-7.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf95981b126f23db63e9dbe4cf65bd71f9a6305696fa5e2262693bc4e2183f5b"},
+ {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f05031cf21699785cd47cb7485f67df619e7bcdae38e0fde40d23d3d0210d3c3"},
+ {file = "coverage-7.9.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4fbcab8764dc072cb651a4bcda4d11fb5658a1d8d68842a862a6610bd8cfa3"},
+ {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16649a7330ec307942ed27d06ee7e7a38417144620bb3d6e9a18ded8a2d3e5"},
+ {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cea0a27a89e6432705fffc178064503508e3c0184b4f061700e771a09de58187"},
+ {file = "coverage-7.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e980b53a959fa53b6f05343afbd1e6f44a23ed6c23c4b4c56c6662bbb40c82ce"},
+ {file = "coverage-7.9.1-cp310-cp310-win32.whl", hash = "sha256:70760b4c5560be6ca70d11f8988ee6542b003f982b32f83d5ac0b72476607b70"},
+ {file = "coverage-7.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a66e8f628b71f78c0e0342003d53b53101ba4e00ea8dabb799d9dba0abbbcebe"},
+ {file = "coverage-7.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95c765060e65c692da2d2f51a9499c5e9f5cf5453aeaf1420e3fc847cc060582"},
+ {file = "coverage-7.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba383dc6afd5ec5b7a0d0c23d38895db0e15bcba7fb0fa8901f245267ac30d86"},
+ {file = "coverage-7.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37ae0383f13cbdcf1e5e7014489b0d71cc0106458878ccde52e8a12ced4298ed"},
+ {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69aa417a030bf11ec46149636314c24c8d60fadb12fc0ee8f10fda0d918c879d"},
+ {file = "coverage-7.9.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a4be2a28656afe279b34d4f91c3e26eccf2f85500d4a4ff0b1f8b54bf807338"},
+ {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:382e7ddd5289f140259b610e5f5c58f713d025cb2f66d0eb17e68d0a94278875"},
+ {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e5532482344186c543c37bfad0ee6069e8ae4fc38d073b8bc836fc8f03c9e250"},
+ {file = "coverage-7.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a39d18b3f50cc121d0ce3838d32d58bd1d15dab89c910358ebefc3665712256c"},
+ {file = "coverage-7.9.1-cp311-cp311-win32.whl", hash = "sha256:dd24bd8d77c98557880def750782df77ab2b6885a18483dc8588792247174b32"},
+ {file = "coverage-7.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b55ad10a35a21b8015eabddc9ba31eb590f54adc9cd39bcf09ff5349fd52125"},
+ {file = "coverage-7.9.1-cp311-cp311-win_arm64.whl", hash = "sha256:6ad935f0016be24c0e97fc8c40c465f9c4b85cbbe6eac48934c0dc4d2568321e"},
+ {file = "coverage-7.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8de12b4b87c20de895f10567639c0797b621b22897b0af3ce4b4e204a743626"},
+ {file = "coverage-7.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5add197315a054e92cee1b5f686a2bcba60c4c3e66ee3de77ace6c867bdee7cb"},
+ {file = "coverage-7.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600a1d4106fe66f41e5d0136dfbc68fe7200a5cbe85610ddf094f8f22e1b0300"},
+ {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a876e4c3e5a2a1715a6608906aa5a2e0475b9c0f68343c2ada98110512ab1d8"},
+ {file = "coverage-7.9.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81f34346dd63010453922c8e628a52ea2d2ccd73cb2487f7700ac531b247c8a5"},
+ {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:888f8eee13f2377ce86d44f338968eedec3291876b0b8a7289247ba52cb984cd"},
+ {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9969ef1e69b8c8e1e70d591f91bbc37fc9a3621e447525d1602801a24ceda898"},
+ {file = "coverage-7.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:60c458224331ee3f1a5b472773e4a085cc27a86a0b48205409d364272d67140d"},
+ {file = "coverage-7.9.1-cp312-cp312-win32.whl", hash = "sha256:5f646a99a8c2b3ff4c6a6e081f78fad0dde275cd59f8f49dc4eab2e394332e74"},
+ {file = "coverage-7.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:30f445f85c353090b83e552dcbbdad3ec84c7967e108c3ae54556ca69955563e"},
+ {file = "coverage-7.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:af41da5dca398d3474129c58cb2b106a5d93bbb196be0d307ac82311ca234342"},
+ {file = "coverage-7.9.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:31324f18d5969feef7344a932c32428a2d1a3e50b15a6404e97cba1cc9b2c631"},
+ {file = "coverage-7.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0c804506d624e8a20fb3108764c52e0eef664e29d21692afa375e0dd98dc384f"},
+ {file = "coverage-7.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef64c27bc40189f36fcc50c3fb8f16ccda73b6a0b80d9bd6e6ce4cffcd810bbd"},
+ {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4fe2348cc6ec372e25adec0219ee2334a68d2f5222e0cba9c0d613394e12d86"},
+ {file = "coverage-7.9.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34ed2186fe52fcc24d4561041979a0dec69adae7bce2ae8d1c49eace13e55c43"},
+ {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25308bd3d00d5eedd5ae7d4357161f4df743e3c0240fa773ee1b0f75e6c7c0f1"},
+ {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:73e9439310f65d55a5a1e0564b48e34f5369bee943d72c88378f2d576f5a5751"},
+ {file = "coverage-7.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ab6be0859141b53aa89412a82454b482c81cf750de4f29223d52268a86de67"},
+ {file = "coverage-7.9.1-cp313-cp313-win32.whl", hash = "sha256:64bdd969456e2d02a8b08aa047a92d269c7ac1f47e0c977675d550c9a0863643"},
+ {file = "coverage-7.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:be9e3f68ca9edb897c2184ad0eee815c635565dbe7a0e7e814dc1f7cbab92c0a"},
+ {file = "coverage-7.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:1c503289ffef1d5105d91bbb4d62cbe4b14bec4d13ca225f9c73cde9bb46207d"},
+ {file = "coverage-7.9.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0b3496922cb5f4215bf5caaef4cf12364a26b0be82e9ed6d050f3352cf2d7ef0"},
+ {file = "coverage-7.9.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9565c3ab1c93310569ec0d86b017f128f027cab0b622b7af288696d7ed43a16d"},
+ {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2241ad5dbf79ae1d9c08fe52b36d03ca122fb9ac6bca0f34439e99f8327ac89f"},
+ {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bb5838701ca68b10ebc0937dbd0eb81974bac54447c55cd58dea5bca8451029"},
+ {file = "coverage-7.9.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b30a25f814591a8c0c5372c11ac8967f669b97444c47fd794926e175c4047ece"},
+ {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2d04b16a6062516df97969f1ae7efd0de9c31eb6ebdceaa0d213b21c0ca1a683"},
+ {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7931b9e249edefb07cd6ae10c702788546341d5fe44db5b6108a25da4dca513f"},
+ {file = "coverage-7.9.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52e92b01041151bf607ee858e5a56c62d4b70f4dac85b8c8cb7fb8a351ab2c10"},
+ {file = "coverage-7.9.1-cp313-cp313t-win32.whl", hash = "sha256:684e2110ed84fd1ca5f40e89aa44adf1729dc85444004111aa01866507adf363"},
+ {file = "coverage-7.9.1-cp313-cp313t-win_amd64.whl", hash = "sha256:437c576979e4db840539674e68c84b3cda82bc824dd138d56bead1435f1cb5d7"},
+ {file = "coverage-7.9.1-cp313-cp313t-win_arm64.whl", hash = "sha256:18a0912944d70aaf5f399e350445738a1a20b50fbea788f640751c2ed9208b6c"},
+ {file = "coverage-7.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f424507f57878e424d9a95dc4ead3fbdd72fd201e404e861e465f28ea469951"},
+ {file = "coverage-7.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:535fde4001b2783ac80865d90e7cc7798b6b126f4cd8a8c54acfe76804e54e58"},
+ {file = "coverage-7.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02532fd3290bb8fa6bec876520842428e2a6ed6c27014eca81b031c2d30e3f71"},
+ {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56f5eb308b17bca3bbff810f55ee26d51926d9f89ba92707ee41d3c061257e55"},
+ {file = "coverage-7.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfa447506c1a52271f1b0de3f42ea0fa14676052549095e378d5bff1c505ff7b"},
+ {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ca8e220006966b4a7b68e8984a6aee645a0384b0769e829ba60281fe61ec4f7"},
+ {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49f1d0788ba5b7ba65933f3a18864117c6506619f5ca80326b478f72acf3f385"},
+ {file = "coverage-7.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68cd53aec6f45b8e4724c0950ce86eacb775c6be01ce6e3669fe4f3a21e768ed"},
+ {file = "coverage-7.9.1-cp39-cp39-win32.whl", hash = "sha256:95335095b6c7b1cc14c3f3f17d5452ce677e8490d101698562b2ffcacc304c8d"},
+ {file = "coverage-7.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:e1b5191d1648acc439b24721caab2fd0c86679d8549ed2c84d5a7ec1bedcc244"},
+ {file = "coverage-7.9.1-pp39.pp310.pp311-none-any.whl", hash = "sha256:db0f04118d1db74db6c9e1cb1898532c7dcc220f1d2718f058601f7c3f499514"},
+ {file = "coverage-7.9.1-py3-none-any.whl", hash = "sha256:66b974b145aa189516b6bf2d8423e888b742517d37872f6ee4c5be0073bd9a3c"},
+ {file = "coverage-7.9.1.tar.gz", hash = "sha256:6cf43c78c4282708a28e466316935ec7489a9c487518a77fa68f716c67909cec"},
]
-[package.dependencies]
-tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
-
[package.extras]
-toml = ["tomli"]
+toml = ["tomli ; python_full_version <= \"3.11.0a6\""]
[[package]]
name = "cryptography"
-version = "44.0.0"
+version = "45.0.4"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
optional = false
python-versions = "!=3.9.0,!=3.9.1,>=3.7"
-files = [
- {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"},
- {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"},
- {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"},
- {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"},
- {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"},
- {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"},
- {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"},
- {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"},
- {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"},
- {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"},
- {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"},
- {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"},
- {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"},
- {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"},
- {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"},
- {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"},
- {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"},
- {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"},
- {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"},
- {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"},
- {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"},
- {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"},
- {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"},
- {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"},
- {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"},
- {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"},
- {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"},
+groups = ["main", "types"]
+files = [
+ {file = "cryptography-45.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:425a9a6ac2823ee6e46a76a21a4e8342d8fa5c01e08b823c1f19a8b74f096069"},
+ {file = "cryptography-45.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:680806cf63baa0039b920f4976f5f31b10e772de42f16310a6839d9f21a26b0d"},
+ {file = "cryptography-45.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4ca0f52170e821bc8da6fc0cc565b7bb8ff8d90d36b5e9fdd68e8a86bdf72036"},
+ {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f3fe7a5ae34d5a414957cc7f457e2b92076e72938423ac64d215722f6cf49a9e"},
+ {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:25eb4d4d3e54595dc8adebc6bbd5623588991d86591a78c2548ffb64797341e2"},
+ {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ce1678a2ccbe696cf3af15a75bb72ee008d7ff183c9228592ede9db467e64f1b"},
+ {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:49fe9155ab32721b9122975e168a6760d8ce4cffe423bcd7ca269ba41b5dfac1"},
+ {file = "cryptography-45.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2882338b2a6e0bd337052e8b9007ced85c637da19ef9ecaf437744495c8c2999"},
+ {file = "cryptography-45.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:23b9c3ea30c3ed4db59e7b9619272e94891f8a3a5591d0b656a7582631ccf750"},
+ {file = "cryptography-45.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0a97c927497e3bc36b33987abb99bf17a9a175a19af38a892dc4bbb844d7ee2"},
+ {file = "cryptography-45.0.4-cp311-abi3-win32.whl", hash = "sha256:e00a6c10a5c53979d6242f123c0a97cff9f3abed7f064fc412c36dc521b5f257"},
+ {file = "cryptography-45.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:817ee05c6c9f7a69a16200f0c90ab26d23a87701e2a284bd15156783e46dbcc8"},
+ {file = "cryptography-45.0.4-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:964bcc28d867e0f5491a564b7debb3ffdd8717928d315d12e0d7defa9e43b723"},
+ {file = "cryptography-45.0.4-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6a5bf57554e80f75a7db3d4b1dacaa2764611ae166ab42ea9a72bcdb5d577637"},
+ {file = "cryptography-45.0.4-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:46cf7088bf91bdc9b26f9c55636492c1cce3e7aaf8041bbf0243f5e5325cfb2d"},
+ {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7bedbe4cc930fa4b100fc845ea1ea5788fcd7ae9562e669989c11618ae8d76ee"},
+ {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eaa3e28ea2235b33220b949c5a0d6cf79baa80eab2eb5607ca8ab7525331b9ff"},
+ {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7ef2dde4fa9408475038fc9aadfc1fb2676b174e68356359632e980c661ec8f6"},
+ {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6a3511ae33f09094185d111160fd192c67aa0a2a8d19b54d36e4c78f651dc5ad"},
+ {file = "cryptography-45.0.4-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:06509dc70dd71fa56eaa138336244e2fbaf2ac164fc9b5e66828fccfd2b680d6"},
+ {file = "cryptography-45.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5f31e6b0a5a253f6aa49be67279be4a7e5a4ef259a9f33c69f7d1b1191939872"},
+ {file = "cryptography-45.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:944e9ccf67a9594137f942d5b52c8d238b1b4e46c7a0c2891b7ae6e01e7c80a4"},
+ {file = "cryptography-45.0.4-cp37-abi3-win32.whl", hash = "sha256:c22fe01e53dc65edd1945a2e6f0015e887f84ced233acecb64b4daadb32f5c97"},
+ {file = "cryptography-45.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:627ba1bc94f6adf0b0a2e35d87020285ead22d9f648c7e75bb64f367375f3b22"},
+ {file = "cryptography-45.0.4-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a77c6fb8d76e9c9f99f2f3437c1a4ac287b34eaf40997cfab1e9bd2be175ac39"},
+ {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7aad98a25ed8ac917fdd8a9c1e706e5a0956e06c498be1f713b61734333a4507"},
+ {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3530382a43a0e524bc931f187fc69ef4c42828cf7d7f592f7f249f602b5a4ab0"},
+ {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:6b613164cb8425e2f8db5849ffb84892e523bf6d26deb8f9bb76ae86181fa12b"},
+ {file = "cryptography-45.0.4-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:96d4819e25bf3b685199b304a0029ce4a3caf98947ce8a066c9137cc78ad2c58"},
+ {file = "cryptography-45.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b97737a3ffbea79eebb062eb0d67d72307195035332501722a9ca86bab9e3ab2"},
+ {file = "cryptography-45.0.4-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4828190fb6c4bcb6ebc6331f01fe66ae838bb3bd58e753b59d4b22eb444b996c"},
+ {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:03dbff8411206713185b8cebe31bc5c0eb544799a50c09035733716b386e61a4"},
+ {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51dfbd4d26172d31150d84c19bbe06c68ea4b7f11bbc7b3a5e146b367c311349"},
+ {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:0339a692de47084969500ee455e42c58e449461e0ec845a34a6a9b9bf7df7fb8"},
+ {file = "cryptography-45.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:0cf13c77d710131d33e63626bd55ae7c0efb701ebdc2b3a7952b9b23a0412862"},
+ {file = "cryptography-45.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bbc505d1dc469ac12a0a064214879eac6294038d6b24ae9f71faae1448a9608d"},
+ {file = "cryptography-45.0.4.tar.gz", hash = "sha256:7405ade85c83c37682c8fe65554759800a4a8c54b2d96e0f8ad114d31b808d57"},
]
[package.dependencies]
-cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
+cffi = {version = ">=1.14", markers = "platform_python_implementation != \"PyPy\""}
[package.extras]
-docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"]
+docs = ["sphinx (>=5.3.0)", "sphinx-inline-tabs ; python_full_version >= \"3.8.0\"", "sphinx-rtd-theme (>=3.0.0) ; python_full_version >= \"3.8.0\""]
docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"]
-nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"]
-pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
+nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_full_version >= \"3.8.0\""]
+pep8test = ["check-sdist ; python_full_version >= \"3.8.0\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"]
sdist = ["build (>=1.0.0)"]
ssh = ["bcrypt (>=3.1.5)"]
-test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
+test = ["certifi (>=2024)", "cryptography-vectors (==45.0.4)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"]
test-randomorder = ["pytest-randomly"]
[[package]]
name = "dill"
-version = "0.3.9"
+version = "0.4.0"
description = "serialize all of Python"
optional = false
python-versions = ">=3.8"
+groups = ["linters"]
files = [
- {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"},
- {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"},
+ {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"},
+ {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"},
]
[package.extras]
@@ -621,40 +651,28 @@ version = "0.21.2"
description = "Docutils -- Python Documentation Utilities"
optional = false
python-versions = ">=3.9"
+groups = ["docs"]
files = [
{file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"},
{file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"},
]
[[package]]
-name = "exceptiongroup"
-version = "1.2.2"
-description = "Backport of PEP 654 (exception groups)"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
- {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
-]
-
-[package.extras]
-test = ["pytest (>=6)"]
-
-[[package]]
name = "filelock"
-version = "3.17.0"
+version = "3.18.0"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"},
- {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"},
+ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"},
+ {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"},
]
[package.extras]
docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"]
-typing = ["typing-extensions (>=4.12.2)"]
+typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
[[package]]
name = "fitparse"
@@ -662,6 +680,7 @@ version = "1.2.0"
description = "Python library to parse ANT/Garmin .FIT files"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "fitparse-1.2.0.tar.gz", hash = "sha256:2d691022452dea6dabad13cc6e017ca467fe8a3a895cd3ac67a50a7bb716b4a9"},
]
@@ -672,6 +691,7 @@ version = "1.6.2"
description = "GPX file parser and GPS track manipulation library"
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "gpxpy-1.6.2-py3-none-any.whl", hash = "sha256:289bc2d80f116c988d0a1e763fda22838f83005573ece2bbc6521817b26fb40a"},
{file = "gpxpy-1.6.2.tar.gz", hash = "sha256:a72c484b97ec42b80834353b029cc8ee1b79f0ffca1179b2210bb3baf26c01ae"},
@@ -679,85 +699,68 @@ files = [
[[package]]
name = "greenlet"
-version = "3.1.1"
+version = "3.2.3"
description = "Lightweight in-process concurrent programming"
optional = false
-python-versions = ">=3.7"
-files = [
- {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"},
- {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"},
- {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"},
- {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"},
- {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"},
- {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"},
- {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"},
- {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"},
- {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"},
- {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"},
- {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"},
- {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"},
- {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"},
- {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"},
- {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"},
- {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"},
- {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"},
- {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"},
- {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"},
- {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"},
- {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"},
- {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"},
- {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"},
- {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"},
- {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"},
- {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"},
- {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"},
- {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"},
- {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"},
- {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"},
- {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"},
- {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"},
- {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"},
- {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"},
- {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"},
- {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"},
- {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"},
- {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"},
- {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"},
- {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"},
- {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"},
- {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"},
- {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"},
- {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"},
- {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"},
- {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"},
- {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"},
- {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"},
- {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"},
- {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"},
- {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"},
- {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"},
- {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"},
- {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"},
- {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"},
- {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"},
- {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"},
- {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"},
- {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"},
- {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"},
- {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"},
- {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"},
- {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"},
- {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"},
- {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"},
- {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"},
- {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"},
- {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"},
- {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"},
- {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"},
- {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"},
- {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"},
- {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"},
-]
+python-versions = ">=3.9"
+groups = ["main", "testing"]
+files = [
+ {file = "greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be"},
+ {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac"},
+ {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392"},
+ {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c"},
+ {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db"},
+ {file = "greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b"},
+ {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712"},
+ {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00"},
+ {file = "greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302"},
+ {file = "greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822"},
+ {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83"},
+ {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf"},
+ {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b"},
+ {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147"},
+ {file = "greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5"},
+ {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc"},
+ {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba"},
+ {file = "greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34"},
+ {file = "greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d"},
+ {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b"},
+ {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d"},
+ {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264"},
+ {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688"},
+ {file = "greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb"},
+ {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c"},
+ {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163"},
+ {file = "greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849"},
+ {file = "greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad"},
+ {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef"},
+ {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3"},
+ {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95"},
+ {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb"},
+ {file = "greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b"},
+ {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0"},
+ {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36"},
+ {file = "greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3"},
+ {file = "greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86"},
+ {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97"},
+ {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728"},
+ {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a"},
+ {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892"},
+ {file = "greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141"},
+ {file = "greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a"},
+ {file = "greenlet-3.2.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64"},
+ {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7"},
+ {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805"},
+ {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72"},
+ {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904"},
+ {file = "greenlet-3.2.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26"},
+ {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da"},
+ {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4"},
+ {file = "greenlet-3.2.3-cp39-cp39-win32.whl", hash = "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57"},
+ {file = "greenlet-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322"},
+ {file = "greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365"},
+]
+markers = {main = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
[package.extras]
docs = ["Sphinx", "furo"]
@@ -769,6 +772,7 @@ version = "1.12.1"
description = "Integrated process monitor for developing and reloading daemons."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "hupper-1.12.1-py3-none-any.whl", hash = "sha256:e872b959f09d90be5fb615bd2e62de89a0b57efc037bdf9637fb09cdf8552b19"},
{file = "hupper-1.12.1.tar.gz", hash = "sha256:06bf54170ff4ecf4c84ad5f188dee3901173ab449c2608ad05b9bfd6b13e32eb"},
@@ -784,6 +788,7 @@ version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.6"
+groups = ["main", "docs", "testing"]
files = [
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
@@ -798,6 +803,7 @@ version = "1.4.1"
description = "Getting image size from png/jpeg/jpeg2000/gif file"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+groups = ["docs"]
files = [
{file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
{file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
@@ -805,24 +811,26 @@ files = [
[[package]]
name = "iniconfig"
-version = "2.0.0"
+version = "2.1.0"
description = "brain-dead simple config-ini parsing"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
+groups = ["testing"]
files = [
- {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
- {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
+ {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
]
[[package]]
name = "isort"
-version = "6.0.0"
+version = "6.0.1"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.9.0"
+groups = ["linters"]
files = [
- {file = "isort-6.0.0-py3-none-any.whl", hash = "sha256:567954102bb47bb12e0fae62606570faacddd441e45683968c8d1734fb1af892"},
- {file = "isort-6.0.0.tar.gz", hash = "sha256:75d9d8a1438a9432a7d7b54f2d3b45cad9a4a0fdba43617d9873379704a8bdf1"},
+ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"},
+ {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"},
]
[package.extras]
@@ -831,13 +839,14 @@ plugins = ["setuptools"]
[[package]]
name = "jinja2"
-version = "3.1.5"
+version = "3.1.6"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
+groups = ["main", "docs"]
files = [
- {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
- {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
+ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
+ {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
]
[package.dependencies]
@@ -848,24 +857,27 @@ i18n = ["Babel (>=2.7)"]
[[package]]
name = "legacy-cgi"
-version = "2.6.2"
-description = "Fork of the standard library cgi and cgitb modules, being deprecated in PEP-594"
+version = "2.6.3"
+description = "Fork of the standard library cgi and cgitb modules removed in Python 3.13"
optional = false
-python-versions = ">=3.10"
+python-versions = ">=3.8"
+groups = ["main", "testing"]
+markers = "python_version >= \"3.13\""
files = [
- {file = "legacy_cgi-2.6.2-py3-none-any.whl", hash = "sha256:a7b83afb1baf6ebeb56522537c5943ef9813cf933f6715e88a803f7edbce0bff"},
- {file = "legacy_cgi-2.6.2.tar.gz", hash = "sha256:9952471ceb304043b104c22d00b4f333cac27a6abe446d8a528fc437cf13c85f"},
+ {file = "legacy_cgi-2.6.3-py3-none-any.whl", hash = "sha256:6df2ea5ae14c71ef6f097f8b6372b44f6685283dc018535a75c924564183cdab"},
+ {file = "legacy_cgi-2.6.3.tar.gz", hash = "sha256:4c119d6cb8e9d8b6ad7cc0ddad880552c62df4029622835d06dfd18f438a8154"},
]
[[package]]
name = "mako"
-version = "1.3.8"
+version = "1.3.10"
description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
- {file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627"},
- {file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8"},
+ {file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"},
+ {file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"},
]
[package.dependencies]
@@ -878,17 +890,18 @@ testing = ["pytest"]
[[package]]
name = "markdown"
-version = "3.7"
+version = "3.8"
description = "Python implementation of John Gruber's Markdown."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"},
- {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"},
+ {file = "markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc"},
+ {file = "markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f"},
]
[package.extras]
-docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
+docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
testing = ["coverage", "pyyaml"]
[[package]]
@@ -897,6 +910,7 @@ version = "3.0.2"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.9"
+groups = ["main", "docs"]
files = [
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
@@ -967,6 +981,7 @@ version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
+groups = ["linters"]
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
@@ -974,54 +989,49 @@ files = [
[[package]]
name = "mypy"
-version = "1.14.1"
+version = "1.16.1"
description = "Optional static typing for Python"
optional = false
-python-versions = ">=3.8"
-files = [
- {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"},
- {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"},
- {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"},
- {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"},
- {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"},
- {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"},
- {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"},
- {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"},
- {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"},
- {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"},
- {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"},
- {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"},
- {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"},
- {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"},
- {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"},
- {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"},
- {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"},
- {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"},
- {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"},
- {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"},
- {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"},
- {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"},
- {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"},
- {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"},
- {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"},
- {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"},
- {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"},
- {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"},
- {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"},
- {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"},
- {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"},
- {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"},
- {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"},
- {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"},
- {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"},
- {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"},
- {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"},
- {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"},
+python-versions = ">=3.9"
+groups = ["main", "types"]
+files = [
+ {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"},
+ {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"},
+ {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"},
+ {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"},
+ {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"},
+ {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"},
+ {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"},
+ {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"},
+ {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"},
+ {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"},
+ {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"},
+ {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"},
+ {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"},
+ {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"},
+ {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"},
+ {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"},
+ {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"},
+ {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"},
+ {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"},
+ {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"},
+ {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"},
+ {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"},
+ {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"},
+ {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"},
+ {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"},
+ {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"},
+ {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"},
+ {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"},
+ {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"},
+ {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"},
+ {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"},
+ {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"},
]
[package.dependencies]
mypy_extensions = ">=1.0.0"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+pathspec = ">=0.9.0"
typing_extensions = ">=4.6.0"
[package.extras]
@@ -1033,57 +1043,60 @@ reports = ["lxml"]
[[package]]
name = "mypy-extensions"
-version = "1.0.0"
+version = "1.1.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.8"
+groups = ["main", "linters", "types"]
files = [
- {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
- {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
+ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
]
[[package]]
name = "nh3"
-version = "0.2.20"
+version = "0.2.21"
description = "Python binding to Ammonia HTML sanitizer Rust crate"
optional = false
python-versions = ">=3.8"
-files = [
- {file = "nh3-0.2.20-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e1061a4ab6681f6bdf72b110eea0c4e1379d57c9de937db3be4202f7ad6043db"},
- {file = "nh3-0.2.20-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4254b1dac4a1ee49919a5b3f1caf9803ea8dada1816d9e8289e63d3cd0dd9a"},
- {file = "nh3-0.2.20-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ae9cbd713524cdb81e64663d0d6aae26f678db9f2cd9db0bf162606f1f9f20c"},
- {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1f7370b4e14cc03f5ae141ef30a1caf81fa5787711f80be9081418dd9eb79d2"},
- {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:ac4d27dc836a476efffc6eb661994426b8b805c951b29c9cf2ff36bc9ad58bc5"},
- {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4fd2e9248725ebcedac3997a8d3da0d90a12a28c9179c6ba51f1658938ac30d0"},
- {file = "nh3-0.2.20-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f7d564871833ddbe54df3aa59053b1110729d3a800cb7628ae8f42adb3d75208"},
- {file = "nh3-0.2.20-cp313-cp313t-win32.whl", hash = "sha256:d2a176fd4306b6f0f178a3f67fac91bd97a3a8d8fafb771c9b9ef675ba5c8886"},
- {file = "nh3-0.2.20-cp313-cp313t-win_amd64.whl", hash = "sha256:6ed834c68452a600f517dd3e1534dbfaff1f67f98899fecf139a055a25d99150"},
- {file = "nh3-0.2.20-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:76e2f603b30c02ff6456b233a83fc377dedab6a50947b04e960a6b905637b776"},
- {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:181063c581defe683bd4bb78188ac9936d208aebbc74c7f7c16b6a32ae2ebb38"},
- {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:231addb7643c952cd6d71f1c8702d703f8fe34afcb20becb3efb319a501a12d7"},
- {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1b9a8340a0aab991c68a5ca938d35ef4a8a3f4bf1b455da8855a40bee1fa0ace"},
- {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10317cd96fe4bbd4eb6b95f3920b71c902157ad44fed103fdcde43e3b8ee8be6"},
- {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8698db4c04b140800d1a1cd3067fda399e36e1e2b8fc1fe04292a907350a3e9b"},
- {file = "nh3-0.2.20-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eb04b9c3deb13c3a375ea39fd4a3c00d1f92e8fb2349f25f1e3e4506751774b"},
- {file = "nh3-0.2.20-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92f3f1c4f47a2c6f3ca7317b1d5ced05bd29556a75d3a4e2715652ae9d15c05d"},
- {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ddefa9fd6794a87e37d05827d299d4b53a3ec6f23258101907b96029bfef138a"},
- {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ce3731c8f217685d33d9268362e5b4f770914e922bba94d368ab244a59a6c397"},
- {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:09f037c02fc2c43b211ff1523de32801dcfb0918648d8e651c36ef890f1731ec"},
- {file = "nh3-0.2.20-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:813f1c8012dd64c990514b795508abb90789334f76a561fa0fd4ca32d2275330"},
- {file = "nh3-0.2.20-cp38-abi3-win32.whl", hash = "sha256:47b2946c0e13057855209daeffb45dc910bd0c55daf10190bb0b4b60e2999784"},
- {file = "nh3-0.2.20-cp38-abi3-win_amd64.whl", hash = "sha256:da87573f03084edae8eb87cfe811ec338606288f81d333c07d2a9a0b9b976c0b"},
- {file = "nh3-0.2.20.tar.gz", hash = "sha256:9705c42d7ff88a0bea546c82d7fe5e59135e3d3f057e485394f491248a1f8ed5"},
+groups = ["main"]
+files = [
+ {file = "nh3-0.2.21-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286"},
+ {file = "nh3-0.2.21-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde"},
+ {file = "nh3-0.2.21-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243"},
+ {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b"},
+ {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251"},
+ {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b"},
+ {file = "nh3-0.2.21-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9"},
+ {file = "nh3-0.2.21-cp313-cp313t-win32.whl", hash = "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d"},
+ {file = "nh3-0.2.21-cp313-cp313t-win_amd64.whl", hash = "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82"},
+ {file = "nh3-0.2.21-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967"},
+ {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759"},
+ {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab"},
+ {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42"},
+ {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f"},
+ {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578"},
+ {file = "nh3-0.2.21-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585"},
+ {file = "nh3-0.2.21-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293"},
+ {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431"},
+ {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa"},
+ {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1"},
+ {file = "nh3-0.2.21-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283"},
+ {file = "nh3-0.2.21-cp38-abi3-win32.whl", hash = "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a"},
+ {file = "nh3-0.2.21-cp38-abi3-win_amd64.whl", hash = "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629"},
+ {file = "nh3-0.2.21.tar.gz", hash = "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e"},
]
[[package]]
name = "packaging"
-version = "24.2"
+version = "25.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
+groups = ["main", "docs", "linters", "testing"]
files = [
- {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
- {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
+ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
+ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
]
[[package]]
@@ -1092,6 +1105,7 @@ version = "3.1.0"
description = "Load, configure, and compose WSGI applications and servers"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "PasteDeploy-3.1.0-py3-none-any.whl", hash = "sha256:76388ad53a661448d436df28c798063108f70e994ddc749540d733cdbd1b38cf"},
{file = "PasteDeploy-3.1.0.tar.gz", hash = "sha256:9ddbaf152f8095438a9fe81f82c78a6714b92ae8e066bed418b6a7ff6a095a95"},
@@ -1108,17 +1122,119 @@ version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
+groups = ["main", "linters", "types"]
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
+name = "pillow"
+version = "11.2.1"
+description = "Python Imaging Library (Fork)"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047"},
+ {file = "pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95"},
+ {file = "pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61"},
+ {file = "pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1"},
+ {file = "pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c"},
+ {file = "pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d"},
+ {file = "pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97"},
+ {file = "pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579"},
+ {file = "pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d"},
+ {file = "pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad"},
+ {file = "pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2"},
+ {file = "pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70"},
+ {file = "pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf"},
+ {file = "pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7"},
+ {file = "pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8"},
+ {file = "pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600"},
+ {file = "pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788"},
+ {file = "pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e"},
+ {file = "pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e"},
+ {file = "pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6"},
+ {file = "pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193"},
+ {file = "pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7"},
+ {file = "pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f"},
+ {file = "pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b"},
+ {file = "pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d"},
+ {file = "pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4"},
+ {file = "pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d"},
+ {file = "pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4"},
+ {file = "pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443"},
+ {file = "pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c"},
+ {file = "pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3"},
+ {file = "pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941"},
+ {file = "pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb"},
+ {file = "pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28"},
+ {file = "pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830"},
+ {file = "pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0"},
+ {file = "pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1"},
+ {file = "pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f"},
+ {file = "pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155"},
+ {file = "pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14"},
+ {file = "pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b"},
+ {file = "pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2"},
+ {file = "pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691"},
+ {file = "pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c"},
+ {file = "pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22"},
+ {file = "pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7"},
+ {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16"},
+ {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b"},
+ {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406"},
+ {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91"},
+ {file = "pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751"},
+ {file = "pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9"},
+ {file = "pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd"},
+ {file = "pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e"},
+ {file = "pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681"},
+ {file = "pillow-11.2.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:7491cf8a79b8eb867d419648fff2f83cb0b3891c8b36da92cc7f1931d46108c8"},
+ {file = "pillow-11.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b02d8f9cb83c52578a0b4beadba92e37d83a4ef11570a8688bbf43f4ca50909"},
+ {file = "pillow-11.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:014ca0050c85003620526b0ac1ac53f56fc93af128f7546623cc8e31875ab928"},
+ {file = "pillow-11.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3692b68c87096ac6308296d96354eddd25f98740c9d2ab54e1549d6c8aea9d79"},
+ {file = "pillow-11.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:f781dcb0bc9929adc77bad571b8621ecb1e4cdef86e940fe2e5b5ee24fd33b35"},
+ {file = "pillow-11.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2b490402c96f907a166615e9a5afacf2519e28295f157ec3a2bb9bd57de638cb"},
+ {file = "pillow-11.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6b20b93b3ccc9c1b597999209e4bc5cf2853f9ee66e3fc9a400a78733ffc9a"},
+ {file = "pillow-11.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b835d89c08a6c2ee7781b8dd0a30209a8012b5f09c0a665b65b0eb3560b6f36"},
+ {file = "pillow-11.2.1-cp39-cp39-win32.whl", hash = "sha256:b10428b3416d4f9c61f94b494681280be7686bda15898a3a9e08eb66a6d92d67"},
+ {file = "pillow-11.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:6ebce70c3f486acf7591a3d73431fa504a4e18a9b97ff27f5f47b7368e4b9dd1"},
+ {file = "pillow-11.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:c27476257b2fdcd7872d54cfd119b3a9ce4610fb85c8e32b70b42e3680a29a1e"},
+ {file = "pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156"},
+ {file = "pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772"},
+ {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363"},
+ {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0"},
+ {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01"},
+ {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193"},
+ {file = "pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013"},
+ {file = "pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed"},
+ {file = "pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c"},
+ {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd"},
+ {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076"},
+ {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b"},
+ {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f"},
+ {file = "pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044"},
+ {file = "pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6"},
+]
+
+[package.extras]
+docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
+fpx = ["olefile"]
+mic = ["olefile"]
+test-arrow = ["pyarrow"]
+tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"]
+typing = ["typing-extensions ; python_version < \"3.10\""]
+xmp = ["defusedxml"]
+
+[[package]]
name = "plaster"
version = "1.1.2"
description = "A loader interface around multiple config file formats."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "plaster-1.1.2-py2.py3-none-any.whl", hash = "sha256:42992ab1f4865f1278e2ad740e8ad145683bb4022e03534265528f0c23c0df2d"},
{file = "plaster-1.1.2.tar.gz", hash = "sha256:f8befc54bf8c1147c10ab40297ec84c2676fa2d4ea5d6f524d9436a80074ef98"},
@@ -1134,6 +1250,7 @@ version = "1.0.1"
description = "A loader implementing the PasteDeploy syntax to be used by plaster."
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "plaster_pastedeploy-1.0.1-py2.py3-none-any.whl", hash = "sha256:ad3550cc744648969ed3b810f33c9344f515ee8d8a8cec18e8f2c4a643c2181f"},
{file = "plaster_pastedeploy-1.0.1.tar.gz", hash = "sha256:be262e6d2e41a7264875daa2fe2850cbb0615728bcdc92828fdc72736e381412"},
@@ -1148,54 +1265,58 @@ testing = ["pytest", "pytest-cov"]
[[package]]
name = "platformdirs"
-version = "4.3.6"
+version = "4.3.8"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["linters"]
files = [
- {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
- {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
+ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"},
+ {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"},
]
[package.extras]
-docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
-type = ["mypy (>=1.11.2)"]
+docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"]
+type = ["mypy (>=1.14.1)"]
[[package]]
name = "playwright"
-version = "1.49.1"
+version = "1.52.0"
description = "A high-level API to automate web browsers"
optional = false
python-versions = ">=3.9"
+groups = ["testing"]
files = [
- {file = "playwright-1.49.1-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:1041ffb45a0d0bc44d698d3a5aa3ac4b67c9bd03540da43a0b70616ad52592b8"},
- {file = "playwright-1.49.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9f38ed3d0c1f4e0a6d1c92e73dd9a61f8855133249d6f0cec28648d38a7137be"},
- {file = "playwright-1.49.1-py3-none-macosx_11_0_universal2.whl", hash = "sha256:3be48c6d26dc819ca0a26567c1ae36a980a0303dcd4249feb6f59e115aaddfb8"},
- {file = "playwright-1.49.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:753ca90ee31b4b03d165cfd36e477309ebf2b4381953f2a982ff612d85b147d2"},
- {file = "playwright-1.49.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd9bc8dab37aa25198a01f555f0a2e2c3813fe200fef018ac34dfe86b34994b9"},
- {file = "playwright-1.49.1-py3-none-win32.whl", hash = "sha256:43b304be67f096058e587dac453ece550eff87b8fbed28de30f4f022cc1745bb"},
- {file = "playwright-1.49.1-py3-none-win_amd64.whl", hash = "sha256:47b23cb346283278f5b4d1e1990bcb6d6302f80c0aa0ca93dd0601a1400191df"},
+ {file = "playwright-1.52.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:19b2cb9d4794062008a635a99bd135b03ebb782d460f96534a91cb583f549512"},
+ {file = "playwright-1.52.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0797c0479cbdc99607412a3c486a3a2ec9ddc77ac461259fd2878c975bcbb94a"},
+ {file = "playwright-1.52.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:7223960b7dd7ddeec1ba378c302d1d09733b8dac438f492e9854c85d3ca7144f"},
+ {file = "playwright-1.52.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:d010124d24a321e0489a8c0d38a3971a7ca7656becea7656c9376bfea7f916d4"},
+ {file = "playwright-1.52.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4173e453c43180acc60fd77ffe1ebee8d0efbfd9986c03267007b9c3845415af"},
+ {file = "playwright-1.52.0-py3-none-win32.whl", hash = "sha256:cd0bdf92df99db6237a99f828e80a6a50db6180ef8d5352fc9495df2c92f9971"},
+ {file = "playwright-1.52.0-py3-none-win_amd64.whl", hash = "sha256:dcbf75101eba3066b7521c6519de58721ea44379eb17a0dafa94f9f1b17f59e4"},
+ {file = "playwright-1.52.0-py3-none-win_arm64.whl", hash = "sha256:9d0085b8de513de5fb50669f8e6677f0252ef95a9a1d2d23ccee9638e71e65cb"},
]
[package.dependencies]
-greenlet = "3.1.1"
-pyee = "12.0.0"
+greenlet = ">=3.1.1,<4.0.0"
+pyee = ">=13,<14"
[[package]]
name = "pluggy"
-version = "1.5.0"
+version = "1.6.0"
description = "plugin and hook calling mechanisms for python"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["testing"]
files = [
- {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
- {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
+ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
+ {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
]
[package.extras]
dev = ["pre-commit", "tox"]
-testing = ["pytest", "pytest-benchmark"]
+testing = ["coverage", "pytest", "pytest-benchmark"]
[[package]]
name = "pycparser"
@@ -1203,6 +1324,8 @@ version = "2.22"
description = "C parser in Python"
optional = false
python-versions = ">=3.8"
+groups = ["main", "types"]
+markers = "platform_python_implementation != \"PyPy\""
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
@@ -1210,131 +1333,133 @@ files = [
[[package]]
name = "pydantic"
-version = "2.10.6"
+version = "2.11.7"
description = "Data validation using Python type hints"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
- {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
+ {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"},
+ {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
-pydantic-core = "2.27.2"
+pydantic-core = "2.33.2"
typing-extensions = ">=4.12.2"
+typing-inspection = ">=0.4.0"
[package.extras]
email = ["email-validator (>=2.0.0)"]
-timezone = ["tzdata"]
+timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
[[package]]
name = "pydantic-core"
-version = "2.27.2"
+version = "2.33.2"
description = "Core functionality for Pydantic validation and serialization"
optional = false
-python-versions = ">=3.8"
-files = [
- {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
- {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"},
- {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"},
- {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"},
- {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"},
- {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"},
- {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"},
- {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"},
- {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"},
- {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"},
- {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"},
- {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"},
- {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"},
- {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"},
- {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"},
- {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"},
- {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"},
- {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"},
- {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"},
- {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"},
- {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"},
- {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"},
- {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"},
- {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"},
- {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"},
- {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"},
- {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"},
- {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"},
- {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"},
- {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"},
- {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"},
- {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"},
- {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"},
- {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"},
- {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"},
- {file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506"},
- {file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5"},
- {file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a"},
- {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d"},
- {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9"},
- {file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da"},
- {file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b"},
- {file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad"},
- {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"},
- {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"},
- {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"},
- {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"},
- {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"},
- {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"},
- {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"},
- {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"},
- {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"},
- {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"},
- {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"},
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"},
+ {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"},
+ {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"},
+ {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"},
+ {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"},
+ {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"},
+ {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"},
+ {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"},
+ {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"},
+ {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"},
+ {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"},
+ {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"},
+ {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"},
+ {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"},
+ {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"},
+ {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"},
+ {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"},
+ {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"},
+ {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"},
+ {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"},
+ {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"},
+ {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"},
+ {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"},
+ {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"},
+ {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"},
+ {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"},
+ {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"},
+ {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"},
+ {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"},
+ {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"},
+ {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"},
+ {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"},
+ {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"},
+ {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"},
+ {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"},
]
[package.dependencies]
@@ -1342,20 +1467,21 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pyee"
-version = "12.0.0"
+version = "13.0.0"
description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own"
optional = false
python-versions = ">=3.8"
+groups = ["testing"]
files = [
- {file = "pyee-12.0.0-py3-none-any.whl", hash = "sha256:7b14b74320600049ccc7d0e0b1becd3b4bd0a03c745758225e31a59f4095c990"},
- {file = "pyee-12.0.0.tar.gz", hash = "sha256:c480603f4aa2927d4766eb41fa82793fe60a82cbfdb8d688e0d08c55a534e145"},
+ {file = "pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498"},
+ {file = "pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37"},
]
[package.dependencies]
typing-extensions = "*"
[package.extras]
-dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "sphinx", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"]
+dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "mypy", "pytest", "pytest-asyncio ; python_version >= \"3.4\"", "pytest-trio ; python_version >= \"3.7\"", "sphinx", "toml", "tox", "trio", "trio ; python_version > \"3.6\"", "trio-typing ; python_version > \"3.6\"", "twine", "twisted", "validate-pyproject[all]"]
[[package]]
name = "pygments"
@@ -1363,6 +1489,7 @@ version = "2.19.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
+groups = ["main", "docs", "testing"]
files = [
{file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
{file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
@@ -1373,27 +1500,26 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pylint"
-version = "3.3.4"
+version = "3.3.7"
description = "python code static checker"
optional = false
python-versions = ">=3.9.0"
+groups = ["linters"]
files = [
- {file = "pylint-3.3.4-py3-none-any.whl", hash = "sha256:289e6a1eb27b453b08436478391a48cd53bb0efb824873f949e709350f3de018"},
- {file = "pylint-3.3.4.tar.gz", hash = "sha256:74ae7a38b177e69a9b525d0794bd8183820bfa7eb68cc1bee6e8ed22a42be4ce"},
+ {file = "pylint-3.3.7-py3-none-any.whl", hash = "sha256:43860aafefce92fca4cf6b61fe199cdc5ae54ea28f9bf4cd49de267b5195803d"},
+ {file = "pylint-3.3.7.tar.gz", hash = "sha256:2b11de8bde49f9c5059452e0c310c079c746a0a8eeaa789e5aa966ecc23e4559"},
]
[package.dependencies]
-astroid = ">=3.3.8,<=3.4.0-dev0"
+astroid = ">=3.3.8,<=3.4.0.dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
dill = [
- {version = ">=0.2", markers = "python_version < \"3.11\""},
{version = ">=0.3.7", markers = "python_version >= \"3.12\""},
- {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
+ {version = ">=0.3.6", markers = "python_version == \"3.11\""},
]
-isort = ">=4.2.5,<5.13.0 || >5.13.0,<7"
+isort = ">=4.2.5,<5.13 || >5.13,<7"
mccabe = ">=0.6,<0.8"
-platformdirs = ">=2.2.0"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
+platformdirs = ">=2.2"
tomlkit = ">=0.10.1"
[package.extras]
@@ -1406,6 +1532,7 @@ version = "2.0.2"
description = "The Pyramid Web Framework, a Pylons project"
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "pyramid-2.0.2-py3-none-any.whl", hash = "sha256:2e6585ac55c147f0a51bc00dadf72075b3bdd9a871b332ff9e5e04117ccd76fa"},
{file = "pyramid-2.0.2.tar.gz", hash = "sha256:372138a738e4216535cc76dcce6eddd5a1aaca95130f2354fb834264c06f18de"},
@@ -1432,6 +1559,7 @@ version = "4.12.1"
description = "A package which provides an interactive HTML debugger for Pyramid application development"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "pyramid_debugtoolbar-4.12.1-py3-none-any.whl", hash = "sha256:1d13a82444b3396d5a76d1e611d0de1b38da096f04440041e8e889536103864b"},
{file = "pyramid_debugtoolbar-4.12.1.tar.gz", hash = "sha256:71e888d349c85fcca12b3e6dc4c7ae8e3f02a1d5acc05154fd9ba8c7f661b43d"},
@@ -1452,6 +1580,7 @@ version = "2.10.1"
description = "Jinja2 template bindings for the Pyramid web framework"
optional = false
python-versions = ">=3.7.0"
+groups = ["main"]
files = [
{file = "pyramid_jinja2-2.10.1-py3-none-any.whl", hash = "sha256:425a52a0c1cc2a83e183d22bb15cdd999112aa4c7b1e0f4e21c49a6b523ad6e1"},
{file = "pyramid_jinja2-2.10.1.tar.gz", hash = "sha256:8c508cb35c135f95149ca236110f9c8875343575740d16c5cb73a50ef1c21677"},
@@ -1473,6 +1602,7 @@ version = "1.1.0"
description = "Mako template bindings for the Pyramid web framework"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "pyramid_mako-1.1.0-py2.py3-none-any.whl", hash = "sha256:76104592d292b6974cf7080aa52405c51f396a621535f01e274d7fe546e85a43"},
{file = "pyramid_mako-1.1.0.tar.gz", hash = "sha256:0066c863441f1c3ddea60cee1ccc50d00a91a317a8052ca44131da1a12a840e2"},
@@ -1492,6 +1622,7 @@ version = "2.1.1"
description = "An execution policy for Pyramid that supports retrying requests after certain failure exceptions."
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "pyramid_retry-2.1.1-py2.py3-none-any.whl", hash = "sha256:b5129a60eb9d7409234ea52839006426d2ae887b4a1f0530c75ec336cabf2476"},
{file = "pyramid_retry-2.1.1.tar.gz", hash = "sha256:baa8276ae68babad09e5f2f94efc4f7421f3b8fb526151df522052f8cd3ec0c9"},
@@ -1511,6 +1642,7 @@ version = "2.6"
description = "A package which allows Pyramid requests to join the active transaction"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
{file = "pyramid_tm-2.6-py3-none-any.whl", hash = "sha256:665a4ee1d6f41f0c7ffa5e54d9b01d70cd3e8e5bd76277529acbdd6b6bd6fe9e"},
{file = "pyramid_tm-2.6.tar.gz", hash = "sha256:8148d2191285280c9a0c23e6df1018b3514b4cef02115b872dd0350a4d78709c"},
@@ -1526,25 +1658,25 @@ testing = ["WebTest", "coverage (>=5.0)", "pytest", "pytest-cov"]
[[package]]
name = "pytest"
-version = "8.3.4"
+version = "8.4.1"
description = "pytest: simple powerful testing with Python"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["testing"]
files = [
- {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
- {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
+ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"},
+ {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"},
]
[package.dependencies]
-colorama = {version = "*", markers = "sys_platform == \"win32\""}
-exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
-iniconfig = "*"
-packaging = "*"
+colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+iniconfig = ">=1"
+packaging = ">=20"
pluggy = ">=1.5,<2"
-tomli = {version = ">=1", markers = "python_version < \"3.11\""}
+pygments = ">=2.7.2"
[package.extras]
-dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-base-url"
@@ -1552,6 +1684,7 @@ version = "2.1.0"
description = "pytest plugin for URL based testing"
optional = false
python-versions = ">=3.8"
+groups = ["testing"]
files = [
{file = "pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6"},
{file = "pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45"},
@@ -1566,31 +1699,34 @@ test = ["black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "pytest
[[package]]
name = "pytest-cov"
-version = "6.0.0"
+version = "6.2.1"
description = "Pytest plugin for measuring coverage."
optional = false
python-versions = ">=3.9"
+groups = ["testing"]
files = [
- {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"},
- {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"},
+ {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"},
+ {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"},
]
[package.dependencies]
coverage = {version = ">=7.5", extras = ["toml"]}
-pytest = ">=4.6"
+pluggy = ">=1.2"
+pytest = ">=6.2.5"
[package.extras]
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"]
[[package]]
name = "pytest-playwright"
-version = "0.6.2"
+version = "0.7.0"
description = "A pytest wrapper with fixtures for Playwright to automate web browsers"
optional = false
python-versions = ">=3.9"
+groups = ["testing"]
files = [
- {file = "pytest_playwright-0.6.2-py3-none-any.whl", hash = "sha256:0eff73bebe497b0158befed91e2f5fe94cfa17181f8b3acf575beed84e7e9043"},
- {file = "pytest_playwright-0.6.2.tar.gz", hash = "sha256:ff4054b19aa05df096ac6f74f0572591566aaf0f6d97f6cb9674db8a4d4ed06c"},
+ {file = "pytest_playwright-0.7.0-py3-none-any.whl", hash = "sha256:2516d0871fa606634bfe32afbcc0342d68da2dbff97fe3459849e9c428486da2"},
+ {file = "pytest_playwright-0.7.0.tar.gz", hash = "sha256:b3f2ea514bbead96d26376fac182f68dcd6571e7cb41680a89ff1673c05d60b6"},
]
[package.dependencies]
@@ -1605,6 +1741,7 @@ version = "8.0.4"
description = "A Python slugify application that also handles Unicode"
optional = false
python-versions = ">=3.7"
+groups = ["testing"]
files = [
{file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"},
{file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"},
@@ -1618,36 +1755,39 @@ unidecode = ["Unidecode (>=1.1.1)"]
[[package]]
name = "redis"
-version = "5.2.1"
+version = "6.2.0"
description = "Python client for Redis database and key-value store"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"},
- {file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"},
+ {file = "redis-6.2.0-py3-none-any.whl", hash = "sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e"},
+ {file = "redis-6.2.0.tar.gz", hash = "sha256:e821f129b75dde6cb99dd35e5c76e8c49512a5a0d8dfdc560b2fbd44b85ca977"},
]
[package.dependencies]
async-timeout = {version = ">=4.0.3", markers = "python_full_version < \"3.11.3\""}
[package.extras]
-hiredis = ["hiredis (>=3.0.0)"]
-ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"]
+hiredis = ["hiredis (>=3.2.0)"]
+jwt = ["pyjwt (>=2.9.0)"]
+ocsp = ["cryptography (>=36.0.1)", "pyopenssl (>=20.0.1)", "requests (>=2.31.0)"]
[[package]]
name = "requests"
-version = "2.32.3"
+version = "2.32.4"
description = "Python HTTP for Humans."
optional = false
python-versions = ">=3.8"
+groups = ["main", "docs", "testing"]
files = [
- {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
- {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
+ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
+ {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
]
[package.dependencies]
certifi = ">=2017.4.17"
-charset-normalizer = ">=2,<4"
+charset_normalizer = ">=2,<4"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3"
@@ -1656,56 +1796,76 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
+name = "roman-numerals-py"
+version = "3.1.0"
+description = "Manipulate well-formed Roman numerals"
+optional = false
+python-versions = ">=3.9"
+groups = ["docs"]
+files = [
+ {file = "roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c"},
+ {file = "roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d"},
+]
+
+[package.extras]
+lint = ["mypy (==1.15.0)", "pyright (==1.1.394)", "ruff (==0.9.7)"]
+test = ["pytest (>=8)"]
+
+[[package]]
name = "setuptools"
-version = "75.8.0"
+version = "80.9.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"},
- {file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"},
+ {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"},
+ {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"},
]
[package.extras]
-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"]
-core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""]
+core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"]
-test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
-type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"]
+test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
+type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"]
[[package]]
name = "snowballstemmer"
-version = "2.2.0"
-description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
+version = "3.0.1"
+description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms."
optional = false
-python-versions = "*"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*"
+groups = ["docs"]
files = [
- {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
- {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
+ {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"},
+ {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"},
]
[[package]]
name = "soupsieve"
-version = "2.6"
+version = "2.7"
description = "A modern CSS selector implementation for Beautiful Soup."
optional = false
python-versions = ">=3.8"
+groups = ["testing"]
files = [
- {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"},
- {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"},
+ {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"},
+ {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"},
]
[[package]]
name = "sphinx"
-version = "8.1.3"
+version = "8.2.3"
description = "Python documentation generator"
optional = false
-python-versions = ">=3.10"
+python-versions = ">=3.11"
+groups = ["docs"]
files = [
- {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"},
- {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"},
+ {file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"},
+ {file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"},
]
[package.dependencies]
@@ -1718,6 +1878,7 @@ Jinja2 = ">=3.1"
packaging = ">=23.0"
Pygments = ">=2.17"
requests = ">=2.30.0"
+roman-numerals-py = ">=1.0.0"
snowballstemmer = ">=2.2"
sphinxcontrib-applehelp = ">=1.0.7"
sphinxcontrib-devhelp = ">=1.0.6"
@@ -1725,30 +1886,30 @@ sphinxcontrib-htmlhelp = ">=2.0.6"
sphinxcontrib-jsmath = ">=1.0.1"
sphinxcontrib-qthelp = ">=1.0.6"
sphinxcontrib-serializinghtml = ">=1.1.9"
-tomli = {version = ">=2", markers = "python_version < \"3.11\""}
[package.extras]
docs = ["sphinxcontrib-websupport"]
-lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"]
-test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"]
+lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"]
+test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"]
[[package]]
name = "sphinx-autodoc-typehints"
-version = "3.0.1"
+version = "3.2.0"
description = "Type hints (PEP 484) support for the Sphinx autodoc extension"
optional = false
-python-versions = ">=3.10"
+python-versions = ">=3.11"
+groups = ["docs"]
files = [
- {file = "sphinx_autodoc_typehints-3.0.1-py3-none-any.whl", hash = "sha256:4b64b676a14b5b79cefb6628a6dc8070e320d4963e8ff640a2f3e9390ae9045a"},
- {file = "sphinx_autodoc_typehints-3.0.1.tar.gz", hash = "sha256:b9b40dd15dee54f6f810c924f863f9cf1c54f9f3265c495140ea01be7f44fa55"},
+ {file = "sphinx_autodoc_typehints-3.2.0-py3-none-any.whl", hash = "sha256:884b39be23b1d884dcc825d4680c9c6357a476936e3b381a67ae80091984eb49"},
+ {file = "sphinx_autodoc_typehints-3.2.0.tar.gz", hash = "sha256:107ac98bc8b4837202c88c0736d59d6da44076e65a0d7d7d543a78631f662a9b"},
]
[package.dependencies]
-sphinx = ">=8.1.3"
+sphinx = ">=8.2"
[package.extras]
docs = ["furo (>=2024.8.6)"]
-testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "defusedxml (>=0.7.1)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "sphobjinv (>=2.3.1.2)", "typing-extensions (>=4.12.2)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.6.12)", "defusedxml (>=0.7.1)", "diff-cover (>=9.2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "sphobjinv (>=2.3.1.2)", "typing-extensions (>=4.12.2)"]
[[package]]
name = "sphinxcontrib-applehelp"
@@ -1756,6 +1917,7 @@ version = "2.0.0"
description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
optional = false
python-versions = ">=3.9"
+groups = ["docs"]
files = [
{file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
{file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
@@ -1772,6 +1934,7 @@ version = "2.0.0"
description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
optional = false
python-versions = ">=3.9"
+groups = ["docs"]
files = [
{file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
{file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
@@ -1788,6 +1951,7 @@ version = "2.1.0"
description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
optional = false
python-versions = ">=3.9"
+groups = ["docs"]
files = [
{file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
{file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
@@ -1804,6 +1968,7 @@ version = "1.0.1"
description = "A sphinx extension which renders display math in HTML via JavaScript"
optional = false
python-versions = ">=3.5"
+groups = ["docs"]
files = [
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
@@ -1818,6 +1983,7 @@ version = "2.0.0"
description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
optional = false
python-versions = ">=3.9"
+groups = ["docs"]
files = [
{file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
{file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
@@ -1834,6 +2000,7 @@ version = "2.0.0"
description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
optional = false
python-versions = ">=3.9"
+groups = ["docs"]
files = [
{file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
{file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
@@ -1846,81 +2013,82 @@ test = ["pytest"]
[[package]]
name = "sqlalchemy"
-version = "2.0.37"
+version = "2.0.41"
description = "Database Abstraction Library"
optional = false
python-versions = ">=3.7"
-files = [
- {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da36c3b0e891808a7542c5c89f224520b9a16c7f5e4d6a1156955605e54aef0e"},
- {file = "SQLAlchemy-2.0.37-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e7402ff96e2b073a98ef6d6142796426d705addd27b9d26c3b32dbaa06d7d069"},
- {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6f5d254a22394847245f411a2956976401e84da4288aa70cbcd5190744062c1"},
- {file = "SQLAlchemy-2.0.37-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41296bbcaa55ef5fdd32389a35c710133b097f7b2609d8218c0eabded43a1d84"},
- {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bedee60385c1c0411378cbd4dc486362f5ee88deceea50002772912d798bb00f"},
- {file = "SQLAlchemy-2.0.37-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6c67415258f9f3c69867ec02fea1bf6508153709ecbd731a982442a590f2b7e4"},
- {file = "SQLAlchemy-2.0.37-cp310-cp310-win32.whl", hash = "sha256:650dcb70739957a492ad8acff65d099a9586b9b8920e3507ca61ec3ce650bb72"},
- {file = "SQLAlchemy-2.0.37-cp310-cp310-win_amd64.whl", hash = "sha256:93d1543cd8359040c02b6614421c8e10cd7a788c40047dbc507ed46c29ae5636"},
- {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:78361be6dc9073ed17ab380985d1e45e48a642313ab68ab6afa2457354ff692c"},
- {file = "SQLAlchemy-2.0.37-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b661b49d0cb0ab311a189b31e25576b7ac3e20783beb1e1817d72d9d02508bf5"},
- {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d57bafbab289e147d064ffbd5cca2d7b1394b63417c0636cea1f2e93d16eb9e8"},
- {file = "SQLAlchemy-2.0.37-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fa2c0913f02341d25fb858e4fb2031e6b0813494cca1ba07d417674128ce11b"},
- {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9df21b8d9e5c136ea6cde1c50d2b1c29a2b5ff2b1d610165c23ff250e0704087"},
- {file = "SQLAlchemy-2.0.37-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db18ff6b8c0f1917f8b20f8eca35c28bbccb9f83afa94743e03d40203ed83de9"},
- {file = "SQLAlchemy-2.0.37-cp311-cp311-win32.whl", hash = "sha256:46954173612617a99a64aee103bcd3f078901b9a8dcfc6ae80cbf34ba23df989"},
- {file = "SQLAlchemy-2.0.37-cp311-cp311-win_amd64.whl", hash = "sha256:7b7e772dc4bc507fdec4ee20182f15bd60d2a84f1e087a8accf5b5b7a0dcf2ba"},
- {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2952748ecd67ed3b56773c185e85fc084f6bdcdec10e5032a7c25a6bc7d682ef"},
- {file = "SQLAlchemy-2.0.37-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3151822aa1db0eb5afd65ccfafebe0ef5cda3a7701a279c8d0bf17781a793bb4"},
- {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaa8039b6d20137a4e02603aba37d12cd2dde7887500b8855356682fc33933f4"},
- {file = "SQLAlchemy-2.0.37-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdba1f73b64530c47b27118b7053b8447e6d6f3c8104e3ac59f3d40c33aa9fd"},
- {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1b2690456528a87234a75d1a1644cdb330a6926f455403c8e4f6cad6921f9098"},
- {file = "SQLAlchemy-2.0.37-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf5ae8a9dcf657fd72144a7fd01f243236ea39e7344e579a121c4205aedf07bb"},
- {file = "SQLAlchemy-2.0.37-cp312-cp312-win32.whl", hash = "sha256:ea308cec940905ba008291d93619d92edaf83232ec85fbd514dcb329f3192761"},
- {file = "SQLAlchemy-2.0.37-cp312-cp312-win_amd64.whl", hash = "sha256:635d8a21577341dfe4f7fa59ec394b346da12420b86624a69e466d446de16aff"},
- {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8c4096727193762e72ce9437e2a86a110cf081241919ce3fab8e89c02f6b6658"},
- {file = "SQLAlchemy-2.0.37-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e4fb5ac86d8fe8151966814f6720996430462e633d225497566b3996966b9bdb"},
- {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e56a139bfe136a22c438478a86f8204c1eb5eed36f4e15c4224e4b9db01cb3e4"},
- {file = "SQLAlchemy-2.0.37-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f95fc8e3f34b5f6b3effb49d10ac97c569ec8e32f985612d9b25dd12d0d2e94"},
- {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c505edd429abdfe3643fa3b2e83efb3445a34a9dc49d5f692dd087be966020e0"},
- {file = "SQLAlchemy-2.0.37-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:12b0f1ec623cccf058cf21cb544f0e74656618165b083d78145cafde156ea7b6"},
- {file = "SQLAlchemy-2.0.37-cp313-cp313-win32.whl", hash = "sha256:293f9ade06b2e68dd03cfb14d49202fac47b7bb94bffcff174568c951fbc7af2"},
- {file = "SQLAlchemy-2.0.37-cp313-cp313-win_amd64.whl", hash = "sha256:d70f53a0646cc418ca4853da57cf3ddddbccb8c98406791f24426f2dd77fd0e2"},
- {file = "SQLAlchemy-2.0.37-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:44f569d0b1eb82301b92b72085583277316e7367e038d97c3a1a899d9a05e342"},
- {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2eae3423e538c10d93ae3e87788c6a84658c3ed6db62e6a61bb9495b0ad16bb"},
- {file = "SQLAlchemy-2.0.37-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfff7be361048244c3aa0f60b5e63221c5e0f0e509f4e47b8910e22b57d10ae7"},
- {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:5bc3339db84c5fb9130ac0e2f20347ee77b5dd2596ba327ce0d399752f4fce39"},
- {file = "SQLAlchemy-2.0.37-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:84b9f23b0fa98a6a4b99d73989350a94e4a4ec476b9a7dfe9b79ba5939f5e80b"},
- {file = "SQLAlchemy-2.0.37-cp37-cp37m-win32.whl", hash = "sha256:51bc9cfef83e0ac84f86bf2b10eaccb27c5a3e66a1212bef676f5bee6ef33ebb"},
- {file = "SQLAlchemy-2.0.37-cp37-cp37m-win_amd64.whl", hash = "sha256:8e47f1af09444f87c67b4f1bb6231e12ba6d4d9f03050d7fc88df6d075231a49"},
- {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6b788f14c5bb91db7f468dcf76f8b64423660a05e57fe277d3f4fad7b9dcb7ce"},
- {file = "SQLAlchemy-2.0.37-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521ef85c04c33009166777c77e76c8a676e2d8528dc83a57836b63ca9c69dcd1"},
- {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75311559f5c9881a9808eadbeb20ed8d8ba3f7225bef3afed2000c2a9f4d49b9"},
- {file = "SQLAlchemy-2.0.37-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cce918ada64c956b62ca2c2af59b125767097ec1dca89650a6221e887521bfd7"},
- {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9d087663b7e1feabea8c578d6887d59bb00388158e8bff3a76be11aa3f748ca2"},
- {file = "SQLAlchemy-2.0.37-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cf95a60b36997dad99692314c4713f141b61c5b0b4cc5c3426faad570b31ca01"},
- {file = "SQLAlchemy-2.0.37-cp38-cp38-win32.whl", hash = "sha256:d75ead7dd4d255068ea0f21492ee67937bd7c90964c8f3c2bea83c7b7f81b95f"},
- {file = "SQLAlchemy-2.0.37-cp38-cp38-win_amd64.whl", hash = "sha256:74bbd1d0a9bacf34266a7907d43260c8d65d31d691bb2356f41b17c2dca5b1d0"},
- {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:648ec5acf95ad59255452ef759054f2176849662af4521db6cb245263ae4aa33"},
- {file = "SQLAlchemy-2.0.37-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:35bd2df269de082065d4b23ae08502a47255832cc3f17619a5cea92ce478b02b"},
- {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f581d365af9373a738c49e0c51e8b18e08d8a6b1b15cc556773bcd8a192fa8b"},
- {file = "SQLAlchemy-2.0.37-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82df02816c14f8dc9f4d74aea4cb84a92f4b0620235daa76dde002409a3fbb5a"},
- {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94b564e38b344d3e67d2e224f0aec6ba09a77e4582ced41e7bfd0f757d926ec9"},
- {file = "SQLAlchemy-2.0.37-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:955a2a765aa1bd81aafa69ffda179d4fe3e2a3ad462a736ae5b6f387f78bfeb8"},
- {file = "SQLAlchemy-2.0.37-cp39-cp39-win32.whl", hash = "sha256:03f0528c53ca0b67094c4764523c1451ea15959bbf0a8a8a3096900014db0278"},
- {file = "SQLAlchemy-2.0.37-cp39-cp39-win_amd64.whl", hash = "sha256:4b12885dc85a2ab2b7d00995bac6d967bffa8594123b02ed21e8eb2205a7584b"},
- {file = "SQLAlchemy-2.0.37-py3-none-any.whl", hash = "sha256:a8998bf9f8658bd3839cbc44ddbe982955641863da0c1efe5b00c1ab4f5c16b1"},
- {file = "sqlalchemy-2.0.37.tar.gz", hash = "sha256:12b28d99a9c14eaf4055810df1001557176716de0167b91026e648e65229bffb"},
+groups = ["main"]
+files = [
+ {file = "SQLAlchemy-2.0.41-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6854175807af57bdb6425e47adbce7d20a4d79bbfd6f6d6519cd10bb7109a7f8"},
+ {file = "SQLAlchemy-2.0.41-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05132c906066142103b83d9c250b60508af556982a385d96c4eaa9fb9720ac2b"},
+ {file = "SQLAlchemy-2.0.41-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b4af17bda11e907c51d10686eda89049f9ce5669b08fbe71a29747f1e876036"},
+ {file = "SQLAlchemy-2.0.41-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c0b0e5e1b5d9f3586601048dd68f392dc0cc99a59bb5faf18aab057ce00d00b2"},
+ {file = "SQLAlchemy-2.0.41-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0b3dbf1e7e9bc95f4bac5e2fb6d3fb2f083254c3fdd20a1789af965caf2d2348"},
+ {file = "SQLAlchemy-2.0.41-cp37-cp37m-win32.whl", hash = "sha256:1e3f196a0c59b0cae9a0cd332eb1a4bda4696e863f4f1cf84ab0347992c548c2"},
+ {file = "SQLAlchemy-2.0.41-cp37-cp37m-win_amd64.whl", hash = "sha256:6ab60a5089a8f02009f127806f777fca82581c49e127f08413a66056bd9166dd"},
+ {file = "sqlalchemy-2.0.41-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1f09b6821406ea1f94053f346f28f8215e293344209129a9c0fcc3578598d7b"},
+ {file = "sqlalchemy-2.0.41-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1936af879e3db023601196a1684d28e12f19ccf93af01bf3280a3262c4b6b4e5"},
+ {file = "sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2ac41acfc8d965fb0c464eb8f44995770239668956dc4cdf502d1b1ffe0d747"},
+ {file = "sqlalchemy-2.0.41-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81c24e0c0fde47a9723c81d5806569cddef103aebbf79dbc9fcbb617153dea30"},
+ {file = "sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:23a8825495d8b195c4aa9ff1c430c28f2c821e8c5e2d98089228af887e5d7e29"},
+ {file = "sqlalchemy-2.0.41-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:60c578c45c949f909a4026b7807044e7e564adf793537fc762b2489d522f3d11"},
+ {file = "sqlalchemy-2.0.41-cp310-cp310-win32.whl", hash = "sha256:118c16cd3f1b00c76d69343e38602006c9cfb9998fa4f798606d28d63f23beda"},
+ {file = "sqlalchemy-2.0.41-cp310-cp310-win_amd64.whl", hash = "sha256:7492967c3386df69f80cf67efd665c0f667cee67032090fe01d7d74b0e19bb08"},
+ {file = "sqlalchemy-2.0.41-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6375cd674fe82d7aa9816d1cb96ec592bac1726c11e0cafbf40eeee9a4516b5f"},
+ {file = "sqlalchemy-2.0.41-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f8c9fdd15a55d9465e590a402f42082705d66b05afc3ffd2d2eb3c6ba919560"},
+ {file = "sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f9dc8c44acdee06c8fc6440db9eae8b4af8b01e4b1aee7bdd7241c22edff4f"},
+ {file = "sqlalchemy-2.0.41-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c11ceb9a1f482c752a71f203a81858625d8df5746d787a4786bca4ffdf71c6"},
+ {file = "sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:911cc493ebd60de5f285bcae0491a60b4f2a9f0f5c270edd1c4dbaef7a38fc04"},
+ {file = "sqlalchemy-2.0.41-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03968a349db483936c249f4d9cd14ff2c296adfa1290b660ba6516f973139582"},
+ {file = "sqlalchemy-2.0.41-cp311-cp311-win32.whl", hash = "sha256:293cd444d82b18da48c9f71cd7005844dbbd06ca19be1ccf6779154439eec0b8"},
+ {file = "sqlalchemy-2.0.41-cp311-cp311-win_amd64.whl", hash = "sha256:3d3549fc3e40667ec7199033a4e40a2f669898a00a7b18a931d3efb4c7900504"},
+ {file = "sqlalchemy-2.0.41-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:81f413674d85cfd0dfcd6512e10e0f33c19c21860342a4890c3a2b59479929f9"},
+ {file = "sqlalchemy-2.0.41-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:598d9ebc1e796431bbd068e41e4de4dc34312b7aa3292571bb3674a0cb415dd1"},
+ {file = "sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a104c5694dfd2d864a6f91b0956eb5d5883234119cb40010115fd45a16da5e70"},
+ {file = "sqlalchemy-2.0.41-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6145afea51ff0af7f2564a05fa95eb46f542919e6523729663a5d285ecb3cf5e"},
+ {file = "sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b46fa6eae1cd1c20e6e6f44e19984d438b6b2d8616d21d783d150df714f44078"},
+ {file = "sqlalchemy-2.0.41-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41836fe661cc98abfae476e14ba1906220f92c4e528771a8a3ae6a151242d2ae"},
+ {file = "sqlalchemy-2.0.41-cp312-cp312-win32.whl", hash = "sha256:a8808d5cf866c781150d36a3c8eb3adccfa41a8105d031bf27e92c251e3969d6"},
+ {file = "sqlalchemy-2.0.41-cp312-cp312-win_amd64.whl", hash = "sha256:5b14e97886199c1f52c14629c11d90c11fbb09e9334fa7bb5f6d068d9ced0ce0"},
+ {file = "sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443"},
+ {file = "sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc"},
+ {file = "sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1"},
+ {file = "sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a"},
+ {file = "sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d"},
+ {file = "sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23"},
+ {file = "sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f"},
+ {file = "sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df"},
+ {file = "sqlalchemy-2.0.41-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90144d3b0c8b139408da50196c5cad2a6909b51b23df1f0538411cd23ffa45d3"},
+ {file = "sqlalchemy-2.0.41-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:023b3ee6169969beea3bb72312e44d8b7c27c75b347942d943cf49397b7edeb5"},
+ {file = "sqlalchemy-2.0.41-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725875a63abf7c399d4548e686debb65cdc2549e1825437096a0af1f7e374814"},
+ {file = "sqlalchemy-2.0.41-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81965cc20848ab06583506ef54e37cf15c83c7e619df2ad16807c03100745dea"},
+ {file = "sqlalchemy-2.0.41-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dd5ec3aa6ae6e4d5b5de9357d2133c07be1aff6405b136dad753a16afb6717dd"},
+ {file = "sqlalchemy-2.0.41-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ff8e80c4c4932c10493ff97028decfdb622de69cae87e0f127a7ebe32b4069c6"},
+ {file = "sqlalchemy-2.0.41-cp38-cp38-win32.whl", hash = "sha256:4d44522480e0bf34c3d63167b8cfa7289c1c54264c2950cc5fc26e7850967e45"},
+ {file = "sqlalchemy-2.0.41-cp38-cp38-win_amd64.whl", hash = "sha256:81eedafa609917040d39aa9332e25881a8e7a0862495fcdf2023a9667209deda"},
+ {file = "sqlalchemy-2.0.41-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9a420a91913092d1e20c86a2f5f1fc85c1a8924dbcaf5e0586df8aceb09c9cc2"},
+ {file = "sqlalchemy-2.0.41-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:906e6b0d7d452e9a98e5ab8507c0da791856b2380fdee61b765632bb8698026f"},
+ {file = "sqlalchemy-2.0.41-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a373a400f3e9bac95ba2a06372c4fd1412a7cee53c37fc6c05f829bf672b8769"},
+ {file = "sqlalchemy-2.0.41-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:087b6b52de812741c27231b5a3586384d60c353fbd0e2f81405a814b5591dc8b"},
+ {file = "sqlalchemy-2.0.41-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:34ea30ab3ec98355235972dadc497bb659cc75f8292b760394824fab9cf39826"},
+ {file = "sqlalchemy-2.0.41-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8280856dd7c6a68ab3a164b4a4b1c51f7691f6d04af4d4ca23d6ecf2261b7923"},
+ {file = "sqlalchemy-2.0.41-cp39-cp39-win32.whl", hash = "sha256:b50eab9994d64f4a823ff99a0ed28a6903224ddbe7fef56a6dd865eec9243440"},
+ {file = "sqlalchemy-2.0.41-cp39-cp39-win_amd64.whl", hash = "sha256:5e22575d169529ac3e0a120cf050ec9daa94b6a9597993d1702884f6954a7d71"},
+ {file = "sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576"},
+ {file = "sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9"},
]
[package.dependencies]
-greenlet = {version = "!=0.4.17", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
+greenlet = {version = ">=1", markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"}
mypy = {version = ">=0.910", optional = true, markers = "extra == \"mypy\""}
typing-extensions = ">=4.6.0"
[package.extras]
-aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
-aioodbc = ["aioodbc", "greenlet (!=0.4.17)"]
-aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"]
-asyncio = ["greenlet (!=0.4.17)"]
-asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"]
+aiomysql = ["aiomysql (>=0.2.0)", "greenlet (>=1)"]
+aioodbc = ["aioodbc", "greenlet (>=1)"]
+aiosqlite = ["aiosqlite", "greenlet (>=1)", "typing_extensions (!=3.10.0.1)"]
+asyncio = ["greenlet (>=1)"]
+asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (>=1)"]
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"]
mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
@@ -1931,7 +2099,7 @@ mysql-connector = ["mysql-connector-python"]
oracle = ["cx_oracle (>=8)"]
oracle-oracledb = ["oracledb (>=1.0.1)"]
postgresql = ["psycopg2 (>=2.7)"]
-postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
+postgresql-asyncpg = ["asyncpg", "greenlet (>=1)"]
postgresql-pg8000 = ["pg8000 (>=1.29.1)"]
postgresql-psycopg = ["psycopg (>=3.0.7)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
@@ -1942,13 +2110,14 @@ sqlcipher = ["sqlcipher3_binary"]
[[package]]
name = "termcolor"
-version = "2.5.0"
+version = "3.1.0"
description = "ANSI color formatting for output in terminal"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
- {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"},
- {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"},
+ {file = "termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa"},
+ {file = "termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970"},
]
[package.extras]
@@ -1960,61 +2129,22 @@ version = "1.3"
description = "The most basic Text::Unidecode port"
optional = false
python-versions = "*"
+groups = ["testing"]
files = [
{file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"},
{file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"},
]
[[package]]
-name = "tomli"
-version = "2.2.1"
-description = "A lil' TOML parser"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
- {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
- {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
- {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
- {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
- {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
- {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
- {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
- {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
- {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
- {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
- {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
- {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
- {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
-]
-
-[[package]]
name = "tomlkit"
-version = "0.13.2"
+version = "0.13.3"
description = "Style preserving TOML library"
optional = false
python-versions = ">=3.8"
+groups = ["linters"]
files = [
- {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"},
- {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"},
+ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"},
+ {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"},
]
[[package]]
@@ -2023,6 +2153,7 @@ version = "5.0"
description = "Transaction management for Python"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "transaction-5.0-py3-none-any.whl", hash = "sha256:b4c0b2d49a042d86235fa76531c3356b66d7635bb0e9f29ba2512915fc7b7a42"},
{file = "transaction-5.0.tar.gz", hash = "sha256:106e7bd782bcc0cb5119fc9225b0c9a71dfc53adb938be905223adaef22b1174"},
@@ -2041,6 +2172,7 @@ version = "1.4"
description = "Utility library for i18n relied on by various Repoze and Pyramid packages"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "translationstring-1.4-py2.py3-none-any.whl", hash = "sha256:5f4dc4d939573db851c8d840551e1a0fb27b946afe3b95aafc22577eed2d6262"},
{file = "translationstring-1.4.tar.gz", hash = "sha256:bf947538d76e69ba12ab17283b10355a9ecfbc078e6123443f43f2107f6376f3"},
@@ -2055,6 +2187,7 @@ version = "2.11.0.15"
description = "Typing stubs for babel"
optional = false
python-versions = "*"
+groups = ["types"]
files = [
{file = "types-babel-2.11.0.15.tar.gz", hash = "sha256:282c184c8c9d81e8269212c1b8fa0d39ee88fb8bc43be47980412781c9c85f7e"},
{file = "types_babel-2.11.0.15-py3-none-any.whl", hash = "sha256:d0579f2e8adeaef3fbe2eb63e5a2ecf01767fc018e5f3f36a3c9d8b723bd62c7"},
@@ -2066,13 +2199,14 @@ types-setuptools = "*"
[[package]]
name = "types-cffi"
-version = "1.16.0.20241221"
+version = "1.17.0.20250523"
description = "Typing stubs for cffi"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["types"]
files = [
- {file = "types_cffi-1.16.0.20241221-py3-none-any.whl", hash = "sha256:e5b76b4211d7a9185f6ab8d06a106d56c7eb80af7cdb8bfcb4186ade10fb112f"},
- {file = "types_cffi-1.16.0.20241221.tar.gz", hash = "sha256:1c96649618f4b6145f58231acb976e0b448be6b847f7ab733dabe62dfbff6591"},
+ {file = "types_cffi-1.17.0.20250523-py3-none-any.whl", hash = "sha256:e98c549d8e191f6220e440f9f14315d6775a21a0e588c32c20476be885b2fad9"},
+ {file = "types_cffi-1.17.0.20250523.tar.gz", hash = "sha256:e7110f314c65590533adae1b30763be08ca71ad856a1ae3fe9b9d8664d49ec22"},
]
[package.dependencies]
@@ -2080,13 +2214,14 @@ types-setuptools = "*"
[[package]]
name = "types-markdown"
-version = "3.7.0.20241204"
+version = "3.8.0.20250415"
description = "Typing stubs for Markdown"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["types"]
files = [
- {file = "types_Markdown-3.7.0.20241204-py3-none-any.whl", hash = "sha256:f96146c367ea9c82bfe9903559d72706555cc2a1a3474c58ebba03b418ab18da"},
- {file = "types_markdown-3.7.0.20241204.tar.gz", hash = "sha256:ecca2b25cd23163fd28ed5ba34d183d731da03e8a5ed3a20b60daded304c5410"},
+ {file = "types_markdown-3.8.0.20250415-py3-none-any.whl", hash = "sha256:b41abed474a303ba300e3a4cf6f27eda339219124a59d529a158203570007776"},
+ {file = "types_markdown-3.8.0.20250415.tar.gz", hash = "sha256:98ab13587d1177769d93e55586d3dc97047df75bc6e37ce4074666f5dd4212ba"},
]
[[package]]
@@ -2095,6 +2230,7 @@ version = "24.1.0.20240722"
description = "Typing stubs for pyOpenSSL"
optional = false
python-versions = ">=3.8"
+groups = ["types"]
files = [
{file = "types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39"},
{file = "types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54"},
@@ -2106,13 +2242,14 @@ types-cffi = "*"
[[package]]
name = "types-pytz"
-version = "2024.2.0.20241221"
+version = "2025.2.0.20250516"
description = "Typing stubs for pytz"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["types"]
files = [
- {file = "types_pytz-2024.2.0.20241221-py3-none-any.whl", hash = "sha256:8fc03195329c43637ed4f593663df721fef919b60a969066e22606edf0b53ad5"},
- {file = "types_pytz-2024.2.0.20241221.tar.gz", hash = "sha256:06d7cde9613e9f7504766a0554a270c369434b50e00975b3a4a0f6eed0f2c1a9"},
+ {file = "types_pytz-2025.2.0.20250516-py3-none-any.whl", hash = "sha256:e0e0c8a57e2791c19f718ed99ab2ba623856b11620cb6b637e5f62ce285a7451"},
+ {file = "types_pytz-2025.2.0.20250516.tar.gz", hash = "sha256:e1216306f8c0d5da6dafd6492e72eb080c9a166171fa80dd7a1990fd8be7a7b3"},
]
[[package]]
@@ -2121,6 +2258,7 @@ version = "4.6.0.20241004"
description = "Typing stubs for redis"
optional = false
python-versions = ">=3.8"
+groups = ["types"]
files = [
{file = "types-redis-4.6.0.20241004.tar.gz", hash = "sha256:5f17d2b3f9091ab75384153bfa276619ffa1cf6a38da60e10d5e6749cc5b902e"},
{file = "types_redis-4.6.0.20241004-py3-none-any.whl", hash = "sha256:ef5da68cb827e5f606c8f9c0b49eeee4c2669d6d97122f301d3a55dc6a63f6ed"},
@@ -2132,13 +2270,14 @@ types-pyOpenSSL = "*"
[[package]]
name = "types-requests"
-version = "2.32.0.20241016"
+version = "2.32.4.20250611"
description = "Typing stubs for requests"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["types"]
files = [
- {file = "types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95"},
- {file = "types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747"},
+ {file = "types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072"},
+ {file = "types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826"},
]
[package.dependencies]
@@ -2146,39 +2285,57 @@ urllib3 = ">=2"
[[package]]
name = "types-setuptools"
-version = "75.8.0.20250110"
+version = "80.9.0.20250529"
description = "Typing stubs for setuptools"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["types"]
files = [
- {file = "types_setuptools-75.8.0.20250110-py3-none-any.whl", hash = "sha256:a9f12980bbf9bcdc23ecd80755789085bad6bfce4060c2275bc2b4ca9f2bc480"},
- {file = "types_setuptools-75.8.0.20250110.tar.gz", hash = "sha256:96f7ec8bbd6e0a54ea180d66ad68ad7a1d7954e7281a710ea2de75e355545271"},
+ {file = "types_setuptools-80.9.0.20250529-py3-none-any.whl", hash = "sha256:00dfcedd73e333a430e10db096e4d46af93faf9314f832f13b6bbe3d6757e95f"},
+ {file = "types_setuptools-80.9.0.20250529.tar.gz", hash = "sha256:79e088ba0cba2186c8d6499cbd3e143abb142d28a44b042c28d3148b1e353c91"},
]
[[package]]
name = "typing-extensions"
-version = "4.12.2"
-description = "Backported and Experimental Type Hints for Python 3.8+"
+version = "4.14.0"
+description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
+groups = ["main", "testing", "types"]
files = [
- {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
- {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
+ {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"},
+ {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"},
]
[[package]]
+name = "typing-inspection"
+version = "0.4.1"
+description = "Runtime typing introspection tools"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"},
+ {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.12.0"
+
+[[package]]
name = "urllib3"
-version = "2.3.0"
+version = "2.4.0"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
+groups = ["main", "docs", "testing", "types"]
files = [
- {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
- {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
+ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"},
+ {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"},
]
[package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
@@ -2189,6 +2346,7 @@ version = "3.1.1"
description = "A library for deferring decorator actions"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "venusian-3.1.1-py3-none-any.whl", hash = "sha256:0845808a985976acbceaa1fbb871c7fac4fb28ae75453232970e9c2c2866dbf4"},
{file = "venusian-3.1.1.tar.gz", hash = "sha256:534fb3b355669283eb3954581931e5d1d071fce61d029d58f3219a5e3a6f0c41"},
@@ -2204,6 +2362,7 @@ version = "3.0.2"
description = "Waitress WSGI server"
optional = false
python-versions = ">=3.9.0"
+groups = ["main", "testing"]
files = [
{file = "waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e"},
{file = "waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f"},
@@ -2219,6 +2378,7 @@ version = "1.8.9"
description = "WSGI request and response object"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main", "testing"]
files = [
{file = "WebOb-1.8.9-py2.py3-none-any.whl", hash = "sha256:45e34c58ed0c7e2ecd238ffd34432487ff13d9ad459ddfd77895e67abba7c1f9"},
{file = "webob-1.8.9.tar.gz", hash = "sha256:ad6078e2edb6766d1334ec3dee072ac6a7f95b1e32ce10def8ff7f0f02d56589"},
@@ -2233,13 +2393,14 @@ testing = ["coverage", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist"]
[[package]]
name = "webtest"
-version = "3.0.3"
+version = "3.0.6"
description = "Helper to test WSGI applications"
optional = false
python-versions = ">=3.9"
+groups = ["testing"]
files = [
- {file = "WebTest-3.0.3-py3-none-any.whl", hash = "sha256:25a2f3b1ad273655ed22fdb4f9eff62d02f22d9d5ffeb0b58627759b1f62edff"},
- {file = "webtest-3.0.3.tar.gz", hash = "sha256:b635f6fe6584bc9737496b687155e9373f3d01bcb1b46169007da0f7ba6238f9"},
+ {file = "webtest-3.0.6-py3-none-any.whl", hash = "sha256:0c4a5a3dcf745a78c7905b803d6a520a2cf241c1f00ccdf4351f52916d555543"},
+ {file = "webtest-3.0.6.tar.gz", hash = "sha256:4256fd5242448f56c575bcb9afe275e305a6f0723c4b01438dbdd4dd5344944b"},
]
[package.dependencies]
@@ -2257,6 +2418,7 @@ version = "5.1"
description = "Zope Deprecation Infrastructure"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
{file = "zope.deprecation-5.1-py3-none-any.whl", hash = "sha256:60f957b964d8f947a4a592c647d51ce0f4f844d1f041657956ddde0d9fa9a76a"},
{file = "zope_deprecation-5.1.tar.gz", hash = "sha256:46bed4611fb53edc731aadeb64b28308bcb848f4cc150c60c948d078f7108721"},
@@ -2275,6 +2437,7 @@ version = "7.2"
description = "Interfaces for Python"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "zope.interface-7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce290e62229964715f1011c3dbeab7a4a1e4971fd6f31324c4519464473ef9f2"},
{file = "zope.interface-7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05b910a5afe03256b58ab2ba6288960a2892dfeef01336dc4be6f1b9ed02ab0a"},
@@ -2329,6 +2492,7 @@ version = "3.1"
description = "Minimal Zope/SQLAlchemy transaction integration"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
files = [
{file = "zope.sqlalchemy-3.1-py3-none-any.whl", hash = "sha256:fdc7d65d8da335a34b90fb993e8217ef12808bad3025d2e3a6720db4138e4985"},
{file = "zope.sqlalchemy-3.1.tar.gz", hash = "sha256:d9c2c3be695c213c5e22b7f7c6a4a214fa8eb5940b033465ba1c10a9d8b346db"},
@@ -2345,6 +2509,6 @@ transaction = ">=1.6.0"
test = ["zope.testing"]
[metadata]
-lock-version = "2.0"
-python-versions = "^3.10"
-content-hash = "5862b6f9b19b0b451a9c0a01e37ed6a68bd241d43f16a7d2e40d3d25104880f5"
+lock-version = "2.1"
+python-versions = "^3.11"
+content-hash = "2a783d3d70463e01e91f4c20034c392e5a95a08e74632e41ede93d650f3c86e7"
diff --git a/pyproject.toml b/pyproject.toml
index 3338057..26d89bb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "fietsboek"
description = "GPX file sharing website"
-version = "0.10.0"
+version = "0.11.0"
license = "AGPL-3.0-or-later"
readme = "README.md"
authors = [
@@ -25,7 +25,7 @@ repository = "https://gitlab.com/dunj3/fietsboek"
keywords = ["web", "gpx"]
[tool.poetry.dependencies]
-python = "^3.10"
+python = "^3.11"
pyramid = "^2"
pyramid_jinja2 = "^2.10"
@@ -38,10 +38,10 @@ SQLAlchemy = { version = "^2.0.15", extras = ["mypy"] }
alembic = "^1.8"
transaction = "^5"
"zope.sqlalchemy" = "^3.0"
-redis = "^5"
+redis = "^6"
Babel = "^2.11"
-cryptography = "^44"
+cryptography = "^45"
gpxpy = "^1.5"
markdown = "^3.4"
nh3 = "^0.2.9"
@@ -49,11 +49,12 @@ Click = "^8.1"
requests = "^2.28.1"
pydantic = "^2"
-termcolor = "^2.1.1"
+termcolor = "^3.1.0"
filelock = "^3.8.2"
brotli = "^1.0.9"
click-option-group = "^0.5.5"
fitparse = "^1.2.0"
+pillow = "^11.2.1"
[tool.poetry.group.docs]
optional = true
@@ -69,7 +70,7 @@ optional = true
pytest = "^8.0.0"
webtest = "^3.0.0"
pytest-cov = "^6.0.0"
-pytest-playwright = "^0.6.0"
+pytest-playwright = "^0.7.0"
[tool.poetry.group.linters]
optional = true
diff --git a/release-checklist.md b/release-checklist.md
index 3b9632f..67e4ca4 100644
--- a/release-checklist.md
+++ b/release-checklist.md
@@ -7,7 +7,7 @@
- [ ] Commit those changes (`git add ... && git commit -m 'bump version to X.Y.Z'`)
- [ ] Make sure the directory is clean (no uncommited changes)
- [ ] Make sure the tests & lints pass
- - [ ] Make sure they also do so on **Python 3.10** (current minimum version)
+ - [ ] Make sure they also do so on **Python 3.11** (current minimum version)
- [ ] Create a new git tag: `git tag -a vX.Y.Z`
- [ ] Push the tag to the remote repositories
- [ ] Publish to PyPI: `poetry publish`
diff --git a/testing.ini b/testing.ini
index 82fddfd..ed53bdc 100644
--- a/testing.ini
+++ b/testing.ini
@@ -12,6 +12,8 @@ pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
+# The sqlalchemy.url is overwritten by the test setup script for different
+# database engines. We leave sqlite here as default so a local tox run works fine.
sqlalchemy.url = sqlite:///%(here)s/testing.sqlite
# The pytest tests usually overwrite this with a temporary directory. Since
# this is cleaned on test teardown, we don't want to accidentally delete data
diff --git a/tests/bootstrap/test_new_instance.py b/tests/bootstrap/test_new_instance.py
index dc3076e..05076f4 100644
--- a/tests/bootstrap/test_new_instance.py
+++ b/tests/bootstrap/test_new_instance.py
@@ -2,6 +2,7 @@
script, as described in the documentation.
"""
+import configparser
import contextlib
import logging
import os
@@ -10,6 +11,8 @@ import subprocess
import venv
from pathlib import Path
+import sqlalchemy
+
LOGGER = logging.getLogger(__name__)
REPO_BASE = Path(__file__).parent.parent.parent
@@ -51,31 +54,66 @@ def create_config(config_name: Path):
Path("data").mkdir()
+def cleanup_database(config_name: Path):
+ """Connects to the database and ensures everything is reset.
+
+ :param config_name: Path to the config file.
+ """
+ if not config_name.exists():
+ return
+
+ parser = configparser.ConfigParser()
+ parser["DEFAULT"]["here"] = str(config_name.parent)
+ parser.read(config_name)
+
+ db_url = parser["app:main"]["sqlalchemy.url"]
+ engine = sqlalchemy.create_engine(db_url)
+
+ match engine.name:
+ case "sqlite":
+ pass
+ case "postgresql":
+ with engine.connect() as connection:
+ connection.execute(sqlalchemy.text("DROP SCHEMA public CASCADE;"))
+ connection.execute(sqlalchemy.text("CREATE SCHEMA public;"))
+ connection.commit()
+
+
def test_setup_via_fietsupdate(tmpdir):
with chdir(tmpdir):
- # We create a new temporary virtual environment with a fresh install, just
- # to be sure there's as little interference as possible.
- LOGGER.info("Installing Fietsboek into clean env")
- binaries_path = install_fietsboek(tmpdir / "venv")
-
- LOGGER.info("Creating a test configuration")
- create_config(Path("testing.ini"))
-
- # Try to run the migrations
- subprocess.check_call(
- [binaries_path / "fietsupdate", "update", "-c", "testing.ini", "-f"]
- )
-
- # Also try to add an administrator
- subprocess.check_call([
- binaries_path / "fietsctl",
- "user",
- "add",
- "-c", "testing.ini",
- "--email", "foobar@example.com",
- "--name", "Foo Bar",
- "--password", "raboof",
- "--admin",
- ])
-
- assert True
+ try:
+ # We create a new temporary virtual environment with a fresh install, just
+ # to be sure there's as little interference as possible.
+ LOGGER.info("Installing Fietsboek into clean env")
+ binaries_path = install_fietsboek(tmpdir / "venv")
+
+ LOGGER.info("Installing additional SQL engines")
+ subprocess.check_call(
+ [binaries_path / "pip", "install", "psycopg2"]
+ )
+
+ LOGGER.info("Creating a test configuration")
+ create_config(Path("testing.ini"))
+
+ # Try to run the migrations
+ subprocess.check_call(
+ [binaries_path / "fietsupdate", "update", "-c", "testing.ini", "-f"]
+ )
+
+ # Also try to add an administrator
+ subprocess.check_call([
+ binaries_path / "fietsctl",
+ "user",
+ "add",
+ "-c", "testing.ini",
+ "--email", "foobar@example.com",
+ "--name", "Foo Bar",
+ "--password", "raboof",
+ "--admin",
+ ])
+
+ assert True
+ finally:
+ # Clean up the database. This is important with anything but SQLite, as
+ # the tables would otherwise persist and interfere with the other tests.
+ cleanup_database(Path("testing.ini"))
diff --git a/tests/conftest.py b/tests/conftest.py
index cd74b0b..652d443 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -52,6 +52,7 @@ def dbengine(app_settings, ini_file):
yield engine
+ engine.dispose()
Base.metadata.drop_all(bind=engine)
alembic.command.stamp(alembic_cfg, None, purge=True)
diff --git a/tests/integration/test_browse.py b/tests/integration/test_browse.py
index 875821d..83218cc 100644
--- a/tests/integration/test_browse.py
+++ b/tests/integration/test_browse.py
@@ -22,6 +22,7 @@ def added_tracks(tm, dbsession, owner, data_manager):
tm.abort()
tracks = []
+ track_ids = []
with tm:
track = models.Track(
owner=owner,
@@ -37,6 +38,7 @@ def added_tracks(tm, dbsession, owner, data_manager):
dbsession.flush()
data_manager.initialize(track.id).compress_gpx(load_gpx_asset("MyTourbook_1.gpx.gz"))
tracks.append(track)
+ track_ids.append(track.id)
track = models.Track(
owner=owner,
@@ -52,12 +54,62 @@ def added_tracks(tm, dbsession, owner, data_manager):
dbsession.flush()
data_manager.initialize(track.id).compress_gpx(load_gpx_asset("Teasi_1.gpx.gz"))
tracks.append(track)
+ track_ids.append(track.id)
tm.begin()
tm.doom()
try:
- yield tracks
+ yield track_ids
+ finally:
+ tm.abort()
+ with tm:
+ for track in tracks:
+ dbsession.delete(track)
+ tm.begin()
+ tm.doom()
+
+
+@contextmanager
+def a_lot_of_tracks(tm, dbsession, owner, data_manager):
+ """Adds some tracks to the database session.
+
+ This function should be used as a context manager and it ensures that the
+ added tracks are deleted again after the test, to make a clean slate for
+ the next test.
+ """
+ # The normal transaction is "doomed", so we need to abort it, start a fresh
+ # one, and then explicitely commit it, otherwise we will not persist the
+ # objects to the database.
+ tm.abort()
+
+ gpx_data = load_gpx_asset("MyTourbook_1.gpx.gz")
+
+ tracks = []
+ track_ids = []
+ with tm:
+ for index in range(50):
+ track = models.Track(
+ owner=owner,
+ title=f"Traxi {index}",
+ visibility=Visibility.PUBLIC,
+ description="One of many",
+ badges=[],
+ link_secret="foobar",
+ tagged_people=[],
+ )
+ track.date = datetime(2022 - index, 3, 14, 9, 26, 59)
+ dbsession.add(track)
+ dbsession.flush()
+ data_manager.initialize(track.id).compress_gpx(gpx_data)
+ tracks.append(track)
+ track_ids.append(track.id)
+
+ tm.begin()
+ tm.doom()
+
+ try:
+ yield track_ids
finally:
tm.abort()
with tm:
@@ -78,15 +130,43 @@ def test_browse(testapp, dbsession, route_path, logged_in, tm, data_manager):
assert "Barfoo" in browse.text
+def test_browse_paged(testapp, dbsession, route_path, logged_in, tm, data_manager):
+ # pylint: disable=too-many-positional-arguments
+ with a_lot_of_tracks(tm, dbsession, logged_in, data_manager):
+ page_1 = testapp.get(route_path("browse", _query=[("page", 1)]))
+ assert "Traxi 0" in page_1.text
+ assert "Traxi 10" in page_1.text
+ assert "Traxi 20" not in page_1.text
+ assert "Traxi 30" not in page_1.text
+ assert "Traxi 40" not in page_1.text
+
+ page_2 = testapp.get(route_path("browse", _query=[("page", 2)]))
+ assert "Traxi 0" not in page_2.text
+ assert "Traxi 10" not in page_2.text
+ assert "Traxi 20" in page_2.text
+ assert "Traxi 30" in page_2.text
+ assert "Traxi 40" not in page_2.text
+
+ page_3 = testapp.get(route_path("browse", _query=[("page", 3)]))
+ assert "Traxi 0" not in page_3.text
+ assert "Traxi 10" not in page_3.text
+ assert "Traxi 20" not in page_3.text
+ assert "Traxi 30" not in page_3.text
+ assert "Traxi 40" in page_3.text
+
+
def test_archive(testapp, dbsession, route_path, logged_in, tm, data_manager):
# pylint: disable=too-many-positional-arguments
- with added_tracks(tm, dbsession, logged_in, data_manager):
+ with added_tracks(tm, dbsession, logged_in, data_manager) as tracks:
archive = testapp.get(
- route_path('track-archive', _query=[("track_id[]", "1"), ("track_id[]", "2")])
+ route_path(
+ 'track-archive',
+ _query=[("track_id[]", tracks[0]), ("track_id[]", tracks[1])],
+ )
)
result = io.BytesIO(archive.body)
with zipfile.ZipFile(result, 'r') as zipped:
assert len(zipped.namelist()) == 2
- assert "track_1.gpx" in zipped.namelist()
- assert "track_2.gpx" in zipped.namelist()
+ assert f"track_{tracks[0]}.gpx" in zipped.namelist()
+ assert f"track_{tracks[1]}.gpx" in zipped.namelist()
diff --git a/tests/playwright/conftest.py b/tests/playwright/conftest.py
index 4e7f5a4..adf5ef3 100644
--- a/tests/playwright/conftest.py
+++ b/tests/playwright/conftest.py
@@ -7,10 +7,12 @@ from wsgiref import simple_server
from pyramid.authentication import AuthTktCookieHelper
from pyramid.testing import DummyRequest
+import fietsboek.config
from testutils import load_gpx_asset
from fietsboek import models, util, actions
from fietsboek.models.track import Visibility, TrackType
from fietsboek.config import Config
+from fietsboek.views.tileproxy import TileRequester
import pytest
@@ -55,7 +57,11 @@ def dbaccess(app):
through and the running WSGI app cannot read them.
"""
session_factory = app.registry["dbsession_factory"]
- return session_factory()
+ factory = session_factory()
+
+ yield factory
+
+ factory.close()
class Helper:
@@ -112,11 +118,14 @@ class Helper:
"""
if user is None:
user = self.john_doe()
+ config = fietsboek.config.parse(self.app_settings)
with self.dbaccess:
user = self.dbaccess.merge(user)
track = actions.add_track(
self.dbaccess,
self.data_manager,
+ TileRequester(None),
+ config.public_tile_layers()[0],
owner=user,
title="Another awesome track",
visibility=Visibility.PRIVATE,
diff --git a/tests/playwright/test_basic.py b/tests/playwright/test_basic.py
index 231962e..3ae0f58 100644
--- a/tests/playwright/test_basic.py
+++ b/tests/playwright/test_basic.py
@@ -101,6 +101,33 @@ def test_edit(page: Page, playwright_helper, dbaccess):
assert track.description == "Not so descriptive anymore"
+def test_edit_change_gpx(page: Page, playwright_helper, tmp_path, dbaccess):
+ playwright_helper.login()
+ track_id = playwright_helper.add_track().id
+
+ track = dbaccess.execute(select(models.Track).filter_by(id=track_id)).scalar_one()
+ old_cache = track.cache.length, track.cache.uphill, track.cache.downhill
+
+ page.goto(f"/track/{track_id}")
+ page.locator(".btn", has_text="Edit").click()
+
+ gpx_data = load_gpx_asset("Synthetic_BRouter_1.gpx.gz")
+ gpx_path = tmp_path / "NewGPX.gpx"
+ with open(gpx_path, "wb") as gpx_fobj:
+ gpx_fobj.write(gpx_data)
+
+ page.get_by_label("New file for this track").set_input_files(gpx_path)
+ page.locator(".btn", has_text="Save").click()
+
+ track = dbaccess.execute(select(models.Track).filter_by(id=track_id)).scalar_one()
+ new_cache = track.cache
+ dbaccess.refresh(new_cache)
+
+ assert old_cache[0] != new_cache.length
+ assert old_cache[1] != new_cache.uphill
+ assert old_cache[2] != new_cache.downhill
+
+
def test_browse(page: Page, playwright_helper, dbaccess):
playwright_helper.login()
track = playwright_helper.add_track()
diff --git a/tests/playwright/test_profiles.py b/tests/playwright/test_profiles.py
index 7e5fb3c..ffbaab0 100644
--- a/tests/playwright/test_profiles.py
+++ b/tests/playwright/test_profiles.py
@@ -5,7 +5,7 @@ def test_forbidden(page: Page, playwright_helper):
john = playwright_helper.john_doe()
with page.expect_response(lambda resp: resp.status == 403):
- page.goto(f"/user/{john.id}")
+ page.goto(f"/user/{john.id}/")
def test_profile(page: Page, playwright_helper):
diff --git a/tests/playwright/test_share.py b/tests/playwright/test_share.py
index de288a0..dcba899 100644
--- a/tests/playwright/test_share.py
+++ b/tests/playwright/test_share.py
@@ -29,7 +29,8 @@ def test_view_wrong_link(page: Page, playwright_helper, dbaccess):
with page.expect_response(lambda resp: resp.status == 403):
page.goto(f"/track/{track.id}?secret=foobar")
- assert "Forbidden" in page.content()
+ assert "No entry" in page.content()
+ assert "not allowed to access" in page.content()
def test_change_link(page: Page, playwright_helper, dbaccess):
diff --git a/tests/playwright/test_tileproxy.py b/tests/playwright/test_tileproxy.py
index d4d3389..2a2bdc0 100644
--- a/tests/playwright/test_tileproxy.py
+++ b/tests/playwright/test_tileproxy.py
@@ -14,7 +14,7 @@ def test_tileproxy(page: Page, playwright_helper, caplog):
# If we're too fast, the log entry might not be there yet, wait 2 more
# seconds
- if "Skipping tile proxy request for testing URL" not in caplog.messages:
+ if "Skipping tile request for testing URL" not in caplog.messages:
time.sleep(2)
- assert "Skipping tile proxy request for testing URL" in caplog.messages
+ assert "Skipping tile request for testing URL" in caplog.messages
diff --git a/tox.ini b/tox.ini
index 17d6bdb..3fad742 100644
--- a/tox.ini
+++ b/tox.ini
@@ -11,7 +11,9 @@ envlist = python,pylint,pylint-tests,flake,mypy,black,isort
isolated_build = true
[testenv]
-deps = poetry
+deps =
+ poetry
+ psycopg2
skip_install = true
passenv =
TERM