From 8ec55029a2759fda2a471db6579bbf0973a4ca6e Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Thu, 16 Oct 2025 13:48:03 +0100 Subject: [PATCH 01/12] Run Flake8 in CI --- .flake8 | 4 +++ .github/workflows/check-build-deploy.yaml | 32 ++++++++++++++++-- .pre-commit-config.yaml | 8 ++++- .yamllint.yaml | 40 +++++++++++++++++++++++ pyproject.toml | 1 + 5 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 .flake8 create mode 100644 .yamllint.yaml diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..c3c9502c --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +select = CAR,TXB +extend_exclude = migrations,.venv +require_plugins = flake8-carrot diff --git a/.github/workflows/check-build-deploy.yaml b/.github/workflows/check-build-deploy.yaml index e5e67789..f9ee27c5 100644 --- a/.github/workflows/check-build-deploy.yaml +++ b/.github/workflows/check-build-deploy.yaml @@ -22,6 +22,34 @@ jobs: - name: Check uv.lock (ensure all dependencies up to date) run: uv lock --check + # yamllint disable-line rule:key-ordering + flake8: + env: + UV_FROZEN: true + UV_NO_SYNC: true + UV_PYTHON_DOWNLOADS: never + needs: [uv-check] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - name: Set Up Python + uses: actions/setup-python@v6 + with: + python-version-file: .python-version + + - name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + + - name: Install Flake8 From Locked Dependencies + run: uv sync --no-group dev + + - name: Run Flake8 + run: uv run -- flake8 + mypy: needs: [uv-check] runs-on: ubuntu-latest @@ -98,7 +126,7 @@ jobs: - name: Save pre-commit Checks Which Require Skipping run: | if [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == "${{ github.event.repository.default_branch }}" ]]; then - echo "SKIP=check-github-workflows,ruff,uv-lock,gitlint-ci" >> $GITHUB_ENV + echo "SKIP=check-github-workflows,flake8,ruff-check,uv-lock,gitlint-ci" >> $GITHUB_ENV else echo "SKIP=check-github-workflows,ruff,uv-lock" >> $GITHUB_ENV fi @@ -228,7 +256,7 @@ jobs: github.event.pull_request.head.repo.full_name == 'CSSUoB/TeX-Bot-Py-V2' runs-on: ubuntu-latest environment: publish - needs: [mypy, pre-commit, pymarkdown, pytest, ruff-lint, uv-check] + needs: [flake8, mypy, pre-commit, pymarkdown, pytest, ruff-lint, uv-check] permissions: contents: read packages: write diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4ddd0fd..bc25d0d9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,6 +10,12 @@ repos: hooks: - id: hadolint-docker + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.37.1 + hooks: + - id: yamllint + args: [--strict] + - repo: https://github.com/renovatebot/pre-commit-hooks rev: 41.149.2 hooks: @@ -76,7 +82,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.14.0 hooks: - - id: ruff + - id: ruff-check args: [--fix] - id: ruff-format diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 00000000..cd13ce8a --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,40 @@ +extends: default + +locale: en_GB.UTF-8 + +rules: + anchors: + forbid-duplicated-anchors: true + forbid-unused-anchors: true + braces: + forbid: true + brackets: + max-spaces-inside-empty: 0 + min-spaces-inside-empty: 0 + document-end: + level: warning + present: false + document-start: + present: false + empty-lines: + max: 2 + empty-values: enable + float-values: + forbid-inf: true + forbid-nan: true + require-numeral-before-decimal: true + indentation: disable + key-ordering: + ignored-keys: + - always_run + - args + - hooks + - jobs + - steps + line-length: disable + new-lines: disable + octal-values: enable + quoted-strings: + allow-quoted-quotes: true + quote-type: double + required: only-when-needed diff --git a/pyproject.toml b/pyproject.toml index e1c6319b..e772f6b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,6 +128,7 @@ allowed-confusables = [ "ᴡ", "ᴢ", ] +external = ["CAR", "TXB"] ignore = [ "C90", "COM812", From 885ade6c878ffefbf2da518684602767485245c7 Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Thu, 16 Oct 2025 14:15:05 +0100 Subject: [PATCH 02/12] Fix mismatched Flake8 Python versions --- .github/workflows/check-build-deploy.yaml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/check-build-deploy.yaml b/.github/workflows/check-build-deploy.yaml index f9ee27c5..33fba223 100644 --- a/.github/workflows/check-build-deploy.yaml +++ b/.github/workflows/check-build-deploy.yaml @@ -28,7 +28,6 @@ jobs: UV_FROZEN: true UV_NO_SYNC: true UV_PYTHON_DOWNLOADS: never - needs: [uv-check] runs-on: ubuntu-latest steps: @@ -37,18 +36,15 @@ jobs: - name: Set Up Python uses: actions/setup-python@v6 with: - python-version-file: .python-version + python-version: 3.14 - name: Install uv uses: astral-sh/setup-uv@v7 with: enable-cache: true - - name: Install Flake8 From Locked Dependencies - run: uv sync --no-group dev - - name: Run Flake8 - run: uv run -- flake8 + run: uvx --python 3.14 --with flake8-carrot -- flake8 mypy: needs: [uv-check] @@ -126,7 +122,7 @@ jobs: - name: Save pre-commit Checks Which Require Skipping run: | if [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == "${{ github.event.repository.default_branch }}" ]]; then - echo "SKIP=check-github-workflows,flake8,ruff-check,uv-lock,gitlint-ci" >> $GITHUB_ENV + echo "SKIP=check-github-workflows,ruff-check,uv-lock,gitlint-ci" >> $GITHUB_ENV else echo "SKIP=check-github-workflows,ruff,uv-lock" >> $GITHUB_ENV fi @@ -256,7 +252,7 @@ jobs: github.event.pull_request.head.repo.full_name == 'CSSUoB/TeX-Bot-Py-V2' runs-on: ubuntu-latest environment: publish - needs: [flake8, mypy, pre-commit, pymarkdown, pytest, ruff-lint, uv-check] + needs: [mypy, pre-commit, pymarkdown, pytest, ruff-lint, uv-check] permissions: contents: read packages: write From cbb14722da94ec633e30cc76a99c3b39749a66ce Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Thu, 16 Oct 2025 14:37:47 +0100 Subject: [PATCH 03/12] Fix yamllint errors --- .github/workflows/check-build-deploy.yaml | 120 +++++++++--------- .github/workflows/pr-auto-updater.yaml | 23 ++-- .../prevent-migrations-deletion.yaml | 9 +- .hadolint.yaml | 4 +- .pre-commit-config.yaml | 5 +- .yamlfmt.yaml | 12 +- main.py | 18 +-- 7 files changed, 94 insertions(+), 97 deletions(-) diff --git a/.github/workflows/check-build-deploy.yaml b/.github/workflows/check-build-deploy.yaml index 33fba223..87391647 100644 --- a/.github/workflows/check-build-deploy.yaml +++ b/.github/workflows/check-build-deploy.yaml @@ -1,11 +1,11 @@ name: Check, Build and Deploy -on: +"on": pull_request: branches: [main] push: branches: [main] - tags: ["v*"] + tags: [v*] jobs: uv-check: @@ -46,13 +46,13 @@ jobs: - name: Run Flake8 run: uvx --python 3.14 --with flake8-carrot -- flake8 - mypy: - needs: [uv-check] - runs-on: ubuntu-latest + mypy: # yamllint disable-line rule:key-ordering env: - UV_NO_SYNC: true UV_FROZEN: true + UV_NO_SYNC: true UV_PYTHON_DOWNLOADS: never + needs: [uv-check] + runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -70,25 +70,25 @@ jobs: - name: Install mypy From Locked Dependencies run: uv sync --no-group dev --group type-check - - name: Store Hashed Python Version - id: store-hashed-python-version + - id: store-hashed-python-version + name: Store Hashed Python Version run: echo "hashed_python_version=$(uv run -- python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT - uses: actions/cache@v4 with: - path: ./.mypy_cache key: mypy|${{steps.store-hashed-python-version.outputs.hashed_python_version}} + path: ./.mypy_cache - name: Run mypy run: uv run -- mypy . # TODO: Add GitHub workflows output format - pre-commit: - runs-on: ubuntu-latest + pre-commit: # yamllint disable-line rule:key-ordering env: - UV_NO_SYNC: true UV_FROZEN: true + UV_NO_SYNC: true UV_PYTHON_DOWNLOADS: never + runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -106,15 +106,15 @@ jobs: - name: Install pre-commit From Locked Dependencies run: uv sync --only-group pre-commit - - name: Store Hashed Python Version - id: store-hashed-python-version + - id: store-hashed-python-version + name: Store Hashed Python Version run: echo "hashed_python_version=$(uv run -- python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT - uses: actions/cache@v4 with: - path: ~/.cache/pre-commit key: pre-commit|${{steps.store-hashed-python-version.outputs.hashed_python_version}}|${{hashFiles('.pre-commit-config.yaml')}} + path: ~/.cache/pre-commit - name: Setup pre-commit Environments run: uv run -- pre-commit install-hooks @@ -130,15 +130,15 @@ jobs: - name: Run pre-commit run: uv run -- pre-commit run --all-files --hook-stage manual # TODO: Add GitHub workflows output format - - uses: pre-commit-ci/lite-action@v1.1.0 - if: ${{!cancelled()}} + - if: ${{!cancelled()}} + uses: pre-commit-ci/lite-action@v1.1.0 - pymarkdown: - runs-on: ubuntu-latest + pymarkdown: # yamllint disable-line rule:key-ordering env: - UV_NO_SYNC: true UV_FROZEN: true + UV_NO_SYNC: true UV_PYTHON_DOWNLOADS: never + runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -159,15 +159,15 @@ jobs: - name: Run PyMarkdown scan run: uv run -- pymarkdown scan . - pytest: - needs: [uv-check] - runs-on: ubuntu-latest - permissions: - id-token: write + pytest: # yamllint disable-line rule:key-ordering env: - UV_NO_SYNC: true UV_FROZEN: true + UV_NO_SYNC: true UV_PYTHON_DOWNLOADS: never + needs: [uv-check] + permissions: + id-token: write + runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -185,37 +185,37 @@ jobs: - name: Install pytest From Locked Dependencies run: uv sync --no-group dev --group test - - name: Store Hashed Python Version - id: store-hashed-python-version + - id: store-hashed-python-version + name: Store Hashed Python Version run: echo "hashed_python_version=$(uv run -- python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT - uses: actions/cache@v4 with: - path: ./.pytest_cache key: pytest|${{steps.store-hashed-python-version.outputs.hashed_python_version}} + path: ./.pytest_cache - name: Run pytest run: uv run pytest --cov --cov-branch --cov-report=xml --junitxml=junit.xml - - name: Upload test results to Codecov - if: ${{ !cancelled() }} + - if: ${{ !cancelled() }} + name: Upload test results to Codecov uses: codecov/test-results-action@v1 with: use_oidc: true - - name: Upload coverage report to Codecov + - if: ${{ !cancelled() }} + name: Upload coverage report to Codecov uses: codecov/codecov-action@v5 - if: ${{ !cancelled() }} with: use_oidc: true - ruff-lint: - runs-on: ubuntu-latest + ruff-lint: # yamllint disable-line rule:key-ordering env: - UV_NO_SYNC: true UV_FROZEN: true + UV_NO_SYNC: true UV_PYTHON_DOWNLOADS: never + runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -233,49 +233,48 @@ jobs: - name: Install ruff From Locked Dependencies run: uv sync --only-group lint-format - - name: Store Hashed Python Version - id: store-hashed-python-version + - id: store-hashed-python-version + name: Store Hashed Python Version run: echo "hashed_python_version=$(uv run -- python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_OUTPUT - uses: actions/cache@v4 with: - path: ./.ruff_cache key: ruff|${{steps.store-hashed-python-version.outputs.hashed_python_version}} + path: ./.ruff_cache - name: Run Ruff run: uv run -- ruff check --no-fix --output-format=github - build-and-publish: + build-and-publish: # yamllint disable-line rule:key-ordering + env: + IMAGE_NAME: ${{github.repository}} + REGISTRY: ghcr.io + environment: publish if: | github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'CSSUoB/TeX-Bot-Py-V2' - runs-on: ubuntu-latest - environment: publish needs: [mypy, pre-commit, pymarkdown, pytest, ruff-lint, uv-check] permissions: - contents: read - packages: write attestations: write + contents: read id-token: write - - env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{github.repository}} + packages: write + runs-on: ubuntu-latest steps: - name: Log in to the Container registry uses: docker/login-action@v3.6.0 with: + password: ${{secrets.GITHUB_TOKEN}} registry: ${{env.REGISTRY}} username: ${{github.actor}} - password: ${{secrets.GITHUB_TOKEN}} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Extract metadata (tags, labels) for Docker - id: docker-extract-metadata + - id: docker-extract-metadata + name: Extract metadata (tags, labels) for Docker uses: docker/metadata-action@v5.8.0 with: images: ${{env.REGISTRY}}/${{env.IMAGE_NAME}} @@ -286,33 +285,32 @@ jobs: type=semver,pattern={{major}}.{{minor}} type=semver,pattern=v{{major}},enable=${{!startsWith(github.ref, 'refs/tags/v0.')}} - - name: Build and Publish - id: build-and-publish + - id: build-and-publish + name: Build and Publish uses: docker/build-push-action@v6 with: + labels: ${{steps.docker-extract-metadata.outputs.labels}} push: true tags: ${{steps.docker-extract-metadata.outputs.tags}} - labels: ${{steps.docker-extract-metadata.outputs.labels}} - name: Generate Artifact Attestation uses: actions/attest-build-provenance@v3 with: - subject-name: ${{env.REGISTRY}}/${{env.IMAGE_NAME}} - subject-digest: ${{steps.build-and-publish.outputs.digest}} push-to-registry: true + subject-digest: ${{steps.build-and-publish.outputs.digest}} + subject-name: ${{env.REGISTRY}}/${{env.IMAGE_NAME}} - release: + release: # yamllint disable-line rule:key-ordering + if: github.ref_type == 'tag' needs: [build-and-publish] - runs-on: ubuntu-latest permissions: contents: write id-token: write - - if: github.ref_type == 'tag' + runs-on: ubuntu-latest steps: - name: Create GitHub Release - env: + env: # yamllint disable-line rule:key-ordering GITHUB_TOKEN: ${{ github.token }} run: gh release create '${{ github.ref_name }}' --repo '${{github.repository}}' --verify-tag --generate-notes diff --git a/.github/workflows/pr-auto-updater.yaml b/.github/workflows/pr-auto-updater.yaml index 80e788fe..b73d0fbd 100644 --- a/.github/workflows/pr-auto-updater.yaml +++ b/.github/workflows/pr-auto-updater.yaml @@ -1,26 +1,27 @@ name: Automatic PR Updater -on: - push: {} + +"on": + push: ~ jobs: pr-auto-update: - runs-on: ubuntu-latest permissions: - pull-requests: write contents: write + pull-requests: write + runs-on: ubuntu-latest steps: - - name: Generate Access Token + - id: generate-token + name: Generate Access Token uses: actions/create-github-app-token@v2 - id: generate-token with: app-id: ${{ vars.PR_AUTO_UPDATE_CLIENT_ID }} private-key: ${{ secrets.PR_AUTO_UPDATE_PRIVATE_KEY }} - uses: CSSUoB/pr-auto-updater@v2.2.4 - env: + env: # yamllint disable-line rule:key-ordering GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - PR_FILTER: 'labelled' - PR_LABELS: 'sync' - MERGE_CONFLICT_ACTION: 'label' - MERGE_CONFLICT_LABEL: 'conflict' + MERGE_CONFLICT_ACTION: label + MERGE_CONFLICT_LABEL: conflict + PR_FILTER: labelled + PR_LABELS: sync diff --git a/.github/workflows/prevent-migrations-deletion.yaml b/.github/workflows/prevent-migrations-deletion.yaml index 307f0a4c..34fc3a08 100644 --- a/.github/workflows/prevent-migrations-deletion.yaml +++ b/.github/workflows/prevent-migrations-deletion.yaml @@ -1,20 +1,19 @@ name: Prevent Database Migration Files Deletion -on: +"on": pull_request_target: branches: [main] jobs: prevent-migrations-deletion: - runs-on: ubuntu-latest - permissions: pull-requests: read + runs-on: ubuntu-latest steps: - name: Prevent migrations files being changed or deleted uses: xalvarez/prevent-file-change-action@v3.0.0 with: - githubToken: ${{ secrets.GITHUB_TOKEN }} - pattern: '.*\/db\/.+\/migrations\/\d{4}\w*\.py$' allowNewFiles: true + githubToken: ${{ secrets.GITHUB_TOKEN }} + pattern: .*\/db\/.+\/migrations\/\d{4}\w*\.py$ diff --git a/.hadolint.yaml b/.hadolint.yaml index 2b39b4d6..4a202497 100644 --- a/.hadolint.yaml +++ b/.hadolint.yaml @@ -1,5 +1,5 @@ failure-threshold: style -strict-labels: true label-schema: - org.opencontainers.image.source: url org.opencontainers.image.licenses: spdx + org.opencontainers.image.source: url +strict-labels: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc25d0d9..876b3782 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,7 @@ default_install_hook_types: [pre-commit, commit-msg] -default_stages: [pre-commit, pre-merge-commit, manual] - default_language_version: python: python3.12 +default_stages: [pre-commit, pre-merge-commit, manual] repos: - repo: https://github.com/hadolint/hadolint @@ -62,7 +61,7 @@ repos: - id: name-tests-test args: [--pytest-test-first] - id: pretty-format-json - args: [--autofix, --indent, '4'] + args: [--autofix, --indent, "4"] - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks rev: v2.15.0 diff --git a/.yamlfmt.yaml b/.yamlfmt.yaml index c00dbf7d..5e988b84 100644 --- a/.yamlfmt.yaml +++ b/.yamlfmt.yaml @@ -1,10 +1,10 @@ -line_ending: lf -gitignore_excludes: true formatter: - indent: 4 - retain_line_breaks_single: true disallow_anchors: true - max_line_length: 95 - trim_trailing_whitespace: true eof_newline: true + indent: 4 + max_line_length: 95 pad_line_comments: 2 + retain_line_breaks_single: true + trim_trailing_whitespace: true +gitignore_excludes: true +line_ending: lf diff --git a/main.py b/main.py index ec9f69be..af195879 100755 --- a/main.py +++ b/main.py @@ -13,7 +13,7 @@ import config from config import settings -from utils import SuppressTraceback, TeXBot +from utils import TeXBot if TYPE_CHECKING: from collections.abc import Sequence @@ -21,17 +21,17 @@ __all__: "Sequence[str]" = ("bot",) -with SuppressTraceback(): - config.run_setup() +# with SuppressTraceback(): +config.run_setup() - intents: discord.Intents = discord.Intents.default() - intents.members = True +intents: discord.Intents = discord.Intents.default() +intents.members = True - bot: TeXBot = TeXBot( - intents=intents - ) # NOTE: See https://github.com/CSSUoB/TeX-Bot-Py-V2/issues/261 +bot: TeXBot = TeXBot( + intents=intents +) # NOTE: See https://github.com/CSSUoB/TeX-Bot-Py-V2/issues/261 - bot.load_extension("cogs") +bot.load_extension("cogs") def _run_bot() -> "NoReturn": # NOTE: See https://github.com/CSSUoB/TeX-Bot-Py-V2/issues/261 From c54b8d11f09417e0312ff48a94434d0f8ebfc638 Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Thu, 16 Oct 2025 15:22:40 +0100 Subject: [PATCH 04/12] Fix flake8-carrot linting errors --- .flake8 | 1 + cogs/__init__.py | 16 ++++++++-------- cogs/add_users_to_threads_and_channels.py | 4 ++-- cogs/annual_handover_and_reset.py | 1 + cogs/archive.py | 5 +++-- cogs/check_su_platform_authorisation.py | 1 + cogs/command_error.py | 1 + cogs/committee_actions_tracking.py | 15 ++++++++------- cogs/edit_message.py | 2 +- cogs/everest.py | 1 + cogs/induct.py | 1 + cogs/kill.py | 1 + cogs/make_applicant.py | 1 + cogs/send_get_roles_reminders.py | 1 + cogs/send_introduction_reminders.py | 3 ++- cogs/startup.py | 1 + cogs/strike.py | 5 +++-- config.py | 3 ++- db/__init__.py | 1 + db/core/models/__init__.py | 2 +- db/core/models/utils.py | 8 ++++---- main.py | 19 ++++++++++--------- tests/test_utils.py | 1 + utils/__init__.py | 1 + utils/error_capture_decorators.py | 3 ++- utils/message_sender_components.py | 1 + utils/tex_bot.py | 5 +++-- 27 files changed, 63 insertions(+), 41 deletions(-) diff --git a/.flake8 b/.flake8 index c3c9502c..dd38a479 100644 --- a/.flake8 +++ b/.flake8 @@ -2,3 +2,4 @@ select = CAR,TXB extend_exclude = migrations,.venv require_plugins = flake8-carrot +ignore = CAR180 diff --git a/cogs/__init__.py b/cogs/__init__.py index beca9474..0c74de61 100644 --- a/cogs/__init__.py +++ b/cogs/__init__.py @@ -7,20 +7,20 @@ from typing import TYPE_CHECKING -from .add_users_to_threads_and_channels import AddUsersToThreadsAndChannelsCommandCog +from .add_users_to_threads_and_channels import AddUsersToThreadsAndChannelsCommandsCog from .annual_handover_and_reset import ( AnnualRolesResetCommandCog, AnnualYearChannelsIncrementCommandCog, CommitteeHandoverCommandCog, ) -from .archive import ArchiveCommandCog +from .archive import ArchiveCommandsCog from .check_su_platform_authorisation import ( CheckSUPlatformAuthorisationCommandCog, CheckSUPlatformAuthorisationTaskCog, ) from .command_error import CommandErrorCog from .committee_actions_tracking import ( - CommitteeActionsTrackingContextCommandsCog, + CommitteeActionsTrackingContextCommandCog, CommitteeActionsTrackingSlashCommandsCog, ) from .delete_all import DeleteAllCommandsCog @@ -43,7 +43,7 @@ from .source import SourceCommandCog from .startup import StartupCog from .stats import StatsCommandsCog -from .strike import ManualModerationCog, StrikeCommandCog, StrikeContextCommandsCog +from .strike import ManualModerationCog, StrikeCommandsCog, StrikeContextCommandsCog from .write_roles import WriteRolesCommandCog if TYPE_CHECKING: @@ -94,14 +94,14 @@ def setup(bot: "TeXBot") -> None: """Add all the cogs to the bot, at bot startup.""" cogs: Iterable[type[TeXBotBaseCog]] = ( - AddUsersToThreadsAndChannelsCommandCog, + AddUsersToThreadsAndChannelsCommandsCog, AnnualRolesResetCommandCog, AnnualYearChannelsIncrementCommandCog, - ArchiveCommandCog, + ArchiveCommandsCog, ClearRemindersBacklogTaskCog, CommandErrorCog, CommitteeActionsTrackingSlashCommandsCog, - CommitteeActionsTrackingContextCommandsCog, + CommitteeActionsTrackingContextCommandCog, CommitteeHandoverCommandCog, DeleteAllCommandsCog, EditMessageCommandCog, @@ -125,7 +125,7 @@ def setup(bot: "TeXBot") -> None: SourceCommandCog, StartupCog, StatsCommandsCog, - StrikeCommandCog, + StrikeCommandsCog, StrikeContextCommandsCog, CheckSUPlatformAuthorisationTaskCog, WriteRolesCommandCog, diff --git a/cogs/add_users_to_threads_and_channels.py b/cogs/add_users_to_threads_and_channels.py index 05b49dc3..42d3ee8a 100644 --- a/cogs/add_users_to_threads_and_channels.py +++ b/cogs/add_users_to_threads_and_channels.py @@ -20,13 +20,13 @@ from utils import TeXBotApplicationContext, TeXBotAutocompleteContext -__all__: "Sequence[str]" = ("AddUsersToThreadsAndChannelsCommandCog",) +__all__: "Sequence[str]" = ("AddUsersToThreadsAndChannelsCommandsCog",) logger: "Final[Logger]" = logging.getLogger("TeX-Bot") -class AddUsersToThreadsAndChannelsCommandCog(TeXBotBaseCog): +class AddUsersToThreadsAndChannelsCommandsCog(TeXBotBaseCog): """Cog for adding users to threads.""" @staticmethod diff --git a/cogs/annual_handover_and_reset.py b/cogs/annual_handover_and_reset.py index 355babbe..75db3fd5 100644 --- a/cogs/annual_handover_and_reset.py +++ b/cogs/annual_handover_and_reset.py @@ -23,6 +23,7 @@ "CommitteeHandoverCommandCog", ) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") diff --git a/cogs/archive.py b/cogs/archive.py index 0a0029c0..bac7de4f 100644 --- a/cogs/archive.py +++ b/cogs/archive.py @@ -17,12 +17,13 @@ from utils import AllChannelTypes, TeXBotApplicationContext, TeXBotAutocompleteContext -__all__: "Sequence[str]" = ("ArchiveCommandCog",) +__all__: "Sequence[str]" = ("ArchiveCommandsCog",) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") -class ArchiveCommandCog(TeXBotBaseCog): +class ArchiveCommandsCog(TeXBotBaseCog): """Cog class that defines the "/archive" command and its call-back method.""" @staticmethod diff --git a/cogs/check_su_platform_authorisation.py b/cogs/check_su_platform_authorisation.py index c57012a8..c60c9fad 100644 --- a/cogs/check_su_platform_authorisation.py +++ b/cogs/check_su_platform_authorisation.py @@ -28,6 +28,7 @@ "CheckSUPlatformAuthorisationTaskCog", ) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") REQUEST_HEADERS: "Final[Mapping[str, str]]" = { diff --git a/cogs/command_error.py b/cogs/command_error.py index ccb74de6..609ed15c 100644 --- a/cogs/command_error.py +++ b/cogs/command_error.py @@ -20,6 +20,7 @@ __all__: "Sequence[str]" = ("CommandErrorCog",) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") diff --git a/cogs/committee_actions_tracking.py b/cogs/committee_actions_tracking.py index cdd79076..271c90d5 100644 --- a/cogs/committee_actions_tracking.py +++ b/cogs/committee_actions_tracking.py @@ -29,10 +29,11 @@ __all__: "Sequence[str]" = ( "CommitteeActionsTrackingBaseCog", - "CommitteeActionsTrackingContextCommandsCog", + "CommitteeActionsTrackingContextCommandCog", "CommitteeActionsTrackingSlashCommandsCog", ) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") @@ -348,7 +349,7 @@ async def update_status( # NOTE: Committee role check is not present because no ) @discord.option( name="description", - description="The description to be used for the action", + description="The description to be used for the action.", input_type=str, required=True, parameter_name="action_description", @@ -402,7 +403,7 @@ async def update_description( ) @discord.option( name="description", - description="The description to be used for the action", + description="The description to be used for the action.", input_type=str, required=True, parameter_name="action_description", @@ -461,11 +462,11 @@ async def action_random_user( @committee_actions.command( name="action-all-committee", - description="Creates an action with the description for every committee member", + description="Creates an action with the description for every committee member.", ) @discord.option( name="description", - description="The description to be used for the actions", + description="The description to be used for the actions.", input_type=str, required=True, parameter_name="action_description", @@ -526,7 +527,7 @@ async def action_all_committee( await ctx.respond(content=response_message) @committee_actions.command( - name="list", description="Lists all actions for a specified user" + name="list", description="Lists all actions for a specified user." ) @discord.option( name="user", @@ -838,7 +839,7 @@ async def delete_action(self, ctx: "TeXBotApplicationContext", action_id: str) - await ctx.respond(content=f"Action `{action_description}` successfully deleted.") -class CommitteeActionsTrackingContextCommandsCog(CommitteeActionsTrackingBaseCog): +class CommitteeActionsTrackingContextCommandCog(CommitteeActionsTrackingBaseCog): """Cog class to define the actions tracking message context commands.""" @discord.message_command( diff --git a/cogs/edit_message.py b/cogs/edit_message.py index 66b1405e..7d2b78a3 100644 --- a/cogs/edit_message.py +++ b/cogs/edit_message.py @@ -56,7 +56,7 @@ async def autocomplete_get_text_channels( parameter_name="str_channel_id", ) @discord.option( - name="message_id", + name="message-id", input_type=str, description="The ID of the message you wish to edit.", required=True, diff --git a/cogs/everest.py b/cogs/everest.py index 70dfe312..4047d832 100644 --- a/cogs/everest.py +++ b/cogs/everest.py @@ -18,6 +18,7 @@ __all__: "Sequence[str]" = ("EverestCommandCog",) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") MOUNT_EVEREST_TOTAL_STEPS: "Final[int]" = 44250 diff --git a/cogs/induct.py b/cogs/induct.py index d4363a82..21940dd2 100644 --- a/cogs/induct.py +++ b/cogs/induct.py @@ -35,6 +35,7 @@ "InductSlashCommandCog", ) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") diff --git a/cogs/kill.py b/cogs/kill.py index aafa0160..3fdb1207 100644 --- a/cogs/kill.py +++ b/cogs/kill.py @@ -18,6 +18,7 @@ __all__: "Sequence[str]" = ("ConfirmKillView", "KillCommandCog") + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") diff --git a/cogs/make_applicant.py b/cogs/make_applicant.py index 72045354..619d7756 100644 --- a/cogs/make_applicant.py +++ b/cogs/make_applicant.py @@ -21,6 +21,7 @@ "MakeApplicantSlashCommandCog", ) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") diff --git a/cogs/send_get_roles_reminders.py b/cogs/send_get_roles_reminders.py index 606f1bfb..3add4980 100644 --- a/cogs/send_get_roles_reminders.py +++ b/cogs/send_get_roles_reminders.py @@ -28,6 +28,7 @@ __all__: "Sequence[str]" = ("SendGetRolesRemindersTaskCog",) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") diff --git a/cogs/send_introduction_reminders.py b/cogs/send_introduction_reminders.py index 720bc616..44d5b9c6 100644 --- a/cogs/send_introduction_reminders.py +++ b/cogs/send_introduction_reminders.py @@ -34,7 +34,8 @@ __all__: "Sequence[str]" = ("SendIntroductionRemindersTaskCog",) -logger: "Logger" = logging.getLogger("TeX-Bot") + +logger: "Final[Logger]" = logging.getLogger("TeX-Bot") class SendIntroductionRemindersTaskCog(TeXBotBaseCog): diff --git a/cogs/startup.py b/cogs/startup.py index 0051db07..d84430c6 100644 --- a/cogs/startup.py +++ b/cogs/startup.py @@ -26,6 +26,7 @@ __all__: "Sequence[str]" = ("StartupCog",) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") diff --git a/cogs/strike.py b/cogs/strike.py index c5113683..4b312059 100644 --- a/cogs/strike.py +++ b/cogs/strike.py @@ -40,11 +40,12 @@ "ConfirmStrikeMemberView", "ConfirmStrikesOutOfSyncWithBanView", "ManualModerationCog", - "StrikeCommandCog", + "StrikeCommandsCog", "StrikeContextCommandsCog", "perform_moderation_action", ) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") FORMATTED_MODERATION_ACTIONS: "Final[Mapping[discord.AuditLogAction, str]]" = { @@ -797,7 +798,7 @@ async def on_member_ban( ) -class StrikeCommandCog(BaseStrikeCog): +class StrikeCommandsCog(BaseStrikeCog): """Cog class that defines the "/strike" command and its call-back method.""" @staticmethod diff --git a/config.py b/config.py index d2107818..284fef8c 100644 --- a/config.py +++ b/config.py @@ -43,6 +43,7 @@ "settings", ) + PROJECT_ROOT: "Final[Path]" = Path(__file__).parent.resolve() TRUE_VALUES: "Final[AbstractSet[str]]" = {"true", "1", "t", "y", "yes", "on"} @@ -987,7 +988,7 @@ def _setup_env_variables(cls) -> None: def _settings_class_factory() -> type[Settings]: @final - class RuntimeSettings(Settings): + class RuntimeSettings(Settings): # noqa: CAR160 """ Settings class that provides access to all settings values. diff --git a/db/__init__.py b/db/__init__.py index 02debfb1..ced013d7 100644 --- a/db/__init__.py +++ b/db/__init__.py @@ -10,6 +10,7 @@ __all__: "Sequence[str]" = () + os.environ["DJANGO_SETTINGS_MODULE"] = "db._settings" diff --git a/db/core/models/__init__.py b/db/core/models/__init__.py index 3978d25d..d9be7b40 100644 --- a/db/core/models/__init__.py +++ b/db/core/models/__init__.py @@ -211,7 +211,7 @@ class Meta(TypedModelMeta): # noqa: D106 @override def __setattr__(self, name: str, value: object) -> None: if name == "group_member_id": - if not isinstance(value, str | int): + if not isinstance(value, (str, int)): INVALID_GROUP_MEMBER_ID_TYPE_MESSAGE: Final[str] = ( "group_member_id must be an instance of str or int." ) diff --git a/db/core/models/utils.py b/db/core/models/utils.py index 2c024ee8..c030cd70 100644 --- a/db/core/models/utils.py +++ b/db/core/models/utils.py @@ -33,14 +33,14 @@ class Meta(TypedModelMeta): # noqa: D106 abstract: "ClassVar[bool]" = True @override - def __init__(self, *args: object, **kwargs: object) -> None: + def __init__(self, *args: object, **kwargs: object) -> None: # noqa: CAR150 proxy_fields: dict[str, object] = { field_name: kwargs.pop(field_name) for field_name in set(kwargs.keys()) & self._get_proxy_field_names() } with transaction.atomic(): - super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # noqa: 151 field_name: str value: object @@ -73,7 +73,7 @@ def update( force_update: bool = False, using: str | None = None, update_fields: "Iterable[str] | None" = None, - **kwargs: object, + **kwargs: object, # noqa: CAR150 ) -> None: """ Change an in-memory object's values, then save it to the database. @@ -127,7 +127,7 @@ async def aupdate( force_update: bool = False, using: str | None = None, update_fields: "Iterable[str] | None" = None, - **kwargs: object, + **kwargs: object, # noqa: CAR150 ) -> None: """ Asynchronously change an in-memory object's values, then save it to the database. diff --git a/main.py b/main.py index af195879..67f33b2f 100755 --- a/main.py +++ b/main.py @@ -13,7 +13,7 @@ import config from config import settings -from utils import TeXBot +from utils import SuppressTraceback, TeXBot if TYPE_CHECKING: from collections.abc import Sequence @@ -21,17 +21,18 @@ __all__: "Sequence[str]" = ("bot",) -# with SuppressTraceback(): -config.run_setup() -intents: discord.Intents = discord.Intents.default() -intents.members = True +with SuppressTraceback(): + config.run_setup() -bot: TeXBot = TeXBot( - intents=intents -) # NOTE: See https://github.com/CSSUoB/TeX-Bot-Py-V2/issues/261 + intents: discord.Intents = discord.Intents.default() + intents.members = True -bot.load_extension("cogs") + bot: TeXBot = TeXBot( + intents=intents + ) # NOTE: See https://github.com/CSSUoB/TeX-Bot-Py-V2/issues/261 + + bot.load_extension("cogs") def _run_bot() -> "NoReturn": # NOTE: See https://github.com/CSSUoB/TeX-Bot-Py-V2/issues/261 diff --git a/tests/test_utils.py b/tests/test_utils.py index 7d27e4ae..866335f1 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -12,6 +12,7 @@ __all__: "Sequence[str]" = () + # TODO(CarrotManMatt): Move to stats_tests # noqa: FIX002 # https://github.com/CSSUoB/TeX-Bot-Py-V2/issues/57 # class TestPlotBarChart: diff --git a/utils/__init__.py b/utils/__init__.py index 62c3eade..48ce5285 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -33,6 +33,7 @@ "is_running_in_async", ) + if TYPE_CHECKING: type AllChannelTypes = ( discord.VoiceChannel diff --git a/utils/error_capture_decorators.py b/utils/error_capture_decorators.py index c2a821ea..d0a43497 100644 --- a/utils/error_capture_decorators.py +++ b/utils/error_capture_decorators.py @@ -23,6 +23,7 @@ "capture_strike_tracking_error", ) + if TYPE_CHECKING: type WrapperInputFunc[**P, T_ret] = ( Callable[Concatenate[TeXBotBaseCog, P], Coroutine[object, object, T_ret]] @@ -56,7 +57,7 @@ def capture_error_and_close[**P, T_ret, T_cog: TeXBotBaseCog]( """ # noqa: D401 @functools.wraps(func) - async def wrapper(self: T_cog, /, *args: P.args, **kwargs: P.kwargs) -> T_ret | None: # type: ignore[misc] + async def wrapper(self: T_cog, /, *args: P.args, **kwargs: P.kwargs) -> T_ret | None: # type: ignore[misc] # noqa: CAR150 try: return await func(self, *args, **kwargs) except error_type as error: diff --git a/utils/message_sender_components.py b/utils/message_sender_components.py index 7f5493f9..99de05c1 100644 --- a/utils/message_sender_components.py +++ b/utils/message_sender_components.py @@ -19,6 +19,7 @@ "ResponseMessageSender", ) + if TYPE_CHECKING: class _BaseChannelSendKwargs(TypedDict): diff --git a/utils/tex_bot.py b/utils/tex_bot.py index a45c3c3b..ae231966 100644 --- a/utils/tex_bot.py +++ b/utils/tex_bot.py @@ -35,6 +35,7 @@ __all__: "Sequence[str]" = ("TeXBot",) + logger: "Final[Logger]" = logging.getLogger("TeX-Bot") @@ -48,7 +49,7 @@ class TeXBot(discord.Bot): """ @override - def __init__(self, *args: object, **options: object) -> None: + def __init__(self, *args: object, **options: object) -> None: # noqa: CAR150 """Initialise a new Pycord Bot subclass with empty shortcut accessors.""" self._main_guild: discord.Guild | None = None self._committee_role: discord.Role | None = None @@ -64,7 +65,7 @@ def __init__(self, *args: object, **options: object) -> None: self._main_guild_set: bool = False - super().__init__(*args, **options) # type: ignore[no-untyped-call] + super().__init__(*args, **options) # type: ignore[no-untyped-call] # noqa: CAR151 @override async def close(self) -> "NoReturn": # type: ignore[misc] From ae6bc208b618922dfcaf47fbf34e7f344a5e78f7 Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Thu, 16 Oct 2025 15:30:04 +0100 Subject: [PATCH 05/12] Fix missing locales in CI --- .github/workflows/check-build-deploy.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/check-build-deploy.yaml b/.github/workflows/check-build-deploy.yaml index 87391647..aa9063dc 100644 --- a/.github/workflows/check-build-deploy.yaml +++ b/.github/workflows/check-build-deploy.yaml @@ -93,6 +93,13 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Add GB Locale + run: | + sudo apt-get update + sudo apt-get install -y locales + sudo locale-gen en_GB.UTF-8 + shell: bash + - name: Set Up Python uses: actions/setup-python@v6 with: From ca185adb868a56f15dadac40fbf891823d6caf1d Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Thu, 16 Oct 2025 19:37:29 +0100 Subject: [PATCH 06/12] Fix linting ignore statements --- .flake8 | 1 - cogs/add_users_to_threads_and_channels.py | 2 +- cogs/archive.py | 2 +- cogs/induct.py | 2 +- cogs/kill.py | 2 +- cogs/make_member.py | 4 ++-- cogs/send_introduction_reminders.py | 13 +++++++------ cogs/strike.py | 8 ++++---- utils/tex_bot.py | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.flake8 b/.flake8 index dd38a479..c3c9502c 100644 --- a/.flake8 +++ b/.flake8 @@ -2,4 +2,3 @@ select = CAR,TXB extend_exclude = migrations,.venv require_plugins = flake8-carrot -ignore = CAR180 diff --git a/cogs/add_users_to_threads_and_channels.py b/cogs/add_users_to_threads_and_channels.py index 42d3ee8a..0eb47896 100644 --- a/cogs/add_users_to_threads_and_channels.py +++ b/cogs/add_users_to_threads_and_channels.py @@ -172,7 +172,7 @@ async def on_thread_create(self, thread: discord.Thread) -> None: committee_elect_role: discord.Role = await self.bot.committee_elect_role if ( - thread.parent is None + thread.parent is None # noqa: CAR180 or thread.parent.category is None or "committee" not in thread.parent.category.name.lower() or not settings["AUTO_ADD_COMMITTEE_TO_THREADS"] diff --git a/cogs/archive.py b/cogs/archive.py index bac7de4f..1980a560 100644 --- a/cogs/archive.py +++ b/cogs/archive.py @@ -90,7 +90,7 @@ async def autocomplete_get_non_archived_channels( discord.OptionChoice(name=channel.name, value=str(channel.id)) for channel in main_guild.channels if ( - not isinstance(channel, discord.CategoryChannel) + not isinstance(channel, discord.CategoryChannel) # noqa: CAR180 and channel.category and "archive" not in channel.category.name.lower() and isinstance(interaction_user, discord.Member) diff --git a/cogs/induct.py b/cogs/induct.py index 21940dd2..dd52a938 100644 --- a/cogs/induct.py +++ b/cogs/induct.py @@ -54,7 +54,7 @@ async def on_member_update(self, before: discord.Member, after: discord.Member) # NOTE: Shortcut accessors are placed at the top of the function so that the exceptions they raise are displayed before any further errors may be sent main_guild: discord.Guild = self.bot.main_guild - if before.guild != main_guild or after.guild != main_guild or before.bot or after.bot: + if before.guild != main_guild or after.guild != main_guild or before.bot or after.bot: # noqa: CAR180 return try: diff --git a/cogs/kill.py b/cogs/kill.py index 3fdb1207..2ca4e3b3 100644 --- a/cogs/kill.py +++ b/cogs/kill.py @@ -83,7 +83,7 @@ async def kill(self, ctx: "TeXBotApplicationContext") -> None: button_interaction: discord.Interaction = await self.bot.wait_for( "interaction", check=lambda interaction: ( - interaction.type == discord.InteractionType.component + interaction.type == discord.InteractionType.component # noqa: CAR180 and interaction.message.id == confirmation_message.id and ((committee_role in interaction.user.roles) if committee_role else True) and "custom_id" in interaction.data diff --git a/cogs/make_member.py b/cogs/make_member.py index 007a04f8..6139b209 100644 --- a/cogs/make_member.py +++ b/cogs/make_member.py @@ -32,7 +32,7 @@ if ( settings["_GROUP_FULL_NAME"] and ( - "computer science society" in settings["_GROUP_FULL_NAME"].lower() + "computer science society" in settings["_GROUP_FULL_NAME"].lower() # noqa: CAR180 or "css" in settings["_GROUP_FULL_NAME"].lower() or "uob" in settings["_GROUP_FULL_NAME"].lower() or "university of birmingham" in settings["_GROUP_FULL_NAME"].lower() @@ -69,7 +69,7 @@ class MakeMemberCommandCog(TeXBotBaseCog): if ( settings["_GROUP_FULL_NAME"] and ( - "computer science society" in settings["_GROUP_FULL_NAME"].lower() + "computer science society" in settings["_GROUP_FULL_NAME"].lower() # noqa: CAR180 or "css" in settings["_GROUP_FULL_NAME"].lower() or "uob" in settings["_GROUP_FULL_NAME"].lower() or "university of birmingham" in settings["_GROUP_FULL_NAME"].lower() diff --git a/cogs/send_introduction_reminders.py b/cogs/send_introduction_reminders.py index 44d5b9c6..67480680 100644 --- a/cogs/send_introduction_reminders.py +++ b/cogs/send_introduction_reminders.py @@ -132,14 +132,15 @@ async def send_introduction_reminders(self) -> None: continue async for message in member.history(): - MESSAGE_CONTAINS_OPT_IN_OUT_BUTTON: bool = bool( - bool(message.components) + if ( + message.components # noqa: CAR180 and isinstance(message.components[0], discord.ActionRow) and isinstance(message.components[0].children[0], discord.Button) - and message.components[0].children[0].custom_id - == "opt_out_introduction_reminders_button" - ) - if MESSAGE_CONTAINS_OPT_IN_OUT_BUTTON: + and ( + message.components[0].children[0].custom_id + == "opt_out_introduction_reminders_button" + ) + ): await message.edit(view=None) if ( diff --git a/cogs/strike.py b/cogs/strike.py index 4b312059..70bfcbe8 100644 --- a/cogs/strike.py +++ b/cogs/strike.py @@ -277,7 +277,7 @@ async def _confirm_perform_moderation_action( button_interaction: discord.Interaction = await self.bot.wait_for( "interaction", check=lambda interaction: ( - interaction.type == discord.InteractionType.component + interaction.type == discord.InteractionType.component # noqa: CAR180 and interaction.user == interaction_user and interaction.channel == button_callback_channel and "custom_id" in interaction.data @@ -587,7 +587,7 @@ async def _confirm_manual_add_strike( # noqa: PLR0915 out_of_sync_ban_button_interaction: discord.Interaction = await self.bot.wait_for( "interaction", check=lambda interaction: ( - interaction.type == discord.InteractionType.component + interaction.type == discord.InteractionType.component # noqa: CAR180 and ( (interaction.user == applied_action_user) if not applied_action_user.bot @@ -684,7 +684,7 @@ async def _confirm_manual_add_strike( # noqa: PLR0915 button_interaction: discord.Interaction = await self.bot.wait_for( "interaction", check=lambda interaction: ( - interaction.type == discord.InteractionType.component + interaction.type == discord.InteractionType.component # noqa: CAR180 and ( (interaction.user == applied_action_user) if not applied_action_user.bot @@ -742,7 +742,7 @@ async def on_member_update(self, before: discord.Member, after: discord.Member) # NOTE: Shortcut accessors are placed at the top of the function so that the exceptions they raise are displayed before any further errors may be sent main_guild: discord.Guild = self.bot.main_guild - if before.guild != main_guild or after.guild != main_guild or before.bot or after.bot: + if before.guild != main_guild or after.guild != main_guild or before.bot or after.bot: # noqa: CAR180 return if not after.timed_out or before.timed_out == after.timed_out: diff --git a/utils/tex_bot.py b/utils/tex_bot.py index ae231966..a004318e 100644 --- a/utils/tex_bot.py +++ b/utils/tex_bot.py @@ -340,7 +340,7 @@ def group_member_id_type(self) -> str: return ( "UoB Student" if ( - "computer science society" in self.group_full_name.lower() + "computer science society" in self.group_full_name.lower() # noqa: CAR180 or "css" in self.group_full_name.lower() or "uob" in self.group_full_name.lower() or "university of birmingham" in self.group_full_name.lower() @@ -363,7 +363,7 @@ def group_moderation_contact(self) -> str: return ( "the Guild of Students" if ( - "computer science society" in self.group_full_name.lower() + "computer science society" in self.group_full_name.lower() # noqa: CAR180 or "css" in self.group_full_name.lower() or "uob" in self.group_full_name.lower() or "university of birmingham" in self.group_full_name.lower() From e0377ddb3bf1bc630cbd7c91809e5172c184db4e Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Thu, 16 Oct 2025 22:00:21 +0100 Subject: [PATCH 07/12] Fix yaml formatting mistakes --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47f8dff4..c72aacf8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -95,7 +95,7 @@ repos: - repo: https://github.com/google/yamlfmt rev: v0.18.1 hooks: - - id: yamlfmt - entry: yamlfmt - types: [yaml] + - entry: yamlfmt + id: yamlfmt pass_filenames: true + types: [yaml] From 55cb6939be60e76bbd666f3abb8c6fe5ea3a0d85 Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Thu, 16 Oct 2025 22:03:38 +0100 Subject: [PATCH 08/12] Fix union ordering --- cogs/committee_actions_tracking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cogs/committee_actions_tracking.py b/cogs/committee_actions_tracking.py index 271c90d5..c19643cf 100644 --- a/cogs/committee_actions_tracking.py +++ b/cogs/committee_actions_tracking.py @@ -557,9 +557,9 @@ async def action_all_committee( async def list_user_actions( # NOTE: Committee role check is not present because non-committee can have actions, and need to be able to list their own actions. self, ctx: "TeXBotApplicationContext", - action_member_id: None | str, + action_member_id: str | None, ping: bool, # noqa: FBT001 - status: None | str, + status: str | None, ) -> None: """ Definition and callback of the "/list" command. From ee76c45f2d6fcc12a8739ea631247b90dd9a87d0 Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Mon, 20 Oct 2025 23:31:53 +0100 Subject: [PATCH 09/12] Update check-build-deploy.yaml Signed-off-by: Matt Norton --- .github/workflows/check-build-deploy.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/check-build-deploy.yaml b/.github/workflows/check-build-deploy.yaml index aa9063dc..bcd91df6 100644 --- a/.github/workflows/check-build-deploy.yaml +++ b/.github/workflows/check-build-deploy.yaml @@ -22,8 +22,7 @@ jobs: - name: Check uv.lock (ensure all dependencies up to date) run: uv lock --check - # yamllint disable-line rule:key-ordering - flake8: + flake8: # yamllint disable-line rule:key-ordering env: UV_FROZEN: true UV_NO_SYNC: true From abd0445b660557138bd11d1e2d8caefcaee6f11f Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Mon, 20 Oct 2025 23:32:25 +0100 Subject: [PATCH 10/12] Update utils.py Signed-off-by: Matt Norton --- db/core/models/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/core/models/utils.py b/db/core/models/utils.py index c030cd70..dad75218 100644 --- a/db/core/models/utils.py +++ b/db/core/models/utils.py @@ -40,7 +40,7 @@ def __init__(self, *args: object, **kwargs: object) -> None: # noqa: CAR150 } with transaction.atomic(): - super().__init__(*args, **kwargs) # noqa: 151 + super().__init__(*args, **kwargs) # noqa: CAR151 field_name: str value: object From ebd81b938c0ea20df4284cb035ac1255d184defc Mon Sep 17 00:00:00 2001 From: Matt Norton Date: Sun, 26 Oct 2025 04:05:43 +0000 Subject: [PATCH 11/12] Improve type annotations for config values Signed-off-by: Matt Norton --- config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config.py b/config.py index e8d4f92a..cc6ae922 100644 --- a/config.py +++ b/config.py @@ -31,7 +31,7 @@ from collections.abc import Sequence from collections.abc import Set as AbstractSet from logging import Logger - from typing import IO, Any, ClassVar, Final + from typing import IO, Any, ClassVar, Final, LiteralString __all__: "Sequence[str]" = ( "DEFAULT_STATISTICS_ROLES", @@ -46,12 +46,12 @@ PROJECT_ROOT: "Final[Path]" = Path(__file__).parent.resolve() -TRUE_VALUES: "Final[AbstractSet[str]]" = {"true", "1", "t", "y", "yes", "on"} -FALSE_VALUES: "Final[AbstractSet[str]]" = {"false", "0", "f", "n", "no", "off"} -VALID_SEND_INTRODUCTION_REMINDERS_VALUES: "Final[AbstractSet[str]]" = ( +TRUE_VALUES: "Final[AbstractSet[LiteralString]]" = {"true", "1", "t", "y", "yes", "on"} +FALSE_VALUES: "Final[AbstractSet[LiteralString]]" = {"false", "0", "f", "n", "no", "off"} +VALID_SEND_INTRODUCTION_REMINDERS_VALUES: "Final[AbstractSet[LiteralString]]" = ( {"once", "interval"} | TRUE_VALUES | FALSE_VALUES ) -DEFAULT_STATISTICS_ROLES: "Final[AbstractSet[str]]" = { +DEFAULT_STATISTICS_ROLES: "Final[AbstractSet[LiteralString]]" = { "Committee", "Committee-Elect", "Student Rep", @@ -70,7 +70,7 @@ "Postdoc", "Quiz Victor", } -LOG_LEVEL_CHOICES: "Final[Sequence[str]]" = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL") +LOG_LEVEL_CHOICES: "Final[Sequence[LiteralString]]" = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL") logger: "Final[Logger]" = logging.getLogger("TeX-Bot") discord_logger: "Final[Logger]" = logging.getLogger("discord") From 4611086208d49650355b2c600bb82472b10c002c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sun, 26 Oct 2025 04:06:45 +0000 Subject: [PATCH 12/12] [pre-commit.ci lite] apply automatic fixes --- config.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config.py b/config.py index cc6ae922..e38a9818 100644 --- a/config.py +++ b/config.py @@ -70,7 +70,13 @@ "Postdoc", "Quiz Victor", } -LOG_LEVEL_CHOICES: "Final[Sequence[LiteralString]]" = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL") +LOG_LEVEL_CHOICES: "Final[Sequence[LiteralString]]" = ( + "DEBUG", + "INFO", + "WARNING", + "ERROR", + "CRITICAL", +) logger: "Final[Logger]" = logging.getLogger("TeX-Bot") discord_logger: "Final[Logger]" = logging.getLogger("discord")