From ab9f910828bb8672cad0559ffd75b23489eeea80 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Mon, 6 Nov 2023 13:28:04 +0530 Subject: [PATCH 1/6] Switch from typing_extensions to typing for Python 3.11 imports (#1922) * Switch from `typing_extensions` to `typing` for Python 3.11 imports * Remove unnecessary base class in Update model * Install `z-base-32` from the git repo for unreleased Python 3.12 support (fixes CI builds) * Switch to forked `wtforms-sqlalchemy` pending upstream release * Upgrade Python dependencies --- funnel/devtest.py | 3 +-- funnel/models/notification.py | 3 ++- funnel/models/update.py | 3 +-- funnel/utils/markdown/escape.py | 3 +-- funnel/views/jobs.py | 2 +- requirements/base.in | 3 ++- requirements/base.txt | 45 ++++++++++++++++----------------- requirements/dev.txt | 16 ++++++------ requirements/test.txt | 8 +++--- 9 files changed, 42 insertions(+), 44 deletions(-) diff --git a/funnel/devtest.py b/funnel/devtest.py index 21cdb251f..ae00c228e 100644 --- a/funnel/devtest.py +++ b/funnel/devtest.py @@ -13,8 +13,7 @@ import weakref from collections.abc import Callable, Iterable from secrets import token_urlsafe -from typing import Any, NamedTuple -from typing_extensions import Protocol +from typing import Any, NamedTuple, Protocol from flask import Flask diff --git a/funnel/models/notification.py b/funnel/models/notification.py index 0abcb5a6c..cee88a787 100644 --- a/funnel/models/notification.py +++ b/funnel/models/notification.py @@ -90,13 +90,14 @@ ClassVar, Generic, Optional, + Protocol, TypeVar, Union, cast, get_args, get_origin, ) -from typing_extensions import Protocol, get_original_bases +from typing_extensions import get_original_bases from uuid import UUID, uuid4 from sqlalchemy import event diff --git a/funnel/models/update.py b/funnel/models/update.py index 68a28157d..3f637aeb2 100644 --- a/funnel/models/update.py +++ b/funnel/models/update.py @@ -15,7 +15,6 @@ Mapped, Model, Query, - TimestampMixin, TSVectorType, UuidMixin, backref, @@ -47,7 +46,7 @@ class VISIBILITY_STATE(LabeledEnum): # noqa: N801 RESTRICTED = (2, 'restricted', __("Restricted")) -class Update(UuidMixin, BaseScopedIdNameMixin, TimestampMixin, Model): +class Update(UuidMixin, BaseScopedIdNameMixin, Model): __tablename__ = 'update' _visibility_state = sa.orm.mapped_column( diff --git a/funnel/utils/markdown/escape.py b/funnel/utils/markdown/escape.py index a83a68f84..208a8ed2b 100644 --- a/funnel/utils/markdown/escape.py +++ b/funnel/utils/markdown/escape.py @@ -6,8 +6,7 @@ import string from collections.abc import Callable, Iterable, Mapping from functools import wraps -from typing import Any, Concatenate, SupportsIndex, TypeVar -from typing_extensions import ParamSpec, Protocol, Self +from typing import Any, Concatenate, ParamSpec, Protocol, Self, SupportsIndex, TypeVar __all__ = ['HasMarkdown', 'MarkdownString', 'markdown_escape'] diff --git a/funnel/views/jobs.py b/funnel/views/jobs.py index dc2e2e64a..6282f1821 100644 --- a/funnel/views/jobs.py +++ b/funnel/views/jobs.py @@ -5,7 +5,7 @@ from collections import defaultdict from collections.abc import Callable from functools import wraps -from typing_extensions import Protocol +from typing import Protocol import requests from flask import g diff --git a/requirements/base.in b/requirements/base.in index 3919f51d1..33b3e1533 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -68,5 +68,6 @@ urllib3[socks] # Not required here, but the [socks] extra shows up in test.txt user-agents werkzeug whitenoise -z-base-32 +wtforms-sqlalchemy @ git+https://github.com/jace/wtforms-sqlalchemy # See /pull/1 +z-base-32 @ git+https://github.com/matusf/z-base-32 # See /issues/13 for Py 3.12 fix zxcvbn diff --git a/requirements/base.txt b/requirements/base.txt index 6047c603d..6832c9b8a 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -# SHA1:8d570f8f7fb4bd607d33ddb31c5b62c51b09ec48 +# SHA1:b825fb2bb7b273cf1c5f4d42e496b72531f87768 # # This file is autogenerated by pip-compile-multi # To update, run: @@ -28,7 +28,7 @@ alembic==1.12.1 aniso8601==9.0.1 # via coaster anyio==4.0.0 - # via httpcore + # via httpx argon2-cffi==23.1.0 # via -r requirements/base.in argon2-cffi-bindings==21.2.0 @@ -55,15 +55,15 @@ bleach==6.1.0 # via # baseframe # coaster -blinker==1.6.3 +blinker==1.7.0 # via # -r requirements/base.in # baseframe # coaster # flask -boto3==1.28.72 +boto3==1.28.78 # via -r requirements/base.in -botocore==1.31.72 +botocore==1.31.78 # via # boto3 # s3transfer @@ -83,7 +83,7 @@ cffi==1.16.0 # via # argon2-cffi-bindings # cryptography -charset-normalizer==3.3.1 +charset-normalizer==3.3.2 # via # aiohttp # requests @@ -114,7 +114,7 @@ dnspython==2.4.2 # pyisemail emoji==2.8.0 # via baseframe -filelock==3.12.4 +filelock==3.13.1 # via tldextract flask==3.0.0 # via @@ -148,7 +148,7 @@ flask-executor==1.0.0 # via -r requirements/base.in flask-flatpages==0.8.1 # via -r requirements/base.in -flask-mailman==0.3.0 +flask-mailman==1.0.0 # via -r requirements/base.in flask-migrate==4.0.5 # via @@ -200,19 +200,19 @@ html5lib==1.1 # via # baseframe # coaster -httpcore==0.18.0 +httpcore==1.0.1 # via httpx httplib2==0.22.0 # via # oauth2 # oauth2client -httpx[http2]==0.25.0 +httpx[http2]==0.25.1 # via # -r requirements/base.in # python-telegram-bot hyperframe==6.0.1 # via h2 -icalendar==5.0.10 +icalendar==5.0.11 # via -r requirements/base.in idna==3.4 # via @@ -248,7 +248,7 @@ lxml==4.9.3 # via premailer mako==1.2.4 # via alembic -markdown==3.5 +markdown==3.5.1 # via # coaster # flask-flatpages @@ -274,8 +274,6 @@ mdit-py-plugins==0.4.0 # via -r requirements/base.in mdurl==0.1.2 # via markdown-it-py -mkdocs-material-extensions==1.3 - # via flask-mailman multidict==6.0.4 # via # aiohttp @@ -302,7 +300,7 @@ packaging==23.2 # marshmallow passlib==1.7.4 # via -r requirements/base.in -phonenumbers==8.13.23 +phonenumbers==8.13.24 # via -r requirements/base.in premailer==3.10.0 # via -r requirements/base.in @@ -422,7 +420,7 @@ semantic-version==2.10.0 # via # baseframe # coaster -sentry-sdk==1.32.0 +sentry-sdk==1.34.0 # via baseframe six==1.16.0 # via @@ -439,9 +437,8 @@ six==1.16.0 sniffio==1.3.0 # via # anyio - # httpcore # httpx -sqlalchemy==2.0.22 +sqlalchemy==2.0.23 # via # -r requirements/base.in # alembic @@ -460,7 +457,7 @@ statsd==4.0.1 # via baseframe tinydb==4.8.0 # via tuspy -tldextract==5.0.1 +tldextract==5.1.0 # via # coaster # mxsniff @@ -519,16 +516,18 @@ werkzeug==3.0.1 # flask whitenoise==6.6.0 # via -r requirements/base.in -wtforms==3.1.0 +wtforms==3.1.1 # via # baseframe # flask-wtf # wtforms-sqlalchemy -wtforms-sqlalchemy==0.3 - # via baseframe +wtforms-sqlalchemy @ git+https://github.com/jace/wtforms-sqlalchemy + # via + # -r requirements/base.in + # baseframe yarl==1.9.2 # via aiohttp -z-base-32==0.1.2 +z-base-32 @ git+https://github.com/matusf/z-base-32 # via -r requirements/base.in zxcvbn==4.4.28 # via -r requirements/base.in diff --git a/requirements/dev.txt b/requirements/dev.txt index 9c3a6a5fd..8864e82bb 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -57,13 +57,13 @@ flake8-blind-except==0.2.1 # via -r requirements/dev.in flake8-bugbear==23.9.16 # via -r requirements/dev.in -flake8-builtins==2.1.0 +flake8-builtins==2.2.0 # via -r requirements/dev.in flake8-comprehensions==3.14.0 # via -r requirements/dev.in flake8-docstrings==1.7.0 # via -r requirements/dev.in -flake8-isort==6.1.0 +flake8-isort==6.1.1 # via -r requirements/dev.in flake8-logging-format==0.9.0 # via -r requirements/dev.in @@ -87,7 +87,7 @@ html-tag-names==0.1.2 # via djlint html-void-elements==0.1.0 # via djlint -identify==2.5.30 +identify==2.5.31 # via pre-commit isort==5.12.0 # via @@ -149,7 +149,7 @@ pyupgrade==3.15.0 # via -r requirements/dev.in reformat-gherkin==3.0.1 # via -r requirements/dev.in -ruff==0.1.3 +ruff==0.1.4 # via -r requirements/dev.in smmap==5.0.1 # via gitdb @@ -173,19 +173,19 @@ types-maxminddb==1.5.0 # via types-geoip2 types-mock==5.1.0.2 # via -r requirements/dev.in -types-pyopenssl==23.2.0.2 +types-pyopenssl==23.3.0.0 # via types-redis types-pytz==2023.3.1.1 # via -r requirements/dev.in -types-redis==4.6.0.8 +types-redis==4.6.0.9 # via -r requirements/dev.in types-requests==2.31.0.10 # via -r requirements/dev.in virtualenv==20.24.6 # via pre-commit -wcwidth==0.2.8 +wcwidth==0.2.9 # via reformat-gherkin -wheel==0.41.2 +wheel==0.41.3 # via pip-tools # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/test.txt b/requirements/test.txt index facf4d46e..04e794f1b 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -64,9 +64,9 @@ pytest-cov==4.1.0 # via -r requirements/test.in pytest-dotenv==0.5.2 # via -r requirements/test.in -pytest-env==1.1.0 +pytest-env==1.1.1 # via -r requirements/test.in -pytest-html==4.0.2 +pytest-html==4.1.0 # via pytest-selenium pytest-metadata==3.0.0 # via pytest-html @@ -94,9 +94,9 @@ sttable==0.0.1 # via -r requirements/test.in tenacity==8.2.3 # via pytest-selenium -tomlkit==0.12.1 +tomlkit==0.12.2 # via -r requirements/test.in -trio==0.22.2 +trio==0.23.1 # via # selenium # trio-websocket From 64cd34233e634b611a0d872a3c0226737054ca88 Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Mon, 6 Nov 2023 16:04:53 +0530 Subject: [PATCH 2/6] Remove unused dependencies and update typing imports for 3.11 (#1923) --- .pre-commit-config.yaml | 25 +- funnel/models/notification.py | 2 +- funnel/typing.py | 3 +- funnel/utils/markdown/base.py | 3 +- funnel/utils/markdown/mdit_plugins/toc.py | 2 +- funnel/utils/mustache.py | 3 +- funnel/views/notifications/mixins.py | 3 +- funnel/views/search.py | 3 +- funnel/views/video.py | 3 +- package-lock.json | 460 +++++++++++----------- pyproject.toml | 2 - requirements/base.in | 7 +- requirements/base.txt | 26 +- requirements/dev.in | 2 - requirements/dev.txt | 4 +- 15 files changed, 271 insertions(+), 277 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a0c91efbe..604ab19ee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,6 +8,7 @@ ci: skip: [ 'pip-audit', 'yesqa', + 'creosote', 'no-commit-to-branch', # 'hadolint-docker', 'docker-compose-check', @@ -52,9 +53,9 @@ repos: rev: v3.15.0 hooks: - id: pyupgrade - args: ['--keep-runtime-typing', '--py310-plus'] + args: ['--keep-runtime-typing', '--py311-plus'] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.3 + rev: v0.1.4 hooks: - id: ruff args: ['--fix', '--exit-non-zero-on-fix'] @@ -148,6 +149,26 @@ repos: args: ['-c', 'pyproject.toml'] additional_dependencies: - 'bandit[toml]' + - repo: https://github.com/fredrikaverpil/creosote + rev: v3.0.0 + hooks: + - id: creosote + args: + - --venv=.venv + - --path=funnel + - --path=tests + - --path=migrations/versions + - --deps-file=requirements/base.in + - --exclude-dep=argon2-cffi # Optional dep for passlib + - --exclude-dep=bcrypt # Optional dep for passlib + - --exclude-dep=gunicorn # Not imported, used as server + - --exclude-dep=linkify-it-py # Optional dep for markdown-it-py + - --exclude-dep=psycopg # Optional dep for SQLAlchemy + - --exclude-dep=rq-dashboard # Creosote fails to recognise the import + - --exclude-dep=tzdata # Data-only dep, therefore no import statement + - --exclude-dep=urllib3 # Required to silence a pip-audit warning + - --exclude-dep=wtforms-sqlalchemy # Temp dep on an unreleased git branch + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: diff --git a/funnel/models/notification.py b/funnel/models/notification.py index cee88a787..165b32007 100644 --- a/funnel/models/notification.py +++ b/funnel/models/notification.py @@ -97,12 +97,12 @@ get_args, get_origin, ) -from typing_extensions import get_original_bases from uuid import UUID, uuid4 from sqlalchemy import event from sqlalchemy.orm import column_keyed_dict from sqlalchemy.orm.exc import NoResultFound +from typing_extensions import get_original_bases from werkzeug.utils import cached_property from baseframe import __ diff --git a/funnel/typing.py b/funnel/typing.py index 65302c043..52446c431 100644 --- a/funnel/typing.py +++ b/funnel/typing.py @@ -2,8 +2,7 @@ from __future__ import annotations -from typing import Optional, TypeAlias, TypeVar, Union -from typing_extensions import ParamSpec +from typing import Optional, ParamSpec, TypeAlias, TypeVar, Union from flask.typing import ResponseReturnValue from werkzeug.wrappers import Response # Base class for Flask Response diff --git a/funnel/utils/markdown/base.py b/funnel/utils/markdown/base.py index 46f8bae0a..960a67557 100644 --- a/funnel/utils/markdown/base.py +++ b/funnel/utils/markdown/base.py @@ -4,8 +4,7 @@ from collections.abc import Callable, Iterable, Mapping from dataclasses import dataclass -from typing import Any, ClassVar, Literal, overload -from typing_extensions import Self +from typing import Any, ClassVar, Literal, Self, overload from markdown_it import MarkdownIt from markupsafe import Markup diff --git a/funnel/utils/markdown/mdit_plugins/toc.py b/funnel/utils/markdown/mdit_plugins/toc.py index 0fa35eff2..221736060 100644 --- a/funnel/utils/markdown/mdit_plugins/toc.py +++ b/funnel/utils/markdown/mdit_plugins/toc.py @@ -15,7 +15,7 @@ import re from collections.abc import MutableMapping, Sequence from functools import reduce -from typing_extensions import TypedDict +from typing import TypedDict from markdown_it import MarkdownIt from markdown_it.renderer import OptionsDict, RendererHTML diff --git a/funnel/utils/mustache.py b/funnel/utils/mustache.py index 5de588abb..7c551f10c 100644 --- a/funnel/utils/mustache.py +++ b/funnel/utils/mustache.py @@ -4,8 +4,7 @@ import types from collections.abc import Callable from copy import copy -from typing import TypeVar -from typing_extensions import ParamSpec +from typing import ParamSpec, TypeVar from chevron import render from markupsafe import Markup, escape as html_escape diff --git a/funnel/views/notifications/mixins.py b/funnel/views/notifications/mixins.py index bd99e1f76..b3e7270d3 100644 --- a/funnel/views/notifications/mixins.py +++ b/funnel/views/notifications/mixins.py @@ -1,8 +1,7 @@ """Notification helpers and mixins.""" from collections.abc import Callable -from typing import Generic, TypeVar, overload -from typing_extensions import Self +from typing import Generic, Self, TypeVar, overload import grapheme diff --git a/funnel/views/search.py b/funnel/views/search.py index cecdcd5b0..f518001aa 100644 --- a/funnel/views/search.py +++ b/funnel/views/search.py @@ -4,8 +4,7 @@ import re from html import unescape as html_unescape -from typing import Any, TypeVar -from typing_extensions import TypedDict +from typing import Any, TypedDict, TypeVar from urllib.parse import quote as urlquote from flask import request, url_for diff --git a/funnel/views/video.py b/funnel/views/video.py index 2ccfaca36..8978018e8 100644 --- a/funnel/views/video.py +++ b/funnel/views/video.py @@ -3,8 +3,7 @@ from __future__ import annotations from datetime import datetime -from typing import cast -from typing_extensions import TypedDict +from typing import TypedDict, cast import requests import vimeo diff --git a/package-lock.json b/package-lock.json index 188c4f49d..d33c92e94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1829,9 +1829,9 @@ } }, "node_modules/@codemirror/language": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.9.1.tgz", - "integrity": "sha512-lWRP3Y9IUdOms6DXuBpoWwjkR7yRmnS0hKYCbSfPz9v6Em1A1UCRujAkDiCrdYfs1Z0Eu4dGtwovNPStIfkgNA==", + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.9.2.tgz", + "integrity": "sha512-QGTQXSpAKDIzaSE96zNK1UfIUhPgkT1CLjh1N5qVzZuxgsEOhz5RqaN8QCIdyOQklGLx3MgHd9YrE3X3+Pl1ow==", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", @@ -1857,9 +1857,9 @@ "integrity": "sha512-88e4HhMtKJyw6fKprGaN/yZfiaoGYOi2nM45YCUC6R/kex9sxFWBDGatS1vk4lMgnWmdIIB9tk8Gj1LmL8YfvA==" }, "node_modules/@codemirror/view": { - "version": "6.21.3", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.21.3.tgz", - "integrity": "sha512-8l1aSQ6MygzL4Nx7GVYhucSXvW4jQd0F6Zm3v9Dg+6nZEfwzJVqi4C2zHfDljID+73gsQrWp9TgHc81xU15O4A==", + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.22.0.tgz", + "integrity": "sha512-6zLj4YIoIpfTGKrDMTbeZRpa8ih4EymMCKmddEDcJWrCdp/N1D46B38YEz4creTb4T177AVS9EyXkLeC/HL2jA==", "dependencies": { "@codemirror/state": "^6.1.4", "style-mod": "^4.1.0", @@ -2088,18 +2088,18 @@ } }, "node_modules/@lezer/javascript": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.8.tgz", - "integrity": "sha512-QRmw/5xrcyRLyWr3JT3KCzn2XZr5NYNqQMGsqnYy+FghbQn9DZPuj6JDkE6uSXvfMLpdapu8KBIaeoJFaR4QVw==", + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.9.tgz", + "integrity": "sha512-7Uv8mBBE6l44spgWEZvEMdDqGV+FIuY7kJ1o5TFm+jxIuxydO3PcKJYiINij09igd1D/9P7l2KDqpkN8c3bM6A==", "dependencies": { "@lezer/highlight": "^1.1.3", "@lezer/lr": "^1.3.0" } }, "node_modules/@lezer/lr": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.13.tgz", - "integrity": "sha512-RLAbau/4uSzKgIKj96mI5WUtG1qtiR0Frn0Ei9zhPj8YOkHM+1Bb8SgdVvmR/aWJCFIzjo2KFnDiRZ75Xf5NdQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.14.tgz", + "integrity": "sha512-z5mY4LStlA3yL7aHT/rqgG614cfcvklS+8oFRFBYrs4YaWLJyKKM4+nN6KopToX0o9Hj6zmH6M5kinOYuy06ug==", "dependencies": { "@lezer/common": "^1.0.0" } @@ -2274,9 +2274,9 @@ } }, "node_modules/@types/d3-array": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.9.tgz", - "integrity": "sha512-mZowFN3p64ajCJJ4riVYlOjNlBJv3hctgAY01pjw3qTnJePD8s9DZmYDzhHKvzfCYvdjwylkU38+Vdt7Cu2FDA==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.0.tgz", + "integrity": "sha512-tjU8juPSfhMnu6mJZPOCVVGba4rZoE0tjHDPb81PYwA8CzbaFscGjgkUM7juUJu6iWA1cCVWNEVwxZ5HN9Jj8Q==" }, "node_modules/@types/d3-axis": { "version": "3.0.5", @@ -2477,9 +2477,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", - "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.4.tgz", + "integrity": "sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==" }, "node_modules/@types/geojson": { "version": "7946.0.12", @@ -2514,11 +2514,11 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz", - "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==", + "version": "20.8.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", + "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", "dependencies": { - "undici-types": "~5.25.1" + "undici-types": "~5.26.4" } }, "node_modules/@types/resolve": { @@ -2537,42 +2537,42 @@ "dev": true }, "node_modules/@types/sizzle": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.4.tgz", - "integrity": "sha512-jA2llq2zNkg8HrALI7DtWzhALcVH0l7i89yhY3iBdOz6cBPeACoFq+fkQrjHA39t1hnSFOboZ7A/AY5MMZSlag==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.5.tgz", + "integrity": "sha512-tAe4Q+OLFOA/AMD+0lq8ovp8t3ysxAOeaScnfNdZpUxaGl51ZMDEITxkvFl1STudQ58mz6gzVGl9VhMKhwRnZQ==", "dev": true }, "node_modules/@types/source-list-map": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.3.tgz", - "integrity": "sha512-I9R/7fUjzUOyDy6AFkehCK711wWoAXEaBi80AfjZt1lIkbe6AcXKd3ckQc3liMvQExWvfOeh/8CtKzrfUFN5gA==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.4.tgz", + "integrity": "sha512-Kdfm7Sk5VX8dFW7Vbp18+fmAatBewzBILa1raHYxrGEFXT0jNl9x3LWfuW7bTbjEKFNey9Dfkj/UzT6z/NvRlg==", "dev": true }, "node_modules/@types/tapable": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.9.tgz", - "integrity": "sha512-fOHIwZua0sRltqWzODGUM6b4ffZrf/vzGUmNXdR+4DzuJP42PMbM5dLKcdzlYvv8bMJ3GALOzkk1q7cDm2zPyA==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.10.tgz", + "integrity": "sha512-q8F20SdXG5fdVJQ5yxsVlH+f+oekP42QeHv4s5KlrxTMT0eopXn7ol1rhxMcksf8ph7XNv811iVDE2hOpUvEPg==", "dev": true }, "node_modules/@types/trusted-types": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.4.tgz", - "integrity": "sha512-IDaobHimLQhjwsQ/NMwRVfa/yL7L/wriQPMhw1ZJall0KX6E1oxk29XMDeilW5qTIg5aoiqf5Udy8U/51aNoQQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.5.tgz", + "integrity": "sha512-I3pkr8j/6tmQtKV/ZzHtuaqYSQvyjGRKH4go60Rr0IDLlFxuRT5V32uvB1mecM5G1EVAUyF/4r4QZ1GHgz+mxA==", "dev": true }, "node_modules/@types/uglify-js": { - "version": "3.17.2", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.2.tgz", - "integrity": "sha512-9SjrHO54LINgC/6Ehr81NjAxAYvwEZqjUHLjJYvC4Nmr9jbLQCIZbWSvl4vXQkkmR1UAuaKDycau3O1kWGFyXQ==", + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.3.tgz", + "integrity": "sha512-ToldSfJ6wxO21cakcz63oFD1GjqQbKzhZCD57eH7zWuYT5UEZvfUoqvrjX5d+jB9g4a/sFO0n6QSVzzn5sMsjg==", "dev": true, "dependencies": { "source-map": "^0.6.1" } }, "node_modules/@types/webpack": { - "version": "4.41.34", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.34.tgz", - "integrity": "sha512-CN2aOGrR3zbMc2v+cKqzaClYP1ldkpPOgtdNvgX+RmlWCSWxHxpzz6WSCVQZRkF8D60ROlkRzAoEpgjWQ+bd2g==", + "version": "4.41.35", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.35.tgz", + "integrity": "sha512-XRC6HLGHtNfN8/xWeu1YUQV1GSE+28q8lSqvcJ+0xt/zW9Wmn4j9pCSvaXPyRlCKrl5OuqECQNEJUy2vo8oWqg==", "dev": true, "dependencies": { "@types/node": "*", @@ -2584,9 +2584,9 @@ } }, "node_modules/@types/webpack-sources": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.1.tgz", - "integrity": "sha512-iLC3Fsx62ejm3ST3PQ8vBMC54Rb3EoCprZjeJGI5q+9QjfDLGt9jeg/k245qz1G9AQnORGk0vqPicJFPT1QODQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.2.tgz", + "integrity": "sha512-acCzhuVe+UJy8abiSFQWXELhhNMZjQjQKpLNEi1pKGgKXZj0ul614ATcx4kkhunPost6Xw+aCq8y8cn1/WwAiA==", "dev": true, "dependencies": { "@types/node": "*", @@ -2604,9 +2604,9 @@ } }, "node_modules/@types/yauzl": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.1.tgz", - "integrity": "sha512-CHzgNU3qYBnp/O4S3yv2tXPlvMTq0YWSTVg2/JYLqWZGHwwgJGAwd00poay/11asPq8wLFwHzubyInqHIFmmiw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.2.tgz", + "integrity": "sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==", "dev": true, "optional": true, "dependencies": { @@ -2614,9 +2614,9 @@ } }, "node_modules/@vue/compiler-sfc": { - "version": "2.7.14", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz", - "integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==", + "version": "2.7.15", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.15.tgz", + "integrity": "sha512-FCvIEevPmgCgqFBH7wD+3B97y7u7oj/Wr69zADBf403Tui377bThTjBvekaZvlRr4IwUAu3M6hYZeULZFJbdYg==", "dependencies": { "@babel/parser": "^7.18.4", "postcss": "^8.4.14", @@ -3166,9 +3166,9 @@ } }, "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true }, "node_modules/astral-regex": { @@ -3180,9 +3180,9 @@ } }, "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", "dev": true }, "node_modules/asynciterator.prototype": { @@ -3190,7 +3190,6 @@ "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", "dev": true, - "peer": true, "dependencies": { "has-symbols": "^1.0.3" } @@ -3246,9 +3245,9 @@ "dev": true }, "node_modules/axe-core": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.8.2.tgz", - "integrity": "sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", + "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", "dev": true, "engines": { "node": ">=4" @@ -3318,13 +3317,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", - "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", + "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", "dev": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.32.2" + "core-js-compat": "^3.33.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -3517,13 +3516,14 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3538,9 +3538,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001550", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001550.tgz", - "integrity": "sha512-p82WjBYIypO0ukTsd/FG3Xxs+4tFeaY9pfT4amQL8KWtYH7H9nYwReGAbMTJ0hsmRO8IfDtsS6p3ZWj8+1c2RQ==", + "version": "1.0.30001561", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz", + "integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==", "funding": [ { "type": "opencollective", @@ -3893,9 +3893,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", - "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz", + "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==", "dev": true, "dependencies": { "browserslist": "^4.22.1" @@ -3951,9 +3951,9 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/cypress": { - "version": "13.3.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.1.tgz", - "integrity": "sha512-g4mJLZxYN+UAF2LMy3Znd4LBnUmS59Vynd81VES59RdW48Yt+QtR2cush3melOoVNz0PPbADpWr8DcUx6mif8Q==", + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.4.0.tgz", + "integrity": "sha512-KeWNC9xSHG/ewZURVbaQsBQg2mOKw4XhjJZFKjWbEjgZCdxpPXLpJnfq5Jns1Gvnjp6AlnIfpZfWFlDgVKXdWQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -4009,10 +4009,13 @@ } }, "node_modules/cypress/node_modules/@types/node": { - "version": "18.18.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.5.tgz", - "integrity": "sha512-4slmbtwV59ZxitY4ixUZdy1uRLf9eSIvBWPQxNjhHYWEtn0FryfKpyS2cvADYXTayWdKEIsJengncrVvkI4I6A==", - "dev": true + "version": "18.18.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.8.tgz", + "integrity": "sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/cypress/node_modules/ansi-styles": { "version": "4.3.0", @@ -4133,9 +4136,9 @@ "dev": true }, "node_modules/cytoscape": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.26.0.tgz", - "integrity": "sha512-IV+crL+KBcrCnVVUCZW+zRRRFUZQcrtdOPXki+o4CFUWLdAEYvuZLcBSJC9EBK++suamERKzeY7roq2hdovV3w==", + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.27.0.tgz", + "integrity": "sha512-pPZJilfX9BxESwujODz5pydeGi+FBrXq1rcaB1mfhFXXFJ9GjE6CNndAk+8jPzoXGD+16LtSS4xlYEIUiW4Abg==", "dependencies": { "heap": "^0.2.6", "lodash": "^4.17.21" @@ -4824,9 +4827,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.557", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.557.tgz", - "integrity": "sha512-6x0zsxyMXpnMJnHrondrD3SuAeKcwij9S+83j2qHAQPXbGTDDfgImzzwgGlzrIcXbHQ42tkG4qA6U860cImNhw==" + "version": "1.4.576", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz", + "integrity": "sha512-yXsZyXJfAqzWk1WKryr0Wl0MN2D47xodPvEEwlVePBnhU5E7raevLQR+E6b9JAD3GfL/7MbAL9ZtWQQPcLx7wA==" }, "node_modules/elkjs": { "version": "0.8.2", @@ -4905,9 +4908,9 @@ } }, "node_modules/envinfo": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.10.0.tgz", - "integrity": "sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", "dev": true, "bin": { "envinfo": "dist/cli.js" @@ -4917,26 +4920,26 @@ } }, "node_modules/es-abstract": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", - "integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==", + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", "arraybuffer.prototype.slice": "^1.0.2", "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "call-bind": "^1.0.5", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.1", + "get-intrinsic": "^1.2.2", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has": "^1.0.3", "has-property-descriptors": "^1.0.0", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", + "hasown": "^2.0.0", "internal-slot": "^1.0.5", "is-array-buffer": "^3.0.2", "is-callable": "^1.2.7", @@ -4946,7 +4949,7 @@ "is-string": "^1.0.7", "is-typed-array": "^1.1.12", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.5.1", @@ -4960,7 +4963,7 @@ "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -4974,7 +4977,6 @@ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", "integrity": "sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==", "dev": true, - "peer": true, "dependencies": { "asynciterator.prototype": "^1.0.0", "call-bind": "^1.0.2", @@ -4998,26 +5000,26 @@ "integrity": "sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==" }, "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "node_modules/es-to-primitive": { @@ -5243,26 +5245,26 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.28.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", - "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", "dev": true, "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.findlastindex": "^1.2.2", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", + "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.8.0", - "has": "^1.0.3", - "is-core-module": "^2.13.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.6", - "object.groupby": "^1.0.0", - "object.values": "^1.1.6", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", "semver": "^6.3.1", "tsconfig-paths": "^3.14.2" }, @@ -5295,27 +5297,27 @@ } }, "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz", - "integrity": "sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.20.7", - "aria-query": "^5.1.3", - "array-includes": "^3.1.6", - "array.prototype.flatmap": "^1.3.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.6.2", - "axobject-query": "^3.1.1", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz", + "integrity": "sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "aria-query": "^5.3.0", + "array-includes": "^3.1.7", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "=4.7.0", + "axobject-query": "^3.2.1", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.3", - "language-tags": "=1.0.5", + "es-iterator-helpers": "^1.0.15", + "hasown": "^2.0.0", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", "minimatch": "^3.1.2", - "object.entries": "^1.1.6", - "object.fromentries": "^2.0.6", - "semver": "^6.3.0" + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7" }, "engines": { "node": ">=4.0" @@ -6215,15 +6217,15 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "dev": true, "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", + "function-bind": "^1.1.2", "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6449,15 +6451,6 @@ "node": ">=0.8.0" } }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -6476,12 +6469,12 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.1" + "get-intrinsic": "^1.2.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6526,15 +6519,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/heap": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==" }, "node_modules/htmx.org": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.6.tgz", - "integrity": "sha512-4Zebo9nzg8u2ZHuIJmvB/nQS6kIMLIoEfhTg/oRwyCIJhL5MLA/jPU1EPEBtGOmG4ZG0k05Vpd3sab2+zfvteQ==" + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.7.tgz", + "integrity": "sha512-bHONSJ3WBtMG5Ex6xmC+t///rCKINrrPNeZ0Nq9cylVbzZ9ZGjWn1z60hripbqZeIM3WSpEATgm+MglDIK5WVQ==" }, "node_modules/http-signature": { "version": "1.3.6", @@ -6700,13 +6705,13 @@ } }, "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", "side-channel": "^1.0.4" }, "engines": { @@ -6749,7 +6754,6 @@ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", "dev": true, - "peer": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -6825,12 +6829,12 @@ } }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6864,7 +6868,6 @@ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2" }, @@ -6885,7 +6888,6 @@ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", "dev": true, - "peer": true, "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -6928,7 +6930,6 @@ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7067,7 +7068,6 @@ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7164,7 +7164,6 @@ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", "dev": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7186,7 +7185,6 @@ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "get-intrinsic": "^1.1.1" @@ -7226,7 +7224,6 @@ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", "dev": true, - "peer": true, "dependencies": { "define-properties": "^1.2.1", "get-intrinsic": "^1.2.1", @@ -7606,12 +7603,15 @@ "dev": true }, "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", "dev": true, "dependencies": { - "language-subtag-registry": "~0.3.2" + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" } }, "node_modules/layout-base": { @@ -8739,9 +8739,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -8829,9 +8829,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.0.tgz", - "integrity": "sha512-HQ4J+ic8hKrgIt3mqk6cVOVrW2ozL4KdvHlqpBv9vDYWx9ysAgENAdvy4FoGF+KFdhR7nQTNm5J0ctAeOwn+3g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9335,9 +9335,9 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } @@ -9476,7 +9476,6 @@ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", "integrity": "sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", @@ -9900,9 +9899,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.69.4", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.4.tgz", - "integrity": "sha512-+qEreVhqAy8o++aQfCJwp0sklr2xyEzkm9Pp/Igu9wNPoe7EZEQ8X/MBvvXggI2ql607cxKg/RKOwDj6pp2XDA==", + "version": "1.69.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", + "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -9997,6 +9996,21 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/set-function-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", @@ -10164,9 +10178,9 @@ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "dev": true, "dependencies": { "asn1": "~0.2.3", @@ -10518,9 +10532,9 @@ } }, "node_modules/terser": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.22.0.tgz", - "integrity": "sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.24.0.tgz", + "integrity": "sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -10585,9 +10599,9 @@ } }, "node_modules/terser/node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "bin": { "acorn": "bin/acorn" }, @@ -10900,9 +10914,9 @@ } }, "node_modules/undici-types": { - "version": "5.25.3", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -10957,9 +10971,9 @@ } }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" @@ -11549,11 +11563,11 @@ "dev": true }, "node_modules/vue": { - "version": "2.7.14", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz", - "integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==", + "version": "2.7.15", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.15.tgz", + "integrity": "sha512-a29fsXd2G0KMRqIFTpRgpSbWaNBK3lpCTOLuGLEDnlHWdjB8fwl6zyYZ8xCrqkJdatwZb4mGHiEfJjnw0Q6AwQ==", "dependencies": { - "@vue/compiler-sfc": "2.7.14", + "@vue/compiler-sfc": "2.7.15", "csstype": "^3.1.0" } }, @@ -11743,9 +11757,9 @@ } }, "node_modules/webpack/node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "bin": { "acorn": "bin/acorn" }, @@ -11822,7 +11836,6 @@ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", "dev": true, - "peer": true, "dependencies": { "function.prototype.name": "^1.1.5", "has-tostringtag": "^1.0.0", @@ -11849,7 +11862,6 @@ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", "dev": true, - "peer": true, "dependencies": { "is-map": "^2.0.1", "is-set": "^2.0.1", @@ -11861,13 +11873,13 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", - "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "call-bind": "^1.0.4", "for-each": "^0.3.3", "gopd": "^1.0.1", "has-tostringtag": "^1.0.0" diff --git a/pyproject.toml b/pyproject.toml index 97c1bb854..f1874885f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,6 @@ order_by_type = true use_parentheses = true combine_as_imports = true split_on_trailing_comma = false -extra_standard_library = ['typing_extensions'] known_repo = ['funnel'] known_first_party = ['baseframe', 'coaster', 'flask_lastuser'] default_section = 'THIRDPARTY' @@ -300,7 +299,6 @@ max-complexity = 10 [tool.ruff.isort] # These config options should match isort config above under [tool.isort] combine-as-imports = true -extra-standard-library = ['typing_extensions'] split-on-trailing-comma = false relative-imports-order = 'furthest-to-closest' known-first-party = ['baseframe', 'coaster', 'flask_lastuser'] diff --git a/requirements/base.in b/requirements/base.in index 33b3e1533..8843ddffe 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -6,15 +6,12 @@ Babel base58 bcrypt better_profanity!=0.7.0 # https://github.com/snguyenthanh/better_profanity/issues/19 -blinker -boto3 # Required in Imgee not here, but has a pin on urllib3 version Brotli chevron click cryptography dataclasses-json Flask -Flask-Assets2 Flask-Babel Flask-Executor Flask-FlatPages @@ -37,7 +34,6 @@ itsdangerous linkify-it-py markdown-it-py mdit-py-plugins -oauth2 oauth2client passlib phonenumbers @@ -59,12 +55,11 @@ rq-dashboard @ git+https://github.com/jace/rq-dashboard SQLAlchemy sqlalchemy-json SQLAlchemy-Utils -toml tweepy twilio typing-extensions tzdata -urllib3[socks] # Not required here, but the [socks] extra shows up in test.txt +urllib3[socks] # Not direct dep; pip-audit complains of dupe urllib[socks] in test.txt user-agents werkzeug whitenoise diff --git a/requirements/base.txt b/requirements/base.txt index 6832c9b8a..e16a1dc84 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,4 +1,4 @@ -# SHA1:b825fb2bb7b273cf1c5f4d42e496b72531f87768 +# SHA1:3f7e275da4cb49056d67911958d5b0a59e01971d # # This file is autogenerated by pip-compile-multi # To update, run: @@ -57,16 +57,9 @@ bleach==6.1.0 # coaster blinker==1.7.0 # via - # -r requirements/base.in # baseframe # coaster # flask -boto3==1.28.78 - # via -r requirements/base.in -botocore==1.31.78 - # via - # boto3 - # s3transfer brotli==1.1.0 # via -r requirements/base.in cachelib==0.9.0 @@ -135,7 +128,6 @@ flask==3.0.0 # rq-dashboard flask-assets2==2.1 # via - # -r requirements/base.in # baseframe # coaster flask-babel==4.0.0 @@ -203,9 +195,7 @@ html5lib==1.1 httpcore==1.0.1 # via httpx httplib2==0.22.0 - # via - # oauth2 - # oauth2client + # via oauth2client httpx[http2]==0.25.1 # via # -r requirements/base.in @@ -236,10 +226,6 @@ jinja2==3.1.2 # flask # flask-babel # flask-flatpages -jmespath==1.0.1 - # via - # boto3 - # botocore joblib==1.3.2 # via nltk linkify-it-py==2.0.2 @@ -284,8 +270,6 @@ mypy-extensions==1.0.0 # via typing-inspect nltk==3.8.1 # via coaster -oauth2==1.9.0.post1 - # via -r requirements/base.in oauth2client==4.1.3 # via -r requirements/base.in oauthlib==3.2.2 @@ -345,7 +329,6 @@ python-dateutil==2.8.2 # -r requirements/base.in # arrow # baseframe - # botocore # freezegun # icalendar # rq-scheduler @@ -414,8 +397,6 @@ rq-scheduler==0.13.1 # via flask-rq2 rsa==4.9 # via oauth2client -s3transfer==0.7.0 - # via boto3 semantic-version==2.10.0 # via # baseframe @@ -461,8 +442,6 @@ tldextract==5.1.0 # via # coaster # mxsniff -toml==0.10.2 - # via -r requirements/base.in tqdm==4.66.1 # via nltk tuspy==1.0.1 @@ -497,7 +476,6 @@ unidecode==1.3.7 urllib3[socks]==2.0.7 # via # -r requirements/base.in - # botocore # requests # sentry-sdk user-agents==2.2.0 diff --git a/requirements/dev.in b/requirements/dev.in index 36553fc8a..1f6446083 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -28,8 +28,6 @@ pylint pyupgrade reformat-gherkin ruff -toml -tomli types-chevron types-geoip2 types-mock diff --git a/requirements/dev.txt b/requirements/dev.txt index 8864e82bb..4d5dcfd6c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,4 +1,4 @@ -# SHA1:c00161aa72c5c14155654eb36f53feb8f23b1522 +# SHA1:7bfc341e23494b80e1f77e06104f53470de16cfa # # This file is autogenerated by pip-compile-multi # To update, run: @@ -159,8 +159,6 @@ stevedore==5.1.0 # via bandit tokenize-rt==5.2.0 # via pyupgrade -tomli==2.0.1 - # via -r requirements/dev.in toposort==1.10 # via pip-compile-multi types-chevron==0.14.2.5 From becda18c0bf8b3d7c6fd71bf56facbd041cdfbb8 Mon Sep 17 00:00:00 2001 From: Vidya Ramakrishnan Date: Mon, 6 Nov 2023 20:39:19 +0530 Subject: [PATCH 3/6] Fix the profile transition form (#1924) * Fix the profile transition form * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- funnel/forms/profile.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/funnel/forms/profile.py b/funnel/forms/profile.py index 8236caf2a..046c656cd 100644 --- a/funnel/forms/profile.py +++ b/funnel/forms/profile.py @@ -91,7 +91,9 @@ class ProfileTransitionForm(forms.Form): def set_queries(self) -> None: """Prepare form for use.""" - self.transition.choices = list(self.edit_obj.state.transitions().items()) + self.transition.choices = list( + self.edit_obj.profile_state.transitions().items() + ) @Account.forms('logo') From 4e2cd1b9787c124562bc375e56672dd7bb4f2396 Mon Sep 17 00:00:00 2001 From: Vidya Ramakrishnan Date: Tue, 7 Nov 2023 12:17:56 +0530 Subject: [PATCH 4/6] Show placeholder page for private accounts (#1912) * For private profile show just the header * Remove print statments * Remove role check * User profile check not required * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix org url in the dropdown menu --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- funnel/templates/account_menu.html.jinja2 | 2 +- funnel/templates/profile_layout.html.jinja2 | 8 ++--- funnel/templates/user_profile.html.jinja2 | 26 +++++++++------- .../user_profile_projects.html.jinja2 | 30 +++++++++++-------- .../user_profile_proposals.html.jinja2 | 10 +++++-- funnel/views/profile.py | 8 +++-- 6 files changed, 50 insertions(+), 34 deletions(-) diff --git a/funnel/templates/account_menu.html.jinja2 b/funnel/templates/account_menu.html.jinja2 index 382e9e75f..fcaf35c39 100644 --- a/funnel/templates/account_menu.html.jinja2 +++ b/funnel/templates/account_menu.html.jinja2 @@ -36,7 +36,7 @@ {%- for orgmem in orgmemlist.recent %}
  • - {%- if orgmem.account.logo_url.url %} diff --git a/funnel/templates/profile_layout.html.jinja2 b/funnel/templates/profile_layout.html.jinja2 index 0a7a9a1a7..01c2e51cf 100644 --- a/funnel/templates/profile_layout.html.jinja2 +++ b/funnel/templates/profile_layout.html.jinja2 @@ -426,10 +426,10 @@ {% if profile.is_organization_profile %} {% trans %}Home{% endtrans %} {% trans %}Admins{% endtrans %} {{ faicon(icon='chevron-right', icon_size='subhead') }} - {% else %} - {% trans %}Sessions{% endtrans %} - {% trans %}Projects{% endtrans %}{{ faicon(icon='chevron-right', icon_size='subhead') }} - {% trans %}Submissions{% endtrans %}{{ faicon(icon='chevron-right', icon_size='subhead') }} + {% elif not profile.features.is_private() %} + {% trans %}Sessions{% endtrans %} + {% trans %}Projects{% endtrans %}{{ faicon(icon='chevron-right', icon_size='subhead') }} + {% trans %}Submissions{% endtrans %}{{ faicon(icon='chevron-right', icon_size='subhead') }} {% endif %} diff --git a/funnel/templates/user_profile.html.jinja2 b/funnel/templates/user_profile.html.jinja2 index 8e8fd02e6..cbe8c5929 100644 --- a/funnel/templates/user_profile.html.jinja2 +++ b/funnel/templates/user_profile.html.jinja2 @@ -35,18 +35,22 @@
    {% block contentwrapper %} -
    -
    - {% if not tagged_sessions %} -

    {% trans %}No tagged sessions yet{% endtrans %}

    - {% endif %} -
    - {% for session in tagged_sessions %} -
    - {{ video_thumbnail(session) }} + {% if profile.features.is_private() %} +

    {% trans %}This is a private account{% endtrans %}

    + {% else %} +
    +
    + {% if not tagged_sessions %} +

    {% trans %}No tagged sessions yet{% endtrans %}

    + {% endif %}
    - {% endfor %} -
    + {% for session in tagged_sessions %} +
    + {{ video_thumbnail(session) }} +
    + {% endfor %} +
    + {% endif %} {% endblock contentwrapper %}
    diff --git a/funnel/templates/user_profile_projects.html.jinja2 b/funnel/templates/user_profile_projects.html.jinja2 index feb9a523d..34578edd7 100644 --- a/funnel/templates/user_profile_projects.html.jinja2 +++ b/funnel/templates/user_profile_projects.html.jinja2 @@ -8,18 +8,24 @@ {% block contentwrapper %}
    -
    - {% if not participated_projects %} -

    {% trans %}No participation yet{% endtrans %}

    - {% endif %} -
    -
      - {% for project in participated_projects %} -
    • - {{ projectcard(project, include_calendar=true, calendarwidget_compact=false) }} -
    • - {%- endfor -%} -
    + {% if profile.features.is_private() %} +
    +

    {% trans %}This is a private account{% endtrans %}

    +
    + {% else %} +
    + {% if not participated_projects %} +

    {% trans %}No participation yet{% endtrans %}

    + {% endif %} +
    +
      + {% for project in participated_projects %} +
    • + {{ projectcard(project, include_calendar=true, calendarwidget_compact=false) }} +
    • + {%- endfor -%} +
    + {% endif %}
    {% endblock contentwrapper %} diff --git a/funnel/templates/user_profile_proposals.html.jinja2 b/funnel/templates/user_profile_proposals.html.jinja2 index f2b376aae..a750825ac 100644 --- a/funnel/templates/user_profile_proposals.html.jinja2 +++ b/funnel/templates/user_profile_proposals.html.jinja2 @@ -9,10 +9,14 @@ {% block contentwrapper %}
    - {% if submitted_proposals %} - {{ proposal_list(submitted_proposals) }} + {% if profile.features.is_private() %} +

    {% trans %}This is a private account{% endtrans %}

    {% else %} -

    {% trans %}No submissions{% endtrans %}

    + {% if submitted_proposals %} + {{ proposal_list(submitted_proposals) }} + {% else %} +

    {% trans %}No submissions{% endtrans %}

    + {% endif %} {% endif %}
    diff --git a/funnel/views/profile.py b/funnel/views/profile.py index dd224508f..c16173794 100644 --- a/funnel/views/profile.py +++ b/funnel/views/profile.py @@ -62,6 +62,11 @@ def feature_profile_make_private(obj: Account): return obj.current_roles.admin and obj.make_profile_private.is_available +@Account.features('is_private') +def feature_profile_is_private(obj: Account): + return not obj.current_roles.admin and not bool(obj.profile_state.ACTIVE_AND_PUBLIC) + + def template_switcher(templateargs): template = templateargs.pop('template') return render_template(template, **templateargs) @@ -72,7 +77,6 @@ def template_switcher(templateargs): class ProfileView(AccountViewMixin, UrlChangeCheck, UrlForView, ModelView): @route('', endpoint='profile') @render_with({'text/html': template_switcher}, json=True) - @requires_roles({'reader', 'admin'}) def view(self) -> ReturnRenderWith: template_name = None ctx = {} @@ -240,7 +244,6 @@ def view(self) -> ReturnRenderWith: @route('in/projects') @render_with('user_profile_projects.html.jinja2', json=True) - @requires_roles({'reader', 'admin'}) def user_participated_projects(self) -> ReturnRenderWith: if self.obj.is_organization_profile: abort(404) @@ -260,7 +263,6 @@ def user_participated_projects(self) -> ReturnRenderWith: @route('in/submissions') @route('in/proposals') # Legacy route, will be auto-redirected to `in/submissions` @render_with('user_profile_proposals.html.jinja2', json=True) - @requires_roles({'reader', 'admin'}) def user_proposals(self) -> ReturnRenderWith: if self.obj.is_organization_profile: abort(404) From d9ef18778c6456aca455a38530b19deba6a2ea48 Mon Sep 17 00:00:00 2001 From: anishTP <119032387+anishTP@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:39:59 +0530 Subject: [PATCH 5/6] Increase image quality for project banners (#1925) --- funnel/templates/macros.html.jinja2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/funnel/templates/macros.html.jinja2 b/funnel/templates/macros.html.jinja2 index 5d508bc80..a2ef2faa6 100644 --- a/funnel/templates/macros.html.jinja2 +++ b/funnel/templates/macros.html.jinja2 @@ -1,6 +1,6 @@ {%- set img_size = namespace() %} {%- set img_size.profile_banner = 1200 %} -{%- set img_size.spotlight_banner = 700 %} +{%- set img_size.spotlight_banner = 1200 %} {%- set img_size.card_banner = 400 %} {%- set img_size.card_banner_small = 100 %} {%- set img_size.profile_logo = 240 %} From 28cfbe30124e0adb83eeaea9d3d625980cd0006d Mon Sep 17 00:00:00 2001 From: Kiran Jonnalagadda Date: Tue, 14 Nov 2023 22:08:20 +0530 Subject: [PATCH 6/6] Use `BaseMixin[type]` for UUID pkeys and update other type annotations (#1927) * Use ProtoMixin name suffix for mixin classes that have expectations (ie, are protocols) * Lock rows for update when reordering --- funnel/forms/account.py | 8 ++- funnel/models/account.py | 9 ++- funnel/models/comment.py | 19 +++--- funnel/models/membership_mixin.py | 18 +++--- funnel/models/moderation.py | 5 +- funnel/models/notification.py | 8 +-- funnel/models/proposal.py | 4 +- funnel/models/proposal_membership.py | 9 ++- funnel/models/reorder_mixin.py | 20 +++--- funnel/models/sponsor_membership.py | 12 ++-- funnel/typing.py | 4 +- funnel/views/mixins.py | 4 +- funnel/views/search.py | 6 +- migrations/script.py.mako | 6 +- tests/conftest.py | 91 ++++++++++++++-------------- tests/integration/views/conftest.py | 4 +- 16 files changed, 123 insertions(+), 104 deletions(-) diff --git a/funnel/forms/account.py b/funnel/forms/account.py index 027368c5d..0060c8be5 100644 --- a/funnel/forms/account.py +++ b/funnel/forms/account.py @@ -496,7 +496,7 @@ def validate_username(self, field: forms.Field) -> None: raise_username_error(reason) -class EnableNotificationsDescriptionMixin: +class EnableNotificationsDescriptionProtoMixin: """Mixin to add a link in the description for enabling notifications.""" enable_notifications: forms.Field @@ -513,7 +513,9 @@ def set_queries(self) -> None: @Account.forms('email_add') -class NewEmailAddressForm(EnableNotificationsDescriptionMixin, forms.RecaptchaForm): +class NewEmailAddressForm( + EnableNotificationsDescriptionProtoMixin, forms.RecaptchaForm +): """Form to add a new email address to an account.""" __expects__ = ('edit_user',) @@ -560,7 +562,7 @@ class EmailPrimaryForm(forms.Form): @Account.forms('phone_add') -class NewPhoneForm(EnableNotificationsDescriptionMixin, forms.RecaptchaForm): +class NewPhoneForm(EnableNotificationsDescriptionProtoMixin, forms.RecaptchaForm): """Form to add a new mobile number (SMS-capable) to an account.""" __expects__ = ('edit_user',) diff --git a/funnel/models/account.py b/funnel/models/account.py index bbc34da48..994b1593b 100644 --- a/funnel/models/account.py +++ b/funnel/models/account.py @@ -6,7 +6,7 @@ import itertools from collections.abc import Iterable, Iterator from datetime import datetime, timedelta -from typing import ClassVar, Literal, Union, cast, overload +from typing import ClassVar, Literal, cast, overload from uuid import UUID import phonenumbers @@ -259,7 +259,7 @@ class Account(UuidMixin, BaseMixin, Model): sa.orm.mapped_column(sa.Integer, nullable=False), read={'all'} ) - search_vector: Mapped[str] = sa.orm.mapped_column( + search_vector: Mapped[TSVectorType] = sa.orm.mapped_column( TSVectorType( 'title', 'name', @@ -1227,11 +1227,10 @@ def organization_links(self) -> list: add_search_trigger(Account, 'name_vector') -class AccountOldId(UuidMixin, BaseMixin, Model): +class AccountOldId(UuidMixin, BaseMixin[UUID], Model): """Record of an older UUID for an account, after account merger.""" __tablename__ = 'account_oldid' - __uuid_primary_key__ = True #: Old account, if still present old_account: Mapped[Account] = relationship( @@ -2178,7 +2177,7 @@ def get( ) #: Anchor type -Anchor = Union[AccountEmail, AccountEmailClaim, AccountPhone, EmailAddress, PhoneNumber] +Anchor = AccountEmail | AccountEmailClaim | AccountPhone | EmailAddress | PhoneNumber # Tail imports # pylint: disable=wrong-import-position diff --git a/funnel/models/comment.py b/funnel/models/comment.py index c0361d9e6..e6f3e4240 100644 --- a/funnel/models/comment.py +++ b/funnel/models/comment.py @@ -4,7 +4,7 @@ from collections.abc import Sequence from datetime import datetime -from typing import Any +from typing import TYPE_CHECKING, Any from werkzeug.utils import cached_property @@ -134,7 +134,7 @@ def __init__(self, **kwargs) -> None: self.count = 0 @cached_property - def parent(self) -> BaseMixin: + def parent(self) -> Project | Proposal | Update: # FIXME: Move this to a CommentMixin that uses a registry, like EmailAddress if self.project is not None: return self.project @@ -147,11 +147,8 @@ def parent(self) -> BaseMixin: with_roles(parent, read={'all'}, datasets={'primary'}) @cached_property - def parent_type(self) -> str | None: - parent = self.parent - if parent is not None: - return parent.__tablename__ - return None + def parent_type(self) -> str: + return self.parent.__tablename__ with_roles(parent_type, read={'all'}) @@ -363,6 +360,7 @@ def _message_expression(cls): @property def title(self) -> str: + """A made-up title referring to the context for the comment.""" obj = self.commentset.parent if obj is not None: return _("{user} commented on {obj}").format( @@ -439,3 +437,10 @@ class __Commentset: ), viewonly=True, ) + + +# Tail imports for type checking +if TYPE_CHECKING: + from .project import Project + from .proposal import Proposal + from .update import Update diff --git a/funnel/models/membership_mixin.py b/funnel/models/membership_mixin.py index 345b9ac89..37e61d46b 100644 --- a/funnel/models/membership_mixin.py +++ b/funnel/models/membership_mixin.py @@ -5,6 +5,7 @@ from collections.abc import Callable, Iterable from datetime import datetime as datetime_type from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar +from uuid import UUID from sqlalchemy import event from sqlalchemy.sql.expression import ColumnElement @@ -27,7 +28,7 @@ sa, ) from .account import Account -from .reorder_mixin import ReorderMixin +from .reorder_mixin import ReorderProtoMixin # Export only symbols needed in views. __all__ = [ @@ -40,7 +41,9 @@ # --- Typing --------------------------------------------------------------------------- MembershipType = TypeVar('MembershipType', bound='ImmutableMembershipMixin') -FrozenAttributionType = TypeVar('FrozenAttributionType', bound='FrozenAttributionMixin') +FrozenAttributionType = TypeVar( + 'FrozenAttributionType', bound='FrozenAttributionProtoMixin' +) # --- Enum ----------------------------------------------------------------------------- @@ -82,10 +85,9 @@ class MembershipRecordTypeError(MembershipError): @declarative_mixin -class ImmutableMembershipMixin(UuidMixin, BaseMixin): +class ImmutableMembershipMixin(UuidMixin, BaseMixin[UUID]): """Support class for immutable memberships.""" - __uuid_primary_key__ = True #: Can granted_by be null? Only in memberships based on legacy data __null_granted_by__: ClassVar[bool] = False #: List of columns that will be copied into a new row when a membership is amended @@ -383,7 +385,7 @@ def member_id(cls) -> Mapped[int]: @with_roles(read={'member', 'editor'}, grants_via={None: {'admin': 'member'}}) @declared_attr @classmethod - def member(cls) -> Mapped[Account]: + def member(cls) -> Mapped[Account]: # type: ignore[override] """Member in this membership record.""" return relationship(Account, foreign_keys=[cls.member_id]) @@ -490,7 +492,7 @@ def migrate_account(cls, old_account: Account, new_account: Account) -> None: @declarative_mixin -class ReorderMembershipMixin(ReorderMixin): +class ReorderMembershipProtoMixin(ReorderProtoMixin): """Customizes ReorderMixin for membership models.""" if TYPE_CHECKING: @@ -558,7 +560,7 @@ def parent_scoped_reorder_query_filter(self) -> ColumnElement: @declarative_mixin -class FrozenAttributionMixin: +class FrozenAttributionProtoMixin: """Provides a `title` data column and support method to freeze it.""" if TYPE_CHECKING: @@ -580,7 +582,7 @@ def _title(cls) -> Mapped[str | None]: def title(self) -> str: """Attribution title for this record.""" if self._local_data_only: - return self._title # This may be None + return self._title # This may be None # type: ignore[return-value] return self._title or self.member.title @title.setter diff --git a/funnel/models/moderation.py b/funnel/models/moderation.py index 0ba9f69e2..7ec499771 100644 --- a/funnel/models/moderation.py +++ b/funnel/models/moderation.py @@ -2,6 +2,8 @@ from __future__ import annotations +from uuid import UUID + from baseframe import __ from coaster.sqlalchemy import StateManager, with_roles from coaster.utils import LabeledEnum @@ -20,9 +22,8 @@ class MODERATOR_REPORT_TYPE(LabeledEnum): # noqa: N801 SPAM = (2, 'spam', __("Spam")) -class CommentModeratorReport(UuidMixin, BaseMixin, Model): +class CommentModeratorReport(UuidMixin, BaseMixin[UUID], Model): __tablename__ = 'comment_moderator_report' - __uuid_primary_key__ = True comment_id = sa.orm.mapped_column( sa.Integer, sa.ForeignKey('comment.id'), nullable=False, index=True diff --git a/funnel/models/notification.py b/funnel/models/notification.py index 165b32007..f6d3661b4 100644 --- a/funnel/models/notification.py +++ b/funnel/models/notification.py @@ -636,7 +636,7 @@ def allow_transport(cls, transport: str) -> bool: @property def role_provider_obj(self) -> _F | _D: """Return fragment if exists, document otherwise, indicating role provider.""" - return cast(Union[_F, _D], self.fragment or self.document) + return cast(_F | _D, self.fragment or self.document) def dispatch(self) -> Generator[NotificationRecipient, None, None]: """ @@ -733,7 +733,7 @@ def __getattr__(self, attr: str) -> Any: return getattr(self.cls, attr) -class NotificationRecipientMixin: +class NotificationRecipientProtoMixin: """Shared mixin for :class:`NotificationRecipient` and :class:`NotificationFor`.""" notification: Mapped[Notification] | Notification | PreviewNotification @@ -788,7 +788,7 @@ def is_not_deleted(self, revoke: bool = False) -> bool: return False -class NotificationRecipient(NotificationRecipientMixin, NoIdMixin, Model): +class NotificationRecipient(NoIdMixin, NotificationRecipientProtoMixin, Model): """ The recipient of a notification. @@ -1201,7 +1201,7 @@ def migrate_account(cls, old_account: Account, new_account: Account) -> None: ) -class NotificationFor(NotificationRecipientMixin): +class NotificationFor(NotificationRecipientProtoMixin): """View-only wrapper to mimic :class:`UserNotification`.""" notification: Notification | PreviewNotification diff --git a/funnel/models/proposal.py b/funnel/models/proposal.py index 3f504f63f..3b79396de 100644 --- a/funnel/models/proposal.py +++ b/funnel/models/proposal.py @@ -33,7 +33,7 @@ ) from .project import Project from .project_membership import project_child_role_map -from .reorder_mixin import ReorderMixin +from .reorder_mixin import ReorderProtoMixin from .video_mixin import VideoMixin __all__ = ['PROPOSAL_STATE', 'Proposal', 'ProposalSuuidRedirect'] @@ -119,7 +119,7 @@ class PROPOSAL_STATE(LabeledEnum): # noqa: N801 class Proposal( # type: ignore[misc] - UuidMixin, BaseScopedIdNameMixin, VideoMixin, ReorderMixin, Model + UuidMixin, BaseScopedIdNameMixin, VideoMixin, ReorderProtoMixin, Model ): __tablename__ = 'proposal' diff --git a/funnel/models/proposal_membership.py b/funnel/models/proposal_membership.py index 1813a6138..115d086bc 100644 --- a/funnel/models/proposal_membership.py +++ b/funnel/models/proposal_membership.py @@ -10,9 +10,9 @@ from .account import Account from .helpers import reopen from .membership_mixin import ( - FrozenAttributionMixin, + FrozenAttributionProtoMixin, ImmutableUserMembershipMixin, - ReorderMembershipMixin, + ReorderMembershipProtoMixin, ) from .project import Project from .proposal import Proposal @@ -21,7 +21,10 @@ class ProposalMembership( # type: ignore[misc] - FrozenAttributionMixin, ReorderMembershipMixin, ImmutableUserMembershipMixin, Model + ImmutableUserMembershipMixin, + FrozenAttributionProtoMixin, + ReorderMembershipProtoMixin, + Model, ): """Users can be presenters or reviewers on proposals.""" diff --git a/funnel/models/reorder_mixin.py b/funnel/models/reorder_mixin.py index b8367035e..1453252ce 100644 --- a/funnel/models/reorder_mixin.py +++ b/funnel/models/reorder_mixin.py @@ -8,23 +8,24 @@ from . import Mapped, QueryProperty, db, declarative_mixin, sa -__all__ = ['ReorderMixin'] +__all__ = ['ReorderProtoMixin'] -# Use of TypeVar for subclasses of ReorderMixin as defined in this mypy ticket: +# Use of TypeVar for subclasses of ReorderMixin as defined in these mypy tickets: # https://github.com/python/mypy/issues/1212 -Reorderable = TypeVar('Reorderable', bound='ReorderMixin') +# https://github.com/python/mypy/issues/7191 +Reorderable = TypeVar('Reorderable', bound='ReorderProtoMixin') @declarative_mixin -class ReorderMixin: +class ReorderProtoMixin: """Adds support for re-ordering sequences within a parent container.""" if TYPE_CHECKING: #: Subclasses must have a created_at column created_at: Mapped[datetime] #: Subclass must have a primary key that is int or uuid - id: Mapped[int] # noqa: A001 + id: Mapped[int | UUID] # noqa: A001 #: Subclass must declare a parent_id synonym to the parent model fkey column parent_id: Mapped[int | UUID] #: Subclass must declare a seq column or synonym, holding a sequence id. It @@ -36,7 +37,7 @@ class ReorderMixin: query: ClassVar[QueryProperty] @property - def parent_scoped_reorder_query_filter(self: Reorderable): + def parent_scoped_reorder_query_filter(self: Reorderable) -> sa.ColumnElement[bool]: """ Return a query filter that includes a scope limitation to the parent. @@ -80,6 +81,7 @@ def reorder_item(self: Reorderable, other: Reorderable, before: bool) -> None: cls.seq >= min(self.seq, other.seq), cls.seq <= max(self.seq, other.seq), ) + .with_for_update(of=cls) # Lock these rows to prevent a parallel update .options(sa.orm.load_only(cls.id, cls.seq)) .order_by(*order_columns) .all() @@ -99,7 +101,9 @@ def reorder_item(self: Reorderable, other: Reorderable, before: bool) -> None: new_seq_number = self.seq # Temporarily give self an out-of-bounds number self.seq = ( - sa.select(sa.func.coalesce(sa.func.max(cls.seq) + 1, 1)) + sa.select( # type: ignore[assignment] + sa.func.coalesce(sa.func.max(cls.seq) + 1, 1) + ) .where(self.parent_scoped_reorder_query_filter) .scalar_subquery() ) @@ -109,7 +113,7 @@ def reorder_item(self: Reorderable, other: Reorderable, before: bool) -> None: for reorderable_item in items_to_reorder[1:]: # Skip 0, which is self reorderable_item.seq, new_seq_number = new_seq_number, reorderable_item.seq # Flush to force execution order. This does not expunge SQLAlchemy cache as - # of SQLAlchemy 1.3.x. Should that behaviour change, a switch to + # of SQLAlchemy 2.0.x. Should that behaviour change, a switch to # bulk_update_mappings will be required db.session.flush() if reorderable_item.id == other.id: diff --git a/funnel/models/sponsor_membership.py b/funnel/models/sponsor_membership.py index 3bfb4ff34..52b8bd9de 100644 --- a/funnel/models/sponsor_membership.py +++ b/funnel/models/sponsor_membership.py @@ -10,9 +10,9 @@ from .account import Account from .helpers import reopen from .membership_mixin import ( - FrozenAttributionMixin, + FrozenAttributionProtoMixin, ImmutableUserMembershipMixin, - ReorderMembershipMixin, + ReorderMembershipProtoMixin, ) from .project import Project from .proposal import Proposal @@ -21,9 +21,9 @@ class ProjectSponsorMembership( # type: ignore[misc] - FrozenAttributionMixin, - ReorderMembershipMixin, ImmutableUserMembershipMixin, + FrozenAttributionProtoMixin, + ReorderMembershipProtoMixin, Model, ): """Sponsor of a project.""" @@ -151,8 +151,8 @@ def has_sponsors(self) -> bool: # FIXME: Replace this with existing proposal collaborator as they're now both related # to "account" class ProposalSponsorMembership( # type: ignore[misc] - FrozenAttributionMixin, - ReorderMembershipMixin, + FrozenAttributionProtoMixin, + ReorderMembershipProtoMixin, ImmutableUserMembershipMixin, Model, ): diff --git a/funnel/typing.py b/funnel/typing.py index 52446c431..4e0aced06 100644 --- a/funnel/typing.py +++ b/funnel/typing.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional, ParamSpec, TypeAlias, TypeVar, Union +from typing import ParamSpec, TypeAlias, TypeVar from flask.typing import ResponseReturnValue from werkzeug.wrappers import Response # Base class for Flask Response @@ -32,7 +32,7 @@ ReturnView: TypeAlias = ResponseReturnValue #: Return type of the `migrate_user` and `migrate_profile` methods -OptionalMigratedTables: TypeAlias = Optional[Union[list[str], tuple[str], set[str]]] +OptionalMigratedTables: TypeAlias = list[str] | tuple[str] | set[str] | None #: Return type for Response objects ReturnResponse: TypeAlias = Response diff --git a/funnel/views/mixins.py b/funnel/views/mixins.py index 49127da29..9ec94ab2f 100644 --- a/funnel/views/mixins.py +++ b/funnel/views/mixins.py @@ -24,7 +24,7 @@ VenueRoom, db, ) -from ..typing import ReturnRenderWith, ReturnView +from ..typing import ReturnView from .helpers import render_redirect @@ -250,7 +250,7 @@ def get_draft_data( return draft.revision, draft.formdata return None, None - def autosave_post(self, obj: UuidModelUnion | None = None) -> ReturnRenderWith: + def autosave_post(self, obj: UuidModelUnion | None = None) -> ReturnView: """Handle autosave POST requests.""" obj = obj if obj is not None else self.obj if 'form.revision' not in request.form: diff --git a/funnel/views/search.py b/funnel/views/search.py index f518001aa..dc14c3fae 100644 --- a/funnel/views/search.py +++ b/funnel/views/search.py @@ -86,7 +86,11 @@ def regconfig(self) -> str: @property def title_column(self) -> sa.ColumnElement[str]: """Return a column or column expression representing the object's title.""" - return self.model.title + # `Comment.title` is a property not a column, as comments don't have titles. + # That makes this return value incorrect, but here we ignore the error as + # class:`CommentSearch` explicitly overrides :meth:`hltitle_column`, and that is + # the only place this property is accessed + return self.model.title # type: ignore[return-value] @property def hltext(self) -> sa.ColumnElement[str]: diff --git a/migrations/script.py.mako b/migrations/script.py.mako index af188f4d1..88b3d025d 100644 --- a/migrations/script.py.mako +++ b/migrations/script.py.mako @@ -9,8 +9,6 @@ Create Date: ${create_date} """ -from typing import Optional, Tuple, Union - from alembic import op import sqlalchemy as sa ${imports if imports else ""} @@ -18,8 +16,8 @@ ${imports if imports else ""} # revision identifiers, used by Alembic. revision: str = ${repr(up_revision)} down_revision: str = ${repr(down_revision)} -branch_labels: Optional[Union[str, Tuple[str, ...]]] = ${repr(branch_labels)} -depends_on: Optional[Union[str, Tuple[str, ...]]] = ${repr(depends_on)} +branch_labels: str | tuple[str, ...] | None = ${repr(branch_labels)} +depends_on: str, tuple[str, ...] | None = ${repr(depends_on)} def upgrade(engine_name: str = '') -> None: diff --git a/tests/conftest.py b/tests/conftest.py index f5055d380..fc4a58083 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,13 +5,14 @@ import re import time -import typing as t import warnings +from collections.abc import Callable, Iterator from contextlib import ExitStack from dataclasses import dataclass from datetime import datetime, timezone from difflib import unified_diff from types import MethodType, ModuleType, SimpleNamespace +from typing import TYPE_CHECKING, Any, NamedTuple, get_type_hints from unittest.mock import patch import flask_wtf.csrf @@ -23,7 +24,7 @@ from flask_sqlalchemy.session import Session as FsaSession from sqlalchemy.orm import Session as DatabaseSessionClass -if t.TYPE_CHECKING: +if TYPE_CHECKING: from flask import Flask from flask.testing import FlaskClient, TestResponse from rich.console import Console @@ -60,7 +61,7 @@ def firefox_options(firefox_options): return firefox_options -def pytest_collection_modifyitems(items: t.List[pytest.Function]) -> None: +def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: """Sort tests to run lower level before higher level.""" test_order = ( 'tests/unit/models', @@ -79,7 +80,7 @@ def pytest_collection_modifyitems(items: t.List[pytest.Function]) -> None: 'tests/features', ) - def sort_key(item: pytest.Function) -> t.Tuple[int, str]: + def sort_key(item: pytest.Function) -> tuple[int, str]: # pytest.Function's base class pytest.Item reports the file containing the test # as item.location == (file_path, line_no, function_name). However, pytest-bdd # reports itself for file_path, so we can't use that and must extract the path @@ -96,10 +97,10 @@ def sort_key(item: pytest.Function) -> t.Tuple[int, str]: # Adapted from https://github.com/untitaker/pytest-fixture-typecheck def pytest_runtest_call(item: pytest.Function) -> None: try: - annotations = t.get_type_hints( + annotations = get_type_hints( item.obj, globalns=item.obj.__globals__, - localns={'Any': t.Any}, # pytest-bdd appears to insert an `Any` annotation + localns={'Any': Any}, # pytest-bdd appears to insert an `Any` annotation ) except TypeError: # get_type_hints may fail on Python <3.10 because pytest-bdd appears to have @@ -154,7 +155,7 @@ def funnel_devtest() -> ModuleType: @pytest.fixture(scope='session') -def response_with_forms() -> t.Any: # Since the actual return type is defined within +def response_with_forms() -> Any: # Since the actual return type is defined within from flask.wrappers import Response from lxml.html import FormElement, HtmlElement, fromstring # nosec @@ -175,11 +176,11 @@ def response_with_forms() -> t.Any: # Since the actual return type is defined w re.ASCII | re.IGNORECASE | re.VERBOSE, ) - class MetaRefreshContent(t.NamedTuple): + class MetaRefreshContent(NamedTuple): """Timeout and optional URL in a Meta Refresh tag.""" timeout: int - url: t.Optional[str] = None + url: str | None = None class ResponseWithForms(Response): """ @@ -196,7 +197,7 @@ def test_mytest(client) -> None: next_response = form.submit(client) """ - _parsed_html: t.Optional[HtmlElement] = None + _parsed_html: HtmlElement | None = None @property def html(self) -> HtmlElement: @@ -206,7 +207,7 @@ def html(self) -> HtmlElement: # add click method to all links def _click( - self: HtmlElement, client: FlaskClient, **kwargs: t.Any + self: HtmlElement, client: FlaskClient, **kwargs: Any ) -> TestResponse: # `self` is the `a` element here path = self.attrib['href'] @@ -219,8 +220,8 @@ def _click( def _submit( self: FormElement, client: FlaskClient, - path: t.Optional[str] = None, - **kwargs: t.Any, + path: str | None = None, + **kwargs: Any, ) -> TestResponse: # `self` is the `form` element here data = dict(self.form_values()) @@ -238,7 +239,7 @@ def _submit( return self._parsed_html @property - def forms(self) -> t.List[FormElement]: + def forms(self) -> list[FormElement]: """ Return list of all forms in the document. @@ -249,8 +250,8 @@ def forms(self) -> t.List[FormElement]: return self.html.forms def form( - self, id_: t.Optional[str] = None, name: t.Optional[str] = None - ) -> t.Optional[FormElement]: + self, id_: str | None = None, name: str | None = None + ) -> FormElement | None: """Return the first form matching given id or name in the document.""" if id_: forms = self.html.cssselect(f'form#{id_}') @@ -262,11 +263,11 @@ def form( return forms[0] return None - def links(self, selector: str = 'a') -> t.List[HtmlElement]: + def links(self, selector: str = 'a') -> list[HtmlElement]: """Get all the links matching the given CSS selector.""" return self.html.cssselect(selector) - def link(self, selector: str = 'a') -> t.Optional[HtmlElement]: + def link(self, selector: str = 'a') -> HtmlElement | None: """Get first link matching the given CSS selector.""" links = self.links(selector) if links: @@ -274,7 +275,7 @@ def link(self, selector: str = 'a') -> t.Optional[HtmlElement]: return None @property - def metarefresh(self) -> t.Optional[MetaRefreshContent]: + def metarefresh(self) -> MetaRefreshContent | None: """Get content of Meta Refresh tag if present.""" meta_elements = self.html.cssselect('meta[http-equiv="refresh"]') if not meta_elements: @@ -299,7 +300,7 @@ def rich_console() -> Console: @pytest.fixture(scope='session') -def colorama() -> t.Iterator[SimpleNamespace]: +def colorama() -> Iterator[SimpleNamespace]: """Provide the colorama print colorizer.""" from colorama import Back, Fore, Style, deinit, init @@ -309,10 +310,10 @@ def colorama() -> t.Iterator[SimpleNamespace]: @pytest.fixture(scope='session') -def colorize_code(rich_console: Console) -> t.Callable[[str, t.Optional[str]], str]: +def colorize_code(rich_console: Console) -> Callable[[str, str | None], str]: """Return colorized output for a string of code, for current terminal's colors.""" - def no_colorize(code_string: str, lang: t.Optional[str] = 'python') -> str: + def no_colorize(code_string: str, lang: str | None = 'python') -> str: # Pygments is not available or terminal does not support colour output return code_string @@ -337,7 +338,7 @@ def no_colorize(code_string: str, lang: t.Optional[str] = 'python') -> str: # color_system is `None` or `'windows'` or something unrecognised. No colours. return no_colorize - def colorize(code_string: str, lang: t.Optional[str] = 'python') -> str: + def colorize(code_string: str, lang: str | None = 'python') -> str: if lang in (None, 'auto'): lexer = guess_lexer(code_string) else: @@ -348,7 +349,7 @@ def colorize(code_string: str, lang: t.Optional[str] = 'python') -> str: @pytest.fixture(scope='session') -def print_stack(pytestconfig, colorama, colorize_code) -> t.Callable[[int, int], None]: +def print_stack(pytestconfig, colorama, colorize_code) -> Callable[[int, int], None]: """Print a stack trace up to an outbound call from within this repository.""" import os.path from inspect import stack as inspect_stack @@ -419,20 +420,20 @@ def unsubscribeapp(funnel) -> Flask: @pytest.fixture() -def app_context(app) -> t.Iterator: +def app_context(app) -> Iterator: """Create an app context for the test.""" with app.app_context() as ctx: yield ctx @pytest.fixture() -def request_context(app) -> t.Iterator: +def request_context(app) -> Iterator: """Create a request context with default values for the test.""" with app.test_request_context() as ctx: yield ctx -config_test_keys: t.Dict[str, t.Set[str]] = { +config_test_keys: dict[str, set[str]] = { 'recaptcha': {'RECAPTCHA_PUBLIC_KEY', 'RECAPTCHA_PRIVATE_KEY'}, 'twilio': {'SMS_TWILIO_SID', 'SMS_TWILIO_TOKEN'}, 'exotel': {'SMS_EXOTEL_SID', 'SMS_EXOTEL_TOKEN'}, @@ -459,11 +460,11 @@ def request_context(app) -> t.Iterator: @pytest.fixture(autouse=True) -def _mock_config(request: pytest.FixtureRequest) -> t.Iterator: +def _mock_config(request: pytest.FixtureRequest) -> Iterator: """Mock app config (using ``mock_config`` mark).""" def backup_and_apply_config( - app_name: str, app_fixture: Flask, saved_config: dict, key: str, value: t.Any + app_name: str, app_fixture: Flask, saved_config: dict, key: str, value: Any ) -> None: if key in saved_config: pytest.fail(f"Duplicate mock for {app_name}.config[{key!r}]") @@ -479,7 +480,7 @@ def backup_and_apply_config( app_fixture.config[key] = value if request.node.get_closest_marker('mock_config'): - saved_app_config: t.Dict[str, t.Any] = {} + saved_app_config: dict[str, Any] = {} for mark in request.node.iter_markers('mock_config'): if len(mark.args) < 1: pytest.fail(_mock_config_syntax) @@ -534,7 +535,7 @@ def _requires_config(request: pytest.FixtureRequest) -> None: @pytest.fixture(scope='session') -def _app_events(colorama, print_stack, app) -> t.Iterator: +def _app_events(colorama, print_stack, app) -> Iterator: """Fixture to report Flask signals with a stack trace when debugging a test.""" from functools import partial @@ -572,7 +573,7 @@ def signal_handler(signal_name, *args, **kwargs): @pytest.fixture() -def _database_events(models, colorama, colorize_code, print_stack) -> t.Iterator: +def _database_events(models, colorama, colorize_code, print_stack) -> Iterator: """ Fixture to report database session events for debugging a test. @@ -911,7 +912,7 @@ def drop_tables(): @pytest.fixture() def db_session_truncate( funnel, app, database, app_context -) -> t.Iterator[DatabaseSessionClass]: +) -> Iterator[DatabaseSessionClass]: """Empty the database after each use of the fixture.""" yield database.session sa.orm.close_all_sessions() @@ -927,27 +928,27 @@ def db_session_truncate( @dataclass class BindConnectionTransaction: engine: sa.engine.Engine - connection: t.Any - transaction: t.Any + connection: Any + transaction: Any class BoundSession(FsaSession): def __init__( self, db: SQLAlchemy, - bindcts: t.Dict[t.Optional[str], BindConnectionTransaction], - **kwargs: t.Any, + bindcts: dict[str | None, BindConnectionTransaction], + **kwargs: Any, ) -> None: super().__init__(db, **kwargs) self.bindcts = bindcts def get_bind( self, - mapper: t.Optional[t.Any] = None, - clause: t.Optional[t.Any] = None, - bind: t.Optional[t.Union[sa.engine.Engine, sa.engine.Connection]] = None, - **kwargs: t.Any, - ) -> t.Union[sa.engine.Engine, sa.engine.Connection]: + mapper: Any | None = None, + clause: Any | None = None, + bind: sa.engine.Engine | sa.engine.Connection | None = None, + **kwargs: Any, + ) -> sa.engine.Engine | sa.engine.Connection: if bind is not None: return bind if mapper is not None: @@ -964,11 +965,11 @@ def get_bind( @pytest.fixture() def db_session_rollback( funnel, app, database, app_context -) -> t.Iterator[DatabaseSessionClass]: +) -> Iterator[DatabaseSessionClass]: """Create a nested transaction for the test and rollback after.""" original_session = database.session - bindcts: t.Dict[t.Optional[str], BindConnectionTransaction] = {} + bindcts: dict[str | None, BindConnectionTransaction] = {} for bind, engine in database.engines.items(): connection = engine.connect() transaction = connection.begin() @@ -1152,7 +1153,7 @@ def logout() -> None: @pytest.fixture() -def getuser(request) -> t.Callable[[str], funnel_models.User]: +def getuser(request) -> Callable[[str], funnel_models.User]: """Get a user fixture by their name.""" usermap = { "Twoflower": 'user_twoflower', diff --git a/tests/integration/views/conftest.py b/tests/integration/views/conftest.py index 843909c00..979f9bb36 100644 --- a/tests/integration/views/conftest.py +++ b/tests/integration/views/conftest.py @@ -2,11 +2,11 @@ from __future__ import annotations -import typing as t +from typing import TYPE_CHECKING from pytest_bdd import given, parsers, when -if t.TYPE_CHECKING: +if TYPE_CHECKING: from funnel import models