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
Clone the repository:
git clone https://github.com/ryancheley/yt-cli.git cd yt-cli
Install development dependencies:
uv sync --dev
Install pre-commit hooks:
uv run pre-commit install
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
Create a GitHub issue for the feature
Create a feature branch:
git checkout -b feature/issue-123-add-feature
Implement the feature with tests
Update documentation
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 dependenciesIntegration 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_issuesfixture to track created issuesUse
integration_test_datafixture for unique test identifiersTest 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
Use consistent option names across commands
Provide helpful help text for all options
Include examples in docstrings
Handle errors gracefully with user-friendly messages
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:
Pre-flight checks: Verify you’re on main branch, working directory is clean, and up-to-date with remote
Quality checks: Run all linting, formatting, type checking, and tests
Version bump: Update
pyproject.tomlanduv.lockCommit and push: Create version bump commit and push to main
Tag creation: Create and push the release tag
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
mainbranchWorking 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:
Test PyPI Deployment: Validates package and publishes to Test PyPI
PyPI Deployment: After Test PyPI succeeds, publishes to main PyPI
GitHub Release: Creates GitHub release with assets and attestations
Security Attestations: Generates digital attestations for packages
Contributing Guidelines
Pull Request Process
Fork the repository
Create a feature branch
Make your changes with tests
Update documentation
Ensure all quality checks pass
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