diff options
112 files changed, 9131 insertions, 2622 deletions
@@ -26,5 +26,8 @@ test poetry.toml /language-packs # The module docs are regenerated using sphinx-apidoc -doc/developer/module/ +doc/developer/module/* +# However, we manually document hittekaart_py: +!doc/developer/module/modules.rst +!doc/developer/module/hittekaart_py.rst /node_modules diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 9263e91..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,58 +0,0 @@ -# Change pip's cache directory to be inside the project directory since we can -# only cache local items. -variables: - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" - -default: - image: python:bullseye - # Pip's cache doesn't store the python packages - # https://pip.pypa.io/en/stable/topics/caching/ - # - # If you want to also cache the installed packages, you have to install - # them in a virtualenv and cache it as well. - cache: - paths: - - .cache/pip - - .tox - - before_script: - - python --version # For debugging - - pip install tox - -test: - 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 - -# test-pypy: -# image: pypy:3 -# # nh3 does not have wheels prepared for pypy, so we need a working Rust -# # compiler to compile the module: -# script: -# - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh /dev/stdin -y --profile=minimal -# - source "$HOME/.cargo/env" -# - 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 pypy3 -- --browser firefox - -lint: - script: - - tox -e pylint,pylint-tests,flake,black,isort - -mypy: - script: - - tox -e mypy - -eslint: - image: node - before_script: [] - script: - - npm install - - npx eslint asset-sources/*.ts diff --git a/.woodpecker/eslint.yml b/.woodpecker/eslint.yml new file mode 100644 index 0000000..52fb895 --- /dev/null +++ b/.woodpecker/eslint.yml @@ -0,0 +1,9 @@ +when: + - event: push + +steps: + - name: eslint + image: node + commands: + - npm install + - npx eslint asset-sources/*.ts diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml new file mode 100644 index 0000000..37eebba --- /dev/null +++ b/.woodpecker/lint.yml @@ -0,0 +1,9 @@ +when: + - event: push + +steps: + - name: lint + image: python:bookworm + commands: + - pip install tox + - tox -e pylint,pylint-tests,flake,black,isort diff --git a/.woodpecker/mypy.yml b/.woodpecker/mypy.yml new file mode 100644 index 0000000..92ad9cf --- /dev/null +++ b/.woodpecker/mypy.yml @@ -0,0 +1,9 @@ +when: + - event: push + +steps: + - name: mypy + image: python:bookworm + commands: + - pip install tox + - tox -e mypy diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml new file mode 100644 index 0000000..f50057a --- /dev/null +++ b/.woodpecker/test.yml @@ -0,0 +1,14 @@ +when: + - event: push + +matrix: + DB: + - sqlite + - postgres + +steps: + - name: test + image: python:bookworm + commands: + - pip install tox + - ci/run_tests.sh $DB diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a49f7b6..ab0c9c4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,69 @@ Changelog Unreleased ---------- +0.12.1 - 2026-01-03 +------------------- + +Fixed +^^^^^ + +- Fixed ``fietsctl maintenance-mode``. + +0.12.0 - 2026-01-03 +------------------- + +Added +^^^^^ + +- Added opengraph tags for better previews. +- Added PDF renderings of tracks. +- Added journeys. + +Changed +^^^^^^^ + +- ``hittekaart`` is now built as a dependency, no longer an external binary. +- Tracks are now stored in the database. + +Fixed +^^^^^ + +- Backup GPX files are now included in the storage breakdown. +- No more warnings about ``pkg_resources`` being deprecated. +- Proper deletion of (partial) track data if something fails during the upload. + +Removed +^^^^^^^ + +- The ``hittekaart.bin`` configuration setting (not needed anymore, since it's + a dependency now). +- ``Content-Encoding: br`` for GPX files (not worth anymore, as we need to + compress on-the-fly). + +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 ------------------- @@ -1,4 +1,4 @@ -FROM python:3.11 +FROM python:3.14 RUN pip install gunicorn psycopg2-binary mysqlclient RUN mkdir /package diff --git a/asset-sources/fietsboek.ts b/asset-sources/fietsboek.ts index 66caa4f..bcce661 100644 --- a/asset-sources/fietsboek.ts +++ b/asset-sources/fietsboek.ts @@ -540,6 +540,23 @@ function loadProfileStats() { /* Used via in-page scripts, so make eslint happy */ loadProfileStats; +/** + * Formats the given timestamp to the user's locale. + * + * @param timestamp - The timestamp in milliseconds since the epoch. + * @return The formatted string. + */ +function formatTimestamp(timestamp: number): string { + const date = new Date(timestamp); + // TypeScript complains about this, but according to MDN it is fine, at + // least in "somewhat modern" browsers + const intl = new Intl.DateTimeFormat(LOCALE, { + dateStyle: "medium", + timeStyle: "medium", + } as any); + return intl.format(date); +} + document.addEventListener('DOMContentLoaded', function() { window.fietsboekImageIndex = 0; @@ -567,13 +584,6 @@ document.addEventListener('DOMContentLoaded', function() { /* Format all datetimes to the local timezone */ document.querySelectorAll(".fietsboek-local-datetime").forEach((obj) => { const timestamp = parseFloat(obj.attributes.getNamedItem("data-utc-timestamp")!.value); - const date = new Date(timestamp * 1000); - // TypeScript complains about this, but according to MDN it is fine, at - // least in "somewhat modern" browsers - const intl = new Intl.DateTimeFormat(LOCALE, { - dateStyle: "medium", - timeStyle: "medium", - } as any); - obj.innerHTML = intl.format(date); + obj.innerHTML = formatTimestamp(timestamp * 1000); }); }); 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/configuration.rst b/doc/administration/configuration.rst index 24c5fdb..98f3592 100644 --- a/doc/administration/configuration.rst +++ b/doc/administration/configuration.rst @@ -221,15 +221,12 @@ true``. This will cause the tiles to be loaded directly by the client. Hittekaart Integration ---------------------- -Fietsboek can use hittekaart_ to generate heat maps for users. For that, you -can set ``hittekaart.bin`` to the path to the ``hittekaart`` binary. If unset, -it is assumed that the binary can be found in your ``$PATH``. +Fietsboek can use hittekaart_ to generate heat maps for users. -In addition, you can set ``hittekaart.autogenerate`` to the list of overlay -maps you want to automatically generate and update. By default, this list is -empty, which means that Fietsboek will not generate any overlays on its own. -You can add ``heatmap`` and/or ``tilehunter`` to generate those maps -automatically. +You can set ``hittekaart.autogenerate`` to the list of overlay maps you want to +automatically generate and update. By default, this list is empty, which means +that Fietsboek will not generate any overlays on its own. You can add +``heatmap`` and/or ``tilehunter`` to generate those maps automatically. By default, ``hittekaart`` will use as many threads as CPU cores are available. This leads to the fastest heatmap generation, but might be undesired on shared @@ -253,7 +250,6 @@ An example configuration excerpt can look like this: .. code-block:: ini - hittekaart.bin = /usr/local/bin/hittekaart hittekaart.autogenerate = heatmap tilehunter hittekaart.threads = 2 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/doc/developer/module/hittekaart_py.rst b/doc/developer/module/hittekaart_py.rst new file mode 100644 index 0000000..052c2b0 --- /dev/null +++ b/doc/developer/module/hittekaart_py.rst @@ -0,0 +1,149 @@ +hittekaart_py package +===================== + +.. module:: hittekaart_py + + The ``hittekaart_py`` module provides a Python interface for hittekaart__ + heatmap generation. Unlike previous modules, this is a binding using + PyO3__, and not a subprocess wrapper. + +.. __: https://gitlab.com/dunj3/hittekaart +.. __: https://crates.io/crates/pyo3 + +Example +------- + +.. code-block:: python + + from hittekaart_py import ( + Track, HeatmapRenderer, Settings, Storage, generate + ) + + settings = Settings(threads=3) + tracks = [ + Track.from_file(b"Documents/track.gpx", None), + Track.from_coordinates([(45.0, 47.0)]), + ] + storage = Storage.Sqlite(b"/tmp/tiles.sqlite") + generate(settings, tracks, HeatmapRenderer(), storage) + +Input and output +---------------- + +.. class:: Track + + An in-memory representation of a track. + + .. staticmethod:: from_file(path, compression) + + Loads a track from the given file. + + :param path: Path to the file. + :type path: bytes + :param compression: Decompression algorithm to use when reading the + file. Can be :obj:`None`, ``"gzip"`` or ``"brotli"``. + :type compression: str | None + :return: The created track. + :rtype: Track + + .. staticmethod:: from_coordinates(coordinates) + + Directly represents the given coordinates as a track. + + :param coordinates: The coordinates as a list of longitude-latitude + pairs. + :type coordinates: list[tuple[float, float]] + :return: The created track. + :rtype: Track + + +.. class:: Storage + + Represents the output storage. + + .. staticmethod:: Folder(path) + + Output the tiles to the given folder. This will create a subdirectory + for every zoom level, then a directory for the x coordinate, then a + file ``y.png``. + + Note that this will create many small files, which can waste space. + + :param path: Path to the folder. + :type path: bytes + :return: The created storage. + :rtype: Storage + + .. staticmethod:: Sqlite(path) + + Output the tiles to the given SQLite database. + + :param path: Path to the database. + :type path: bytes + :return: The created storage. + :rtype: Storage + +Renderers +--------- + +.. class:: HeatmapRenderer() + + The renderer that generates a heatmap. + +.. class:: MarktileRenderer() + + The renderer that will only mark visited tiles. + +.. class:: TilehuntRenderer(zoom) + + The renderer that will mark visisted tiles at a fixed zoom level. + + :param zoom: The zoom level. + :type zoom: int + +Tile generation +--------------- + +.. class:: Settings(min_zoom=1, max_zoom=19, threads=0) + + Settings that apply to all renderers. + + .. attribute:: min_zoom + :type: int + + Smalles zoom level to generate tiles for. + + .. attribute:: max_zoom + :type: int + + Largest zoom level to generate tiles for. + + .. attribute:: threads + :type: int + + Number of threads to use for tile generation. + + Setting this to 0 will automatically determine the number of available + cores and use that many threads. + +.. function:: generate(settings, items, renderer, storage) + + Generates the tiles using the given renderer, and saves them to the given + storage. + + :param settings: The settings. + :type settings: Settings + :param items: The tracks to render. + :type items: ~typing.Iterable[Track] + :param renderer: The renderer to use. + :type renderer: HeatmapRenderer | MarktileRenderer | TilehuntRenderer + :param storage: The storage to output to. + :type storage: Storage + +Errors +------ + +.. exception:: HitteError + + Catch-all error for underlying hittekaart errors. See the string + description for the error cause. diff --git a/doc/developer/module/modules.rst b/doc/developer/module/modules.rst new file mode 100644 index 0000000..9585a5f --- /dev/null +++ b/doc/developer/module/modules.rst @@ -0,0 +1,8 @@ +fietsboek +========= + +.. toctree:: + :maxdepth: 1 + + fietsboek + hittekaart_py diff --git a/fietsboek/__init__.py b/fietsboek/__init__.py index 3d6c125..1f21c5f 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 @@ -30,6 +31,7 @@ from pyramid.response import Response from pyramid.session import SignedCookieSessionFactory from . import config as mod_config +from . import fstrans from . import jinja2 as mod_jinja2 from . import transformers from .data import DataManager @@ -37,7 +39,10 @@ from .pages import Pages from .security import SecurityPolicy from .updater import Updater, UpdateState -__VERSION__ = importlib.metadata.version("fietsboek") +try: + __VERSION__ = importlib.metadata.version("fietsboek") +except importlib.metadata.PackageNotFoundError: + __VERSION__ = "<unknown>" LOGGER = logging.getLogger(__name__) @@ -88,7 +93,10 @@ def maintenance_mode( """ def tween(request: Request) -> Response: - maintenance = request.data_manager.maintenance_mode() + # We don't want to mess around with transactioned data mangers here, + # so we create a new one without transaction + data_manager = DataManager(Path(request.config.data_dir)) + maintenance = data_manager.maintenance_mode() if maintenance is None: return handler(request) @@ -119,6 +127,35 @@ 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 create_data_folders(data_dir: Path): + """Creates the subfolders of the data directory. + + :param data_dir: Path to the data directory, from the config. + """ + LOGGER.debug("Creating %s/tracks/", data_dir) + (data_dir / "tracks").mkdir(exist_ok=True) + LOGGER.debug("Creating %s/users/", data_dir) + (data_dir / "users").mkdir(exist_ok=True) + LOGGER.debug("Creating %s/journeys/", data_dir) + (data_dir / "journeys").mkdir(exist_ok=True) + + def main(global_config, **settings): """This function returns a Pyramid WSGI application.""" # Avoid a circular import by not importing at the top level @@ -132,8 +169,13 @@ def main(global_config, **settings): parsed_config = mod_config.parse(settings) settings["jinja2.newstyle"] = True + check_db_engine(parsed_config.sqlalchemy_url) + create_data_folders(parsed_config.data_dir) + def data_manager(request): - return DataManager(Path(request.config.data_dir)) + data_dir = Path(request.config.data_dir) + lock_file = data_dir / "lock" + return DataManager(data_dir, txn=fstrans.begin(lock_file, request.tm)) def redis_(request): return redis.from_url(request.config.redis_url) @@ -174,7 +216,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..cdddaa2 100644 --- a/fietsboek/actions.py +++ b/fietsboek/actions.py @@ -7,23 +7,24 @@ the test functions. """ import datetime +import io import logging import re from typing import Optional -import brotli -import gpxpy from pyramid.i18n import TranslationString as _ from pyramid.request import Request from sqlalchemy import select from sqlalchemy.orm.session import Session -from . import email, models +from . import convert, 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 +32,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, @@ -69,16 +72,19 @@ def add_track( """ # pylint: disable=too-many-positional-arguments,too-many-locals,too-many-arguments LOGGER.debug("Inserting new track...") - track = models.Track( - owner=owner, - title=title, - visibility=visibility, - type=track_type, - description=description, - badges=badges, - link_secret=util.random_link_secret(), - tagged_people=tagged_people, - ) + track = convert.smart_convert(gpx_data) + path = track.path() + # We set the path later using fast_set_path. By setting the empty list + # here, we avoid doing an insert of many thousand points prematurely. + track.points = [] + track.owner = owner + track.title = title + track.visibility = visibility + track.type = track_type + track.description = description + track.badges = badges + track.link_secret = util.random_link_secret() + track.tagged_people = tagged_people track.date = date track.sync_tags(tags) dbsession.add(track) @@ -87,31 +93,27 @@ def add_track( # Save the GPX data LOGGER.debug("Creating a new data folder for %d", track.id) assert track.id is not None - with data_manager.initialize(track.id) as manager: - LOGGER.debug("Saving GPX to %s", manager.gpx_path()) - manager.compress_gpx(gpx_data) - manager.backup() - - gpx = gpxpy.parse(gpx_data) - for transformer in transformers: - LOGGER.debug("Running %s with %r", transformer, transformer.parameters) - transformer.execute(gpx) - track.transformers = [ - [tfm.identifier(), tfm.parameters.model_dump()] for tfm in transformers - ] - - # Best time to build the cache is right after the upload, but *after* the - # transformers have been applied! - track.ensure_cache(gpx) - dbsession.add(track.cache) - - manager.engrave_metadata( - title=track.title, - description=track.description, - author_name=track.owner.name, - time=track.date, - gpx=gpx, - ) + manager = data_manager.initialize(track.id) + LOGGER.debug("Saving backup to %s", manager.backup_path()) + manager.compress_backup(gpx_data) + + for transformer in transformers: + LOGGER.debug("Running %s with %r", transformer, transformer.parameters) + transformer.execute(path) + track.transformers = [[tfm.identifier(), tfm.parameters.model_dump()] for tfm in transformers] + + track.fast_set_path(path) + + # Best time to build the cache is right after the upload, but *after* the + # transformers have been applied! + track.ensure_cache(path) + dbsession.add(track.cache) + + LOGGER.debug("Building preview image for %s", track.id) + preview_image = trackmap.render(path, layer, tile_requester) + image_io = io.BytesIO() + preview_image.save(image_io, "PNG") + manager.set_preview(image_io.getvalue()) return track @@ -187,7 +189,7 @@ def edit_images(request: Request, track: models.Track, *, manager: Optional[Trac request.dbsession.add(image_meta) -def execute_transformers(request: Request, track: models.Track) -> Optional[gpxpy.gpx.GPX]: +def execute_transformers(request: Request, track: models.Track): """Execute the transformers for the given track. Note that this function "short circuits" if the saved transformer settings @@ -198,7 +200,6 @@ def execute_transformers(request: Request, track: models.Track) -> Optional[gpxp :param request: The request. :param track: The track. - :return: The transformed track. """ # pylint: disable=too-many-locals LOGGER.debug("Executing transformers for %d", track.id) @@ -208,22 +209,21 @@ def execute_transformers(request: Request, track: models.Track) -> Optional[gpxp serialized = [[tfm.identifier(), tfm.parameters.model_dump()] for tfm in settings] if serialized == track.transformers: LOGGER.debug("Applied transformations match on %d, skipping", track.id) - return None + return # We always start with the backup, that way we don't get "deepfried GPX" # files by having the same filters run multiple times on the same input. # They are not idempotent after all. manager = request.data_manager.open(track.id) - gpx_bytes = manager.backup_path().read_bytes() - gpx_bytes = brotli.decompress(gpx_bytes) - gpx = gpxpy.parse(gpx_bytes) + backup_bytes = manager.decompress_backup() + reloaded = convert.smart_convert(backup_bytes) + path = reloaded.path() for transformer in settings: LOGGER.debug("Running %s with %r", transformer, transformer.parameters) - transformer.execute(gpx) + transformer.execute(path) - LOGGER.debug("Saving transformed file for %d", track.id) - manager.compress_gpx(util.encode_gpx(gpx)) + track.fast_set_path(path) LOGGER.debug("Saving new transformers on %d", track.id) track.transformers = serialized @@ -231,9 +231,8 @@ def execute_transformers(request: Request, track: models.Track) -> Optional[gpxp LOGGER.debug("Rebuilding cache for %d", track.id) request.dbsession.delete(track.cache) track.cache = None - track.ensure_cache(gpx) + track.ensure_cache() request.dbsession.add(track.cache) - return gpx def send_verification_token(request: Request, user: models.User): diff --git a/fietsboek/alembic/versions/20220808_d085998b49ca.py b/fietsboek/alembic/versions/20220808_d085998b49ca.py index d6353d2..5b47668 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 = lambda: 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..d1c2c2f --- /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 = lambda: 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/alembic/versions/20251019_90b39fdf6e4b.py b/fietsboek/alembic/versions/20251019_90b39fdf6e4b.py new file mode 100644 index 0000000..825f774 --- /dev/null +++ b/fietsboek/alembic/versions/20251019_90b39fdf6e4b.py @@ -0,0 +1,46 @@ +"""add table for track points + +Revision ID: 90b39fdf6e4b +Revises: 2ebe1bf66430 +Create Date: 2025-10-19 20:17:12.562653 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = '90b39fdf6e4b' +down_revision = '2ebe1bf66430' +branch_labels = None +depends_on = None + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('track_points', + sa.Column('track_id', sa.Integer(), nullable=False), + sa.Column('index', sa.Integer(), nullable=False), + sa.Column('longitude', sa.Float(), nullable=False), + sa.Column('latitude', sa.Float(), nullable=False), + sa.Column('elevation', sa.Float(), nullable=True), + sa.Column('time_offset', sa.Float(), nullable=True), + sa.ForeignKeyConstraint(['track_id'], ['tracks.id'], name=op.f('fk_track_points_track_id_tracks')), + sa.PrimaryKeyConstraint('track_id', 'index', name=op.f('pk_track_points')) + ) + op.create_table('waypoints', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('track_id', sa.Integer(), nullable=False), + sa.Column('longitude', sa.Float(), nullable=False), + sa.Column('latitude', sa.Float(), nullable=False), + sa.Column('elevation', sa.Float(), nullable=True), + sa.Column('name', sa.Text(), nullable=True), + sa.Column('description', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['track_id'], ['tracks.id'], name=op.f('fk_waypoints_track_id_tracks')), + sa.PrimaryKeyConstraint('id', name=op.f('pk_waypoints')) + ) + # ### end Alembic commands ### + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('track_points') + op.drop_table('waypoints') + # ### end Alembic commands ### diff --git a/fietsboek/alembic/versions/20251230_f9ca03541351.py b/fietsboek/alembic/versions/20251230_f9ca03541351.py new file mode 100644 index 0000000..363c6c1 --- /dev/null +++ b/fietsboek/alembic/versions/20251230_f9ca03541351.py @@ -0,0 +1,43 @@ +"""add journeys table + +Revision ID: f9ca03541351 +Revises: 90b39fdf6e4b +Create Date: 2025-12-30 22:23:17.765361 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = 'f9ca03541351' +down_revision = '90b39fdf6e4b' +branch_labels = None +depends_on = None + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('journeys', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('owner_id', sa.Integer(), nullable=False), + sa.Column('title', sa.Text(), nullable=False), + sa.Column('description', sa.Text(), nullable=False), + sa.Column('visibility', sa.Enum('PRIVATE', 'FRIENDS', 'LOGGED_IN', 'PUBLIC', name='journey_visibility'), nullable=False), + sa.Column('link_secret', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['owner_id'], ['users.id'], name=op.f('fk_journeys_owner_id_users')), + sa.PrimaryKeyConstraint('id', name=op.f('pk_journeys')) + ) + op.create_table('journey_track_assoc', + sa.Column('journey_id', sa.Integer(), nullable=False), + sa.Column('track_id', sa.Integer(), nullable=False), + sa.Column('sort_index', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['journey_id'], ['journeys.id'], name=op.f('fk_journey_track_assoc_journey_id_journeys')), + sa.ForeignKeyConstraint(['track_id'], ['tracks.id'], name=op.f('fk_journey_track_assoc_track_id_tracks')), + sa.PrimaryKeyConstraint('journey_id', 'track_id', name=op.f('pk_journey_track_assoc')) + ) + # ### end Alembic commands ### + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('journey_track_assoc') + op.drop_table('journeys') + # ### end Alembic commands ### diff --git a/fietsboek/config.py b/fietsboek/config.py index af946b4..bd2347a 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 ( @@ -63,6 +64,11 @@ KNOWN_TILE_LAYERS = [ "hiking", ] +WARNINGS = { + "hittekaart.bin": "hittekaart is now used via a Python module. " + "Enable extra `hittekaart` to install the dependency.", +} + class ValidationError(Exception): """Exception for malformed configurations. @@ -202,9 +208,6 @@ class Config(BaseModel): tile_layers: list[TileLayerConfig] = [] """Tile layers.""" - hittekaart_bin: str = Field("", alias="hittekaart.bin") - """Path to the hittekaart binary.""" - hittekaart_autogenerate: PyramidList = Field([], alias="hittekaart.autogenerate") """Overlay maps to automatically generate.""" @@ -277,6 +280,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`. @@ -325,10 +349,17 @@ def parse(config: dict) -> Config: keys.discard(_field_name(field_name, field)) keys -= KNOWN_PYRAMID_SETTINGS + _warn_unknown(keys) + + return parsed_config + + +def _warn_unknown(keys): for key in keys: LOGGER.warning("Unknown configuration key: %r", key) - return parsed_config + if warning := WARNINGS.get(key): + LOGGER.warning(warning) def _field_name(field_name, field): diff --git a/fietsboek/convert.py b/fietsboek/convert.py index 3c8208b..5d7b43e 100644 --- a/fietsboek/convert.py +++ b/fietsboek/convert.py @@ -1,11 +1,27 @@ """Conversion functions to convert between various recording formats.""" +import datetime + import fitparse -from gpxpy.gpx import GPX, GPXTrack, GPXTrackPoint, GPXTrackSegment +import gpxpy + +from . import geo, util +from .models import Track, Waypoint FIT_RECORD_FIELDS = ["position_lat", "position_long", "altitude", "timestamp"] +class ConversionError(Exception): + """Error that occurred when loading a track from a file.""" + + +class UnknownFormat(ConversionError): + """The format of the source file could not be identified.""" + + def __str__(self): + return type(self).__doc__ + + def semicircles_to_deg(circles: int) -> float: """Convert semicircles coordinate to degree coordinate. @@ -15,8 +31,8 @@ def semicircles_to_deg(circles: int) -> float: return circles * (180 / 2**31) -def from_fit(data: bytes) -> GPX: - """Reads a .fit as GPX data. +def from_fit(data: bytes) -> Track: + """Reads a .fit as track data. This uses the fitparse_ library under the hood. @@ -24,29 +40,127 @@ def from_fit(data: bytes) -> GPX: :param data: The input bytes. :return: The converted structure. + :raises ConversionError: If conversion failed. """ fitfile = fitparse.FitFile(data) + start_time = None points = [] for record in fitfile.get_messages("record"): values = record.get_values() try: if any(values[field] is None for field in FIT_RECORD_FIELDS): continue - point = GPXTrackPoint( + time = values["timestamp"] + if start_time is None: + start_time = time + point = geo.Point( latitude=semicircles_to_deg(values["position_lat"]), longitude=semicircles_to_deg(values["position_long"]), elevation=values["altitude"], - time=values["timestamp"], + time_offset=(time - start_time).total_seconds(), ) except KeyError: pass else: points.append(point) - track = GPXTrack() - track.segments = [GPXTrackSegment(points)] - gpx = GPX() - gpx.tracks = [track] - return gpx + path = geo.Path(points) + track = Track() + track.set_path(path) + track.date = start_time + return track + + +def from_gpx(data: bytes) -> Track: + """Reads a .gpx as track data. + + This uses the gpxpy_ library under the hood. + + .. _gpxpy: https://github.com/tkrajina/gpxpy + + :param data: The input bytes. + :return: The converted structure. + :raises ConversionError: If conversion failed. + """ + # pylint: disable=too-many-locals + gpx = gpxpy.parse(data) + points = [] + start_time = None + + for track in gpx.tracks: + for segment in track.segments: + for point in segment.points: + if start_time is None: + start_time = point.time + + if point.time is not None and start_time is not None: + time_offset = (point.time - start_time).total_seconds() + else: + time_offset = 0 + points.append( + geo.Point( + longitude=point.longitude, + latitude=point.latitude, + elevation=point.elevation or 0.0, + time_offset=time_offset, + ) + ) + + timezone = util.guess_gpx_timezone(gpx) + date = gpx.time or gpx.get_time_bounds().start_time or datetime.datetime.now() + date = date.astimezone(timezone) + track_name = gpx.name + track_desc = gpx.description + for track in gpx.tracks: + if not track_name and track.name: + track_name = track.name + if not track_desc and track.description: + track_desc = track.description + + path = geo.Path(points) + track = Track() + track.set_path(path) + track.title = track_name + track.description = track_desc + track.date = date + + for waypoint in gpx.waypoints: + desc = None + # GPX waypoints can have both description and comment. It seems like + # comment is what is usually used (GPXViewer only shows the comment), + # so we'll prioritize that. + if waypoint.comment: + desc = waypoint.comment + if not desc and waypoint.description: + desc = waypoint.description + wpt = Waypoint( + longitude=waypoint.longitude, + latitude=waypoint.latitude, + elevation=waypoint.elevation, + name=waypoint.name, + description=desc, + ) + track.waypoints.append(wpt) + + return track + + +def smart_convert(data: bytes) -> Track: + """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. + :raises ConversionError: When conversion fails. + """ + if len(data) > 11 and data[9:12] == b"FIT": + return from_fit(data) + if data.startswith(b"<?xml") and b"<gpx" in data[:200]: + return from_gpx(data) + raise UnknownFormat() -__all__ = ["from_fit"] +__all__ = ["ConversionError", "from_fit", "from_gpx", "smart_convert"] diff --git a/fietsboek/data.py b/fietsboek/data.py index a7e9b19..a6492c3 100644 --- a/fietsboek/data.py +++ b/fietsboek/data.py @@ -7,21 +7,19 @@ the database itself. This module makes access to such data objects easier. # We don't have onexc yet in all supported versions, so let's ignore the # deprecation for now and stick with onerror: # pylint: disable=deprecated-argument -import datetime import logging -import os import random import shutil import string import uuid from pathlib import Path -from typing import BinaryIO, Literal, Optional +from typing import BinaryIO, Optional import brotli -import gpxpy from filelock import FileLock from . import util +from .fstrans import Transaction LOGGER = logging.getLogger(__name__) @@ -44,6 +42,10 @@ def generate_filename(filename: Optional[str]) -> str: return str(uuid.uuid4()) +def _log_deletion_error(_, path, exc_info): + LOGGER.warning("Failed to remove %s", path, exc_info=exc_info) + + class DataManager: """Data manager. @@ -53,8 +55,9 @@ class DataManager: :ivar data_dir: Path to the data folder. """ - def __init__(self, data_dir: Path): + def __init__(self, data_dir: Path, *, txn: Transaction | None = None): self.data_dir: Path = data_dir + self.txn = txn def _track_data_dir(self, track_id): return self.data_dir / "tracks" / str(track_id) @@ -62,6 +65,9 @@ class DataManager: def _user_data_dir(self, user_id): return self.data_dir / "users" / str(user_id) + def _journey_data_dir(self, journey_id): + return self.data_dir / "journeys" / str(journey_id) + def maintenance_mode(self) -> Optional[str]: """Checks whether the maintenance mode is enabled. @@ -84,8 +90,11 @@ class DataManager: :return: The manager that can be used to manage this track's data. """ path = self._track_data_dir(track_id) - path.mkdir(parents=True) - return TrackDataDir(track_id, path, journal=True, is_fresh=True) + if self.txn: + self.txn.make_dir(path) + else: + path.mkdir(parents=True) + return TrackDataDir(track_id, path, txn=self.txn) def initialize_user(self, user_id: int) -> "UserDataDir": """Creates the data directory for a user. @@ -95,8 +104,22 @@ class DataManager: :return: The manager that can be used to manage this user's data. """ path = self._user_data_dir(user_id) + if self.txn: + self.txn.make_dir(path) + else: + path.mkdir(parents=True) + return UserDataDir(user_id, path, txn=self.txn) + + def initialize_journey(self, journey_id: int) -> "JourneyDataDir": + """Creates the data directory for a journey. + + :raises FileExistsError: If the directory already exists. + :param journey_id: ID of the journey. + :return: The manager that can be used to manage this journey's data. + """ + path = self._journey_data_dir(journey_id) path.mkdir(parents=True) - return UserDataDir(user_id, path) + return JourneyDataDir(journey_id, path) def purge(self, track_id: int): """Forcefully purges all data from the given track. @@ -104,9 +127,9 @@ class DataManager: This function logs errors but raises no exception, as such it can always be used to clean up after a track. """ - TrackDataDir(track_id, self._track_data_dir(track_id)).purge() + TrackDataDir(track_id, self._track_data_dir(track_id), txn=self.txn).purge() - def open(self, track_id: int) -> "TrackDataDir": + def open(self, track_id: int, *, force: bool = False) -> "TrackDataDir": """Opens a track's data directory. :raises FileNotFoundError: If the track directory does not exist. @@ -114,9 +137,9 @@ class DataManager: :return: The manager that can be used to manage this track's data. """ path = self._track_data_dir(track_id) - if not path.is_dir(): + if not force and not path.is_dir(): raise FileNotFoundError(f"The path {path} is not a directory") from None - return TrackDataDir(track_id, path) + return TrackDataDir(track_id, path, txn=self.txn) def open_user(self, user_id: int) -> "UserDataDir": """Opens a user's data directory. @@ -128,98 +151,71 @@ class DataManager: path = self._user_data_dir(user_id) if not path.is_dir(): raise FileNotFoundError(f"The path {path} is not a directory") from None - return UserDataDir(user_id, path) + return UserDataDir(user_id, path, txn=self.txn) + def open_journey(self, journey_id: int) -> "JourneyDataDir": + """Open a journey's data directory. -class TrackDataDir: - """Manager for a single track's data. + :raises FileNotFoundError: If the journey directory does not exist. + :param journey_id: ID of the journey. + :return: The manager that can be used to manage this journey's data. + """ + path = self._journey_data_dir(journey_id) + if not path.is_dir(): + raise FileNotFoundError(f"The path {path} is not a directory") from None + return JourneyDataDir(journey_id, path) - If initialized with ``journal = True``, then you can use :meth:`rollback` - to roll back the changes in case of an error. In case of no error, use - :meth:`commit` to commit the changes. If you don't want the "journalling" - semantics, use ``journal = False``. - """ + def size(self) -> int: + """Returns the size of all data. - def __init__(self, track_id: int, path: Path, *, journal: bool = False, is_fresh: bool = False): - self.track_id: int = track_id - self.path: Path = path - self.journal: Optional[list] = [] if journal else None - self.is_fresh = is_fresh + :return: The size of all data in bytes. + """ + return util.recursive_size(self.data_dir) - def __enter__(self) -> "TrackDataDir": - if self.journal is None: - self.journal = [] - return self + def list_tracks(self) -> list[int]: + """Returns a list of all tracks. - def __exit__(self, exc_type, exc_val, exc_tb) -> Literal[False]: - if exc_type is None and exc_val is None and exc_tb is None: - self.commit() - else: - self.rollback() - return False + :return: A list of all track IDs. + """ + try: + return [int(track.name) for track in self._track_data_dir(".").iterdir()] + except FileNotFoundError: + return [] - def rollback(self): - """Rolls back the journal, e.g. in case of error. + def list_users(self) -> list[int]: + """Returns a list of all users. - :raises ValueError: If the data directory was opened without the - journal, this raises :exc:`ValueError`. + :return: A list of all user IDs. """ - LOGGER.debug("Rolling back state of %s", self.path) - - if self.journal is None: - raise ValueError("Rollback on a non-journalling data directory") + try: + return [int(user.name) for user in self._user_data_dir(".").iterdir()] + except FileNotFoundError: + return [] - if self.is_fresh: - # Shortcut if the directory is fresh, simply remove everything - self.journal = None - self.purge() - return + def list_journeys(self) -> list[int]: + """Returns a list of all journeys. - for action, *rest in reversed(self.journal): - if action == "purge": - (new_name,) = rest - shutil.move(new_name, self.path) - elif action == "compress_gpx": - (old_data,) = rest - if old_data is None: - self.gpx_path().unlink() - else: - self.gpx_path().write_bytes(old_data) - elif action == "add_image": - (image_path,) = rest - image_path.unlink() - elif action == "delete_image": - path, data = rest - path.write_bytes(data) - - self.journal = None - - def commit(self): - """Commits all changed and deletes the journal. - - Note that this function will do nothing if the journal is disabled, - meaning it can always be called. + :return: A list of all journey IDs. """ - LOGGER.debug("Committing journal for %s", self.path) + try: + return [int(journey.name) for journey in self._journey_data_dir(".").iterdir()] + except FileNotFoundError: + return [] - if self.journal is None: - return - for action, *rest in reversed(self.journal): - if action == "purge": - (new_name,) = rest - shutil.rmtree(new_name, ignore_errors=False, onerror=self._log_deletion_error) - elif action == "compress_gpx": - # Nothing to do here, the new data is already on the disk - pass - elif action == "add_image": - # Nothing to do here, the image is already saved - pass - elif action == "delete_image": - # Again, nothing to do here, we simply discard the in-memory image data - pass - - self.journal = None +class TrackDataDir: + """Manager for a single track's data. + + If initialized with ``journal = True``, then you can use :meth:`rollback` + to roll back the changes in case of an error. In case of no error, use + :meth:`commit` to commit the changes. If you don't want the "journalling" + semantics, use ``journal = False``. + """ + + def __init__(self, track_id: int, path: Path, *, txn: Transaction | None = None): + self.track_id: int = track_id + self.path: Path = path + self.txn = txn def lock(self) -> FileLock: """Returns a FileLock that can be used to lock access to the track's @@ -229,121 +225,54 @@ class TrackDataDir: """ return FileLock(self.path / "lock") - @staticmethod - def _log_deletion_error(_, path, exc_info): - LOGGER.warning("Failed to remove %s", path, exc_info=exc_info) - def purge(self): """Purge all data pertaining to the track. This function logs errors but raises no exception, as such it can always be used to clean up after a track. """ - if self.journal is None: - if self.path.is_dir(): - shutil.rmtree(self.path, ignore_errors=False, onerror=self._log_deletion_error) + if self.txn: + self.txn.purge(self.path) else: - new_name = self.path.with_name("trash-" + self.path.name) - shutil.move(self.path, new_name) - self.journal.append(("purge", new_name)) + if self.path.is_dir(): + shutil.rmtree(self.path, ignore_errors=False, onerror=_log_deletion_error) def size(self) -> int: """Returns the size of the data that this track entails. :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 - - def gpx_path(self) -> Path: - """Returns the path of the GPX file. - - This file contains the (brotli) compressed GPX data. - - :return: The path where the GPX is supposed to be. - """ - return self.path / "track.gpx.br" + return util.recursive_size(self.path) - def compress_gpx(self, data: bytes, quality: int = 4): - """Set the GPX content to the compressed form of data. + def compress_backup(self, data: bytes, quality: int = 4): + """Set the content of the backup to the compressed form of data. - If you want to write compressed data directly, use :meth:`gpx_path` to + If you want to write compressed data directly, use :meth:`backup_path` to get the path of the GPX file. :param data: The GPX data (uncompressed). :param quality: Compression quality, from 0 to 11 - 11 is highest quality but slowest compression speed. """ - if self.journal is not None: - # First, we check if we already saved an old state of the GPX data - for action, *_ in self.journal: - if action == "compress_gpx": - break - else: - # We did not save a state yet - old_data = None if not self.gpx_path().is_file() else self.gpx_path().read_bytes() - self.journal.append(("compress_gpx", old_data)) - compressed = brotli.compress(data, quality=quality) - self.gpx_path().write_bytes(compressed) + if self.txn: + self.txn.write_bytes(self.backup_path(), compressed) + else: + self.backup_path().write_bytes(compressed) - def decompress_gpx(self) -> bytes: - """Returns the GPX bytes decompressed. + def decompress_backup(self) -> bytes: + """Returns the backup bytes decompressed. :return: The saved GPX file, decompressed. """ - return brotli.decompress(self.gpx_path().read_bytes()) - - def engrave_metadata( - self, - title: Optional[str], - description: Optional[str], - author_name: Optional[str], - time: Optional[datetime.datetime], - *, - gpx: Optional[gpxpy.gpx.GPX] = None, - ): - """Engrave the given metadata into the GPX file. - - Note that this will overwrite all existing metadata in the given - fields. - - If ``None`` is given, it will erase that specific part of the metadata. - - :param title: The title of the track. - :param description: The description of the track. - :param creator: Name of the track's creator. - :param time: Time of the track. - :param gpx: The pre-parsed GPX track, to save time if it is already parsed. - """ - # pylint: disable=too-many-arguments - if gpx is None: - gpx = gpxpy.parse(self.decompress_gpx()) - # First we delete the existing metadata - for track in gpx.tracks: - track.name = None - track.description = None - - # Now we add the new metadata - gpx.author_name = author_name - gpx.name = title - gpx.description = description - gpx.time = time - - self.compress_gpx(util.encode_gpx(gpx)) - - def backup(self): - """Create a backup of the GPX file.""" - shutil.copy(self.gpx_path(), self.backup_path()) + return brotli.decompress(self.backup_path().read_bytes()) def backup_path(self) -> Path: """Path of the GPX backup file. :return: The path of the backup file. """ - return self.path / "track.bck.gpx.br" + return self.path / "track.bck.br" def images(self) -> list[str]: """Returns a list of images that belong to the track. @@ -377,15 +306,18 @@ class TrackDataDir: :return: The ID of the saved image. """ image_dir = self.path / "images" - image_dir.mkdir(parents=True, exist_ok=True) + if self.txn: + self.txn.make_dir(image_dir, exist_ok=True) + else: + image_dir.mkdir(parents=True, exist_ok=True) filename = generate_filename(filename) path = image_dir / filename - with open(path, "wb") as fobj: - shutil.copyfileobj(image, fobj) - - if self.journal is not None: - self.journal.append(("add_image", path)) + if self.txn: + self.txn.write_bytes(path, image.read()) + else: + with open(path, "wb") as fobj: + shutil.copyfileobj(image, fobj) return filename @@ -401,18 +333,36 @@ class TrackDataDir: return path = self.image_path(image_id) - if self.journal is not None: - self.journal.append(("delete_image", path, path.read_bytes())) + if self.txn: + self.txn.unlink(path) + else: + path.unlink() - 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.txn: + self.txn.write_bytes(self.preview_path(), data) + else: + self.preview_path().write_bytes(data) class UserDataDir: """Manager for a single user's data.""" - def __init__(self, user_id: int, path: Path): + def __init__(self, user_id: int, path: Path, *, txn: Transaction | None = None): self.user_id = user_id self.path = path + self.txn = txn def heatmap_path(self) -> Path: """Returns the path for the heatmap tile file. @@ -429,4 +379,51 @@ class UserDataDir: return self.path / "tilehunt.sqlite" -__all__ = ["generate_filename", "DataManager", "TrackDataDir", "UserDataDir"] +class JourneyDataDir: + """Manager for a single journey's data.""" + + def __init__(self, journey_id: int, path: Path, *, txn: Transaction | None = None): + self.journey_id = journey_id + self.path = path + self.txn = txn + + def purge(self): + """Purge all data pertaining to the journey. + + This function logs errors but raises no exception, as such it can + always be used to clean up after a track. + """ + if self.txn: + self.txn.purge(self.path) + else: + if self.path.is_dir(): + shutil.rmtree(self.path, ignore_errors=False, onerror=_log_deletion_error) + + 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.txn: + self.txn.write_bytes(self.preview_path(), data) + else: + self.preview_path().write_bytes(data) + + def remove_preview(self): + """Deletes the preview image.""" + if not self.preview_path().exists(): + return + if self.txn: + self.txn.unlink(self.preview_path()) + else: + self.preview_path().unlink() + + +__all__ = ["generate_filename", "DataManager", "TrackDataDir", "UserDataDir", "JourneyDataDir"] diff --git a/fietsboek/fstrans.py b/fietsboek/fstrans.py new file mode 100644 index 0000000..d402266 --- /dev/null +++ b/fietsboek/fstrans.py @@ -0,0 +1,417 @@ +"""Filesystem transactions. + +Motivation +########## + +Fietsboek does a lot of filesystem stuff, such as saving images and track +backups. Like database actions, we want to ensure that these actions happen +"atomically" -- if an error occurs during one action, we want to undo the +previous ones. Similarly, if an error occurs after things have been sent to the +database/filesystem, we want to ensure that we "clean up" (see `issue 98`_). + +.. _issue 98: https://gitlab.com/dunj3/fietsboek/-/issues/98 + +By having "transactionized" file system actions, we can ensure that we do not +not have such issues: + +* Actions are reversible in case changes need to be rolled back. +* Actions are done all-or-nothing. +* Transactions are combined with other transactions (such as SQLAlchemy); if + one fails, the others will be rolled back. + +Implementation +############## + +The main mechanism is :class:`Transaction`. It provides the commit/abort +interface as required by the transaction_ module, and user-facing methods to +enqueue filesystem modifications. + +.. _transaction: https://github.com/zopefoundation/transaction + +A transaction is started by :func:`begin`, which also joins the transaction +to the other transactions managed by ``transaction``. + +A transaction records :class:`Action`, which represent modifications to be done +to the filesystem. Each action knows how it should be executed. Additionally, +each action records how to undo it -- the :class:`WriteBytes` action, for +example, records the previous file state so it can be restored. + +This implementation has some drawbacks: + +* Modifications are kept in-memory, which might be an issue for larger files. +* Extra time is needed to record previous states (read a file before it is + overwritten). +* Changes are not visible to other programs until they are committed. + +But the advantage is the ease of implementation: Cancelling a transaction just +involves clearing the in-memory buffer, and there are no additional temporary +files needed. + +To further ensure that the filesystem is in a consistent state, the +transactions use a lock file to get exclusive access. Currently, this lock +spans the complete data directory. In the future, more fine-grained locking can +be implemented. + +Usage +##### + +The transaction is automatically used by the +:class:`fietsboek.data.DataManager`. + +Interface +######### +""" + +import enum +import logging +import shutil +import uuid +from pathlib import Path + +import transaction # type: ignore +from filelock import FileLock + +LOGGER = logging.getLogger(__name__) + + +def _log_deletion_error(_, path, exc_info): + LOGGER.warning("Failed to remove %s", path, exc_info=exc_info) + + +class TransactionalError(Exception): + """An exception that occurs when committing filesystem transactions.""" + + +class State(enum.Enum): + """State of the transaction.""" + + OPEN = enum.auto() + """Transaction is open for further actions.""" + + COMMITTING = enum.auto() + """Transaction is in the process of being committed.""" + + COMMITTED = enum.auto() + """Transaction has been committed.""" + + TAINTED = enum.auto() + """Transaction is tainted.""" + + +class Action: + """Base class for any actions that can be applied to the filesystem.""" + + def commit_1(self): + """Commit this action (phase 1). + + This corresponds to ``tpc_vote``, and may raise an exception if + committing should be cancelled. + """ + + def commit_2(self): + """Commit this action (phase 2). + + This corresponds to ``tpc_finish``, and should not raise an exception. + """ + + def undo(self): + """Undo this action. + + This is called if commiting fails, to undo any changes that were + already committed. + """ + + +class WriteBytes(Action): + """Write bytes to the given file.""" + + def __init__(self, path: Path, data: bytes): + self.path = path + self.data = data + self.old: bytes | None = None + + def __repr__(self): + return f"<WriteBytes {len(self.data)} to {self.path}>" + + def commit_1(self): + try: + self.old = self.path.read_bytes() + except FileNotFoundError: + self.old = None + self.path.write_bytes(self.data) + + def undo(self): + if self.old is None: + self.path.unlink() + else: + self.path.write_bytes(self.old) + + +class Unlink(Action): + """Remove the given file.""" + + def __init__(self, path: Path): + self.path = path + self.old: bytes | None = None + + def __repr__(self): + return f"<Unlink {self.path}>" + + def commit_1(self): + self.old = self.path.read_bytes() + self.path.unlink() + + def undo(self): + # This should not happen, unless an exception occurs when we read the + # file + if self.old is not None: + self.path.write_bytes(self.old) + + +class MakeDir(Action): + """Create the given directory.""" + + def __init__(self, path: Path, exist_ok: bool = False): + self.path = path + self.exist_ok = exist_ok + self.existed: bool | None = None + + def __repr__(self): + return f"<MakeDir {self.path}>" + + def commit_1(self): + self.existed = self.path.is_dir() + self.path.mkdir(exist_ok=self.exist_ok) + + def undo(self): + if not self.existed: + self.path.rmdir() + + +class RemoveDir(Action): + """Remove the given (empty) directory.""" + + def __init__(self, path: Path): + self.path = path + + def __repr__(self): + return f"<RemoveDir {self.path}>" + + def commit_1(self): + self.path.rmdir() + + def undo(self): + self.path.mkdir() + + +class Purge(Action): + """Purge (recursively remove) the given directory.""" + + def __init__(self, path: Path): + self.path = path + + def __repr__(self): + return f"<Purge {self.path}>" + + def commit_2(self): + # pylint: disable=deprecated-argument + shutil.rmtree(self.path, ignore_errors=False, onerror=_log_deletion_error) + + +class Transaction: + """A transaction, recording pending filesystem changes.""" + + def __init__(self, lock_path: Path): + self.actions: list[Action] = [] + self.actions_done: list[Action] = [] + self.state = State.OPEN + self.lock_path = lock_path + self.id = uuid.uuid4().hex + + def tpc_begin(self, _trans): + """Begin the transaction. + + This is required by the two-phase commit protocol of ``transaction``. + """ + + def tpc_vote(self, _trans): + """Commit (phase 1) the pending transaction. + + This is required by the two-phase commit protocol of ``transaction``. + + This method may raise exceptions to signal that the transaction (and + all linked transactions) should be aborted. + """ + if not self.actions: + return + + with FileLock(self.lock_path): + self.commit_1() + + def tpc_finish(self, _trans): + """Commit (phase 2) the pending transaction. + + This is required by the two-phase commit protocol of ``transaction``. + + This method should not raise an exception. + """ + if not self.actions: + return + + with FileLock(self.lock_path): + self.commit_2() + + def tpc_abort(self, _trans): + """Abort the transaction, undoing all previously done changes. + + This is required by the two-phase commit protocol of ``transaction``. + """ + if not self.actions_done: + return + + with FileLock(self.lock_path): + self.undo() + + # Needs to conform to transaction API: + # pylint: disable=invalid-name + def sortKey(self): + """Returns the sort key to sort this transaction in relation to others.""" + return f"filesystem:{self.id}" + + def undo(self): + """Undo all actions that have already been applied.""" + # pylint: disable=broad-exception-caught + for action in reversed(self.actions_done): + LOGGER.debug("Undoing %s", action) + try: + action.undo() + except Exception as exc_inner: + # Hide "during the handling of ... another exception occurred" + exc_inner.__context__ = None + LOGGER.exception( + "Exception ignored during rollback of %s", + action, + exc_info=exc_inner, + ) + + def commit(self, _trans): + """Commit this transaction. + + This is required by the interface of ``transaction``. + """ + + def commit_1(self): + """Start the first phase of committing. + + This method is called automatically by the transaction manager. + """ + if self.state != State.OPEN: + raise TransactionalError(f"Transaction is not open but {self.state}") + + self.state = State.COMMITTING + + try: + for action in self.actions: + LOGGER.debug("Executing 1st phase %s", action) + action.commit_1() + self.actions_done.append(action) + except Exception as exc: + LOGGER.debug("Exception while committing") + self.state = State.TAINTED + raise exc + + def commit_2(self): + """Start the second phase of committing. + + This method is called automatically by the transaction manager. + """ + if self.state != State.COMMITTING: + raise TransactionalError(f"Transaction is not committing but {self.state}") + + self.state = State.TAINTED + for action in self.actions: + LOGGER.debug("Executing 2nd phase %s", action) + action.commit_2() + self.state = State.COMMITTED + + def abort(self, _trans=None): + """Abort this transaction.""" + self.actions.clear() + self.state = State.TAINTED + + def write_bytes(self, path: Path, data: bytes): + """Write the given bytes to the given path. + + This is a transactioned version of :meth:`Path.write_bytes`. + + :param path: Path where to write the bytes to. + :param data: The data to write. + """ + if self.state != State.OPEN: + raise TransactionalError(f"Transaction is not open but {self.state}") + self.actions.append(WriteBytes(path, data)) + + def unlink(self, path: Path): + """Unlinks (removes) the given file. + + This is a transactioned version of :math:`Path.unlink`. + + :param path: The path to the file to unlink. + """ + if self.state != State.OPEN: + raise TransactionalError(f"Transaction is not open but {self.state}") + self.actions.append(Unlink(path)) + + def make_dir(self, path: Path, *, exist_ok: bool = False): + """Creates the directory. + + This is a transactioned version of :meth:`Path.mkdir`. + + :param path: The directory to create. + :param exist_ok: If ``True``, no error will be raised if the directory + already exists. + """ + if self.state != State.OPEN: + raise TransactionalError(f"Transaction is not open but {self.state}") + self.actions.append(MakeDir(path, exist_ok=exist_ok)) + + def remove_dir(self, path: Path): + """Removes the (empty) directory. + + This is a transactioned version of :meth:`Path.rmdir`. + + :param path: The directory to remove. + """ + if self.state != State.OPEN: + raise TransactionalError(f"Transaction is not open but {self.state}") + self.actions.append(RemoveDir(path)) + + def purge(self, path: Path): + """Completely remove (recursively) the given path. + + This uses :func:`shutil.rmtree` to delete the path. + + Unlike other actions, this cannot be undone! + + :param path: The directory to remove. + """ + if self.state != State.OPEN: + raise TransactionalError(f"Transaction is not open but {self.state}") + self.actions.append(Purge(path)) + + +def begin(lock_path: Path, tm=None) -> Transaction: + """Begin a new transaction and register it. + + :param lock_path: The path to the lock file to use in order to synchronize + the transaction. + :param tm: The transaction manager from ``transaction`` in which to join + this transaction. + :return: The :class:`Transaction`. + """ + trans = Transaction(lock_path) + if tm: + tm.get().join(trans) + else: + transaction.manager.get().join(trans) + return trans diff --git a/fietsboek/geo.py b/fietsboek/geo.py new file mode 100644 index 0000000..e6abb71 --- /dev/null +++ b/fietsboek/geo.py @@ -0,0 +1,236 @@ +"""This module implements GPS related functionality.""" + +import datetime +import io +from dataclasses import dataclass +from itertools import islice +from math import cos, radians, sin, sqrt + +from . import util + +# WGS-84 equatorial radius, also called the semi-major axis. +# https://en.wikipedia.org/wiki/Earth_radius +EARTH_RADIUS = 6378137.0 +"""Radius of the earth, in meters.""" + +# https://en.wikipedia.org/wiki/Preferred_walking_speed +MOVING_THRESHOLD = 1.1 +"""Speed which is considered to be the moving threshold, in m/s.""" + + +@dataclass +class Waypoint: + """A waypoint, a special landmark marked in the track.""" + + longitude: float + latitude: float + elevation: float | None + name: str | None + description: str | None + + +@dataclass +class MovementData: + """Movement statistics for a path.""" + + # pylint: disable=too-many-instance-attributes + + duration: float = 0.0 + """Duration of the path, in seconds.""" + + moving_duration: float = 0.0 + """Duration spent moving, in seconds.""" + + stopped_duration: float = 0.0 + """Duration spent stopped, in seconds.""" + + length: float = 0.0 + """Length of the path, in meters.""" + + average_speed: float = 0.0 + """Average speed, in m/s.""" + + maximum_speed: float = 0.0 + """Maximum speed, in m/s.""" + + uphill: float = 0.0 + """Uphill elevation, in meters.""" + + downhill: float = 0.0 + """Downhill elevation, in meters.""" + + +@dataclass(slots=True) +class Point: + """A GPS point, represented as longitude/latitude/elevation.""" + + longitude: float + latitude: float + elevation: float + time_offset: float + + def distance(self, other: "Point") -> float: + """Returns the distance between this point and the given other point in + meters. + """ + r_1 = EARTH_RADIUS + self.elevation + r_2 = EARTH_RADIUS + other.elevation + # The formula assumes that 0° is straight upward, but 0° in geo + # coordinates is actually on the equator plane. + t_1 = radians(90 - self.latitude) + t_2 = radians(90 - other.latitude) + p_1 = radians(self.longitude) + p_2 = radians(other.longitude) + # See + # https://en.wikipedia.org/wiki/Spherical_coordinate_system#Distance_in_spherical_coordinates + # While this is not the Haversine formula for distances along the + # circle curvature, it allows us to take the elevation into account, + # and for most GPS point differences that we encounter it should be + # enough. + radicand = ( + r_1**2 + + r_2**2 + - 2 * r_1 * r_2 * (sin(t_1) * sin(t_2) * cos(p_1 - p_2) + cos(t_1) * cos(t_2)) + ) + if radicand < 0.0: + return 0.0 + return sqrt(radicand) + + def flat_distance(self, other: "Point") -> float: + """Returns the distance between this point and the other point in + meters. + + This does not take elevation into account, and only looks at the 2d distance. + """ + r = EARTH_RADIUS + # The formula assumes that 0° is straight upward, but 0° in geo + # coordinates is actually on the equator plane. + t_1 = radians(90 - self.latitude) + t_2 = radians(90 - other.latitude) + p_1 = radians(self.longitude) + p_2 = radians(other.longitude) + # See + # https://en.wikipedia.org/wiki/Spherical_coordinate_system#Distance_in_spherical_coordinates + # While this is not the Haversine formula for distances along the + # circle curvature, it allows us to take the elevation into account, + # and for most GPS point differences that we encounter it should be + # enough. + radicand = 2 * r**2 * (1 - (sin(t_1) * sin(t_2) * cos(p_1 - p_2) + cos(t_1) * cos(t_2))) + if radicand < 0.0: + return 0.0 + return sqrt(radicand) + + +class Path: + """A GPS path, that is a series of GPS points.""" + + # pylint: disable=too-few-public-methods + + def __init__(self, points: list[Point]): + self.points = points + + def _point_pairs(self): + return zip(self.points, islice(self.points, 1, None)) + + def movement_data(self) -> MovementData: + """Returns the movement data.""" + movement_data = MovementData() + for a, b in self._point_pairs(): + distance = a.distance(b) + time = b.time_offset - a.time_offset + if time != 0: + speed = distance / time + else: + speed = 0.0 + elevation = b.elevation - a.elevation + + movement_data.length += distance + if speed >= MOVING_THRESHOLD: + movement_data.moving_duration += time + else: + movement_data.stopped_duration += time + movement_data.maximum_speed = max(movement_data.maximum_speed, speed) + if elevation > 0.0: + movement_data.uphill += elevation + else: + movement_data.downhill += -elevation + movement_data.duration = b.time_offset + + if movement_data.moving_duration > 0: + movement_data.average_speed = movement_data.length / movement_data.moving_duration + else: + movement_data.average_speed = 0.0 + return movement_data + + +def gpx_xml( + title: str | None, + description: str | None, + date: datetime.datetime, + points: list[Point], + waypoints: list[Waypoint], +) -> bytes: + """Returns an XML representation of the given path. + + :param title: The title of the resulting track. + :param description: The description of the resulting track. + :param points: The points that make up this track. + :param waypoints: The waypoints that should be included. + :return: The XML representation (a GPX file). + """ + # This is a cumbersome way to do it, as we're re-implementing XML + # serialization logic. However, recreating the track in gpxpy and + # letting it serialize it is much slower: + # For a track with around 50,000 points, the gpxpy method takes + # ~5.9 seconds here, while the "manual" buffer takes only ~2.4 seconds. + # This is a speed-up we're happy to take! + buf = io.BytesIO() + buf.write(b'<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>') + buf.write(b'<gpx version="1.1" xmlns="http://www.topografix.com/GPX/1/1">') + + buf.write(b"<metadata>") + if title: + buf.write(b"<name>%s</name>" % util.xml_escape(title)) + if description: + buf.write(b"<desc>%s</desc>" % util.xml_escape(description)) + buf.write(b"</metadata>") + + # Cache for fast access + write = buf.write + + write(b"<trk>") + write(b"<trkseg>") + for point in points: + write(b'<trkpt lat="') + write(str(point.latitude).encode("ascii")) + write(b'" lon="') + write(str(point.longitude).encode("ascii")) + write(b'">') + write(b"<ele>") + write(str(point.elevation).encode("ascii")) + write(b"</ele>") + write(b"<time>") + write(str(date + datetime.timedelta(seconds=point.time_offset)).encode("ascii")) + write(b"</time>") + write(b"</trkpt>\n") + write(b"</trkseg>") + write(b"</trk>") + + # This loop is not as hot: + for wpt in waypoints: + write( + b'<wpt lat="%s" lon="%s">' + % (util.xml_escape(str(wpt.latitude)), util.xml_escape(str(wpt.longitude))) + ) + if wpt.elevation is not None: + write(b"<ele>%s</ele>" % util.xml_escape(str(wpt.elevation))) + if wpt.name is not None: + write(b"<name>%s</name>" % util.xml_escape(wpt.name)) + if wpt.description is not None: + write(b"<cmt>%s</cmt>" % util.xml_escape(wpt.description)) + write(b"<desc>%s</desc>" % util.xml_escape(wpt.description)) + write(b"</wpt>") + + write(b"</gpx>") + + return buf.getvalue() diff --git a/fietsboek/hittekaart.py b/fietsboek/hittekaart.py index 15f2855..8a48d60 100644 --- a/fietsboek/hittekaart.py +++ b/fietsboek/hittekaart.py @@ -6,21 +6,25 @@ import enum import logging import shutil -import subprocess import tempfile from pathlib import Path -from typing import Optional +try: + import hittekaart_py +except ImportError: + pass from sqlalchemy import select from sqlalchemy.orm import aliased from sqlalchemy.orm.session import Session -from . import models +from . import geo, models from .data import DataManager from .models.track import TrackType LOGGER = logging.getLogger(__name__) +TILEHUNTER_ZOOM = 14 + class Mode(enum.Enum): """Heatmap generation mode. @@ -36,9 +40,8 @@ class Mode(enum.Enum): def generate( output: Path, mode: Mode, - input_files: list[Path], + input_files: list[geo.Path], *, - exe_path: Optional[Path] = None, threads: int = 0, ): """Calls hittekaart with the given arguments. @@ -46,14 +49,32 @@ def generate( :param output: Output filename. Note that this function always uses the sqlite output mode. :param mode: What to generate. - :param input_files: List of paths to the input files. - :param exe_path: Path to the hittekaart binary. If not given, - ``hittekaart`` is searched in the path. + :param input_files: List of input paths. :param threads: Number of threads that ``hittekaart`` should use. Defaults to 0, which uses all available cores. """ + try: + hittekaart_py + except NameError: + raise RuntimeError("hittekaart not available") from None + if not input_files: return + + renderer: hittekaart_py.HeatmapRenderer | hittekaart_py.TilehuntRenderer + if mode == Mode.HEATMAP: + renderer = hittekaart_py.HeatmapRenderer() + elif mode == Mode.TILEHUNTER: + renderer = hittekaart_py.TilehuntRenderer(TILEHUNTER_ZOOM) + + LOGGER.debug("Loading tracks ...") + tracks = [ + hittekaart_py.Track.from_coordinates( + [(point.longitude, point.latitude) for point in input_file.points] + ) + for input_file in input_files + ] + LOGGER.debug("Tracks loaded!") # There are two reasons why we do the tempfile dance: # 1. hittekaart refuses to overwrite existing files # 2. This way we can (hope for?) an atomic move (at least if temporary file @@ -61,23 +82,12 @@ def generate( # this, but for now, it's alright. with tempfile.TemporaryDirectory() as tempdir: tmpfile = Path(tempdir) / "hittekaart.sqlite" - binary = str(exe_path) if exe_path else "hittekaart" - cmdline = [ - binary, - "--sqlite", - "-o", - str(tmpfile), - "-m", - mode.value, - "-t", - str(threads), - "--", - ] - cmdline.extend(map(str, input_files)) - LOGGER.debug("Running %r", cmdline) - subprocess.run(cmdline, check=True, stdout=subprocess.DEVNULL) - - LOGGER.debug("Moving temporary file") + sink = hittekaart_py.Storage.OsmAnd(bytes(tmpfile)) + settings = hittekaart_py.Settings(threads=threads) + LOGGER.debug("Running hittekaart (renderer %r) to %r", renderer, tmpfile) + hittekaart_py.generate(settings, tracks, renderer, sink) + + LOGGER.debug("Moving temporary file %r to %r", tmpfile, output) shutil.move(tmpfile, output) @@ -87,7 +97,6 @@ def generate_for( data_manager: DataManager, mode: Mode, *, - exe_path: Optional[Path] = None, threads: int = 0, ): """Uses :meth:`generate` to generate a heatmap for the given user. @@ -104,7 +113,6 @@ def generate_for( :param dbsession: The database session. :param data_manager: The data manager. :param mode: The mode of the heatmap. - :param exe_path: See :meth:`generate`. :param threads: See :meth:`generate`. """ # pylint: disable=too-many-arguments @@ -117,8 +125,7 @@ def generate_for( for track in dbsession.execute(query).scalars(): if track.id is None: continue - path = data_manager.open(track.id).gpx_path() - input_paths.append(path) + input_paths.append(track.path()) if not input_paths: return @@ -133,7 +140,7 @@ def generate_for( Mode.TILEHUNTER: user_dir.tilehunt_path(), } - generate(output_paths[mode], mode, input_paths, exe_path=exe_path, threads=threads) + generate(output_paths[mode], mode, input_paths, threads=threads) __all__ = ["Mode", "generate", "generate_for"] diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.mo b/fietsboek/locale/de/LC_MESSAGES/messages.mo Binary files differindex 9de152c..356be25 100644 --- a/fietsboek/locale/de/LC_MESSAGES/messages.mo +++ b/fietsboek/locale/de/LC_MESSAGES/messages.mo diff --git a/fietsboek/locale/de/LC_MESSAGES/messages.po b/fietsboek/locale/de/LC_MESSAGES/messages.po index ccb46a3..9367d28 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: 2026-01-03 19:25+0100\n" "PO-Revision-Date: 2022-07-02 17:35+0200\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language: de\n" @@ -16,87 +16,225 @@ 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:266 msgid "email.verify_mail.subject" msgstr "Fietsboek Konto Bestätigung" -#: fietsboek/actions.py:270 +#: fietsboek/actions.py:269 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/pdf.py:233 +msgid "pdf.table.date" +msgstr "Datum" + +#: fietsboek/pdf.py:235 +msgid "pdf.table.length" +msgstr "Länge" + +#: fietsboek/pdf.py:239 +msgid "pdf.table.uphill" +msgstr "Bergauf" + +#: fietsboek/pdf.py:243 +msgid "pdf.table.downhill" +msgstr "Bergab" + +#: fietsboek/pdf.py:246 +msgid "pdf.table.moving_time" +msgstr "Fahrzeit" + +#: fietsboek/pdf.py:247 +msgid "pdf.table.stopped_time" +msgstr "Haltezeit" + +#: fietsboek/pdf.py:249 +msgid "pdf.table.max_speed" +msgstr "Maximalgeschwindigkeit" + +#: fietsboek/pdf.py:253 +msgid "pdf.table.avg_speed" +msgstr "Durchschnittsgeschwindigkeit" + +#: fietsboek/util.py:299 msgid "password_constraint.mismatch" msgstr "Passwörter stimmen nicht überein" -#: fietsboek/util.py:335 +#: fietsboek/util.py:301 msgid "password_constraint.length" msgstr "Passwort zu kurz" -#: fietsboek/models/track.py:603 +#: fietsboek/models/track.py:774 msgid "tooltip.table.length" msgstr "Länge" -#: fietsboek/models/track.py:604 +#: fietsboek/models/track.py:775 msgid "tooltip.table.people" msgstr "# Personen" -#: fietsboek/models/track.py:605 +#: fietsboek/models/track.py:776 msgid "tooltip.table.uphill" msgstr "Bergauf" -#: fietsboek/models/track.py:606 +#: fietsboek/models/track.py:777 msgid "tooltip.table.downhill" msgstr "Bergab" -#: fietsboek/models/track.py:607 fietsboek/templates/home.jinja2:7 +#: fietsboek/models/track.py:778 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:779 fietsboek/templates/home.jinja2:8 msgid "tooltip.table.stopped_time" msgstr "Haltezeit" -#: fietsboek/models/track.py:610 +#: fietsboek/models/track.py:781 msgid "tooltip.table.max_speed" msgstr "Maximalgeschwindigkeit" -#: fietsboek/models/track.py:614 +#: fietsboek/models/track.py:785 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.track_data" +msgstr "Streckendaten" + +#: fietsboek/templates/admin_overview.jinja2:56 +msgid "admin.overview.storage_graph.label.backups" +msgstr "Sicherungskopien" + +#: fietsboek/templates/admin_overview.jinja2:57 +msgid "admin.overview.storage_graph.label.images" +msgstr "Bilder" + +#: fietsboek/templates/admin_overview.jinja2:58 +msgid "admin.overview.storage_graph.label.track_previews" +msgstr "Streckenvoransichten" + +#: fietsboek/templates/admin_overview.jinja2:59 +msgid "admin.overview.storage_graph.label.journey_previews" +msgstr "Reisenvoransichten" + +#: fietsboek/templates/admin_overview.jinja2:60 +msgid "admin.overview.storage_graph.label.user_maps" +msgstr "Nutzerkarten" + +#: fietsboek/templates/admin_overview.jinja2:88 +msgid "admin.overview.storage_graph.title" +msgstr "Speicherübersicht" + #: fietsboek/templates/browse.jinja2:4 msgid "page.browse.title" msgstr "Stöbern" @@ -193,73 +331,100 @@ 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:127 +#: fietsboek/templates/journey_details.jinja2:124 +#: 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:141 +#: fietsboek/templates/journey_details.jinja2:76 +#: fietsboek/templates/journey_details.jinja2:126 +#: 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:132 +#: fietsboek/templates/journey_details.jinja2:131 +#: 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:136 +#: fietsboek/templates/journey_details.jinja2:133 +#: 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:145 +#: fietsboek/templates/journey_details.jinja2:80 +#: fietsboek/templates/journey_details.jinja2:138 +#: 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:149 +#: fietsboek/templates/journey_details.jinja2:84 +#: fietsboek/templates/journey_details.jinja2:140 +#: 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:154 +#: fietsboek/templates/journey_details.jinja2:88 +#: fietsboek/templates/journey_details.jinja2:145 +#: 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:158 +#: fietsboek/templates/journey_details.jinja2:92 +#: fietsboek/templates/journey_details.jinja2:147 +#: 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:162 +#: fietsboek/templates/journey_details.jinja2:96 +#: fietsboek/templates/journey_details.jinja2:151 +#: 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:166 +#: fietsboek/templates/journey_details.jinja2:100 +#: fietsboek/templates/journey_details.jinja2:153 +#: fietsboek/templates/profile_overview.jinja2:46 msgid "page.details.avg_speed" msgstr "durchschnittliche Geschwindigkeit" -#: fietsboek/templates/browse.jinja2:192 +#: fietsboek/templates/browse.jinja2:196 +#: fietsboek/templates/journey_details.jinja2:158 msgid "page.browse.card.comments" msgstr "Kommentare" -#: fietsboek/templates/browse.jinja2:194 +#: fietsboek/templates/browse.jinja2:198 +#: fietsboek/templates/journey_details.jinja2:160 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, " @@ -305,90 +470,95 @@ msgstr "Passwort wiederholen" msgid "page.create_account.create" msgstr "Erstellen" -#: fietsboek/templates/details.jinja2:7 +#: fietsboek/templates/details.jinja2:24 msgid "page.details.title" msgstr "Details" -#: fietsboek/templates/details.jinja2:20 +#: fietsboek/templates/details.jinja2:37 msgid "page.details.edit" msgstr "Bearbeiten" -#: fietsboek/templates/details.jinja2:21 +#: fietsboek/templates/details.jinja2:38 msgid "page.details.share" msgstr "Teilen" -#: fietsboek/templates/details.jinja2:22 +#: fietsboek/templates/details.jinja2:39 msgid "page.details.delete" msgstr "Löschen" -#: fietsboek/templates/details.jinja2:28 +#: fietsboek/templates/details.jinja2:45 msgid "page.details.sharelink.title" msgstr "Link zum Teilen" -#: fietsboek/templates/details.jinja2:32 +#: fietsboek/templates/details.jinja2:49 msgid "page.details.sharelink.info" msgstr "Jeder mit Zugang zu diesem Link kann die Strecke ansehen!" -#: fietsboek/templates/details.jinja2:39 +#: fietsboek/templates/details.jinja2:56 msgid "page.details.sharelink.invalidate" msgstr "Link invalidieren" -#: fietsboek/templates/details.jinja2:41 +#: fietsboek/templates/details.jinja2:58 msgid "page.details.sharelink.close" msgstr "Schließen" -#: fietsboek/templates/details.jinja2:51 +#: fietsboek/templates/details.jinja2:68 msgid "page.details.delete.title" msgstr "Strecke Löschen" -#: fietsboek/templates/details.jinja2:55 +#: fietsboek/templates/details.jinja2:72 msgid "page.details.delete.info" msgstr "Das Löschen der Strecke wird alle damit verbundenen Informationen löschen!" -#: fietsboek/templates/details.jinja2:60 +#: fietsboek/templates/details.jinja2:77 msgid "page.details.delete.delete" msgstr "Löschen" -#: fietsboek/templates/details.jinja2:62 +#: fietsboek/templates/details.jinja2:79 msgid "page.details.delete.close" msgstr "Abbrechen" -#: fietsboek/templates/details.jinja2:81 +#: fietsboek/templates/details.jinja2:98 msgid "page.details.tags" msgstr "Schlagwörter" -#: fietsboek/templates/details.jinja2:91 fietsboek/templates/edit.jinja2:10 +#: fietsboek/templates/details.jinja2:108 fietsboek/templates/edit.jinja2:10 #: fietsboek/templates/finish_upload.jinja2:10 +#: fietsboek/templates/journey_details.jinja2:66 msgid "page.noscript" msgstr "" "JavaScript ist deaktiviert, zum Nutzen aller Funktionen bitte JavaScript " "aktivieren" -#: fietsboek/templates/details.jinja2:97 +#: fietsboek/templates/details.jinja2:115 msgid "page.details.download" msgstr "Herunterladen" -#: fietsboek/templates/details.jinja2:187 +#: fietsboek/templates/details.jinja2:120 +msgid "page.details.download_pdf" +msgstr "PDF Übersicht" + +#: fietsboek/templates/details.jinja2:211 msgid "page.details.comments" msgstr "Kommentare" -#: fietsboek/templates/details.jinja2:191 +#: fietsboek/templates/details.jinja2:215 msgid "page.details.comments.author" msgstr "Kommentar von {}" -#: fietsboek/templates/details.jinja2:208 +#: fietsboek/templates/details.jinja2:232 msgid "page.details.comments.new.title" msgstr "Kommentar erstellen" -#: fietsboek/templates/details.jinja2:211 +#: fietsboek/templates/details.jinja2:235 msgid "page.details.comments.new.input_title" msgstr "Titel" -#: fietsboek/templates/details.jinja2:212 +#: fietsboek/templates/details.jinja2:236 msgid "page.details.comments.new.input_comment" msgstr "Kommentar" -#: fietsboek/templates/details.jinja2:215 +#: fietsboek/templates/details.jinja2:239 msgid "page.details.comments.new.submit" msgstr "Absenden" @@ -396,11 +566,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,61 +718,177 @@ 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" -#: fietsboek/templates/layout.jinja2:43 +#: fietsboek/templates/journey_details.jinja2:10 +msgid "journey.edit" +msgstr "Bearbeiten" + +#: fietsboek/templates/journey_details.jinja2:11 +msgid "journey.share" +msgstr "Teilen" + +#: fietsboek/templates/journey_details.jinja2:12 +msgid "journey.delete" +msgstr "Löschen" + +#: fietsboek/templates/journey_details.jinja2:18 +msgid "journey.sharelink.title" +msgstr "Link zum Teilen" + +#: fietsboek/templates/journey_details.jinja2:22 +msgid "journey.sharelink.info" +msgstr "Jeder mit Zugang zu diesem Link kann die Reise ansehen!" + +#: fietsboek/templates/journey_details.jinja2:29 +msgid "journey.sharelink.invalidate" +msgstr "Link invalidieren" + +#: fietsboek/templates/journey_details.jinja2:31 +msgid "journey.sharelink.close" +msgstr "Schließen" + +#: fietsboek/templates/journey_details.jinja2:41 +msgid "journey.delete.title" +msgstr "Reise Löschen" + +#: fietsboek/templates/journey_details.jinja2:45 +msgid "journey.delete.info" +msgstr "Das Löschen der Reise wird die einzelnen Strecken nicht löschen." + +#: fietsboek/templates/journey_details.jinja2:50 +msgid "journey.delete.delete" +msgstr "Löschen" + +#: fietsboek/templates/journey_details.jinja2:52 +msgid "journey.delete.close" +msgstr "Abbrechen" + +#: fietsboek/templates/journey_details.jinja2:108 +msgid "journey.tracks" +msgstr "Strecken" + +#: fietsboek/templates/journey_details.jinja2:174 +msgid "journeys.track.hidden" +msgstr "Du hast nicht die Rechte, diese Strecke zu sehen. Sie ist versteckt." + +#: fietsboek/templates/journey_form.jinja2:40 +msgid "journeys.new.form.title" +msgstr "Titel" + +#: fietsboek/templates/journey_form.jinja2:43 +msgid "journeys.new.form.requires_title" +msgstr "Ein Titel wird benötigt" + +#: fietsboek/templates/journey_form.jinja2:47 +msgid "journeys.new.form.description" +msgstr "Beschreibung" + +#: fietsboek/templates/journey_form.jinja2:51 +msgid "journeys.new.form.visibility" +msgstr "Sichtbarkeit" + +#: fietsboek/templates/journey_form.jinja2:54 +msgid "journeys.new.form.visibility.private" +msgstr "Privat" + +#: fietsboek/templates/journey_form.jinja2:55 +msgid "journeys.new.form.visibility.friends" +msgstr "Nur Freunde" + +#: fietsboek/templates/journey_form.jinja2:56 +msgid "journeys.new.form.visibility.logged_in" +msgstr "Angemeldete Nutzer" + +#: fietsboek/templates/journey_form.jinja2:57 +msgid "journeys.new.form.visibility.public" +msgstr "Öffentlich" + +#: fietsboek/templates/journey_form.jinja2:62 +msgid "journeys.new.form.tracksearch" +msgstr "Nach Strecken suchen" + +#: fietsboek/templates/journey_form.jinja2:71 +msgid "journeys.new.form.tracks" +msgstr "Strecken (ziehen zum Ordnen)" + +#: fietsboek/templates/journey_form.jinja2:90 +msgid "journeys.new.form.submit" +msgstr "Speichern" + +#: fietsboek/templates/journey_form.jinja2:93 +msgid "journeys.new.form.requires_tracks" +msgstr "Es muss mindestens eine Strecke vorhanden sein" + +#: fietsboek/templates/journey_list.jinja2:4 +msgid "journeys.overview.title" +msgstr "Reisen" + +#: fietsboek/templates/journey_list.jinja2:10 +msgid "journeys.overview.new" +msgstr "Neue Reise" + +#: fietsboek/templates/journey_new.jinja2:10 +msgid "journeys.new.title" +msgstr "Neue Reise" + +#: fietsboek/templates/layout.jinja2:44 msgid "page.navbar.toggle" msgstr "Navigation umschalten" -#: fietsboek/templates/layout.jinja2:54 +#: fietsboek/templates/layout.jinja2:55 msgid "page.navbar.home" msgstr "Startseite" -#: fietsboek/templates/layout.jinja2:57 +#: fietsboek/templates/layout.jinja2:58 msgid "page.navbar.browse" msgstr "Stöbern" #: fietsboek/templates/layout.jinja2:61 +msgid "page.navbar.journeys" +msgstr "Reisen" + +#: fietsboek/templates/layout.jinja2:65 msgid "page.navbar.upload" msgstr "Hochladen" -#: fietsboek/templates/layout.jinja2:70 +#: fietsboek/templates/layout.jinja2:74 msgid "page.navbar.user" msgstr "Nutzer" -#: fietsboek/templates/layout.jinja2:74 +#: fietsboek/templates/layout.jinja2:78 msgid "page.navbar.welcome_user" msgstr "Willkommen, {}!" -#: fietsboek/templates/layout.jinja2:77 +#: fietsboek/templates/layout.jinja2:81 msgid "page.navbar.logout" msgstr "Abmelden" -#: fietsboek/templates/layout.jinja2:80 +#: fietsboek/templates/layout.jinja2:84 msgid "page.navbar.profile" msgstr "Profil" -#: fietsboek/templates/layout.jinja2:83 +#: fietsboek/templates/layout.jinja2:87 msgid "page.navbar.user_data" msgstr "Persönliche Daten" -#: fietsboek/templates/layout.jinja2:87 +#: fietsboek/templates/layout.jinja2:91 msgid "page.navbar.admin" msgstr "Admin" -#: fietsboek/templates/layout.jinja2:93 +#: fietsboek/templates/layout.jinja2:97 msgid "page.navbar.login" msgstr "Anmelden" -#: fietsboek/templates/layout.jinja2:97 +#: fietsboek/templates/layout.jinja2:101 msgid "page.navbar.create_account" msgstr "Konto Erstellen" @@ -654,91 +944,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" @@ -866,11 +1156,11 @@ msgstr "" "Diese Transformation passt die Höhenangabe für Punkte an, bei denen die " "Höhenangabe fehlt." -#: fietsboek/transformers/elevation.py:116 +#: fietsboek/transformers/elevation.py:109 msgid "transformers.fix-elevation-jumps" msgstr "Höhensprünge beheben" -#: fietsboek/transformers/elevation.py:120 +#: fietsboek/transformers/elevation.py:113 msgid "transformers.fix-elevation-jumps.description" msgstr "" "Diese Transformation passt die Höhenangabe für Punkte an, bei denen die " @@ -888,15 +1178,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:189 msgid "flash.badge_added" msgstr "Wappen hinzugefügt" -#: fietsboek/views/admin.py:73 +#: fietsboek/views/admin.py:213 msgid "flash.badge_modified" msgstr "Wappen bearbeitet" -#: fietsboek/views/admin.py:93 +#: fietsboek/views/admin.py:233 msgid "flash.badge_deleted" msgstr "Wappen gelöscht" @@ -956,23 +1246,27 @@ msgstr "E-Mail-Adresse bestätigt" msgid "flash.password_updated" msgstr "Passwort aktualisiert" -#: fietsboek/views/detail.py:162 +#: fietsboek/views/detail.py:187 msgid "flash.track_deleted" msgstr "Strecke gelöscht" +#: fietsboek/views/edit.py:97 fietsboek/views/upload.py:63 +msgid "flash.invalid_file" +msgstr "Ungültige GPX-Datei gesendet" + +#: fietsboek/views/journey.py:251 +msgid "flash.journey_deleted" +msgstr "Reise gelöscht" + #: fietsboek/views/upload.py:53 msgid "flash.no_file_selected" msgstr "Keine Datei ausgewählt" -#: fietsboek/views/upload.py:66 -msgid "flash.invalid_file" -msgstr "Ungültige GPX-Datei gesendet" - -#: fietsboek/views/upload.py:192 +#: fietsboek/views/upload.py:177 msgid "flash.upload_success" msgstr "Hochladen erfolgreich" -#: fietsboek/views/upload.py:211 +#: fietsboek/views/upload.py:196 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 Binary files differindex 5f8edc6..f4b7bbf 100644 --- a/fietsboek/locale/en/LC_MESSAGES/messages.mo +++ b/fietsboek/locale/en/LC_MESSAGES/messages.mo diff --git a/fietsboek/locale/en/LC_MESSAGES/messages.po b/fietsboek/locale/en/LC_MESSAGES/messages.po index 981d134..d52960b 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: 2026-01-03 19:25+0100\n" "PO-Revision-Date: 2023-04-03 20:42+0200\n" "Last-Translator: \n" "Language: en\n" @@ -16,87 +16,225 @@ 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:266 msgid "email.verify_mail.subject" msgstr "Fietsboek Account Verification" -#: fietsboek/actions.py:270 +#: fietsboek/actions.py:269 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/pdf.py:233 +msgid "pdf.table.date" +msgstr "Date" + +#: fietsboek/pdf.py:235 +msgid "pdf.table.length" +msgstr "Length" + +#: fietsboek/pdf.py:239 +msgid "pdf.table.uphill" +msgstr "Uphill" + +#: fietsboek/pdf.py:243 +msgid "pdf.table.downhill" +msgstr "Downhill" + +#: fietsboek/pdf.py:246 +msgid "pdf.table.moving_time" +msgstr "Moving Time" + +#: fietsboek/pdf.py:247 +msgid "pdf.table.stopped_time" +msgstr "Stopped Time" + +#: fietsboek/pdf.py:249 +msgid "pdf.table.max_speed" +msgstr "Max Speed" + +#: fietsboek/pdf.py:253 +msgid "pdf.table.avg_speed" +msgstr "Average Speed" + +#: fietsboek/util.py:299 msgid "password_constraint.mismatch" msgstr "Passwords don't match" -#: fietsboek/util.py:335 +#: fietsboek/util.py:301 msgid "password_constraint.length" msgstr "Password not long enough" -#: fietsboek/models/track.py:603 +#: fietsboek/models/track.py:774 msgid "tooltip.table.length" msgstr "Length" -#: fietsboek/models/track.py:604 +#: fietsboek/models/track.py:775 msgid "tooltip.table.people" msgstr "# People" -#: fietsboek/models/track.py:605 +#: fietsboek/models/track.py:776 msgid "tooltip.table.uphill" msgstr "Uphill" -#: fietsboek/models/track.py:606 +#: fietsboek/models/track.py:777 msgid "tooltip.table.downhill" msgstr "Downhill" -#: fietsboek/models/track.py:607 fietsboek/templates/home.jinja2:7 +#: fietsboek/models/track.py:778 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:779 fietsboek/templates/home.jinja2:8 msgid "tooltip.table.stopped_time" msgstr "Stopped Time" -#: fietsboek/models/track.py:610 +#: fietsboek/models/track.py:781 msgid "tooltip.table.max_speed" msgstr "Max Speed" -#: fietsboek/models/track.py:614 +#: fietsboek/models/track.py:785 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.track_data" +msgstr "Track data" + +#: fietsboek/templates/admin_overview.jinja2:56 +msgid "admin.overview.storage_graph.label.backups" +msgstr "File backups" + +#: fietsboek/templates/admin_overview.jinja2:57 +msgid "admin.overview.storage_graph.label.images" +msgstr "Images" + +#: fietsboek/templates/admin_overview.jinja2:58 +msgid "admin.overview.storage_graph.label.track_previews" +msgstr "Track previews" + +#: fietsboek/templates/admin_overview.jinja2:59 +msgid "admin.overview.storage_graph.label.journey_previews" +msgstr "Journey previews" + +#: fietsboek/templates/admin_overview.jinja2:60 +msgid "admin.overview.storage_graph.label.user_maps" +msgstr "User maps" + +#: fietsboek/templates/admin_overview.jinja2:88 +msgid "admin.overview.storage_graph.title" +msgstr "Storage breakdown" + #: fietsboek/templates/browse.jinja2:4 msgid "page.browse.title" msgstr "Browse" @@ -193,73 +331,100 @@ 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:127 +#: fietsboek/templates/journey_details.jinja2:124 +#: 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:141 +#: fietsboek/templates/journey_details.jinja2:76 +#: fietsboek/templates/journey_details.jinja2:126 +#: 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:132 +#: fietsboek/templates/journey_details.jinja2:131 +#: 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:136 +#: fietsboek/templates/journey_details.jinja2:133 +#: 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:145 +#: fietsboek/templates/journey_details.jinja2:80 +#: fietsboek/templates/journey_details.jinja2:138 +#: 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:149 +#: fietsboek/templates/journey_details.jinja2:84 +#: fietsboek/templates/journey_details.jinja2:140 +#: 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:154 +#: fietsboek/templates/journey_details.jinja2:88 +#: fietsboek/templates/journey_details.jinja2:145 +#: 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:158 +#: fietsboek/templates/journey_details.jinja2:92 +#: fietsboek/templates/journey_details.jinja2:147 +#: 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:162 +#: fietsboek/templates/journey_details.jinja2:96 +#: fietsboek/templates/journey_details.jinja2:151 +#: 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:166 +#: fietsboek/templates/journey_details.jinja2:100 +#: fietsboek/templates/journey_details.jinja2:153 +#: fietsboek/templates/profile_overview.jinja2:46 msgid "page.details.avg_speed" msgstr "Average Speed" -#: fietsboek/templates/browse.jinja2:192 +#: fietsboek/templates/browse.jinja2:196 +#: fietsboek/templates/journey_details.jinja2:158 msgid "page.browse.card.comments" msgstr "Comments" -#: fietsboek/templates/browse.jinja2:194 +#: fietsboek/templates/browse.jinja2:198 +#: fietsboek/templates/journey_details.jinja2:160 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." @@ -303,88 +468,93 @@ msgstr "Repeat password" msgid "page.create_account.create" msgstr "Create" -#: fietsboek/templates/details.jinja2:7 +#: fietsboek/templates/details.jinja2:24 msgid "page.details.title" msgstr "Track Details" -#: fietsboek/templates/details.jinja2:20 +#: fietsboek/templates/details.jinja2:37 msgid "page.details.edit" msgstr "Edit" -#: fietsboek/templates/details.jinja2:21 +#: fietsboek/templates/details.jinja2:38 msgid "page.details.share" msgstr "Share" -#: fietsboek/templates/details.jinja2:22 +#: fietsboek/templates/details.jinja2:39 msgid "page.details.delete" msgstr "Delete" -#: fietsboek/templates/details.jinja2:28 +#: fietsboek/templates/details.jinja2:45 msgid "page.details.sharelink.title" msgstr "Share Link" -#: fietsboek/templates/details.jinja2:32 +#: fietsboek/templates/details.jinja2:49 msgid "page.details.sharelink.info" msgstr "Everyone with access to this link can view the track!" -#: fietsboek/templates/details.jinja2:39 +#: fietsboek/templates/details.jinja2:56 msgid "page.details.sharelink.invalidate" msgstr "Invalidate link" -#: fietsboek/templates/details.jinja2:41 +#: fietsboek/templates/details.jinja2:58 msgid "page.details.sharelink.close" msgstr "Close" -#: fietsboek/templates/details.jinja2:51 +#: fietsboek/templates/details.jinja2:68 msgid "page.details.delete.title" msgstr "Delete Track" -#: fietsboek/templates/details.jinja2:55 +#: fietsboek/templates/details.jinja2:72 msgid "page.details.delete.info" msgstr "Deleting this track will remove all associated information with it!" -#: fietsboek/templates/details.jinja2:60 +#: fietsboek/templates/details.jinja2:77 msgid "page.details.delete.delete" msgstr "Delete" -#: fietsboek/templates/details.jinja2:62 +#: fietsboek/templates/details.jinja2:79 msgid "page.details.delete.close" msgstr "Abort" -#: fietsboek/templates/details.jinja2:81 +#: fietsboek/templates/details.jinja2:98 msgid "page.details.tags" msgstr "Tagged as" -#: fietsboek/templates/details.jinja2:91 fietsboek/templates/edit.jinja2:10 +#: fietsboek/templates/details.jinja2:108 fietsboek/templates/edit.jinja2:10 #: fietsboek/templates/finish_upload.jinja2:10 +#: fietsboek/templates/journey_details.jinja2:66 msgid "page.noscript" msgstr "JavaScript is disabled, please enable JavaScript" -#: fietsboek/templates/details.jinja2:97 +#: fietsboek/templates/details.jinja2:115 msgid "page.details.download" msgstr "Download Tour" -#: fietsboek/templates/details.jinja2:187 +#: fietsboek/templates/details.jinja2:120 +msgid "page.details.download_pdf" +msgstr "PDF overview" + +#: fietsboek/templates/details.jinja2:211 msgid "page.details.comments" msgstr "Comments" -#: fietsboek/templates/details.jinja2:191 +#: fietsboek/templates/details.jinja2:215 msgid "page.details.comments.author" msgstr "Comment by {}" -#: fietsboek/templates/details.jinja2:208 +#: fietsboek/templates/details.jinja2:232 msgid "page.details.comments.new.title" msgstr "Create a new comment" -#: fietsboek/templates/details.jinja2:211 +#: fietsboek/templates/details.jinja2:235 msgid "page.details.comments.new.input_title" msgstr "Title" -#: fietsboek/templates/details.jinja2:212 +#: fietsboek/templates/details.jinja2:236 msgid "page.details.comments.new.input_comment" msgstr "Comment" -#: fietsboek/templates/details.jinja2:215 +#: fietsboek/templates/details.jinja2:239 msgid "page.details.comments.new.submit" msgstr "Submit" @@ -392,11 +562,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,61 +712,177 @@ 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" -#: fietsboek/templates/layout.jinja2:43 +#: fietsboek/templates/journey_details.jinja2:10 +msgid "journey.edit" +msgstr "Edit" + +#: fietsboek/templates/journey_details.jinja2:11 +msgid "journey.share" +msgstr "Share" + +#: fietsboek/templates/journey_details.jinja2:12 +msgid "journey.delete" +msgstr "Delete" + +#: fietsboek/templates/journey_details.jinja2:18 +msgid "journey.sharelink.title" +msgstr "Share Link" + +#: fietsboek/templates/journey_details.jinja2:22 +msgid "journey.sharelink.info" +msgstr "Everyone with access to this link can view the journey!" + +#: fietsboek/templates/journey_details.jinja2:29 +msgid "journey.sharelink.invalidate" +msgstr "Invalidate link" + +#: fietsboek/templates/journey_details.jinja2:31 +msgid "journey.sharelink.close" +msgstr "Close" + +#: fietsboek/templates/journey_details.jinja2:41 +msgid "journey.delete.title" +msgstr "Delete Journey" + +#: fietsboek/templates/journey_details.jinja2:45 +msgid "journey.delete.info" +msgstr "Deleting this journey will not remove the individual tracks." + +#: fietsboek/templates/journey_details.jinja2:50 +msgid "journey.delete.delete" +msgstr "Delete" + +#: fietsboek/templates/journey_details.jinja2:52 +msgid "journey.delete.close" +msgstr "Abort" + +#: fietsboek/templates/journey_details.jinja2:108 +msgid "journey.tracks" +msgstr "Tracks" + +#: fietsboek/templates/journey_details.jinja2:174 +msgid "journeys.track.hidden" +msgstr "This track is hidden, you don't have permission to view it." + +#: fietsboek/templates/journey_form.jinja2:40 +msgid "journeys.new.form.title" +msgstr "Title" + +#: fietsboek/templates/journey_form.jinja2:43 +msgid "journeys.new.form.requires_title" +msgstr "A title is required" + +#: fietsboek/templates/journey_form.jinja2:47 +msgid "journeys.new.form.description" +msgstr "Description" + +#: fietsboek/templates/journey_form.jinja2:51 +msgid "journeys.new.form.visibility" +msgstr "Visibility" + +#: fietsboek/templates/journey_form.jinja2:54 +msgid "journeys.new.form.visibility.private" +msgstr "Private" + +#: fietsboek/templates/journey_form.jinja2:55 +msgid "journeys.new.form.visibility.friends" +msgstr "Friends only" + +#: fietsboek/templates/journey_form.jinja2:56 +msgid "journeys.new.form.visibility.logged_in" +msgstr "Logged in users" + +#: fietsboek/templates/journey_form.jinja2:57 +msgid "journeys.new.form.visibility.public" +msgstr "Public" + +#: fietsboek/templates/journey_form.jinja2:62 +msgid "journeys.new.form.tracksearch" +msgstr "Search for tracks" + +#: fietsboek/templates/journey_form.jinja2:71 +msgid "journeys.new.form.tracks" +msgstr "Tracks (drag to re-order)" + +#: fietsboek/templates/journey_form.jinja2:90 +msgid "journeys.new.form.submit" +msgstr "Save" + +#: fietsboek/templates/journey_form.jinja2:93 +msgid "journeys.new.form.requires_tracks" +msgstr "A journey must have at least one track" + +#: fietsboek/templates/journey_list.jinja2:4 +msgid "journeys.overview.title" +msgstr "Journeys" + +#: fietsboek/templates/journey_list.jinja2:10 +msgid "journeys.overview.new" +msgstr "New journey" + +#: fietsboek/templates/journey_new.jinja2:10 +msgid "journeys.new.title" +msgstr "New Journey" + +#: fietsboek/templates/layout.jinja2:44 msgid "page.navbar.toggle" msgstr "Toggle navigation" -#: fietsboek/templates/layout.jinja2:54 +#: fietsboek/templates/layout.jinja2:55 msgid "page.navbar.home" msgstr "Home" -#: fietsboek/templates/layout.jinja2:57 +#: fietsboek/templates/layout.jinja2:58 msgid "page.navbar.browse" msgstr "Browse" #: fietsboek/templates/layout.jinja2:61 +msgid "page.navbar.journeys" +msgstr "Journeys" + +#: fietsboek/templates/layout.jinja2:65 msgid "page.navbar.upload" msgstr "Upload" -#: fietsboek/templates/layout.jinja2:70 +#: fietsboek/templates/layout.jinja2:74 msgid "page.navbar.user" msgstr "User" -#: fietsboek/templates/layout.jinja2:74 +#: fietsboek/templates/layout.jinja2:78 msgid "page.navbar.welcome_user" msgstr "Welcome, {}!" -#: fietsboek/templates/layout.jinja2:77 +#: fietsboek/templates/layout.jinja2:81 msgid "page.navbar.logout" msgstr "Logout" -#: fietsboek/templates/layout.jinja2:80 +#: fietsboek/templates/layout.jinja2:84 msgid "page.navbar.profile" msgstr "Profile" -#: fietsboek/templates/layout.jinja2:83 +#: fietsboek/templates/layout.jinja2:87 msgid "page.navbar.user_data" msgstr "Personal Data" -#: fietsboek/templates/layout.jinja2:87 +#: fietsboek/templates/layout.jinja2:91 msgid "page.navbar.admin" msgstr "Admin" -#: fietsboek/templates/layout.jinja2:93 +#: fietsboek/templates/layout.jinja2:97 msgid "page.navbar.login" msgstr "Login" -#: fietsboek/templates/layout.jinja2:97 +#: fietsboek/templates/layout.jinja2:101 msgid "page.navbar.create_account" msgstr "Create Account" @@ -648,91 +938,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" @@ -858,11 +1148,11 @@ msgstr "Fix null elevation" msgid "transformers.fix-null-elevation.description" msgstr "This transformer fixes the elevation of points whose elevation is unset." -#: fietsboek/transformers/elevation.py:116 +#: fietsboek/transformers/elevation.py:109 msgid "transformers.fix-elevation-jumps" msgstr "Fix elevation jumps" -#: fietsboek/transformers/elevation.py:120 +#: fietsboek/transformers/elevation.py:113 msgid "transformers.fix-elevation-jumps.description" msgstr "This transformer fixes abrupt jumps in the elevation value." @@ -878,15 +1168,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:189 msgid "flash.badge_added" msgstr "Badge has been added" -#: fietsboek/views/admin.py:73 +#: fietsboek/views/admin.py:213 msgid "flash.badge_modified" msgstr "Badge has been modified" -#: fietsboek/views/admin.py:93 +#: fietsboek/views/admin.py:233 msgid "flash.badge_deleted" msgstr "Badge has been deleted" @@ -945,23 +1235,27 @@ 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:187 msgid "flash.track_deleted" msgstr "Track has been deleted" +#: fietsboek/views/edit.py:97 fietsboek/views/upload.py:63 +msgid "flash.invalid_file" +msgstr "Invalid GPX file selected" + +#: fietsboek/views/journey.py:251 +msgid "flash.journey_deleted" +msgstr "Journey has been deleted" + #: fietsboek/views/upload.py:53 msgid "flash.no_file_selected" msgstr "No file selected" -#: fietsboek/views/upload.py:66 -msgid "flash.invalid_file" -msgstr "Invalid GPX file selected" - -#: fietsboek/views/upload.py:192 +#: fietsboek/views/upload.py:177 msgid "flash.upload_success" msgstr "Upload successful" -#: fietsboek/views/upload.py:211 +#: fietsboek/views/upload.py:196 msgid "flash.upload_cancelled" msgstr "Upload cancelled" diff --git a/fietsboek/locale/fietslog.pot b/fietsboek/locale/fietslog.pot index 60c77a5..50a43a0 100644 --- a/fietsboek/locale/fietslog.pot +++ b/fietsboek/locale/fietslog.pot @@ -1,98 +1,234 @@ # Translations template for PROJECT. -# Copyright (C) 2025 ORGANIZATION +# Copyright (C) 2026 ORGANIZATION # This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR <EMAIL@ADDRESS>, 2025. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2026. # #, fuzzy 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: 2026-01-03 19:25+0100\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:266 msgid "email.verify_mail.subject" msgstr "" -#: fietsboek/actions.py:270 +#: fietsboek/actions.py:269 msgid "email.verify.text" msgstr "" -#: fietsboek/util.py:333 +#: fietsboek/pdf.py:233 +msgid "pdf.table.date" +msgstr "" + +#: fietsboek/pdf.py:235 +msgid "pdf.table.length" +msgstr "" + +#: fietsboek/pdf.py:239 +msgid "pdf.table.uphill" +msgstr "" + +#: fietsboek/pdf.py:243 +msgid "pdf.table.downhill" +msgstr "" + +#: fietsboek/pdf.py:246 +msgid "pdf.table.moving_time" +msgstr "" + +#: fietsboek/pdf.py:247 +msgid "pdf.table.stopped_time" +msgstr "" + +#: fietsboek/pdf.py:249 +msgid "pdf.table.max_speed" +msgstr "" + +#: fietsboek/pdf.py:253 +msgid "pdf.table.avg_speed" +msgstr "" + +#: fietsboek/util.py:299 msgid "password_constraint.mismatch" msgstr "" -#: fietsboek/util.py:335 +#: fietsboek/util.py:301 msgid "password_constraint.length" msgstr "" -#: fietsboek/models/track.py:603 +#: fietsboek/models/track.py:774 msgid "tooltip.table.length" msgstr "" -#: fietsboek/models/track.py:604 +#: fietsboek/models/track.py:775 msgid "tooltip.table.people" msgstr "" -#: fietsboek/models/track.py:605 +#: fietsboek/models/track.py:776 msgid "tooltip.table.uphill" msgstr "" -#: fietsboek/models/track.py:606 +#: fietsboek/models/track.py:777 msgid "tooltip.table.downhill" msgstr "" -#: fietsboek/models/track.py:607 fietsboek/templates/home.jinja2:7 +#: fietsboek/models/track.py:778 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:779 fietsboek/templates/home.jinja2:8 msgid "tooltip.table.stopped_time" msgstr "" -#: fietsboek/models/track.py:610 +#: fietsboek/models/track.py:781 msgid "tooltip.table.max_speed" msgstr "" -#: fietsboek/models/track.py:614 +#: fietsboek/models/track.py:785 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.track_data" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:56 +msgid "admin.overview.storage_graph.label.backups" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:57 +msgid "admin.overview.storage_graph.label.images" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:58 +msgid "admin.overview.storage_graph.label.track_previews" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:59 +msgid "admin.overview.storage_graph.label.journey_previews" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:60 +msgid "admin.overview.storage_graph.label.user_maps" +msgstr "" + +#: fietsboek/templates/admin_overview.jinja2:88 +msgid "admin.overview.storage_graph.title" +msgstr "" + #: fietsboek/templates/browse.jinja2:4 msgid "page.browse.title" msgstr "" @@ -189,73 +325,100 @@ 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:127 +#: fietsboek/templates/journey_details.jinja2:124 +#: 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:141 +#: fietsboek/templates/journey_details.jinja2:76 +#: fietsboek/templates/journey_details.jinja2:126 +#: 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:132 +#: fietsboek/templates/journey_details.jinja2:131 +#: 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:136 +#: fietsboek/templates/journey_details.jinja2:133 +#: 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:145 +#: fietsboek/templates/journey_details.jinja2:80 +#: fietsboek/templates/journey_details.jinja2:138 +#: 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:149 +#: fietsboek/templates/journey_details.jinja2:84 +#: fietsboek/templates/journey_details.jinja2:140 +#: 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:154 +#: fietsboek/templates/journey_details.jinja2:88 +#: fietsboek/templates/journey_details.jinja2:145 +#: 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:158 +#: fietsboek/templates/journey_details.jinja2:92 +#: fietsboek/templates/journey_details.jinja2:147 +#: 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:162 +#: fietsboek/templates/journey_details.jinja2:96 +#: fietsboek/templates/journey_details.jinja2:151 +#: 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:166 +#: fietsboek/templates/journey_details.jinja2:100 +#: fietsboek/templates/journey_details.jinja2:153 +#: fietsboek/templates/profile_overview.jinja2:46 msgid "page.details.avg_speed" msgstr "" -#: fietsboek/templates/browse.jinja2:192 +#: fietsboek/templates/browse.jinja2:196 +#: fietsboek/templates/journey_details.jinja2:158 msgid "page.browse.card.comments" msgstr "" -#: fietsboek/templates/browse.jinja2:194 +#: fietsboek/templates/browse.jinja2:198 +#: fietsboek/templates/journey_details.jinja2:160 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 "" @@ -299,88 +462,93 @@ msgstr "" msgid "page.create_account.create" msgstr "" -#: fietsboek/templates/details.jinja2:7 +#: fietsboek/templates/details.jinja2:24 msgid "page.details.title" msgstr "" -#: fietsboek/templates/details.jinja2:20 +#: fietsboek/templates/details.jinja2:37 msgid "page.details.edit" msgstr "" -#: fietsboek/templates/details.jinja2:21 +#: fietsboek/templates/details.jinja2:38 msgid "page.details.share" msgstr "" -#: fietsboek/templates/details.jinja2:22 +#: fietsboek/templates/details.jinja2:39 msgid "page.details.delete" msgstr "" -#: fietsboek/templates/details.jinja2:28 +#: fietsboek/templates/details.jinja2:45 msgid "page.details.sharelink.title" msgstr "" -#: fietsboek/templates/details.jinja2:32 +#: fietsboek/templates/details.jinja2:49 msgid "page.details.sharelink.info" msgstr "" -#: fietsboek/templates/details.jinja2:39 +#: fietsboek/templates/details.jinja2:56 msgid "page.details.sharelink.invalidate" msgstr "" -#: fietsboek/templates/details.jinja2:41 +#: fietsboek/templates/details.jinja2:58 msgid "page.details.sharelink.close" msgstr "" -#: fietsboek/templates/details.jinja2:51 +#: fietsboek/templates/details.jinja2:68 msgid "page.details.delete.title" msgstr "" -#: fietsboek/templates/details.jinja2:55 +#: fietsboek/templates/details.jinja2:72 msgid "page.details.delete.info" msgstr "" -#: fietsboek/templates/details.jinja2:60 +#: fietsboek/templates/details.jinja2:77 msgid "page.details.delete.delete" msgstr "" -#: fietsboek/templates/details.jinja2:62 +#: fietsboek/templates/details.jinja2:79 msgid "page.details.delete.close" msgstr "" -#: fietsboek/templates/details.jinja2:81 +#: fietsboek/templates/details.jinja2:98 msgid "page.details.tags" msgstr "" -#: fietsboek/templates/details.jinja2:91 fietsboek/templates/edit.jinja2:10 +#: fietsboek/templates/details.jinja2:108 fietsboek/templates/edit.jinja2:10 #: fietsboek/templates/finish_upload.jinja2:10 +#: fietsboek/templates/journey_details.jinja2:66 msgid "page.noscript" msgstr "" -#: fietsboek/templates/details.jinja2:97 +#: fietsboek/templates/details.jinja2:115 msgid "page.details.download" msgstr "" -#: fietsboek/templates/details.jinja2:187 +#: fietsboek/templates/details.jinja2:120 +msgid "page.details.download_pdf" +msgstr "" + +#: fietsboek/templates/details.jinja2:211 msgid "page.details.comments" msgstr "" -#: fietsboek/templates/details.jinja2:191 +#: fietsboek/templates/details.jinja2:215 msgid "page.details.comments.author" msgstr "" -#: fietsboek/templates/details.jinja2:208 +#: fietsboek/templates/details.jinja2:232 msgid "page.details.comments.new.title" msgstr "" -#: fietsboek/templates/details.jinja2:211 +#: fietsboek/templates/details.jinja2:235 msgid "page.details.comments.new.input_title" msgstr "" -#: fietsboek/templates/details.jinja2:212 +#: fietsboek/templates/details.jinja2:236 msgid "page.details.comments.new.input_comment" msgstr "" -#: fietsboek/templates/details.jinja2:215 +#: fietsboek/templates/details.jinja2:239 msgid "page.details.comments.new.submit" msgstr "" @@ -388,11 +556,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,61 +704,177 @@ 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 "" -#: fietsboek/templates/layout.jinja2:43 +#: fietsboek/templates/journey_details.jinja2:10 +msgid "journey.edit" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:11 +msgid "journey.share" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:12 +msgid "journey.delete" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:18 +msgid "journey.sharelink.title" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:22 +msgid "journey.sharelink.info" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:29 +msgid "journey.sharelink.invalidate" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:31 +msgid "journey.sharelink.close" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:41 +msgid "journey.delete.title" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:45 +msgid "journey.delete.info" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:50 +msgid "journey.delete.delete" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:52 +msgid "journey.delete.close" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:108 +msgid "journey.tracks" +msgstr "" + +#: fietsboek/templates/journey_details.jinja2:174 +msgid "journeys.track.hidden" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:40 +msgid "journeys.new.form.title" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:43 +msgid "journeys.new.form.requires_title" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:47 +msgid "journeys.new.form.description" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:51 +msgid "journeys.new.form.visibility" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:54 +msgid "journeys.new.form.visibility.private" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:55 +msgid "journeys.new.form.visibility.friends" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:56 +msgid "journeys.new.form.visibility.logged_in" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:57 +msgid "journeys.new.form.visibility.public" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:62 +msgid "journeys.new.form.tracksearch" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:71 +msgid "journeys.new.form.tracks" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:90 +msgid "journeys.new.form.submit" +msgstr "" + +#: fietsboek/templates/journey_form.jinja2:93 +msgid "journeys.new.form.requires_tracks" +msgstr "" + +#: fietsboek/templates/journey_list.jinja2:4 +msgid "journeys.overview.title" +msgstr "" + +#: fietsboek/templates/journey_list.jinja2:10 +msgid "journeys.overview.new" +msgstr "" + +#: fietsboek/templates/journey_new.jinja2:10 +msgid "journeys.new.title" +msgstr "" + +#: fietsboek/templates/layout.jinja2:44 msgid "page.navbar.toggle" msgstr "" -#: fietsboek/templates/layout.jinja2:54 +#: fietsboek/templates/layout.jinja2:55 msgid "page.navbar.home" msgstr "" -#: fietsboek/templates/layout.jinja2:57 +#: fietsboek/templates/layout.jinja2:58 msgid "page.navbar.browse" msgstr "" #: fietsboek/templates/layout.jinja2:61 +msgid "page.navbar.journeys" +msgstr "" + +#: fietsboek/templates/layout.jinja2:65 msgid "page.navbar.upload" msgstr "" -#: fietsboek/templates/layout.jinja2:70 +#: fietsboek/templates/layout.jinja2:74 msgid "page.navbar.user" msgstr "" -#: fietsboek/templates/layout.jinja2:74 +#: fietsboek/templates/layout.jinja2:78 msgid "page.navbar.welcome_user" msgstr "" -#: fietsboek/templates/layout.jinja2:77 +#: fietsboek/templates/layout.jinja2:81 msgid "page.navbar.logout" msgstr "" -#: fietsboek/templates/layout.jinja2:80 +#: fietsboek/templates/layout.jinja2:84 msgid "page.navbar.profile" msgstr "" -#: fietsboek/templates/layout.jinja2:83 +#: fietsboek/templates/layout.jinja2:87 msgid "page.navbar.user_data" msgstr "" -#: fietsboek/templates/layout.jinja2:87 +#: fietsboek/templates/layout.jinja2:91 msgid "page.navbar.admin" msgstr "" -#: fietsboek/templates/layout.jinja2:93 +#: fietsboek/templates/layout.jinja2:97 msgid "page.navbar.login" msgstr "" -#: fietsboek/templates/layout.jinja2:97 +#: fietsboek/templates/layout.jinja2:101 msgid "page.navbar.create_account" msgstr "" @@ -642,91 +930,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 "" @@ -846,11 +1134,11 @@ msgstr "" msgid "transformers.fix-null-elevation.description" msgstr "" -#: fietsboek/transformers/elevation.py:116 +#: fietsboek/transformers/elevation.py:109 msgid "transformers.fix-elevation-jumps" msgstr "" -#: fietsboek/transformers/elevation.py:120 +#: fietsboek/transformers/elevation.py:113 msgid "transformers.fix-elevation-jumps.description" msgstr "" @@ -866,15 +1154,15 @@ msgstr "" msgid "flash.a_confirmation_link_has_been_sent" msgstr "" -#: fietsboek/views/admin.py:49 +#: fietsboek/views/admin.py:189 msgid "flash.badge_added" msgstr "" -#: fietsboek/views/admin.py:73 +#: fietsboek/views/admin.py:213 msgid "flash.badge_modified" msgstr "" -#: fietsboek/views/admin.py:93 +#: fietsboek/views/admin.py:233 msgid "flash.badge_deleted" msgstr "" @@ -930,23 +1218,27 @@ msgstr "" msgid "flash.password_updated" msgstr "" -#: fietsboek/views/detail.py:162 +#: fietsboek/views/detail.py:187 msgid "flash.track_deleted" msgstr "" -#: fietsboek/views/upload.py:53 -msgid "flash.no_file_selected" +#: fietsboek/views/edit.py:97 fietsboek/views/upload.py:63 +msgid "flash.invalid_file" msgstr "" -#: fietsboek/views/upload.py:66 -msgid "flash.invalid_file" +#: fietsboek/views/journey.py:251 +msgid "flash.journey_deleted" +msgstr "" + +#: fietsboek/views/upload.py:53 +msgid "flash.no_file_selected" msgstr "" -#: fietsboek/views/upload.py:192 +#: fietsboek/views/upload.py:177 msgid "flash.upload_success" msgstr "" -#: fietsboek/views/upload.py:211 +#: fietsboek/views/upload.py:196 msgid "flash.upload_cancelled" msgstr "" diff --git a/fietsboek/models/__init__.py b/fietsboek/models/__init__.py index 6f91eae..2130901 100644 --- a/fietsboek/models/__init__.py +++ b/fietsboek/models/__init__.py @@ -5,13 +5,14 @@ access the submodules if you need some of the auxiliary definitions. """ import zope.sqlalchemy -from sqlalchemy import engine_from_config +from sqlalchemy import engine_from_config, event from sqlalchemy.orm import configure_mappers, sessionmaker from .badge import Badge # flake8: noqa from .comment import Comment # flake8: noqa from .image import ImageMetadata # flake8: noqa -from .track import Tag, Track, TrackCache, Upload # flake8: noqa +from .journey import Journey +from .track import Tag, Track, TrackCache, Upload, Waypoint # flake8: noqa # Import or define all models here to ensure they are attached to the # ``Base.metadata`` prior to any initialization routines. @@ -24,7 +25,16 @@ configure_mappers() def get_engine(settings, prefix="sqlalchemy."): """Create an SQL Engine from the given settings.""" - return engine_from_config(settings, prefix) + engine = engine_from_config(settings, prefix) + + # SQLite is quite loose with foreign keys by default, so make sure it + # checks them. + def _fk_pragma_on_connect(dbapi_con, _con_record): + dbapi_con.execute("PRAGMA foreign_keys=ON;") + + if engine.dialect.name == "sqlite": + event.listen(engine, "connect", _fk_pragma_on_connect) + return engine def get_session_factory(engine): diff --git a/fietsboek/models/badge.py b/fietsboek/models/badge.py index 2a6ef95..d80d9fb 100644 --- a/fietsboek/models/badge.py +++ b/fietsboek/models/badge.py @@ -3,8 +3,8 @@ from typing import TYPE_CHECKING from pyramid.httpexceptions import HTTPNotFound -from sqlalchemy import Column, Integer, LargeBinary, Text, select -from sqlalchemy.orm import Mapped, relationship +from sqlalchemy import Integer, LargeBinary, Text, select +from sqlalchemy.orm import Mapped, mapped_column, relationship from .meta import Base @@ -29,9 +29,9 @@ class Badge(Base): # pylint: disable=too-few-public-methods __tablename__ = "badges" - id = Column(Integer, primary_key=True) - title = Column(Text) - image = Column(LargeBinary) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + title: Mapped[str] = mapped_column(Text) + image: Mapped[bytes] = mapped_column(LargeBinary) tracks: Mapped[list["Track"]] = relationship( "Track", secondary="track_badge_assoc", back_populates="badges" diff --git a/fietsboek/models/comment.py b/fietsboek/models/comment.py index e1762d5..a06b595 100644 --- a/fietsboek/models/comment.py +++ b/fietsboek/models/comment.py @@ -1,9 +1,10 @@ """Comment model.""" +import datetime from typing import TYPE_CHECKING -from sqlalchemy import Column, DateTime, ForeignKey, Integer, Text -from sqlalchemy.orm import Mapped, relationship +from sqlalchemy import DateTime, ForeignKey, Integer, Text +from sqlalchemy.orm import Mapped, mapped_column, relationship from .meta import Base @@ -35,12 +36,12 @@ class Comment(Base): # pylint: disable=too-few-public-methods __tablename__ = "comments" - id = Column(Integer, primary_key=True) - author_id = Column(Integer, ForeignKey("users.id")) - track_id = Column(Integer, ForeignKey("tracks.id")) - date = Column(DateTime(False)) - title = Column(Text) - text = Column(Text) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + author_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id")) + track_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("tracks.id")) + date: Mapped[datetime.datetime | None] = mapped_column(DateTime(False)) + title: Mapped[str | None] = mapped_column(Text) + text: Mapped[str | None] = mapped_column(Text) author: Mapped["User"] = relationship("User", back_populates="comments") track: Mapped["Track"] = relationship("Track", back_populates="comments") diff --git a/fietsboek/models/image.py b/fietsboek/models/image.py index dfa9ffb..1f0d4a9 100644 --- a/fietsboek/models/image.py +++ b/fietsboek/models/image.py @@ -6,8 +6,8 @@ image description here. from typing import TYPE_CHECKING -from sqlalchemy import Column, ForeignKey, Integer, Text, UniqueConstraint, select -from sqlalchemy.orm import Mapped, relationship +from sqlalchemy import ForeignKey, Integer, Text, UniqueConstraint, select +from sqlalchemy.orm import Mapped, mapped_column, relationship from .meta import Base @@ -32,10 +32,10 @@ class ImageMetadata(Base): # pylint: disable=too-few-public-methods __tablename__ = "image_metadata" - id = Column(Integer, primary_key=True) - track_id = Column(Integer, ForeignKey("tracks.id"), nullable=False) - image_name = Column(Text, nullable=False) - description = Column(Text) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + track_id: Mapped[int] = mapped_column(Integer, ForeignKey("tracks.id"), nullable=False) + image_name: Mapped[str] = mapped_column(Text, nullable=False) + description: Mapped[str] = mapped_column(Text) track: Mapped["Track"] = relationship("Track", back_populates="images") diff --git a/fietsboek/models/journey.py b/fietsboek/models/journey.py new file mode 100644 index 0000000..0f1639e --- /dev/null +++ b/fietsboek/models/journey.py @@ -0,0 +1,190 @@ +"""Journey model definition. + +A Journey is an ordered collection of tracks, with a title and (optionally) a description. +""" + +import dataclasses +import datetime +import enum +import logging +from typing import TYPE_CHECKING, Self + +from pyramid.authorization import ( + ALL_PERMISSIONS, + Allow, + Authenticated, + Everyone, +) +from pyramid.httpexceptions import HTTPNotFound +from pyramid.request import Request +from sqlalchemy import ( + Column, + Enum, + ForeignKey, + Integer, + Table, + Text, + delete, + insert, + inspect, + select, +) +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from .. import geo +from .meta import Base + +if TYPE_CHECKING: + from .. import models + + +LOGGER = logging.getLogger(__name__) + + +class Visibility(enum.Enum): + """An enum representing the visibility of Journeys.""" + + PRIVATE = enum.auto() + """Only the owner can see the journey.""" + + FRIENDS = enum.auto() + """Friends can see the journey.""" + + LOGGED_IN = enum.auto() + """Logged in users can see the journey.""" + + PUBLIC = enum.auto() + """Everybody can see the journey.""" + + +journey_track_assoc = Table( + "journey_track_assoc", + Base.metadata, + Column("journey_id", ForeignKey("journeys.id"), primary_key=True), + Column("track_id", ForeignKey("tracks.id"), primary_key=True), + Column("sort_index", Integer, nullable=False), +) + + +class Journey(Base): + """A :class:`Journey` represents a collection of tracks, with a title and description.""" + + __tablename__ = "journeys" + id: Mapped[int] = mapped_column(Integer, primary_key=True) + owner_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False) + title: Mapped[str] = mapped_column(Text, nullable=False) + description: Mapped[str] = mapped_column(Text, nullable=False) + visibility: Mapped[Visibility] = mapped_column( + Enum(Visibility, name="journey_visibility"), + nullable=False, + ) + link_secret: Mapped[str | None] = mapped_column(Text) + + owner: Mapped["models.User"] = relationship("User", back_populates="journeys") + tracks: Mapped[list["models.Track"]] = relationship( + "Track", + back_populates="journeys", + secondary=journey_track_assoc, + order_by=journey_track_assoc.c.sort_index, + ) + + @classmethod + def factory(cls, request: Request) -> Self: + """Factory method to pass to a route definition. + + This factory retrieves the journey based on the ``journey_id`` matched + route parameter, and returns the journey. If the journey is not found, + ``HTTPNotFound`` is raised. + + :raises pyramid.httpexception.NotFound: If the journey is not found. + :param request: The pyramid request. + :type request: ~pyramid.request.Request + :return: The journey. + :type: Track + """ + journey_id = request.matchdict["journey_id"] + query = select(cls).filter_by(id=journey_id) + journey = request.dbsession.execute(query).scalar_one_or_none() + if journey is None: + raise HTTPNotFound() + return journey + + def __acl__(self): + # Basic ACL: Permissions for the admin, the owner and the share link + acl = [ + (Allow, "group:admins", ALL_PERMISSIONS), + ( + Allow, + f"user:{self.owner_id}", + [ + "journey.view", + "journey.edit", + "journey.unshare", + "journey.comment", + "journey.delete", + ], + ), + (Allow, f"secret:{self.link_secret}", "journey.view"), + ] + + if self.visibility == Visibility.PUBLIC: + acl.append((Allow, Everyone, "journey.view")) + acl.append((Allow, Authenticated, "journey.comment")) + elif self.visibility == Visibility.LOGGED_IN: + acl.append((Allow, Authenticated, ["journey.view", "journey.comment"])) + elif self.visibility == Visibility.FRIENDS: + acl.extend( + (Allow, f"user:{friend.id}", ["journey.view", "journey.comment"]) + for friend in self.owner.get_friends() + ) + return acl + + def set_track_ids(self, track_ids: list[int]): + """Sets the IDs of the contained tracks. + + The order is relevant and will be saved. + + Needs to have a session, as it will directly issue INSERT statements. + + :param track_ids: The IDs of the tracks that should be in this journey. + """ + session = inspect(self).session + assert session is not None, "Can only use set_track_ids() if journey is in a session" + del_stmt = delete(journey_track_assoc).where(journey_track_assoc.c.journey_id == self.id) + session.execute(del_stmt) + for index, track_id in enumerate(track_ids, 1): + ins_stmt = insert(journey_track_assoc).values( + journey_id=self.id, + track_id=track_id, + sort_index=index, + ) + session.execute(ins_stmt) + + def path(self) -> geo.Path: + """Returns the concatenated path of all contained tracks.""" + offset = 0.0 + points = [] + for track in self.tracks: + point = None + for point in track.path().points: + new_point = dataclasses.replace(point, time_offset=point.time_offset + offset) + points.append(new_point) + if point: + offset += point.time_offset + return geo.Path(points) + + def gpx_xml(self) -> bytes: + """Returns a GPX XML that represents this journey. + + :return: The XML file. + """ + return geo.gpx_xml( + self.title, + self.description, + datetime.datetime.fromtimestamp(0).replace(tzinfo=datetime.UTC), + self.path().points, + [], + ) + + +__all__ = ["Journey", "Visibility"] diff --git a/fietsboek/models/meta.py b/fietsboek/models/meta.py index 45723fd..0e7dd15 100644 --- a/fietsboek/models/meta.py +++ b/fietsboek/models/meta.py @@ -1,6 +1,6 @@ """Base metadata definition for the SQLAlchemy models.""" -from sqlalchemy.orm import declarative_base +from sqlalchemy.orm import DeclarativeBase from sqlalchemy.schema import MetaData # Recommended naming convention used by Alembic, as various different database @@ -14,8 +14,14 @@ NAMING_CONVENTION = { "pk": "pk_%(table_name)s", } -metadata = MetaData(naming_convention=NAMING_CONVENTION) -Base = declarative_base(metadata=metadata) +sqla_metadata = MetaData(naming_convention=NAMING_CONVENTION) -__all__ = ["NAMING_CONVENTION", "metadata", "Base"] +class Base(DeclarativeBase): + """Base class for SQLAlchemy model definitions.""" + + # pylint: disable=too-few-public-methods + metadata = sqla_metadata + + +__all__ = ["NAMING_CONVENTION", "sqla_metadata", "Base"] diff --git a/fietsboek/models/track.py b/fietsboek/models/track.py index 0737982..95e341f 100644 --- a/fietsboek/models/track.py +++ b/fietsboek/models/track.py @@ -12,14 +12,17 @@ example all cached data to be re-computed without interfering with the other meta information. """ +# pylint: disable=too-many-lines + import datetime import enum import gzip +import json import logging from itertools import chain -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Optional -import gpxpy +import sqlalchemy.types from babel.numbers import format_decimal from markupsafe import Markup from pyramid.authorization import ( @@ -34,7 +37,6 @@ from pyramid.httpexceptions import HTTPNotFound from pyramid.i18n import Localizer from pyramid.i18n import TranslationString as _ from sqlalchemy import ( - JSON, Column, DateTime, Enum, @@ -44,11 +46,14 @@ from sqlalchemy import ( LargeBinary, Table, Text, + delete, + insert, + inspect, select, ) -from sqlalchemy.orm import Mapped, relationship +from sqlalchemy.orm import Mapped, mapped_column, relationship -from .. import util +from .. import geo, util from .meta import Base if TYPE_CHECKING: @@ -58,6 +63,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. @@ -71,8 +94,8 @@ class Tag(Base): # pylint: disable=too-few-public-methods __tablename__ = "tags" - track_id = Column(Integer, ForeignKey("tracks.id"), primary_key=True) - tag = Column(Text, primary_key=True) + track_id: Mapped[int] = mapped_column(Integer, ForeignKey("tracks.id"), primary_key=True) + tag: Mapped[str] = mapped_column(Text, primary_key=True) track: Mapped["Track"] = relationship("Track", back_populates="tags") @@ -132,6 +155,67 @@ track_favourite_assoc = Table( Column("user_id", ForeignKey("users.id"), primary_key=True), ) + +class Waypoint(Base): + """A waypoint represents a "point of interest" along a path. + + Waypoints can have a name and description set. They exist outside of the + actual route. + """ + + # pylint: disable=too-few-public-methods + __tablename__ = "waypoints" + id: Mapped[int] = mapped_column(Integer, primary_key=True) + track_id: Mapped[int] = mapped_column(Integer, ForeignKey("tracks.id"), nullable=False) + longitude: Mapped[float] = mapped_column(Float, nullable=False) + latitude: Mapped[float] = mapped_column(Float, nullable=False) + elevation: Mapped[float | None] = mapped_column(Float) + name: Mapped[str | None] = mapped_column(Text) + description: Mapped[str | None] = mapped_column(Text) + + track: Mapped["Track"] = relationship("Track", back_populates="waypoints") + + def to_geo_waypoint(self) -> geo.Waypoint: + """Converts this waypoint (a database object) to a plain waypoint. + + :return: The converted point. + """ + return geo.Waypoint( + latitude=self.latitude, + longitude=self.longitude, + elevation=self.elevation, + name=self.name, + description=self.description, + ) + + +class TrackPoint(Base): + """A track point represents a single GPS point along a path.""" + + # pylint: disable=too-few-public-methods + __tablename__ = "track_points" + track_id: Mapped[int] = mapped_column(Integer, ForeignKey("tracks.id"), primary_key=True) + index: Mapped[int] = mapped_column(Integer, primary_key=True) + longitude: Mapped[float] = mapped_column(Float, nullable=False) + latitude: Mapped[float] = mapped_column(Float, nullable=False) + elevation: Mapped[float | None] = mapped_column(Float) + time_offset: Mapped[float | None] = mapped_column(Float) + + track: Mapped["Track"] = relationship("Track", back_populates="points") + + def to_geo_point(self) -> geo.Point: + """Converts this point (a database object) to a plain point. + + :return: The converted point. + """ + return geo.Point( + latitude=self.latitude, + longitude=self.longitude, + elevation=self.elevation or 0.0, + time_offset=self.time_offset or 0.0, + ) + + # Some words about timezone handling in saved tracks: # https://www.youtube.com/watch?v=-5wpm-gesOY # @@ -204,18 +288,24 @@ class Track(Base): """ __tablename__ = "tracks" - id = Column(Integer, primary_key=True) - owner_id = Column(Integer, ForeignKey("users.id")) - title = Column(Text) - description = Column(Text) - date_raw = Column(DateTime(False)) - date_tz = Column(Integer) - visibility = Column(Enum(Visibility)) - link_secret = Column(Text) - type = Column(Enum(TrackType)) - transformers = Column(JSON) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + owner_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id")) + title: Mapped[str | None] = mapped_column(Text) + description: Mapped[str | None] = mapped_column(Text) + date_raw: Mapped[datetime.datetime | None] = mapped_column(DateTime(False)) + date_tz: Mapped[int | None] = mapped_column(Integer) + visibility: Mapped[Visibility | None] = mapped_column(Enum(Visibility)) + link_secret: Mapped[str | None] = mapped_column(Text) + type: Mapped[TrackType | None] = mapped_column(Enum(TrackType)) + transformers: Mapped[list | dict | bool | str | int | float | None] = mapped_column(JsonText) owner: Mapped["models.User"] = relationship("User", back_populates="tracks") + points: Mapped[list["TrackPoint"]] = relationship( + "TrackPoint", back_populates="track", cascade="all, delete-orphan" + ) + waypoints: Mapped[list["Waypoint"]] = relationship( + "Waypoint", back_populates="track", cascade="all, delete-orphan" + ) cache: Mapped[Optional["TrackCache"]] = relationship( "TrackCache", back_populates="track", uselist=False, cascade="all, delete-orphan" ) @@ -237,6 +327,9 @@ class Track(Base): favourees: Mapped[list["models.User"]] = relationship( "User", secondary=track_favourite_assoc, back_populates="favourite_tracks" ) + journeys: Mapped[list["models.Journey"]] = relationship( + "Journey", secondary="journey_track_assoc", back_populates="tracks" + ) @classmethod def factory(cls, request): @@ -297,6 +390,74 @@ class Track(Base): ) return acl + def set_path(self, path: geo.Path): + """Sets this track's represented path to the given path. + + If the track is in the database session, you can use + :meth:`fast_set_path`. + + :param path: The new GPS path of this track. + """ + self.points = [ + TrackPoint( + track=self, + index=i, + longitude=point.longitude, + latitude=point.latitude, + elevation=point.elevation, + time_offset=point.time_offset, + ) + for i, point in enumerate(path.points) + ] + + def fast_set_path(self, path: geo.Path): + """Sets this track's represented path to the given path. + + This method is faster than using :meth:`set_path`, as it does a bulk + insert of the path points. However, this requires the path to be in the + database and the session. + + :param path: The new GPS path of this track. + """ + session = inspect(self).session + assert session + points = [ + { + "track_id": self.id, + "index": index, + "longitude": p.longitude, + "latitude": p.latitude, + "elevation": p.elevation, + "time_offset": p.time_offset, + } + for index, p in enumerate(path.points) + ] + session.execute(delete(TrackPoint).where(TrackPoint.track_id == self.id)) + session.execute(insert(TrackPoint), points) + session.expire(self, ["points"]) + + def path(self) -> geo.Path: + """Returns the path of this track. + + :return: The GPS path of this track. + """ + return geo.Path( + [point.to_geo_point() for point in sorted(self.points, key=lambda p: p.index or 0.0)] + ) + + def gpx_xml(self) -> bytes: + """Returns an XML representation of this track. + + :return: The XML representation (a GPX file). + """ + return geo.gpx_xml( + self.title, + self.description, + self.date, + self.path().points, + [wpt.to_geo_waypoint() for wpt in self.waypoints], + ) + @property def date(self): """The time-zone-aware date this track has set. @@ -355,24 +516,35 @@ class Track(Base): result = ACLHelper().permits(self, principals, "track.view") return isinstance(result, ACLAllowed) - def ensure_cache(self, gpx_data: Union[str, bytes, gpxpy.gpx.GPX]): + def ensure_cache(self, path: geo.Path | None = None): """Ensure that a cached version of this track's metadata exists. - :param gpx_data: GPX data (uncompressed) from which to build the cache. + If ``path`` is given, it is used as the basis for the calculation. This + is useful if you have already loaded the path, to avoid a database + access. + + :param path: The path, if preloaded. Otherwise, ``self.path()`` will be + used. """ if self.cache is not None: return - self.cache = TrackCache(track=self) - meta = util.tour_metadata(gpx_data) - self.cache.length = meta["length"] - self.cache.uphill = meta["uphill"] - self.cache.downhill = meta["downhill"] - self.cache.moving_time = meta["moving_time"] - self.cache.stopped_time = meta["stopped_time"] - self.cache.max_speed = meta["max_speed"] - self.cache.avg_speed = meta["avg_speed"] - self.cache.start_time = meta["start_time"] - self.cache.end_time = meta["end_time"] + self.cache = TrackCache() + if path is None: + path = self.path() + meta = path.movement_data() + self.cache.length = meta.length + self.cache.uphill = meta.uphill + self.cache.downhill = meta.downhill + self.cache.moving_time = meta.moving_duration + self.cache.stopped_time = meta.stopped_duration + self.cache.max_speed = meta.maximum_speed + self.cache.avg_speed = meta.average_speed + self.cache.start_time = self.date + self.cache.end_time = self.date + datetime.timedelta(seconds=meta.duration) + + def with_metadata(self) -> "TrackWithMetadata": + """Returns this track with attached path metadata.""" + return TrackWithMetadata(self) def text_tags(self): """Returns a set of textual tags. @@ -443,6 +615,9 @@ class Track(Base): if not self.transformers: return None + if not isinstance(self.transformers, dict): + return None + for t_id, settings in self.transformers: if t_id == transformer_id: return settings @@ -452,7 +627,7 @@ class Track(Base): class TrackWithMetadata: """A class to add metadata to a :class:`Track`. - This basically caches the result of :func:`fietsboek.util.tour_metadata`, + This basically caches the result of :func:`fietsboek.geo.Path.movement_data`, or uses the track's cache if possible. Loading of the metadata is lazy on first access. The track is accessible as @@ -461,19 +636,17 @@ class TrackWithMetadata: # pylint: disable=too-many-public-methods - def __init__(self, track: Track, data_manager): + def __init__(self, track: Track): self.track = track self.cache = track.cache - self.data_manager = data_manager - self._cached_meta: Optional[dict] = None + self._cached_meta: Optional[geo.MovementData] = None def _meta(self): # Already loaded, we're done if self._cached_meta: return self._cached_meta - data = self.data_manager.open(self.track.id).decompress_gpx() - self._cached_meta = util.tour_metadata(data) + self._cached_meta = self.track.path().movement_data() return self._cached_meta @property @@ -483,7 +656,7 @@ class TrackWithMetadata: :return: Length of the track in meters. """ if self.cache is None or self.cache.length is None: - return self._meta()["length"] + return self._meta().length return float(self.cache.length) @property @@ -493,7 +666,7 @@ class TrackWithMetadata: :return: Downhill in meters. """ if self.cache is None or self.cache.downhill is None: - return self._meta()["downhill"] + return self._meta().downhill return float(self.cache.downhill) @property @@ -503,7 +676,7 @@ class TrackWithMetadata: :return: Uphill in meters. """ if self.cache is None or self.cache.uphill is None: - return self._meta()["uphill"] + return self._meta().uphill return float(self.cache.uphill) @property @@ -513,7 +686,7 @@ class TrackWithMetadata: :return: Moving time in seconds. """ if self.cache is None or self.cache.moving_time is None: - value = self._meta()["moving_time"] + value = self._meta().moving_duration else: value = self.cache.moving_time return datetime.timedelta(seconds=value) @@ -525,7 +698,7 @@ class TrackWithMetadata: :return: Stopped time in seconds. """ if self.cache is None or self.cache.stopped_time is None: - value = self._meta()["stopped_time"] + value = self._meta().stopped_duration else: value = self.cache.stopped_time return datetime.timedelta(seconds=value) @@ -537,7 +710,7 @@ class TrackWithMetadata: :return: Maximum speed in meters/second. """ if self.cache is None or self.cache.max_speed is None: - return self._meta()["max_speed"] + return self._meta().maximum_speed return float(self.cache.max_speed) @property @@ -547,7 +720,7 @@ class TrackWithMetadata: :return: Average speed in meters/second. """ if self.cache is None or self.cache.avg_speed is None: - return self._meta()["avg_speed"] + return self._meta().average_speed return float(self.cache.avg_speed) @property @@ -558,9 +731,7 @@ class TrackWithMetadata: :return: Start time. """ - if self.cache is None or self.cache.start_time is None: - return self._meta()["start_time"] - return self.cache.start_time + return self.track.date @property def end_time(self) -> datetime.datetime: @@ -571,7 +742,7 @@ class TrackWithMetadata: :return: End time. """ if self.cache is None or self.cache.end_time is None: - return self._meta()["end_time"] + return self.track.date + datetime.timedelta(seconds=self._meta().duration) return self.cache.end_time @property @@ -754,18 +925,18 @@ class TrackCache(Base): # pylint: disable=too-many-instance-attributes,too-few-public-methods __tablename__ = "track_cache" - track_id = Column(Integer, ForeignKey("tracks.id"), primary_key=True) - length = Column(Float) - uphill = Column(Float) - downhill = Column(Float) - moving_time = Column(Float) - stopped_time = Column(Float) - max_speed = Column(Float) - avg_speed = Column(Float) - start_time_raw = Column(DateTime(False)) - start_time_tz = Column(Integer) - end_time_raw = Column(DateTime(False)) - end_time_tz = Column(Integer) + track_id: Mapped[int] = mapped_column(Integer, ForeignKey("tracks.id"), primary_key=True) + length: Mapped[float | None] = mapped_column(Float) + uphill: Mapped[float | None] = mapped_column(Float) + downhill: Mapped[float | None] = mapped_column(Float) + moving_time: Mapped[float | None] = mapped_column(Float) + stopped_time: Mapped[float | None] = mapped_column(Float) + max_speed: Mapped[float | None] = mapped_column(Float) + avg_speed: Mapped[float | None] = mapped_column(Float) + start_time_raw: Mapped[datetime.datetime | None] = mapped_column(DateTime(False)) + start_time_tz: Mapped[int | None] = mapped_column(Integer) + end_time_raw: Mapped[datetime.datetime | None] = mapped_column(DateTime(False)) + end_time_tz: Mapped[int | None] = mapped_column(Integer) track: Mapped["Track"] = relationship("Track", back_populates="cache") @@ -846,10 +1017,10 @@ class Upload(Base): # pylint: disable=too-many-instance-attributes,too-few-public-methods __tablename__ = "uploads" - id = Column(Integer, primary_key=True) - uploaded_at = Column(DateTime(False)) - owner_id = Column(Integer, ForeignKey("users.id")) - gpx = Column(LargeBinary) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + uploaded_at: Mapped[datetime.datetime | None] = mapped_column(DateTime(False)) + owner_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id")) + gpx: Mapped[bytes | None] = mapped_column(LargeBinary) owner: Mapped["models.User"] = relationship("User", back_populates="uploads") diff --git a/fietsboek/models/user.py b/fietsboek/models/user.py index 725fb3a..551d920 100644 --- a/fietsboek/models/user.py +++ b/fietsboek/models/user.py @@ -31,14 +31,16 @@ from sqlalchemy import ( union, ) from sqlalchemy.exc import NoResultFound -from sqlalchemy.orm import Mapped, Session, relationship, with_parent +from sqlalchemy.orm import Mapped, Session, mapped_column, relationship, with_parent from sqlalchemy.orm.attributes import flag_dirty from sqlalchemy.orm.session import object_session +from sqlalchemy.sql.expression import CompoundSelect from .meta import Base if TYPE_CHECKING: from .comment import Comment + from .journey import Journey from .track import Track, Upload @@ -112,14 +114,14 @@ class User(Base): """ __tablename__ = "users" - id = Column(Integer, primary_key=True) - name = Column(Text) - password = Column(LargeBinary) - salt = Column(LargeBinary) - email = Column(Text) - session_secret = Column(LargeBinary(SESSION_SECRET_LENGTH)) - is_admin = Column(Boolean, default=False) - is_verified = Column(Boolean, default=False) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + name: Mapped[str | None] = mapped_column(Text) + password: Mapped[bytes | None] = mapped_column(LargeBinary) + salt: Mapped[bytes | None] = mapped_column(LargeBinary) + email: Mapped[str | None] = mapped_column(Text) + session_secret: Mapped[bytes | None] = mapped_column(LargeBinary(SESSION_SECRET_LENGTH)) + is_admin: Mapped[bool | None] = mapped_column(Boolean, default=False) + is_verified: Mapped[bool | None] = mapped_column(Boolean, default=False) tracks: Mapped[list["Track"]] = relationship( "Track", back_populates="owner", cascade="all, delete-orphan" @@ -139,6 +141,7 @@ class User(Base): comments: Mapped[list["Comment"]] = relationship( "Comment", back_populates="author", cascade="all, delete-orphan" ) + journeys: Mapped[list["Journey"]] = relationship("Journey", back_populates="owner") # We don't use them, but include them to ensure our cascading works friends_1: Mapped[list["User"]] = relationship( @@ -395,6 +398,44 @@ class User(Base): return union(*queries) + @staticmethod + def visible_journeys_query(user: Optional["User"] = None) -> CompoundSelect: + """Returns a query that returns the visible journeys for a user. + + If the user is ``None``, only public journeys will be returned. + + :param user: The user which to query the journeys for. + :return: The query that selects all fitting journeys. + """ + # Late import to avoid cycles + # pylint: disable=import-outside-toplevel,protected-access + from .journey import Journey, Visibility + + queries = [] + + # Step 1: Own journeys + if user: + queries.append(select(Journey).where(with_parent(user, User.journeys))) + + # Step 2: Public journeys + queries.append(select(Journey).filter_by(visibility=Visibility.PUBLIC)) + + # Step 3: Journeys for logged in users + if user: + queries.append(select(Journey).filter_by(visibility=Visibility.LOGGED_IN)) + + # Step 4: Friends' journeys + if user: + friend_query = user._friend_query().subquery() + friend_ids = select(friend_query.c.id) + queries.append( + select(Journey) + .filter_by(visibility=Visibility.FRIENDS) + .where(Journey.owner_id.in_(friend_ids)) + ) + + return union(*queries) + def _friend_query(self): qry1 = select(User).filter( friends_assoc.c.user_1_id == self.id, friends_assoc.c.user_2_id == User.id @@ -486,10 +527,10 @@ class FriendRequest(Base): # pylint: disable=too-few-public-methods __tablename__ = "friend_requests" - id = Column(Integer, primary_key=True) - sender_id = Column(Integer, ForeignKey("users.id")) - recipient_id = Column(Integer, ForeignKey("users.id")) - date = Column(DateTime(False)) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + sender_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id")) + recipient_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id")) + date: Mapped[datetime.datetime | None] = mapped_column(DateTime(False)) sender: Mapped["User"] = relationship( "User", primaryjoin="User.id == FriendRequest.sender_id", backref="outgoing_requests" @@ -540,11 +581,11 @@ class Token(Base): # pylint: disable=too-few-public-methods __tablename__ = "tokens" - id = Column(Integer, primary_key=True) - user_id = Column(Integer, ForeignKey("users.id")) - uuid = Column(Text) - token_type = Column(Enum(TokenType)) - date = Column(DateTime(False)) + id: Mapped[int] = mapped_column(Integer, primary_key=True) + user_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id")) + uuid: Mapped[str | None] = mapped_column(Text) + token_type: Mapped[TokenType | None] = mapped_column(Enum(TokenType)) + date: Mapped[datetime.datetime | None] = mapped_column(DateTime(False)) user: Mapped["User"] = relationship("User", back_populates="tokens") diff --git a/fietsboek/pdf-assets/Nunito.ttf b/fietsboek/pdf-assets/Nunito.ttf Binary files differnew file mode 100644 index 0000000..be80c3f --- /dev/null +++ b/fietsboek/pdf-assets/Nunito.ttf diff --git a/fietsboek/pdf-assets/overview.typ b/fietsboek/pdf-assets/overview.typ new file mode 100644 index 0000000..8628c81 --- /dev/null +++ b/fietsboek/pdf-assets/overview.typ @@ -0,0 +1,29 @@ +#set page(margin: 1cm) +#set text(font: "Nunito") + +#show heading.where(level: 1): set align(center) +#show heading.where(level: 1): set text(size: 20pt) + +#let rowHead(body) = strong(body) + +#heading[{{ title | typst_escape }}] + +#text(baseline: -1pt, emoji.person) +{% for person in people -%} +#strong[{{ person | typst_escape }}]{% if not loop.last %}, {% endif %} +{%- endfor %} + +#image("mapimage.png") + +#image(width: 100%, "height_profile.pdf") + +#table( + columns: (50%, 50%), + stroke: none, + fill: (_, y) => if calc.odd(y) { rgb("#efefef") } else { none }, + {% for name, value in table -%} + rowHead[{{ name | typst_escape }}], [{{ value | typst_escape }}], + {% endfor %} +) + +{{ description | md_to_typst }} diff --git a/fietsboek/pdf.py b/fietsboek/pdf.py new file mode 100644 index 0000000..de6e50d --- /dev/null +++ b/fietsboek/pdf.py @@ -0,0 +1,289 @@ +"""PDF generation for tracks. + +This module implements functionality that renders a PDF overview for a track. +The PDF overview consists of a map using OSM tiles, and a table with the +computed metadata. + +PDF generation is done using Typst_ via the `Python bindings`_. Typst provides +layouting and good typography without too much effort from our side. The Typst +file is generated from a Jinja2 template, saved to a temporary directory +together with the track image, and then compiled. + +.. _Typst: https://typst.app/ +.. _Python bindings: https://pypi.org/project/typst/ +""" + +import html.parser +import importlib.resources +import io +import logging +import tempfile +from itertools import chain +from pathlib import Path + +import jinja2 +import matplotlib +import typst +from babel.dates import format_datetime +from babel.numbers import format_decimal +from matplotlib import pyplot as plt +from pyramid.i18n import Localizer +from pyramid.i18n import TranslationString as _ + +from . import trackmap, util +from .config import TileLayerConfig +from .models import Track +from .models.track import TrackWithMetadata +from .views.tileproxy import TileRequester + +LOGGER = logging.getLogger(__name__) +TEMP_PREFIX = "fietsboek-typst-" +IMAGE_WIDTH = 1500 +IMAGE_HEIGHT = 500 +# See https://typst.app/docs/reference/syntax/ +TO_ESCAPE = { + "$", + "#", + "[", + "]", + "*", + "_", + "`", + "<", + ">", + "@", + "=", + "-", + "+", + "/", + "\\", +} + + +class HtmlToTypst(html.parser.HTMLParser): + """A parser that converts HTML to Typst syntax. + + This is adjusted for the HTML output that the markdown converted produces. + It will not work for arbitrary HTML. + """ + + # pylint: disable=too-many-branches + def __init__(self, out): + super().__init__() + self.out = out + + def handle_data(self, data): + self.out.write(typst_escape(data)) + + def handle_starttag(self, tag, attrs): + if tag == "strong": + self.out.write("#strong[") + elif tag == "em": + self.out.write("#emph[") + elif tag == "a": + href = "" + for key, val in attrs: + if key == "href": + href = val + self.out.write(f"#link({typst_string(href)})[") + elif tag == "ul": + self.out.write("#list(") + elif tag == "ol": + self.out.write("#enum(") + elif tag == "li": + self.out.write("[") + elif tag == "h1": + self.out.write("#heading(level: 1)[") + elif tag == "h2": + self.out.write("#heading(level: 2)[") + elif tag == "h3": + self.out.write("#heading(level: 3)[") + elif tag == "h4": + self.out.write("#heading(level: 4)[") + elif tag == "h5": + self.out.write("#heading(level: 5)[") + elif tag == "h6": + self.out.write("#heading(level: 6)[") + + def handle_endtag(self, tag): + if tag == "p": + self.out.write("\n\n") + elif tag == "strong": + self.out.write("]") + elif tag == "em": + self.out.write("]") + elif tag == "a": + self.out.write("]") + elif tag == "ul": + self.out.write(")") + elif tag == "ol": + self.out.write(")") + elif tag == "li": + self.out.write("],") + elif tag in {"h1", "h2", "h3", "h4", "h5", "h6"}: + self.out.write("]") + + +def typst_string(value: str) -> str: + """Serializes a string to a string that can be embedded in Typst source. + + This wraps the value in quotes, and escapes all characters. + + :param value: The value to serialize. + :return: The serialized string, ready to be embedded. + """ + return '"' + "".join(f"\\u{{{ord(char):x}}}" for char in value) + '"' + + +def typst_escape(value: str) -> str: + """Escapes Typst markup in the given value. + + :param value: The value to escape. + :return: The value with formatting characters escaped. + """ + return "".join("\\" + char if char in TO_ESCAPE else char for char in value) + + +def md_to_typst(value: str) -> str: + """Convert Markdown-formatted text to Typst source code. + + :param value: The Markdown source to convert. + :return: The Typst code. + """ + html_source = util.safe_markdown(value).unescape() + buffer = io.StringIO() + parser = HtmlToTypst(buffer) + parser.feed(html_source) + return buffer.getvalue() + + +def draw_map(track: Track, requester: TileRequester, tile_layer: TileLayerConfig, outfile: Path): + """Renders the track map. + + :param track: The track. + :param requester: The requester which is used to retrieve tiles. + :param tile_layer: The OSM tile layer configuration. + :param outfile: Path to the output file. + """ + map_image = trackmap.render( + track.path(), + tile_layer, + requester, + size=(IMAGE_WIDTH, IMAGE_HEIGHT), + ) + map_image.save(str(outfile)) + + +def draw_height_profile(track: Track, outfile: Path): + """Renders the height graph. + + :param track: The track. + :param outfile: The output file. + """ + x, y = [], [] + cur = 0.0 + last = None + for point in track.path().points: + x.append(cur / 1000) + y.append(point.elevation) + if last: + cur += point.distance(last) + last = point + + matplotlib.use("pdf") + fig, ax = plt.subplots(1, 1, figsize=(11, 3)) + ax.plot(x, y) + ax.grid() + ax.xaxis.set_major_formatter("{x} km") + ax.yaxis.set_major_formatter("{x} m") + fig.savefig(outfile, bbox_inches="tight") + + +def generate( + track: Track, requester: TileRequester, tile_layer: TileLayerConfig, localizer: Localizer +) -> bytes: + """Generate a PDF representation for the given track. + + :param track: The track for which to generate a PDF overview. + :param requester: The tile requester to render the track map. + :param tile_layer: The tile layer to use for the track map. + :param localizer: The localizer. + :return: The PDF bytes. + """ + # Yes, we could use f-strings, but I don't like embedding the huge + # expressions below in f-strings: + # pylint: disable=consider-using-f-string + # And this is a false positive for typst.compile: + # pylint: disable=no-member + env = jinja2.Environment( + loader=jinja2.PackageLoader("fietsboek", "pdf-assets"), + autoescape=False, + ) + env.filters["typst_escape"] = typst_escape + env.filters["md_to_typst"] = md_to_typst + + twm = TrackWithMetadata(track) + template = env.get_template("overview.typ") + translate = localizer.translate + locale = localizer.locale_name + placeholders = { + "title": track.title or track.date.strftime("%Y-%m-%d %H:%M"), + "people": [person.name for person in chain([track.owner], track.tagged_people)], + "table": [ + (translate(_("pdf.table.date")), format_datetime(track.date, locale=locale)), + ( + translate(_("pdf.table.length")), + "{} km".format(format_decimal(twm.length / 1000, locale=locale)), + ), + ( + translate(_("pdf.table.uphill")), + "{} m".format(format_decimal(twm.uphill, locale=locale)), + ), + ( + translate(_("pdf.table.downhill")), + "{} m".format(format_decimal(twm.downhill, locale=locale)), + ), + (translate(_("pdf.table.moving_time")), str(twm.moving_time)), + (translate(_("pdf.table.stopped_time")), str(twm.stopped_time)), + ( + translate(_("pdf.table.max_speed")), + "{} km/h".format(format_decimal(util.mps_to_kph(twm.max_speed), locale=locale)), + ), + ( + translate(_("pdf.table.avg_speed")), + "{} km/h".format(format_decimal(util.mps_to_kph(twm.avg_speed), locale=locale)), + ), + ], + "description": track.description, + } + + with tempfile.TemporaryDirectory(prefix=TEMP_PREFIX) as temp_dir_name: + temp_dir = Path(temp_dir_name) + LOGGER.debug("New PDF generation in %s", temp_dir) + + # importlib.resources.read_bytes cannot handle subdirs in 3.11 + font_data = ( + importlib.resources.files("fietsboek").joinpath("pdf-assets/Nunito.ttf").read_bytes() + ) + (temp_dir / "Nunito.ttf").write_bytes(font_data) + + draw_map(track, requester, tile_layer, temp_dir / "mapimage.png") + LOGGER.debug("%s: map drawn", temp_dir) + + draw_height_profile(track, temp_dir / "height_profile.pdf") + LOGGER.debug("%s: height profile drawn", temp_dir) + + rendered = template.render(placeholders) + LOGGER.debug("%s: typst template rendered", temp_dir) + + (temp_dir / "overview.typ").write_text(rendered) + pdf_bytes = typst.compile( + str(temp_dir / "overview.typ"), + font_paths=[str(temp_dir)], + ) + LOGGER.debug("%s: PDF rendering complete", temp_dir) + + return pdf_bytes + + +__all__ = ["generate"] diff --git a/fietsboek/routes.py b/fietsboek/routes.py index d5caef8..7042415 100644 --- a/fietsboek/routes.py +++ b/fietsboek/routes.py @@ -2,7 +2,7 @@ def includeme(config): - # pylint: disable=missing-function-docstring + # pylint: disable=missing-function-docstring,too-many-statements config.add_static_view("static", "static", cache_max_age=3600) config.add_route("home", "/") config.add_route("login", "/login") @@ -46,13 +46,51 @@ 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( + "track-pdf", + "/track/{track_id}/index.pdf", + factory="fietsboek.models.Track.factory", + ) + + config.add_route("journey-list", "/journey/") + config.add_route( + "journey-map", + "/journey/{journey_id}/preview", + factory="fietsboek.models.Journey.factory", + ) + config.add_route( + "journey-gpx", "/journey/{journey_id}/gpx", factory="fietsboek.models.Journey.factory" + ) + config.add_route( + "journey-details", "/journey/{journey_id}/", factory="fietsboek.models.Journey.factory" + ) + config.add_route( + "journey-edit", "/journey/{journey_id}/edit", factory="fietsboek.models.Journey.factory" + ) + config.add_route( + "journey-invalidate-share", + "/journey/{journey_id}/invalidate-link", + factory="fietsboek.models.Journey.factory", + ) + config.add_route( + "delete-journey", + "/journey/{journey_id}/delete", + factory="fietsboek.models.Journey.factory", + ) + config.add_route("journey-new", "/journey/new") 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..ea05f15 100644 --- a/fietsboek/scripts/fietscron.py +++ b/fietsboek/scripts/fietscron.py @@ -3,7 +3,6 @@ import datetime import logging import logging.config -from pathlib import Path import click import pyramid.paster @@ -14,10 +13,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 +33,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 +48,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) + rebuild_cache(engine) + 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.""" @@ -78,7 +83,7 @@ def remove_old_tokens(engine: Engine): session.commit() -def rebuild_cache(engine: Engine, data_manager: DataManager): +def rebuild_cache(engine: Engine): """Rebuilds the cache entries that are currently missing.""" LOGGER.debug("Rebuilding caches") session = Session(engine) @@ -89,12 +94,38 @@ def rebuild_cache(engine: Engine, data_manager: DataManager): for track in session.execute(needed_rebuilds).scalars(): assert track.id is not None LOGGER.info("Rebuilding cache for track %d", track.id) - gpx_data = data_manager.open(track.id).decompress_gpx() - track.ensure_cache(gpx_data) + track.ensure_cache() session.add(track) 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) + preview = trackmap.render(track.path(), 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: @@ -107,7 +138,6 @@ def run_hittekaart(engine: Engine, data_manager: DataManager, redis: Redis, conf # re-generate all maps over time (e.g. if the hittekaart version changes or # we miss an update). modes = [hittekaart.Mode(mode) for mode in config.hittekaart_autogenerate] - exe_path = Path(config.hittekaart_bin) if config.hittekaart_bin else None session = Session(engine) had_hq_item = False @@ -129,7 +159,6 @@ def run_hittekaart(engine: Engine, data_manager: DataManager, redis: Redis, conf session, data_manager, mode, - exe_path=exe_path, threads=config.hittekaart_threads, ) @@ -153,7 +182,7 @@ def run_hittekaart(engine: Engine, data_manager: DataManager, redis: Redis, conf for mode in modes: LOGGER.info("Generating %s for user %d (low-priority)", mode.value, user.id) hittekaart.generate_for( - user, session, data_manager, mode, exe_path=exe_path, threads=config.hittekaart_threads + user, session, data_manager, mode, threads=config.hittekaart_threads ) diff --git a/fietsboek/scripts/fietsctl.py b/fietsboek/scripts/fietsctl.py index ad06e91..fa751b7 100644 --- a/fietsboek/scripts/fietsctl.py +++ b/fietsboek/scripts/fietsctl.py @@ -313,11 +313,14 @@ def cmd_user_hittekaart( else: query = models.User.query_by_email(email) - exe_path = env["request"].config.hittekaart_bin threads = env["request"].config.hittekaart_threads with env["request"].tm: dbsession = env["request"].dbsession data_manager: DataManager = env["request"].data_manager + # We disable the transaction here to not cause issues with delayed + # folder creation. Heatmap generation is atomic anyway, as we only move + # one SQLite file. + data_manager.txn = None user = dbsession.execute(query).scalar_one_or_none() if user is None: click.echo("Error: No such user found.", err=True) @@ -337,9 +340,7 @@ def cmd_user_hittekaart( click.echo(f"Generating overlay maps for {click.style(user.name, fg=FG_USER_NAME)}...") for mode in modes: - hittekaart.generate_for( - user, dbsession, data_manager, mode, exe_path=exe_path, threads=threads - ) + hittekaart.generate_for(user, dbsession, data_manager, mode, threads=threads) click.echo(f"Generated {mode.value}") @@ -435,7 +436,9 @@ def cmd_maintenance_mode(ctx: click.Context, config: str, disable: bool, reason: maintenance mode. """ env = setup(config) - data_manager = env["request"].data_manager + # Create a fresh one to avoid creating (and having to deal with) + # transactions + data_manager = DataManager(env["request"].config.data_dir) if disable and reason: click.echo("Cannot enable and disable maintenance mode at the same time", err=True) ctx.exit(EXIT_FAILURE) @@ -448,6 +451,7 @@ def cmd_maintenance_mode(ctx: click.Context, config: str, disable: bool, reason: elif disable: (data_manager.data_dir / "MAINTENANCE").unlink() else: + assert reason is not None (data_manager.data_dir / "MAINTENANCE").write_text(reason, encoding="utf-8") 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/fietsboek.js b/fietsboek/static/fietsboek.js index bbdf6e9..d7d903e 100644 --- a/fietsboek/static/fietsboek.js +++ b/fietsboek/static/fietsboek.js @@ -445,6 +445,22 @@ function loadProfileStats() { } /* Used via in-page scripts, so make eslint happy */ loadProfileStats; +/** + * Formats the given timestamp to the user's locale. + * + * @param timestamp - The timestamp in milliseconds since the epoch. + * @return The formatted string. + */ +function formatTimestamp(timestamp) { + const date = new Date(timestamp); + // TypeScript complains about this, but according to MDN it is fine, at + // least in "somewhat modern" browsers + const intl = new Intl.DateTimeFormat(LOCALE, { + dateStyle: "medium", + timeStyle: "medium", + }); + return intl.format(date); +} document.addEventListener('DOMContentLoaded', function () { window.fietsboekImageIndex = 0; /* Enable tooltips */ @@ -466,14 +482,7 @@ document.addEventListener('DOMContentLoaded', function () { /* Format all datetimes to the local timezone */ document.querySelectorAll(".fietsboek-local-datetime").forEach((obj) => { const timestamp = parseFloat(obj.attributes.getNamedItem("data-utc-timestamp").value); - const date = new Date(timestamp * 1000); - // TypeScript complains about this, but according to MDN it is fine, at - // least in "somewhat modern" browsers - const intl = new Intl.DateTimeFormat(LOCALE, { - dateStyle: "medium", - timeStyle: "medium", - }); - obj.innerHTML = intl.format(date); + obj.innerHTML = formatTimestamp(timestamp * 1000); }); }); //# sourceMappingURL=fietsboek.js.map
\ No newline at end of file diff --git a/fietsboek/static/fietsboek.js.map b/fietsboek/static/fietsboek.js.map index 2299118..0dc6ff9 100644 --- a/fietsboek/static/fietsboek.js.map +++ b/fietsboek/static/fietsboek.js.map @@ -1 +1 @@ -{"version":3,"file":"fietsboek.js","sourceRoot":"","sources":["../../asset-sources/fietsboek.ts"],"names":[],"mappings":";AAqBA,kDAAkD;AAClD,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC;AAQpB;;;;;GAKG;AACH,SAAS,SAAS,CAAC,IAAY;;IAC3B,OAAO,MAAA,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;SAC7B,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,0CACxC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACxB,CAAC;AAGD;;;;;;;GAOG;AACH,SAAS,OAAO,CAAC,IAAY;IACzB,OAAO,IAAI,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,UAAU,CACf,QAAkB,EAClB,KAAQ,EACR,OAAoD;IAEpD,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAC/B,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAwB,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,KAAiB;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;IACxC,MAAM,QAAQ,GAAI,KAAK,CAAC,MAAsB,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;IAC7E,QAAQ,CAAC,MAAM,GAAG,oBAAoB,QAAQ,UAAU,IAAI,EAAE,CAAC;IAC/D,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,cAAc,EAAE,CAAC;AAC3B,CAAC;AAED,UAAU,CAAC,kBAAkB,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC;AAEzD;;;;GAIG;AACH,SAAS,UAAU,CAAC,KAAiB;IACjC,MAAM,IAAI,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,MAAM,CAAE,CAAC;IAC5D,IAAI,CAAC,UAAW,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,UAAU,CAAC,YAAY,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAE9C;;GAEG;AACH,SAAS,MAAM;;IACX,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAqB,CAAC;IACtE,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,EAAE;QACrB,OAAO;KACV;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAChC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAChC,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,CAAC,IAAI,GAAG,OAAO,CAAC;IACrB,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxB,MAAA,QAAQ,CAAC,aAAa,CAAC,WAAW,CAAC,0CAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAA,QAAQ,CAAC,aAAa,CAAC,WAAW,CAAC,0CAAE,WAAW,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;AACtB,CAAC;AAED,UAAU,CAAC,cAAc,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAC5C,uCAAuC;AACvC,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;IACzC,IAAI,KAAK,CAAC,IAAI,IAAI,OAAO,EAAE;QACvB,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,MAAM,EAAE,CAAC;KACZ;AACL,CAAC,CAAC,CAAC;AAEH;;;;;GAKG;AACH,SAAS,qBAAqB,CAAC,IAAc,EAAE,MAAgB;IAC3D,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAqB,CAAC;IACtE,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAqB,CAAC;IAE1E,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAE,CAAC;IAC3C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAEvC,sEAAsE;IACtE,sEAAsE;IACtE,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;QACjE,YAAY,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;KAC/C;SAAM;QACH,YAAY,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;KACtC;IAED,IAAI,YAAY,CAAC,KAAK,IAAI,cAAc,CAAC,KAAK,EAAE;QAC5C,cAAc,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;KACtD;SAAM;QACH,cAAc,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;KACxC;AACL,CAAC;AAED,2EAA2E;AAC3E,qBAAqB,CAAC;AAEtB;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,IAAc;IACrC,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAqB,CAAC;IACnE,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE;QAC7B,SAAS,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;KAC/C;AACL,CAAC;AAED,2EAA2E;AAC3E,iBAAiB,CAAC;AAElB;;;;;;;;GAQG;AACH,SAAS,cAAc,CAAC,QAAgB;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,CAAC;SACnE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAuB,CAAC;SACrC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;SAC9B,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC;SACvB,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa;IAClB,MAAM,aAAa,GAAI,QAAQ,CAAC,aAAa,CAAC,oBAAoB,CAAsB;QACpF,KAAK,CAAC,WAAW,EAAE,CAAC;IACxB,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAC,eAAe,CAAE,CAAC;IAC9D,YAAY,CAAC,SAAS,GAAG,EAAE,CAAC;IAC5B,KAAK,CAAC,WAAW,CAAC;SACb,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;SACnC,IAAI,CAAC,CAAC,QAAsB,EAAE,EAAE;QAC7B,MAAM,SAAS,GACV,QAAQ,CAAC,aAAa,CAAC,wBAAwB,CAAyB,CAAC,OAAO,CAAC;QAEtF,yCAAyC;QACzC,IAAI,OAAO,GAAG,QAAQ,CAAC,MAAM,CACzB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAC/D,CAAC;QAEF,8CAA8C;QAC9C,OAAO,GAAG,OAAO,CAAC,MAAM,CACpB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CACnC,CAAC;QAEF,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;;YACvB,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAqB,CAAC;YAC1D,IAAI,CAAC,aAAa,CAAC,cAAc,CAAqB,CAAC,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;YAClF,MAAA,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,0CAAE,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAiB,EAAE,EAAE;gBAC1E,MAAM,MAAM,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,QAAQ,CAAE,CAAC;gBAChE,MAAM,CAAC,UAAW,CAAC,UAAW,CAAC,WAAW,CAAC,MAAM,CAAC,UAAW,CAAC,CAAC;gBAE/D,MAAM,KAAK,GACN,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAA0B;qBACxE,OAAO;qBACP,SAAS,CAAC,IAAI,CAAqB,CAAC;gBACxC,KAAK,CAAC,aAAa,CAAC,cAAc,CAAqB;oBACpD,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;gBAC9B,KAAK,CAAC,aAAa,CAAC,OAAO,CAAE,CAAC,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC;gBAC3D,KAAK,CAAC,aAAa,CAAC,OAAO,CAAE,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;gBAC1D,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;gBAC9E,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACjE,CAAC,CAAC,CAAC;YACH,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACX,CAAC;AAED,UAAU,CAAC,iBAAiB,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;AAC9D,4CAA4C;AAC5C,UAAU,CAAC,oBAAoB,EAAE,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;IACnD,IAAI,KAAK,CAAC,IAAI,IAAI,OAAO,EAAE;QACvB,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,aAAa,EAAE,CAAC;KACnB;AACL,CAAC,CAAC,CAAC;AAEH;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,KAAiB;IAC1C,MAAM,MAAM,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,QAAQ,CAAE,CAAC;IAChE,MAAM,CAAC,UAAW,CAAC,UAAW,CAAC,WAAW,CAAC,MAAM,CAAC,UAAW,CAAC,CAAC;AACnE,CAAC;AAED,UAAU,CAAC,uBAAuB,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC;AAElE;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAAC,KAAY;;IACtC,MAAM,MAAM,GAAG,KAAK,CAAC,MAA0B,CAAC;IAChD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,MAAA,MAAM,CAAC,KAAK,mCAAI,EAAE,CAAC,EAAE;QAC/C,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAE7B,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC;QACpB,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;QACpB,KAAK,CAAC,IAAI,GAAG,SAAS,MAAM,CAAC,mBAAmB,GAAG,CAAC;QAEpD,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;QACpC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzB,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;QAE7B,MAAM,OAAO,GACR,QAAQ,CAAC,aAAa,CAAC,6BAA6B,CAAyB;YAC9E,OAAO;YACP,SAAS,CAAC,IAAI,CAAqB,CAAC;QACxC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAE,CAAC,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC9D,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAE;YACzC,gBAAgB,CAAC,OAAO,EAAE,wBAAyC,CAAC,CAAC;QACzE,OAAO,CAAC,aAAa,CAAC,+BAA+B,CAAE;YACnD,gBAAgB,CAAC,OAAO,EAAE,2BAA4C,CAAC,CAAC;QAC3E,OAAO,CAAC,aAAa,CAAC,+BAA+B,CAAsB;YACxE,IAAI,GAAG,qBAAqB,MAAM,CAAC,mBAAmB,GAAG,CAAC;QAC9D,OAAO,CAAC,aAAa,CAAC,KAAK,CAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAEjD,QAAQ,CAAC,aAAa,CAAC,iBAAiB,CAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;KACnE;IAED,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;AACtB,CAAC;AAED,UAAU,CAAC,gBAAgB,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC;AAE7D;;;;GAIG;AACH,SAAS,wBAAwB,CAAC,KAAiB;IAC/C,MAAM,OAAO,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,yBAAyB,CAAE,CAAC;IAClF,8DAA8D;IAC9D,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACxD,IAAI,KAAK,EAAE;QACP,OAAO,CAAC,UAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACzC,OAAO;KACV;IAED,4EAA4E;IAC5E,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,2BAA2B,CAAE,CAAC;IACpE,OAAO,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;IACpC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7B,OAAO,CAAC,UAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,UAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED,UAAU,CAAC,qBAAqB,EAAE,OAAO,EAAE,wBAAwB,CAAC,CAAC;AAErE;;;;GAIG;AACH,SAAS,2BAA2B,CAAC,KAAiB;IAClD,MAAM,CAAC,qBAAqB,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,KAAK,CAAE,CAAC;IAE7E,MAAM,UAAU,GAEZ,MAAM,CAAC,qBAAqB,CAAC,aAAa,CAAC,+BAA+B,CAC7E,CAAC;IACF,MAAM,kBAAkB,GAAG,UAAU,CAAC,KAAK,CAAC;IAC5C,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,uBAAuB,CAAE,CAAC;IACnE,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAE,CAAC,KAAK,GAAG,kBAAkB,CAAC;IAE/D,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAChE,KAAK,CAAC,IAAI,EAAE,CAAC;AACjB,CAAC;AAED,UAAU,CAAC,+BAA+B,EAAE,OAAO,EAAE,2BAA2B,CAAC,CAAC;AAElF;;;;GAIG;AACH,SAAS,2BAA2B,CAAC,MAAkB;IACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,uBAAuB,CAAE,CAAC;IACnE,MAAM,iBAAiB,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAE,CAAC,KAAK,CAAC;IACnE,MAAM,CAAC,qBAAsB;QAC1B,aAAa,CAAC,+BAA+B,CAAsB;QACnE,KAAK,GAAG,iBAAiB,CAAC;IAC9B,MAAM,CAAC,qBAAsB;QACzB,aAAa,CAAC,KAAK,CAAE,CAAC,KAAK,GAAG,iBAAiB,CAAC;IAEpD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAChE,KAAK,CAAC,IAAI,EAAE,CAAC;IAEb,MAAM,CAAC,qBAAqB,GAAG,IAAI,CAAC;AACxC,CAAC;AAED,UAAU,CAAC,2CAA2C,EAAE,OAAO,EAAE,2BAA2B,CAAC,CAAC;AAE9F;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAiB;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAqB,CAAC;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,kBAAmB,CAAC;IAC/C,SAAS,CAAC,QAAQ,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;IACzD,IAAI,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE;QAC/C,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAC5C,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;KAC7C;SAAM;QACH,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAC7C,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;KAC5C;AACL,CAAC;AAED,UAAU,CAAC,kBAAkB,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AAEvD;;GAEG;AACH,UAAU,CAAC,wBAAwB,EAAE,OAAO,EAAE,GAAG,EAAE;IAC/C,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,CAAC;IACvE,MAAM,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACrC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QAClB,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,YAAY,EAAG,CAAsB,CAAC,KAAK,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AACH;;GAEG;AACH,UAAU,CAAC,mBAAmB,EAAE,QAAQ,EAAE,GAAG,EAAE;IAC3C,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,CAAC;IACvE,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,wBAAwB,CAAsB,CAAC;IAC7F,cAAc,CAAC,QAAQ,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,SAAS,uBAAuB,CAAC,KAAiB;IAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAC;IAC3C,MAAM,CAAC,OAAO,CAAC,cAAc,CAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IACvF,MAAM,CAAC,OAAO,CAAC,cAAc,CAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;AAC5F,CAAC;AAED,UAAU,CAAC,qBAAqB,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAC;AAGpE;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,MAAkB;;IACzC,MAAM,cAAc,GAAG,MAAA,SAAS,CAAC,cAAc,CAAC,mCAAI,KAAK,CAAC;IAC1D,MAAM,UAAU,GAAG,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5D,QAAQ,CAAC,MAAM,GAAG,gBAAgB,UAAU,gBAAgB,CAAC;IAC7D,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED,UAAU,CAAC,oBAAoB,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC;AAG7D;;;;;;;GAOG;AACH,SAAS,oBAAoB,CAAC,KAAiB;;IAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAC;IAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;IACrD,IAAI,OAAO,KAAK,IAAI,EAAE;QAClB,OAAO;KACV;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAA,SAAS,CAAC,YAAY,CAAC,mCAAI,EAAE,CAAC,CAAC;IAC7D,KAAK,CAAC,GAAG,EAAE;QACP,QAAQ,EAAE,MAAM;QAChB,MAAM,EAAE,QAAQ;KACnB,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;QACzC,IAAI,cAAc,EAAE;YAChB,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;SACvD;aAAM;YACH,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;SACvD;IACL,CAAC,CAAC,CAAC,CAAC;AACR,CAAC;AAED,UAAU,CAAC,iBAAiB,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAAC;AAE7D;;GAEG;AACH,SAAS,sBAAsB;IAC3B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;QACzB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,EAAC,KAAK,EAAE,MAAM,EAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;KACxB;IACD,OAAO,UAAU,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB;IACrB,MAAM,UAAU,GAAG,sBAAsB,EAAE,CAAC;IAE5C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACvC,KAAK,CAAC,GAAG,CAAC;SACL,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;SACjC,IAAI,CAAC,CAAC,QAAqB,EAAE,EAAE;QAC5B,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;YACnD,MAAM,IAAI,GAAG,EAAE,CAAC;YAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE;gBAC1B,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aACrD;YACD,QAAQ,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,IAAI;gBACV,KAAK,EAAE,IAAI;aACd,CAAC,CAAC;SACN;QAED,IAAI,KAAK,CACL,qBAAqB,EACrB;YACI,IAAI,EAAE,KAAK;YACX,IAAI,EAAE;gBACF,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE,UAAU;aACrB;SACJ,CACJ,CAAC;IACN,CAAC,CAAC,CAAC;AACX,CAAC;AAED,oDAAoD;AACpD,gBAAgB,CAAC;AAEjB,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE;IAC1C,MAAM,CAAC,mBAAmB,GAAG,CAAC,CAAC;IAE/B,qBAAqB;IACrB,MAAM,kBAAkB,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CACpC,QAAQ,CAAC,gBAAgB,CAAC,4BAA4B,CAAC,CAC1D,CAAC;IACF,kBAAkB,CAAC,GAAG,CAAC,CAAC,gBAAgB,EAAE,EAAE;QACxC,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,sCAAsC;IACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;IAC7D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAC/B,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YACtC,IAAI,CAAE,IAAwB,CAAC,aAAa,EAAE,EAAE;gBAC5C,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,KAAK,CAAC,eAAe,EAAE,CAAC;aAC3B;YAED,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QACxC,CAAC,EAAE,KAAK,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,gDAAgD;IAChD,QAAQ,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACnE,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,oBAAoB,CAAE,CAAC,KAAK,CAAC,CAAC;QACvF,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;QACxC,uEAAuE;QACvE,sCAAsC;QACtC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE;YACzC,SAAS,EAAE,QAAQ;YACnB,SAAS,EAAE,QAAQ;SACf,CAAC,CAAC;QACV,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
\ No newline at end of file +{"version":3,"file":"fietsboek.js","sourceRoot":"","sources":["../../asset-sources/fietsboek.ts"],"names":[],"mappings":";AAqBA,kDAAkD;AAClD,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC;AAQpB;;;;;GAKG;AACH,SAAS,SAAS,CAAC,IAAY;;IAC3B,OAAO,MAAA,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;SAC7B,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,0CACxC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AACxB,CAAC;AAGD;;;;;;;GAOG;AACH,SAAS,OAAO,CAAC,IAAY;IACzB,OAAO,IAAI,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;GAMG;AACH,SAAS,UAAU,CACf,QAAkB,EAClB,KAAQ,EACR,OAAoD;IAEpD,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QAC/B,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAwB,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,KAAiB;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;IACxC,MAAM,QAAQ,GAAI,KAAK,CAAC,MAAsB,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;IAC7E,QAAQ,CAAC,MAAM,GAAG,oBAAoB,QAAQ,UAAU,IAAI,EAAE,CAAC;IAC/D,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,cAAc,EAAE,CAAC;AAC3B,CAAC;AAED,UAAU,CAAC,kBAAkB,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC;AAEzD;;;;GAIG;AACH,SAAS,UAAU,CAAC,KAAiB;IACjC,MAAM,IAAI,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,MAAM,CAAE,CAAC;IAC5D,IAAI,CAAC,UAAW,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,UAAU,CAAC,YAAY,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAE9C;;GAEG;AACH,SAAS,MAAM;;IACX,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAqB,CAAC;IACtE,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,EAAE;QACrB,OAAO;KACV;IACD,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAChC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAChC,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,CAAC,IAAI,GAAG,OAAO,CAAC;IACrB,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC3B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxB,MAAA,QAAQ,CAAC,aAAa,CAAC,WAAW,CAAC,0CAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAA,QAAQ,CAAC,aAAa,CAAC,WAAW,CAAC,0CAAE,WAAW,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;AACtB,CAAC;AAED,UAAU,CAAC,cAAc,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAC5C,uCAAuC;AACvC,UAAU,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;IACzC,IAAI,KAAK,CAAC,IAAI,IAAI,OAAO,EAAE;QACvB,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,MAAM,EAAE,CAAC;KACZ;AACL,CAAC,CAAC,CAAC;AAEH;;;;;GAKG;AACH,SAAS,qBAAqB,CAAC,IAAc,EAAE,MAAgB;IAC3D,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAqB,CAAC;IACtE,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAqB,CAAC;IAE1E,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAE,CAAC;IAC3C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAEvC,sEAAsE;IACtE,sEAAsE;IACtE,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;QACjE,YAAY,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;KAC/C;SAAM;QACH,YAAY,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;KACtC;IAED,IAAI,YAAY,CAAC,KAAK,IAAI,cAAc,CAAC,KAAK,EAAE;QAC5C,cAAc,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;KACtD;SAAM;QACH,cAAc,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;KACxC;AACL,CAAC;AAED,2EAA2E;AAC3E,qBAAqB,CAAC;AAEtB;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,IAAc;IACrC,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAqB,CAAC;IACnE,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE;QAC7B,SAAS,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;KAC/C;AACL,CAAC;AAED,2EAA2E;AAC3E,iBAAiB,CAAC;AAElB;;;;;;;;GAQG;AACH,SAAS,cAAc,CAAC,QAAgB;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,CAAC;SACnE,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAuB,CAAC;SACrC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;SAC9B,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC;SACvB,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa;IAClB,MAAM,aAAa,GAAI,QAAQ,CAAC,aAAa,CAAC,oBAAoB,CAAsB;QACpF,KAAK,CAAC,WAAW,EAAE,CAAC;IACxB,MAAM,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAC,eAAe,CAAE,CAAC;IAC9D,YAAY,CAAC,SAAS,GAAG,EAAE,CAAC;IAC5B,KAAK,CAAC,WAAW,CAAC;SACb,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;SACnC,IAAI,CAAC,CAAC,QAAsB,EAAE,EAAE;QAC7B,MAAM,SAAS,GACV,QAAQ,CAAC,aAAa,CAAC,wBAAwB,CAAyB,CAAC,OAAO,CAAC;QAEtF,yCAAyC;QACzC,IAAI,OAAO,GAAG,QAAQ,CAAC,MAAM,CACzB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAC/D,CAAC;QAEF,8CAA8C;QAC9C,OAAO,GAAG,OAAO,CAAC,MAAM,CACpB,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CACnC,CAAC;QAEF,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;;YACvB,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,IAAI,CAAqB,CAAC;YAC1D,IAAI,CAAC,aAAa,CAAC,cAAc,CAAqB,CAAC,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;YAClF,MAAA,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,0CAAE,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAiB,EAAE,EAAE;gBAC1E,MAAM,MAAM,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,QAAQ,CAAE,CAAC;gBAChE,MAAM,CAAC,UAAW,CAAC,UAAW,CAAC,WAAW,CAAC,MAAM,CAAC,UAAW,CAAC,CAAC;gBAE/D,MAAM,KAAK,GACN,QAAQ,CAAC,aAAa,CAAC,uBAAuB,CAA0B;qBACxE,OAAO;qBACP,SAAS,CAAC,IAAI,CAAqB,CAAC;gBACxC,KAAK,CAAC,aAAa,CAAC,cAAc,CAAqB;oBACpD,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;gBAC9B,KAAK,CAAC,aAAa,CAAC,OAAO,CAAE,CAAC,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC;gBAC3D,KAAK,CAAC,aAAa,CAAC,OAAO,CAAE,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;gBAC1D,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;gBAC9E,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACjE,CAAC,CAAC,CAAC;YACH,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACX,CAAC;AAED,UAAU,CAAC,iBAAiB,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE,CAAC,CAAC;AAC9D,4CAA4C;AAC5C,UAAU,CAAC,oBAAoB,EAAE,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;IACnD,IAAI,KAAK,CAAC,IAAI,IAAI,OAAO,EAAE;QACvB,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,aAAa,EAAE,CAAC;KACnB;AACL,CAAC,CAAC,CAAC;AAEH;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,KAAiB;IAC1C,MAAM,MAAM,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,QAAQ,CAAE,CAAC;IAChE,MAAM,CAAC,UAAW,CAAC,UAAW,CAAC,WAAW,CAAC,MAAM,CAAC,UAAW,CAAC,CAAC;AACnE,CAAC;AAED,UAAU,CAAC,uBAAuB,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC;AAElE;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAAC,KAAY;;IACtC,MAAM,MAAM,GAAG,KAAK,CAAC,MAA0B,CAAC;IAChD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,MAAA,MAAM,CAAC,KAAK,mCAAI,EAAE,CAAC,EAAE;QAC/C,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAE7B,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC;QACpB,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;QACpB,KAAK,CAAC,IAAI,GAAG,SAAS,MAAM,CAAC,mBAAmB,GAAG,CAAC;QAEpD,MAAM,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;QACpC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzB,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;QAE7B,MAAM,OAAO,GACR,QAAQ,CAAC,aAAa,CAAC,6BAA6B,CAAyB;YAC9E,OAAO;YACP,SAAS,CAAC,IAAI,CAAqB,CAAC;QACxC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAE,CAAC,GAAG,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC9D,OAAO,CAAC,aAAa,CAAC,qBAAqB,CAAE;YACzC,gBAAgB,CAAC,OAAO,EAAE,wBAAyC,CAAC,CAAC;QACzE,OAAO,CAAC,aAAa,CAAC,+BAA+B,CAAE;YACnD,gBAAgB,CAAC,OAAO,EAAE,2BAA4C,CAAC,CAAC;QAC3E,OAAO,CAAC,aAAa,CAAC,+BAA+B,CAAsB;YACxE,IAAI,GAAG,qBAAqB,MAAM,CAAC,mBAAmB,GAAG,CAAC;QAC9D,OAAO,CAAC,aAAa,CAAC,KAAK,CAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAEjD,QAAQ,CAAC,aAAa,CAAC,iBAAiB,CAAE,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;KACnE;IAED,MAAM,CAAC,KAAK,GAAG,EAAE,CAAC;AACtB,CAAC;AAED,UAAU,CAAC,gBAAgB,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC;AAE7D;;;;GAIG;AACH,SAAS,wBAAwB,CAAC,KAAiB;IAC/C,MAAM,OAAO,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,yBAAyB,CAAE,CAAC;IAClF,8DAA8D;IAC9D,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACxD,IAAI,KAAK,EAAE;QACP,OAAO,CAAC,UAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACzC,OAAO;KACV;IAED,4EAA4E;IAC5E,MAAM,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,2BAA2B,CAAE,CAAC;IACpE,OAAO,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;IACpC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7B,OAAO,CAAC,UAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,UAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED,UAAU,CAAC,qBAAqB,EAAE,OAAO,EAAE,wBAAwB,CAAC,CAAC;AAErE;;;;GAIG;AACH,SAAS,2BAA2B,CAAC,KAAiB;IAClD,MAAM,CAAC,qBAAqB,GAAI,KAAK,CAAC,MAAsB,CAAC,OAAO,CAAC,KAAK,CAAE,CAAC;IAE7E,MAAM,UAAU,GAEZ,MAAM,CAAC,qBAAqB,CAAC,aAAa,CAAC,+BAA+B,CAC7E,CAAC;IACF,MAAM,kBAAkB,GAAG,UAAU,CAAC,KAAK,CAAC;IAC5C,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,uBAAuB,CAAE,CAAC;IACnE,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAE,CAAC,KAAK,GAAG,kBAAkB,CAAC;IAE/D,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAChE,KAAK,CAAC,IAAI,EAAE,CAAC;AACjB,CAAC;AAED,UAAU,CAAC,+BAA+B,EAAE,OAAO,EAAE,2BAA2B,CAAC,CAAC;AAElF;;;;GAIG;AACH,SAAS,2BAA2B,CAAC,MAAkB;IACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,cAAc,CAAC,uBAAuB,CAAE,CAAC;IACnE,MAAM,iBAAiB,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAE,CAAC,KAAK,CAAC;IACnE,MAAM,CAAC,qBAAsB;QAC1B,aAAa,CAAC,+BAA+B,CAAsB;QACnE,KAAK,GAAG,iBAAiB,CAAC;IAC9B,MAAM,CAAC,qBAAsB;QACzB,aAAa,CAAC,KAAK,CAAE,CAAC,KAAK,GAAG,iBAAiB,CAAC;IAEpD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,mBAAmB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAChE,KAAK,CAAC,IAAI,EAAE,CAAC;IAEb,MAAM,CAAC,qBAAqB,GAAG,IAAI,CAAC;AACxC,CAAC;AAED,UAAU,CAAC,2CAA2C,EAAE,OAAO,EAAE,2BAA2B,CAAC,CAAC;AAE9F;;;;GAIG;AACH,SAAS,aAAa,CAAC,KAAiB;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAqB,CAAC;IAC5C,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,kBAAmB,CAAC;IAC/C,SAAS,CAAC,QAAQ,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;IACzD,IAAI,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE;QAC/C,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAC5C,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;KAC7C;SAAM;QACH,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAC7C,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;KAC5C;AACL,CAAC;AAED,UAAU,CAAC,kBAAkB,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;AAEvD;;GAEG;AACH,UAAU,CAAC,wBAAwB,EAAE,OAAO,EAAE,GAAG,EAAE;IAC/C,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,CAAC;IACvE,MAAM,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACrC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QAClB,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,YAAY,EAAG,CAAsB,CAAC,KAAK,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC,CAAC,CAAC;AACH;;GAEG;AACH,UAAU,CAAC,mBAAmB,EAAE,QAAQ,EAAE,GAAG,EAAE;IAC3C,MAAM,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,CAAC;IACvE,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,wBAAwB,CAAsB,CAAC;IAC7F,cAAc,CAAC,QAAQ,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,SAAS,uBAAuB,CAAC,KAAiB;IAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAC;IAC3C,MAAM,CAAC,OAAO,CAAC,cAAc,CAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;IACvF,MAAM,CAAC,OAAO,CAAC,cAAc,CAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;AAC5F,CAAC;AAED,UAAU,CAAC,qBAAqB,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAC;AAGpE;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,MAAkB;;IACzC,MAAM,cAAc,GAAG,MAAA,SAAS,CAAC,cAAc,CAAC,mCAAI,KAAK,CAAC;IAC1D,MAAM,UAAU,GAAG,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;IAC5D,QAAQ,CAAC,MAAM,GAAG,gBAAgB,UAAU,gBAAgB,CAAC;IAC7D,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED,UAAU,CAAC,oBAAoB,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC;AAG7D;;;;;;;GAOG;AACH,SAAS,oBAAoB,CAAC,KAAiB;;IAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAC;IAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,eAAe,CAAC,CAAC;IACrD,IAAI,OAAO,KAAK,IAAI,EAAE;QAClB,OAAO;KACV;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAA,SAAS,CAAC,YAAY,CAAC,mCAAI,EAAE,CAAC,CAAC;IAC7D,KAAK,CAAC,GAAG,EAAE;QACP,QAAQ,EAAE,MAAM;QAChB,MAAM,EAAE,QAAQ;KACnB,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;QACzC,IAAI,cAAc,EAAE;YAChB,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;SACvD;aAAM;YACH,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;SACvD;IACL,CAAC,CAAC,CAAC,CAAC;AACR,CAAC;AAED,UAAU,CAAC,iBAAiB,EAAE,OAAO,EAAE,oBAAoB,CAAC,CAAC;AAE7D;;GAEG;AACH,SAAS,sBAAsB;IAC3B,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE;QACzB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,EAAC,KAAK,EAAE,MAAM,EAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;KACxB;IACD,OAAO,UAAU,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB;IACrB,MAAM,UAAU,GAAG,sBAAsB,EAAE,CAAC;IAE5C,MAAM,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACvC,KAAK,CAAC,GAAG,CAAC;SACL,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;SACjC,IAAI,CAAC,CAAC,QAAqB,EAAE,EAAE;QAC5B,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;YACnD,MAAM,IAAI,GAAG,EAAE,CAAC;YAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,EAAE;gBAC1B,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aACrD;YACD,QAAQ,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,IAAI;gBACV,KAAK,EAAE,IAAI;aACd,CAAC,CAAC;SACN;QAED,IAAI,KAAK,CACL,qBAAqB,EACrB;YACI,IAAI,EAAE,KAAK;YACX,IAAI,EAAE;gBACF,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE,UAAU;aACrB;SACJ,CACJ,CAAC;IACN,CAAC,CAAC,CAAC;AACX,CAAC;AAED,oDAAoD;AACpD,gBAAgB,CAAC;AAEjB;;;;;GAKG;AACH,SAAS,eAAe,CAAC,SAAiB;IACtC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;IACjC,uEAAuE;IACvE,sCAAsC;IACtC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE;QACzC,SAAS,EAAE,QAAQ;QACnB,SAAS,EAAE,QAAQ;KACf,CAAC,CAAC;IACV,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE;IAC1C,MAAM,CAAC,mBAAmB,GAAG,CAAC,CAAC;IAE/B,qBAAqB;IACrB,MAAM,kBAAkB,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CACpC,QAAQ,CAAC,gBAAgB,CAAC,4BAA4B,CAAC,CAC1D,CAAC;IACF,kBAAkB,CAAC,GAAG,CAAC,CAAC,gBAAgB,EAAE,EAAE;QACxC,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,sCAAsC;IACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;IAC7D,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAC/B,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YACtC,IAAI,CAAE,IAAwB,CAAC,aAAa,EAAE,EAAE;gBAC5C,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,KAAK,CAAC,eAAe,EAAE,CAAC;aAC3B;YAED,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QACxC,CAAC,EAAE,KAAK,CAAC,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,gDAAgD;IAChD,QAAQ,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACnE,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,oBAAoB,CAAE,CAAC,KAAK,CAAC,CAAC;QACvF,GAAG,CAAC,SAAS,GAAG,eAAe,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
\ 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..fbb626b --- /dev/null +++ b/fietsboek/templates/admin_overview.jinja2 @@ -0,0 +1,97 @@ +{% set admin_index = 0 %} +{% extends "admin.jinja2" %} +{% block admin_content %} +<p class="admin-stat"> + {{ _("admin.overview.instance_has") }}… +</p> + +<p class="admin-stat"> + … {{ ngettext("admin.overview.stat.user", "admin.overview.stat.users", user_count) }} +</p> + +<p class="admin-stat"> + … {{ ngettext("admin.overview.stat.track", "admin.overview.stat.tracks", track_count) }} +</p> + +<p class="admin-stat"> + … {{ (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.track_data") | tojson }}, + {{ _("admin.overview.storage_graph.label.backups") | tojson }}, + {{ _("admin.overview.storage_graph.label.images") | tojson }}, + {{ _("admin.overview.storage_graph.label.track_previews") | tojson }}, + {{ _("admin.overview.storage_graph.label.journey_previews") | tojson }}, + {{ _("admin.overview.storage_graph.label.user_maps") | tojson }} + ], + datasets: [ + { + label: "MiB", + data: [ + {{ (size_breakdown.track_data / 1024 / 1024) | tojson }}, + {{ (size_breakdown.backups / 1024 / 1024) | tojson }}, + {{ (size_breakdown.image_files / 1024 / 1024) | tojson }}, + {{ (size_breakdown.track_previews / 1024 / 1024) | tojson }}, + {{ (size_breakdown.journey_previews / 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/details.jinja2 b/fietsboek/templates/details.jinja2 index d8c9250..f97f01f 100644 --- a/fietsboek/templates/details.jinja2 +++ b/fietsboek/templates/details.jinja2 @@ -1,6 +1,23 @@ {% extends "layout.jinja2" %} {% import "util.jinja2" as util with context %} +{% block extrahead %} +{% if 'secret' in request.GET %} +{% set preview_url = request.route_url('track-map', track_id=track.id, _query=[('secret', request.GET['secret'])]) %} +{% else %} +{% set preview_url = request.route_url('track-map', track_id=track.id) %} +{% endif %} +<meta property="og:title" content="{{ track.title | default(track.date | format_datetime, true) }}"> +<meta property="og:type" content="website"> +<meta property="og:url" content="{{ request.url }}"> +<meta property="og:image" content="{{ preview_url }}"> +<meta property="og:image:url" content="{{ preview_url }}"> +<meta property="og:image:type" content="image/png"> +<meta property="og:image:alt" content="Track overview"> +<meta property="og:description" content="{{ (track.length / 1000) | round(2) | format_decimal }} km{% if og_description %}: {{ og_description }}{% endif %}"> +<meta property="og:site_name" content="Fietsboek"> +{% endblock %} + {% block content %} <div class="container"> <h1> @@ -94,7 +111,14 @@ </div> <div class="mb-3"> - <a class="btn btn-primary ui-element" href="{{ gpx_url }}"><i class="bi bi-download"></i> {{ _("page.details.download") }}</a> + <div class="btn-group" role="group"> + <a class="btn btn-primary ui-element" href="{{ gpx_url }}"><i class="bi bi-download"></i> {{ _("page.details.download") }}</a> + <a class="btn btn-secondary ui-element" href="{% if 'secret' in request.GET %} + {{ request.route_path('track-pdf', track_id=track.id, _query=[('secret', request.GET['secret'])]) }} + {% else %} + {{ request.route_path('track-pdf', track_id=track.id) }} + {% endif %}"><i class="bi bi-file-earmark-pdf"></i> {{ _("page.details.download_pdf") }}</a> + </div> </div> <table class="table table-hover table-sm"> 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/journey_details.jinja2 b/fietsboek/templates/journey_details.jinja2 new file mode 100644 index 0000000..a4941e5 --- /dev/null +++ b/fietsboek/templates/journey_details.jinja2 @@ -0,0 +1,180 @@ +{% extends "layout.jinja2" %} +{% import "util.jinja2" as util with context %} + +{% block content %} +<div class="container"> + <h1>{{ journey.title }}</h1> + + {% if show_edit_link %} + <div class="btn-group mb-3" role="group"> + <a class="btn btn-success ui-element" href="{{ request.route_path('journey-edit', journey_id=journey.id) }}"><i class="bi-pencil-square"></i> {{ _("journey.edit") }}</a> + <button type="button" class="btn btn-info ui-element" id="showShareLink" data-bs-toggle="modal" data-bs-target="#shareLinkModal"><i class="bi-share"></i> {{ _("journey.share") }}</button> + <button type="button" class="btn btn-danger ui-element" id="deleteLink" data-bs-toggle="modal" data-bs-target="#deleteModal"><i class="bi bi-trash"></i> {{ _("journey.delete") }}</button> + </div> + <div class="modal fade" id="shareLinkModal" tabindex="-1" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title">{{ _("journey.sharelink.title") }}</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + <p>{{ _("journey.sharelink.info") }}</p> + {% set share_link = request.route_url('journey-details', journey_id=journey.id, _query=[("secret", journey.link_secret)]) %} + <a href="{{ share_link }}">{{ share_link }}</a> + </div> + <div class="modal-footer"> + <form method="POST" action="{{ request.route_url('journey-invalidate-share', journey_id=journey.id) }}"> + {{ util.hidden_csrf_input() }} + <button type="submit" class="btn btn-warning ui-element">{{ _("journey.sharelink.invalidate") }}</button> + </form> + <button type="button" class="btn btn-secondary ui-element" data-bs-dismiss="modal">{{ _("journey.sharelink.close") }}</button> + </div> + </div> + </div> + </div> + + <div class="modal fade" id="deleteModal" tabindex="-1" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title">{{ _("journey.delete.title") }}</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + <p>{{ _("journey.delete.info") }}</p> + </div> + <div class="modal-footer"> + <form method="POST" action="{{ request.route_url('delete-journey', journey_id=journey.id) }}"> + {{ util.hidden_csrf_input() }} + <button type="submit" class="btn btn-danger ui-element">{{ _("journey.delete.delete") }}</button> + </form> + <button type="button" class="btn btn-secondary ui-element" data-bs-dismiss="modal">{{ _("journey.delete.close") }}</button> + </div> + </div> + </div> + </div> + {% endif %} + + {% if 'secret' in request.GET %} + {% set gpx_url = request.route_path("journey-gpx", journey_id=journey.id, _query=[('secret', request.GET['secret'])]) %} + {% else %} + {% set gpx_url = request.route_path("journey-gpx", journey_id=journey.id) %} + {% endif %} + <div class="mb-3"> + <div id="mainmap" class="gpxview:{{ gpx_url }}:OSM" style="width:100%;height:600px"> + <noscript><p>{{ _("page.noscript") }}<p></noscript> + </div> + </div> + <div class="mb-3"> + <div id="mainmap_hp" style="width:100%;height:300px"></div> + </div> + + <table class="table table-hover" style="margin-top: 10px;"> + <tbody> + <tr> + <th scope="row">{{ _("page.details.length") }}</th> + <td id="detailsLength">{{ (movement_data.length / 1000) | round(2) | format_decimal }} km</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.uphill") }}</th> + <td id="detailsUphill">{{ movement_data.uphill | round(2) | format_decimal }} m</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.downhill") }}</th> + <td id="detailsDownhill">{{ movement_data.downhill | round(2) | format_decimal }} m</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.moving_time") }}</th> + <td id="detailsDownhill">{{ timedelta(seconds=movement_data.moving_duration) }}</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.stopped_time") }}</th> + <td id="detailsDownhill">{{ timedelta(seconds=movement_data.stopped_duration) }}</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.max_speed") }}</th> + <td id="detailsDownhill">{{ mps_to_kph(movement_data.maximum_speed) | round(2) | format_decimal }} km/h</td> + </tr> + <tr> + <th scope="row">{{ _("page.details.avg_speed") }}</th> + <td id="detailsDownhill">{{ mps_to_kph(movement_data.average_speed) | round(2) | format_decimal }} km/h</td> + </tr> + </tbody> + </table> + + {{ md_to_html(journey.description) }} + + <h2>{{ _("journey.tracks") }}</h2> + + {% for track in tracks %} + {% if track.track.is_visible_to(request.identity) %} + <div class="card mb-3"> + <h5 class="card-header"> + <a href="{{ request.route_url('details', track_id=track.id) }}">{{ track.title | default(track.date, true) }}</a> + </h5> + <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> + </div> + </div> + </div> + {% else %} + <div class="card mb-3"> + <h5 class="card-header"> + {{ track.title | default(track.date, true) }} + </h5> + <div class="card-body"> + {{ _("journeys.track.hidden") }} + </div> + </div> + {% endif %} + {% endfor %} +</div> +{% endblock %} diff --git a/fietsboek/templates/journey_edit.jinja2 b/fietsboek/templates/journey_edit.jinja2 new file mode 100644 index 0000000..fa39228 --- /dev/null +++ b/fietsboek/templates/journey_edit.jinja2 @@ -0,0 +1,21 @@ +{% extends "layout.jinja2" %} +{% import "journey_form.jinja2" as form with context %} + +{% block extrahead %} +{{ form.journey_css() }} +{% endblock %} + +{% block content %} +<div class="container"> + <h1>{{ journey.title }}</h1> + + <form method="POST"> + {{ form.journey_form(journey) }} + </form> +</div> + +{% endblock %} + +{% block latescripts %} +{{ form.journey_js() }} +{% endblock %} diff --git a/fietsboek/templates/journey_form.jinja2 b/fietsboek/templates/journey_form.jinja2 new file mode 100644 index 0000000..3776b6c --- /dev/null +++ b/fietsboek/templates/journey_form.jinja2 @@ -0,0 +1,261 @@ +{% import "util.jinja2" as util with context %} + +{% macro journey_css() %} +<style> +.track-query-response, .journey-track { + background-color: var(--bs-body-bg); + padding: 0.375rem; + margin-bottom: 0.1rem; + display: flex; + align-items: center; + gap: 1rem; + + .track-title { + font-weight: 450; + font-size: 110%; + } + + .track-date { + color: #808080; + } + + .track-length { + color: #808080; + } +} + +.journey-track { + cursor: grab; +} + +.dragging { + opacity: 0.7; +} +</style> +{% endmacro %} + + +{% macro journey_form(journey) %} +<div class="mb-3"> + <label for="journeyTitle" class="form-label">{{ _("journeys.new.form.title") }}</label> + <input type="text" class="form-control" id="journeyTitle" name="journeyTitle" value="{{ journey.title }}" onchange="checkTitleValidity()"> + <div class="invalid-feedback"> + {{ _("journeys.new.form.requires_title") }} + </div> +</div> +<div class="mb-3"> + <label for="journeyDescription" class="form-label">{{ _("journeys.new.form.description") }}</label> + <textarea class="form-control" id="journeyDescription" name="journeyDescription">{{ journey.description }}</textarea> +</div> +<div class="mb-3"> + <label for="journeyVisibility" class="form-label">{{ _("journeys.new.form.visibility") }}</label> + <select class="form-select" id="journeyVisibility" name="journeyVisibility"> + {% set visibility = journey.visibility.name if journey else "" %} + <option value="PRIVATE"{% if visibility== "PRIVATE" %} selected{% endif %}>{{ _("journeys.new.form.visibility.private") }}</option> + <option value="FRIENDS"{% if visibility== "FRIENDS" %} selected{% endif %}>{{ _("journeys.new.form.visibility.friends") }}</option> + <option value="LOGGED_IN"{% if visibility== "LOGGED_IN" %} selected{% endif %}>{{ _("journeys.new.form.visibility.logged_in") }}</option> + <option value="PUBLIC"{% if visibility== "PUBLIC" %} selected{% endif %}>{{ _("journeys.new.form.visibility.public") }}</option> + </select> +</div> +<div class="mb-3"> + <p> + {{ _("journeys.new.form.tracksearch") }} + </p> + <div class="input-group"> + <input type="text" id="trackSearch" placeholder="Title" class="form-control"> + <button class="btn btn-secondary" id="trackSearchButton"><i class="bi bi-search"></i></button> + </div> +</div> +<div class="mb-3" id="trackSearchResults"></div> +<div class="mb-3"> + <p>{{ _("journeys.new.form.tracks") }}<p> +</div> +<div class="mb-3" id="journeyTracks"> + {% for track in journey.tracks %} + <div class="journey-track" draggable="true"> + <input type="hidden" name="journeyTrack[]" value="{{ track.id }}"> + <button class="btn btn-danger btn-sm"><i class="bi bi-x-circle-fill"></i></button> + <div class="track-title">{{ track.title }}</div> + <div class="track-length">{{ (track.with_metadata().length / 1000) | round(2) }} km</div> + <div class="track-date">{{ track.date | format_datetime }}</div> + </div> + {% endfor %} +</div> + +{{ util.hidden_csrf_input() }} + +<div> + <button class="btn btn-primary" type="submit" id="journeySubmit"> + <i class="bi bi-save"></i> + {{ _("journeys.new.form.submit") }} + </button> + <div class="invalid-feedback"> + {{ _("journeys.new.form.requires_tracks") }} + </div> +</div> + +<template id="queryResponse"> + <div class="track-query-response"> + <button class="btn btn-success btn-sm"><i class="bi bi-plus-square-fill"></i></button> + <div class="track-title"></div> + <div class="track-length"></div> + <div class="track-date"></div> + </div> +</template> + +<template id="journeyTrack"> + <div class="journey-track" draggable="true"> + <input type="hidden" name="journeyTrack[]"> + <button class="btn btn-danger btn-sm"><i class="bi bi-x-circle-fill"></i></button> + <div class="track-title"></div> + <div class="track-length"></div> + <div class="track-date"></div> + </div> +</template> +{% endmacro %} + + +{% macro journey_js() %} +<script> +// Make sure the mouse pointer stays "grab", even when leaving the list of +// tracks. +document.addEventListener("dragover", (event) => event.preventDefault()); + +let trDrag; + +function trDragStart(event) { + trDrag = event.target; + event.target.closest(".journey-track").classList.add("dragging"); + event.dataTransfer.effectAllowed = "move"; +} + +function trDragOver(event) { + let target = event.target.closest(".journey-track"); + + // Check whether we are in the top of bottom half of the element + let rect = target.getBoundingClientRect(); + let is_top_half = event.clientY < rect.top + rect.height / 2; + + if (is_top_half) { + target.insertAdjacentElement("beforebegin", trDrag); + } else { + target.insertAdjacentElement("afterend", trDrag); + } + event.preventDefault(); +} + +function trDragLeave(event) { + let target = event.target.closest(".journey-track"); + target.style.marginTop = ""; + target.style.marginBottom = ""; + event.preventDefault(); +} + +function trDragEnd(event) { + trDrag.closest(".journey-track").classList.remove("dragging"); + trDrag = null; +} + +function removeTrackFromJourney(event) { + let track = event.target.closest("div"); + track.parentNode.removeChild(track); + + checkJourneyValidity(); + + event.preventDefault(); +} + +addHandler(".journey-track button", "click", removeTrackFromJourney); + +function addTrackToJourney(event) { + let track = event.target.closest("div"); + let template = document.getElementById("journeyTrack"); + let clone = document.importNode(template.content, true); + + clone.querySelector("input").setAttribute("value", track.getAttribute("data-track-id")); + for (let sel of [".track-title", ".track-length", ".track-date"]) { + clone.querySelector(sel).textContent = track.querySelector(sel).textContent; + } + clone.querySelector("button").addEventListener("click", removeTrackFromJourney); + clone.querySelector(".journey-track").addEventListener("dragstart", trDragStart); + clone.querySelector(".journey-track").addEventListener("dragover", trDragOver); + clone.querySelector(".journey-track").addEventListener("dragleave", trDragLeave); + clone.querySelector(".journey-track").addEventListener("dragend", trDragEnd); + + document.getElementById("journeyTracks").appendChild(clone); + track.parentElement.removeChild(track); + + checkJourneyValidity(); + + event.preventDefault(); +} + +addHandler(".journey-track", "dragstart", trDragStart); +addHandler(".journey-track", "dragover", trDragOver); +addHandler(".journey-track", "dragleave", trDragLeave); +addHandler(".journey-track", "dragend", trDragEnd); + +function hasTrack(id) { + for (let track of document.querySelectorAll(".journey-track")) { + let tid = track.querySelector("input").value; + if (parseInt(tid) == id) { + return true; + } + } + return false; +} + +function searchTracks() { + let template = document.getElementById("queryResponse"); + let results = document.getElementById("trackSearchResults"); + let pattern = document.getElementById("trackSearch").value; + let url = makeUrl(`/track/?format=json&search-terms=${encodeURIComponent(pattern)}`); + fetch(url) + .then((response) => response.json()) + .then((response) => { + results.replaceChildren(); + for (let track of response) { + if (hasTrack(track.id)) { + continue; + } + let clone = document.importNode(template.content, true); + clone.firstElementChild.setAttribute("data-track-id", track.id); + clone.querySelector(".track-title").textContent = track.title.length > 0 ? track.title : formatTimestamp(track.date * 1000); + clone.querySelector(".track-date").textContent = formatTimestamp(track.date * 1000); + clone.querySelector(".track-length").textContent = `${(track.length / 1000).toFixed(2)} km`; + clone.querySelector("button").addEventListener("click", addTrackToJourney); + results.appendChild(clone); + } + }); +} + +function checkTitleValidity() { + let title = document.querySelector("#journeyTitle"); + if (title.value.length > 0) { + title.setCustomValidity(""); + } else { + title.setCustomValidity("title missing"); + } +} + +checkTitleValidity(); + +function checkJourneyValidity() { + let btn = document.querySelector("#journeySubmit"); + let track_count = document.querySelectorAll(".journey-track").length; + + if (track_count == 0) { + btn.setCustomValidity("no tracks"); + } else { + btn.setCustomValidity(""); + } +} + +checkJourneyValidity(); + +document.querySelector("#trackSearchButton").addEventListener("click", (event) => { + searchTracks(); + event.preventDefault(); +}); +</script> +{% endmacro %} diff --git a/fietsboek/templates/journey_list.jinja2 b/fietsboek/templates/journey_list.jinja2 new file mode 100644 index 0000000..8c7bfe9 --- /dev/null +++ b/fietsboek/templates/journey_list.jinja2 @@ -0,0 +1,32 @@ +{% extends "layout.jinja2" %} +{% block content %} +<div class="container"> + <h1>{{ _("journeys.overview.title") }}</h1> + + {% if show_new_button %} + <div class="mb-3"> + <a href="{{ request.route_url('journey-new') }}" class="btn btn-primary"> + <i class="bi bi-plus-circle"></i> + {{ _("journeys.overview.new") }} + </a> + </div> + {% endif %} + + {% for journey in journeys %} + <div class="card mb-5"> + <img src="{{ request.route_url('journey-map', journey_id=journey.id) }}" class="card-img-top" alt="Rendered map of the journey"> + <div class="card-body"> + <h5 class="card-title"> + <a href="{{ request.route_url('journey-details', journey_id=journey.id) }}">{{ journey.title }}</a> + </h5> + {{ md_to_html(journey.description) }} + </div> + <ul class="list-group list-group-flush"> + {% for track in journey.tracks %} + <li class="list-group-item">{{ track.title | default(track.date, true) }}</li> + {% endfor %} + </ul> + </div> + {% endfor %} +</div> +{% endblock %} diff --git a/fietsboek/templates/journey_new.jinja2 b/fietsboek/templates/journey_new.jinja2 new file mode 100644 index 0000000..b1cfffb --- /dev/null +++ b/fietsboek/templates/journey_new.jinja2 @@ -0,0 +1,21 @@ +{% extends "layout.jinja2" %} +{% import "journey_form.jinja2" as form with context %} + +{% block extrahead %} +{{ form.journey_css() }} +{% endblock %} + +{% block content %} +<div class="container"> + <h1>{{ _("journeys.new.title") }}</h1> + + <form method="POST" class="needs-validation" novalidate> + {{ form.journey_form(none) }} + </form> +</div> + +{% endblock %} + +{% block latescripts %} +{{ form.journey_js() }} +{% endblock %} diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2 index c96716d..3058553 100644 --- a/fietsboek/templates/layout.jinja2 +++ b/fietsboek/templates/layout.jinja2 @@ -32,6 +32,7 @@ const Fullscreenbutton = true; const Legende = false; </script> + {% block extrahead %}{% endblock %} </head> <body> @@ -56,6 +57,9 @@ const Legende = false; <li class="nav-item"> <a class="nav-link" href="{{ request.route_url('browse') }}">{{ _("page.navbar.browse") }}</a> </li> + <li class="nav-item"> + <a class="nav-link" href="{{ request.route_url('journey-list') }}">{{ _("page.navbar.journeys") }}</a> + </li> {% if request.identity is not none %} <li class="nav-item"> <a class="nav-link" href="{{ request.route_url('upload') }}">{{ _("page.navbar.upload") }}</a> 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..4cb13aa --- /dev/null +++ b/fietsboek/trackmap.py @@ -0,0 +1,148 @@ +"""Module to render tracks to static images on OSM tiles.""" + +import io +import math + +from PIL import Image, ImageDraw + +from . import geo +from .config import TileLayerConfig +from .views.tileproxy import TileRequester + +TILE_SIZE = 256 + +# This is arbitrarily set to provide some image in case a render is requested +# for a track without points. I've arbitrarily chosen Berlin as the represented +# area. +DEFAULT_ZOOM = 9 +DEFAULT_BBOX = (70062, 70611, 42824, 43179) + + +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: geo.Path, + 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]]: + if not self.track.points: + return DEFAULT_ZOOM, DEFAULT_BBOX + + 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.points: + 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 DEFAULT_ZOOM, DEFAULT_BBOX + + 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.points + ) + coords = [(x - start_x, y - start_y) for x, y in coords] + + if coords: + draw = ImageDraw.Draw(image) + draw.line(coords, fill=self.color, width=self.line_width, joint="curve") + + +def render( + track: geo.Path, + layer: TileLayerConfig, + requester: TileRequester, + size: tuple[int, int] = (300, 300), +) -> Image.Image: + """Shorthand to construct a :class:`TrackMapRenderer` and render the preview. + + :param track: 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, size, layer).render() + + +__all__ = ["to_web_mercator", "TrackMapRenderer", "render"] diff --git a/fietsboek/transformers/__init__.py b/fietsboek/transformers/__init__.py index b1a0245..097fbaf 100644 --- a/fietsboek/transformers/__init__.py +++ b/fietsboek/transformers/__init__.py @@ -13,11 +13,12 @@ from abc import ABC, abstractmethod from collections.abc import Mapping from typing import Literal, NamedTuple, TypeVar -from gpxpy.gpx import GPX from pydantic import BaseModel from pyramid.i18n import TranslationString from pyramid.request import Request +from .. import geo + _ = TranslationString T = TypeVar("T", bound="Transformer") @@ -117,12 +118,12 @@ class Transformer(ABC): pass @abstractmethod - def execute(self, gpx: GPX): + def execute(self, path: geo.Path): """Run the transformation on the input gpx. - This is expected to modify the GPX object to represent the new state. + This is expected to modify the path to represent the new state. - :param gpx: The GPX object to transform. Note that this object will be + :param path: The path to transform. Note that this object will be mutated! """ diff --git a/fietsboek/transformers/breaks.py b/fietsboek/transformers/breaks.py index 789fdfd..1072eef 100644 --- a/fietsboek/transformers/breaks.py +++ b/fietsboek/transformers/breaks.py @@ -2,9 +2,9 @@ import datetime -from gpxpy.gpx import GPX, GPXTrack from pyramid.i18n import TranslationString +from .. import geo from . import Parameters, Transformer _ = TranslationString @@ -47,34 +47,27 @@ class RemoveBreaks(Transformer): def parameters(self, value): pass - def execute(self, gpx: GPX): - for track in gpx.tracks: - self._clean(track) - - def _clean(self, track: GPXTrack): - if not track.get_points_no(): + def execute(self, path: geo.Path): + if not path.points: return i = 0 - while i < track.get_points_no(): - segment_idx, point_idx = index(track, i) - point = track.segments[segment_idx].points[point_idx] + while i < len(path.points): + point = path.points[i] # We check if the following points constitute a break, and if yes, # how many of them count = 0 current_length = 0.0 last_point = point - while True: - try: - j_segment, j_point = index(track, i + count + 1) - except IndexError: - break - current_point = track.segments[j_segment].points[j_point] - current_length += last_point.distance_3d(current_point) or 0.0 + while i + count + 1 < len(path.points): + current_point = path.points[i + count + 1] + current_length += last_point.distance(current_point) or 0.0 last_point = current_point - delta_t = datetime.timedelta(seconds=point.time_difference(last_point) or 0.0) + delta_t = datetime.timedelta( + seconds=last_point.time_offset - point.time_offset or 0.0 + ) if not delta_t or current_length / delta_t.total_seconds() > STOPPED_SPEED_LIMIT: break count += 1 @@ -85,7 +78,7 @@ class RemoveBreaks(Transformer): continue # At this point, check if the break is long enough to be removed - delta_t = datetime.timedelta(seconds=point.time_difference(last_point) or 0.0) + delta_t = datetime.timedelta(seconds=last_point.time_offset - point.time_offset or 0.0) if delta_t < MIN_BREAK_TO_REMOVE: i += 1 continue @@ -93,32 +86,12 @@ class RemoveBreaks(Transformer): # Here, we have a proper break to remove # Delete the points belonging to the break ... for _ in range(count): - j_segment, j_point = index(track, i + 1) - del track.segments[j_segment].points[j_point] + del path.points[i + 1] # ... and shift the time of the following points j = i + 1 - while j < track.get_points_no(): - j_segment, j_point = index(track, j) - track.segments[j_segment].points[j_point].adjust_time(-delta_t) - j += 1 - - -def index(track: GPXTrack, idx: int) -> tuple[int, int]: - """Takes a one-dimensional index (the point index) and returns an index - into the segment/segment points. - - :raises IndexError: When the given index is out of bounds. - :param track: The track for which to get the index. - :param idx: The "1D" index. - :return: A tuple with the segment index, and the index of the point within - the segment. - """ - for segment_idx, segment in enumerate(track.segments): - if idx < len(segment.points): - return (segment_idx, idx) - idx -= len(segment.points) - raise IndexError + for j_point in path.points[j:]: + j_point.time_offset -= delta_t.total_seconds() __all__ = ["RemoveBreaks"] diff --git a/fietsboek/transformers/elevation.py b/fietsboek/transformers/elevation.py index e1f7c7c..27683bb 100644 --- a/fietsboek/transformers/elevation.py +++ b/fietsboek/transformers/elevation.py @@ -3,9 +3,9 @@ from collections.abc import Callable, Iterable from itertools import islice, zip_longest -from gpxpy.gpx import GPX, GPXTrackPoint from pyramid.i18n import TranslationString +from .. import geo from . import Parameters, Transformer _ = TranslationString @@ -13,7 +13,7 @@ _ = TranslationString MAX_ORGANIC_SLOPE: float = 1.0 -def slope(point_a: GPXTrackPoint, point_b: GPXTrackPoint) -> float: +def slope(point_a: geo.Point, point_b: geo.Point) -> float: """Returns the slope between two GPX points. This is defined as delta_h / euclid_distance. @@ -25,7 +25,7 @@ def slope(point_a: GPXTrackPoint, point_b: GPXTrackPoint) -> float: if point_a.elevation is None or point_b.elevation is None: return 0.0 delta_h = abs(point_a.elevation - point_b.elevation) - dist = point_a.distance_2d(point_b) + dist = point_a.flat_distance(point_b) if dist == 0.0 or dist is None: return 0.0 return delta_h / dist @@ -58,19 +58,12 @@ class FixNullElevation(Transformer): def parameters(self, value): pass - def execute(self, gpx: GPX): + def execute(self, path: geo.Path): def all_points(): - return gpx.walk(only_points=True) + return iter(path.points) def rev_points(): - # We cannot use reversed(gpx.walk(...)) since that is not a - # generator, so we do it manually. - return ( - point - for track in reversed(gpx.tracks) - for segment in reversed(track.segments) - for point in reversed(segment.points) - ) + return reversed(path.points) # First, from the front self.fixup(all_points) @@ -78,7 +71,7 @@ class FixNullElevation(Transformer): self.fixup(rev_points) @classmethod - def fixup(cls, points: Callable[[], Iterable[GPXTrackPoint]]): + def fixup(cls, points: Callable[[], Iterable[geo.Point]]): """Fixes the given GPX points. This iterates over the points and checks for the first point that has a @@ -131,17 +124,16 @@ class FixElevationJumps(Transformer): def parameters(self, value): pass - def execute(self, gpx: GPX): + def execute(self, path: geo.Path): current_adjustment = 0.0 - points = gpx.walk(only_points=True) - next_points = gpx.walk(only_points=True) + points = iter(path.points) + next_points = iter(path.points) for current_point, next_point in zip_longest(points, islice(next_points, 1, None)): point_adjustment = current_adjustment if next_point and slope(current_point, next_point) > MAX_ORGANIC_SLOPE: current_adjustment += current_point.elevation - next_point.elevation - print(f"{current_adjustment=}") current_point.elevation += point_adjustment diff --git a/fietsboek/updater/__init__.py b/fietsboek/updater/__init__.py index 42e40f4..4d3b0ee 100644 --- a/fietsboek/updater/__init__.py +++ b/fietsboek/updater/__init__.py @@ -331,6 +331,27 @@ class Updater: return UpdateState.OUTDATED return state + def check_connectivity(self) -> str | None: + """Checks whether the data directory and the SQL server accessible. + + Returns ``None`` if there are no problems, or a string describing the + error. + + :return: Whether there is a connection error. + """ + data_dir = Path(self.settings["fietsboek.data_dir"]) + if not data_dir.exists(): + return "data directory does not exist" + + engine = sqlalchemy.create_engine(self.settings["sqlalchemy.url"]) + try: + with engine.connect(): + pass + except sqlalchemy.exc.OperationalError as exc: + return f"could not connect to database\n\n{exc}" + + return None + class UpdateScript: """Represents an update script.""" diff --git a/fietsboek/updater/cli.py b/fietsboek/updater/cli.py index 9b7d92e..271e7a1 100644 --- a/fietsboek/updater/cli.py +++ b/fietsboek/updater/cli.py @@ -8,6 +8,7 @@ migrating the configuration. """ import logging.config +import sys import click @@ -32,6 +33,21 @@ def user_confirm(verb): click.confirm("Proceed?", abort=True) +def check_connectivity(updater: Updater): + """Makes sure that the updater can connect to the database. + + Aborts the program if not. + + :param updater: The updater. + """ + error = updater.check_connectivity() + if error is None: + return + click.secho("Error: ", fg="red", nl=False) + click.echo(error) + sys.exit(1) + + @click.group( help=__doc__, context_settings={"help_option_names": ["-h", "--help"]}, @@ -62,6 +78,7 @@ def update(ctx, config, version, force): logging.config.fileConfig(config) updater = Updater(config) updater.load() + check_connectivity(updater) if version and not updater.exists(version): ctx.fail(f"Version {version!r} not found") @@ -107,6 +124,7 @@ def downgrade(ctx, config, version, force): logging.config.fileConfig(config) updater = Updater(config) updater.load() + check_connectivity(updater) if version and not updater.exists(version): ctx.fail(f"Version {version!r} not found") @@ -158,6 +176,7 @@ def status(config): logging.config.fileConfig(config) updater = Updater(config) updater.load() + check_connectivity(updater) current = updater.current_versions() heads = updater.heads() click.secho("Current versions:", fg="yellow") diff --git a/fietsboek/updater/scripts/upd_20230103_lu8w3rwlz4ddcpms.py b/fietsboek/updater/scripts/upd_20230103_lu8w3rwlz4ddcpms.py index 4362c2d..10c42d0 100644 --- a/fietsboek/updater/scripts/upd_20230103_lu8w3rwlz4ddcpms.py +++ b/fietsboek/updater/scripts/upd_20230103_lu8w3rwlz4ddcpms.py @@ -29,42 +29,42 @@ alembic_revision = 'c939800af428' class Up(UpdateScript): def pre_alembic(self, config): engine = create_engine(config["sqlalchemy.url"]) - connection = engine.connect() data_dir = Path(config["fietsboek.data_dir"]) - sql = ( - "SELECT tracks.id, tracks.title, tracks.description, tracks.date_raw, " - "tracks.date_tz, users.name " - "FROM tracks, users " - "WHERE tracks.owner_id = users.id;" - ) - for row in connection.execute(text(sql)): - track_id, title, description, date_raw, date_tz, author_name = row - if isinstance(date_raw, str): - date_raw = datetime.datetime.strptime(date_raw, "%Y-%m-%d %H:%M:%S.%f") - if date_tz is None: - timezone = datetime.timezone.utc - else: - timezone = datetime.timezone(datetime.timedelta(minutes=date_tz)) - date = date_raw.replace(tzinfo=timezone) - - self.tell(f"Embedding metadata for track {track_id}") - track_dir = data_dir / "tracks" / str(track_id) - gpx_path = track_dir / "track.gpx.br" - - raw_gpx = brotli.decompress(gpx_path.read_bytes()) - gpx = gpxpy.parse(raw_gpx) - - for track in gpx.tracks: - track.name = None - track.description = None - - gpx.author_name = author_name - gpx.name = title - gpx.description = description - gpx.time = date - - gpx_path.write_bytes(brotli.compress(gpx.to_xml().encode("utf-8"), quality=4)) + with engine.connect() as connection: + sql = ( + "SELECT tracks.id, tracks.title, tracks.description, tracks.date_raw, " + "tracks.date_tz, users.name " + "FROM tracks, users " + "WHERE tracks.owner_id = users.id;" + ) + for row in connection.execute(text(sql)): + track_id, title, description, date_raw, date_tz, author_name = row + if isinstance(date_raw, str): + date_raw = datetime.datetime.strptime(date_raw, "%Y-%m-%d %H:%M:%S.%f") + if date_tz is None: + timezone = datetime.timezone.utc + else: + timezone = datetime.timezone(datetime.timedelta(minutes=date_tz)) + date = date_raw.replace(tzinfo=timezone) + + self.tell(f"Embedding metadata for track {track_id}") + track_dir = data_dir / "tracks" / str(track_id) + gpx_path = track_dir / "track.gpx.br" + + raw_gpx = brotli.decompress(gpx_path.read_bytes()) + gpx = gpxpy.parse(raw_gpx) + + for track in gpx.tracks: + track.name = None + track.description = None + + gpx.author_name = author_name + gpx.name = title + gpx.description = description + gpx.time = date + + gpx_path.write_bytes(brotli.compress(gpx.to_xml().encode("utf-8"), quality=4)) def post_alembic(self, config): pass 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/updater/scripts/upd_20251109_nm561argcq1s8w27.py b/fietsboek/updater/scripts/upd_20251109_nm561argcq1s8w27.py new file mode 100644 index 0000000..ae6c29a --- /dev/null +++ b/fietsboek/updater/scripts/upd_20251109_nm561argcq1s8w27.py @@ -0,0 +1,163 @@ +"""Revision upgrade script nm561argcq1s8w27 + +This script moves data from the GPX files in the data directory to the SQL +database. + +Date created: 2025-11-09 18:27:48.493007 +""" +import datetime +import logging +import shutil +from pathlib import Path + +import brotli +import gpxpy +from sqlalchemy import create_engine +from sqlalchemy.sql import text + +from fietsboek import convert +from fietsboek.updater.script import UpdateScript + +LOGGER = logging.getLogger(__name__) + +update_id = 'nm561argcq1s8w27' +previous = [ + 'v0.11.0', +] +alembic_revision = '90b39fdf6e4b' + + +class Up(UpdateScript): + def pre_alembic(self, config): + pass + + def post_alembic(self, config): + engine = create_engine(config["sqlalchemy.url"]) + connection = engine.connect() + data_dir = Path(config["fietsboek.data_dir"]) + + with engine.connect() as connection: + # This can happen in a fresh instance + if not (data_dir / "tracks").exists(): + return + + for track_dir in (data_dir / "tracks").iterdir(): + track_id = int(track_dir.name) + self.tell(f"Loading track {track_id}") + + gpx_path = track_dir / "track.gpx.br" + + # We're careful here, in case a previous update was interrupted + if not gpx_path.exists(): + continue + + gpx_bytes = brotli.decompress(gpx_path.read_bytes()) + + track = convert.smart_convert(gpx_bytes) + with connection.begin(): + connection.execute( + text("DELETE FROM track_points WHERE track_id = :id;"), + {"id": track_id}, + ) + connection.execute( + text("DELETE FROM waypoints WHERE track_id = :id;"), + {"id": track_id}, + ) + for index, point in enumerate(track.path().points): + connection.execute( + text("""INSERT INTO track_points ( + track_id, "index", longitude, latitude, elevation, time_offset + ) VALUES ( + :track_id, :index, :longitude, :latitude, :elevation, :time_offset + );"""), + { + "track_id": track_id, + "index": index, + "longitude": point.longitude, + "latitude": point.latitude, + "elevation": point.elevation, + "time_offset": point.time_offset, + }, + ) + for waypoint in track.waypoints: + connection.execute( + text("""INSERT INTO waypoints ( + track_id, longitude, latitude, elevation, name, description + ) VALUES ( + :track_id, :longitude, :latitude, :elevation, :name, :description + );"""), + { + "track_id": track_id, + "longitude": waypoint.longitude, + "latitude": waypoint.latitude, + "elevation": waypoint.elevation, + "name": waypoint.name, + "description": waypoint.description, + }, + ) + + gpx_path.unlink() + shutil.move( + track_dir / "track.bck.gpx.br", + track_dir / "track.bck.br", + ) + + +class Down(UpdateScript): + def pre_alembic(self, config): + engine = create_engine(config["sqlalchemy.url"]) + data_dir = Path(config["fietsboek.data_dir"]) + + query = text("SELECT id, title, description, date_raw FROM tracks;") + + with engine.connect() as connection: + for row in connection.execute(query): + gpx = gpxpy.gpx.GPX() + gpx.description = row.description + gpx.name = row.title + + start_date = row.date_raw + if isinstance(start_date, str): + start_date = datetime.datetime.fromisoformat(start_date) + + segment = gpxpy.gpx.GPXTrackSegment() + points_query = text(""" + SELECT longitude, latitude, elevation, time_offset + FROM track_points WHERE track_id = :track_id ORDER BY "index"; + """) + for point in connection.execute(points_query, {"track_id": row.id}): + segment.points.append( + gpxpy.gpx.GPXTrackPoint( + latitude=point.latitude, + longitude=point.longitude, + elevation=point.elevation, + time=start_date + datetime.timedelta(seconds=point.time_offset), + ) + ) + track = gpxpy.gpx.GPXTrack() + track.segments.append(segment) + gpx.tracks.append(track) + + waypoints_query = text(""" + SELECT longitude, latitude, elevation, name, description + FROM waypoints WHERE track_id = :track_id; + """) + for wpt in connection.execute(waypoints_query, {"track_id": row.id}): + gpx.waypoints.append( + gpxpy.gpx.GPXWaypoint( + longitude=wpt.longitude, + latitude=wpt.latitude, + elevation=wpt.elevation, + name=wpt.name, + comment=wpt.description, + description=wpt.description, + ) + ) + + xml_data = gpx.to_xml(prettyprint=False).encode("utf-8") + track_dir = data_dir / "tracks" / str(row.id) + (track_dir / "track.gpx.br").write_bytes(brotli.compress(xml_data)) + shutil.move(track_dir / "track.bck.br", track_dir / "track.bck.gpx.br") + + def post_alembic(self, config): + pass diff --git a/fietsboek/updater/scripts/upd_20260103_v0.12.0.py b/fietsboek/updater/scripts/upd_20260103_v0.12.0.py new file mode 100644 index 0000000..697421e --- /dev/null +++ b/fietsboek/updater/scripts/upd_20260103_v0.12.0.py @@ -0,0 +1,27 @@ +"""Revision upgrade script v0.12.0 + +Date created: 2026-01-03 20:38:01.341899 +""" +from fietsboek.updater.script import UpdateScript + +update_id = 'v0.12.0' +previous = [ + 'nm561argcq1s8w27', +] +alembic_revision = 'f9ca03541351' + + +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/updater/scripts/upd_20260103_v0.12.1.py b/fietsboek/updater/scripts/upd_20260103_v0.12.1.py new file mode 100644 index 0000000..2c46c24 --- /dev/null +++ b/fietsboek/updater/scripts/upd_20260103_v0.12.1.py @@ -0,0 +1,27 @@ +"""Revision upgrade script v0.12.1 + +Date created: 2026-01-03 23:48:08.321881 +""" +from fietsboek.updater.script import UpdateScript + +update_id = 'v0.12.1' +previous = [ + 'v0.12.0', +] +alembic_revision = 'f9ca03541351' + + +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/updater/scripts/upd_30ppwg8zi4ujb46f.py b/fietsboek/updater/scripts/upd_30ppwg8zi4ujb46f.py index e900c7a..cdc09f6 100644 --- a/fietsboek/updater/scripts/upd_30ppwg8zi4ujb46f.py +++ b/fietsboek/updater/scripts/upd_30ppwg8zi4ujb46f.py @@ -25,18 +25,18 @@ alembic_revision = 'c939800af428' class Up(UpdateScript): def pre_alembic(self, config): engine = create_engine(config["sqlalchemy.url"]) - connection = engine.connect() - data_dir = Path(config["fietsboek.data_dir"]) + with engine.connect() as connection: + data_dir = Path(config["fietsboek.data_dir"]) - for row in connection.execute(text("SELECT id, gpx FROM tracks;")): - self.tell(f"Moving GPX data for track {row.id} from database to disk") - track_dir = data_dir / "tracks" / str(row.id) - track_dir.mkdir(parents=True, exist_ok=True) + for row in connection.execute(text("SELECT id, gpx FROM tracks;")): + self.tell(f"Moving GPX data for track {row.id} from database to disk") + track_dir = data_dir / "tracks" / str(row.id) + track_dir.mkdir(parents=True, exist_ok=True) - raw_gpx = gzip.decompress(row.gpx) - gpx_path = track_dir / "track.gpx.br" - gpx_path.write_bytes(brotli.compress(raw_gpx, quality=5)) - shutil.copy(gpx_path, track_dir / "track.bck.gpx.br") + raw_gpx = gzip.decompress(row.gpx) + gpx_path = track_dir / "track.gpx.br" + gpx_path.write_bytes(brotli.compress(raw_gpx, quality=5)) + shutil.copy(gpx_path, track_dir / "track.bck.gpx.br") def post_alembic(self, config): pass @@ -48,18 +48,18 @@ class Down(UpdateScript): def post_alembic(self, config): engine = create_engine(config["sqlalchemy.url"]) - connection = engine.connect() - data_dir = Path(config["fietsboek.data_dir"]) + with engine.connect() as connection: + data_dir = Path(config["fietsboek.data_dir"]) - for track_path in (data_dir / "tracks").iterdir(): - track_id = int(track_path.name) - self.tell(f"Moving GPX data for track {track_id} from disk to database") - brotli_data = (track_path / "track.gpx.br").read_bytes() - gzip_data = gzip.compress(brotli.decompress(brotli_data)) - connection.execute( - text("UPDATE tracks SET gpx = :gpx WHERE id = :id;"), - gpx=gzip_data, id=track_id - ) + for track_path in (data_dir / "tracks").iterdir(): + track_id = int(track_path.name) + self.tell(f"Moving GPX data for track {track_id} from disk to database") + brotli_data = (track_path / "track.gpx.br").read_bytes() + gzip_data = gzip.compress(brotli.decompress(brotli_data)) + connection.execute( + text("UPDATE tracks SET gpx = :gpx WHERE id = :id;"), + gpx=gzip_data, id=track_id + ) - (track_path / "track.gpx.br").unlink() - (track_path / "track.bck.gpx.br").unlink(missing_ok=True) + (track_path / "track.gpx.br").unlink() + (track_path / "track.bck.gpx.br").unlink(missing_ok=True) diff --git a/fietsboek/util.py b/fietsboek/util.py index 9284ce2..ecb3f43 100644 --- a/fietsboek/util.py +++ b/fietsboek/util.py @@ -1,13 +1,13 @@ """Various utility functions.""" import datetime -import html import importlib.resources import os import re import secrets import unicodedata -from typing import Optional, TypeVar, Union +from pathlib import Path +from typing import Optional, TypeVar import babel import gpxpy @@ -63,6 +63,9 @@ _windows_device_files = ( ) +_valid_xml_chars = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,-: ") + + def safe_markdown(md_source: str) -> Markup: """Transform a markdown document into a safe HTML document. @@ -172,43 +175,6 @@ def guess_gpx_timezone(gpx: gpxpy.gpx.GPX) -> datetime.tzinfo: return datetime.timezone.utc -def tour_metadata(gpx_data: Union[str, bytes, gpxpy.gpx.GPX]) -> dict: - """Calculate the metadata of the tour. - - Returns a dict with ``length``, ``uphill``, ``downhill``, ``moving_time``, - ``stopped_time``, ``max_speed``, ``avg_speed``, ``start_time`` and - ``end_time``. - - :param gpx_data: The GPX data of the tour. Can be pre-parsed to save time. - :return: A dictionary with the computed values. - """ - if isinstance(gpx_data, bytes): - gpx_data = gpx_data.decode("utf-8") - if isinstance(gpx_data, gpxpy.gpx.GPX): - gpx = gpx_data - else: - gpx = gpxpy.parse(gpx_data) - timezone = guess_gpx_timezone(gpx) - uphill, downhill = gpx.get_uphill_downhill() - moving_data = gpx.get_moving_data() - time_bounds = gpx.get_time_bounds() - try: - avg_speed = moving_data.moving_distance / moving_data.moving_time - except ZeroDivisionError: - avg_speed = 0.0 - return { - "length": gpx.length_3d(), - "uphill": uphill, - "downhill": downhill, - "moving_time": moving_data.moving_time, - "stopped_time": moving_data.stopped_time, - "max_speed": moving_data.max_speed, - "avg_speed": avg_speed, - "start_time": (time_bounds.start_time or DEFAULT_START_TIME).astimezone(timezone), - "end_time": (time_bounds.end_time or DEFAULT_END_TIME).astimezone(timezone), - } - - def mps_to_kph(mps: float) -> float: """Converts meters/second to kilometers/hour. @@ -442,22 +408,6 @@ def tile_url(request: Request, route_name: str, **kwargs: str) -> str: return route.replace("__x__", "{x}").replace("__y__", "{y}").replace("__z__", "{z}") -def encode_gpx(gpx: gpxpy.gpx.GPX) -> bytes: - """Encodes a GPX in-memory representation to the XML representation. - - This ensures that the resulting XML file is valid. - - Returns the contents of the XML as bytes, ready to be written to disk. - - :param gpx: The GPX file to encode. Might be modified! - :return: The encoded GPX content. - """ - for track in gpx.tracks: - if track.link: - track.link = html.escape(track.link) - return gpx.to_xml().encode("utf-8") - - def secure_filename(filename: str) -> str: r"""Pass it a filename and it will return a secure version of it. This filename can then safely be stored on a regular file system and passed @@ -504,6 +454,36 @@ 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 + + +def xml_escape(value: str) -> bytes: + """Escapes and encodes a string to be embedded in a XML document. + + This replaces characters like < and > with their entities. + + :param value: The value. + :return: The escaped and encoded string. + """ + return b"".join( + ( + char.encode("ascii") + if char in _valid_xml_chars + else b"&#x%s;" % hex(ord(char))[2:].encode("ascii") + ) + for char in value + ) + + __all__ = [ "ALLOWED_TAGS", "ALLOWED_ATTRIBUTES", @@ -515,7 +495,6 @@ __all__ = [ "round_timedelta_to_multiple", "round_to_seconds", "guess_gpx_timezone", - "tour_metadata", "mps_to_kph", "human_size", "month_name", @@ -527,6 +506,7 @@ __all__ = [ "locale_display_name", "list_locales", "tile_url", - "encode_gpx", "secure_filename", + "recursive_size", + "xml_escape", ] diff --git a/fietsboek/views/admin.py b/fietsboek/views/admin.py index d078794..f0aa271 100644 --- a/fietsboek/views/admin.py +++ b/fietsboek/views/admin.py @@ -1,21 +1,161 @@ """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, text + +from .. import models, util +from ..data import DataManager + +GOOD_CRON_THRESHOLD = datetime.timedelta(hours=1) -from .. import models + +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.""" + + track_data: int = 0 + backups: int = 0 + image_files: int = 0 + track_previews: int = 0 + journey_previews: int = 0 + user_maps: int = 0 + + +def _get_size_breakdown(dbsession, data_manager: DataManager): + breakdown = SizeBreakdown() + + dialect = dbsession.bind.dialect.name + if dialect == "sqlite": + query = text("""SELECT SUM("pgsize") FROM "dbstat" WHERE name='track_points';""") + result = dbsession.execute(query).scalar_one() + breakdown.track_data += result + elif dialect == "postgresql": + query = text("""SELECT pg_relation_size('track_points');""") + result = dbsession.execute(query).scalar_one() + breakdown.track_data += result + + for track_id in data_manager.list_tracks(): + track = data_manager.open(track_id) + breakdown.backups += _safe_size(track.backup_path()) + breakdown.track_previews += _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()) + + for journey_id in data_manager.list_journeys(): + journey = data_manager.open_journey(journey_id) + breakdown.journey_previews += _safe_size(journey.preview_path()) + + return breakdown + + +def _get_db_size(dbsession): + dialect = dbsession.bind.dialect.name + if dialect == "sqlite": + query = text( + """SELECT page_size * page_count FROM pragma_page_count(), pragma_page_size();""" + ) + result = dbsession.execute(query).scalar_one() + return result + if dialect == "postgresql": + database_name = dbsession.bind.url.database + query = text(f"""SELECT pg_database_size('{database_name}');""") + result = dbsession.execute(query).scalar_one() + return result + return 0 + + +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() + _get_db_size(request.dbsession) + size_breakdown = _get_size_breakdown(request.dbsession, 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 +187,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 +211,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 +231,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..e2742ad 100644 --- a/fietsboek/views/browse.py +++ b/fietsboek/views/browse.py @@ -1,10 +1,12 @@ """Views for browsing all tracks.""" import datetime +import json +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 +14,13 @@ 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 ..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,47 @@ def apply_order(query: Select, track: AliasedTrack, order: ResultOrder) -> Selec return query +def paginate( + dbsession: Session, + 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 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) + if filters.apply(track): + num_retrieved += 1 + yield track + + @view_config( route_name="browse", renderer="fietsboek:templates/browse.jinja2", request_method="GET" ) @@ -431,18 +483,45 @@ 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, + 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, + ) + ) + + if request.params.get("format") == "json": + obj = [ + { + "id": track.id, + "title": track.title, + "date": (track.date or datetime.datetime.fromtimestamp(0)).timestamp(), + "length": track.length, + } + for track in tracks + ] + return Response(json.dumps(obj).encode("ascii"), content_type="application/json") + 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), } @@ -467,12 +546,18 @@ def archive(request: Request) -> Response: if not track.is_visible_to(request.identity): return HTTPForbidden() + # Since we stream the data, we need to ensure it's loaded before we close + # the session + for track in tracks: + request.dbsession.refresh(track, ["points", "waypoints"]) + request.dbsession.expunge(track) + def generate(): stream = Stream() with ZipFile(stream, "w", ZIP_DEFLATED) as zipfile: # type: ignore - for track_id in track_ids: - data = request.data_manager.open(track_id).decompress_gpx() - zipfile.writestr(f"track_{track_id}.gpx", data) + for track in tracks: + data = track.gpx_xml() + zipfile.writestr(f"track_{track.id}.gpx", data) yield stream.readall() yield stream.readall() diff --git a/fietsboek/views/default.py b/fietsboek/views/default.py index 8a9718d..320d02d 100644 --- a/fietsboek/views/default.py +++ b/fietsboek/views/default.py @@ -61,7 +61,7 @@ def home(request: Request) -> Response: gpx_data = request.data_manager.open(track.id).decompress_gpx() track.ensure_cache(gpx_data) request.dbsession.add(track.cache) - summary.add(TrackWithMetadata(track, request.data_manager)) + summary.add(TrackWithMetadata(track)) unfinished_uploads = request.identity.uploads diff --git a/fietsboek/views/detail.py b/fietsboek/views/detail.py index 2bc5d9a..ca3a0af 100644 --- a/fietsboek/views/detail.py +++ b/fietsboek/views/detail.py @@ -2,21 +2,25 @@ import datetime import gzip +import io import logging +from html.parser import HTMLParser +from markupsafe import Markup from pyramid.httpexceptions import ( HTTPFound, - HTTPInternalServerError, HTTPNotAcceptable, 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, pdf, trackmap, util from ..models.track import Track, TrackWithMetadata +from .tileproxy import ITileRequester LOGGER = logging.getLogger(__name__) @@ -32,6 +36,34 @@ def _sort_key(image_name: str) -> str: return image_name.split("_", 1)[1] +class _DescriptionParser(HTMLParser): + # We don't go overboard with the parsing here, as we expect to only be fed + # output from the markdown conversion (so simple documents) + def __init__(self): + super().__init__() + self.par = False + self.text = "" + + def handle_starttag(self, tag, attrs): + if tag == "p": + self.par = True + + def handle_endtag(self, tag): + if tag == "p": + self.par = False + + def handle_data(self, data): + if self.par: + self.text += data + + +def _og_description(description: Markup) -> str: + """Returns a truncated, non-marked up description of the given input description.""" + parser = _DescriptionParser() + parser.feed(str(description)) + return parser.text + + @view_config( route_name="details", renderer="fietsboek:templates/details.jinja2", permission="track.view" ) @@ -45,6 +77,7 @@ def details(request): """ track = request.context description = util.safe_markdown(track.description) + og_description = _og_description(description) show_edit_link = track.owner == request.identity on_disk_images = [] @@ -71,7 +104,7 @@ def details(request): # Strip off the sort key again images = [(image[1], image[2]) for image in images] - with_meta = TrackWithMetadata(track, request.data_manager) + with_meta = TrackWithMetadata(track) return { "track": with_meta, "show_organic": track.show_organic_data(), @@ -79,6 +112,7 @@ def details(request): "mps_to_kph": util.mps_to_kph, "comment_md_to_html": util.safe_markdown, "description": description, + "og_description": og_description, "images": images, } @@ -93,37 +127,28 @@ def gpx(request): :rtype: pyramid.response.Response """ track: Track = request.context - try: - manager = request.data_manager.open(track.id) - except FileNotFoundError: - LOGGER.error("Track exists in database, but not on disk: %d", track.id) - return HTTPInternalServerError() if track.title: wanted_filename = f"{track.id} - {util.secure_filename(track.title)}.gpx" else: wanted_filename = f"{track.id}.gpx" content_disposition = f'attachment; filename="{wanted_filename}"' - # We can be nice to the client if they support it, and deliver the gzipped - # data straight. This saves decompression time on the server and saves a - # lot of bandwidth. - accepted = request.accept_encoding.acceptable_offers(["br", "gzip", "identity"]) + gpx_data = track.gpx_xml() + # We used to offer brotli compression here as well, but brotli is too + # inefficient to do on-the-fly. That was only useful while we had the data + # stored brotli-compressed. For comparison, a track with ~50k points: + # identity -- 2.4s -- 5.49 MiB + # gzip -- 2.5s -- 657.53 KiB + # brotli -- 12s -- 389.45 KiB + # So yes, brotli does give better compression than gzip, but the time is + # not worth paying for. + accepted = request.accept_encoding.acceptable_offers(["gzip", "identity"]) for encoding, _qvalue in accepted: - if encoding == "br": - response = FileResponse( - str(manager.gpx_path()), - request, - content_type="application/gpx+xml", - content_encoding="br", - ) - break if encoding == "gzip": - # gzip'ed GPX files are so much smaller than uncompressed ones, it - # is worth re-compressing them for the client - data = gzip.compress(manager.decompress_gpx()) + data = gzip.compress(gpx_data) response = Response(data, content_type="application/gpx+xml", content_encoding="gzip") break if encoding == "identity": - response = Response(manager.decompress_gpx(), content_type="application/gpx+xml") + response = Response(gpx_data, content_type="application/gpx+xml") break else: return HTTPNotAcceptable("No data with acceptable encoding found") @@ -222,4 +247,65 @@ 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] + + track_image = trackmap.render(track.path(), 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 + + +@view_config(route_name="track-pdf", permission="track.view") +def track_pdf(request: Request): + """Endpoint to provide the track's PDF overview. + + :param request: The pyramid request. + :return: The HTTP response. + """ + loader: ITileRequester = request.registry.getUtility(ITileRequester) + layer = request.config.public_tile_layers()[0] + pdf_bytes = pdf.generate(request.context, loader, layer, request.localizer) + response = Response(pdf_bytes, content_type="application/pdf") + return response + + +__all__ = [ + "details", + "gpx", + "invalidate_share", + "delete_track", + "badge", + "image", + "add_comment", + "track_map", + "track_pdf", +] diff --git a/fietsboek/views/edit.py b/fietsboek/views/edit.py index c3a4fc5..2b559d4 100644 --- a/fietsboek/views/edit.py +++ b/fietsboek/views/edit.py @@ -5,10 +5,11 @@ import logging from collections import namedtuple 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 +69,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() @@ -83,27 +84,45 @@ def do_edit(request): data: TrackDataDir = request.data_manager.open(track.id) tz_offset = datetime.timedelta(minutes=int(request.params["date-tz"])) date = datetime.datetime.fromisoformat(request.params["date"]) - with data, data.lock(): - track.date = date.replace(tzinfo=datetime.timezone(tz_offset)) - - track.tagged_people = tagged_people - track.title = request.params["title"] - track.visibility = Visibility[request.params["visibility"]] - track.type = TrackType[request.params["type"]] - track.description = request.params["description"] - track.badges = badges - tags = request.params.getall("tag[]") - track.sync_tags(tags) - - actions.edit_images(request, request.context, manager=data) - gpx = actions.execute_transformers(request, request.context) - data.engrave_metadata( - title=track.title, - description=track.description, - author_name=track.owner.name, - time=track.date, - gpx=gpx, - ) + redo_cache = False + try: + gpx_bytes = request.POST["gpx"].file.read() + except AttributeError: + pass + else: + LOGGER.info("Setting new track for %s", track.id) + try: + new_track = convert.smart_convert(gpx_bytes) + except convert.ConversionError as exc: + request.session.flash(request.localizer.translate(_("flash.invalid_file"))) + LOGGER.info("Could not parse gpx: %s", exc) + return HTTPFound(request.route_url("edit", track_id=track.id)) + data.compress_backup(gpx_bytes) + track.fast_set_path(new_track.path()) + track.transformers = [] + redo_cache = True + + track.date = date.replace(tzinfo=datetime.timezone(tz_offset)) + + track.tagged_people = tagged_people + track.title = request.params["title"] + track.visibility = Visibility[request.params["visibility"]] + track.type = TrackType[request.params["type"]] + track.description = request.params["description"] + track.badges = badges + tags = request.params.getall("tag[]") + track.sync_tags(tags) + + actions.edit_images(request, request.context, manager=data) + actions.execute_transformers(request, request.context) + + # actions.execute_transformers automatically rebuilds the cache, so we only need to do + # this if execute_transformers didn't do it + if redo_cache: + LOGGER.info("New file detected, rebuilding cache for %s", track.id) + track.cache = None + track.ensure_cache() + 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/journey.py b/fietsboek/views/journey.py new file mode 100644 index 0000000..74b0fad --- /dev/null +++ b/fietsboek/views/journey.py @@ -0,0 +1,267 @@ +"""Views relating to journeys.""" + +import io +import logging +from datetime import timedelta + +from pyramid.httpexceptions import HTTPBadRequest, HTTPFound +from pyramid.i18n import TranslationString as _ +from pyramid.request import Request +from pyramid.response import Response +from pyramid.view import view_config +from sqlalchemy import select +from sqlalchemy.orm import aliased + +from .. import trackmap, util +from ..data import JourneyDataDir +from ..models import User +from ..models.journey import Journey, Visibility +from ..models.track import Track, TrackWithMetadata +from .tileproxy import ITileRequester + +LOGGER = logging.getLogger(__name__) + + +@view_config( + route_name="journey-list", + renderer="fietsboek:templates/journey_list.jinja2", +) +def journey_list(request: Request): + """Lists the available journeys. + + :param request: The pyramid request. + """ + query = select(aliased(Journey, User.visible_journeys_query(request.identity).subquery())) + journeys = request.dbsession.execute(query).scalars() + show_new_button = request.identity is not None + return { + "journeys": journeys, + "md_to_html": util.safe_markdown, + "show_new_button": show_new_button, + } + + +@view_config( + route_name="journey-details", + renderer="fietsboek:templates/journey_details.jinja2", + permission="journey.view", +) +def journey_details(request: Request): + """Shows details for a single journey. + + :param request: The pyramid request. + """ + journey: Journey = request.context + tracks = [TrackWithMetadata(track) for track in journey.tracks] + movement_data = journey.path().movement_data() + show_edit_link = request.identity == journey.owner + return { + "journey": journey, + "tracks": tracks, + "movement_data": movement_data, + "mps_to_kph": util.mps_to_kph, + "md_to_html": util.safe_markdown, + "timedelta": timedelta, + "show_edit_link": show_edit_link, + } + + +@view_config(route_name="journey-gpx", http_cache=3600, permission="journey.view") +def journey_gpx(request: Request): + """The view that returns the journey's GPX. + + :param request: The pyramid request. + """ + gpx_xml = request.context.gpx_xml() + response = Response(gpx_xml, content_type="application/gpx+xml") + response.md5_etag() + return response + + +@view_config(route_name="journey-map", http_cache=3600, permission="journey.view") +def journey_map(request: Request): + """The journey preview map image. + + :param request: The pyramid request. + """ + journey: Journey = request.context + journey_data: JourneyDataDir = request.data_manager.open_journey(journey.id) + preview_path = journey_data.preview_path() + + if preview_path.exists(): + response = Response(preview_path.read_bytes(), content_type="image/png") + response.md5_etag() + return response + + loader: ITileRequester = request.registry.getUtility(ITileRequester) + layer = request.config.public_tile_layers()[0] + + track_image = trackmap.render(journey.path(), layer, loader, size=(1300, 300)) + + imageio = io.BytesIO() + track_image.save(imageio, "png") + tile_data = imageio.getvalue() + + if not preview_path.exists(): + LOGGER.debug("Setting preview at %s", preview_path) + journey_data.set_preview(tile_data) + + response = Response(tile_data, content_type="image/png") + response.md5_etag() + return response + + +@view_config( + route_name="journey-new", + renderer="fietsboek:templates/journey_new.jinja2", + permission="new-journey", +) +def journey_new(_request: Request): + """The form to add a new journey. + + :param request: The pyramid request. + """ + return {} + + +@view_config( + route_name="journey-new", + permission="new-journey", + request_method="POST", +) +def do_journey_new(request: Request): + """Handler for submitting the new-journey form. + + :param request: The pyramid request. + """ + journey = Journey( + owner=request.identity, + title=request.params.get("journeyTitle"), + description=request.params.get("journeyDescription"), + visibility=_extract_visibility(request), + link_secret=util.random_link_secret(), + tracks=[], + ) + + request.dbsession.add(journey) + request.dbsession.flush() + + track_ids = _extract_valid_tracks(request, set()) + journey.set_track_ids(track_ids) + + request.data_manager.initialize_journey(journey.id) + + return HTTPFound(request.route_url("journey-details", journey_id=journey.id)) + + +@view_config( + route_name="journey-edit", + renderer="fietsboek:templates/journey_edit.jinja2", + permission="journey.edit", +) +def journey_edit(request: Request): + """The form to edit a journey. + + :param request: The pyramid request. + """ + journey: Journey = request.context + return { + "journey": journey, + } + + +@view_config( + route_name="journey-edit", + permission="journey.edit", + request_method="POST", +) +def do_journey_edit(request: Request): + """Handler for submitting the edit-journey form. + + :param request: The pyramid request. + """ + journey: Journey = request.context + request.data_manager.open_journey(journey.id).remove_preview() + + journey.title = request.params.get("journeyTitle") + journey.description = request.params.get("journeyDescription") + journey.visibility = _extract_visibility(request) + + track_ids = _extract_valid_tracks(request, {track.id for track in journey.tracks}) + journey.set_track_ids(track_ids) + + request.dbsession.add(journey) + + return HTTPFound(request.route_url("journey-details", journey_id=journey.id)) + + +def _extract_visibility(request: Request) -> Visibility: + key = request.params.get("journeyVisibility") + try: + return Visibility[key] + except KeyError: + raise HTTPBadRequest("Invalid visibility") from None + + +def _extract_valid_tracks(request: Request, current_ids: set[int]) -> list[int]: + user: User = request.identity + + if not request.params.get("journeyTitle"): + raise HTTPBadRequest("Needs a title") + + try: + track_ids = [int(tid) for tid in request.params.getall("journeyTrack[]")] + except ValueError: + # Shouldn't happen if users don't tamper with the requests manually, so we don't translate + raise HTTPBadRequest("Invalid track ID") from None + + if not track_ids: + raise HTTPBadRequest("No track IDs given") + + for track_id in track_ids: + query = select(Track).filter_by(id=track_id) + track: Track = request.dbsession.execute(query).scalar_one_or_none() + if track is None: + raise HTTPBadRequest("Invalid track ID") + # We don't really want users to add tracks to journeys that they can't + # see, because that leaks information (e.g., you create a journey and + # add a single tracks, that gives you the clear path). + # However, if a track used to be visible and now is no longer, we don't + # want editing to fail, so we allow a non-visible track if it is already + # in the journey. + if not track.is_visible_to(user) and track_id not in current_ids: + raise HTTPBadRequest("Invalid track ID") + + return track_ids + + +@view_config( + route_name="delete-journey", + permission="journey.delete", + request_method="POST", +) +def do_journey_delete(request: Request): + """Handler to delete a journey. + + :param request: The pyramid request. + """ + journey: Journey = request.context + request.data_manager.open_journey(journey.id).purge() + request.dbsession.delete(journey) + request.session.flash(request.localizer.translate(_("flash.journey_deleted"))) + return HTTPFound(request.route_url("journey-list")) + + +@view_config( + route_name="journey-invalidate-share", + permission="journey.edit", + request_method="POST", +) +def do_journey_invalidate_share(request: Request): + """Handler to invalidate a journey share link. + + :param request: The pyramid request. + """ + journey: Journey = request.context + journey.link_secret = util.random_link_secret() + return HTTPFound(request.route_url("journey-details", journey_id=journey.id)) 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/profile.py b/fietsboek/views/profile.py index 15bc46c..6d671b9 100644 --- a/fietsboek/views/profile.py +++ b/fietsboek/views/profile.py @@ -14,7 +14,7 @@ from sqlalchemy import select from sqlalchemy.orm import aliased from .. import models, util -from ..data import DataManager, UserDataDir +from ..data import UserDataDir from ..models.track import TrackType, TrackWithMetadata from ..summaries import CumulativeStats, Summary @@ -54,7 +54,7 @@ def profile_data(request: Request) -> dict: query = select(aliased(models.Track, query)).where(query.c.type == TrackType.ORGANIC) track: models.Track for track in request.dbsession.execute(query).scalars(): - meta = TrackWithMetadata(track, request.data_manager) + meta = TrackWithMetadata(track) total.add(meta) total.moving_time = util.round_to_seconds(total.moving_time) @@ -132,7 +132,6 @@ def profile_calendar(request: Request) -> dict: data["user"] = request.context data["calendar_rows"] = calendar_rows( request.dbsession, - request.data_manager, request.context, date.year, date.month, @@ -161,7 +160,6 @@ def profile_calendar_ym(request: Request) -> dict: data["user"] = request.context data["calendar_rows"] = calendar_rows( request.dbsession, - request.data_manager, request.context, date.year, date.month, @@ -200,7 +198,6 @@ def cell_style(tracks: list[TrackWithMetadata]) -> str: def calendar_rows( dbsession: "sqlalchemy.orm.session.Session", - data_manager: DataManager, user: models.User, year: int, month: int, @@ -222,9 +219,7 @@ def calendar_rows( # Step 1: Retrieve all tracks query = user.all_tracks_query() query = select(aliased(models.Track, query)).where(query.c.type == TrackType.ORGANIC) - tracks = [ - TrackWithMetadata(track, data_manager) for track in dbsession.execute(query).scalars() - ] + tracks = [TrackWithMetadata(track) for track in dbsession.execute(query).scalars()] # Step 2: Build the calendar days = [] @@ -313,7 +308,7 @@ def user_tile(request: Request) -> Response: cursor = connection.cursor() result = cursor.execute( - "SELECT data FROM tiles WHERE zoom = ? AND x = ? AND y = ?;", + "SELECT image FROM tiles WHERE z = ? AND x = ? AND y = ?;", (int(request.matchdict["z"]), int(request.matchdict["x"]), int(request.matchdict["y"])), ) result = result.fetchone() @@ -340,7 +335,7 @@ def json_summary(request: Request) -> Response: if track.cache is None: LOGGER.debug("Skipping track %d as it has no cached metadata", track.id) continue - summary.add(TrackWithMetadata(track, request.data_manager)) + summary.add(TrackWithMetadata(track)) return {y.year: {m.month: m.total_length for m in y} for y in summary} 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..7be5a42 100644 --- a/fietsboek/views/upload.py +++ b/fietsboek/views/upload.py @@ -3,7 +3,6 @@ import datetime import logging -import gpxpy from pyramid.httpexceptions import HTTPBadRequest, HTTPFound from pyramid.i18n import TranslationString as _ from pyramid.response import Response @@ -12,6 +11,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,15 +53,12 @@ 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") - # Before we do anything, we check if we can parse the file. # gpxpy might throw different exceptions, so we simply catch `Exception` # here - if we can't parse it, we don't care too much why at this point. # pylint: disable=broad-except try: - gpxpy.parse(gpx) + track = convert.smart_convert(gpx) except Exception as exc: request.session.flash(request.localizer.translate(_("flash.invalid_file"))) LOGGER.info("Could not parse gpx: %s", exc) @@ -73,7 +70,7 @@ def do_upload(request): owner=request.identity, uploaded_at=now, ) - upload.gpx_data = gpx + upload.gpx_data = track.gpx_xml() request.dbsession.add(upload) request.dbsession.flush() @@ -111,28 +108,19 @@ def finish_upload(request): upload = request.context badges = request.dbsession.execute(select(models.Badge)).scalars() badges = [(False, badge) for badge in badges] - gpx = gpxpy.parse(upload.gpx_data) - timezone = util.guess_gpx_timezone(gpx) - date = gpx.time or gpx.get_time_bounds().start_time or datetime.datetime.now() - date = date.astimezone(timezone) - tz_offset = timezone.utcoffset(date) + track = convert.smart_convert(upload.gpx_data) + timezone = track.date.tzinfo + tz_offset = timezone.utcoffset(track.date) tz_offset = 0 if tz_offset is None else tz_offset.total_seconds() - track_name = "" - track_desc = "" - for track in gpx.tracks: - if not track_name and track.name: - track_name = track.name - if not track_desc and track.description: - track_desc = track.description return { "preview_id": upload.id, - "upload_title": gpx.name or track_name, - "upload_date": date, + "upload_title": track.title, + "upload_date": track.date, "upload_date_tz": int(tz_offset // 60), "upload_visibility": Visibility.PRIVATE, "upload_type": TrackType.ORGANIC, - "upload_description": gpx.description or track_desc, + "upload_description": track.description, "upload_tags": set(), "upload_tagged_people": [], "badges": badges, @@ -165,6 +153,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"]], @@ -180,14 +170,9 @@ def do_finish_upload(request): request.dbsession.delete(upload) # Don't forget to add the images + manager = request.data_manager.open(track.id, force=True) LOGGER.debug("Starting to edit images for the upload") - try: - actions.edit_images(request, track) - except Exception: - # We just created the folder, so we'll be fine deleting it - LOGGER.info("Deleting partially created folder for track %d", track.id) - request.data_manager.open(track.id).purge() - raise + actions.edit_images(request, track, manager=manager) request.session.flash(request.localizer.translate(_("flash.upload_success"))) diff --git a/poetry.lock b/poetry.lock index c1347df..3fc3a7a 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.3.3 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.18.4" description = "A database migration tool for SQLAlchemy." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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.18.4-py3-none-any.whl", hash = "sha256:a5ed4adcf6d8a4cb575f3d759f071b03cd6e5c7618eb796cb52497be25bfe19a"}, + {file = "alembic-1.18.4.tar.gz", hash = "sha256:cb6e1fd84b6174ab8dbb2329f86d631ba9559dd78df550b57804d607672cedbc"}, ] [package.dependencies] Mako = "*" -SQLAlchemy = ">=1.3.0" -typing-extensions = ">=4" +SQLAlchemy = ">=1.4.23" +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 = "4.0.4" description = "An abstract syntax tree for Python with inference support." optional = false -python-versions = ">=3.9.0" +python-versions = ">=3.10.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-4.0.4-py3-none-any.whl", hash = "sha256:52f39653876c7dec3e3afd4c2696920e05c83832b9737afc21928f2d2eb7a753"}, + {file = "astroid-4.0.4.tar.gz", hash = "sha256:986fed8bcf79fb82c78b18a53352a0b287a73817d6dbcfba3162da36667c49a0"}, ] -[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.18.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.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"}, + {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"}, ] [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.14.3" 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.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, + {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, ] [package.dependencies] -soupsieve = ">1.2" +soupsieve = ">=1.6.1" +typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] @@ -103,33 +109,39 @@ lxml = ["lxml"] [[package]] name = "black" -version = "25.1.0" +version = "25.12.0" description = "The uncompromising code formatter." optional = false -python-versions = ">=3.9" -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"}, - {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, - {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, - {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, - {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, - {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, - {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, - {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, - {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, - {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, - {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, - {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, - {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, - {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, - {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, - {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, - {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, - {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, - {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, - {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, - {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, +python-versions = ">=3.10" +groups = ["linters"] +files = [ + {file = "black-25.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f85ba1ad15d446756b4ab5f3044731bf68b777f8f9ac9cdabd2425b97cd9c4e8"}, + {file = "black-25.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:546eecfe9a3a6b46f9d69d8a642585a6eaf348bcbbc4d87a19635570e02d9f4a"}, + {file = "black-25.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17dcc893da8d73d8f74a596f64b7c98ef5239c2cd2b053c0f25912c4494bf9ea"}, + {file = "black-25.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:09524b0e6af8ba7a3ffabdfc7a9922fb9adef60fed008c7cd2fc01f3048e6e6f"}, + {file = "black-25.12.0-cp310-cp310-win_arm64.whl", hash = "sha256:b162653ed89eb942758efeb29d5e333ca5bb90e5130216f8369857db5955a7da"}, + {file = "black-25.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0cfa263e85caea2cff57d8f917f9f51adae8e20b610e2b23de35b5b11ce691a"}, + {file = "black-25.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a2f578ae20c19c50a382286ba78bfbeafdf788579b053d8e4980afb079ab9be"}, + {file = "black-25.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e1b65634b0e471d07ff86ec338819e2ef860689859ef4501ab7ac290431f9b"}, + {file = "black-25.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a3fa71e3b8dd9f7c6ac4d818345237dfb4175ed3bf37cd5a581dbc4c034f1ec5"}, + {file = "black-25.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:51e267458f7e650afed8445dc7edb3187143003d52a1b710c7321aef22aa9655"}, + {file = "black-25.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f96b7c98c1ddaeb07dc0f56c652e25bdedaac76d5b68a059d998b57c55594a"}, + {file = "black-25.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:05dd459a19e218078a1f98178c13f861fe6a9a5f88fc969ca4d9b49eb1809783"}, + {file = "black-25.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1f68c5eff61f226934be6b5b80296cf6939e5d2f0c2f7d543ea08b204bfaf59"}, + {file = "black-25.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:274f940c147ddab4442d316b27f9e332ca586d39c85ecf59ebdea82cc9ee8892"}, + {file = "black-25.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:169506ba91ef21e2e0591563deda7f00030cb466e747c4b09cb0a9dae5db2f43"}, + {file = "black-25.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a05ddeb656534c3e27a05a29196c962877c83fa5503db89e68857d1161ad08a5"}, + {file = "black-25.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ec77439ef3e34896995503865a85732c94396edcc739f302c5673a2315e1e7f"}, + {file = "black-25.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e509c858adf63aa61d908061b52e580c40eae0dfa72415fa47ac01b12e29baf"}, + {file = "black-25.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:252678f07f5bac4ff0d0e9b261fbb029fa530cfa206d0a636a34ab445ef8ca9d"}, + {file = "black-25.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bc5b1c09fe3c931ddd20ee548511c64ebf964ada7e6f0763d443947fd1c603ce"}, + {file = "black-25.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0a0953b134f9335c2434864a643c842c44fba562155c738a2a37a4d61f00cad5"}, + {file = "black-25.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2355bbb6c3b76062870942d8cc450d4f8ac71f9c93c40122762c8784df49543f"}, + {file = "black-25.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9678bd991cc793e81d19aeeae57966ee02909877cb65838ccffef24c3ebac08f"}, + {file = "black-25.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:97596189949a8aad13ad12fcbb4ae89330039b96ad6742e6f6b45e75ad5cfd83"}, + {file = "black-25.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:778285d9ea197f34704e3791ea9404cd6d07595745907dd2ce3da7a13627b29b"}, + {file = "black-25.12.0-py3-none-any.whl", hash = "sha256:48ceb36c16dbc84062740049eef990bb2ce07598272e673c17d1a7720c71c828"}, + {file = "black-25.12.0.tar.gz", hash = "sha256:8d3dd9cea14bff7ddc0eb243c811cdb1a011ebb4800a5f0335a01a68654796a7"}, ] [package.dependencies] @@ -138,8 +150,7 @@ 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\""} +pytokens = ">=0.3.0" [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -149,296 +160,373 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "brotli" -version = "1.1.0" +version = "1.2.0" description = "Python bindings for the Brotli compression library" optional = false python-versions = "*" -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"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ae56aca0402a0f9a3431cddda62ad71666ca9d4dc3a10a142b9dce2e3c0cda3"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43ce1b9935bfa1ede40028054d7f48b5469cd02733a365eec8a329ffd342915d"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c4855522edb2e6ae7fdb58e07c3ba9111e7621a8956f481c68d5d979c93032e"}, - {file = "Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:38025d9f30cf4634f8309c6874ef871b841eb3c347e90b0851f63d1ded5212da"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6a904cb26bfefc2f0a6f240bdf5233be78cd2488900a2f846f3c3ac8489ab80"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a37b8f0391212d29b3a91a799c8e4a2855e0576911cdfb2515487e30e322253d"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e84799f09591700a4154154cab9787452925578841a94321d5ee8fb9a9a328f0"}, - {file = "Brotli-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f66b5337fa213f1da0d9000bc8dc0cb5b896b726eefd9c6046f699b169c41b9e"}, - {file = "Brotli-1.1.0-cp310-cp310-win32.whl", hash = "sha256:be36e3d172dc816333f33520154d708a2657ea63762ec16b62ece02ab5e4daf2"}, - {file = "Brotli-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c6244521dda65ea562d5a69b9a26120769b7a9fb3db2fe9545935ed6735b128"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc"}, - {file = "Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61"}, - {file = "Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265"}, - {file = "Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8"}, - {file = "Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50"}, - {file = "Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"}, - {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"}, - {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"}, - {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"}, - {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"}, - {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"}, - {file = "Brotli-1.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a090ca607cbb6a34b0391776f0cb48062081f5f60ddcce5d11838e67a01928d1"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de9d02f5bda03d27ede52e8cfe7b865b066fa49258cbab568720aa5be80a47d"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2333e30a5e00fe0fe55903c8832e08ee9c3b1382aacf4db26664a16528d51b4b"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4d4a848d1837973bf0f4b5e54e3bec977d99be36a7895c61abb659301b02c112"}, - {file = "Brotli-1.1.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdc3ff3bfccdc6b9cc7c342c03aa2400683f0cb891d46e94b64a197910dc4064"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5eeb539606f18a0b232d4ba45adccde4125592f3f636a6182b4a8a436548b914"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:fd5f17ff8f14003595ab414e45fce13d073e0762394f957182e69035c9f3d7c2"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:069a121ac97412d1fe506da790b3e69f52254b9df4eb665cd42460c837193354"}, - {file = "Brotli-1.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e93dfc1a1165e385cc8239fab7c036fb2cd8093728cbd85097b284d7b99249a2"}, - {file = "Brotli-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:a599669fd7c47233438a56936988a2478685e74854088ef5293802123b5b2460"}, - {file = "Brotli-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d143fd47fad1db3d7c27a1b1d66162e855b5d50a89666af46e1679c496e8e579"}, - {file = "Brotli-1.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:11d00ed0a83fa22d29bc6b64ef636c4552ebafcef57154b4ddd132f5638fbd1c"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f733d788519c7e3e71f0855c96618720f5d3d60c3cb829d8bbb722dddce37985"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:929811df5462e182b13920da56c6e0284af407d1de637d8e536c5cd00a7daf60"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b63b949ff929fbc2d6d3ce0e924c9b93c9785d877a21a1b678877ffbbc4423a"}, - {file = "Brotli-1.1.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d192f0f30804e55db0d0e0a35d83a9fead0e9a359a9ed0285dbacea60cc10a84"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f296c40e23065d0d6650c4aefe7470d2a25fffda489bcc3eb66083f3ac9f6643"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:919e32f147ae93a09fe064d77d5ebf4e35502a8df75c29fb05788528e330fe74"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:23032ae55523cc7bccb4f6a0bf368cd25ad9bcdcc1990b64a647e7bbcce9cb5b"}, - {file = "Brotli-1.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:224e57f6eac61cc449f498cc5f0e1725ba2071a3d4f48d5d9dffba42db196438"}, - {file = "Brotli-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:587ca6d3cef6e4e868102672d3bd9dc9698c309ba56d41c2b9c85bbb903cdb95"}, - {file = "Brotli-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2954c1c23f81c2eaf0b0717d9380bd348578a94161a65b3a2afc62c86467dd68"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:efa8b278894b14d6da122a72fefcebc28445f2d3f880ac59d46c90f4c13be9a3"}, - {file = "Brotli-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:03d20af184290887bdea3f0f78c4f737d126c74dc2f3ccadf07e54ceca3bf208"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6172447e1b368dcbc458925e5ddaf9113477b0ed542df258d84fa28fc45ceea7"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a743e5a28af5f70f9c080380a5f908d4d21d40e8f0e0c8901604d15cfa9ba751"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0541e747cce78e24ea12d69176f6a7ddb690e62c425e01d31cc065e69ce55b48"}, - {file = "Brotli-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cdbc1fc1bc0bff1cef838eafe581b55bfbffaed4ed0318b724d0b71d4d377619"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:890b5a14ce214389b2cc36ce82f3093f96f4cc730c1cffdbefff77a7c71f2a97"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ab4fbee0b2d9098c74f3057b2bc055a8bd92ccf02f65944a241b4349229185a"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:141bd4d93984070e097521ed07e2575b46f817d08f9fa42b16b9b5f27b5ac088"}, - {file = "Brotli-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fce1473f3ccc4187f75b4690cfc922628aed4d3dd013d047f95a9b3919a86596"}, - {file = "Brotli-1.1.0-cp38-cp38-win32.whl", hash = "sha256:db85ecf4e609a48f4b29055f1e144231b90edc90af7481aa731ba2d059226b1b"}, - {file = "Brotli-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3d7954194c36e304e1523f55d7042c59dc53ec20dd4e9ea9d151f1b62b4415c0"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5fb2ce4b8045c78ebbc7b8f3c15062e435d47e7393cc57c25115cfd49883747a"}, - {file = "Brotli-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7905193081db9bfa73b1219140b3d315831cbff0d8941f22da695832f0dd188f"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a77def80806c421b4b0af06f45d65a136e7ac0bdca3c09d9e2ea4e515367c7e9"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dadd1314583ec0bf2d1379f7008ad627cd6336625d6679cf2f8e67081b83acf"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:901032ff242d479a0efa956d853d16875d42157f98951c0230f69e69f9c09bac"}, - {file = "Brotli-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:22fc2a8549ffe699bfba2256ab2ed0421a7b8fadff114a3d201794e45a9ff578"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae15b066e5ad21366600ebec29a7ccbc86812ed267e4b28e860b8ca16a2bc474"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:949f3b7c29912693cee0afcf09acd6ebc04c57af949d9bf77d6101ebb61e388c"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:89f4988c7203739d48c6f806f1e87a1d96e0806d44f0fba61dba81392c9e474d"}, - {file = "Brotli-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:de6551e370ef19f8de1807d0a9aa2cdfdce2e85ce88b122fe9f6b2b076837e59"}, - {file = "Brotli-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f0d8a7a6b5983c2496e364b969f0e526647a06b075d034f3297dc66f3b360c64"}, - {file = "Brotli-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdad5b9014d83ca68c25d2e9444e28e967ef16e80f6b436918c700c117a85467"}, - {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"}, +groups = ["main"] +files = [ + {file = "brotli-1.2.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:99cfa69813d79492f0e5d52a20fd18395bc82e671d5d40bd5a91d13e75e468e8"}, + {file = "brotli-1.2.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3ebe801e0f4e56d17cd386ca6600573e3706ce1845376307f5d2cbd32149b69a"}, + {file = "brotli-1.2.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a387225a67f619bf16bd504c37655930f910eb03675730fc2ad69d3d8b5e7e92"}, + {file = "brotli-1.2.0-cp27-cp27m-win32.whl", hash = "sha256:b908d1a7b28bc72dfb743be0d4d3f8931f8309f810af66c906ae6cd4127c93cb"}, + {file = "brotli-1.2.0-cp27-cp27m-win_amd64.whl", hash = "sha256:d206a36b4140fbb5373bf1eb73fb9de589bb06afd0d22376de23c5e91d0ab35f"}, + {file = "brotli-1.2.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7e9053f5fb4e0dfab89243079b3e217f2aea4085e4d58c5c06115fc34823707f"}, + {file = "brotli-1.2.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4735a10f738cb5516905a121f32b24ce196ab82cfc1e4ba2e3ad1b371085fd46"}, + {file = "brotli-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b90b767916ac44e93a8e28ce6adf8d551e43affb512f2377c732d486ac6514e"}, + {file = "brotli-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6be67c19e0b0c56365c6a76e393b932fb0e78b3b56b711d180dd7013cb1fd984"}, + {file = "brotli-1.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0bbd5b5ccd157ae7913750476d48099aaf507a79841c0d04a9db4415b14842de"}, + {file = "brotli-1.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3f3c908bcc404c90c77d5a073e55271a0a498f4e0756e48127c35d91cf155947"}, + {file = "brotli-1.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b557b29782a643420e08d75aea889462a4a8796e9a6cf5621ab05a3f7da8ef2"}, + {file = "brotli-1.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81da1b229b1889f25adadc929aeb9dbc4e922bd18561b65b08dd9343cfccca84"}, + {file = "brotli-1.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ff09cd8c5eec3b9d02d2408db41be150d8891c5566addce57513bf546e3d6c6d"}, + {file = "brotli-1.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a1778532b978d2536e79c05dac2d8cd857f6c55cd0c95ace5b03740824e0e2f1"}, + {file = "brotli-1.2.0-cp310-cp310-win32.whl", hash = "sha256:b232029d100d393ae3c603c8ffd7e3fe6f798c5e28ddca5feabb8e8fdb732997"}, + {file = "brotli-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:ef87b8ab2704da227e83a246356a2b179ef826f550f794b2c52cddb4efbd0196"}, + {file = "brotli-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:15b33fe93cedc4caaff8a0bd1eb7e3dab1c61bb22a0bf5bdfdfd97cd7da79744"}, + {file = "brotli-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:898be2be399c221d2671d29eed26b6b2713a02c2119168ed914e7d00ceadb56f"}, + {file = "brotli-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:350c8348f0e76fff0a0fd6c26755d2653863279d086d3aa2c290a6a7251135dd"}, + {file = "brotli-1.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1ad3fda65ae0d93fec742a128d72e145c9c7a99ee2fcd667785d99eb25a7fe"}, + {file = "brotli-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40d918bce2b427a0c4ba189df7a006ac0c7277c180aee4617d99e9ccaaf59e6a"}, + {file = "brotli-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2a7f1d03727130fc875448b65b127a9ec5d06d19d0148e7554384229706f9d1b"}, + {file = "brotli-1.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9c79f57faa25d97900bfb119480806d783fba83cd09ee0b33c17623935b05fa3"}, + {file = "brotli-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:844a8ceb8483fefafc412f85c14f2aae2fb69567bf2a0de53cdb88b73e7c43ae"}, + {file = "brotli-1.2.0-cp311-cp311-win32.whl", hash = "sha256:aa47441fa3026543513139cb8926a92a8e305ee9c71a6209ef7a97d91640ea03"}, + {file = "brotli-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:022426c9e99fd65d9475dce5c195526f04bb8be8907607e27e747893f6ee3e24"}, + {file = "brotli-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d382625778834a7f3061b15423919aa03e4f5da34ac8e02c074e4b75ab4f84"}, + {file = "brotli-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a61c06b334bd99bc5ae84f1eeb36bfe01400264b3c352f968c6e30a10f9d08b"}, + {file = "brotli-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:acec55bb7c90f1dfc476126f9711a8e81c9af7fb617409a9ee2953115343f08d"}, + {file = "brotli-1.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:260d3692396e1895c5034f204f0db022c056f9e2ac841593a4cf9426e2a3faca"}, + {file = "brotli-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:072e7624b1fc4d601036ab3f4f27942ef772887e876beff0301d261210bca97f"}, + {file = "brotli-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adedc4a67e15327dfdd04884873c6d5a01d3e3b6f61406f99b1ed4865a2f6d28"}, + {file = "brotli-1.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7a47ce5c2288702e09dc22a44d0ee6152f2c7eda97b3c8482d826a1f3cfc7da7"}, + {file = "brotli-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:af43b8711a8264bb4e7d6d9a6d004c3a2019c04c01127a868709ec29962b6036"}, + {file = "brotli-1.2.0-cp312-cp312-win32.whl", hash = "sha256:e99befa0b48f3cd293dafeacdd0d191804d105d279e0b387a32054c1180f3161"}, + {file = "brotli-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b35c13ce241abdd44cb8ca70683f20c0c079728a36a996297adb5334adfc1c44"}, + {file = "brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab"}, + {file = "brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c"}, + {file = "brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f"}, + {file = "brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6"}, + {file = "brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c"}, + {file = "brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48"}, + {file = "brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18"}, + {file = "brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5"}, + {file = "brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a"}, + {file = "brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8"}, + {file = "brotli-1.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c12dad5cd04530323e723787ff762bac749a7b256a5bece32b2243dd5c27b21"}, + {file = "brotli-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3219bd9e69868e57183316ee19c84e03e8f8b5a1d1f2667e1aa8c2f91cb061ac"}, + {file = "brotli-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:963a08f3bebd8b75ac57661045402da15991468a621f014be54e50f53a58d19e"}, + {file = "brotli-1.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9322b9f8656782414b37e6af884146869d46ab85158201d82bab9abbcb971dc7"}, + {file = "brotli-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf9cba6f5b78a2071ec6fb1e7bd39acf35071d90a81231d67e92d637776a6a63"}, + {file = "brotli-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7547369c4392b47d30a3467fe8c3330b4f2e0f7730e45e3103d7d636678a808b"}, + {file = "brotli-1.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361"}, + {file = "brotli-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2d085ded05278d1c7f65560aae97b3160aeb2ea2c0b3e26204856beccb60888"}, + {file = "brotli-1.2.0-cp314-cp314-win32.whl", hash = "sha256:832c115a020e463c2f67664560449a7bea26b0c1fdd690352addad6d0a08714d"}, + {file = "brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3"}, + {file = "brotli-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:82676c2781ecf0ab23833796062786db04648b7aae8be139f6b8065e5e7b1518"}, + {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c16ab1ef7bb55651f5836e8e62db1f711d55b82ea08c3b8083ff037157171a69"}, + {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e85190da223337a6b7431d92c799fca3e2982abd44e7b8dec69938dcc81c8e9e"}, + {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d8c05b1dfb61af28ef37624385b0029df902ca896a639881f594060b30ffc9a7"}, + {file = "brotli-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:465a0d012b3d3e4f1d6146ea019b5c11e3e87f03d1676da1cc3833462e672fb0"}, + {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:96fbe82a58cdb2f872fa5d87dedc8477a12993626c446de794ea025bbda625ea"}, + {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:1b71754d5b6eda54d16fbbed7fce2d8bc6c052a1b91a35c320247946ee103502"}, + {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:66c02c187ad250513c2f4fce973ef402d22f80e0adce734ee4e4efd657b6cb64"}, + {file = "brotli-1.2.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:ba76177fd318ab7b3b9bf6522be5e84c2ae798754b6cc028665490f6e66b5533"}, + {file = "brotli-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:c1702888c9f3383cc2f09eb3e88b8babf5965a54afb79649458ec7c3c7a63e96"}, + {file = "brotli-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f8d635cafbbb0c61327f942df2e3f474dde1cff16c3cd0580564774eaba1ee13"}, + {file = "brotli-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e80a28f2b150774844c8b454dd288be90d76ba6109670fe33d7ff54d96eb5cb8"}, + {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b1b799f45da91292ffaa21a473ab3a3054fa78560e8ff67082a185274431c8"}, + {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29b7e6716ee4ea0c59e3b241f682204105f7da084d6254ec61886508efeb43bc"}, + {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:640fe199048f24c474ec6f3eae67c48d286de12911110437a36a87d7c89573a6"}, + {file = "brotli-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:92edab1e2fd6cd5ca605f57d4545b6599ced5dea0fd90b2bcdf8b247a12bd190"}, + {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7274942e69b17f9cef76691bcf38f2b2d4c8a5f5dba6ec10958363dcb3308a0a"}, + {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:a56ef534b66a749759ebd091c19c03ef81eb8cd96f0d1d16b59127eaf1b97a12"}, + {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5732eff8973dd995549a18ecbd8acd692ac611c5c0bb3f59fa3541ae27b33be3"}, + {file = "brotli-1.2.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:598e88c736f63a0efec8363f9eb34e5b5536b7b6b1821e401afcb501d881f59a"}, + {file = "brotli-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:7ad8cec81f34edf44a1c6a7edf28e7b7806dfb8886e371d95dcf789ccd4e4982"}, + {file = "brotli-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:865cedc7c7c303df5fad14a57bc5db1d4f4f9b2b4d0a7523ddd206f00c121a16"}, + {file = "brotli-1.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ac27a70bda257ae3f380ec8310b0a06680236bea547756c277b5dfe55a2452a8"}, + {file = "brotli-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e813da3d2d865e9793ef681d3a6b66fa4b7c19244a45b817d0cceda67e615990"}, + {file = "brotli-1.2.0-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fe11467c42c133f38d42289d0861b6b4f9da31e8087ca2c0d7ebb4543625526"}, + {file = "brotli-1.2.0-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c0d6770111d1879881432f81c369de5cde6e9467be7c682a983747ec800544e2"}, + {file = "brotli-1.2.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:eda5a6d042c698e28bda2507a89b16555b9aa954ef1d750e1c20473481aff675"}, + {file = "brotli-1.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3173e1e57cebb6d1de186e46b5680afbd82fd4301d7b2465beebe83ed317066d"}, + {file = "brotli-1.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:71a66c1c9be66595d628467401d5976158c97888c2c9379c034e1e2312c5b4f5"}, + {file = "brotli-1.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:1e68cdf321ad05797ee41d1d09169e09d40fdf51a725bb148bff892ce04583d7"}, + {file = "brotli-1.2.0-cp38-cp38-win32.whl", hash = "sha256:f16dace5e4d3596eaeb8af334b4d2c820d34b8278da633ce4a00020b2eac981c"}, + {file = "brotli-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:14ef29fc5f310d34fc7696426071067462c9292ed98b5ff5a27ac70a200e5470"}, + {file = "brotli-1.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8d4f47f284bdd28629481c97b5f29ad67544fa258d9091a6ed1fda47c7347cd1"}, + {file = "brotli-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2881416badd2a88a7a14d981c103a52a23a276a553a8aacc1346c2ff47c8dc17"}, + {file = "brotli-1.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d39b54b968f4b49b5e845758e202b1035f948b0561ff5e6385e855c96625971"}, + {file = "brotli-1.2.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:95db242754c21a88a79e01504912e537808504465974ebb92931cfca2510469e"}, + {file = "brotli-1.2.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bba6e7e6cfe1e6cb6eb0b7c2736a6059461de1fa2c0ad26cf845de6c078d16c8"}, + {file = "brotli-1.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:88ef7d55b7bcf3331572634c3fd0ed327d237ceb9be6066810d39020a3ebac7a"}, + {file = "brotli-1.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7fa18d65a213abcfbb2f6cafbb4c58863a8bd6f2103d65203c520ac117d1944b"}, + {file = "brotli-1.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:09ac247501d1909e9ee47d309be760c89c990defbb2e0240845c892ea5ff0de4"}, + {file = "brotli-1.2.0-cp39-cp39-win32.whl", hash = "sha256:c25332657dee6052ca470626f18349fc1fe8855a56218e19bd7a8c6ad4952c49"}, + {file = "brotli-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:1ce223652fd4ed3eb2b7f78fbea31c52314baecfac68db44037bb4167062a937"}, + {file = "brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a"}, ] [[package]] name = "certifi" -version = "2024.12.14" +version = "2026.4.22" 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-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"}, + {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"}, ] [[package]] name = "cffi" -version = "1.17.1" +version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false -python-versions = ">=3.8" -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"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, - {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, - {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, - {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, - {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, - {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, - {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, - {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, - {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, - {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, - {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, - {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, - {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, - {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, - {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, - {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, - {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, - {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, - {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, - {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, - {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, - {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, - {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, - {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, - {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, - {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, - {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, - {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, - {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, - {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +python-versions = ">=3.9" +groups = ["main", "types"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, + {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4"}, + {file = "cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5"}, + {file = "cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb"}, + {file = "cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a"}, + {file = "cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe"}, + {file = "cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664"}, + {file = "cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414"}, + {file = "cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743"}, + {file = "cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5"}, + {file = "cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5"}, + {file = "cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d"}, + {file = "cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037"}, + {file = "cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94"}, + {file = "cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187"}, + {file = "cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18"}, + {file = "cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5"}, + {file = "cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb"}, + {file = "cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3"}, + {file = "cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c"}, + {file = "cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b"}, + {file = "cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27"}, + {file = "cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75"}, + {file = "cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5"}, + {file = "cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef"}, + {file = "cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205"}, + {file = "cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1"}, + {file = "cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f"}, + {file = "cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25"}, + {file = "cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9"}, + {file = "cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc"}, + {file = "cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512"}, + {file = "cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4"}, + {file = "cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e"}, + {file = "cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6"}, + {file = "cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:fe562eb1a64e67dd297ccc4f5addea2501664954f2692b69a76449ec7913ecbf"}, + {file = "cffi-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de8dad4425a6ca6e4e5e297b27b5c824ecc7581910bf9aee86cb6835e6812aa7"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4647afc2f90d1ddd33441e5b0e85b16b12ddec4fca55f0d9671fef036ecca27c"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3f4d46d8b35698056ec29bca21546e1551a205058ae1a181d871e278b0b28165"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e6e73b9e02893c764e7e8d5bb5ce277f1a009cd5243f8228f75f842bf937c534"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:cb527a79772e5ef98fb1d700678fe031e353e765d1ca2d409c92263c6d43e09f"}, + {file = "cffi-2.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61d028e90346df14fedc3d1e5441df818d095f3b87d286825dfcbd6459b7ef63"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0f6084a0ea23d05d20c3edcda20c3d006f9b6f3fefeac38f59262e10cef47ee2"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1cd13c99ce269b3ed80b417dcd591415d3372bcac067009b6e0f59c7d4015e65"}, + {file = "cffi-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89472c9762729b5ae1ad974b777416bfda4ac5642423fa93bd57a09204712322"}, + {file = "cffi-2.0.0-cp39-cp39-win32.whl", hash = "sha256:2081580ebb843f759b9f617314a24ed5738c51d2aee65d31e02f6f7a2b97707a"}, + {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, + {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] [package.dependencies] -pycparser = "*" +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.7" 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.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"}, + {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"}, + {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"}, + {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"}, + {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"}, + {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"}, + {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"}, + {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"}, + {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"}, + {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"}, + {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"}, ] [[package]] name = "click" -version = "8.1.8" +version = "8.3.3" 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.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"}, + {file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"}, ] [package.dependencies] @@ -446,22 +534,24 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "click-option-group" -version = "0.5.6" +version = "0.5.9" 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.9-py3-none-any.whl", hash = "sha256:ad2599248bd373e2e19bec5407967c3eec1d0d4fc4a5e77b08a0481e75991080"}, + {file = "click_option_group-0.5.9.tar.gz", hash = "sha256:f94ed2bc4cf69052e0f29592bd1e771a1789bd7bfc482dd0bc482134aff95823"}, ] [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,209 +559,443 @@ 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 = "contourpy" +version = "1.3.3" +description = "Python library for calculating contours of 2D quadrilateral grids" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1"}, + {file = "contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a"}, + {file = "contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620"}, + {file = "contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f"}, + {file = "contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff"}, + {file = "contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42"}, + {file = "contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"}, + {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"}, + {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"}, + {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"}, + {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"}, + {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"}, + {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"}, + {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"}, + {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"}, + {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"}, + {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"}, + {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"}, + {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"}, + {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"}, + {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"}, + {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"}, + {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"}, + {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"}, + {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"}, + {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"}, + {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"}, + {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"}, + {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"}, + {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"}, + {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"}, + {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"}, + {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"}, + {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"}, + {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989"}, + {file = "contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77"}, + {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"}, +] + +[package.dependencies] +numpy = ">=1.25" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] +mypy = ["bokeh", "contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.17.0)", "types-Pillow"] +test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] +test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] [[package]] name = "coverage" -version = "7.6.10" +version = "7.14.0" 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"}, +python-versions = ">=3.10" +groups = ["testing"] +files = [ + {file = "coverage-7.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:84c32d90bf4537f0e7b4dec9aaa9a938fb8205136b9d2ecf4d7629d5262dc075"}, + {file = "coverage-7.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7c843572c605ab51cfdb5c6b5f2586e2a8467c0d28eca4bdef4ec70c5fecbd82"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0c451757d3fa2603354fdc789b5e58a0e327a117c370a40e3476ba4eabab228c"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3fd43f0616e765ab78d069cf8358def7363957a45cee446d65c502dcfeea7893"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:731e535b1498b27d13594a0527a79b0510867b0ad891532be41cb883f2128e20"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7492f2d493b976941c7ca050f273cbda2f43c381124f7586a3e3c16d1804fec"}, + {file = "coverage-7.14.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dc38367eaa2abb1b766ac333142bce7655335a73537f5c8b75aaa89c2b987757"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0a951308cde22cf77f953955a754d04dccb57fe3bb8e345d685778ed9fc1632a"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fab3877e4ebb06bd9d4d4d00ee53309ee5478e66873c66a382272e3ee33eb7ea"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b812eb847b19876ebf33fb6c4f11819af05ab6050b0bfa1bc53412ae81779adb"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d9c8ef6ed820c433de075657d72dda1f89a2984955e58b8a75feb3f184250218"}, + {file = "coverage-7.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d128b1bba9361fbaaf6a19e179e6cfd6a9103ce0c0555876f72780acc93efd85"}, + {file = "coverage-7.14.0-cp310-cp310-win32.whl", hash = "sha256:65f267ca1370726ec2c1aa38bbe4df9a71a740f22878d2d4bf59d71a4cd8d323"}, + {file = "coverage-7.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:b34ece8065914f938ed7f2c5872bb865336977a52919149846eac3744327267a"}, + {file = "coverage-7.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a78e2a9d9c5e3b8d4ab9b9d28c985ea66fced0a7d7c2aec1f216e03a2011480"}, + {file = "coverage-7.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1816c505187592dcd1c5a5f226601a549f70365fbd00930ac88b0c225b76bb4"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d8e1762f0e9cbc26ec315471e7b47855218e833cd5a032d706fbf43845d878c7"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9336e23e8bb3a3925398261385e2a1533957d3e760e91070dcb0e98bfa514eed"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd1169b2230f9cbe9c638ba38022ed7a2b1e641cc07f7cea0365e4be2a74980"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d1bb3543b58fea74d2cd1abc4054cc927e4724687cb4560cd2ed88d2c7d820c0"}, + {file = "coverage-7.14.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a93bac2cb577ef60074999ed56d8a1535894398e2ed920d4185c3ec0c8864742"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5904abf7e18cddc463219b17552229650c6b79e061d31a1059283051169cf7d5"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:741f57cddc9004a8c81b084660215f33a6b597dbe62c31386b983ee26310e327"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:664123feb0929d7affc135717dbd70d61d98688a08ab1e5ba464739620c6252d"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c83d2399a51bbec8429266905d33616f04bc5726b1138c35844d5fcd896b2e20"}, + {file = "coverage-7.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb2e855b87321259a037429288ae85216d191c74de3e79bf57cd2bc0761992c"}, + {file = "coverage-7.14.0-cp311-cp311-win32.whl", hash = "sha256:731dc15b385ac52289743d476245b61e1a2927e803bef655b52bc3b2a75a21f3"}, + {file = "coverage-7.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:bfb0ed8ec5d25e93face268115d7964db9df8b9aae8edcde9ec6b16c726a7cc1"}, + {file = "coverage-7.14.0-cp311-cp311-win_arm64.whl", hash = "sha256:7ebb1c6df9f78046a1b1e0a89674cd4bf73b7c648914eebcf976a57fd99a5627"}, + {file = "coverage-7.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7ffd19fc8aed057fd686a17a4935eef5f9859d69208f96310e893e64b9b6ccf5"}, + {file = "coverage-7.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:829994cfe1aeb773ca27bf246d4badc1e764893e3bfb98fff820fcecd1ca4662"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b4f07cf7edcb7ec39431a5074d7ea83b29a9f71fcfc494f0f40af4e65180420f"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca3d9cf2c32b521bd9518385608787fa86f38daf993695307531822c3430ed67"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92af52828e7f29d827346b0294e5a0853fa206db77db0395b282918d41e28db9"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b2bb6c9d7e769360d0f20a0f219603fd64f0c8f97de17ab25853261602be0fb"}, + {file = "coverage-7.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c9ed6ef99f88fb8c14aa8e2bf8eb0fe55fa2edfea68f8675d78741df1a5ac0e"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8231ade007f37959fbf58acc677f26b922c02eda6f0428ea307da0fd39681bf3"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d8b013632cc1ce1d09dbe4f32667b4d320ec2f54fc326ebeffcd0b0bcc2bb6c4"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1733198802d71ec4c524f322e2867ee05c62e9e75df86bdca545407a221827d1"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:72a305291fa8ee01332f1aaf38b348ca34097f6aa0b0ef627eef2837e57bbba5"}, + {file = "coverage-7.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcaba850dd317c65423a9d63d88f9573c53b00354d6dd95724576cc98a131595"}, + {file = "coverage-7.14.0-cp312-cp312-win32.whl", hash = "sha256:5ac83957a80d0701310e96d8bec68cdcf4f90a7674b7d13f15a344315b41ab27"}, + {file = "coverage-7.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:70390b0da32cb90b501953716302906e8bcce087cb283e70d8c97729f22e92b2"}, + {file = "coverage-7.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:91b993743d959b8be85b4abf9d5478216a69329c321efe5be0433c1a841d691d"}, + {file = "coverage-7.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f2bbb8254370eb4c628ff3d6fa8a7f74ddc40565394d4f7ab791d1fe568e37ef"}, + {file = "coverage-7.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23b81107f46d3f21d0cbce30664fcec0f5d9f585638a67081750f99738f6bf66"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:22a7e06a5f11a757cdfe79018e9095f9f69ae283c5cd8123774c788deec8717b"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9d1aa57a1dc8e05bdc42e81c5d671d849577aeedf279f4c449d6d286f9ed88ca"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90c1a51bcfddf645b3bb7ec333d9e94393a8e94f55642380fa8a9a5a9e636cb7"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a841fae2fadcae4f438d43b6ccc4aac2ad609f47cdb6cfdce60cbb3fe5ca7bc2"}, + {file = "coverage-7.14.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c79d2319cabef1fe8e86df73371126931550804738f78ad7d31e3aad85a67367"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b23b0c6f0b1db6ad769b7050c8b641c0bf215ded26c1816955b17b7f26edfa9"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:55d3089079ce181a4566b1065ab28d2575eb76d8ac8f81f4fcda2bf037fee087"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:49c005cba1e2f9677fb2845dcdf9a2e72a52a17d63e8231aaaae35d9f50215ef"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9117377b823daa28aa8635fbb08cda1cd6be3d7143257345459559aeef852d52"}, + {file = "coverage-7.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7b79d646cf46d5cf9a9f40281d4441df5849e445726e369006d2b117710b33fe"}, + {file = "coverage-7.14.0-cp313-cp313-win32.whl", hash = "sha256:fb609b3658479e33f9516d46f1a89dbb9b6c261366e3a11844a96ec487533dae"}, + {file = "coverage-7.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0773d8329cf32b6fd222e4b52622c61fe8d503eb966cfc8d3c3c10c96266d50e"}, + {file = "coverage-7.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:b4e26a0f1b696faf283bffe5b8569e44e336c582439df5d53281ab89ee0cba96"}, + {file = "coverage-7.14.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:953f521ca9445300397e65fda3dca58b2dbd68fee983777420b57ac3c77e9f90"}, + {file = "coverage-7.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:98af83fd65ae24b1fdd03aaead967a9f523bcd2f1aab2d4f3ffda65bb568a6f1"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:668b92e6958c4db7cf92e81caac328dfbbdbb215db2850ad28f0cbe1eea0bfbd"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9fbd898551762dea00d3fef2b1c4f99afd2c6a3ff952ea07d60a9bd5ed4f34bc"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68af363c07ecd8d4b7d4043d85cb376d7d227eceb54e5323ee45da73dbd3e426"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e57054a583da8ac55edf24117ea4c9133032cfc4cf72aa2d48c1e5d4b52f899"}, + {file = "coverage-7.14.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3499459bbcdd51a65b64c35ab7ed2764eaf3cba826e0df3f1d7fe2e102b70b"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:45899ec2138a4346ed34d601dedf5076fb74edf2d1dd9dc76a78e82397edee90"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8767486808c436f05b23ab98eb963fb29185e32a9357a166971685cb3459900f"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a3b5ddfd6aa7ddad53ee3edb231e88a2151507a43229b7d71b953916deca127d"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:63df0fe568e698e1045792399f8ab6da3a6c2dce3182813fb92afa2641087b47"}, + {file = "coverage-7.14.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:827d6397dbd95144939b18f89edf31f63e1f99633e8d5f32f22ba8bdda567477"}, + {file = "coverage-7.14.0-cp313-cp313t-win32.whl", hash = "sha256:7bf43e000d24012599b879791cff41589af90674722421ef11b11a5431920bab"}, + {file = "coverage-7.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3f5549365af25d770e06b1f8f5682d9a5637d06eb494db91c6fa75d3950cc917"}, + {file = "coverage-7.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6d160217ec6fe890f16ad3a9531761589443749e448f91986c972714fad361c8"}, + {file = "coverage-7.14.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9aed9fa983514ca032790f3fe0d1c0e42ca7e16b42432af1706b50a9a46bef5d"}, + {file = "coverage-7.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ba3b8390db29296dbbf49e91b6fe08f990743a90c8f447ba4c2ffc29670dfa63"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3a5d8e876dfa2f102e970b183863d6dedd023d3c0eeca1fe7a9787bc5f28b212"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ebb8f4614a3787d567e610bbfdf96a4798dd69a1afb1bd8ad228d4111fe6ff3"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b9bf47223dd8db3d4c4b2e443b02bace480d428f0822c3f991600448a176c97"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3485a836550b303d006d57cc06e3d5afaabc642c77050b7c985a97b13e3776b8"}, + {file = "coverage-7.14.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3e7e88110bae996d199d1693ca8ec3fd52441d426401ae963437598667b4c5eb"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15228a6800ce7bdf1b74800595e56db7138cecb338fdbf044806e10dcf182dfe"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9d26ac7f5398bafc5b57421ad994e8a4749e8a7a0e62d05ec7d53014d5963bfa"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb73254ff43c911c967a899e1359bc5049b4b115d6e8fbdde4937d0a2246cd5"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:454a380af72c6adada298ed270d38c7a391288198dbfb8467f786f588751a90c"}, + {file = "coverage-7.14.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:65c86fb646d2bd2972e96bd1a8b45817ed907cee68655d6295fe7ec031d04cca"}, + {file = "coverage-7.14.0-cp314-cp314-win32.whl", hash = "sha256:6a6516b02a6101398e19a3f44820f69bab2590697f7def4331f668b14adaf828"}, + {file = "coverage-7.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:45e0f79d8351fa76e256716df91eab12890d32678b9590df7ae1042e4bd4cf5d"}, + {file = "coverage-7.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:4b899594a8b2d81e5cc064a0d7f9cac2081fed91049456cae7676787e41549c9"}, + {file = "coverage-7.14.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f580f8c80acd94ac72e863efe2cab791d8c38d153e0b463b92dfa000d5c84cd1"}, + {file = "coverage-7.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a2bd259c442cd43c49b30fbafc51776eb19ea396faf159d26a83e6a0a5f13b0c"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a706b908dfa85538863504c624b237a3cc34232bf403c057414ebfdb3b4d9f84"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7333cd944ee4393b9b3d3c1b598c936d4fc8d70573a4c7dacfec5590dd50e436"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f162bc9a15b82d947b02651b0c7e1609d6f7a8735ca330cfadec8481dd97d5a"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:362cb78e01a5dc82009d88004cf60f2e6b6d6fcbfdec05b05af73b0abf40118f"}, + {file = "coverage-7.14.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:acebd068fca5512c3a6fde9c045f901613478781a73f0e82b307b214daef23fb"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:29fe3da551dface75deb2ccbf87b6b66e2e7ef38f6d89050b428be94afff3490"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b4cc4fce8672fffcb09b0eafc167b396b3ba53c4a7230f54b7aaffbf6c835fa9"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5d4a51aad8ba8bdcd2b8bd8f03d4aca19693fa2327a3470e4718a25b03481020"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9f323af3e1e4f68b60b7b247e37b8515563a61375518fa59de1af48ba28a3db6"}, + {file = "coverage-7.14.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1a0abc7342ea9711c469dd8b821c6c311e6bc6aac1442e5fbd6b27fae0a8f3db"}, + {file = "coverage-7.14.0-cp314-cp314t-win32.whl", hash = "sha256:a9f864ef57b7172e2db87a096642dd51e179e085ab6b2c371c29e885f65c8fb2"}, + {file = "coverage-7.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29943e552fdc08e082eb51400fb2f58e118a83b5542bd06531214e084399b644"}, + {file = "coverage-7.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:742a73ea621953b012f2c4c2219b512180dd84489acf5b1596b0aafc55b9100b"}, + {file = "coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1"}, + {file = "coverage-7.14.0.tar.gz", hash = "sha256:057a6af2f160a85384cde4ab36f0d2777bae1057bae255f95413cdd382aa5c74"}, ] -[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 = "46.0.7" 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"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.8" +groups = ["main", "types"] +files = [ + {file = "cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb"}, + {file = "cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b"}, + {file = "cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85"}, + {file = "cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e"}, + {file = "cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457"}, + {file = "cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b"}, + {file = "cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1"}, + {file = "cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2"}, + {file = "cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e"}, + {file = "cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee"}, + {file = "cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298"}, + {file = "cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb"}, + {file = "cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006"}, + {file = "cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0"}, + {file = "cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85"}, + {file = "cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e"}, + {file = "cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246"}, + {file = "cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968"}, + {file = "cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4"}, + {file = "cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5"}, ] [package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} +cffi = {version = ">=2.0.0", markers = "python_full_version >= \"3.9.0\" and 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", "sphinx-rtd-theme (>=3.0.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[uv] (>=2024.4.15)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] 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 (==46.0.7)", "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" -description = "serialize all of Python" +name = "cycler" +version = "0.12.1" +description = "Composable style cycles" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "dill-0.3.9-py3-none-any.whl", hash = "sha256:468dff3b89520b474c0397703366b7b95eebe6303f108adf9b19da1f702be87a"}, - {file = "dill-0.3.9.tar.gz", hash = "sha256:81aa267dddf68cbfe8029c42ca9ec6a4ab3b22371d1c450abc54422577b4512c"}, + {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, + {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, ] [package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] +docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] +tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] -name = "docutils" -version = "0.21.2" -description = "Docutils -- Python Documentation Utilities" +name = "dill" +version = "0.4.1" +description = "serialize all of Python" optional = false python-versions = ">=3.9" +groups = ["linters"] files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, + {file = "dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d"}, + {file = "dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa"}, ] +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + [[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" +name = "docutils" +version = "0.22.4" +description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["docs"] files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, + {file = "docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de"}, + {file = "docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968"}, ] -[package.extras] -test = ["pytest (>=6)"] - [[package]] name = "filelock" -version = "3.17.0" +version = "3.29.0" description = "A platform independent file lock." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +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.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258"}, + {file = "filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90"}, ] -[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)"] - [[package]] name = "fitparse" 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"}, ] [[package]] +name = "fonttools" +version = "4.63.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b"}, + {file = "fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94"}, + {file = "fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579"}, + {file = "fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22"}, + {file = "fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e"}, + {file = "fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69"}, + {file = "fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e"}, + {file = "fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac"}, + {file = "fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f"}, + {file = "fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9"}, + {file = "fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b"}, + {file = "fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18"}, + {file = "fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0"}, + {file = "fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007"}, + {file = "fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb"}, + {file = "fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c"}, + {file = "fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02"}, + {file = "fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0"}, + {file = "fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af"}, + {file = "fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8"}, + {file = "fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b"}, + {file = "fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78"}, + {file = "fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263"}, + {file = "fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272"}, + {file = "fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd"}, + {file = "fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59"}, + {file = "fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d"}, + {file = "fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68"}, + {file = "fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be"}, + {file = "fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27"}, + {file = "fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380"}, + {file = "fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b"}, + {file = "fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745"}, + {file = "fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03"}, + {file = "fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49"}, + {file = "fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b"}, + {file = "fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6"}, + {file = "fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4"}, + {file = "fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616"}, + {file = "fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5"}, + {file = "fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001"}, + {file = "fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e"}, + {file = "fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096"}, + {file = "fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f"}, + {file = "fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40"}, + {file = "fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196"}, + {file = "fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8"}, + {file = "fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419"}, + {file = "fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d"}, + {file = "fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0"}, +] + +[package.extras] +all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.45.0)", "unicodedata2 (>=17.0.0) ; python_version <= \"3.14\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""] +lxml = ["lxml (>=4.0)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.45.0)"] +symfont = ["sympy"] +type1 = ["xattr ; sys_platform == \"darwin\""] +unicode = ["unicodedata2 (>=17.0.0) ; python_version <= \"3.14\""] +woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"] + +[[package]] name = "gpxpy" 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,89 +1003,94 @@ files = [ [[package]] name = "greenlet" -version = "3.1.1" +version = "3.5.0" 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.10" +groups = ["main", "testing"] +files = [ + {file = "greenlet-3.5.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:29ea813b2e1f45fa9649a17853b2b5465c4072fbcb072e5af6cd3a288216574a"}, + {file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:804a70b328e706b785c6ef16187051c394a63dd1a906d89be24b6ad77759f13f"}, + {file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:884f649de075b84739713d41dd4dfd41e2b910bfb769c4a3ea02ec1da52cd9bb"}, + {file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d0eadc7e4d9ffb2af4247b606cae307be8e448911e5a0d0b16d72fc3d224cfd"}, + {file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b28037cb07768933c54d81bfe47a85f9f402f57d7d69743b991a713b63954eb"}, + {file = "greenlet-3.5.0-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:f8c30c2225f40dd76c50790f0eb3b5c7c18431efb299e2782083e1981feed243"}, + {file = "greenlet-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cda05425526240807408156b6960a17a79a0c760b813573b67027823be760977"}, + {file = "greenlet-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c615f869163e14bb1ced20322d8038fb680b08236521ac3f30cd4c1288785a0"}, + {file = "greenlet-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba8f0bdc2fae6ce915dfd0c16d2d00bca7e4247c1eae4416e06430e522137858"}, + {file = "greenlet-3.5.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082"}, + {file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3"}, + {file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c"}, + {file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa94cb2288681e3a11645958f1871d48ee9211bd2f66628fdace505927d6e564"}, + {file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662"}, + {file = "greenlet-3.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:64d6ac45f7271f48e45f67c95b54ef73534c52ec041fcda8edf520c6d811f4bc"}, + {file = "greenlet-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b"}, + {file = "greenlet-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4"}, + {file = "greenlet-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8"}, + {file = "greenlet-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:6c18dfb59c70f5a94acd271c72e90128c3c776e41e5f07767908c8c1b74ad339"}, + {file = "greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f"}, + {file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628"}, + {file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b"}, + {file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:41353ec2ecedf7aa8f682753a41919f8718031a6edac46b8d3dc7ed9e1ceb136"}, + {file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c"}, + {file = "greenlet-3.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:58c1c374fe2b3d852f9b6b11a7dff4c85404e51b9a596fd9e89cf904eb09866d"}, + {file = "greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588"}, + {file = "greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e"}, + {file = "greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8"}, + {file = "greenlet-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2"}, + {file = "greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106"}, + {file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b"}, + {file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e"}, + {file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d"}, + {file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13"}, + {file = "greenlet-3.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae"}, + {file = "greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba"}, + {file = "greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846"}, + {file = "greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5"}, + {file = "greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b"}, + {file = "greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8"}, + {file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1"}, + {file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3"}, + {file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37"}, + {file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7"}, + {file = "greenlet-3.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2"}, + {file = "greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf"}, + {file = "greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16"}, + {file = "greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033"}, + {file = "greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988"}, + {file = "greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853"}, + {file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f"}, + {file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7"}, + {file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce"}, + {file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112"}, + {file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2"}, + {file = "greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2"}, + {file = "greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2"}, + {file = "greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86"}, + {file = "greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4"}, +] +markers = {main = "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"] -test = ["objgraph", "psutil"] +test = ["objgraph", "psutil", "setuptools"] + +[[package]] +name = "hittekaart-py" +version = "0.2.0" +description = "" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [] +develop = false + +[package.source] +type = "git" +url = "https://gitlab.com/dunj3/hittekaart" +reference = "9a576a8d3a8cb1d143be0ade0310f71b490cacb9" +resolved_reference = "9a576a8d3a8cb1d143be0ade0310f71b490cacb9" +subdirectory = "hittekaart-py" [[package]] name = "hupper" @@ -769,6 +1098,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"}, @@ -780,64 +1110,82 @@ testing = ["mock", "pytest", "pytest-cov", "watchdog"] [[package]] name = "idna" -version = "3.10" +version = "3.15" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +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"}, + {file = "idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8"}, + {file = "idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc"}, ] [package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "imagesize" -version = "1.4.1" +version = "1.5.0" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["docs"] +markers = "python_version >= \"3.15\"" files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, + {file = "imagesize-1.5.0-py2.py3-none-any.whl", hash = "sha256:32677681b3f434c2cb496f00e89c5a291247b35b1f527589909e008057da5899"}, + {file = "imagesize-1.5.0.tar.gz", hash = "sha256:8bfc5363a7f2133a89f0098451e0bcb1cd71aba4dc02bbcecb39d99d40e1b94f"}, ] [[package]] -name = "iniconfig" +name = "imagesize" version = "2.0.0" +description = "Get image size from headers (BMP/PNG/JPEG/JPEG2000/GIF/TIFF/SVG/Netpbm/WebP/AVIF/HEIC/HEIF)" +optional = false +python-versions = "<3.15,>=3.10" +groups = ["docs"] +markers = "python_version < \"3.15\"" +files = [ + {file = "imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96"}, + {file = "imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3"}, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" +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.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] [[package]] name = "isort" -version = "6.0.0" +version = "8.0.1" description = "A Python utility / library to sort Python imports." optional = false -python-versions = ">=3.9.0" +python-versions = ">=3.10.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-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75"}, + {file = "isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d"}, ] [package.extras] colors = ["colorama"] -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] @@ -847,25 +1195,256 @@ MarkupSafe = ">=2.0" 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" +name = "kiwisolver" +version = "1.5.0" +description = "A fast implementation of the Cassowary constraint solver" optional = false python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374"}, + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd"}, + {file = "kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e"}, + {file = "kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c"}, + {file = "kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede"}, + {file = "kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2"}, + {file = "kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb"}, + {file = "kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc"}, + {file = "kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08"}, + {file = "kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4"}, + {file = "kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b"}, + {file = "kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588"}, + {file = "kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083"}, + {file = "kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314"}, + {file = "kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9"}, + {file = "kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384"}, + {file = "kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3"}, + {file = "kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8"}, + {file = "kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167"}, + {file = "kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0"}, + {file = "kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276"}, + {file = "kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2"}, + {file = "kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02"}, + {file = "kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a"}, + {file = "kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79"}, + {file = "kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796"}, + {file = "kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e"}, + {file = "kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681"}, + {file = "kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7"}, + {file = "kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed"}, + {file = "kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16"}, + {file = "kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1"}, + {file = "kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a"}, +] + +[[package]] +name = "legacy-cgi" +version = "2.6.4" +description = "Fork of the standard library cgi and cgitb modules removed in Python 3.13" +optional = false +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.4-py3-none-any.whl", hash = "sha256:7e235ce58bf1e25d1fc9b2d299015e4e2cd37305eccafec1e6bac3fc04b878cd"}, + {file = "legacy_cgi-2.6.4.tar.gz", hash = "sha256:abb9dfc7835772f7c9317977c63253fd22a7484b5c9bbcdca60a29dcce97c577"}, +] + +[[package]] +name = "librt" +version = "0.11.0" +description = "Mypyc runtime library" +optional = false +python-versions = ">=3.9" +groups = ["types"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6e94ebfcfa2d5e9926d6c3b9aa4617ffc42a845b4321fb84021b872358c82a0f"}, + {file = "librt-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ae627397a2f351560440d872d6f7c8dbb4072e57868e7b2fc5b8b430fe489d45"}, + {file = "librt-0.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc329359321b67d24efdf4bc69012b0597001649544db662c001db5a0184794c"}, + {file = "librt-0.11.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:7e82e642ab0f7608ce2fe53d76ca2280a9ee33a1b06556142c7c6fe80a86fc33"}, + {file = "librt-0.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88145c15c67731d54283d135b03244028c750cc9edc334a96a4f5950ebdb2884"}, + {file = "librt-0.11.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d36a51b3d93320b686588e27123f4995804dbf1bce81df78c02fc3c6eea9280"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3ac06a2a8b246327f11e186a53a100a4d5c7ed52346367e5ec751d51586c"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:461bbceede621f1ffb8839755f8663e886087ee7af16294cab7fb4d782c62eeb"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0cad8a4d6a8ff03c9b76f9414caccd78e7cfbc8a2e12fa334d8e1d9932753783"}, + {file = "librt-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f37aa505b3cf60701562eddb32df74b12a9e380c207fd8b06dd157a943ac7ea0"}, + {file = "librt-0.11.0-cp310-cp310-win32.whl", hash = "sha256:94663a21534637f0e787ec2a2a756022df6e5b7b2335a5cdd7d8e33d68a2af89"}, + {file = "librt-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dec7db73758c2b54953fd8b7fe348c45188fe26b39ee18446196edd08453a5d4"}, + {file = "librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29"}, + {file = "librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9"}, + {file = "librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5"}, + {file = "librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b"}, + {file = "librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89"}, + {file = "librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d"}, + {file = "librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412"}, + {file = "librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d"}, + {file = "librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73"}, + {file = "librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c"}, + {file = "librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46"}, + {file = "librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3"}, + {file = "librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67"}, + {file = "librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a"}, + {file = "librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a"}, + {file = "librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d"}, + {file = "librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8"}, + {file = "librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a"}, + {file = "librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9"}, + {file = "librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c"}, + {file = "librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894"}, + {file = "librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c"}, + {file = "librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea"}, + {file = "librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230"}, + {file = "librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2"}, + {file = "librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be"}, + {file = "librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e"}, + {file = "librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e"}, + {file = "librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47"}, + {file = "librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44"}, + {file = "librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd"}, + {file = "librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4"}, + {file = "librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8"}, + {file = "librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b"}, + {file = "librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175"}, + {file = "librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96"}, + {file = "librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe"}, + {file = "librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f"}, + {file = "librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7"}, + {file = "librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1"}, + {file = "librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72"}, + {file = "librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f"}, + {file = "librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3"}, + {file = "librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd"}, + {file = "librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8"}, + {file = "librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c"}, + {file = "librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253"}, + {file = "librt-0.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6bd72d903911d995ab666dbd1871f8b1e80925a699af8063fbf50053329fb05f"}, + {file = "librt-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ef69ac715f3cd8e5cd252cb2aebfa72c015492aacc339d5d7bf8fef3c62c677"}, + {file = "librt-0.11.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:624a40c4a4ad7773315c287276cd024509b2c66ff5904f504bfc08d2c70293ab"}, + {file = "librt-0.11.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:41dc19fe150b69716c8ece4f76773a9e8813fe3e35e032a58b4d46423fb8d7c0"}, + {file = "librt-0.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e8bd98ea9c47ae90b319a087ab28dac493f1ffbc1ecd1f28fcdbf3b7e1108d1"}, + {file = "librt-0.11.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84308fc49423ce6475d1c5d1985cd69a8ca9f0325fc7d5f81bb690a3f3625d4e"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ff0fbaf5f44a21beeb0110f2ab64f45135a9536a834b79c0d1ef018f2786bbfa"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9c028a9442a18e266955d364ce42259136e79a7ba14d773e0d778d5f70cd56f1"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:9f1692105a02bcf853f355032a5fdc5494358ef83d8fd22d16de375c85cec3f5"}, + {file = "librt-0.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a80a71e1fda83cc752a9141e87aae7fef279538597564d670e9ce513f286192"}, + {file = "librt-0.11.0-cp39-cp39-win32.whl", hash = "sha256:140695816ddf3c86eb972981a26f35efd871c44b0c3aed44c8cd01749386617f"}, + {file = "librt-0.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f7ff819c197fc30473190a12c2856f325ac90aabfccbeb2072d28cc2e234e3"}, + {file = "librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1"}, ] [[package]] name = "mako" -version = "1.3.8" +version = "1.3.12" 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.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9"}, + {file = "mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a"}, ] [package.dependencies] @@ -878,95 +1457,205 @@ testing = ["pytest"] [[package]] name = "markdown" -version = "3.7" +version = "3.10.2" description = "Python implementation of John Gruber's Markdown." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"}, + {file = "markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950"}, ] [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] (>=0.28.3)"] testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "3.0.2" +version = "3.0.3" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -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"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +groups = ["main", "docs"] +files = [ + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, +] + +[[package]] +name = "matplotlib" +version = "3.10.9" +description = "Python plotting package" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217"}, + {file = "matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b"}, + {file = "matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37"}, + {file = "matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294"}, + {file = "matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65"}, + {file = "matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda"}, + {file = "matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb"}, + {file = "matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb"}, + {file = "matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb"}, + {file = "matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9"}, + {file = "matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb"}, + {file = "matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f"}, + {file = "matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80"}, + {file = "matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1"}, + {file = "matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320"}, + {file = "matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285"}, + {file = "matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2"}, + {file = "matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf"}, + {file = "matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6"}, + {file = "matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42"}, + {file = "matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f"}, + {file = "matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e"}, + {file = "matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f"}, + {file = "matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838"}, + {file = "matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2"}, + {file = "matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921"}, + {file = "matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8"}, + {file = "matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9"}, + {file = "matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4"}, + {file = "matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc"}, + {file = "matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99"}, + {file = "matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d"}, + {file = "matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8"}, + {file = "matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38"}, + {file = "matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d"}, + {file = "matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f"}, + {file = "matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b"}, + {file = "matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2"}, + {file = "matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716"}, + {file = "matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f"}, + {file = "matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456"}, + {file = "matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe"}, + {file = "matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6"}, + {file = "matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c"}, + {file = "matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4"}, + {file = "matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf"}, + {file = "matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39"}, + {file = "matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c"}, + {file = "matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b"}, + {file = "matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f"}, + {file = "matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585"}, + {file = "matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20"}, + {file = "matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba"}, + {file = "matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4"}, + {file = "matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358"}, ] +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.3.1" +numpy = ">=1.23" +packaging = ">=20.0" +pillow = ">=8" +pyparsing = ">=3" +python-dateutil = ">=2.7" + +[package.extras] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7,<10)"] + [[package]] name = "mccabe" 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,116 +1663,216 @@ files = [ [[package]] name = "mypy" -version = "1.14.1" +version = "1.20.2" 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.10" +groups = ["types"] +files = [ + {file = "mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4"}, + {file = "mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997"}, + {file = "mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14"}, + {file = "mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99"}, + {file = "mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c"}, + {file = "mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd"}, + {file = "mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2"}, + {file = "mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c"}, + {file = "mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3"}, + {file = "mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254"}, + {file = "mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98"}, + {file = "mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac"}, + {file = "mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67"}, + {file = "mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100"}, + {file = "mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b"}, + {file = "mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4"}, + {file = "mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6"}, + {file = "mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066"}, + {file = "mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102"}, + {file = "mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9"}, + {file = "mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58"}, + {file = "mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026"}, + {file = "mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943"}, + {file = "mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517"}, + {file = "mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15"}, + {file = "mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee"}, + {file = "mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f"}, + {file = "mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330"}, + {file = "mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30"}, + {file = "mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924"}, + {file = "mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb"}, + {file = "mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc"}, + {file = "mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558"}, + {file = "mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8"}, + {file = "mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3"}, + {file = "mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609"}, + {file = "mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2"}, + {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c"}, + {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744"}, + {file = "mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6"}, + {file = "mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec"}, + {file = "mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382"}, + {file = "mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563"}, + {file = "mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665"}, ] [package.dependencies] +librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing_extensions = ">=4.6.0" +pathspec = ">=1.0.0" +typing_extensions = [ + {version = ">=4.6.0", markers = "python_version < \"3.15\""}, + {version = ">=4.14.0", markers = "python_version >= \"3.15\""}, +] [package.extras] dmypy = ["psutil (>=4.0)"] faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] +native-parser = ["ast-serialize (>=0.1.1,<1.0.0)"] 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 = ["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.3.5" 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.3.5-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:23a312224875f72cd16bde417f49071451877e29ef646a60e50fcb69407cc18a"}, + {file = "nh3-0.3.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387abd011e81959d5a35151a11350a0795c6edeb53ebfa02d2e882dc01299263"}, + {file = "nh3-0.3.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48f45e3e914be93a596431aa143dedf1582557bf41a58153c296048d6e3798c9"}, + {file = "nh3-0.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0a09f51806fd51b4fedbf9ea2b61fef388f19aef0d62fe51199d41648be14588"}, + {file = "nh3-0.3.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c357f1d042c67f135a5e6babb2b0e3b9d9224ff4a3543240f597767b01384ffd"}, + {file = "nh3-0.3.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:38748140bf76383ab7ce2dce0ad4cb663855d8fbc9098f7f3483673d09616a17"}, + {file = "nh3-0.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:84bdeb082544fbcb77a12c034dd77d7da0556fdc0727b787eb6214b958c15e29"}, + {file = "nh3-0.3.5-cp314-cp314t-win32.whl", hash = "sha256:c3aae321f67ae66cff2a627115f106a377d4475d10b0e13d97959a13486b9a88"}, + {file = "nh3-0.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c88605d8d468f7fc1b31e06129bc91d6c96f6c621776c9b504a0da9beac9df5f"}, + {file = "nh3-0.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:72c5bdedec27fa33de6a5326346ea8aa3fe54f6ac294d54c4b204fb66a9f1e79"}, + {file = "nh3-0.3.5-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3bb854485c9b33e5bb143ff3e49e577073bc6bc320f0ff8fc316dd89c0d3c101"}, + {file = "nh3-0.3.5-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d401ab2d8e86d59e2126e3ab2a2f45840c405842b626d9a51624b3a33b6878"}, + {file = "nh3-0.3.5-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acfd354e61accbe4c74f8017c6e397a776916dfe47c48643cf7fd84ade826f93"}, + {file = "nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52d877980d7ca01dc3baf3936bf844828bc6f332962227a684ed79c18cce14c3"}, + {file = "nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:207c01801d3e9bb8ec08f08689346bdd30ce15b8bf60013a925d08b5388962a4"}, + {file = "nh3-0.3.5-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea232933394d1d58bf7c4bb348dc4660eae6604e1ae81cd2ba6d9ed80d390f3b"}, + {file = "nh3-0.3.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe3a787dc76b50de6bee54ef242f26c41dfe47654428e3e94f0fae5bb6dd2cc1"}, + {file = "nh3-0.3.5-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:488928988caad25ba14b1eb5bc74e25e21f3b5e40341d956f3ce4a8bc19460dc"}, + {file = "nh3-0.3.5-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c069570b06aa848457713ad7af4a9905691291548c4466a9ad78ee95808382b"}, + {file = "nh3-0.3.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eeedc90ed8c42c327e8e10e621ccfa314fc6cce35d5929f4297ff1cdb89667c4"}, + {file = "nh3-0.3.5-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:de8e8621853b6470fe928c684ee0d3f39ea8086cebafe4c416486488dea7b68d"}, + {file = "nh3-0.3.5-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:6ea58cc44d274c643b83547ca9654a0b1a817609b160601356f76a2b744c49ad"}, + {file = "nh3-0.3.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e49c9b564e6bcb03ecd2f057213df9a0de15a95812ac9db9600b590db23d3ae9"}, + {file = "nh3-0.3.5-cp38-abi3-win32.whl", hash = "sha256:559e4c73b689e9a7aa97ac9760b1bc488038d7c1a575aa4ab5a0e19ee9630c0f"}, + {file = "nh3-0.3.5-cp38-abi3-win_amd64.whl", hash = "sha256:45e6a65dc88a300a2e3502cb9c8e6d1d6b831d6fba7470643333609c6aab1f30"}, + {file = "nh3-0.3.5-cp38-abi3-win_arm64.whl", hash = "sha256:8f85285700a18e9f3fc5bff41fe573fa84f81542ef13b48a89f9fecca0474d3b"}, + {file = "nh3-0.3.5.tar.gz", hash = "sha256:45855e14ff056064fec77133bfcf7cd691838168e5e17bbef075394954dc9dc8"}, +] + +[[package]] +name = "numpy" +version = "2.4.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.11" +groups = ["main"] +files = [ + {file = "numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db"}, + {file = "numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0"}, + {file = "numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015"}, + {file = "numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40"}, + {file = "numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d"}, + {file = "numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502"}, + {file = "numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd"}, + {file = "numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5"}, + {file = "numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e"}, + {file = "numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e"}, + {file = "numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e"}, + {file = "numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b"}, + {file = "numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e"}, + {file = "numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842"}, + {file = "numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8"}, + {file = "numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121"}, + {file = "numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e"}, + {file = "numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44"}, + {file = "numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d"}, + {file = "numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827"}, + {file = "numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a"}, + {file = "numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec"}, + {file = "numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50"}, + {file = "numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115"}, + {file = "numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af"}, + {file = "numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c"}, + {file = "numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103"}, + {file = "numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83"}, + {file = "numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed"}, + {file = "numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959"}, + {file = "numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed"}, + {file = "numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf"}, + {file = "numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d"}, + {file = "numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5"}, + {file = "numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7"}, + {file = "numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93"}, + {file = "numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e"}, + {file = "numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40"}, + {file = "numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e"}, + {file = "numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392"}, + {file = "numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008"}, + {file = "numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8"}, + {file = "numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a"}, + {file = "numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b"}, + {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a"}, + {file = "numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d"}, + {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252"}, + {file = "numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f"}, + {file = "numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc"}, + {file = "numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74"}, + {file = "numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb"}, + {file = "numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e"}, + {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113"}, + {file = "numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d"}, + {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d"}, + {file = "numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f"}, + {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0"}, + {file = "numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150"}, + {file = "numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871"}, + {file = "numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e"}, + {file = "numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f"}, + {file = "numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119"}, + {file = "numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0"}, ] [[package]] name = "packaging" -version = "24.2" +version = "26.2" 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-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"}, + {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"}, ] [[package]] @@ -1092,6 +1881,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"}, @@ -1104,21 +1894,137 @@ testing = ["Paste", "pytest", "pytest-cov"] [[package]] name = "pathspec" -version = "0.12.1" +version = "1.1.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["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"}, + {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"}, + {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"}, +] + +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] + +[[package]] +name = "pillow" +version = "12.2.0" +description = "Python Imaging Library (fork)" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f"}, + {file = "pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136"}, + {file = "pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c"}, + {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3"}, + {file = "pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa"}, + {file = "pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032"}, + {file = "pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5"}, + {file = "pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024"}, + {file = "pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab"}, + {file = "pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705"}, + {file = "pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176"}, + {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b"}, + {file = "pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909"}, + {file = "pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808"}, + {file = "pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60"}, + {file = "pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe"}, + {file = "pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5"}, + {file = "pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005"}, + {file = "pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780"}, + {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5"}, + {file = "pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5"}, + {file = "pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940"}, + {file = "pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5"}, + {file = "pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414"}, + {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c"}, + {file = "pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2"}, + {file = "pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c"}, + {file = "pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795"}, + {file = "pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed"}, + {file = "pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3"}, + {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9"}, + {file = "pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795"}, + {file = "pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e"}, + {file = "pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b"}, + {file = "pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06"}, + {file = "pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b"}, + {file = "pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea"}, + {file = "pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4"}, + {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4"}, + {file = "pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea"}, + {file = "pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24"}, + {file = "pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98"}, + {file = "pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453"}, + {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8"}, + {file = "pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b"}, + {file = "pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295"}, + {file = "pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed"}, + {file = "pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f"}, + {file = "pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286"}, + {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50"}, + {file = "pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104"}, + {file = "pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7"}, + {file = "pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150"}, + {file = "pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1"}, + {file = "pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463"}, + {file = "pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd"}, + {file = "pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e"}, + {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06"}, + {file = "pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43"}, + {file = "pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354"}, + {file = "pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1"}, + {file = "pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1"}, + {file = "pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e"}, + {file = "pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5"}, ] +[package.extras] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +test-arrow = ["arro3-compute", "arro3-core", "nanoarrow", "pyarrow"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma (>=5)", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] +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 +2040,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,224 +2055,250 @@ testing = ["pytest", "pytest-cov"] [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.9.6" 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.10" +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.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"}, + {file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"}, ] -[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)"] - [[package]] name = "playwright" -version = "1.49.1" +version = "1.59.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.59.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:bfc6940100b57423175c819ce2422ec5880d55fa2769987f62ab7a1f5fe6783e"}, + {file = "playwright-1.59.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:af068143a0c045ec11608b67d6c42e58db7e9cf65a742dd21fddedc1a9802c47"}, + {file = "playwright-1.59.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:4a4a2d4842b0e4120de3fa48636e4b69085a05b81d8a35ad4353f530ade72ed6"}, + {file = "playwright-1.59.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c5792aad9e22b91a09264b9edbc18553cf05ea5a39404d65dc19a012c6b2e51d"}, + {file = "playwright-1.59.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c881a19377d2b900af855fb525b5f22a27bf3cfbecba6d1edb36766d56cb100"}, + {file = "playwright-1.59.0-py3-none-win32.whl", hash = "sha256:6989c476be2b9cd3e24a18cc9dcf202e266fb3d91e3e5395cd668c54ea54b119"}, + {file = "playwright-1.59.0-py3-none-win_amd64.whl", hash = "sha256:d5a5cc064b82ca92996080025710844e417f44df8fda9001102c28f44174171c"}, + {file = "playwright-1.59.0-py3-none-win_arm64.whl", hash = "sha256:93581ad515728cadc8af39b288a5633ba6d36e7d72048e79d890ce01ea2156f9"}, ] [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" -version = "2.22" +version = "3.0" description = "C parser in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main", "types"] +markers = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"" files = [ - {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, - {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, + {file = "pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992"}, + {file = "pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29"}, ] [[package]] name = "pydantic" -version = "2.10.6" +version = "2.13.4" 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.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"}, + {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.27.2" -typing-extensions = ">=4.12.2" +pydantic-core = "2.46.4" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [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.46.4" 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.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01"}, + {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f"}, + {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d"}, + {file = "pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"}, + {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"}, + {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"}, + {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"}, + {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"}, + {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"}, + {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"}, + {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"}, + {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"}, + {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"}, + {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"}, + {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"}, + {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c"}, + {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e"}, + {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac"}, + {file = "pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5"}, + {file = "pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"}, + {file = "pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066"}, + {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29"}, + {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1"}, + {file = "pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b"}, + {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526"}, + {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"}, + {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"}, + {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"}, ] [package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +typing-extensions = ">=4.14.1" [[package]] name = "pyee" -version = "12.0.0" +version = "13.0.1" 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.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228"}, + {file = "pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8"}, ] [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" -version = "2.19.1" +version = "2.20.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +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"}, + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, ] [package.extras] @@ -1373,27 +2306,26 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pylint" -version = "3.3.4" +version = "4.0.5" description = "python code static checker" optional = false -python-versions = ">=3.9.0" +python-versions = ">=3.10.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-4.0.5-py3-none-any.whl", hash = "sha256:00f51c9b14a3b3ae08cff6b2cdd43f28165c78b165b628692e428fb1f8dc2cf2"}, + {file = "pylint-4.0.5.tar.gz", hash = "sha256:8cd6a618df75deb013bd7eb98327a95f02a6fb839205a6bbf5456ef96afb317c"}, ] [package.dependencies] -astroid = ">=3.3.8,<=3.4.0-dev0" +astroid = ">=4.0.2,<=4.1.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 = ">=5,<5.13 || >5.13,<9" 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] @@ -1401,20 +2333,34 @@ spelling = ["pyenchant (>=3.2,<4.0)"] testutils = ["gitpython (>3)"] [[package]] -name = "pyramid" -version = "2.0.2" -description = "The Pyramid Web Framework, a Pylons project" +name = "pyparsing" +version = "3.3.2" +description = "pyparsing - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +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"}, + {file = "pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d"}, + {file = "pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc"}, ] +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyramid" +version = "2.1.dev0" +description = "The Pyramid Web Framework, a Pylons project" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [] +develop = false + [package.dependencies] hupper = ">=1.5" plaster = "*" -plaster-pastedeploy = "*" +plaster_pastedeploy = "*" setuptools = "*" translationstring = ">=0.4" venusian = ">=1.0" @@ -1423,15 +2369,22 @@ webob = ">=1.8.3" "zope.interface" = ">=3.8.0" [package.extras] -docs = ["Sphinx (>=3.0.0)", "docutils", "pylons-sphinx-latesturl", "pylons-sphinx-themes (>=1.0.8)", "repoze.sphinx.autointerface", "sphinx-copybutton", "sphinxcontrib-autoprogram"] +docs = ["Sphinx (>=3.0.0)", "docutils", "pylons-sphinx-themes (>=1.0.8)", "pylons_sphinx_latesturl", "repoze.sphinx.autointerface", "sphinx-copybutton", "sphinxcontrib-autoprogram"] testing = ["coverage", "pytest (>=5.4.2)", "pytest-cov", "webtest (>=1.3.1)", "zope.component (>=4.0)"] +[package.source] +type = "git" +url = "https://github.com/Kingdread/pyramid.git" +reference = "7f5a499ccc63a10302fcc8021dcfae90ee97866f" +resolved_reference = "7f5a499ccc63a10302fcc8021dcfae90ee97866f" + [[package]] name = "pyramid-debugtoolbar" 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 +2405,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 +2427,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 +2447,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 +2467,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 +2483,25 @@ testing = ["WebTest", "coverage (>=5.0)", "pytest", "pytest-cov"] [[package]] name = "pytest" -version = "8.3.4" +version = "9.0.3" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, + {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, ] [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.0.1" +packaging = ">=22" 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 +2509,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,45 +2524,64 @@ test = ["black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "pytest [[package]] name = "pytest-cov" -version = "6.0.0" +version = "7.1.0" 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-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, + {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, ] [package.dependencies] -coverage = {version = ">=7.5", extras = ["toml"]} -pytest = ">=4.6" +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +testing = ["process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-playwright" -version = "0.6.2" +version = "0.7.2" description = "A pytest wrapper with fixtures for Playwright to automate web browsers" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +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.2-py3-none-any.whl", hash = "sha256:8084e015b2b3ecff483c2160f1c8219b38b66c0d4578b23c0f700d1b0240ea38"}, + {file = "pytest_playwright-0.7.2.tar.gz", hash = "sha256:247b61123b28c7e8febb993a187a07e54f14a9aa04edc166f7a976d88f04c770"}, ] [package.dependencies] playwright = ">=1.18" -pytest = ">=6.2.4,<9.0.0" +pytest = ">=6.2.4,<10.0.0" pytest-base-url = ">=1.0.0,<3.0.0" python-slugify = ">=6.0.0,<9.0.0" [[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] name = "python-slugify" 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"}, @@ -1617,107 +2594,198 @@ text-unidecode = ">=1.3" unidecode = ["Unidecode (>=1.1.1)"] [[package]] +name = "pytokens" +version = "0.4.1" +description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." +optional = false +python-versions = ">=3.8" +groups = ["linters"] +files = [ + {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"}, + {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"}, + {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"}, + {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"}, + {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"}, + {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"}, + {file = "pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16"}, + {file = "pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6"}, + {file = "pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1"}, + {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1"}, + {file = "pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9"}, + {file = "pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68"}, + {file = "pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f"}, + {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1"}, + {file = "pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4"}, + {file = "pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78"}, + {file = "pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa"}, + {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d"}, + {file = "pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324"}, + {file = "pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9"}, + {file = "pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3"}, + {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975"}, + {file = "pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a"}, + {file = "pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918"}, + {file = "pytokens-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009"}, + {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1"}, + {file = "pytokens-0.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6"}, + {file = "pytokens-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037"}, + {file = "pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1"}, + {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db"}, + {file = "pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1"}, + {file = "pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a"}, + {file = "pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de"}, + {file = "pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a"}, +] + +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] + +[[package]] name = "redis" -version = "5.2.1" +version = "7.4.0" description = "Python client for Redis database and key-value store" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec"}, + {file = "redis-7.4.0.tar.gz", hash = "sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad"}, ] [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)"] +circuit-breaker = ["pybreaker (>=1.4.0)"] +hiredis = ["hiredis (>=3.2.0)"] +jwt = ["pyjwt (>=2.9.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (>=20.0.1)", "requests (>=2.31.0)"] +otel = ["opentelemetry-api (>=1.39.1)", "opentelemetry-exporter-otlp-proto-http (>=1.39.1)", "opentelemetry-sdk (>=1.39.1)"] +xxhash = ["xxhash (>=3.6.0,<3.7.0)"] [[package]] name = "requests" -version = "2.32.3" +version = "2.34.1" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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.34.1-py3-none-any.whl", hash = "sha256:bf38a3ff993960d3dd819c08862c40b3c703306eb7c744fcd9f4ddbb95b548f0"}, + {file = "requests-2.34.1.tar.gz", hash = "sha256:0fc5669f2b69704449fe1552360bd2a73a54512dfd03e65529157f1513322beb"}, ] [package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +certifi = ">=2023.5.7" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" +urllib3 = ">=1.26,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +description = "Manipulate well-formed Roman numerals" +optional = false +python-versions = ">=3.10" +groups = ["docs"] +files = [ + {file = "roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7"}, + {file = "roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2"}, +] [[package]] name = "setuptools" -version = "75.8.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" +version = "82.0.1" +description = "Most extensible Python build backend with support for C/C++ extension modules" 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-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb"}, + {file = "setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9"}, ] [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.13.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)", "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.18.*)", "pytest-mypy"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] [[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.8.3" description = "A modern CSS selector implementation for Beautiful Soup." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +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.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"}, + {file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"}, ] [[package]] name = "sphinx" -version = "8.1.3" +version = "9.0.4" description = "Python documentation generator" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" +groups = ["docs"] +markers = "python_version == \"3.11\"" 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-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb"}, + {file = "sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3"}, ] [package.dependencies] alabaster = ">=0.7.14" babel = ">=2.13" colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} -docutils = ">=0.20,<0.22" +docutils = ">=0.20,<0.23" imagesize = ">=1.3" Jinja2 = ">=3.1" packaging = ">=23.0" Pygments = ">=2.17" requests = ">=2.30.0" +roman-numerals = ">=1.0.0" snowballstemmer = ">=2.2" sphinxcontrib-applehelp = ">=1.0.7" sphinxcontrib-devhelp = ">=1.0.6" @@ -1725,30 +2793,74 @@ 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)"] +[[package]] +name = "sphinx" +version = "9.1.0" +description = "Python documentation generator" +optional = false +python-versions = ">=3.12" +groups = ["docs"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978"}, + {file = "sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb"}, +] + +[package.dependencies] +alabaster = ">=0.7.14" +babel = ">=2.13" +colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} +docutils = ">=0.21,<0.23" +imagesize = ">=1.3" +Jinja2 = ">=3.1" +packaging = ">=23.0" +Pygments = ">=2.17" +requests = ">=2.30.0" +roman-numerals = ">=1.0.0" +snowballstemmer = ">=2.2" +sphinxcontrib-applehelp = ">=1.0.7" +sphinxcontrib-devhelp = ">=1.0.6" +sphinxcontrib-htmlhelp = ">=2.0.6" +sphinxcontrib-jsmath = ">=1.0.1" +sphinxcontrib-qthelp = ">=1.0.6" +sphinxcontrib-serializinghtml = ">=1.1.9" [[package]] name = "sphinx-autodoc-typehints" -version = "3.0.1" +version = "3.6.1" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" +groups = ["docs"] +markers = "python_version == \"3.11\"" 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.6.1-py3-none-any.whl", hash = "sha256:dd818ba31d4c97f219a8c0fcacef280424f84a3589cedcb73003ad99c7da41ca"}, + {file = "sphinx_autodoc_typehints-3.6.1.tar.gz", hash = "sha256:fa0b686ae1b85965116c88260e5e4b82faec3687c2e94d6a10f9b36c3743e2fe"}, ] [package.dependencies] -sphinx = ">=8.1.3" +sphinx = ">=9.0.4" [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)"] +docs = ["furo (>=2025.9.25)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.13)", "defusedxml (>=0.7.1)", "diff-cover (>=9.7.2)", "pytest (>=9.0.2)", "pytest-cov (>=7)", "sphobjinv (>=2.3.1.3)", "typing-extensions (>=4.15)"] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "3.10.2" +description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +optional = false +python-versions = ">=3.12" +groups = ["docs"] +markers = "python_version >= \"3.12\"" +files = [ + {file = "sphinx_autodoc_typehints-3.10.2-py3-none-any.whl", hash = "sha256:2d1bcac8f62b8d0d210f6ca44b8e016e314d07cc8c30d0512cf6986a0b042b26"}, + {file = "sphinx_autodoc_typehints-3.10.2.tar.gz", hash = "sha256:34db651eb14343ba16bd9c03e0f5093971f9bbd3cc6b0a1470adecd578940b9c"}, +] + +[package.dependencies] +sphinx = ">=9.1" [[package]] name = "sphinxcontrib-applehelp" @@ -1756,6 +2868,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 +2885,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 +2902,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 +2919,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 +2934,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 +2951,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 +2964,87 @@ test = ["pytest"] [[package]] name = "sqlalchemy" -version = "2.0.37" +version = "2.0.49" 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.49-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:42e8804962f9e6f4be2cbaedc0c3718f08f60a16910fa3d86da5a1e3b1bfe60f"}, + {file = "sqlalchemy-2.0.49-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc992c6ed024c8c3c592c5fc9846a03dd68a425674900c70122c77ea16c5fb0b"}, + {file = "sqlalchemy-2.0.49-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eb188b84269f357669b62cb576b5b918de10fb7c728a005fa0ebb0b758adce1"}, + {file = "sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62557958002b69699bdb7f5137c6714ca1133f045f97b3903964f47db97ea339"}, + {file = "sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da9b91bca419dc9b9267ffadde24eae9b1a6bffcd09d0a207e5e3af99a03ce0d"}, + {file = "sqlalchemy-2.0.49-cp310-cp310-win32.whl", hash = "sha256:5e61abbec255be7b122aa461021daa7c3f310f3e743411a67079f9b3cc91ece3"}, + {file = "sqlalchemy-2.0.49-cp310-cp310-win_amd64.whl", hash = "sha256:0c98c59075b890df8abfcc6ad632879540f5791c68baebacb4f833713b510e75"}, + {file = "sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe"}, + {file = "sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014"}, + {file = "sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536"}, + {file = "sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88"}, + {file = "sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700"}, + {file = "sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a"}, + {file = "sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af"}, + {file = "sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b"}, + {file = "sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982"}, + {file = "sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672"}, + {file = "sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e"}, + {file = "sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750"}, + {file = "sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0"}, + {file = "sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4"}, + {file = "sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120"}, + {file = "sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2"}, + {file = "sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3"}, + {file = "sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7"}, + {file = "sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33"}, + {file = "sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b"}, + {file = "sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148"}, + {file = "sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518"}, + {file = "sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d"}, + {file = "sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0"}, + {file = "sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08"}, + {file = "sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d"}, + {file = "sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba"}, + {file = "sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e"}, + {file = "sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a"}, + {file = "sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066"}, + {file = "sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187"}, + {file = "sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401"}, + {file = "sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5"}, + {file = "sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5"}, + {file = "sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977"}, + {file = "sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01"}, + {file = "sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61"}, + {file = "sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a"}, + {file = "sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158"}, + {file = "sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7"}, + {file = "sqlalchemy-2.0.49-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8a97ac839c2c6672c4865e48f3cbad7152cee85f4233fb4ca6291d775b9b954a"}, + {file = "sqlalchemy-2.0.49-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c338ec6ec01c0bc8e735c58b9f5d51e75bacb6ff23296658826d7cfdfdb8678a"}, + {file = "sqlalchemy-2.0.49-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:566df36fd0e901625523a5a1835032f1ebdd7f7886c54584143fa6c668b4df3b"}, + {file = "sqlalchemy-2.0.49-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d99945830a6f3e9638d89a28ed130b1eb24c91255e4f24366fbe699b983f29e4"}, + {file = "sqlalchemy-2.0.49-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:01146546d84185f12721a1d2ce0c6673451a7894d1460b592d378ca4871a0c72"}, + {file = "sqlalchemy-2.0.49-cp38-cp38-win32.whl", hash = "sha256:69469ce8ce7a8df4d37620e3163b71238719e1e2e5048d114a1b6ce0fbf8c662"}, + {file = "sqlalchemy-2.0.49-cp38-cp38-win_amd64.whl", hash = "sha256:b95b2f470c1b2683febd2e7eab1d3f0e078c91dbdd0b00e9c645d07a413bb99f"}, + {file = "sqlalchemy-2.0.49-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43d044780732d9e0381ac8d5316f95d7f02ef04d6e4ef6dc82379f09795d993f"}, + {file = "sqlalchemy-2.0.49-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d6be30b2a75362325176c036d7fb8d19e8846c77e87683ffaa8177b35135613"}, + {file = "sqlalchemy-2.0.49-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d898cc2c76c135ef65517f4ddd7a3512fb41f23087b0650efb3418b8389a3cd1"}, + {file = "sqlalchemy-2.0.49-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:059d7151fff513c53a4638da8778be7fce81a0c4854c7348ebd0c4078ddf28fe"}, + {file = "sqlalchemy-2.0.49-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:334edbcff10514ad1d66e3a70b339c0a29886394892490119dbb669627b17717"}, + {file = "sqlalchemy-2.0.49-cp39-cp39-win32.whl", hash = "sha256:74ab4ee7794d7ed1b0c37e7333640e0f0a626fc7b398c07a7aef52f484fddde3"}, + {file = "sqlalchemy-2.0.49-cp39-cp39-win_amd64.whl", hash = "sha256:88690f4e1f0fbf5339bedbb127e240fec1fd3070e9934c0b7bef83432f779d2f"}, + {file = "sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0"}, + {file = "sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f"}, ] [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\")"} -mypy = {version = ">=0.910", optional = true, markers = "extra == \"mypy\""} +greenlet = {version = ">=1", markers = "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\""} 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 +3055,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 +3066,14 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "termcolor" -version = "2.5.0" +version = "3.3.0" description = "ANSI color formatting for output in terminal" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +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.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5"}, + {file = "termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5"}, ] [package.extras] @@ -1960,72 +3085,34 @@ 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.15.0" description = "Style preserving TOML library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +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.15.0-py3-none-any.whl", hash = "sha256:4dbc8f0fc024412b57ced8757ac7461305126a648ff8c2c807fcb8e133a78738"}, + {file = "tomlkit-0.15.0.tar.gz", hash = "sha256:7d1a9ecba3086638211b13814ea79c90dd54dd11993564376f3aa92271f5c7a3"}, ] [[package]] name = "transaction" -version = "5.0" +version = "5.1" description = "Transaction management for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "transaction-5.0-py3-none-any.whl", hash = "sha256:b4c0b2d49a042d86235fa76531c3356b66d7635bb0e9f29ba2512915fc7b7a42"}, - {file = "transaction-5.0.tar.gz", hash = "sha256:106e7bd782bcc0cb5119fc9225b0c9a71dfc53adb938be905223adaef22b1174"}, + {file = "transaction-5.1-py3-none-any.whl", hash = "sha256:05aa77c4543cec8b541565ad8b304857490878605ed727851bc5a531b9060789"}, + {file = "transaction-5.1.tar.gz", hash = "sha256:f6ca8639a3e54f6991de0d9c0983a7cd91acb21a825cb544c168269be956bd41"}, ] [package.dependencies] @@ -2041,6 +3128,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 +3143,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 +3155,14 @@ types-setuptools = "*" [[package]] name = "types-cffi" -version = "1.16.0.20241221" +version = "2.0.0.20260508" description = "Typing stubs for cffi" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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-2.0.0.20260508-py3-none-any.whl", hash = "sha256:d094065daf4edcfbdd3e11c37d2fa9511eaf7c509da7a9d9573c276398a8e745"}, + {file = "types_cffi-2.0.0.20260508.tar.gz", hash = "sha256:746b081b4bf84f9d8855c517a67c2dff717f3c18657fcff8e9c251fb5778f311"}, ] [package.dependencies] @@ -2080,13 +3170,14 @@ types-setuptools = "*" [[package]] name = "types-markdown" -version = "3.7.0.20241204" +version = "3.10.2.20260508" description = "Typing stubs for Markdown" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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.10.2.20260508-py3-none-any.whl", hash = "sha256:2ee5e462c7b821c27c3d8f75f4da873d3e3c87d24d7f2d741f8f4261e4024a99"}, + {file = "types_markdown-3.10.2.20260508.tar.gz", hash = "sha256:64c405a30decd46cf78443bbcde8331f561f83752cc6d962ff3f5ccaabe26a8e"}, ] [[package]] @@ -2095,6 +3186,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 +3198,14 @@ types-cffi = "*" [[package]] name = "types-pytz" -version = "2024.2.0.20241221" +version = "2026.2.0.20260506" description = "Typing stubs for pytz" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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-2026.2.0.20260506-py3-none-any.whl", hash = "sha256:58ab5307c20885f9bcd42ff106616eb0e32710791f8cbdc770aee2ea0c4f01fb"}, + {file = "types_pytz-2026.2.0.20260506.tar.gz", hash = "sha256:fc6a0de6a1b7da82a748fb4065e152372dac3016559cb1eef5e8af1e338eb627"}, ] [[package]] @@ -2121,6 +3214,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 +3226,14 @@ types-pyOpenSSL = "*" [[package]] name = "types-requests" -version = "2.32.0.20241016" +version = "2.33.0.20260513" description = "Typing stubs for requests" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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.33.0.20260513-py3-none-any.whl", hash = "sha256:d5a965f9d18b6e06b72039a69565de9027e58f36a7f709857da747fbe7521122"}, + {file = "types_requests-2.33.0.20260513.tar.gz", hash = "sha256:bd845450e954e751373d5d33526742592f298808a3ee3bda7e858e46b839b57f"}, ] [package.dependencies] @@ -2146,42 +3241,88 @@ urllib3 = ">=2" [[package]] name = "types-setuptools" -version = "75.8.0.20250110" +version = "82.0.0.20260508" description = "Typing stubs for setuptools" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" +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-82.0.0.20260508-py3-none-any.whl", hash = "sha256:ba1d863bbd11526d7232bca8d5a4aebe1d38fa1677a550f47a2692b7d5776900"}, + {file = "types_setuptools-82.0.0.20260508.tar.gz", hash = "sha256:e76ade6f42ba9b4211636b84b65a8e55948a67ffe81f9a44e66b8af93d57e77e"}, ] [[package]] name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.15.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.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[[package]] +name = "typst" +version = "0.14.9" +description = "Python binding to Typst, a new markup-based typesetting system that is powerful and easy to learn." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "typst-0.14.9-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:abc1348393390173ba7b993389b06b3df4b635cf69205aedaa05ce51de9be3b6"}, + {file = "typst-0.14.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e3d62e29ce38c45e94e81716383c153272438964b76c343b9a2183fe52a9ff02"}, + {file = "typst-0.14.9-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f054414502fd21b84474bdd1c92263be1910e010a5a6bdb0d546d7a00131f91"}, + {file = "typst-0.14.9-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2676e5b6548ae83b13ff3b769f5f30cf5d716b712502ea64a8e98fc3ae4577f"}, + {file = "typst-0.14.9-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:456ecf0e9bf02748fb3d7b6f0176aff36027ee8c2e81056eca20b3d4ba54e82b"}, + {file = "typst-0.14.9-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a67963bd4e3b6969eee412a2bf49753276cd95be0acc86c07fd0c06f188a41bc"}, + {file = "typst-0.14.9-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabac1449c75381828996eaf352bfd795857668834a4c9cc6308d7687352ed49"}, + {file = "typst-0.14.9-cp314-cp314t-win_amd64.whl", hash = "sha256:b5637861645b0abb2529e1006a8984a831ecd9d24610a60e99d3ae0af4e4691a"}, + {file = "typst-0.14.9-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:4c4404891b3c8bfbb01d9e9308a24b9637088fb41aea0f24b58a9dffdd43b164"}, + {file = "typst-0.14.9-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:9ade0c99e853bf02bcc28f1bad8be3714de5ee5323e1a0f649abdb4e634240e9"}, + {file = "typst-0.14.9-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339a1d8beb05f098090a8a6c627e1aa7d4c7792b333e59694d30072f6f0ccdb"}, + {file = "typst-0.14.9-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:792818bb147ea4a88b0555fd2066e1bb0b65353aecd01270a8acd3be018f3e39"}, + {file = "typst-0.14.9-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4ecc5102125880baebbe454946a8492f6377680bf3d74f60650fae093958e3b"}, + {file = "typst-0.14.9-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7be2e851ee3ff3561c79d244a2809089fb7cfc678912555ecabb031d5de74da9"}, + {file = "typst-0.14.9-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be26d1f195b75d4c57e7411646124f8257d136417cc5e9c393dcd8ea3c3a11e8"}, + {file = "typst-0.14.9-cp38-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:2f7008b41217194a41b9eec611cef358cc7c8d90193817b182f9abed62526984"}, + {file = "typst-0.14.9-cp38-abi3-win_amd64.whl", hash = "sha256:6ebe6b433f71e9ec03c444b26f123363ab63ebab9a289fc4f26d6962b18fad4e"}, + {file = "typst-0.14.9.tar.gz", hash = "sha256:53a7ee0825c127c100023685eeb8fb911226d7ad9ae861a370c11bb0f5c160cc"}, ] [[package]] name = "urllib3" -version = "2.3.0" +version = "2.7.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +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.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"}, + {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "venusian" @@ -2189,6 +3330,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 +3346,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 +3362,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 +3377,14 @@ testing = ["coverage", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist"] [[package]] name = "webtest" -version = "3.0.3" +version = "3.0.7" 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.7-py3-none-any.whl", hash = "sha256:2f51a0844f3a8beaef89bc23d225fe05ad816f7e429ffcc655a13013a799ac6c"}, + {file = "webtest-3.0.7.tar.gz", hash = "sha256:7aeab50f970d46c068e7a36dd162cb242591edf72a1d04efd21374772b931741"}, ] [package.dependencies] @@ -2253,13 +3398,14 @@ tests = ["PasteDeploy", "WSGIProxy2", "coverage", "pyquery", "pytest", "pytest-c [[package]] name = "zope-deprecation" -version = "5.1" +version = "6.0" 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"}, + {file = "zope_deprecation-6.0-py3-none-any.whl", hash = "sha256:ff72d51c88b516b9ddf2cfb826381cc49f99a6a89b7d35c97faca7bee3b46da6"}, + {file = "zope_deprecation-6.0.tar.gz", hash = "sha256:18727ebda8e63a6d4bd28a290e8b46852e9f14473debb5cc40a0a2dccfadf15f"}, ] [package.dependencies] @@ -2267,57 +3413,53 @@ setuptools = "*" [package.extras] docs = ["Sphinx"] -test = ["zope.testrunner"] +test = ["zope.testrunner (>=6.4)"] [[package]] name = "zope-interface" -version = "7.2" +version = "8.4" description = "Interfaces for Python" optional = false -python-versions = ">=3.8" -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"}, - {file = "zope.interface-7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550f1c6588ecc368c9ce13c44a49b8d6b6f3ca7588873c679bd8fd88a1b557b6"}, - {file = "zope.interface-7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ef9e2f865721553c6f22a9ff97da0f0216c074bd02b25cf0d3af60ea4d6931d"}, - {file = "zope.interface-7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27f926f0dcb058211a3bb3e0e501c69759613b17a553788b2caeb991bed3b61d"}, - {file = "zope.interface-7.2-cp310-cp310-win_amd64.whl", hash = "sha256:144964649eba4c5e4410bb0ee290d338e78f179cdbfd15813de1a664e7649b3b"}, - {file = "zope.interface-7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1909f52a00c8c3dcab6c4fad5d13de2285a4b3c7be063b239b8dc15ddfb73bd2"}, - {file = "zope.interface-7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ecf2451596f19fd607bb09953f426588fc1e79e93f5968ecf3367550396b22"}, - {file = "zope.interface-7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:033b3923b63474800b04cba480b70f6e6243a62208071fc148354f3f89cc01b7"}, - {file = "zope.interface-7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a102424e28c6b47c67923a1f337ede4a4c2bba3965b01cf707978a801fc7442c"}, - {file = "zope.interface-7.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25e6a61dcb184453bb00eafa733169ab6d903e46f5c2ace4ad275386f9ab327a"}, - {file = "zope.interface-7.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f6771d1647b1fc543d37640b45c06b34832a943c80d1db214a37c31161a93f1"}, - {file = "zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7"}, - {file = "zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465"}, - {file = "zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89"}, - {file = "zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54"}, - {file = "zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d"}, - {file = "zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5"}, - {file = "zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98"}, - {file = "zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d"}, - {file = "zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c"}, - {file = "zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398"}, - {file = "zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b"}, - {file = "zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd"}, - {file = "zope.interface-7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d3a8ffec2a50d8ec470143ea3d15c0c52d73df882eef92de7537e8ce13475e8a"}, - {file = "zope.interface-7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:31d06db13a30303c08d61d5fb32154be51dfcbdb8438d2374ae27b4e069aac40"}, - {file = "zope.interface-7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e204937f67b28d2dca73ca936d3039a144a081fc47a07598d44854ea2a106239"}, - {file = "zope.interface-7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:224b7b0314f919e751f2bca17d15aad00ddbb1eadf1cb0190fa8175edb7ede62"}, - {file = "zope.interface-7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf95683cde5bc7d0e12d8e7588a3eb754d7c4fa714548adcd96bdf90169f021"}, - {file = "zope.interface-7.2-cp38-cp38-win_amd64.whl", hash = "sha256:7dc5016e0133c1a1ec212fc87a4f7e7e562054549a99c73c8896fa3a9e80cbc7"}, - {file = "zope.interface-7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bd449c306ba006c65799ea7912adbbfed071089461a19091a228998b82b1fdb"}, - {file = "zope.interface-7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a19a6cc9c6ce4b1e7e3d319a473cf0ee989cbbe2b39201d7c19e214d2dfb80c7"}, - {file = "zope.interface-7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cd1790b48c16db85d51fbbd12d20949d7339ad84fd971427cf00d990c1f137"}, - {file = "zope.interface-7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e446f9955195440e787596dccd1411f543743c359eeb26e9b2c02b077b0519"}, - {file = "zope.interface-7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ad9913fd858274db8dd867012ebe544ef18d218f6f7d1e3c3e6d98000f14b75"}, - {file = "zope.interface-7.2-cp39-cp39-win_amd64.whl", hash = "sha256:1090c60116b3da3bfdd0c03406e2f14a1ff53e5771aebe33fec1edc0a350175d"}, - {file = "zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe"}, +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "zope_interface-8.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:415de524326ddd61a78f0816f65942fa1aa35dced19e72579ad30dd106ce523e"}, + {file = "zope_interface-8.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa0a26d5767087170b3da9ff503221d535ea266bf61b522d0afa2590fd05db0a"}, + {file = "zope_interface-8.4-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:8544081e32b515bbaf1c6339eef06b23ed470cf4876ff2f575803f82a744cc43"}, + {file = "zope_interface-8.4-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:892b4b5350e58d6914858f58eb85d39fe9b992640ac6ece695f46c978046554d"}, + {file = "zope_interface-8.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d683267a6243526869cb69677dcfc663416d5f87904c1576ddec6e420687d51"}, + {file = "zope_interface-8.4-cp310-cp310-win_amd64.whl", hash = "sha256:f00fd65343d2a241a2b17688a12f5e815aa704ed64f9ca375de5f9e0ae9c9bda"}, + {file = "zope_interface-8.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b733af6e89a2b0b8edf5ff7a37988fe4e1788806e84e72127b88c47858f0da6"}, + {file = "zope_interface-8.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:265bad2df2ec070f23ff863249a89b408b11908fd4207662781fd18e3c6fc912"}, + {file = "zope_interface-8.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:e195e76767847afb5379ffd67690c17d3c6efdab58dc0e477cf81ac94d5a5a15"}, + {file = "zope_interface-8.4-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5ec1a56b6cf9a757cbbce9da38284a01473b92b96c1517eabd99150f51f1bb69"}, + {file = "zope_interface-8.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:04c2c9b58e9c177628715d85e94834efa807c1f9f0a2f57ae0f7b553e8266ac4"}, + {file = "zope_interface-8.4-cp311-cp311-win_amd64.whl", hash = "sha256:376d0ef005a131b349e2088e302aa094fa23c826d2ec8a7db4b00fb33c71e0d9"}, + {file = "zope_interface-8.4-cp311-cp311-win_arm64.whl", hash = "sha256:caffd033b27e311b45e15f01923cc9e73c6bfd8e843b4532e29b59ee432bf893"}, + {file = "zope_interface-8.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84064876ed96ddd0744e3ad5d37134c758d77885e54113567792671405a02bac"}, + {file = "zope_interface-8.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:81ed23698bfb588c48b1756129814b890febac971ff6c8a414f82601773145bb"}, + {file = "zope_interface-8.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:e0b9d7e958657fad414f8272afcdf0b8a873fbbb2bb6a6287232d2f11a232bf8"}, + {file = "zope_interface-8.4-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eef0a49e041f4dc4d2a6ab894b4fd0c5354e0e8037e731fb953531e59b0d3d33"}, + {file = "zope_interface-8.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b302f955c36e924e1f4fe70dd9105ff06235857861c6ae72c3b10b016aeee99"}, + {file = "zope_interface-8.4-cp312-cp312-win_amd64.whl", hash = "sha256:4ae6a1e111642dbf724f635424dcaf5a5c8abbde49eac3f452f5323ffaa10232"}, + {file = "zope_interface-8.4-cp312-cp312-win_arm64.whl", hash = "sha256:2e9e4aa33b76877af903d5532545e64d24ade0f6f80d9d1a31e6efcea76a60bc"}, + {file = "zope_interface-8.4-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:cd55965d715413038774aead54851bc3dbdd74a69f3ce30252182a94407b9905"}, + {file = "zope_interface-8.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0d88c1f106a4f06e074a3ada2d20f4a602e3f2871c4f55726ed5d91e94ec19b1"}, + {file = "zope_interface-8.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:36c575356732d59ffd3279ad67e302a6fe517e67db5b061b36b377ee0fa016c4"}, + {file = "zope_interface-8.4-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:29f09ec8bda65f7b30294328070070a2590b90f252f834ee0817cdb0e2c35f6a"}, + {file = "zope_interface-8.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2bc388cebcb753d21eaf2a0481fd6f0ce6840a47300a40dcec0b56bac27d0f97"}, + {file = "zope_interface-8.4-cp313-cp313-win_amd64.whl", hash = "sha256:3e5866917ccb57d929e515a1136d729bd3fa4f367965fb16e38a4bc72cb05521"}, + {file = "zope_interface-8.4-cp313-cp313-win_arm64.whl", hash = "sha256:f1f854bef8bc137519e4413bcc1322d55faad28b20b3ca39f7bec49d2f1b26df"}, + {file = "zope_interface-8.4-cp314-cp314-macosx_10_9_x86_64.whl", hash = "sha256:7cbb887fdbfaacb4c362dbb487033551646e28013ad5ffe72e96eb260003a1a1"}, + {file = "zope_interface-8.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a5638c6be715116d3453e6d099c299c6844d54810de7445ce116424e905ede06"}, + {file = "zope_interface-8.4-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b8147b40bfcd53803870a9519e0879ff066aeecc2fcff8295663c1b17fc38dc2"}, + {file = "zope_interface-8.4-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:049ba3c7b38cc400ae08e011617635706e0f442e1d075db1b015246fcbf6091e"}, + {file = "zope_interface-8.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9c4ac009c2c8e43283842f80387c4d4b41bcbc293391c3b9ab71532ae1ccc301"}, + {file = "zope_interface-8.4-cp314-cp314-win_amd64.whl", hash = "sha256:4713bf651ec36e7eea49d2ace4f0e89bec2b33a339674874b1121f2537edc62a"}, + {file = "zope_interface-8.4-cp314-cp314-win_arm64.whl", hash = "sha256:d934497c4b72d5f528d2b5ebe9b8b5a7004b5877948ebd4ea00c2432fb27178f"}, + {file = "zope_interface-8.4.tar.gz", hash = "sha256:9dbee7925a23aa6349738892c911019d4095a96cff487b743482073ecbc174a8"}, ] -[package.dependencies] -setuptools = "*" - [package.extras] docs = ["Sphinx", "furo", "repoze.sphinx.autointerface"] test = ["coverage[toml]", "zope.event", "zope.testing"] @@ -2325,19 +3467,19 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [[package]] name = "zope-sqlalchemy" -version = "3.1" +version = "4.1" description = "Minimal Zope/SQLAlchemy transaction integration" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" +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"}, + {file = "zope_sqlalchemy-4.1-py3-none-any.whl", hash = "sha256:24c18895af4b962c0b214d84fbdf548df4ac504c9eb437b30d9bff25af0ae072"}, + {file = "zope_sqlalchemy-4.1.tar.gz", hash = "sha256:1912525fa0b2b6ff0c3cab46aab59fed45302d4de4e4b03e0d11386e770f4531"}, ] [package.dependencies] packaging = "*" -setuptools = "*" -SQLAlchemy = ">=1.1,<1.4.0 || >1.4.0,<1.4.1 || >1.4.1,<1.4.2 || >1.4.2,<1.4.3 || >1.4.3,<1.4.4 || >1.4.4,<1.4.5 || >1.4.5,<1.4.6 || >1.4.6" +SQLAlchemy = ">=1.2,<1.4.0 || >1.4.0,<1.4.1 || >1.4.1,<1.4.2 || >1.4.2,<1.4.3 || >1.4.3,<1.4.4 || >1.4.4,<1.4.5 || >1.4.5,<1.4.6 || >1.4.6,<2.0.32 || >2.0.32,<2.0.33 || >2.0.33,<2.0.34 || >2.0.34,<2.0.35 || >2.0.35" transaction = ">=1.6.0" "zope.interface" = ">=3.6.0" @@ -2345,6 +3487,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, <4" +content-hash = "b4d90215cb83febb48f381f7eb9d8d2aa4fe159a34bfb93b96fbef54ea943881" diff --git a/pylint.tests.toml b/pylint.tests.toml index 7a68437..bfa2d9f 100644 --- a/pylint.tests.toml +++ b/pylint.tests.toml @@ -76,10 +76,6 @@ py-version = "3.10" # Discover python modules and packages in the file system subtree. # recursive = -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode = true - # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. # unsafe-load-any-extension = diff --git a/pylint.toml b/pylint.toml index d758ecf..27afd42 100644 --- a/pylint.toml +++ b/pylint.toml @@ -17,7 +17,7 @@ # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. (This is an alternative name to extension-pkg-allow-list # for backward compatibility.) -extension-pkg-whitelist = ["pydantic", "nh3"] +extension-pkg-whitelist = ["pydantic", "nh3", "hittekaart_py"] # Return non-zero exit code if any of these messages/categories are detected, # even if score is above --fail-under value. Syntax same as enable. Messages @@ -76,10 +76,6 @@ py-version = "3.10" # Discover python modules and packages in the file system subtree. # recursive = -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode = true - # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. # unsafe-load-any-extension = diff --git a/pyproject.toml b/pyproject.toml index 3338057..d3d22a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,15 +2,58 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" -[tool.poetry] +[project] name = "fietsboek" description = "GPX file sharing website" -version = "0.10.0" +version = "0.12.1" license = "AGPL-3.0-or-later" readme = "README.md" authors = [ - "Daniel Schadt <fietsboek@kingdread.de>", + { name = "Daniel Schadt", email = "fietsboek@kingdread.de>" }, +] +keywords = ["web", "gpx"] +requires-python = ">=3.11, <4" +dependencies = [ + "pyramid @ git+https://github.com/Kingdread/pyramid.git@7f5a499ccc63a10302fcc8021dcfae90ee97866f", + "pyramid_jinja2 (>=2.10, <3.0)", + "pyramid_debugtoolbar (>=4.9, <5.0)", + "pyramid_retry (>=2.1, <3.0)", + "pyramid_tm (>=2.5, <3.0)", + "waitress (>=3, <4)", + + "SQLAlchemy (>=2.0.15, <3.0.0)", + "alembic (>=1.8, <2.0)", + "transaction (>=5, <6)", + "zope.sqlalchemy (>=4.0, <5.0)", + "redis (>=7, <8)", + + "Babel (>=2.11, <3.0)", + "cryptography (>=46, <47)", + "gpxpy (>=1.5, <2.0)", + "markdown (>=3.4, <4.0)", + "nh3 (>=0.3.0,<0.4.0)", + "Click (>=8.1, <9.0)", + "requests (>=2.28.1, <3.0.0)", + + "pydantic (>=2, <3)", + "termcolor (>=3.1.0, <4.0.0)", + "filelock (>=3.8.2, <4.0.0)", + "brotli (>=1.0.9, <2.0.0)", + "click-option-group (>=0.5.5, <0.6.0)", + "fitparse (>=1.2.0, <2.0.0)", + "pillow (>=12.0.0, <13.0.0)", + "typst (>=0.14.1,<0.15.0)", + "matplotlib (>=3.10.7,<4.0.0)", + + "hittekaart-py @ git+https://gitlab.com/dunj3/hittekaart@9a576a8d3a8cb1d143be0ade0310f71b490cacb9#subdirectory=hittekaart-py", ] + +[project.urls] +documentation = "https://docs.fietsboek.org/" +homepage = "https://fietsboek.org/" +repository = "https://gitlab.com/dunj3/fietsboek" + +[tool.poetry] classifiers = [ 'Development Status :: 3 - Alpha', 'Framework :: Pyramid', @@ -19,63 +62,28 @@ classifiers = [ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Internet :: WWW/HTTP :: WSGI :: Application', ] -documentation = "https://docs.fietsboek.org/" -homepage = "https://fietsboek.org/" -repository = "https://gitlab.com/dunj3/fietsboek" -keywords = ["web", "gpx"] - -[tool.poetry.dependencies] -python = "^3.10" - -pyramid = "^2" -pyramid_jinja2 = "^2.10" -pyramid_debugtoolbar = "^4.9" -pyramid_retry = "^2.1" -pyramid_tm = "^2.5" -waitress = "^3" - -SQLAlchemy = { version = "^2.0.15", extras = ["mypy"] } -alembic = "^1.8" -transaction = "^5" -"zope.sqlalchemy" = "^3.0" -redis = "^5" - -Babel = "^2.11" -cryptography = "^44" -gpxpy = "^1.5" -markdown = "^3.4" -nh3 = "^0.2.9" -Click = "^8.1" -requests = "^2.28.1" - -pydantic = "^2" -termcolor = "^2.1.1" -filelock = "^3.8.2" -brotli = "^1.0.9" -click-option-group = "^0.5.5" -fitparse = "^1.2.0" [tool.poetry.group.docs] optional = true [tool.poetry.group.docs.dependencies] -Sphinx = "^8.0" +Sphinx = "^9.0" sphinx-autodoc-typehints = "^3" [tool.poetry.group.testing] optional = true [tool.poetry.group.testing.dependencies] -pytest = "^8.0.0" +pytest = "^9.0.0" webtest = "^3.0.0" -pytest-cov = "^6.0.0" -pytest-playwright = "^0.6.0" +pytest-cov = "^7.0.0" +pytest-playwright = "^0.7.0" [tool.poetry.group.linters] optional = true [tool.poetry.group.linters.dependencies] -pylint = "^3" +pylint = "^4" black = "^25.0.0" [tool.poetry.group.types] @@ -88,12 +96,12 @@ types-requests = "^2.28.11.5" types-babel = "^2.11.0.7" types-redis = "^4.3.21.6" -[tool.poetry.scripts] +[project.scripts] fietsctl = "fietsboek.scripts.fietsctl:cli" fietscron = "fietsboek.scripts.fietscron:cli" fietsupdate = "fietsboek.updater.cli:cli" -[tool.poetry.plugins."paste.app_factory"] +[project.entry-points."paste.app_factory"] main = "fietsboek:main" [tool.black] 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..add3b3f 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) @@ -59,29 +60,37 @@ def dbengine(app_settings, ini_file): def data_manager(app_settings): return DataManager(Path(app_settings["fietsboek.data_dir"])) + +def clean_directory_content(path: Path): + if path.is_dir(): + shutil.rmtree(path) + path.mkdir() + + @pytest.fixture(autouse=True) def _cleanup_data(app_settings): yield engine = models.get_engine(app_settings) - db_meta = inspect(engine) + # Load all table names beforehand, as has_table() would cause lock conflicts + tables = inspect(engine).get_table_names() with engine.begin() as connection: for table in reversed(Base.metadata.sorted_tables): # The unit tests don't always set up the tables, so be gentle when # tearing them down - if db_meta.has_table(table.name): + if table.name in tables: connection.execute(table.delete()) # The unit tests also often don't have a data directory, so be gentle here as well if "fietsboek.data_dir" in app_settings: data_dir = Path(app_settings["fietsboek.data_dir"]) - if (data_dir / "tracks").is_dir(): - shutil.rmtree(data_dir / "tracks") - if (data_dir / "users").is_dir(): - shutil.rmtree(data_dir / "users") + clean_directory_content(data_dir / "tracks") + clean_directory_content(data_dir / "users") + clean_directory_content(data_dir / "journeys") @pytest.fixture(scope='module') def app(app_settings, dbengine, tmp_path_factory): app_settings["fietsboek.data_dir"] = str(tmp_path_factory.mktemp("data")) logging.getLogger().setLevel(logging.DEBUG) + logging.getLogger("matplotlib").setLevel(logging.INFO) return main({}, dbengine=dbengine, **app_settings) @pytest.fixture @@ -189,7 +198,7 @@ def logged_in(testapp, route_path, dbsession, tm): tm.abort() with tm: - user = models.User(email='foo@barre.com', is_verified=True) + user = models.User(name="Feu Barre", email='foo@barre.com', is_verified=True) user.set_password("foobar") dbsession.add(user) dbsession.flush() diff --git a/tests/integration/test_browse.py b/tests/integration/test_browse.py index 875821d..1b96e2e 100644 --- a/tests/integration/test_browse.py +++ b/tests/integration/test_browse.py @@ -3,8 +3,10 @@ import zipfile from contextlib import contextmanager from datetime import datetime +from sqlalchemy import delete, inspect + from testutils import load_gpx_asset -from fietsboek import models +from fietsboek import convert, models from fietsboek.models.track import Visibility @@ -21,7 +23,10 @@ def added_tracks(tm, dbsession, owner, data_manager): # objects to the database. tm.abort() + path = convert.smart_convert(load_gpx_asset("Teasi_1.gpx.gz")).path() + tracks = [] + track_ids = [] with tm: track = models.Track( owner=owner, @@ -35,8 +40,10 @@ def added_tracks(tm, dbsession, owner, data_manager): track.date = datetime(2022, 3, 14, 9, 26, 54) dbsession.add(track) dbsession.flush() - data_manager.initialize(track.id).compress_gpx(load_gpx_asset("MyTourbook_1.gpx.gz")) + track.fast_set_path(path) + data_manager.initialize(track.id) tracks.append(track) + track_ids.append(track.id) track = models.Track( owner=owner, @@ -50,14 +57,18 @@ def added_tracks(tm, dbsession, owner, data_manager): track.date = datetime(2022, 10, 29, 13, 37, 11) dbsession.add(track) dbsession.flush() - data_manager.initialize(track.id).compress_gpx(load_gpx_asset("Teasi_1.gpx.gz")) + track.fast_set_path(path) + track.ensure_cache(path) + dbsession.add(track.cache) + data_manager.initialize(track.id) tracks.append(track) + track_ids.append(track.id) tm.begin() tm.doom() try: - yield tracks + yield track_ids finally: tm.abort() with tm: @@ -67,26 +78,113 @@ def added_tracks(tm, dbsession, owner, data_manager): 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") + skel = convert.smart_convert(gpx_data) + path = skel.path() + + 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() + track.fast_set_path(path) + track.ensure_cache(path) + dbsession.add(track.cache) + tracks.append(track) + track_ids.append(track.id) + data_manager.initialize(track.id) + + tm.begin() + tm.doom() + + try: + yield track_ids + finally: + tm.abort() + table = inspect(models.track.TrackPoint).tables[0] + with tm: + for track_id in track_ids: + dbsession.execute(delete(table).where(table.c.track_id == track_id)) + dbsession.execute( + delete(models.TrackCache).where(models.TrackCache.track_id == track_id) + ) + dbsession.execute(delete(models.Track).where(models.Track.id == track_id)) + tm.begin() + tm.doom() + + def test_browse(testapp, dbsession, route_path, logged_in, tm, data_manager): # pylint: disable=too-many-positional-arguments # Ensure there are some tracks in the database with added_tracks(tm, dbsession, logged_in, data_manager): # Now go to the browse page - browse = testapp.get(route_path('browse')) + browse = testapp.get(route_path("browse")) assert "Foobar" in browse.text 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: + 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/integration/test_pdf.py b/tests/integration/test_pdf.py new file mode 100644 index 0000000..29cda02 --- /dev/null +++ b/tests/integration/test_pdf.py @@ -0,0 +1,59 @@ +from contextlib import contextmanager +from datetime import datetime + +from testutils import load_gpx_asset +from fietsboek import convert, models +from fietsboek.models.track import Visibility + + +@contextmanager +def a_track(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() + + with tm: + track = models.Track( + owner=owner, + title="Goober", + visibility=Visibility.PUBLIC, + description="A bar'd track", + badges=[], + link_secret="raboof", + tagged_people=[], + ) + track.date = datetime(2027, 3, 14, 9, 26, 54) + track.set_path(convert.smart_convert(load_gpx_asset("MyTourbook_1.gpx.gz")).path()) + dbsession.add(track) + dbsession.flush() + data_manager.initialize(track.id) + track_id = track.id + + tm.begin() + tm.doom() + + try: + yield track_id + finally: + tm.abort() + with tm: + dbsession.delete(track) + data_manager.purge(track_id) + tm.begin() + tm.doom() + + +def test_pdf(testapp, dbsession, route_path, logged_in, tm, data_manager): + # pylint: disable=too-many-positional-arguments + # Ensure there are some tracks in the database + with a_track(tm, dbsession, logged_in, data_manager) as track_id: + pdf = testapp.get(route_path("track-pdf", track_id=track_id)) + + assert pdf diff --git a/tests/playwright/conftest.py b/tests/playwright/conftest.py index 4e7f5a4..435daa6 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: @@ -104,7 +110,10 @@ class Helper: ) def add_track( - self, user: Optional[models.User] = None, track_name: str = "Teasi_1.gpx.gz" + self, + user: Optional[models.User] = None, + track_name: str = "Teasi_1.gpx.gz", + title: str = "Another awesome track", ) -> models.Track: """Add a track to the given user. @@ -112,13 +121,16 @@ 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", + title=title, visibility=Visibility.PRIVATE, description="Another description", track_type=TrackType.ORGANIC, 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_journeys.py b/tests/playwright/test_journeys.py new file mode 100644 index 0000000..2bfd271 --- /dev/null +++ b/tests/playwright/test_journeys.py @@ -0,0 +1,170 @@ +from playwright.sync_api import Page, expect +from sqlalchemy import select + +from fietsboek import models + + +def add_journey(playwright_helper, dbaccess, title): + """Adds a journey for testing purposes. Returns the journey ID.""" + t_1 = playwright_helper.add_track(None, "Teasi_1.gpx.gz", "trayectoria uno") + t_2 = playwright_helper.add_track(None, "MyTourbook_1.gpx.gz", "trayectoria dos") + + with dbaccess: + journey = models.Journey( + owner=playwright_helper.john_doe(), + title=title, + description="You saw sirens?", + visibility=models.journey.Visibility.PUBLIC, + ) + + dbaccess.add(journey) + dbaccess.flush() + + journey.set_track_ids([t_1.id, t_2.id]) + dbaccess.commit() + dbaccess.refresh(journey, ["id"]) + dbaccess.expunge(journey) + + playwright_helper.data_manager.initialize_journey(journey.id) + + return journey.id + + +def test_journey_list(page: Page, playwright_helper, dbaccess): + playwright_helper.login() + + add_journey(playwright_helper, dbaccess, title="Our Journey") + + page.goto("/journey/") + expect(page.locator("h5", has_text="Our Journey")).to_be_visible() + expect(page.locator("li", has_text="trayectoria uno")).to_be_visible() + expect(page.locator("li", has_text="trayectoria dos")).to_be_visible() + + +def test_journey_new(page: Page, playwright_helper, dbaccess): + playwright_helper.login() + + playwright_helper.add_track(None, "Teasi_1.gpx.gz", "trayectoria uno") + playwright_helper.add_track(None, "MyTourbook_1.gpx.gz", "trayectoria dos") + playwright_helper.add_track(None, "MyTourbook_1.gpx.gz", "trayectoria tres") + + page.goto("/journey/") + page.get_by_text("New journey").click() + + page.get_by_label("Title").fill("My Odyssey") + page.get_by_label("Description").fill("I saw sirens!") + + page.locator("#trackSearch").fill("uno") + page.locator("#trackSearchButton").click() + page.locator(".track-query-response button").click() + + page.locator("#trackSearch").fill("dos") + page.locator("#trackSearchButton").click() + page.locator(".track-query-response button").click() + + page.locator("#trackSearch").fill("tres") + page.locator("#trackSearchButton").click() + page.locator(".track-query-response button").click() + page.locator(".journey-track", has_text="tres").locator(".btn").click() + + page.locator(".btn", has_text="Save").click() + + expect(page.locator("h1", has_text="My Odyssey")).to_be_visible() + + expect(page.locator("h5", has_text="trayectoria uno")).to_be_visible() + expect(page.locator("h5", has_text="trayectoria dos")).to_be_visible() + + journey = dbaccess.execute(select(models.Journey).filter_by(title="My Odyssey")).scalar_one() + + assert journey.title == "My Odyssey" + assert journey.description == "I saw sirens!" + assert len(journey.tracks) == 2 + assert journey.tracks[0].title == "trayectoria uno" + assert journey.tracks[1].title == "trayectoria dos" + + +def test_journey_new_empty_title(page: Page, playwright_helper): + playwright_helper.login() + + playwright_helper.add_track(None, "Teasi_1.gpx.gz", "trayectoria uno") + + page.goto("/journey/") + page.get_by_text("New journey").click() + + page.locator("#trackSearch").fill("uno") + page.locator("#trackSearchButton").click() + page.locator(".track-query-response button").click() + page.locator(".btn", has_text="Save").click() + + expect(page.locator(".invalid-feedback", has_text="A title is required")).to_be_visible() + + +def test_journey_new_no_tracks(page: Page, playwright_helper): + playwright_helper.login() + + page.goto("/journey/") + page.get_by_text("New journey").click() + + page.get_by_label("Title").fill("A title is there!") + + page.locator(".btn", has_text="Save").click() + + expect(page.locator(".invalid-feedback", has_text="A journey must have at least one track"))\ + .to_be_visible() + + +def test_journey_edit(page: Page, playwright_helper, dbaccess): + playwright_helper.login() + + journey_id = add_journey(playwright_helper, dbaccess, title="Your Odyssey") + + page.goto(f"/journey/{journey_id}/") + + expect(page.locator("h1", has_text="Your Odyssey")).to_be_visible() + + page.locator("a", has_text="Edit").click() + + page.get_by_label("Title").fill("Their Odyssey") + page.get_by_label("Description").fill("Where is Homer?") + + expect(page.locator(".track-title", has_text="trayectoria uno")).to_be_visible() + page.locator(".journey-track", has_text="uno").locator(".btn").click() + expect(page.locator(".track-title", has_text="trayectoria uno")).not_to_be_visible() + + page.locator(".btn", has_text="Save").click() + + expect(page.locator("h1", has_text="Their Odyssey")).to_be_visible() + expect(page.locator("h5", has_text="trayectoria uno")).not_to_be_visible() + expect(page.locator("h5", has_text="trayectoria dos")).to_be_visible() + + journey = dbaccess.execute(select(models.Journey).filter_by(title="Their Odyssey")).scalar_one() + + assert journey.title == "Their Odyssey" + assert journey.description == "Where is Homer?" + assert len(journey.tracks) == 1 + assert journey.tracks[0].title == "trayectoria dos" + + +def test_journey_reorder(page: Page, playwright_helper, dbaccess): + playwright_helper.login() + + journey_id = add_journey(playwright_helper, dbaccess, title="Her Journey") + + page.goto(f"/journey/{journey_id}/edit") + + expect(page.locator("h1", has_text="Her Journey")).to_be_visible() + + page.locator(".track-title", has_text="trayectoria uno").drag_to( + page.locator(".track-title", has_text="trayectoria dos"), + target_position={"x": 10, "y": 20}, + ) + + page.locator(".btn", has_text="Save").click() + + expect(page.locator("h1", has_text="Her Journey")).to_be_visible() + + journey = dbaccess.execute(select(models.Journey).filter_by(id=journey_id)).scalar_one() + + assert len(journey.tracks) == 2 + assert journey.tracks[0].title == "trayectoria dos" + assert journey.tracks[1].title == "trayectoria uno" 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/tests/playwright/test_transformers.py b/tests/playwright/test_transformers.py index fc89afb..d4e3456 100644 --- a/tests/playwright/test_transformers.py +++ b/tests/playwright/test_transformers.py @@ -26,7 +26,7 @@ def test_transformer_zero_elevation_disabled(page: Page, playwright_helper, tmp_ # Expect early (here and in the other tests) to ensure that the backend has # caught up with executing the transformer. Otherwise it might happen that # we read the database while the request is not finished yet. - expect(page.locator("#detailsUphill")).to_contain_text("167.7 m") + expect(page.locator("#detailsUphill")).to_contain_text("167.79 m") new_track_id = int(page.url.rsplit("/", 1)[1]) track = dbaccess.execute(select(models.Track).filter_by(id=new_track_id)).scalar_one() @@ -90,7 +90,7 @@ def test_transformer_steep_slope_disabled(page: Page, playwright_helper, tmp_pat page.locator(".btn", has_text="Upload").click() - expect(page.locator("#detailsUphill")).to_contain_text("61.54 m") + expect(page.locator("#detailsUphill")).to_contain_text("64.4 m") new_track_id = int(page.url.rsplit("/", 1)[1]) track = dbaccess.execute(select(models.Track).filter_by(id=new_track_id)).scalar_one() @@ -111,11 +111,11 @@ def test_transformer_steep_slope_enabled(page: Page, playwright_helper, tmp_path page.locator(".btn", has_text="Upload").click() - expect(page.locator("#detailsUphill")).to_contain_text("1.2 m") + expect(page.locator("#detailsUphill")).to_contain_text("2.4 m") new_track_id = int(page.url.rsplit("/", 1)[1]) track = dbaccess.execute(select(models.Track).filter_by(id=new_track_id)).scalar_one() - assert track.cache.uphill < 2 + assert track.cache.uphill < 3 def test_transformer_steep_slope_edited(page: Page, playwright_helper, tmp_path, dbaccess): @@ -137,14 +137,14 @@ def test_transformer_steep_slope_edited(page: Page, playwright_helper, tmp_path, page.locator(".btn", has_text="Save").click() - expect(page.locator("#detailsUphill")).to_contain_text("1.2 m") + expect(page.locator("#detailsUphill")).to_contain_text("2.4 m") track_id = int(page.url.rsplit("/", 1)[1]) track = dbaccess.execute(select(models.Track).filter_by(id=track_id)).scalar_one() - assert track.cache.uphill < 2 + assert track.cache.uphill < 3 -def test_transformer_elevation_jump_enabled(page: Page, playwright_helper, tmp_path, data_manager): +def test_transformer_elevation_jump_enabled(page: Page, playwright_helper, tmp_path, dbaccess): playwright_helper.login() page.goto("/") @@ -161,9 +161,10 @@ def test_transformer_elevation_jump_enabled(page: Page, playwright_helper, tmp_p page.locator(".alert", has_text="Upload successful").wait_for() new_track_id = int(page.url.rsplit("/", 1)[1]) - data = data_manager.open(new_track_id) - gpx = gpxpy.parse(data.decompress_gpx()) + gpx = gpxpy.parse( + dbaccess.execute(select(models.Track).filter_by(id=new_track_id)).scalar_one().gpx_xml() + ) points = iter(gpx.walk(only_points=True)) next(points) for prev_point, point in zip(gpx.walk(only_points=True), points): diff --git a/tests/unit/test_pdf.py b/tests/unit/test_pdf.py new file mode 100644 index 0000000..aafa717 --- /dev/null +++ b/tests/unit/test_pdf.py @@ -0,0 +1,58 @@ +import pytest + +from fietsboek import pdf + + +@pytest.mark.parametrize("value, expected", [ + ('', '""'), + ('a', '"\\u{61}"'), + ('FOO', '"\\u{46}\\u{4f}\\u{4f}"'), + ('äß', '"\\u{e4}\\u{df}"'), + ('"', '"\\u{22}"'), + ("'", '"\\u{27}"'), +]) +def test_typst_string(value, expected): + assert pdf.typst_string(value) == expected + + +@pytest.mark.parametrize("value, expected", [ + ("foo", "foo"), + ("*foo*", "\\*foo\\*"), + ("#strong[foo]", "\\#strong\\[foo\\]"), + ("= foo", "\\= foo"), + ("par 1\n\npar 2", "par 1\n\npar 2"), +]) +def test_typst_escape(value, expected): + assert pdf.typst_escape(value) == expected + + +@pytest.mark.parametrize("md_source, typst_source", [ + ("*foo*", "#emph[foo]\n\n"), + ("**foo**", "#strong[foo]\n\n"), + ("***foo***", "#strong[#emph[foo]]\n\n"), + ("[Teksd](https://link)", + '#link("\\u{68}\\u{74}\\u{74}\\u{70}\\u{73}\\u{3a}' + '\\u{2f}\\u{2f}\\u{6c}\\u{69}\\u{6e}\\u{6b}")[Teksd]\n\n'), + ("""\ +# Uperschrift + +Teksd""", """\ +#heading(level: 1)[Uperschrift] +Teksd\n\n"""), + ("""\ +* Eitem 1 +* Eitem 2""", """\ +#list( +[Eitem 1], +[Eitem 2], +)"""), + ("""\ +1. Eitem 1 +1. Eitem 2""", """\ +#enum( +[Eitem 1], +[Eitem 2], +)"""), +]) +def test_md_to_typst(md_source, typst_source): + assert pdf.md_to_typst(md_source) == typst_source diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py index 6dc8e7d..cc92058 100644 --- a/tests/unit/test_util.py +++ b/tests/unit/test_util.py @@ -71,19 +71,6 @@ def test_guess_gpx_timezone(gpx_file, offset): assert timezone.utcoffset(None) == offset -@pytest.mark.parametrize('gpx_file', [ - 'Teasi_1.gpx.gz', - 'MyTourbook_1.gpx.gz', - 'Synthetic_WT2.gpx.gz', - 'Synthetic_BRouter_1.gpx.gz', -]) -def test_tour_metadata(gpx_file): - # Here we simply make sure that we do not crash the metadata extraction - # function. - gpx_data = load_gpx_asset(gpx_file) - assert util.tour_metadata(gpx_data) is not None - - @pytest.mark.parametrize('mps, kph', [(1, 3.6), (10, 36)]) def test_mps_to_kph(mps, kph): assert util.mps_to_kph(mps) == pytest.approx(kph, 0.1) @@ -112,3 +99,15 @@ def test_tile_url(app_request): assert "{y}" in route_url assert "{z}" in route_url assert "bobby" in route_url + + +@pytest.mark.parametrize("value, expected", [ + ("", b""), + ("foo", b"foo"), + ("<foo>", b"<foo>"), + ("foo bar", b"foo bar"), + ("</gpx>", b"</gpx>"), + ("äÖß", b"äÖß"), +]) +def test_xml_escape(value, expected): + assert util.xml_escape(value) == expected @@ -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 @@ -51,7 +53,7 @@ changedir={toxinidir}{/}doc commands_pre = poetry install -v --with docs commands = - sphinx-apidoc -d 1 -f -M -e -o developer/module/ ../fietsboek "upd_*" + sphinx-apidoc -d 1 -M -e -o developer/module/ ../fietsboek "upd_*" make html mkdir -p _build/man rst2man man/fietsctl.rst _build/man/fietsctl.1 |
