Skip to content

feat(presets): add enable/disable toggle and update semantics#1891

Draft
mbachorik wants to merge 5 commits intogithub:mainfrom
mbachorik:feature/preset-update-and-toggle
Draft

feat(presets): add enable/disable toggle and update semantics#1891
mbachorik wants to merge 5 commits intogithub:mainfrom
mbachorik:feature/preset-update-and-toggle

Conversation

@mbachorik
Copy link
Contributor

Summary

  • Add preset enable and preset disable CLI commands for toggling presets without removal
  • Add restore() method to PresetRegistry for rollback scenarios
  • Update get() and list() to return deep copies (prevents accidental mutation)
  • Update list_by_priority() to filter disabled presets by default
  • Disabled presets are skipped during template resolution

Addresses

Defensive Programming

Following feedback from previous PR, this implementation includes:

  • Input validation in restore() for None/non-dict metadata
  • Corrupted registry entry handling in enable/disable commands
  • Deep copy protection to prevent state mutation
  • Tests for all edge cases including corrupted state

Test plan

  • All 386 tests pass
  • Ruff linter clean
  • New tests for restore(), deep copy, enable/disable CLI, corrupted state handling

🤖 Generated with Claude Code

Add preset enable/disable CLI commands and update semantics to match
the extension system capabilities.

Changes:
- Add `preset enable` and `preset disable` CLI commands
- Add `restore()` method to PresetRegistry for rollback scenarios
- Update `get()` and `list()` to return deep copies (prevents mutation)
- Update `list_by_priority()` to filter disabled presets by default
- Add input validation to `restore()` for defensive programming
- Add 16 new tests covering all functionality and edge cases

Closes github#1851
Closes github#1852

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 18, 2026 06:59
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the preset system to support safer registry mutation patterns (rollback/restore and copy isolation) and adds CLI enable/disable toggles so presets can be temporarily turned off without uninstalling, while ensuring disabled presets are skipped during template resolution.

Changes:

  • Added PresetRegistry.restore() plus deep-copy isolation for PresetRegistry.get() and PresetRegistry.list().
  • Updated PresetRegistry.list_by_priority() to exclude disabled presets by default (with an include_disabled override).
  • Introduced specify preset enable|disable commands and added tests covering restore, deep-copy behavior, disabled filtering, CLI flows, and corrupted-state handling.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/specify_cli/presets.py Adds restore(), deep-copy semantics for registry reads, and default filtering of disabled presets in priority listing (affects resolution).
src/specify_cli/__init__.py Adds preset enable / preset disable Typer commands with project checks and corrupted-state handling.
tests/test_presets.py Adds coverage for restore validation, deep-copy protection, list-by-priority filtering, CLI enable/disable flows, and resolution behavior when disabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Fix error message in restore() to match actual validation ("dict" not "non-empty dict")
- Use copy.deepcopy() in restore() to prevent caller mutation
- Apply same fixes to ExtensionRegistry for parity
- Add /defensive-check command for pre-PR validation
- Add tests for restore() validation and deep copy behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mbachorik
Copy link
Contributor Author

Addressed both review comments:

  1. Error message accuracy: Changed from "non-empty dict" to "dict" to match actual validation
  2. Deep copy: Now using copy.deepcopy(metadata) instead of dict(metadata)

Also applied the same fixes to ExtensionRegistry.restore() for parity, and added tests for both.

Added /defensive-check command to catch these issues before PRs.

@mbachorik mbachorik requested a review from Copilot March 18, 2026 08:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the preset system to support safer registry semantics and operational toggling, bringing presets closer to feature parity with extensions (update/restore patterns and enable/disable behavior).

Changes:

  • Add PresetRegistry.restore() plus defensive validation, and align ExtensionRegistry.restore() behavior (validation + deep copy).
  • Return deep copies from preset registry get()/list() and filter disabled presets by default in list_by_priority().
  • Add specify preset enable/disable commands and corresponding tests, including behavior around disabled preset resolution.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore(), deep-copy protections for registry reads, and default filtering of disabled presets in priority ordering (affects resolution).
src/specify_cli/extensions.py Hardens restore() with input validation and deep-copy to avoid caller mutation and align with presets.
src/specify_cli/__init__.py Introduces preset enable/disable CLI commands for toggling preset enabled state.
tests/test_presets.py Adds coverage for preset restore validation, deep-copy behavior, disabled filtering, CLI enable/disable flows, and resolution skipping.
tests/test_extensions.py Adds coverage for extension restore validation and deep-copy behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

- Add note to enable/disable output clarifying commands/skills remain active
- Add include_disabled parameter to ExtensionRegistry.list_by_priority for parity
- Add tests for extension disabled filtering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the preset (and related extension) registry semantics to support safer state management and toggling without uninstalling, aligning presets more closely with extensions and improving robustness during rollback/registry-corruption scenarios.

Changes:

  • Add preset enable / preset disable CLI commands to toggle installed presets without removal.
  • Add PresetRegistry.restore() and update get() / list() to return deep copies (defensive against accidental mutation).
  • Update list_by_priority() for presets/extensions to exclude disabled entries by default (with an override to include them).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore() and deep-copy isolation for registry reads; filters disabled presets in priority listing.
src/specify_cli/extensions.py Adds validation/deep-copy to restore() and changes list_by_priority() to exclude disabled by default.
src/specify_cli/__init__.py Introduces specify preset enable/disable commands with corrupted-state handling and user messaging.
tests/test_presets.py Adds unit/integration tests for restore semantics, deep-copy behavior, enable/disable, and disabled preset resolution behavior.
tests/test_extensions.py Adds tests for restore() input validation/deep-copy and for list_by_priority() disabled filtering semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

…entries

- Fix _get_all_extensions_by_priority to use include_disabled=True for tracking
  registered IDs, preventing disabled extensions from being picked up as
  unregistered directories
- Add corrupted entry handling to get() - returns None for non-dict entries
- Add integration tests for disabled extension template resolution
- Add tests for get() corrupted entry handling in both registries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds preset enable/disable toggling and safer registry mutation semantics to better support rollback/update workflows and prevent accidental state mutation during resolution.

Changes:

  • Introduces PresetRegistry.restore() and strengthens registry isolation by returning deep copies from get()/list().
  • Adds specify preset enable|disable commands and filters disabled presets/extensions out of priority-based resolution by default.
  • Extends test coverage for restore validation, deep-copy behavior, corrupted registry entries, and disabled template resolution.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/specify_cli/presets.py Adds restore(), deep-copy returns, and default filtering of disabled presets during priority iteration; updates resolver to avoid reintroducing disabled extensions via fallback scan.
src/specify_cli/extensions.py Makes restore() validate input + deep copy; ensures get() handles corrupted entries; adds include_disabled option to list_by_priority() with disabled excluded by default.
src/specify_cli/__init__.py Adds new preset enable / preset disable CLI commands with corrupted-state handling and messaging about what disabling affects.
tests/test_presets.py Adds tests for preset restore/deep-copy/corruption handling, disabled preset resolution behavior, and disabled extension template resolution regressions.
tests/test_extensions.py Adds tests for extension restore validation/deep-copy, corrupted get behavior, and include/exclude disabled behavior in list_by_priority().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 346 to +355
def list(self) -> Dict[str, dict]:
"""Get all installed presets.

Returns a deep copy of the presets mapping to prevent callers
from accidentally mutating nested internal registry state.

Returns:
Dictionary of pack_id -> metadata
Dictionary of pack_id -> metadata (deep copies)
"""
return self.data["presets"]
return copy.deepcopy(self.data["presets"])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preset system: Add enable/disable toggle functionality Preset system: Add update semantics (update/restore methods)

2 participants