Development

This guide covers setting up a development environment and contributing to YouTrack CLI.

Development Setup

Prerequisites

  • Python 3.9 or higher

  • uv package manager

  • Git

Clone and Setup

  1. Clone the repository:

    git clone https://github.com/ryancheley/yt-cli.git
    cd yt-cli
    
  2. Install development dependencies:

    uv sync --dev
    
  3. Install pre-commit hooks:

    uv run pre-commit install
    
  4. Verify the setup:

    uv run pytest
    uv run ruff check
    uv run ty check
    

Project Structure

yt-cli/
├── youtrack_cli/           # Main package
│   ├── __init__.py
│   ├── main.py            # CLI entry point and command definitions
│   ├── admin.py           # Admin operations manager
│   ├── articles.py        # Articles management
│   ├── auth.py            # Authentication manager
│   ├── boards.py          # Boards management
│   ├── common.py          # Common CLI options and decorators
│   ├── config.py          # Configuration management
│   ├── exceptions.py      # Custom exception classes
│   ├── issues.py          # Issues management (core functionality)
│   ├── logging.py         # Logging infrastructure
│   ├── projects.py        # Projects management
│   ├── reports.py         # Reports generation
│   ├── time.py            # Time tracking
│   ├── users.py           # User management
│   └── utils.py           # HTTP utilities and error handling
├── tests/                 # Comprehensive test suite
│   ├── test_admin.py      # Admin functionality tests
│   ├── test_articles.py   # Articles tests
│   ├── test_auth.py       # Authentication tests
│   ├── test_boards.py     # Boards tests
│   ├── test_config.py     # Configuration tests
│   ├── test_issues.py     # Issues tests (48 test cases)
│   ├── test_main.py       # CLI interface tests
│   ├── test_projects.py   # Projects tests
│   ├── test_reports.py    # Reports tests
│   ├── test_time.py       # Time tracking tests
│   └── test_users.py      # User management tests
├── docs/                  # Sphinx documentation
│   ├── commands/          # Command-specific documentation
│   ├── conf.py            # Sphinx configuration
│   ├── index.rst          # Documentation index
│   ├── installation.rst   # Installation guide
│   ├── quickstart.rst     # Quick start guide
│   └── development.rst    # This file
├── pyproject.toml         # Project configuration and dependencies
├── uv.lock                # Dependency lock file
├── tox.ini                # Multi-version testing configuration
├── justfile               # Task runner configuration
└── README.md              # Project overview

Dependency Management

The project uses PEP 735 dependency groups for managing development dependencies. This provides a standardized way to organize and install different sets of dependencies.

Dependency Groups

The project defines the following dependency groups in pyproject.toml:

  • dev: Development tools including testing, linting, and type checking

  • docs: Documentation generation tools

Installing Dependencies

Using uv (recommended):

# Install all dependencies including dev group
uv sync --dev

# Install only production dependencies
uv sync

Using pip (requires pip >= 24.0):

# Install with dev dependencies
pip install -e . --group dev

# Install with docs dependencies
pip install -e . --group docs

# Install multiple groups
pip install -e . --group dev --group docs

Development Workflow

Creating Features

  1. Create a GitHub issue for the feature

  2. Create a feature branch:

    git checkout -b feature/issue-123-add-feature
    
  3. Implement the feature with tests

  4. Update documentation

  5. Submit a pull request

Code Quality

The project uses several tools to maintain code quality:

  • Ruff: Linting and code formatting

  • ty: Type checking (modern replacement for MyPy)

  • Pytest: Testing framework

  • Pre-commit: Git hooks for quality checks

  • zizmor: GitHub Actions security analysis

Run quality checks:

# Run all pre-commit hooks
uv run pre-commit run --all-files

# Or run individual tools
uv run ruff check .
uv run ruff format .
uv run ty check
uv run pytest

Pre-commit Hooks

The project uses comprehensive pre-commit hooks to ensure code quality and consistency. These hooks run automatically before each commit and prevent commits with quality issues.

Hook Categories

File Quality Checks:

  • Trailing whitespace removal

  • End-of-file fixing

  • YAML/TOML/JSON validation

  • Large file detection

  • Merge conflict detection

  • Case conflict detection

  • Executable shebang validation

Code Quality Checks:

  • Ruff linting with auto-fix

  • Ruff formatting

  • ty type checking (excluding test files)

  • Debug statement detection

