- Use
make requirementsfor local setup. It installs.[dev,obj]; plain.[dev]missesboto3, which the object-storage plugin and tests use. - Use a virtualenv if possible; package metadata requires Python
>=3.9, CI tests Python 3.9 through 3.13, and the Dockerfile builds on Python 3.13. - On a clean machine, export
LINODE_CLI_TOKENbefore running CLI commands that should not enter interactiveconfigure(). Unit tests avoid config prompts withLINODE_CLI_TEST_MODE=1. make installis the closest local equivalent to CI setup, but it is heavyweight:check-prerequisites -> requirements -> build -> pip3 install --force dist/*.whl.- The wiki setup page is mostly useful, but the wiki testing page has stale target names. Trust the
Makefile: usemake test-unitandmake test-int, notmake testunitormake testint.
- This CLI is spec-driven. Runtime commands load the baked pickle at
linodecli/data-3;MANIFEST.inpackages that file into distributions. make build,make install, andmake lintall run generation throughbuild:clean, rewritelinodecli/version.pyfromLINODE_CLI_VERSION(default0.0.0.dev), regenerate rootdata-3, copy it tolinodecli/data-3, and rebuilddist/.- Prefer
make bake SPEC=/path/to/openapi.jsonormake bake SPEC_VERSION=<tag>; it resolves the spec via./resolve_spec_urlwhen needed, passes$(BAKE_FLAGS)(--debugby default), and copies rootdata-3tolinodecli/data-3. - If you rely on the default
SPEC_VERSION=latest, setGITHUB_TOKEN;resolve_spec_urlcalls the GitHub releases API forlinode/linode-api-openapiand can hit rate limits without it. - Manual bake:
python3 -m linodecli bake <spec> --skip-configwrites rootdata-3only; copy it tolinodecli/data-3yourself or the package keeps the old pickle.--skip-configis a hidden sentinel checked inlinodecli/__init__.pybefore argparse/config bootstrapping. - Never hand-edit
linodecli/data-3or rootdata-3; change bake logic or the source spec/extensions and rebake. CLI._load_openapi_spec()mutates parsed specs with_normalize_content_parameters()beforeopenapi3.OpenAPI(...); this converts OpenAPI Parametercontentforms to top-levelschemabecause theopenapi3package does not support parametercontentdirectly.
- Importing top-level
linodeclihas side effects:linodecli/__init__.pyconstructs a globalCLI, loads baked ops, and may load/configure user state immediately. - CLI entrypoints are
linodecli/__init__.py:mainandlinodecli/__main__.py; console scriptslinode-cli,linode, andlinall point tolinodecli:main. linodecli/cli.pyhandles spec loading/baking, baked-op loading, command lookup, custom aliases, and dispatch.linodecli/api_request.pybuilds request URLs, request bodies,X-Filter, retries, version warnings, and error output.linodecli/output/output_handler.pyhandles table, ASCII table, delimited, JSON, and Markdown output.linodecli/overrides.pycontains command/action/output-mode-specific display overrides.linodecli/configuration/owns config loading, interactive configuration, OAuth token flow, env token handling, and API URL overrides.linodecli/plugins/contains hand-written commands outside the generated OpenAPI surface. If you add or change a plugin, readlinodecli/plugins/README.mdfor thecall(args, context)interface and third-partyPLUGIN_NAMErequirement.- If you touch
linodecli/baked/*.py, readlinodecli/baked/AGENTS.mdfirst. Key constraint: baked model state must stay pickle-safe.
make testonly runs unit tests; it is an alias formake test-unit.- Use
make test-unitfor normal unit verification. It setsLINODE_CLI_TEST_MODE=1andXDG_CONFIG_HOME=/tmp/linode/.configso imports do not trigger interactive config. - Focused unit test:
LINODE_CLI_TEST_MODE=1 XDG_CONFIG_HOME=$(mktemp -d) pytest tests/unit/test_cli.py -k '<expr>'. - Unit tests for bake/parsing behavior use minimal OpenAPI fixtures in
tests/fixtures/and helper fixtures intests/unit/conftest.py; add or update a fixture there when changing generated argument/response behavior. - Integration tests shell out to the installed
linode-clibinary, not the source tree directly. Re-runmake installafter code changes before trusting integration results. - Integration and smoke tests hit the real Linode API and create/destroy real resources. Do not run them casually against a personal account.
tests/integration/conftest.pyhas a session-scoped autouse firewall fixture, so even focused integration runs can create a cloud firewall before the selected test body runs.- Focused integration run:
make test-int TEST_SUITE=domains TEST_CASE=test_create_a_domain TEST_ARGS='-v'. - Integration and smoke tests require
LINODE_CLI_TOKEN. Long-running cases are skipped unlessRUN_LONG_TESTS=Trueexactly. Smoke tests aremake test-smoke.
make lintis not a pure lint pass; it depends onmake build, so it cleans, rebakes, rebuildsdist/, then runspylint,isort --check-only,autoflake --check,black --check, andtwine check dist/*.- Because lint rebakes, default
SPEC_VERSION=latestneeds GitHub API access (and oftenGITHUB_TOKEN). WithoutSPEC=...or a token,make lintcan fail on rate limits during bake, not only on style. - For quick checks without generation side effects, run style tools directly, for example
black --check linodecli tests,isort --check-only linodecli tests, orautoflake --check linodecli tests. - Formatting uses Black/isort with an 80-character line length.
make formatrunsblack, thenisort, thenautoflakeand rewrites files in place. - Keep syntax compatible with Python 3.9 even if developing on a newer interpreter.
- CI enforces PR titles matching
TPT-<number>: <description>unless the PR is labeleddependencies,hotfix,community-contribution, orignore-for-release. e2e_scripts/is a git submodule used by CI/e2e workflows, not the core CLI package. Fresh clones leave it empty untilgit submodule update --init.- If you change behavior, commands, generated-file flow, test/lint setup, or architecture described here, update this
AGENTS.mdin the same change.