diff --git a/.github/matchers/mypy.json b/.github/matchers/mypy.json new file mode 100644 index 0000000000..948387ca01 --- /dev/null +++ b/.github/matchers/mypy.json @@ -0,0 +1,18 @@ +{ + "problemMatcher": [ + { + "owner": "mypy", + "pattern": [ + { + "regexp": "^([^:]*):(\\d+):(?:(\\d+):)? ([^:]*): (.*?)(?: \\[(\\S+)\\])?$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5, + "code": 6 + } + ] + } + ] +} diff --git a/.github/matchers/mypy.json.license b/.github/matchers/mypy.json.license new file mode 100644 index 0000000000..015248065d --- /dev/null +++ b/.github/matchers/mypy.json.license @@ -0,0 +1,5 @@ +Copyright © Michal Čihař + +SPDX-License-Identifier: CC0-1.0 + +This file is maintained in https://github.com/WeblateOrg/meta/ diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml new file mode 100644 index 0000000000..47fd2b061a --- /dev/null +++ b/.github/workflows/mypy.yml @@ -0,0 +1,70 @@ +# Copyright © Michal Čihař +# +# SPDX-License-Identifier: CC0-1.0 + +name: mypy + +on: + push: + branches-ignore: + - deepsource-fix-** + - renovate/** + - weblate + pull_request: + +permissions: + contents: read + +jobs: + mypy: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 2 + - name: Get changed files + if: github.event_name == 'pull_request' + id: changed-files + uses: tj-actions/changed-files@v45 + with: + files: '**.py' + - name: List all changed files + if: github.event_name == 'pull_request' + env: + ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} + run: | + touch changed-files.txt + for file in ${ALL_CHANGED_FILES}; do + echo "$file" >> changed-files.txt + done + cat changed-files.txt + + - uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + cache-dependency-glob: '' + cache-suffix: '3.12' + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install pip dependencies + run: uv pip install --system -r requirements-dev.txt + + - name: Run mypy + run: mypy --show-column-numbers weblate_web > mypy.log + # Temporary hack until we have this fully working + continue-on-error: true + - name: Report mypy + if: always() + run: | + echo "::add-matcher::.github/matchers/mypy.json" + if [ -f changed-files.txt ] ; then + if grep --silent --fixed-strings --file=changed-files.txt mypy.log ; then + grep --fixed-strings --file=changed-files.txt mypy.log + fi + else + cat mypy.log + fi + echo "::remove-matcher owner=mypy::" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 49f27cc453..c8cca32bd8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: cache-dependency-glob: '' cache-suffix: ${{ matrix.python-version }} - name: Install pip dependencies - run: uv pip install --system -r requirements.txt -r requirements-test.txt + run: uv pip install --system -r requirements-dev.txt - name: Compile MO files run: ./scripts/generate-locales - name: Collect static files diff --git a/pyproject.toml b/pyproject.toml index f13d29ff64..4b59b0408f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,19 @@ [tool.black] target-version = ['py311'] +[tool.django-stubs] +django_settings_module = "weblate_web.settings" + [tool.isort] profile = "black" +[tool.mypy] +check_untyped_defs = true +plugins = [ + "mypy_django_plugin.main", + "mypy_drf_plugin.main" +] + [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "weblate_web.settings" addopts = "--reuse-db --cov=weblate_web --cov-report=" @@ -74,3 +84,5 @@ max-complexity = 16 "scripts/*" = ["T201"] "weblate_web/migrations/0031_fill_in_customer.py" = ["T201"] "weblate_web/payments/backends.py" = ["T201"] + +# strict_settings = false diff --git a/requirements-dev.txt b/requirements-dev.txt index 172b0c2d3a..b93f534b5f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,13 @@ -r requirements-lint.txt -r requirements-test.txt -r requirements.txt +django-stubs==5.1.0 +django-stubs-ext==5.1.0 +djangorestframework-stubs==3.15.1 +mypy==1.12.1 PyICU translate-toolkit +types-lxml==2024.9.16 +types-paramiko==3.5.0.20240928 +types-python-dateutil==2.9.0.20241003 +types-requests==2.32.0.20241016 diff --git a/weblate_web/invoices/models.py b/weblate_web/invoices/models.py index f8057a9f94..d646560cd7 100644 --- a/weblate_web/invoices/models.py +++ b/weblate_web/invoices/models.py @@ -39,12 +39,13 @@ def url_fetcher(url: str) -> dict[str, str | bytes]: if not url.startswith(INVOICES_URL): raise ValueError(f"Usupported URL: {url}") - filename = url.removeprefix(INVOICES_URL) + basename = url.removeprefix(INVOICES_URL) + filename = TEMPLATES_PATH / basename result = { - "filename": filename, - "string": (TEMPLATES_PATH / filename).read_bytes(), + "filename": basename, + "string": filename.read_bytes(), } - if filename.endswith("css"): + if basename.endswith("css"): result["mime_type"] = "text/css" result["encoding"] = "utf-8" return result @@ -269,7 +270,7 @@ def display_price(self): def display_total_price(self): return self.invoice.render_amount(self.unit_price * self.quantity) - def get_quantity_unit_display(self) -> str: + def get_quantity_unit_display(self) -> str: # types: ignore[no-redef] # Correcly handle singulars if self.quantity_unit == QuantityUnitChoices.HOURS and self.quantity == 1: return "hour"