Testing:

  • pytest execution with optimized settings for pre-commit

Security:

  • zizmor GitHub Actions security analysis

Managing Pre-commit Hooks

Install hooks:

uv run pre-commit install

Run all hooks manually:

uv run pre-commit run --all-files

Run specific hook:

uv run pre-commit run pytest
uv run pre-commit run ruff
uv run pre-commit run ty

Skip hooks (not recommended):

git commit --no-verify

Update hook versions:

uv run pre-commit autoupdate

Hook Configuration

Pre-commit hooks are configured in .pre-commit-config.yaml. The configuration includes:

  • Fast feedback: Hooks are optimized for speed during development

  • Comprehensive coverage: All CI checks are replicated locally

  • Auto-fixing: Many issues are automatically corrected

  • Selective exclusions: Test files excluded from type checking

Testing

Test Structure

Tests are organized into two main categories with proper pytest markers:

  • Unit tests (@pytest.mark.unit): Fast, isolated tests of individual functions and classes with no external dependencies

  • Integration tests (@pytest.mark.integration): End-to-end tests that require real YouTrack API access

Test Organization:

tests/
├── conftest.py                     # Global test fixtures
├── test_*.py                       # Unit tests (marked with @pytest.mark.unit)
└── integration/                    # Integration tests directory
    ├── conftest.py                 # Integration-specific fixtures
    ├── test_auth_integration.py    # Authentication integration tests
    ├── test_issues_integration.py  # Issue management integration tests
    └── test_projects_integration.py # Project management integration tests

Running Tests

Quick Test Runner (Recommended):

Use the included test runner script for easy test execution:

# Run only unit tests (fast)
python test_runner.py unit

# Run only integration tests
python test_runner.py integration

# Run all tests (unit + integration)
python test_runner.py all

# Run unit tests with coverage
python test_runner.py unit --coverage

# Run tests with verbose output
python test_runner.py all --verbose

Direct pytest Commands:

# Run all unit tests (no external dependencies)
uv run pytest -m unit

# Run all integration tests (requires YouTrack API access)
uv run pytest -m integration

# Run all tests
uv run pytest

# Run with coverage (unit tests only)
uv run pytest -m unit --cov=youtrack_cli --cov-report=html

Integration Test Requirements:

Integration tests require real YouTrack API access. Set these environment variables:

# Required
export YOUTRACK_BASE_URL="https://your-instance.youtrack.cloud"
export YOUTRACK_API_KEY="your-api-token"

# Optional
export YOUTRACK_TEST_PROJECT="FPU"  # Default project for testing
export YOUTRACK_USERNAME="your-username"  # For assignment tests

Randomized Testing Options:

# Run with specific random seed for reproducibility
uv run pytest --randomly-seed=12345

# Disable randomization if needed
uv run pytest --randomly-dont-shuffle

# Show current random seed
uv run pytest --randomly-seed=last

The test suite uses pytest-randomly to randomize test execution order. Each test run displays the random seed used, which can be reused to reproduce specific test failures. This helps identify and eliminate order-dependent test bugs.

Multi-version Testing

Test against multiple Python versions using tox:

uv run tox

Writing Tests

Unit Test Guidelines:

All unit tests should be marked with @pytest.mark.unit and should:

  • Test individual functions/classes in isolation

  • Use mocks for external dependencies

  • Be fast and deterministic

  • Not require network access or external services

Example unit test:

import pytest
from unittest.mock import Mock, patch
from youtrack_cli.issues import IssueManager

@pytest.mark.unit
class TestIssueManager:
    def test_parse_issue_id(self):
        project, number = IssueManager.parse_issue_id("PROJECT-123")
        assert project == "PROJECT"
        assert number == 123

    def test_parse_issue_id_invalid(self):
        with pytest.raises(ValueError):
            IssueManager.parse_issue_id("invalid-id")

    @patch('youtrack_cli.issues.YouTrackClient')
    def test_create_issue(self, mock_client):
        mock_client.return_value.create_issue.return_value = {"id": "PROJ-1"}
        manager = IssueManager(mock_client)
        result = manager.create_issue("PROJ", "Test", "Description")
        assert result["id"] == "PROJ-1"

Integration Test Guidelines:

All integration tests should be marked with @pytest.mark.integration and should:

  • Test real API interactions with YouTrack

  • Use the FPU project for testing by default

  • Clean up any created test data

  • Be resilient to varying YouTrack configurations

