Skip to content

Add comprehensive type annotations and enforce type checking in CI #2362

@m3nu

Description

@m3nu

Summary

Add comprehensive type annotations throughout the Vorta codebase and enforce type checking via CI. This will improve code quality, catch bugs earlier, enable better IDE support, and make the codebase more maintainable.

Current State

  • Type coverage: ~22% of files import typing, ~32% have actual type hints
  • No type checker: Neither mypy nor pyright is configured
  • No py.typed marker: Package cannot be used as a typed library
  • Inconsistent annotations: Some files have excellent type hints (e.g., network_status/abc.py, store/settings.py), while others have none (e.g., borg/borg_job.py, views/repo_tab.py)
  • Minimum Python version: Currently 3.8

Examples of current inconsistency

Well-typed file (src/vorta/network_status/abc.py):

def is_network_metered(self) -> bool:
    """Is the currently connected network a metered connection?"""

def get_current_wifi(self) -> Optional[str]:
    """Get current SSID or None if Wifi is off."""

def get_known_wifis(self) -> List['SystemWifiInfo']:
    """Get WiFi networks known to system."""

Untyped file (src/vorta/borg/create.py):

def process_result(self, result):
    if result['returncode'] in [0, 1] and 'archive' in result['data']:

Proposal

1. Bump minimum Python version to 3.10

Rationale:

  • Python 3.8 reached end-of-life in October 2024
  • Python 3.9 reaches end-of-life in October 2025
  • Python 3.10+ provides better typing ergonomics:
    • X | Y union syntax instead of Union[X, Y]
    • list[str] instead of List[str] (builtin generics)
    • ParamSpec and TypeAlias for advanced patterns
    • Better error messages in type checkers
  • PyQt6 (which Vorta uses) requires Python 3.6.1+ minimum, so this is compatible
  • Most Linux distributions shipping Vorta have Python 3.10+ available

Alternative: Stay at 3.9 minimum if 3.10 is too aggressive, but strongly recommend 3.10+.

2. Choose and configure a type checker

Recommended: mypy

Reasons:

  • Industry standard, well-documented
  • Good PyQt/Qt stubs available (PyQt6-stubs)
  • Mature, stable tool
  • Integrates well with pre-commit and CI

Configuration to add to pyproject.toml:

[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false  # Start permissive, tighten later
check_untyped_defs = true
ignore_missing_imports = true  # For untyped dependencies

# Per-module overrides for gradual adoption
[[tool.mypy.overrides]]
module = "vorta.store.*"
disallow_untyped_defs = true

[[tool.mypy.overrides]]
module = "vorta.network_status.*"
disallow_untyped_defs = true

3. Add type stubs for dependencies

uv add --dev mypy PyQt6-stubs types-peewee

4. Add to CI and pre-commit

.pre-commit-config.yaml:

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.13.0
    hooks:
      - id: mypy
        additional_dependencies:
          - PyQt6-stubs
          - types-peewee
        args: [--config-file=pyproject.toml]

.github/workflows/test.yml - add to lint job:

- name: Type check
  run: uv run mypy src/vorta

5. Add py.typed marker

Create src/vorta/py.typed (empty file) to indicate the package ships type information.

6. Phased rollout

Attempting to type the entire codebase at once would be overwhelming. Recommend a phased approach:

Phase 1: Infrastructure (this issue)

  • Bump Python minimum to 3.10
  • Configure mypy with permissive settings
  • Add to CI (warnings only, don't fail builds initially)
  • Add py.typed marker
  • Add type stubs for dependencies

Phase 2: Core modules

  • src/vorta/store/ - Data models and settings (already partially typed)
  • src/vorta/network_status/ - Already well-typed, just enforce
  • src/vorta/scheduler.py - Already partially typed

Phase 3: Borg layer

  • src/vorta/borg/borg_job.py - Base job class
  • src/vorta/borg/*.py - All borg command implementations
  • src/vorta/jobs_manager.py

Phase 4: Views layer

  • src/vorta/views/*.py - PyQt6 views (most complex due to dynamic UI loading)
  • src/vorta/tray_menu.py

Phase 5: Enforcement

  • Enable disallow_untyped_defs = true globally
  • Make mypy failures block CI
  • Remove ignore_missing_imports where possible

Benefits

  1. Catch bugs at development time - Type mismatches caught before runtime
  2. Better IDE support - Autocomplete, inline docs, refactoring tools work better
  3. Self-documenting code - Types serve as documentation
  4. Easier onboarding - New contributors understand code faster
  5. Safer refactoring - Type checker catches breaking changes

Challenges to address

  1. PyQt6 dynamic UI loading - uic.loadUiType() returns dynamically created classes. May need # type: ignore comments or custom stubs.
  2. Peewee ORM - Model fields have runtime behavior that's tricky to type. types-peewee stubs help but aren't perfect.
  3. Large codebase - 81 Python files means significant effort. Phased approach essential.

Related work

  • Ruff already enforces code style
  • Pre-commit hooks already run on commits
  • CI already blocks on lint failures

Adding type checking fits naturally into the existing quality infrastructure.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    status:planningFor large features, plan it out before implementationtype:enhancementImprovement of an existing function

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions