Skip to content

Latest commit

 

History

History
430 lines (315 loc) · 13.3 KB

File metadata and controls

430 lines (315 loc) · 13.3 KB

Contributing to PyKotor

Thank you for your interest in contributing to PyKotor! This guide covers development setup, coding standards, and the contribution workflow.

Table of Contents

Prerequisites

  • Python 3.8+ (3.9+ recommended for development)
  • Platforms: Windows 7–11, macOS, Linux; common architectures (amd64/arm64) supported
  • Git for version control
  • A code editor with Python support (VS Code, PyCharm, etc.)

Development Setup

1. Clone the Repository

git clone https://github.com/OpenKotOR/PyKotor.git
cd PyKotor

Initialize the wiki submodule when you need the full Markdown corpus under wiki/ (link checks, Holocron help packaging, bulk doc edits):

git submodule update --init wiki

The GitHub wiki web UI mirrors that content. If you change wiki/*.md, follow the dual-repository workflow in .github/copilot-instructions.md: commit and push to PyKotor.wiki, then commit the updated submodule pointer in this repository.

Faster clone (if full clone is slow or stalls): The repository is large (~380 MB). If the clone hangs around 8–15%, use a shallow clone:

# Shallow clone: only latest commit (fast, ~50–100 MB)
git clone --depth 1 https://github.com/OpenKotOR/PyKotor.git
cd PyKotor

To later fetch full history if needed (e.g. for bisect): git fetch --unshallow.

Optional: clone without blob content first, then fetch as needed:

git clone --filter=blob:none --sparse https://github.com/OpenKotOR/PyKotor.git
cd PyKotor

2. Choose Your Setup Method

Option A: Using uv (Recommended - Fastest)

Install uv from astral-sh/uv installation.

Run tools with --with-editable to use your local source:

# PyKotor CLI
uvx --with-editable Libraries/PyKotor pykotor --help

# Tools (add --with-editable for each package you're developing)
uvx --with-editable Libraries/PyKotor --with-editable Tools/HolocronToolset holocrontoolset
uvx --with-editable Libraries/PyKotor --with-editable Tools/HoloPatcher holopatcher
uvx --with-editable Libraries/PyKotor --with-editable Tools/KotorDiff kotordiff

Or run directly from source:

uv run --directory Libraries/PyKotor/src --module pykotor --help
uv run --directory Tools/HolocronToolset/src --module toolset
uv run --directory Tools/HoloPatcher/src --module holopatcher

To install packages in editable mode with uv:

uv pip install -e "Libraries/PyKotor[all,dev]"
# PyKotor depends on workspace member ``bioware-kaitai-formats`` (import ``bioware_kaitai_formats``).
# From repo root, ``uv sync`` installs it; for isolated ``pip install -e Libraries/PyKotor``, install
# ``Libraries/bioware-kaitai-formats`` editable first or use a published ``bioware-kaitai-formats`` wheel on PyPI.
uv pip install -e "Tools/HolocronToolset"
uv pip install -e "Tools/HoloPatcher"
uv pip install -e "Tools/KotorDiff"

Option B: Using pip with venv

# Windows
python -m venv .venv
.venv\Scripts\Activate.ps1
python -m ensurepip
python -m pip install --upgrade pip
python -m pip install -r Tools/HolocronToolset/requirements.txt
python Tools/HolocronToolset/src/toolset/__main__.py
# Repeat for HoloPatcher, KotorDiff, etc. as needed

# macOS/Linux
python3 -m venv .venv
source .venv/bin/activate
python3 -m ensurepip
python3 -m pip install --upgrade pip
python3 -m pip install -r Tools/HolocronToolset/requirements.txt
python3 Tools/HolocronToolset/src/toolset/__main__.py

Or install in editable mode:

# Windows
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -e "Libraries/PyKotor[all,dev]"
python -m pip install -e Tools/HolocronToolset -e Tools/HoloPatcher
# holocrontoolset, holopatcher should now be on PATH

# macOS/Linux
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -e "Libraries/PyKotor[all,dev]"
python3 -m pip install -e Tools/HolocronToolset -e Tools/HoloPatcher
# holocrontoolset, holopatcher should now be on PATH

Option C: Using Poetry

poetry install --with dev
poetry shell

3. Verify Installation

# Check library import
python -c "import pykotor; print('PyKotor installed successfully')"

# Check tools (if installed via pip/pipx)
holocrontoolset --version
holopatcher --version
kotordiff --version  # or kotordiff --version

# PyKotor CLI (included with pykotor package)
pykotor --help
pykotorcli --help

Development Workflow

1. Create a Feature Branch

git checkout -b feature/your-feature-name

Use meaningful branch names:

  • feature/ for new features
  • fix/ for bug fixes
  • docs/ for documentation
  • refactor/ for code refactoring

2. Make Your Changes

  • Write clean, readable code following project conventions
  • Add tests for new functionality
  • Update documentation as needed
  • Keep commits focused and atomic

3. Test Your Changes

Run the scoped test suite before committing (see Testing for the full command):

QT_QPA_PLATFORM=offscreen uv run pytest --import-mode=importlib -m "not gui and not slow" --timeout=120 \
  --ignore=Libraries/PyKotor/tests/resource/formats/test_mdl_ascii.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_registry_strict_typing.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_file_dialog_components.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_keyboard_accessibility_conformance.py \
  Libraries/PyKotor/tests

4. Check Code Quality

# Lint code
ruff check .

# Auto-fix linting issues
ruff check --fix .

# Format code
ruff format .

# Type check
mypy Libraries/PyKotor/src/pykotor

5. Commit Your Changes

Follow Conventional Commits:

git add .
git commit -m "feat: add new feature"

Commit types:

  • feat: - New feature
  • fix: - Bug fix
  • docs: - Documentation changes
  • style: - Code style changes (formatting, missing semi colons, etc)
  • refactor: - Code refactoring
  • test: - Adding or updating tests
  • chore: - Maintenance tasks

6. Push and Create Pull Request

git push origin feature/your-feature-name

Then create a pull request on GitHub with:

  • Clear description of changes
  • Reference to related issues
  • Screenshots for UI changes

Code Standards

Python Style

  • Python Version: Support Python 3.8+ (avoid 3.8+ only features)
  • Code Style: Follow PEP 8
  • Docstrings: Use Google style
  • Type Hints: Add type hints where appropriate (see PEP 484)
  • Line Length: 120 characters maximum

Example Code

from typing import Optional


def process_resource(resource_name: str, game_path: Optional[str] = None) -> bool:
    """Process a game resource file.
    
    Args:
        resource_name: Name of the resource to process.
        game_path: Optional path to game installation.
        
    Returns:
        True if processing succeeded, False otherwise.
        
    Raises:
        ValueError: If resource_name is empty.
    """
    if not resource_name:
        raise ValueError("resource_name cannot be empty")
    
    # Implementation here
    return True

Code Quality Tools

We use the following tools:

  • ruff: Fast Python linter and formatter
  • mypy: Static type checker
  • pytest: Testing framework

Run all checks:

# Lint
ruff check .

# Format
ruff format .

# Type check
mypy Libraries/PyKotor/src/pykotor

# Test (scoped; see Testing section)
QT_QPA_PLATFORM=offscreen uv run pytest --import-mode=importlib -m "not gui and not slow" --timeout=120 \
  --ignore=Libraries/PyKotor/tests/resource/formats/test_mdl_ascii.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_registry_strict_typing.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_file_dialog_components.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_keyboard_accessibility_conformance.py \
  Libraries/PyKotor/tests

Testing

Running Tests

Use the scoped command from AGENTS.md (matches .github/workflows/python-package.yml):

QT_QPA_PLATFORM=offscreen uv run pytest --import-mode=importlib -m "not gui and not slow" --timeout=120 \
  --ignore=Libraries/PyKotor/tests/resource/formats/test_mdl_ascii.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_registry_strict_typing.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_file_dialog_components.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_keyboard_accessibility_conformance.py \
  Libraries/PyKotor/tests

Gotchas:

  • Scope tests to Libraries/PyKotor/tests. Running pytest from the repo root without that path collects other packages (e.g. Toolset) and often fails during collection.
  • Linux requires --import-mode=importlib. Without it, pytest fails with ModuleNotFoundError: No module named 'resource.formats' because the test directory Libraries/PyKotor/tests/resource/ collides with Python's stdlib resource module.

With verbose output:

QT_QPA_PLATFORM=offscreen uv run pytest --import-mode=importlib -m "not gui and not slow" --timeout=120 \
  --ignore=Libraries/PyKotor/tests/resource/formats/test_mdl_ascii.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_registry_strict_typing.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_file_dialog_components.py \
  --ignore=Libraries/PyKotor/tests/test_utility/test_keyboard_accessibility_conformance.py \
  Libraries/PyKotor/tests -v

Run specific tests:

# Specific file
uv run pytest --import-mode=importlib Libraries/PyKotor/tests/test_specific.py

# Specific test
uv run pytest --import-mode=importlib Libraries/PyKotor/tests/test_specific.py::test_function_name

# Pattern matching
uv run pytest --import-mode=importlib -k "test_pattern" Libraries/PyKotor/tests

Writing Tests

Place tests under Libraries/PyKotor/tests/ following these conventions:

File naming:

  • test_*.py for test files
  • Match the module being tested: pykotor/module.py -> tests/test_module.py

Test naming:

  • Use descriptive names: test_function_name_with_valid_input()
  • Use test_ prefix for all test functions

Example test:

import pytest
from pykotor.resource.type import ResourceType


def test_resource_type_from_extension():
    """Test ResourceType creation from file extension."""
    assert ResourceType.from_extension("utc") == ResourceType.UTC
    assert ResourceType.from_extension(".utc") == ResourceType.UTC


def test_resource_type_invalid_extension():
    """Test ResourceType with invalid extension raises error."""
    with pytest.raises(ValueError):
        ResourceType.from_extension("invalid")

Submitting Changes

Before Submitting

Ensure your changes:

  • Pass all tests (scoped command in AGENTS.md)
  • Pass linting (ruff check .)
  • Are formatted correctly (ruff format .)
  • Include type hints where appropriate
  • Have docstrings for public APIs
  • Include tests for new functionality
  • Update relevant documentation

Pull Request Guidelines

Title:

  • Use conventional commit format: feat: add new feature
  • Be descriptive but concise

Description:

  • Explain what changes were made and why
  • Reference related issues: Fixes #123 or Relates to #456
  • Include screenshots for UI changes
  • List any breaking changes

Review Process:

  1. Automated checks must pass (linting, tests, type checking)
  2. At least one maintainer review required
  3. Address review feedback promptly
  4. Keep the PR focused on a single concern

After Approval

  • Maintainers will merge your PR
  • Your changes will be included in the next release
  • You'll be credited in the release notes

Resources

Getting Help

If you need help:

Thank you for contributing to PyKotor!