From 24e13625ef82be7e2fca891e6e85cbef06b28dbd Mon Sep 17 00:00:00 2001 From: Arne Beer Date: Tue, 16 Jan 2024 16:18:27 +0100 Subject: [PATCH] chore: Use ruff and fix lints --- .vim/settings.json | 21 -- Justfile | 19 +- poetry.lock | 189 +++--------------- pollbot/display/misc.py | 6 +- pollbot/display/poll/compilation.py | 10 +- pollbot/display/poll/indices.py | 3 +- pollbot/display/poll/option.py | 6 +- pollbot/display/poll/priority_vote_results.py | 9 +- pollbot/display/poll/vote.py | 16 +- pollbot/enums.py | 1 - pollbot/helper/plot.py | 2 +- pollbot/helper/text.py | 5 +- pollbot/models/option.py | 3 +- pollbot/models/poll.py | 5 +- pollbot/models/reference.py | 4 +- pollbot/models/update.py | 4 +- pollbot/models/vote.py | 4 +- pollbot/poll/creation.py | 3 +- pollbot/poll/option.py | 18 +- pollbot/poll/update.py | 7 +- pollbot/poll/vote.py | 7 +- pollbot/sentry.py | 2 +- pollbot/telegram/callback_handler/creation.py | 3 +- .../telegram/callback_handler/datepicker.py | 8 +- pollbot/telegram/callback_handler/external.py | 5 +- .../telegram/callback_handler/management.py | 8 +- pollbot/telegram/callback_handler/menu.py | 1 - pollbot/telegram/callback_handler/styling.py | 9 - pollbot/telegram/callback_handler/vote.py | 3 +- pollbot/telegram/commands/admin.py | 4 +- pollbot/telegram/commands/external.py | 1 - pollbot/telegram/commands/poll.py | 3 +- pollbot/telegram/commands/start.py | 5 +- pollbot/telegram/keyboard/date_picker.py | 6 +- pollbot/telegram/keyboard/external.py | 3 +- pollbot/telegram/keyboard/management.py | 3 +- pollbot/telegram/keyboard/misc.py | 3 +- pollbot/telegram/keyboard/vote.py | 18 +- pollbot/telegram/message_handler.py | 7 +- pollbot/telegram/native_poll_handler.py | 3 +- pollbot/telegram/session.py | 6 +- pyproject.toml | 50 ++++- setup.cfg | 4 - 43 files changed, 154 insertions(+), 343 deletions(-) delete mode 100644 .vim/settings.json delete mode 100644 setup.cfg diff --git a/.vim/settings.json b/.vim/settings.json deleted file mode 100644 index daa66af8..00000000 --- a/.vim/settings.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "pyls": { - "plugins": { - "pyflakes": { - "enabled": true - }, - "pydocstyle": { - "enabled": false - }, - "pylint": { - "enabled": false - }, - "pylint": { - "enabled": false - }, - "yapf": { - "enabled": false - } - } - } -} diff --git a/Justfile b/Justfile index ed5689b2..66ee2ee0 100644 --- a/Justfile +++ b/Justfile @@ -19,23 +19,12 @@ test: poetry run pytest lint: - poetry run black --check pollbot - poetry run isort \ - --skip __init__.py \ - --check-only pollbot - poetry run flake8 pollbot + poetry run ruff check ./pollbot --show-source + poetry run ruff format ./pollbot --diff format: - # remove unused imports - poetry run autoflake \ - --remove-all-unused-imports \ - --recursive \ - --exclude=__init__.py,.venv \ - --in-place pollbot - poetry run black pollbot - poetry run isort pollbot \ - --skip __init__.py - + poetry run ruff check --fix ./pollbot + poetry run ruff format ./pollbot # Watch for something # E.g. `just watch lint` or `just watch test` diff --git a/poetry.lock b/poetry.lock index 164a0459..2b04858b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -60,67 +60,6 @@ files = [ {file = "argparse-1.4.0.tar.gz", hash = "sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4"}, ] -[[package]] -name = "autoflake" -version = "1.7.8" -description = "Removes unused imports and unused variables" -optional = false -python-versions = ">=3.7" -files = [ - {file = "autoflake-1.7.8-py3-none-any.whl", hash = "sha256:46373ef69b6714f5064c923bb28bd797c4f8a9497f557d87fc36665c6d956b39"}, - {file = "autoflake-1.7.8.tar.gz", hash = "sha256:e7e46372dee46fa1c97acf310d99d922b63d369718a270809d7c278d34a194cf"}, -] - -[package.dependencies] -pyflakes = ">=1.1.0,<3" -tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} - -[[package]] -name = "black" -version = "24.1a1" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.1a1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3d139b9531e6bb6d129497a46475535d8289dddc861a5b980f908c36597b9817"}, - {file = "black-24.1a1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2220c470c22476ca9631337b0daae41be2b215599919b19d576a956ad38aca69"}, - {file = "black-24.1a1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a12829e372563ffff10c18c7aff1ef274da6afbc7bc8ccdb5fcc8ff84cab43f"}, - {file = "black-24.1a1-cp310-cp310-win_amd64.whl", hash = "sha256:d47b6530c55c092a9d841a12c8b3ad838bd639bebf6660a3df9dae83d4ab83c1"}, - {file = "black-24.1a1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b594b3ede60182215d258c76de2de64712d2e8424442ff4402276e22684abbe"}, - {file = "black-24.1a1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915a6b6b916fc66edec886fc71b60284e447d8fa39d22b879af7ae6efccca90f"}, - {file = "black-24.1a1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb0a7ea9aa1c108924e31f1204a1e2534af255dbaa24ecbb8c05f47341a7b6f1"}, - {file = "black-24.1a1-cp311-cp311-win_amd64.whl", hash = "sha256:41c0ce5cbdb701900c166bcca08ac941b64cf1d6967509e3caeab126da0ae0d0"}, - {file = "black-24.1a1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c8165fad00b03d9c1d400b1dd250479792f49d012807ee45162d323d04fc06"}, - {file = "black-24.1a1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6e3c74b35ea179bb69440286b81c309a64c34a032746a9eef3399dc3ce671352"}, - {file = "black-24.1a1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30a018fc03fd1e83c75d40b8a156ef541d0b56b6403b63754e1cc96889849d9"}, - {file = "black-24.1a1-cp312-cp312-win_amd64.whl", hash = "sha256:88d1c60bac2044a409154e895abb9d74c8ff5d034fb70f3e1f7c3ae96206bc0c"}, - {file = "black-24.1a1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4de8ba5825588017f90e63d7a25fc4df33a6342d1f4d628ad76130d8f4488fc6"}, - {file = "black-24.1a1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c86ecd9d3da3d91e96da5f4a43d9c4fe35c5698b0633e91f171ba9468d112a8b"}, - {file = "black-24.1a1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:623efdb54e7290ba75f7b822dfd2d8a47a55e721ae63aab671ccfd46b2ba6c5d"}, - {file = "black-24.1a1-cp38-cp38-win_amd64.whl", hash = "sha256:ec345caf15ae2c61540812500979e92f2989c6b6d4d13d21bdc82908043b3265"}, - {file = "black-24.1a1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ac226f37fc429b386d6447df6256dc958c28dd602f86f950072febf886995f80"}, - {file = "black-24.1a1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cad114d8673adab76b3602c28c461c613b7be3da28415500e42aed47415eb561"}, - {file = "black-24.1a1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a054dbb8947718820be2ed6953d66b912ec2795f282725efdd08381a11b0d0"}, - {file = "black-24.1a1-cp39-cp39-win_amd64.whl", hash = "sha256:b03cdf8a4e15929adf47e5e40a0ddeea1d63b65cf59c22553c12417a0c7ccbf4"}, - {file = "black-24.1a1-py3-none-any.whl", hash = "sha256:a2c977909557439d0f17dc82adaea84e48374950d53416efc0b8451a594d42c3"}, - {file = "black-24.1a1.tar.gz", hash = "sha256:4a159ae57f239f3f1ef6a78784b00c1c617c7bb188cc351b3017b9e0702df11c"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "cachetools" version = "4.2.2" @@ -260,22 +199,6 @@ files = [ [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "flake8" -version = "5.0.4" -description = "the modular source code checker: pep8 pyflakes and co" -optional = false -python-versions = ">=3.6.1" -files = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, -] - -[package.dependencies] -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" - [[package]] name = "fonttools" version = "4.47.2" @@ -423,20 +346,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - [[package]] name = "kiwisolver" version = "1.4.5" @@ -676,28 +585,6 @@ pillow = ">=8" pyparsing = ">=2.3.1" python-dateutil = ">=2.7" -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - [[package]] name = "numpy" version = "1.26.3" @@ -801,17 +688,6 @@ pytz = ">=2020.1" [package.extras] test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - [[package]] name = "pillow" version = "9.5.0" @@ -891,21 +767,6 @@ files = [ docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -[[package]] -name = "platformdirs" -version = "4.1.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, -] - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] - [[package]] name = "pluggy" version = "1.3.0" @@ -1002,28 +863,6 @@ files = [ {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, ] -[[package]] -name = "pycodestyle" -version = "2.9.1" -description = "Python style guide checker" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, -] - -[[package]] -name = "pyflakes" -version = "2.5.0" -description = "passive checker of Python programs" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, -] - [[package]] name = "pyparsing" version = "3.1.1" @@ -1261,6 +1100,32 @@ files = [ {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, ] +[[package]] +name = "ruff" +version = "0.1.13" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e3fd36e0d48aeac672aa850045e784673449ce619afc12823ea7868fcc41d8ba"}, + {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9fb6b3b86450d4ec6a6732f9f60c4406061b6851c4b29f944f8c9d91c3611c7a"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b13ba5d7156daaf3fd08b6b993360a96060500aca7e307d95ecbc5bb47a69296"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ebb40442f7b531e136d334ef0851412410061e65d61ca8ce90d894a094feb22"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226b517f42d59a543d6383cfe03cccf0091e3e0ed1b856c6824be03d2a75d3b6"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f0312ba1061e9b8c724e9a702d3c8621e3c6e6c2c9bd862550ab2951ac75c16"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f59bcf5217c661254bd6bc42d65a6fd1a8b80c48763cb5c2293295babd945dd"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6894b00495e00c27b6ba61af1fc666f17de6140345e5ef27dd6e08fb987259d"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1600942485c6e66119da294c6294856b5c86fd6df591ce293e4a4cc8e72989"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ee3febce7863e231a467f90e681d3d89210b900d49ce88723ce052c8761be8c7"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dcaab50e278ff497ee4d1fe69b29ca0a9a47cd954bb17963628fa417933c6eb1"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f57de973de4edef3ad3044d6a50c02ad9fc2dff0d88587f25f1a48e3f72edf5e"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a36fa90eb12208272a858475ec43ac811ac37e91ef868759770b71bdabe27b6"}, + {file = "ruff-0.1.13-py3-none-win32.whl", hash = "sha256:a623349a505ff768dad6bd57087e2461be8db58305ebd5577bd0e98631f9ae69"}, + {file = "ruff-0.1.13-py3-none-win_amd64.whl", hash = "sha256:f988746e3c3982bea7f824c8fa318ce7f538c4dfefec99cd09c8770bd33e6539"}, + {file = "ruff-0.1.13-py3-none-win_arm64.whl", hash = "sha256:6bbbc3042075871ec17f28864808540a26f0f79a4478c357d3e3d2284e832998"}, + {file = "ruff-0.1.13.tar.gz", hash = "sha256:e261f1baed6291f434ffb1d5c6bd8051d1c2a26958072d38dfbec39b3dda7352"}, +] + [[package]] name = "sentry-sdk" version = "1.39.2" @@ -1601,4 +1466,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "4a8aaffb941316886f507b486399e569b465ca122ee24c037b36f8d040af35d1" +content-hash = "10fbe8543c2fd53d1515b01baa4491636858d5a34576f5b3a6b0c4a05c22f37b" diff --git a/pollbot/display/misc.py b/pollbot/display/misc.py index 03697250..20a15095 100644 --- a/pollbot/display/misc.py +++ b/pollbot/display/misc.py @@ -1,6 +1,4 @@ """Display helper for misc stuff.""" -from typing import Tuple, Union - from sqlalchemy.orm.scoping import scoped_session from telegram.inline.inlinekeyboardmarkup import InlineKeyboardMarkup @@ -13,7 +11,7 @@ def get_help_text_and_keyboard( user: User, current_category: str -) -> Tuple[str, InlineKeyboardMarkup]: +) -> tuple[str, InlineKeyboardMarkup]: """Create the help message depending on the currently selected help category.""" categories = [ "creation", @@ -32,7 +30,7 @@ def get_help_text_and_keyboard( def get_poll_list( session: scoped_session, user: User, offset: int, closed: bool = False -) -> Union[Tuple[str, InlineKeyboardMarkup], Tuple[str, None]]: +) -> tuple[str, InlineKeyboardMarkup] | tuple[str, None]: """Get the a list of polls for the user.""" polls = ( session.query(Poll) diff --git a/pollbot/display/poll/compilation.py b/pollbot/display/poll/compilation.py index 4dec45b4..11d47ad0 100644 --- a/pollbot/display/poll/compilation.py +++ b/pollbot/display/poll/compilation.py @@ -1,5 +1,3 @@ -from typing import List, Optional, Tuple - from sqlalchemy.orm.scoping import scoped_session from telegram.inline.inlinekeyboardmarkup import InlineKeyboardMarkup @@ -16,9 +14,9 @@ def get_poll_text_and_vote_keyboard( session: scoped_session, poll: Poll, - user: Optional[User] = None, + user: User | None = None, show_back: bool = False, -) -> Tuple[str, InlineKeyboardMarkup]: +) -> tuple[str, InlineKeyboardMarkup]: """Get the text and the vote keyboard.""" text, summarize = get_poll_text_and_summarize( session, @@ -38,7 +36,7 @@ def get_poll_text(session: scoped_session, poll: Poll) -> str: def get_poll_text_and_summarize( session: scoped_session, poll: Poll -) -> Tuple[str, bool]: +) -> tuple[str, bool]: """Get the poll text and vote keyboard.""" summarize = poll.permanently_summarized or poll.summarize @@ -70,7 +68,7 @@ def get_poll_text_and_summarize( def compile_poll_text( session: scoped_session, poll: Poll, summarize: bool = False -) -> List[str]: +) -> list[str]: """Create the text of the poll.""" context = Context(session, poll) diff --git a/pollbot/display/poll/indices.py b/pollbot/display/poll/indices.py index 74c537e7..d7ef9cf4 100644 --- a/pollbot/display/poll/indices.py +++ b/pollbot/display/poll/indices.py @@ -1,12 +1,11 @@ import string -from typing import List, Union from sqlalchemy.orm.collections import InstrumentedList from pollbot.models.option import Option -def get_option_indices(options: Union[List[Option], InstrumentedList]) -> List[str]: +def get_option_indices(options: list[Option] | InstrumentedList) -> list[str]: """Simple helper to dynamically create indices/letters for option displaying.""" indices = list(string.ascii_lowercase) if len(options) > len(indices): diff --git a/pollbot/display/poll/option.py b/pollbot/display/poll/option.py index f557a257..04a3899d 100644 --- a/pollbot/display/poll/option.py +++ b/pollbot/display/poll/option.py @@ -1,6 +1,6 @@ """Poll text compilation for options.""" import math -from typing import Any, List, Union +from typing import Any from sqlalchemy.orm.scoping import scoped_session from telegram.chat import Chat @@ -18,7 +18,7 @@ from .vote import get_doodle_vote_lines, get_vote_lines -def next_option(tg_chat: Chat, poll: Poll, added_options: List[str]) -> None: +def next_option(tg_chat: Chat, poll: Poll, added_options: list[str]) -> None: """Send the options message during the creation. This function also has a failsafe in it, that rollbacks the entire transaction, @@ -47,7 +47,7 @@ def next_option(tg_chat: Chat, poll: Poll, added_options: List[str]) -> None: def get_option_information( session: scoped_session, poll: Poll, context: Context, summarize: bool -) -> List[Union[Any, str]]: +) -> list[Any | str]: """Compile all information about a poll option.""" lines = [] # Sort the options accordingly to the polls settings diff --git a/pollbot/display/poll/priority_vote_results.py b/pollbot/display/poll/priority_vote_results.py index c57f8125..9ac23efa 100644 --- a/pollbot/display/poll/priority_vote_results.py +++ b/pollbot/display/poll/priority_vote_results.py @@ -1,5 +1,4 @@ from collections import Counter -from typing import List, Tuple from sqlalchemy.orm.scoping import scoped_session @@ -7,7 +6,7 @@ # this is not used at the moment, but maybe we'd like to add this feature later -def get_priority_result(session: scoped_session, poll: Poll) -> List[str]: +def get_priority_result(session: scoped_session, poll: Poll) -> list[str]: # todo this query fetches all votes for the user, not only those belonging to the current poll users = session.query(User).join(User.votes).filter(Vote.poll_id == poll.id).all() @@ -31,7 +30,7 @@ def get_priority_result(session: scoped_session, poll: Poll) -> List[str]: names = ", ".join( [ session.query(Option).get(id).name - for id in options_with_same_rank + [last_id] + for id in [*options_with_same_rank, last_id] ] ) lines.append( @@ -45,8 +44,8 @@ def get_priority_result(session: scoped_session, poll: Poll) -> List[str]: def get_ranked_options( - option_ids: List[int], users: List[User] -) -> List[Tuple[int, int]]: + option_ids: list[int], users: list[User] +) -> list[tuple[int, int]]: option_votes = Counter({id: 0 for id in option_ids}) for user in users: for vote in sorted(user.votes, key=lambda vote: vote.priority): diff --git a/pollbot/display/poll/vote.py b/pollbot/display/poll/vote.py index ba04c281..82fb0a10 100644 --- a/pollbot/display/poll/vote.py +++ b/pollbot/display/poll/vote.py @@ -1,5 +1,5 @@ """Compilation of vote texts for each option.""" -from typing import Any, List, Optional, Union +from typing import Any from sqlalchemy import func from sqlalchemy.orm.scoping import scoped_session @@ -16,7 +16,7 @@ from pollbot.poll.vote import get_sorted_doodle_votes, get_sorted_votes -def get_doodle_vote_lines(poll: Poll, option: Option, summarize: bool) -> List[str]: +def get_doodle_vote_lines(poll: Poll, option: Option, summarize: bool) -> list[str]: """Return all vote related lines for this option.""" lines = [] votes_by_answer = get_sorted_doodle_votes(poll, option.votes) @@ -51,8 +51,8 @@ def get_doodle_vote_lines(poll: Poll, option: Option, summarize: bool) -> List[s def get_doodle_answer_lines( - votes: List[Vote], summarize: bool, is_last: bool -) -> List[str]: + votes: list[Vote], summarize: bool, is_last: bool +) -> list[str]: """Return the user names for a doodle answer. Try to compress as many usernames as possible into a single line. @@ -96,7 +96,7 @@ def get_doodle_answer_lines( return lines -def get_vote_lines(poll: Poll, option: Option, summarize: bool) -> List[str]: +def get_vote_lines(poll: Poll, option: Option, summarize: bool) -> list[str]: """Return all vote related lines for this option.""" lines = [] threshold = 2 @@ -135,7 +135,7 @@ def get_vote_line(poll: Poll, option: Option, vote: Vote, index: int) -> str: return vote_line -def get_vote_information_line(poll: Poll, context: Context) -> Optional[str]: +def get_vote_information_line(poll: Poll, context: Context) -> str | None: """Get line that shows information about total user votes.""" vote_information = None if context.total_user_count > 1: @@ -154,9 +154,7 @@ def get_vote_information_line(poll: Poll, context: Context) -> Optional[str]: return vote_information -def get_remaining_votes_lines( - session: scoped_session, poll: Poll -) -> List[Union[str, Any]]: +def get_remaining_votes_lines(session: scoped_session, poll: Poll) -> list[str | Any]: """Get the remaining votes for a poll.""" user_vote_count = func.sum(Vote.vote_count).label("user_vote_count") remaining_user_votes = ( diff --git a/pollbot/enums.py b/pollbot/enums.py index 2741648e..ba57e590 100644 --- a/pollbot/enums.py +++ b/pollbot/enums.py @@ -4,7 +4,6 @@ @unique class PollDeletionMode(Enum): - DB_ONLY = 1 WITH_MESSAGES = 2 diff --git a/pollbot/helper/plot.py b/pollbot/helper/plot.py index 6da6fd34..8c86a1e8 100644 --- a/pollbot/helper/plot.py +++ b/pollbot/helper/plot.py @@ -6,7 +6,7 @@ import numpy as np import pandas from matplotlib import dates as mdates -from matplotlib import pyplot as plt # noqa +from matplotlib import pyplot as plt from sqlalchemy import Date, Integer, cast, func from pollbot.models import DailyStatistic, User, Vote diff --git a/pollbot/helper/text.py b/pollbot/helper/text.py index 691008aa..a2efd580 100644 --- a/pollbot/helper/text.py +++ b/pollbot/helper/text.py @@ -1,7 +1,4 @@ -from typing import List - - -def split_text(lines: List[str]) -> List[List[str]]: +def split_text(lines: list[str]) -> list[list[str]]: """Split a text devided by newline into chunks of about 4000 characters.""" chunks = [] current_chunk = [] diff --git a/pollbot/models/option.py b/pollbot/models/option.py index 43db443b..96d5958f 100644 --- a/pollbot/models/option.py +++ b/pollbot/models/option.py @@ -2,7 +2,6 @@ from __future__ import annotations from datetime import date -from typing import Optional from sqlalchemy import Column, ForeignKey, UniqueConstraint, func from sqlalchemy.orm import relationship @@ -70,7 +69,7 @@ def get_formatted_name(self) -> str: return self.name - def as_date(self) -> Optional[date]: + def as_date(self) -> date | None: """Either return the option as date or None.""" if not self.is_date: return None diff --git a/pollbot/models/poll.py b/pollbot/models/poll.py index b882a813..addb60db 100644 --- a/pollbot/models/poll.py +++ b/pollbot/models/poll.py @@ -2,7 +2,6 @@ from __future__ import annotations from datetime import date, datetime, timedelta -from typing import Optional from sqlalchemy import Column, ForeignKey, func, text from sqlalchemy.dialects.postgresql import UUID @@ -142,7 +141,7 @@ def has_date_option(self) -> bool: return True return False - def get_date_option(self, check_date: date) -> Optional[Option]: + def get_date_option(self, check_date: date) -> Option | None: """Return whether an option with this date already exists.""" for option in self.options: if option.is_date and option.as_date() == check_date: @@ -156,7 +155,7 @@ def get_formatted_due_date(self) -> str: return self.due_date.strftime("%Y-%m-%d %H:%M UTC") - def set_due_date(self, date: Optional[datetime]) -> None: + def set_due_date(self, date: datetime | None) -> None: """Set the due date and the next notification.""" if date is None: self.due_date = None diff --git a/pollbot/models/reference.py b/pollbot/models/reference.py index f71feb94..8cc8a205 100644 --- a/pollbot/models/reference.py +++ b/pollbot/models/reference.py @@ -1,6 +1,8 @@ """The sqlalchemy model for a polloption.""" from __future__ import annotations +from typing import Any, ClassVar + from sqlalchemy import Column, ForeignKey, Index, func from sqlalchemy.orm import relationship from sqlalchemy.types import BigInteger, DateTime, Integer, String @@ -13,7 +15,7 @@ class Reference(base): """The model for a Reference.""" __tablename__ = "reference" - __mapper_args__ = {"confirm_deleted_rows": False} + __mapper_args__: ClassVar[dict[str, Any]] = {"confirm_deleted_rows": False} id = Column(Integer, primary_key=True) type = Column(String) diff --git a/pollbot/models/update.py b/pollbot/models/update.py index c22b50f4..f2a9537e 100644 --- a/pollbot/models/update.py +++ b/pollbot/models/update.py @@ -1,6 +1,8 @@ """The sqlalchemy model for a vote.""" from __future__ import annotations +from typing import Any, ClassVar + from sqlalchemy import Column, ForeignKey, UniqueConstraint from sqlalchemy.orm import relationship from sqlalchemy.types import DateTime, Integer @@ -13,7 +15,7 @@ class Update(base): __tablename__ = "update" __table_args__ = (UniqueConstraint("poll_id", name="one_update_per_poll"),) - __mapper_args__ = {"confirm_deleted_rows": False} + __mapper_args__: ClassVar[dict[str, Any]] = {"confirm_deleted_rows": False} id = Column(Integer, primary_key=True) next_update = Column(DateTime, nullable=False) diff --git a/pollbot/models/vote.py b/pollbot/models/vote.py index 0dc3310d..6621e91f 100644 --- a/pollbot/models/vote.py +++ b/pollbot/models/vote.py @@ -1,6 +1,8 @@ """The sqlalchemy model for a vote.""" from __future__ import annotations +from typing import Any, ClassVar + from sqlalchemy import Column, ForeignKey, Index, UniqueConstraint, func from sqlalchemy.orm import relationship from sqlalchemy.types import BigInteger, DateTime, Integer, String @@ -17,7 +19,7 @@ class Vote(base): "user_id", "poll_id", "option_id", name="one_vote_per_option_and_user" ), ) - __mapper_args__ = {"confirm_deleted_rows": False} + __mapper_args__: ClassVar[dict[str, Any]] = {"confirm_deleted_rows": False} id = Column(Integer, primary_key=True) type = Column(String, nullable=True) diff --git a/pollbot/poll/creation.py b/pollbot/poll/creation.py index 2f8fdbbb..e650b368 100644 --- a/pollbot/poll/creation.py +++ b/pollbot/poll/creation.py @@ -1,5 +1,4 @@ """Poll creation helper.""" -from typing import Optional from sqlalchemy.orm.scoping import scoped_session from telegram.chat import Chat @@ -61,7 +60,7 @@ def create_poll( poll: Poll, user: User, chat: Chat, - message: Optional[Message] = None, + message: Message | None = None, ) -> None: """Finish the poll creation.""" poll.created = True diff --git a/pollbot/poll/option.py b/pollbot/poll/option.py index bb15ae17..2f19b9b8 100644 --- a/pollbot/poll/option.py +++ b/pollbot/poll/option.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional, Union +from typing import Any from sqlalchemy.orm.scoping import scoped_session @@ -26,7 +26,7 @@ def add_single_option( def add_options_multiline( session: scoped_session, poll: Poll, text: str, is_date: bool = False -) -> List[Union[Any, str]]: +) -> list[Any | str]: """Add one or multiple new options to the poll from a block of text.""" options_to_add = [x.strip() for x in text.split("\n") if x.strip() != ""] return add_multiple_options(session, poll, options_to_add, is_date=is_date) @@ -35,9 +35,9 @@ def add_options_multiline( def add_multiple_options( session: scoped_session, poll: Poll, - options_to_add: List[str], + options_to_add: list[str], is_date: bool = False, -) -> List[Union[Any, str]]: +) -> list[Any | str]: """Create options from a list of strings.""" added_options = [] @@ -57,8 +57,8 @@ def add_multiple_options( def add_option( - poll: Poll, text: str, added_options: List[str], is_date: bool -) -> Optional[Option]: + poll: Poll, text: str, added_options: list[str], is_date: bool +) -> Option | None: """Parse the incoming text and create a single option from it. We allow option descriptions after an `--` or `—` delimiter. @@ -96,9 +96,7 @@ def add_option( return option -def get_sorted_options( - poll: Poll, total_user_count: int = 0 -) -> List[Union[Option, Any]]: +def get_sorted_options(poll: Poll, total_user_count: int = 0) -> list[Option | Any]: """Sort the options depending on the poll's current settings.""" options = poll.options.copy() @@ -112,7 +110,7 @@ def get_option_percentage(option): return options -def calculate_percentage(option: Option, total_user_count: int) -> Union[float, int]: +def calculate_percentage(option: Option, total_user_count: int) -> float | int: """Calculate the percentage for this option.""" # Return 0 if: # - No voted on this poll yet diff --git a/pollbot/poll/update.py b/pollbot/poll/update.py index 076e73fc..00467fc3 100644 --- a/pollbot/poll/update.py +++ b/pollbot/poll/update.py @@ -1,6 +1,5 @@ """Update or delete poll messages.""" from datetime import datetime, timedelta -from typing import Optional from psycopg2.errors import UniqueViolation from sqlalchemy.exc import IntegrityError @@ -21,9 +20,9 @@ def update_poll_messages( session: scoped_session, bot: Bot, poll: Poll, - message_id: Optional[int] = None, - user: Optional[User] = None, - inline_message_id: Optional[str] = None, + message_id: int | None = None, + user: User | None = None, + inline_message_id: str | None = None, ) -> None: """Logic for handling updates. diff --git a/pollbot/poll/vote.py b/pollbot/poll/vote.py index 65ddf65f..cb8bcee2 100644 --- a/pollbot/poll/vote.py +++ b/pollbot/poll/vote.py @@ -1,6 +1,5 @@ """Helper functions for votes.""" import random -from typing import Dict, List from sqlalchemy.orm.collections import InstrumentedList from sqlalchemy.orm.scoping import scoped_session @@ -32,7 +31,7 @@ def init_votes(session: scoped_session, poll: Poll, user: User) -> None: def init_votes_for_new_options( - session: scoped_session, poll: Poll, added_options: List[str] + session: scoped_session, poll: Poll, added_options: list[str] ) -> None: """ When a new option is added, we need to create new votes @@ -86,7 +85,7 @@ def reorder_votes_after_option_delete(session, poll: Poll): session.flush() -def get_sorted_votes(poll: Poll, votes: List[Vote]) -> InstrumentedList: +def get_sorted_votes(poll: Poll, votes: list[Vote]) -> InstrumentedList: """Sort the votes depending on the poll's current settings.""" def get_user_name(vote): @@ -99,7 +98,7 @@ def get_user_name(vote): return votes -def get_sorted_doodle_votes(poll: Poll, votes: List[Vote]) -> Dict[str, List[Vote]]: +def get_sorted_doodle_votes(poll: Poll, votes: list[Vote]) -> dict[str, list[Vote]]: """Sort the votes depending on the poll's current settings.""" doodle_answers = ["yes", "maybe", "no"] diff --git a/pollbot/sentry.py b/pollbot/sentry.py index 41643cf2..8841e024 100644 --- a/pollbot/sentry.py +++ b/pollbot/sentry.py @@ -20,7 +20,7 @@ def ignore_job_exception(exception): return False -class Sentry(object): +class Sentry: """Sentry wrapper class. This class offers some convenience classes and functions for adding diff --git a/pollbot/telegram/callback_handler/creation.py b/pollbot/telegram/callback_handler/creation.py index 348f702d..d87cced9 100644 --- a/pollbot/telegram/callback_handler/creation.py +++ b/pollbot/telegram/callback_handler/creation.py @@ -1,6 +1,5 @@ """Callback functions needed during creation of a Poll.""" from datetime import date -from typing import Optional from sqlalchemy.orm.scoping import scoped_session from telegram.message import Message @@ -210,7 +209,7 @@ def close_creation_datepicker(session, context, poll): message.edit_text(text, parse_mode="markdown", reply_markup=keyboard) -def cancel_creation(session: scoped_session, context: CallbackContext) -> Optional[str]: +def cancel_creation(session: scoped_session, context: CallbackContext) -> str | None: """Cancel the creation of a poll.""" if context.poll is None: return i18n.t("delete.doesnt_exist", locale=context.user.locale) diff --git a/pollbot/telegram/callback_handler/datepicker.py b/pollbot/telegram/callback_handler/datepicker.py index 56799f39..1a1091b5 100644 --- a/pollbot/telegram/callback_handler/datepicker.py +++ b/pollbot/telegram/callback_handler/datepicker.py @@ -1,6 +1,5 @@ """Option for setting the current date of the picker.""" from datetime import date, datetime, time -from typing import Optional from dateutil.relativedelta import relativedelta from sqlalchemy.orm.scoping import scoped_session @@ -94,7 +93,6 @@ def pick_creation_date( def pick_creation_weekday( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - return @@ -102,7 +100,6 @@ def pick_creation_weekday( def pick_additional_date( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Pick an option after creating the poll.""" owner_pick_date_option(session, context, poll, DatepickerContext.additional_option) @@ -117,7 +114,7 @@ def pick_additional_weekday( @poll_required def pick_external_date( session: scoped_session, context: CallbackContext, poll: Poll -) -> Optional[str]: +) -> str | None: """Add or remove a date option during creation.""" picked_date = date.fromisoformat(context.data[2]) @@ -142,7 +139,7 @@ def pick_external_date( @poll_required def pick_due_date( _: scoped_session, context: CallbackContext, poll: Poll -) -> Optional[str]: +) -> str | None: """Set the due date for a poll.""" picked_date = date.fromisoformat(context.data[2]) if picked_date <= date.today(): @@ -166,7 +163,6 @@ def pick_due_date( @poll_required def set_next_month(_: scoped_session, context: CallbackContext, poll: Poll) -> str: - """Show the datepicker keyboard for the next month.""" this_month = date.fromisoformat(context.data[2]) datepicker_context = DatepickerContext(int(context.data[3])) diff --git a/pollbot/telegram/callback_handler/external.py b/pollbot/telegram/callback_handler/external.py index d9e006b3..b2a018e9 100644 --- a/pollbot/telegram/callback_handler/external.py +++ b/pollbot/telegram/callback_handler/external.py @@ -1,6 +1,5 @@ """Option for setting the current date of the picker.""" from datetime import date -from typing import Optional from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.scoping import scoped_session @@ -21,7 +20,7 @@ @poll_required def activate_notification( session: scoped_session, context: CallbackContext, poll: Poll -) -> Optional[str]: +) -> str | None: """Show to vote type keyboard.""" user = context.user if user != poll.user: @@ -60,7 +59,7 @@ def activate_notification( @poll_required def open_external_datepicker( _: scoped_session, context: CallbackContext, poll: Poll -) -> Optional[str]: +) -> str | None: """This opens the datepicker for non-admin users when they're adding options.""" keyboard = get_external_datepicker_keyboard(poll, date.today()) # Switch from new option by text to new option via datepicker diff --git a/pollbot/telegram/callback_handler/management.py b/pollbot/telegram/callback_handler/management.py index 539a3b8a..4adf4f38 100644 --- a/pollbot/telegram/callback_handler/management.py +++ b/pollbot/telegram/callback_handler/management.py @@ -1,6 +1,5 @@ """Callback functions needed during creation of a Poll.""" from datetime import datetime -from typing import Optional from sqlalchemy.orm.scoping import scoped_session @@ -17,7 +16,6 @@ @poll_required def delete_poll(session: scoped_session, context: CallbackContext, poll: Poll) -> str: - """Permanently delete the poll.""" poll.delete = PollDeletionMode.DB_ONLY.name session.commit() @@ -29,7 +27,6 @@ def delete_poll(session: scoped_session, context: CallbackContext, poll: Poll) - def delete_poll_with_messages( session: scoped_session, context: CallbackContext, poll: Poll ) -> str: - """Permanently delete the poll.""" poll.delete = PollDeletionMode.WITH_MESSAGES.name session.commit() @@ -39,7 +36,6 @@ def delete_poll_with_messages( @poll_required def close_poll(session: scoped_session, context: CallbackContext, poll: Poll) -> str: - """Close this poll.""" poll.closed = True session.commit() @@ -53,8 +49,7 @@ def close_poll(session: scoped_session, context: CallbackContext, poll: Poll) -> @poll_required def reopen_poll( session: scoped_session, context: CallbackContext, poll: Poll -) -> Optional[str]: - +) -> str | None: """Reopen this poll.""" if not poll.results_visible: return i18n.t("callback.cannot_reopen", locale=poll.user.locale) @@ -76,7 +71,6 @@ def reopen_poll( @poll_required def reset_poll(session: scoped_session, context: CallbackContext, poll: Poll) -> str: - """Reset this poll.""" for vote in poll.votes: session.delete(vote) diff --git a/pollbot/telegram/callback_handler/menu.py b/pollbot/telegram/callback_handler/menu.py index 8f61744e..8f2b60b2 100644 --- a/pollbot/telegram/callback_handler/menu.py +++ b/pollbot/telegram/callback_handler/menu.py @@ -25,7 +25,6 @@ @poll_required def go_back(session: scoped_session, context: CallbackContext, poll: Poll) -> None: - """Go back to the original step.""" if context.callback_result == CallbackResult.main_menu: text = get_poll_text(session, poll) diff --git a/pollbot/telegram/callback_handler/styling.py b/pollbot/telegram/callback_handler/styling.py index 110eb394..df908151 100644 --- a/pollbot/telegram/callback_handler/styling.py +++ b/pollbot/telegram/callback_handler/styling.py @@ -46,7 +46,6 @@ def toggle_percentage( def toggle_option_votes( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Toggle the visibility of the vote overview on an option.""" if poll.anonymous and not poll.show_percentage: context.query.message.chat.send_message( @@ -65,7 +64,6 @@ def toggle_option_votes( def toggle_date_format( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Switch between european and US date format.""" poll.european_date_format = not poll.european_date_format poll.user.european_date_format = poll.european_date_format @@ -79,7 +77,6 @@ def toggle_date_format( def toggle_summerization( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Toggle summarization of votes of a poll.""" poll.summarize = not poll.summarize @@ -92,7 +89,6 @@ def toggle_summerization( def toggle_compact_buttons( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Toggle the doodle poll button style.""" poll.compact_buttons = not poll.compact_buttons @@ -105,7 +101,6 @@ def toggle_compact_buttons( def set_option_order( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Set the order in which options are listed.""" option_sorting = OptionSorting(context.action) poll.option_sorting = option_sorting.name @@ -119,7 +114,6 @@ def set_option_order( def set_user_order( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Set the order in which user are listed.""" user_sorting = UserSorting(context.action) poll.user_sorting = user_sorting.name @@ -146,7 +140,6 @@ def send_option_order_message( def open_option_order_menu( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Open the menu for manually adjusting the option order.""" send_option_order_message(session, context) @@ -155,7 +148,6 @@ def open_option_order_menu( def increase_option_index( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Increase the index of a specific option.""" option_id = context.action @@ -195,7 +187,6 @@ def increase_option_index( def decrease_option_index( session: scoped_session, context: CallbackContext, poll: Poll ) -> None: - """Decrease the index of a specific option.""" option_id = context.action diff --git a/pollbot/telegram/callback_handler/vote.py b/pollbot/telegram/callback_handler/vote.py index 7b026b6d..dec3745b 100644 --- a/pollbot/telegram/callback_handler/vote.py +++ b/pollbot/telegram/callback_handler/vote.py @@ -1,5 +1,4 @@ """Callback functions needed during creation of a Poll.""" -from typing import Optional from sqlalchemy import func from sqlalchemy.exc import IntegrityError, OperationalError @@ -108,7 +107,7 @@ def respond_to_vote( line: str, context: CallbackContext, poll: Poll, - remaining_votes: Optional[int] = None, + remaining_votes: int | None = None, limited: bool = False, ) -> None: """Get the formatted response for a user.""" diff --git a/pollbot/telegram/commands/admin.py b/pollbot/telegram/commands/admin.py index 250efa9e..f28219c6 100644 --- a/pollbot/telegram/commands/admin.py +++ b/pollbot/telegram/commands/admin.py @@ -37,7 +37,6 @@ def remaining_time(total, current, start): @message_wrapper() @admin_required def broadcast(bot: Bot, update: Update, session: scoped_session, user: User) -> None: - """Broadcast a message to all users.""" chat = update.message.chat message = update.message.text.split(" ", 1)[1].strip() @@ -79,7 +78,7 @@ def broadcast(bot: Bot, update: Update, session: scoped_session, user: User) -> # The chat does no longer exist, delete it except BadRequest as e: - if e.message == "Chat not found": # noqa + if e.message == "Chat not found": user.started = False # We are not allowed to contact this user. @@ -112,7 +111,6 @@ def broadcast(bot: Bot, update: Update, session: scoped_session, user: User) -> def test_broadcast( bot: Bot, update: Update, session: scoped_session, user: User ) -> None: - """Send the broadcast message to the admin for test purposes.""" message = update.message.text.split(" ", 1)[1].strip() diff --git a/pollbot/telegram/commands/external.py b/pollbot/telegram/commands/external.py index fb6068f5..51df4da1 100644 --- a/pollbot/telegram/commands/external.py +++ b/pollbot/telegram/commands/external.py @@ -13,7 +13,6 @@ @message_wrapper() def notify(bot: Bot, update: Update, session: scoped_session, user: User) -> None: - """Activate notifications for polls with due date.""" polls = ( session.query(Poll) diff --git a/pollbot/telegram/commands/poll.py b/pollbot/telegram/commands/poll.py index 7df575a5..c4ae1378 100644 --- a/pollbot/telegram/commands/poll.py +++ b/pollbot/telegram/commands/poll.py @@ -1,5 +1,4 @@ """Poll related commands.""" -from typing import Optional from sqlalchemy.orm.scoping import scoped_session from telegram.bot import Bot @@ -22,7 +21,7 @@ def create_poll(bot: Bot, update: Update, session: scoped_session, user: User) - @message_wrapper(private=True) def cancel_poll_creation(bot, update, session, user): """Cancels the creation of the current poll.""" - current_poll: Optional[Poll] = user.current_poll + current_poll: Poll | None = user.current_poll if current_poll is None: update.message.chat.send_message( diff --git a/pollbot/telegram/commands/start.py b/pollbot/telegram/commands/start.py index f79fa6df..1a3014a8 100644 --- a/pollbot/telegram/commands/start.py +++ b/pollbot/telegram/commands/start.py @@ -1,6 +1,5 @@ """The start command handler.""" import time -from typing import Optional from uuid import UUID from sqlalchemy.orm.scoping import scoped_session @@ -28,9 +27,7 @@ @message_wrapper() -def start( - bot: Bot, update: Update, session: scoped_session, user: User -) -> Optional[str]: +def start(bot: Bot, update: Update, session: scoped_session, user: User) -> str | None: """Send a start text.""" # Truncate the /start command text = update.message.text[6:].strip() diff --git a/pollbot/telegram/keyboard/date_picker.py b/pollbot/telegram/keyboard/date_picker.py index 52db7f63..c028c693 100644 --- a/pollbot/telegram/keyboard/date_picker.py +++ b/pollbot/telegram/keyboard/date_picker.py @@ -1,7 +1,7 @@ """Reply keyboards.""" import calendar from datetime import date -from typing import Any, List, Tuple, Union +from typing import Any from telegram import InlineKeyboardButton, InlineKeyboardMarkup @@ -92,7 +92,7 @@ def get_external_datepicker_keyboard(poll, current_date): def get_datepicker_buttons( poll: Poll, current_date: date, datetime_context: DatepickerContext -) -> List[List[InlineKeyboardButton]]: +) -> list[list[InlineKeyboardButton]]: """Get the buttons for the datepicker. Since the datepicker is used in several different scenarios, we allow to dynamically @@ -168,7 +168,7 @@ def get_datepicker_buttons( def resolve_context( poll: Poll, context: DatepickerContext -) -> Union[Tuple[int, int, int, List[date]], Tuple[int, int, int, List[Any]]]: +) -> tuple[int, int, int, list[date]] | tuple[int, int, int, list[Any]]: """Return the CallbackTypes, context variable and picked dates depending on input string.""" # Compile a list of all existing option dates option_dates = [] diff --git a/pollbot/telegram/keyboard/external.py b/pollbot/telegram/keyboard/external.py index 9ce449d0..b47d6be6 100644 --- a/pollbot/telegram/keyboard/external.py +++ b/pollbot/telegram/keyboard/external.py @@ -1,5 +1,4 @@ """All keyboards for external users that don't own the poll.""" -from typing import List from telegram import InlineKeyboardButton, InlineKeyboardMarkup @@ -8,7 +7,7 @@ from pollbot.models.poll import Poll -def get_notify_keyboard(polls: List[Poll]) -> InlineKeyboardMarkup: +def get_notify_keyboard(polls: list[Poll]) -> InlineKeyboardMarkup: """Get the keyboard for activationg notifications in a chat.""" # Add back and pick buttons buttons = [] diff --git a/pollbot/telegram/keyboard/management.py b/pollbot/telegram/keyboard/management.py index 7f0e4248..8f751fb2 100644 --- a/pollbot/telegram/keyboard/management.py +++ b/pollbot/telegram/keyboard/management.py @@ -1,5 +1,4 @@ """Reply keyboards.""" -from typing import List from telegram import InlineKeyboardButton, InlineKeyboardMarkup @@ -163,7 +162,7 @@ def get_deletion_confirmation(poll: Poll) -> InlineKeyboardMarkup: def get_poll_list_keyboard( - polls: List[Poll], closed: bool, offset: int, poll_count: int + polls: list[Poll], closed: bool, offset: int, poll_count: int ) -> InlineKeyboardMarkup: """Get the confirmation keyboard for poll deletion.""" buttons = [] diff --git a/pollbot/telegram/keyboard/misc.py b/pollbot/telegram/keyboard/misc.py index 038953a4..7cc56b95 100644 --- a/pollbot/telegram/keyboard/misc.py +++ b/pollbot/telegram/keyboard/misc.py @@ -1,5 +1,4 @@ """All keyboards for external users that don't own the poll.""" -from typing import List from telegram import InlineKeyboardButton, InlineKeyboardMarkup @@ -10,7 +9,7 @@ def get_help_keyboard( - user: User, categories: List[str], current_category: str + user: User, categories: list[str], current_category: str ) -> InlineKeyboardMarkup: """Get the done keyboard for options during poll creation.""" rows = [] diff --git a/pollbot/telegram/keyboard/vote.py b/pollbot/telegram/keyboard/vote.py index 5c40c87d..8cad078e 100644 --- a/pollbot/telegram/keyboard/vote.py +++ b/pollbot/telegram/keyboard/vote.py @@ -1,5 +1,5 @@ """Reply keyboards.""" -from typing import Any, List, Optional, Union +from typing import Any from sqlalchemy.orm import joinedload from telegram import InlineKeyboardButton, InlineKeyboardMarkup @@ -22,7 +22,7 @@ def get_vote_keyboard( - poll: Poll, user: Optional[User], show_back: bool = False, summary: bool = False + poll: Poll, user: User | None, show_back: bool = False, summary: bool = False ) -> InlineKeyboardMarkup: """Get a plain vote keyboard.""" buttons = [] @@ -74,8 +74,8 @@ def get_vote_keyboard( def get_vote_buttons( - poll: Poll, user: Optional[User] = None, show_back: bool = False -) -> List[Union[List[InlineKeyboardButton], Any]]: + poll: Poll, user: User | None = None, show_back: bool = False +) -> list[list[InlineKeyboardButton] | Any]: """Get the keyboard for actual voting.""" if poll_allows_cumulative_votes(poll): buttons = get_cumulative_buttons(poll) @@ -89,7 +89,7 @@ def get_vote_buttons( return buttons -def get_normal_buttons(poll: Poll) -> List[Union[List[InlineKeyboardButton], Any]]: +def get_normal_buttons(poll: Poll) -> list[list[InlineKeyboardButton] | Any]: """Get the normal keyboard with one vote button per option.""" buttons = [] vote_button_type = CallbackType.vote.value @@ -115,7 +115,7 @@ def get_normal_buttons(poll: Poll) -> List[Union[List[InlineKeyboardButton], Any return buttons -def get_cumulative_buttons(poll: Poll) -> List[List[InlineKeyboardButton]]: +def get_cumulative_buttons(poll: Poll) -> list[list[InlineKeyboardButton]]: """Get the cumulative keyboard with two buttons per option.""" vote_button_type = CallbackType.vote.value vote_yes = CallbackResult.yes.value @@ -140,8 +140,8 @@ def get_cumulative_buttons(poll: Poll) -> List[List[InlineKeyboardButton]]: def get_priority_buttons( - poll: Poll, user: Optional[User] -) -> List[List[InlineKeyboardButton]]: + poll: Poll, user: User | None +) -> list[list[InlineKeyboardButton]]: """Create the keyboard for priority poll. Only show the deeplink, if not in a direct conversation.""" if user is None: bot_name = config["telegram"]["bot_name"] @@ -205,7 +205,7 @@ def get_priority_buttons( return buttons -def get_doodle_buttons(poll: Poll) -> List[List[InlineKeyboardButton]]: +def get_doodle_buttons(poll: Poll) -> list[list[InlineKeyboardButton]]: """Get the doodle keyboard with yes, maybe and no button per option.""" show_option_name = CallbackType.show_option_name.value vote_button_type = CallbackType.vote.value diff --git a/pollbot/telegram/message_handler.py b/pollbot/telegram/message_handler.py index c0f25671..9cb19c5b 100644 --- a/pollbot/telegram/message_handler.py +++ b/pollbot/telegram/message_handler.py @@ -1,5 +1,4 @@ """Handle messages.""" -from typing import Optional from sqlalchemy.orm.scoping import scoped_session from telegram.bot import Bot @@ -29,7 +28,7 @@ @message_wrapper() def handle_private_text( bot: Bot, update: Update, session: scoped_session, user: User -) -> Optional[str]: +) -> str | None: """Read all private messages and the creation of polls.""" text = update.message.text.strip() poll = user.current_poll @@ -123,7 +122,7 @@ def handle_create_options( text: str, poll: Poll, chat: Chat, -) -> Optional[str]: +) -> str | None: """Add options to the poll.""" # Multiple options can be sent at once separated by newline # Strip them and ignore empty lines @@ -143,7 +142,7 @@ def handle_set_vote_count( text: str, poll: Poll, chat: Chat, -) -> Optional[str]: +) -> str | None: """Set the amount of possible votes for this poll.""" if poll.poll_type == PollType.limited_vote.name: error_message = i18n.t( diff --git a/pollbot/telegram/native_poll_handler.py b/pollbot/telegram/native_poll_handler.py index 5077b7a7..6d8aef51 100644 --- a/pollbot/telegram/native_poll_handler.py +++ b/pollbot/telegram/native_poll_handler.py @@ -1,8 +1,7 @@ """Handle messages.""" from sqlalchemy.orm import Session -from telegram import Bot +from telegram import Bot, Update from telegram import Poll as NativePoll -from telegram import Update from pollbot.display.creation import get_native_poll_merged_text from pollbot.i18n import i18n diff --git a/pollbot/telegram/session.py b/pollbot/telegram/session.py index 49dba55a..adebe4d3 100644 --- a/pollbot/telegram/session.py +++ b/pollbot/telegram/session.py @@ -1,8 +1,9 @@ """Session helper functions.""" import traceback +from collections.abc import Callable from datetime import date, datetime, timedelta from functools import wraps -from typing import Any, Callable, Union +from typing import Any from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session @@ -122,7 +123,6 @@ def wrapper(update: Update, context: CallbackContext): session = get_session() try: - user = get_user(session, update.callback_query.from_user) # Cache ban value, so we don't have to lookup the value in our database on each request if user.banned: @@ -409,7 +409,7 @@ def should_report_exception(context: CallbackContext, exception: Exception) -> b return False -def ignore_exception(exception: Union[BadRequest, Unauthorized]) -> bool: +def ignore_exception(exception: BadRequest | Unauthorized) -> bool: """Check whether we can safely ignore this exception.""" if type(exception) is BadRequest: if ( diff --git a/pyproject.toml b/pyproject.toml index b7fdc4a3..5d0685f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,17 +25,47 @@ toml = "^0.10" typer = "^0.6" [tool.poetry.dev-dependencies] -autoflake = "^1" -black = { version = "*", allow-prereleases = true } -flake8 = "^5" -isort = "^5" +ruff = "^0.1" pytest = "^7" types-toml = "^0.10" -[build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +[tool.ruff] +line-length = 88 +indent-width = 4 +target-version = "py311" +exclude = [ + ".venv", + "__init__.py", +] -[tool.isort] -line_length = 88 -profile = "black" +[tool.ruff.lint] +select = [ + "E", # Pycodestyle + "F", # Pyflakes + "I", # Isort + "UP", # Language feature updates + "RUF", # Ruff Rules +] +fixable = [ + "E", + "F", + "I", + "UP", # Language feature updates + "RUF", # Ruff Rules +] +ignore = [ + "RUF001", + "E501", +] + +# 4. Ignore `E402` (import violations) in all `__init__.py` and misc files. +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["E402"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" +docstring-code-format = true +docstring-code-line-length = "dynamic" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8a18b6a5..00000000 --- a/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[flake8] -ignore = E501,W503 -max-line-length = 88 -exclude = __init__.py