Skip to content

chore: add interface-first plugin cookiecutter template - #2862

Closed
sfc-gh-olorek wants to merge 3 commits into
mainfrom
feature/plugin-template-and-docs
Closed

sfc-gh-olorek wants to merge 3 commits into
mainfrom
feature/plugin-template-and-docs

Conversation

@sfc-gh-olorek

@sfc-gh-olorek sfc-gh-olorek commented Apr 7, 2026 •

Copy link
Copy Markdown
Contributor

Pre-review checklist

  • If my changes add or modify user-facing interface (commands, flags, output formats), I consulted maintainers and got sign-off beforehand (how).
  • I've confirmed that instructions included in README.md are still correct after my changes in the codebase.
  • I've added or updated unit tests to verify correctness of my new code.
  • I've added or updated integration tests to verify correctness of my new code.
  • Manually tested on macOS
  • Manually tested on Windows
  • I've confirmed my changes are up-to-date with the target branch.
  • I've described my changes in RELEASE-NOTES.md (see when and how).
  • I've updated documentation if behavior changed.

Template/tooling PR: plugin-template/ is excluded from repo CI (Jinja placeholders, not valid Python/TOML), so repo unit/integration tests and manual platform testing don't apply. Instead the generated project was validated end-to-end (see below). skip-release-notes applied.

Changes description

Split from the original scope of this PR; the contributor guide is now in #3132.

Adds a cookiecutter template under plugin-template/ for scaffolding
interface-first Snowflake CLI plugins:

  • Supports group (multiple subcommands) and single command plugins.
  • Generated project: interface.py (Phase 1 reviewable command surface + handler
    ABC), handler.py (Phase 2 implementation), plugin_spec.py (wiring via
    build_command_spec), and a contract test using the
    snowflake.cli.api.plugins.command.testing helpers.
  • Generated files carry the standard Apache license header (matching
    scripts/LICENSE.txt), so a plugin contributed back into snowflake-cli as a
    built-in passes the repo's insert-license check unchanged.

Because the template sources contain Jinja placeholders that are not valid
Python/TOML, they are kept out of the repo's own tooling:

  • pre-commit excludes plugin-template/ globally (black/ruff/check-ast cannot
    parse the Jinja sources).
  • plugin-template/conftest.py keeps the repo-wide pytest runs that collect from
    the project root (pytest -m performance / -m e2e) from importing the Jinja
    template tests.

Validated against current main: cookiecutter plugin-template/ renders both
the group and single variants, the generated project builds and installs
(pip install), and its contract tests pass (4 passed).

Updated from the original draft to match current main:

  • build_command_spec is now imported from ...command.bridge (the package
    __init__ no longer re-exports it); the old import would have failed at runtime.
  • pyproject.toml declares [tool.hatch.build.targets.wheel] packages so the
    generated src/-layout project actually builds.

Pairs with the guide in #3132.

@sfc-gh-olorek
sfc-gh-olorek requested a review from a team as a April 7, 2026 10:58
@sfc-gh-olorek
sfc-gh-olorek force-pushed the feature/plugin-notebook-migration branch from 97d36de to 4f2d603 Compare April 7, 2026 11:04
@sfc-gh-olorek
sfc-gh-olorek force-pushed the feature/plugin-template-and-docs branch from 5f7c143 to e2d7a3a Compare April 7, 2026 11:04
@sfc-gh-olorek
sfc-gh-olorek marked this pull request as draft April 15, 2026 09:46
@sfc-gh-olorek
sfc-gh-olorek force-pushed the feature/plugin-template-and-docs branch from e2d7a3a to da3eb5a Compare June 23, 2026 10:11
@sfc-gh-olorek
sfc-gh-olorek changed the base branch from feature/plugin-notebook-migration to main June 23, 2026 10:11
@sfc-gh-olorek
sfc-gh-olorek force-pushed the feature/plugin-template-and-docs branch from da3eb5a to 9023cc2 Compare June 23, 2026 10:58
@sfc-gh-olorek sfc-gh-olorek changed the title docs: add plugin cookiecutter template and contributor guide chore: add interface-first plugin cookiecutter template Jun 23, 2026
@sfc-gh-olorek
sfc-gh-olorek marked this pull request as ready for review June 23, 2026 11:04
@sfc-gh-olorek
sfc-gh-olorek requested a review from a team as a code owner June 23, 2026 11:04
@sfc-gh-olorek
sfc-gh-olorek force-pushed the feature/plugin-template-and-docs branch from 9023cc2 to 04a544d Compare June 23, 2026 11:16
Scaffold under plugin-template/ for creating interface-first Snowflake
CLI plugins. Supports both `group` (multiple subcommands) and `single`
command plugins.

Generated projects follow the two-phase contribution workflow:
interface.py (Phase 1, reviewable command surface + handler ABC) and
handler.py (Phase 2, implementation), wired in plugin_spec.py via
build_command_spec. Generated files carry the standard Apache license
header, so a plugin contributed back as a built-in passes the repo's
license check unchanged.

The template sources contain Jinja placeholders that are not valid
Python/TOML, so they are kept out of the repo's own tooling:
- pre-commit excludes plugin-template/ globally (black/ruff/check-ast
  cannot parse the Jinja sources).
- plugin-template/conftest.py keeps the repo-wide pytest runs that
  collect from the project root (pytest -m performance / -m e2e) from
  importing the Jinja template tests.
@sfc-gh-olorek
sfc-gh-olorek force-pushed the feature/plugin-template-and-docs branch from 04a544d to c0ca0da Compare June 23, 2026 12:13
Comment thread plugin-template/cookiecutter.json
Collapse the repeated class-name filter chain
`{{ cookiecutter.plugin_module | replace('_', ' ') | title | replace(' ', '') }}`
into a single derived cookiecutter variable `plugin_class_name`, replacing
all 12 occurrences across handler.py, interface.py, plugin_spec.py, and
test_interface.py with `{{ cookiecutter.plugin_class_name }}`.

Generated output is byte-identical before and after for both command_type
variants (group and single).
- Drop the unused `REQUIRED` import. `ParamDef.default` already defaults to
  `REQUIRED`, so the example params are required without it -- the import was
  dead in every generated project.
- Render `cli_parent_path` as a proper tuple by splitting on whitespace, so a
  single-segment parent (e.g. `cortex`) yields a valid 1-tuple `("cortex",)`
  instead of a `NameError` / bare string. Empty stays `()` and multi-segment
  yields `("app", "bundle",)`.

Verified by rendering empty / single / multi parent paths across both
command_type variants; all generated .py compile.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants