Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 4 additions & 1 deletion .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

[![codecov](https://codecov.io/gh/ccBittorrent/ccbt/branch/main/graph/badge.svg)](https://codecov.io/gh/ccBittorrent/ccbt)
[![🥷 Bandit](https://img.shields.io/badge/🥷-security-yellow.svg)](https://ccbittorrent.readthedocs.io/en/reports/bandit/)
[![🐍 Python](https://img.shields.io/badge/python-3.8%2B-blue.svg)](../pyproject.toml)
[![🐍python 🟰](https://github.com/ccBitTorrent/ccbt/actions/workflows/test.yml/badge.svg)](https://github.com/ccBitTorrent/ccbt/actions/workflows/test.yml)
[![🐧Linux](https://github.com/ccBitTorrent/ccbt/actions/workflows/test.yml/badge.svg)](https://github.com/ccBitTorrent/ccbt/actions/workflows/test.yml)
[![🪟Windows](https://github.com/ccBitTorrent/ccbt/actions/workflows/test.yml/badge.svg)](https://github.com/ccBitTorrent/ccbt/actions/workflows/test.yml)

[![📜License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://ccbittorrent.readthedocs.io/en/license/)
[![🤝Contributing](https://img.shields.io/badge/🤝-open-brightgreen?logo=pre-commit&logoColor=white)](https://ccbittorrent.readthedocs.io/en/contributing/)
[![🎁UV](https://img.shields.io/badge/🎁-uv-orange.svg)](https://ccbittorrent.readthedocs.io/en/getting-started/)
Expand Down
63 changes: 55 additions & 8 deletions .github/workflows/build-documentation.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
name: Build Documentation

on:
push:
branches: [main]
paths:
- 'docs/**'
- 'dev/mkdocs.yml'
- '.readthedocs.yaml'
- 'dev/requirements-rtd.txt'
- 'ccbt/**'
pull_request:
branches: [main]
paths:
- 'docs/**'
- 'dev/mkdocs.yml'
- '.readthedocs.yaml'
- 'dev/requirements-rtd.txt'
workflow_dispatch:
# Can be triggered manually from any branch for testing
# Documentation is automatically published to Read the Docs when changes are pushed
Expand Down Expand Up @@ -94,16 +109,13 @@ jobs:

- name: Generate coverage report
run: |
uv run pytest -c dev/pytest.ini tests/ --cov=ccbt --cov-report=html:site/reports/htmlcov || echo "⚠️ Coverage report generation failed, continuing..."
uv run pytest -c dev/pytest.ini tests/ --cov=ccbt --cov-report=html:site/reports/htmlcov
continue-on-error: true

- name: Generate Bandit reports
- name: Generate Bandit report
run: |
uv run python tests/scripts/ensure_bandit_dir.py
# Generate main bandit report
uv run bandit -r ccbt/ -f json -o docs/reports/bandit/bandit-report.json --severity-level medium -x tests,benchmarks,dev,dist,docs,htmlcov,site,.venv,.pre-commit-cache,.pre-commit-home,.pytest_cache,.ruff_cache,.hypothesis,.github,.ccbt,.cursor,.benchmarks || echo "⚠️ Bandit report generation failed"
# Generate all severity levels report
uv run bandit -r ccbt/ -f json -o docs/reports/bandit/bandit-report-all.json --severity-level all -x tests,benchmarks,dev,dist,docs,htmlcov,site,.venv,.pre-commit-cache,.pre-commit-home,.pytest_cache,.ruff_cache,.hypothesis,.github,.ccbt,.cursor,.benchmarks || echo "⚠️ Bandit all report generation failed"
uv run bandit -r ccbt/ -f json -o docs/reports/bandit/bandit-report.json --severity-level medium -x tests,benchmarks,dev,dist,docs,htmlcov,site,.venv,.pre-commit-cache,.pre-commit-home,.pytest_cache,.ruff_cache,.hypothesis,.github,.ccbt,.cursor,.benchmarks
continue-on-error: true

- name: Ensure report files exist in documentation location
Expand Down Expand Up @@ -140,12 +152,19 @@ jobs:

- name: Build documentation
run: |
# Ensure coverage directory exists right before build (in case it was cleaned)
mkdir -p site/reports/htmlcov
if [ ! -f site/reports/htmlcov/index.html ]; then
echo '<html><body><h1>Coverage Report</h1><p>Coverage report not available. Run tests to generate coverage data.</p></body></html>' > site/reports/htmlcov/index.html
fi

# Use the patched build script which includes all necessary patches:
# - i18n plugin fixes (alternates attribute, Locale validation for 'arc')
# - git-revision-date-localized plugin fix for 'arc' locale
# - Autorefs plugin patch to suppress multiple primary URLs warnings
# - Coverage plugin patch to suppress missing directory warnings
# - All patches are applied before mkdocs is imported
# Set MKDOCS_STRICT=true to enable strict mode in CI
# Reports are ensured to exist in previous step to avoid warnings
MKDOCS_STRICT=true uv run python dev/build_docs_patched_clean.py

- name: Upload documentation artifact
Expand All @@ -155,6 +174,34 @@ jobs:
path: site/
retention-days: 7

- name: Trigger Read the Docs build
if: env.RTD_API_TOKEN != ''
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workflow condition checks wrong context, never triggers RTD build

The condition if: env.RTD_API_TOKEN != '' checks for an environment variable at the job level, but RTD_API_TOKEN is only defined within the step's own env: block (line 180). GitHub Actions evaluates if: conditions before the step runs, so env.RTD_API_TOKEN will always be empty/undefined. This means the "Trigger Read the Docs build" step will never execute, even when the secret is properly configured. The condition should use secrets.RTD_API_TOKEN != '' to check if the secret exists.

Additional Locations (1)

Fix in Cursor Fix in Web

env:
RTD_API_TOKEN: ${{ secrets.RTD_API_TOKEN }}
RTD_PROJECT_SLUG: ${{ secrets.RTD_PROJECT_SLUG || 'ccbittorrent' }}
BRANCH_NAME: ${{ github.ref_name }}
run: |
echo "Triggering Read the Docs build for branch: $BRANCH_NAME"
curl -X POST \
-H "Authorization: Token $RTD_API_TOKEN" \
-H "Content-Type: application/json" \
"https://readthedocs.org/api/v3/projects/$RTD_PROJECT_SLUG/versions/$BRANCH_NAME/builds/" \
-d "{}" || echo "⚠️ Failed to trigger Read the Docs build. This may be expected if the branch is not configured in Read the Docs."
continue-on-error: true

- name: Read the Docs build info
if: env.RTD_API_TOKEN == ''
run: |
echo "ℹ️ Read the Docs API token not configured."
echo " To enable automatic Read the Docs builds from any branch:"
echo " 1. Get your Read the Docs API token from https://readthedocs.org/accounts/token/"
echo " 2. Add it as a GitHub secret named RTD_API_TOKEN"
echo " 3. Optionally set RTD_PROJECT_SLUG secret (defaults to 'ccbittorrent')"
echo ""
echo " Note: Read the Docs will only build branches configured in your project settings."
echo " By default, only 'main' and 'dev' branches are built automatically."

# Note: Documentation is automatically published to Read the Docs
# when changes are pushed to the repository. No GitHub Pages deployment needed.
# when changes are pushed to the repository for configured branches (main/dev by default).
# To build other branches, configure them in Read the Docs project settings or use the API trigger above.

4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ jobs:
- name: Run Ruff formatting check
run: |
uv run ruff --config dev/ruff.toml format --check ccbt/

- name: Run compatibility linter
run: |
uv run python dev/compatibility_linter.py ccbt/

type-check:
name: type-check
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ MagicMock
.coverage_html
.cursor
scripts
compatibility_tests/
compatibility_tests/
lint_outputs/

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
6 changes: 3 additions & 3 deletions ccbt/cli/advanced_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import tempfile
import time
from pathlib import Path
from typing import Any
from typing import Any, Optional

import click
from rich.console import Console
Expand All @@ -36,7 +36,7 @@ class OptimizationPreset:
def _apply_optimizations(
preset: str = OptimizationPreset.BALANCED,
save_to_file: bool = False,
config_file: str | None = None,
config_file: Optional[str] = None,
) -> dict[str, Any]:
"""Apply performance optimizations based on system capabilities.

Expand Down Expand Up @@ -248,7 +248,7 @@ def performance(
optimize: bool,
preset: str,
save: bool,
config_file: str | None,
config_file: Optional[str],
benchmark: bool,
profile: bool,
) -> None:
Expand Down
4 changes: 2 additions & 2 deletions ccbt/cli/checkpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import asyncio
import time
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional

from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
from rich.table import Table
Expand Down Expand Up @@ -236,7 +236,7 @@ def backup_checkpoint(
def restore_checkpoint(
config_manager: ConfigManager,
backup_file: str,
info_hash: str | None,
info_hash: Optional[str],
console: Console,
) -> None:
"""Restore a checkpoint from a backup file."""
Expand Down
37 changes: 19 additions & 18 deletions ccbt/cli/config_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import logging
import os
from pathlib import Path
from typing import Optional, Union

import click
import toml
Expand All @@ -26,7 +27,7 @@
logger = logging.getLogger(__name__)


def _find_project_root(start_path: Path | None = None) -> Path | None:
def _find_project_root(start_path: Optional[Path] = None) -> Optional[Path]:
"""Find the project root directory by looking for pyproject.toml or .git.

Walks up the directory tree from start_path (or current directory) until
Expand Down Expand Up @@ -56,7 +57,7 @@ def _find_project_root(start_path: Path | None = None) -> Path | None:


def _should_skip_project_local_write(
config_file: Path | None, explicit_config_file: str | Path | None
config_file: Optional[Path], explicit_config_file: Optional[Union[str, Path]]
) -> bool:
"""Check if we should skip writing to project-local ccbt.toml during tests.

Expand Down Expand Up @@ -130,9 +131,9 @@ def config():
@click.option("--config", "config_file", type=click.Path(exists=True), default=None)
def show_config(
format_: str,
section: str | None,
key: str | None,
config_file: str | None,
section: Optional[str],
key: Optional[str],
config_file: Optional[str],
):
"""Show current configuration in the desired format."""
cm = ConfigManager(config_file)
Expand Down Expand Up @@ -174,7 +175,7 @@ def show_config(
@config.command("get")
@click.argument("key")
@click.option("--config", "config_file", type=click.Path(exists=True), default=None)
def get_value(key: str, config_file: str | None):
def get_value(key: str, config_file: Optional[str]):
"""Get a specific configuration value by dotted path."""
cm = ConfigManager(config_file)
data = cm.config.model_dump(mode="json")
Expand Down Expand Up @@ -223,9 +224,9 @@ def set_value(
value: str,
global_flag: bool,
local_flag: bool,
config_file: str | None,
restart_daemon_flag: bool | None,
no_restart_daemon_flag: bool | None,
config_file: Optional[str],
restart_daemon_flag: Optional[bool],
no_restart_daemon_flag: Optional[bool],
):
"""Set a configuration value and persist to TOML file.

Expand Down Expand Up @@ -325,12 +326,12 @@ def parse_value(raw: str):
help=_("Skip daemon restart even if needed"),
)
def reset_config(
section: str | None,
key: str | None,
section: Optional[str],
key: Optional[str],
confirm: bool,
config_file: str | None,
restart_daemon_flag: bool | None,
no_restart_daemon_flag: bool | None,
config_file: Optional[str],
restart_daemon_flag: Optional[bool],
no_restart_daemon_flag: Optional[bool],
):
"""Reset configuration to defaults (optionally for a section/key)."""
if not confirm:
Expand Down Expand Up @@ -399,7 +400,7 @@ def reset_config(

@config.command("validate")
@click.option("--config", "config_file", type=click.Path(exists=True), default=None)
def validate_config_cmd(config_file: str | None):
def validate_config_cmd(config_file: Optional[str]):
"""Validate configuration file and print result."""
try:
ConfigManager(config_file)
Expand All @@ -414,10 +415,10 @@ def validate_config_cmd(config_file: str | None):
@click.option("--backup", is_flag=True, help=_("Create backup before migration"))
@click.option("--config", "config_file", type=click.Path(exists=True), default=None)
def migrate_config_cmd(
from_version: str | None, # noqa: ARG001
to_version: str | None, # noqa: ARG001
from_version: Optional[str], # noqa: ARG001
to_version: Optional[str], # noqa: ARG001
backup: bool,
config_file: str | None,
config_file: Optional[str],
):
"""Migrate configuration between versions (no-op placeholder)."""
# For now, this is a placeholder that just validates and echoes
Expand Down
Loading
Loading