Skip to content

Contributing and Development

Espen Hovland edited this page Jun 5, 2026 · 4 revisions

Contributing & Development

This page covers working on PyCommander itself: setting up a development environment, running the tests, and the layout of the monorepo.

Repository

Monorepo layout

packages/
├── pycommander/          # meta-package (the `pycommander` command)
├── pycommander-cli/       # CLI flavor   (bundles commander-cli, `pycommander-cli`)
├── pycommander-gui/       # GUI flavor   (bundles commander,     `pycommander-gui`)
└── pycommander-core/      # the framework (all real logic; no executable)
scripts/
└── run_tests.py
archives/                  # platform Commander archives used at release time

See Architecture-and-Packages for how the packages relate and how the bundled executable is located and extracted at runtime.

Development install

PyCommander targets Python 3.10+. Install all four packages in editable mode so changes are picked up immediately:

python -m pip install --upgrade pip
python -m pip install -e packages/pycommander-core \
                      -e packages/pycommander-cli \
                      -e packages/pycommander-gui \
                      -e packages/pycommander \
                      --force-reinstall

The core package depends on pyyaml and platformdirs.

Providing Commander executables locally

The flavor packages need a Simplicity Commander archive under their _archive/ directory to actually run hardware commands. Place the platform-appropriate CLI and GUI archives into:

  • packages/pycommander-cli/src/pycommander_cli/_archive/
  • packages/pycommander-gui/src/pycommander_gui/_archive/

You can also point the API at a specific executable at runtime via Commander(executable_path=...), which is handy for testing against a particular build without touching _archive/.

Running the tests

Each package has a tests/ suite. The aggregate runner discovers and runs all of them:

python scripts/run_tests.py

To produce coverage like CI does:

python -m pip install coverage
python -m pip install -r packages/*/tests/requirements.txt
coverage run scripts/run_tests.py
coverage report
coverage html        # writes htmlcov/

The test suites use mock runners/adapters/commanders (see packages/pycommander-core/tests/), so most tests do not require real hardware.

Continuous integration

Two GitHub Actions workflows live in .github/workflows/:

  • unittest-and-coverage.yml — runs the test suites and produces a combined coverage report across Linux (x86_64 / aarch64), macOS (arm64) and Windows. Triggered manually (workflow_dispatch). It copies the correct Commander archives into the flavor packages before installing and testing.
  • build-wheels-and-publish.yml — builds platform-specific wheels and publishes to PyPI on a v*.*.* tag.

Coding conventions

A few patterns to keep in mind when extending the code:

  • One module per CLI suite under pycommander_core/commands/. Each command class subclasses BaseCommand, builds an argument list with the provided helpers (_get_ranges, _get_patches, _get_regions, _get_tokens, _get_include_sections, …), and returns self._run(...).output.
  • CommanderBase auto-registers every command class from commands.__all__, deriving the attribute name from the class name (FlashCommandcommander.flash). When you add a suite, export it from commands/__init__.py and add a type-hint attribute on CommanderBase.
  • High-level helpers (Adapter-Class, Target-Class, AemStreamBase) parse Commander's JSON into the dataclasses in types.py. Validate inputs and raise ValueError/FileNotFoundError early; return None for failed look-ups and bool for actions.
  • All subprocess work goes through runner.Runner, which centralizes timeouts, logging, JSON handling, and the mapping of return codes to typed exceptions.

Adding a new command

  1. Create pycommander_core/commands/<suite>.py with a class <Suite>Command(BaseCommand).
  2. Add public methods that assemble args and call self._run("<suite>", "<subcommand>", *args).output. Document each with a docstring (Args/Returns) — this wiki is generated from those.
  3. Export the class in commands/__init__.py (from .<suite> import <Suite>Command and add to __all__).
  4. Add a type-hint attribute on CommanderBase (<suite> : "<Suite>Command").
  5. (Optional) Add a typed wrapper on Adapter/Target if the command maps to a common task.
  6. Add tests under the relevant package's tests/.

Clone this wiki locally