How to Generate a Python API from a YAML Device Configuration in Nallely

Nallely provides a built‑in CLI tool that transforms a MIDI device description written in YAML into a fully functional Python module, eliminating the need to write boilerplate MIDI control code manually.

Nallely is an open‑source Python framework for controlling MIDI devices through an intuitive, object‑oriented API. According to the dr-schlange/nallely-midi source code, the library includes a dedicated code generation pipeline that parses YAML configurations and emits Python classes derived from nallely.Module and nallely.MidiDevice.

The Code Generation Pipeline

The generation workflow consists of three distinct phases orchestrated by the nallely generate sub‑command. Each phase is implemented in specific modules within the codebase.

CLI Entry Point

The command‑line interface parses arguments in nallely/cli.py (lines 13‑18) and delegates to the code generator. The CLI accepts an input YAML file via -i and an output path via -o.

nallely generate -i mydevice.yaml -o mydevice.py

YAML Parsing and Validation

The generate_api function in nallely/codegen/midi_module_generator.py (lines 32‑40) handles the configuration loading. It uses ruamel.yaml—a safe YAML parser—to load the device description into a nested Python dictionary representing the hierarchical structure of sections and parameters.

Python Code Emission

The generate_code function in the same file (lines 67‑30) traverses the dictionary and writes a Python file containing:

  • Section classes derived from nallely.Module, each containing ModuleParameter attributes (or specialized variants) for every Continuous Controller (CC), program change, or NRPN defined in the YAML.
  • Device class derived from nallely.MidiDevice that aggregates the sections as properties, providing a unified interface for device control.

Step‑by‑Step Implementation

Follow this workflow to convert a YAML device specification into a working Python API.

1. Define the YAML Configuration

Create a YAML file that describes your MIDI device’s structure. Organize parameters into logical sections such as oscillators, filters, or envelopes. Each parameter requires a CC number, min/max values, and a description.

Korg:
  Minilogue:
    oscillators:
      wave:          { cc: 20, min: 0, max: 7, description: "Waveform selector" }
      pitch:         { cc: 21, min: 0, max: 127, description: "Coarse pitch" }
    filter:
      cutoff:        { cc: 30, min: 0, max: 127, description: "Filter cutoff" }
      resonance:     { cc: 31, min: 0, max: 127, description: "Filter resonance" }

The nested structure (Manufacturer → Device → Section → Parameter) maps directly to the generated class hierarchy.

2. Execute the Generator Command

Run the CLI tool to produce the Python module. The generator is exposed through nallely/codegen/__init__.py and invoked by the CLI parser in nallely/cli.py.

nallely generate -i mydevice.yaml -o mydevice.py

This command triggers the full pipeline: YAML parsing, dictionary conversion, and Python file writing via generate_api and generate_code.

3. Import and Control the Device

Import the generated module to access type‑safe device controls. The generated classes inherit from runtime components defined in nallely/modules/*.py, including Module, ModuleParameter, and MidiDevice.

from mydevice import Minilogue

# Instantiate connects to the first detected Minilogue automatically

synth = Minilogue()

# Set the waveform (CC 20) to "Saw" (value 3)

synth.oscillators.wave = 3

# Sweep the filter cutoff (CC 30)

synth.filter.cutoff = 100

The generated mydevice.py contains OscillatorsSection and FilterSection classes (derived from nallely.Module) and the top‑level Minilogue class (derived from nallely.MidiDevice).

Understanding the Generated Architecture

The emitted Python code follows a strict architectural pattern enforced by the Nallely framework.

Section Classes

Each top‑level key under the device name in your YAML (e.g., oscillators, filter) becomes a class inheriting from nallely.Module. These classes encapsulate related MIDI parameters as typed attributes.

  • ModuleParameter attributes: Each CC definition becomes a ModuleParameter instance (or a specialized subclass) with validated min/max ranges.
  • Automatic binding: The generator wires these parameters to the correct CC numbers specified in the YAML.

Device Class

The root device class (e.g., Minilogue) inherits from nallely.MidiDevice and exposes each section as a property:

  • Property aggregation: Sections are instantiated as properties of the device class, allowing dot‑notation access like synth.oscillators.wave.
  • MIDI lifecycle management: The base class handles connection establishment, port enumeration, and message transmission.

Summary

  • Nallely provides an end‑to‑end code generation pipeline that converts YAML device configurations into executable Python APIs.
  • The nallely generate CLI command (implemented in nallely/cli.py) orchestrates the workflow.
  • generate_api uses ruamel.yaml to safely parse configurations into Python dictionaries (nallely/codegen/midi_module_generator.py, lines 32‑40).
  • generate_code emits Python classes derived from nallely.Module and nallely.MidiDevice, complete with ModuleParameter attributes for each MIDI CC (lines 67‑30).
  • The resulting module supports immediate device control through high‑level attribute assignments rather than raw MIDI byte manipulation.

Frequently Asked Questions

What YAML format does Nallely expect for code generation?

Nallely expects a nested YAML structure with the pattern: Manufacturer → Device Name → Section → Parameter. Each parameter must include a cc (Continuous Controller number), min value, max value, and description string. This structure is parsed by generate_api in nallely/codegen/midi_module_generator.py to build the internal representation used for code emission.

Can I generate a Python API from CSV instead of YAML?

Yes. While the examples above focus on YAML, the nallely generate command also accepts CSV input files. The generator normalizes both formats into the same internal dictionary structure before calling generate_code, allowing you to define device configurations in whichever format suits your workflow.

What dependencies are required for code generation?

The code generation feature requires ruamel.yaml for parsing YAML configurations safely. This dependency is typically installed alongside Nallely. The runtime components (nallely.Module, nallely.MidiDevice, ModuleParameter) used by the generated code are part of the core nallely package defined in nallely/modules/*.py.

How do I customize the parameter ranges in the generated API?

Define the min and max keys explicitly in your YAML configuration for each parameter. The generate_code function reads these bounds and injects them into the ModuleParameter constructor calls within the generated Python file. When you assign values to the generated attributes at runtime, Nallely enforces these constraints automatically, preventing out‑of‑range MIDI values from being transmitted to the hardware.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →