Skip to content

trestle.core.beta_features

trestle.core.beta_features ¤

Beta feature registration and configuration support.

Attributes¤

BETA_FEATURES = {'json-signing': BetaFeature(name='json-signing', description='Sign and verify JSON artifacts with detached DSSE envelopes.', commands=['trestle sign', 'trestle verify'], since_version='4.0.3', stability='beta', documentation_url='https://oscal-compass.github.io/compliance-trestle/tutorials/cli/#trestle-sign', enabled_by_default=False, deprecation_version=None), 'json-manifest-signing': BetaFeature(name='json-manifest-signing', description='Generate, sign, and verify JSON package manifests for JSON artifacts.', commands=['trestle generate-manifest', 'trestle sign-manifest', 'trestle verify-manifest'], since_version='4.0.3', stability='beta', documentation_url='https://oscal-compass.github.io/compliance-trestle/tutorials/cli/#trestle-generate-manifest', enabled_by_default=False, deprecation_version=None)} module-attribute ¤

BETA_FEATURE_ATTR = '_trestle_beta_feature' module-attribute ¤

BETA_FLAG_HELP = 'Run this beta command one time without enabling it in config.' module-attribute ¤

BETA_SECTION = 'beta' module-attribute ¤

BetaCallable = TypeVar('BetaCallable', bound=Callable[..., int]) module-attribute ¤

ENABLED_FEATURES_KEY = 'enabled_features' module-attribute ¤

TRESTLE_BETA_FEATURES_ENV = 'TRESTLE_BETA_FEATURES' module-attribute ¤

Classes¤

BetaFeature dataclass ¤

Definition of a beta feature.

Source code in trestle/core/beta_features.py
37
38
39
40
41
42
43
44
45
46
47
48
@dataclass(frozen=True)
class BetaFeature:
    """Definition of a beta feature."""

    name: str
    description: str
    commands: List[str]
    since_version: str
    stability: str
    documentation_url: str
    enabled_by_default: bool = False
    deprecation_version: Optional[str] = None
Attributes¤
commands instance-attribute ¤
deprecation_version = None class-attribute instance-attribute ¤
description instance-attribute ¤
documentation_url instance-attribute ¤
enabled_by_default = False class-attribute instance-attribute ¤
name instance-attribute ¤
since_version instance-attribute ¤
stability instance-attribute ¤
Methods:¤
__init__(name, description, commands, since_version, stability, documentation_url, enabled_by_default=False, deprecation_version=None) ¤

Functions:¤

beta_feature(feature_name) ¤

Decorate a command run method so it requires an enabled beta feature.

Source code in trestle/core/beta_features.py
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def beta_feature(feature_name: str) -> Callable[[BetaCallable], BetaCallable]:
    """Decorate a command run method so it requires an enabled beta feature."""

    def decorator(func: BetaCallable) -> BetaCallable:
        @functools.wraps(func)
        def wrapper(self: Any, args: Any, *func_args: Any, **func_kwargs: Any) -> int:
            trestle_root = pathlib.Path(getattr(args, 'trestle_root', pathlib.Path.cwd()))
            feature = get_beta_feature(feature_name)
            if getattr(args, 'beta', False) or feature_name in get_enabled_features(trestle_root):
                return func(self, args, *func_args, **func_kwargs)

            self.out(f"Error: The '{self.name}' command is a beta feature and is not enabled.")
            self.out('')
            self.out(f'Feature: {feature.name}')
            self.out(f'Description: {feature.description}')
            self.out(f'Stability: {feature.stability}')
            if feature.documentation_url:
                self.out(f'Documentation: {feature.documentation_url}')
            self.out('')
            self.out('To enable this feature, run:')
            self.out(f'  trestle beta enable {feature.name}')
            return CmdReturnCodes.COMMAND_ERROR.value

        setattr(wrapper, BETA_FEATURE_ATTR, feature_name)
        return cast(BetaCallable, wrapper)

    return decorator

disable_all_features(trestle_root) ¤

Disable every config-enabled beta feature and return the number changed.

Source code in trestle/core/beta_features.py
176
177
178
179
180
181
182
183
def disable_all_features(trestle_root: pathlib.Path) -> int:
    """Disable every config-enabled beta feature and return the number changed."""
    config_path = get_beta_config_path(trestle_root)
    enabled_features = _read_config_enabled_features(config_path)
    configured_features = set(BETA_FEATURES).intersection(enabled_features)
    if configured_features:
        _write_config_enabled_features(config_path, enabled_features.difference(configured_features))
    return len(configured_features)

disable_feature(feature_name, trestle_root) ¤

Disable a beta feature in the selected config file and return True if it changed.

Source code in trestle/core/beta_features.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
def disable_feature(feature_name: str, trestle_root: pathlib.Path) -> bool:
    """Disable a beta feature in the selected config file and return True if it changed."""
    feature = get_beta_feature(feature_name)
    if feature.enabled_by_default:
        raise TrestleError(f"Beta feature '{feature_name}' is enabled by default and cannot be disabled.")
    if feature_name in _read_environment_enabled_features():
        raise TrestleError(
            f"Beta feature '{feature_name}' is enabled by {TRESTLE_BETA_FEATURES_ENV}. "
            f'Remove it from {TRESTLE_BETA_FEATURES_ENV} to disable it.'
        )

    config_path = get_beta_config_path(trestle_root)
    enabled_features = _read_config_enabled_features(config_path)

    if feature_name not in enabled_features:
        return False

    enabled_features.remove(feature_name)
    _write_config_enabled_features(config_path, enabled_features)
    return True

enable_all_features(trestle_root) ¤

Enable every registered beta feature and return the number changed.

Source code in trestle/core/beta_features.py
166
167
168
169
170
171
172
173
def enable_all_features(trestle_root: pathlib.Path) -> int:
    """Enable every registered beta feature and return the number changed."""
    config_path = get_beta_config_path(trestle_root)
    enabled_features = _read_config_enabled_features(config_path)
    newly_enabled = set(BETA_FEATURES).difference(enabled_features)
    if newly_enabled:
        _write_config_enabled_features(config_path, enabled_features.union(newly_enabled))
    return len(newly_enabled)

enable_feature(feature_name, trestle_root) ¤

Enable a beta feature in the selected config file and return True if it changed.

Source code in trestle/core/beta_features.py
130
131
132
133
134
135
136
137
138
139
140
141
def enable_feature(feature_name: str, trestle_root: pathlib.Path) -> bool:
    """Enable a beta feature in the selected config file and return True if it changed."""
    get_beta_feature(feature_name)
    config_path = get_beta_config_path(trestle_root)
    enabled_features = _read_config_enabled_features(config_path)

    if feature_name in enabled_features:
        return False

    enabled_features.add(feature_name)
    _write_config_enabled_features(config_path, enabled_features)
    return True

get_beta_config_path(trestle_root) ¤

Get the beta config path for a trestle workspace path or the user fallback.

Source code in trestle/core/beta_features.py
91
92
93
94
95
96
def get_beta_config_path(trestle_root: pathlib.Path) -> pathlib.Path:
    """Get the beta config path for a trestle workspace path or the user fallback."""
    workspace_root = file_utils.extract_trestle_project_root(pathlib.Path(trestle_root).resolve())
    if workspace_root is not None:
        return workspace_root / const.TRESTLE_CONFIG_DIR / const.TRESTLE_CONFIG_FILE
    return get_user_beta_config_path()

get_beta_feature(feature_name) ¤

Get a registered beta feature by name.

Source code in trestle/core/beta_features.py
 99
100
101
102
103
104
def get_beta_feature(feature_name: str) -> BetaFeature:
    """Get a registered beta feature by name."""
    try:
        return BETA_FEATURES[feature_name]
    except KeyError:
        raise TrestleError(f"Beta feature '{feature_name}' not found.")

get_beta_feature_name(command_callable) ¤

Get the beta feature name from a decorated command callable if present.

Source code in trestle/core/beta_features.py
215
216
217
218
219
220
221
def get_beta_feature_name(command_callable: Callable[..., Any]) -> Optional[str]:
    """Get the beta feature name from a decorated command callable if present."""
    feature_name = getattr(command_callable, BETA_FEATURE_ATTR, None)
    if feature_name is not None:
        return cast(str, feature_name)
    bound_function = getattr(command_callable, '__func__', None)
    return cast(Optional[str], getattr(bound_function, BETA_FEATURE_ATTR, None))

get_enabled_features(trestle_root) ¤

Get registered beta features enabled for the given trestle root.

Source code in trestle/core/beta_features.py
112
113
114
115
116
117
118
119
120
121
def get_enabled_features(trestle_root: pathlib.Path) -> Set[str]:
    """Get registered beta features enabled for the given trestle root."""
    enabled_features = _read_config_enabled_features(get_beta_config_path(trestle_root))
    enabled_features.update(_read_environment_enabled_features())

    for feature_name, feature in BETA_FEATURES.items():
        if feature.enabled_by_default:
            enabled_features.add(feature_name)

    return {feature_name for feature_name in enabled_features if feature_name in BETA_FEATURES}

get_registered_features() ¤

Get all registered beta features.

Source code in trestle/core/beta_features.py
107
108
109
def get_registered_features() -> Dict[str, BetaFeature]:
    """Get all registered beta features."""
    return BETA_FEATURES

get_user_beta_config_path() ¤

Get the user-level beta feature config path.

Source code in trestle/core/beta_features.py
81
82
83
84
85
86
87
88
def get_user_beta_config_path() -> pathlib.Path:
    """Get the user-level beta feature config path."""
    if os.name == 'nt':  # pragma: no cover
        appdata = os.environ.get('APPDATA')
        config_root = pathlib.Path(appdata) if appdata else pathlib.Path.home() / 'AppData' / 'Roaming'
    else:
        config_root = pathlib.Path(os.environ.get('XDG_CONFIG_HOME', pathlib.Path.home() / '.config'))
    return config_root / 'trestle' / 'beta.ini'

is_beta_enabled(feature_name, trestle_root) ¤

Return True if the beta feature is registered and enabled.

Source code in trestle/core/beta_features.py
124
125
126
127
def is_beta_enabled(feature_name: str, trestle_root: pathlib.Path) -> bool:
    """Return True if the beta feature is registered and enabled."""
    get_beta_feature(feature_name)
    return feature_name in get_enabled_features(trestle_root)

parse_feature_names(feature_names) ¤

Parse comma and whitespace separated beta feature names.

Source code in trestle/core/beta_features.py
75
76
77
78
def parse_feature_names(feature_names: str) -> Set[str]:
    """Parse comma and whitespace separated beta feature names."""
    normalized_feature_names = feature_names.replace(',', ' ')
    return {name.strip() for name in normalized_feature_names.split() if name.strip()}

handler: python