Example integration test:

import pytest

@pytest.mark.integration
class TestIssuesIntegration:
    def test_create_and_delete_issue_workflow(
        self,
        integration_issue_manager,
        test_issue_data,
        cleanup_test_issues
    ):
        """Test complete create and delete issue workflow."""
        # Create issue
        created_issue = integration_issue_manager.create_issue(
            project_id=test_issue_data["project"]["id"],
            summary=test_issue_data["summary"],
            description=test_issue_data["description"]
        )

        assert created_issue is not None
        issue_id = created_issue["id"]
        cleanup_test_issues(issue_id)  # Schedule cleanup

        # Verify issue was created
        retrieved_issue = integration_issue_manager.get_issue(issue_id)
        assert retrieved_issue["summary"] == test_issue_data["summary"]

Test Data Management:

Integration tests include automatic cleanup of test data:

  • Use cleanup_test_issues fixture to track created issues

  • Use integration_test_data fixture for unique test identifiers

  • Test data is automatically cleaned up after each test

Adding New Commands

Command Structure

Commands are organized using Click groups. Each command module follows this pattern:

import click
from youtrack_cli.client import get_client

@click.group()
def issues():
    """Issue management commands."""
    pass

@issues.command()
@click.option('--title', required=True, help='Issue title')
@click.option('--description', help='Issue description')
def create(title, description):
    """Create a new issue."""
    client = get_client()
    issue = client.issues.create(title=title, description=description)
    click.echo(f"Created issue: {issue.id}")

Command Guidelines

  1. Use consistent option names across commands

  2. Provide helpful help text for all options

  3. Include examples in docstrings

  4. Handle errors gracefully with user-friendly messages

  5. Support multiple output formats where appropriate

Error Handling Infrastructure

Custom Exceptions

The project uses a structured exception hierarchy for better error handling:

from youtrack_cli.exceptions import (
    YouTrackError,         # Base exception
    AuthenticationError,   # Login/token issues
    ConnectionError,       # Network problems
    NotFoundError,         # Missing resources
    PermissionError,       # Access denied
    ValidationError,       # Invalid input
    RateLimitError,       # Too many requests
)

# Example usage
try:
    result = api_call()
except AuthenticationError as e:
    console.print(f"[red]Error:[/red] {e.message}")
    if e.suggestion:
        console.print(f"[yellow]Suggestion:[/yellow] {e.suggestion}")

HTTP Utilities

The utils.py module provides robust HTTP request handling:

from youtrack_cli.utils import make_request, handle_error, display_error

# Automatic retry with exponential backoff
response = await make_request(
    method="GET",
    url="https://youtrack.example.com/api/issues",
    headers={"Authorization": f"Bearer {token}"},
    max_retries=3,
    timeout=30
)

# Error handling with user-friendly messages
try:
    result = risky_operation()
except Exception as e:
    error_info = handle_error(e, "issue creation")
    display_error(error_info)

Common CLI Components

The common.py module provides reusable CLI components:

from youtrack_cli.common import common_options, async_command, handle_exceptions

@click.command()
@common_options  # Adds --format, --verbose, --debug, --no-color
@async_command   # Handles async functions
@handle_exceptions  # Catches and displays errors
async def my_command(format, verbose, debug, console):
    """Example command with common options."""
    if verbose:
        console.print("Starting operation...")

Logging Infrastructure

Enhanced logging with Rich formatting:

from youtrack_cli.logging import setup_logging, get_logger

# Setup logging (usually in main CLI entry point)
setup_logging(verbose=True, debug=False)

# Get logger in any module
logger = get_logger(__name__)
logger.info("Operation started")
logger.debug("Detailed debug information")
logger.warning("Something to watch out for")

Adding API Endpoints

Client Structure

API clients are organized by resource type:

from typing import List, Optional
from youtrack_cli.models import Issue

class IssuesClient:
    def __init__(self, http_client):
        self.http = http_client

    def list(self, assignee: Optional[str] = None) -> List[Issue]:
        params = {}
        if assignee:
            params['assignee'] = assignee

        response = self.http.get('/issues', params=params)
        return [Issue.parse_obj(item) for item in response.json()]

    def create(self, **kwargs) -> Issue:
        response = self.http.post('/issues', json=kwargs)
        return Issue.parse_obj(response.json())

