Skip to content

Refactor cli.py and apm_package.py into focused modules #172

@sergio-sisternes-epam

Description

@sergio-sisternes-epam

Is your feature request related to a problem? Please describe.

Two source files have grown far beyond maintainable size:

  • src/apm_cli/cli.py~4,675 lines containing 15+ Click commands, 60+ helper functions, and an 860-line install engine all in a single file.
  • src/apm_cli/models/apm_package.py~1,186 lines mixing dataclasses, enums, a 377-line DependencyReference.parse() method, and validation logic.

This makes parallel development difficult, increases merge conflicts, and hurts code navigability. The rest of the codebase follows good modular patterns (50–200 line files).

Describe the solution you'd like

Phase 1 — Extract CLI commands from cli.py into commands/

Reduce cli.py from ~4,675 → ~120 lines (thin wiring layer). Follow the existing commands/deps.py pattern.

New file What moves there ~Lines
commands/_helpers.py Shared utilities: _get_console(), lazy loaders, color constants, _check_orphaned_packages(), _atomic_write() ~250
commands/init.py init() command + _interactive_project_setup(), _auto_detect_author(), _get_default_config(), _create_minimal_apm_yml() ~400
commands/install.py install() command + _install_apm_dependencies() (860-line core engine), _validate_and_add_packages_to_apm_yml(), MCP dep helpers, summary display ~1,400
commands/uninstall.py uninstall() command + transitive cleanup ~420
commands/prune.py prune() command ~150
commands/update.py update() command ~130
commands/compile.py compile() command + _watch_mode(), validation display helpers ~700
commands/run.py run() + preview() commands + runtime detection helpers ~350
commands/list.py list() command ~90
commands/config.py config group (set/get) ~160
commands/runtime.py runtime group (setup/list/remove/status) ~80
commands/mcp.py mcp group (search/show/list) ~200

cli.py becomes a thin wiring layer: @click.group, cli.add_command(...), print_version(), and main().

Design rules:

  • The set = builtins.set / list = builtins.list trick must move to any command file whose Click name clashes with builtins.
  • _helpers.py must NOT import from command modules (prevents circular imports).
  • Command modules must NOT import from each other — shared logic goes in _helpers.py.

Phase 2 — Split apm_package.py into focused model files

Reduce apm_package.py from ~1,186 → ~400 lines.

New file What moves there ~Lines
models/dependency.py DependencyReference (incl. 377-line parse()), ResolvedReference, GitReferenceType, parse_git_reference() ~650
models/validation.py ValidationResult, ValidationError, PackageType, validate_apm_package() + private validators, _has_hook_json(), InvalidVirtualPackageExtensionError ~300
apm_package.py (slimmed) APMPackage, PackageInfo, PackageContentType, cache logic ~400

models/__init__.py re-exports all symbols so from apm_cli.models import X keeps working everywhere.

Verification

  1. uv run pytest -q — full test suite, zero regressions
  2. uv run python -c "from apm_cli.cli import cli; print('OK')" — no circular imports
  3. uv run python -c "from apm_cli.models import DependencyReference, APMPackage, ValidationResult, ResolvedReference, PackageInfo; print('OK')" — re-exports intact
  4. uv run pytest tests/benchmarks/ -q — no perf regression
  5. uv run apm --version && uv run apm --help — CLI smoke test

Describe alternatives you've considered

  • Partial extraction (only the largest commands like install and compile): Would help but still leaves a 2,000+ line cli.py. A full extraction is cleaner and sets the right pattern going forward.
  • Keeping models in one file: The 377-line parse() method and validation logic are sufficiently independent domains to justify separate files. Leaving them mixed increases cognitive load.

Additional context

  • Phases 1 and 2 are independent and can be done in parallel or either order.
  • _install_apm_dependencies() (860 lines) is a candidate for further decomposition into resolve/download/integrate phases, but that should be a separate follow-up to limit blast radius.
  • No user-facing behavior changes — this is pure internal restructuring.
  • The existing commands/deps.py (823 lines with list, tree, clean, update, info subcommands) serves as the template for new command files.

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedDirection approved, safe to start workenhancementNew feature or request

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions