• 
      

    Add support for always-enabled extensions.

    Review Request #15106 — Created June 7, 2026 and updated

    Information

    Djblets
    release-6.x

    Reviewers

    The extension manager can now be given a list of extensions that must
    always be enabled. These act like enabled-by-default extensions, except
    that they cannot be disabled through disable_extension() or the UI,
    and will be considered enabled every time the extension manager loads.

    This and the enabled-by-default extensions can both be set in
    settings, or can now be set as attributes on the extension manager,
    giving implementations a central, non-settings-based place to customize
    this.

    Any always-enabled extension will still show up in the extension
    manager, but it won't have a button for disabling the extension.
    Similarly, the API will return an error when attempting to disable it,
    if invoked through other means.

    get_can_disable_extension(), and therefore disable_extension(), is
    mindful of extensions that are depended on by another. If any dependent
    extension is always-enabled, none of its dependencies anywhere in the
    dependency chain can be disabled.

    There are a few improvements to related functions to make all this work.
    get_can_disable_extension() can now take an extension instance or ID
    string along with the RegisteredExtension.
    get_dependent_extensions() can now return only enabled extensions.

    Unit tests passed.

    Marked some extensions as always-enabled. Verified that they were
    enabled on load/refresh, even when I forced a disable through other
    means.

    Verified that they could not be disabled via the UI. No button
    appeared and API requests attempting to disable were met with an
    error response.

    Summary ID
    Add support for always-enabled extensions.
    The extension manager can now be given a list of extensions that must always be enabled. These act like enabled-by-default extensions, except that they cannot be disabled through `disable_extension()` or the UI, and will be considered enabled every time the extension manager loads. This and the enabled-by-default extensions can both be set in `settings`, or can now be set as attributes on the extension manager, giving implementations a central, non-settings-based place to customize this. Any always-enabled extension will still show up in the extension manager, but it won't have a button for disabling the extension. Similarly, the API will return an error when attempting to disable it, if invoked through other means. `get_can_disable_extension()`, and therefore `disable_extension()`, is mindful of extensions that are depended on by another. If any dependent extension is always-enabled, none of its dependencies anywhere in the dependency chain can be disabled. There are a few improvements to related functions to make all this work. `get_can_disable_extension()` can now take an extension instance or ID string along with the `RegisteredExtension`. `get_dependent_extensions()` can now return only enabled extensions.
    fecfa03f833d37c5f2be6fd2c1ddc26a76c3f0af

    Description From Last Updated

    Missing a ) after EXTENSIONS_ENABLED_BY_DEFAULT

    david david

    And added an attribute for enabled by default extensions, which used to only exist as a setting.

    maubin maubin

    This should say settings.EXTENSIONS_ALWAYS_ENABLED.

    maubin maubin

    This is meant to say always_enabled_extension_ids for the :py:attr:.

    maubin maubin

    This should say settings.EXTENSIONS_ENABLED_BY_DEFAULT. Also we should mention that this will always include the IDs in always_enabled_extension_ids (see my comment …

    maubin maubin

    We should probably fetch get_dependent_extensions(extension_id) first and verify that all of the dependent extensions can be disabled before we allow …

    david david

    This should say settings.EXTENSIONS_ENABLED_BY_DEFAULT

    david david

    I think we should always include the always-enabled extension IDs in this list, regardless of if enabled_dy_default is UNSET or …

    maubin maubin

    This is missing a Version Added.

    maubin maubin

    This is missing a description.

    maubin maubin

    Typo: testing -> Testing

    david david

    Typo: testing -> Testing

    david david

    Typo: testing -> Testing

    david david

    Typo: testing -> Testing

    david david

    Typo: testing -> Testing

    david david

    'typing.TypeAlias' imported but unused Column: 5 Error code: F401

    reviewbot reviewbot
    maubin
    1. 
        
    2. djblets/extensions/manager.py (Diff revision 1)
       
       
      Show all issues

      And added an attribute for enabled by default extensions, which used to only exist as a setting.

    3. djblets/extensions/manager.py (Diff revision 1)
       
       
      Show all issues

      This should say settings.EXTENSIONS_ALWAYS_ENABLED.

    4. djblets/extensions/manager.py (Diff revision 1)
       
       
      Show all issues

      This is meant to say always_enabled_extension_ids for the :py:attr:.

    5. djblets/extensions/manager.py (Diff revision 1)
       
       
      Show all issues

      This should say settings.EXTENSIONS_ENABLED_BY_DEFAULT. Also we should mention that this will always include the IDs in always_enabled_extension_ids (see my comment below).

    6. djblets/extensions/manager.py (Diff revision 1)
       
       
       
       
       
       
       
       
       
       
      Show all issues

      I think we should always include the always-enabled extension IDs in this list, regardless of if enabled_dy_default is UNSET or not. Otherwise its confusing wether the always-enabled extensions are expected to be in this list or not.

    7. 
        
    david
    1. 
        
    2. djblets/extensions/manager.py (Diff revision 1)
       
       
       
      Show all issues

      Missing a ) after EXTENSIONS_ENABLED_BY_DEFAULT

    3. djblets/extensions/manager.py (Diff revision 1)
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
      Show all issues

      We should probably fetch get_dependent_extensions(extension_id) first and verify that all of the dependent extensions can be disabled before we allow the dependency to be disabled.

    4. djblets/extensions/manager.py (Diff revision 1)
       
       
      Show all issues

      This should say settings.EXTENSIONS_ENABLED_BY_DEFAULT

    5. Show all issues

      Typo: testing -> Testing

    6. Show all issues

      Typo: testing -> Testing

    7. Show all issues

      Typo: testing -> Testing

    8. Show all issues

      Typo: testing -> Testing

    9. Show all issues

      Typo: testing -> Testing

    10. 
        
    chipx86
    Review request changed
    Change Summary:
    • Added support for considering dependent extensions for disable checks.
    • get_can_disable_extension() now accepts an extension instance, ID string, or RegisteredExtension.
    • Fixed various typos and doc issues.
    • Added more unit tests.
    Description:
       

    The extension manager can now be given a list of extensions that must

        always be enabled. These act like enabled-by-default extensions, except
        that they cannot be disabled through disable_extension() or the UI,
        and will be considered enabled every time the extension manager loads.

       
       

    This and the enabled-by-default extensions can both be set in

        settings, or can now be set as attributes on the extension manager,
        giving implementations a central, non-settings-based place to customize
        this.

       
       

    Any always-enabled extension will still show up in the extension

        manager, but it won't have a button for disabling the extension.
        Similarly, the API will return an error when attempting to disable it,
        if invoked through other means.

      +
      +

    get_can_disable_extension(), and therefore disable_extension(), is

      + mindful of extensions that are depended on by another. If any dependent
      + extension is always-enabled, none of its dependencies anywhere in the
      + dependency chain can be disabled.

      +
      +

    There are a few improvements to related functions to make all this work.

      + get_can_disable_extension() can now take an extension instance or ID
      + string along with the RegisteredExtension.
      + get_dependent_extensions() can now return only enabled extensions.

    Commits:
    Summary ID
    Add support for always-enabled extensions.
    The extension manager can now be given a list of extensions that must always be enabled. These act like enabled-by-default extensions, except that they cannot be disabled through `disable_extension()` or the UI, and will be considered enabled every time the extension manager loads. This and the enabled-by-default extensions can both be set in `settings`, or can now be set as attributes on the extension manager, giving implementations a central, non-settings-based place to customize this. Any always-enabled extension will still show up in the extension manager, but it won't have a button for disabling the extension. Similarly, the API will return an error when attempting to disable it, if invoked through other means.
    828a5b487bca0e9ae1740f12952f16e5ec6c8f02
    Add support for always-enabled extensions.
    The extension manager can now be given a list of extensions that must always be enabled. These act like enabled-by-default extensions, except that they cannot be disabled through `disable_extension()` or the UI, and will be considered enabled every time the extension manager loads. This and the enabled-by-default extensions can both be set in `settings`, or can now be set as attributes on the extension manager, giving implementations a central, non-settings-based place to customize this. Any always-enabled extension will still show up in the extension manager, but it won't have a button for disabling the extension. Similarly, the API will return an error when attempting to disable it, if invoked through other means. `get_can_disable_extension()`, and therefore `disable_extension()`, is mindful of extensions that are depended on by another. If any dependent extension is always-enabled, none of its dependencies anywhere in the dependency chain can be disabled. There are a few improvements to related functions to make all this work. `get_can_disable_extension()` can now take an extension instance or ID string along with the `RegisteredExtension`. `get_dependent_extensions()` can now return only enabled extensions.
    fecfa03f833d37c5f2be6fd2c1ddc26a76c3f0af

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    maubin
    1. 
        
    2. djblets/extensions/manager.py (Diff revisions 1 - 2)
       
       
      Show all issues

      This is missing a Version Added.

    3. djblets/extensions/manager.py (Diff revisions 1 - 2)
       
       
       
       
      Show all issues

      This is missing a description.

    4.