Model Definitions

Use Pydantic models for data validation:

from datetime import datetime
from typing import Optional
from pydantic import BaseModel, Field

class Issue(BaseModel):
    id: str
    title: str = Field(alias='summary')
    description: Optional[str] = None
    state: str
    assignee: Optional[str] = None
    created: datetime
    updated: datetime

    class Config:
        allow_population_by_field_name = True

Documentation

Writing Documentation

  • Use reStructuredText format

  • Include code examples for all features

  • Keep documentation up-to-date with code changes

  • Add docstrings to all public functions and classes

Building Documentation Locally

cd docs
uv run sphinx-build -b html . _build/html

The documentation will be available at docs/_build/html/index.html.

Release Process

Version Management

The project uses semantic versioning (MAJOR.MINOR.PATCH):

  • MAJOR: Breaking changes

  • MINOR: New features (backward compatible)

  • PATCH: Bug fixes (backward compatible)

Release Workflow

The project uses an automated release process via justfile recipes that handle all aspects of releasing.

Step 1: Pre-Release Validation

Before creating a release, validate your intended version:

# Check if version is valid and ready for release
just release-check 0.2.3

# Check current project status
just release-status

Step 2: Automated Release

Create a complete release with safety checks:

# Full automated release process
just release 0.2.3

This command will:

  1. Pre-flight checks: Verify you’re on main branch, working directory is clean, and up-to-date with remote

  2. Quality checks: Run all linting, formatting, type checking, and tests

  3. Version bump: Update pyproject.toml and uv.lock

  4. Commit and push: Create version bump commit and push to main

  5. Tag creation: Create and push the release tag

  6. Trigger automation: GitHub Actions automatically builds and publishes to PyPI

Step 3: Monitor Release

The release process provides helpful links:

✅ Release 0.2.3 created and published!
🔗 Monitor release progress: https://github.com/ryancheley/yt-cli/actions
📦 Package will be available at: https://pypi.org/project/youtrack-cli/0.2.3/

Emergency Rollback

If a release needs to be rolled back (before PyPI publication):

# Emergency rollback - deletes tag and reverts version commit
just rollback-release 0.2.3

Warning

Rollback is only effective before the package is published to PyPI. Once published, you must create a new version.

Release Safety Features

The release process includes multiple safety checks:

Branch Protection:
  • Must be on main branch

  • Working directory must be clean

  • Must be up-to-date with origin/main

Version Validation:
  • Semantic versioning format (e.g., 1.2.3)

  • Version must not already exist as a tag

  • Must be a proper version increment

Quality Gates:
  • All tests must pass

  • Code must pass linting

  • Type checking must succeed

  • No security issues detected

Manual Release Steps (Advanced)

For advanced users who need manual control:

# Individual steps
just version-bump 0.2.3    # Update pyproject.toml only
just tag 0.2.3             # Create and push tag only

# Quality checks
just check                  # Run all quality checks

Release Troubleshooting

Common Issues and Solutions:

Working directory not clean:
# Check what files are uncommitted
git status

# Commit or stash changes
git add . && git commit -m "commit message"
# or
git stash
Not up-to-date with remote:
git pull origin main
Quality checks failing:
# Run individual checks to identify issues
just lint           # Fix linting issues
just format         # Fix formatting
just typecheck      # Fix type issues
just test          # Fix failing tests
Tag already exists:
# List existing tags
git tag -l

# Use the next appropriate version number

GitHub Actions Integration

The release process automatically triggers GitHub Actions workflows:

  1. Test PyPI Deployment: Validates package and publishes to Test PyPI

  2. PyPI Deployment: After Test PyPI succeeds, publishes to main PyPI

  3. GitHub Release: Creates GitHub release with assets and attestations

  4. Security Attestations: Generates digital attestations for packages

Contributing Guidelines

Pull Request Process

  1. Fork the repository

  2. Create a feature branch

  3. Make your changes with tests

  4. Update documentation

  5. Ensure all quality checks pass

  6. Submit a pull request

Code Style

  • Follow PEP 8

  • Use type hints for all function signatures

  • Write descriptive commit messages

  • Keep functions focused and small

  • Add docstrings to public interfaces

Getting Help

  • Open an issue for bugs or feature requests

  • Join discussions in GitHub Discussions

  • Check existing issues before creating new ones

  • Provide minimal reproducible examples for bugs