From 9b9fcf4e43b25b8fcb524fac92451f6ca041c043 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Tue, 29 Jul 2025 22:16:46 +0200 Subject: [PATCH 01/14] Added a few useful type hints Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/analyze.py | 5 +++-- cfbs/cfbs_config.py | 5 +++-- cfbs/commands.py | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cfbs/analyze.py b/cfbs/analyze.py index a0e2e29d..4dc3813d 100644 --- a/cfbs/analyze.py +++ b/cfbs/analyze.py @@ -1,5 +1,6 @@ from collections import OrderedDict import os +from typing import Tuple, Union from cfbs.internal_file_management import fetch_archive from cfbs.masterfiles.analyze import ( @@ -347,7 +348,7 @@ def to_json_dict(self): class AnalyzedFiles: - def __init__(self, reference_version): + def __init__(self, reference_version: Union[str, None]): self.reference_version = reference_version self.missing = [] @@ -507,7 +508,7 @@ def analyze_policyset( masterfiles_dir="masterfiles", ignored_path_components=None, offline=False, -): +) -> Tuple[AnalyzedFiles, VersionsData]: """`path` should be either a masterfiles-path (containing masterfiles files directly), or a parent-path (containing `masterfiles_dir` and "modules" folders). `is_parentpath` should specify which of the two it is. diff --git a/cfbs/cfbs_config.py b/cfbs/cfbs_config.py index e38c1b88..6db6af56 100644 --- a/cfbs/cfbs_config.py +++ b/cfbs/cfbs_config.py @@ -19,6 +19,7 @@ import glob import logging as log from collections import OrderedDict +from typing import List from cfbs.result import Result from cfbs.utils import ( @@ -43,7 +44,7 @@ # Legacy; do not use. Use the 'Result' namedtuple instead. class CFBSReturnWithoutCommit(Exception): - def __init__(self, r): + def __init__(self, r: int): self.retval = r @@ -399,7 +400,7 @@ def _add_modules( def add_command( self, - to_add: list, + to_add: List[str], added_by="cfbs add", checksum=None, ) -> Result: diff --git a/cfbs/commands.py b/cfbs/commands.py index 56ea6a68..5698ba2d 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -122,7 +122,7 @@ def search_command(terms: List[str]) -> int: # Decorator to specify that a function is a command (verb in the CLI) # Adds the name + function pair to the global dict of commands # Does not modify/wrap the function it decorates. -def cfbs_command(name): +def cfbs_command(name: str): def inner(function): _commands[name] = function return function # Unmodified, we've just added it to the dict @@ -136,7 +136,7 @@ def get_command_names(): @cfbs_command("pretty") -def pretty_command(filenames: list, check: bool, keep_order: bool) -> int: +def pretty_command(filenames: List[str], check: bool, keep_order: bool) -> int: if not filenames: raise CFBSExitError("Filenames missing for cfbs pretty command") @@ -345,7 +345,7 @@ def status_command() -> int: @cfbs_command("search") -def search_command(terms: list) -> int: +def search_command(terms: List[str]) -> int: index = CFBSConfig.get_instance().index results = {} @@ -390,7 +390,7 @@ def search_command(terms: list) -> int: @cfbs_command("add") @commit_after_command("Added module%s %s", [PLURAL_S, FIRST_ARG_SLIST]) def add_command( - to_add: list, + to_add: List[str], added_by="cfbs add", checksum=None, ) -> Union[Result, int]: From 34225c42beca33956aaa3faeff46734f286ec047 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:27:32 +0200 Subject: [PATCH 02/14] Removed an obsolete return type hint --- cfbs/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfbs/commands.py b/cfbs/commands.py index 5698ba2d..b355ae4f 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -393,7 +393,7 @@ def add_command( to_add: List[str], added_by="cfbs add", checksum=None, -) -> Union[Result, int]: +): config = CFBSConfig.get_instance() validate_config_raise_exceptions(config, empty_build_list_ok=True) r = config.add_command(to_add, added_by, checksum) From 19d61443e58206008af22e20163c25d7d8b9aea1 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Tue, 29 Jul 2025 19:55:38 +0200 Subject: [PATCH 03/14] Added type hints to the `Result` type Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/result.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cfbs/result.py b/cfbs/result.py index e9ba98a3..ed4ee0a4 100644 --- a/cfbs/result.py +++ b/cfbs/result.py @@ -1,5 +1,11 @@ -from collections import namedtuple +from typing import List, NamedTuple, Union -Result = namedtuple( - "Result", ("return_code", "do_commit", "commit_message", "commit_files") +Result = NamedTuple( + "Result", + ( + ("return_code", int), + ("do_commit", bool), + ("commit_message", Union[str, None]), + ("commit_files", List[str]), + ), ) From 6e59f6fda1580fae424afab848a76f1090d6adb4 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Tue, 29 Jul 2025 20:10:36 +0200 Subject: [PATCH 04/14] Improved code quality of a function argument Typing was incorrect, name was unclear Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/git_magic.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cfbs/git_magic.py b/cfbs/git_magic.py index c6c9c51a..87778672 100644 --- a/cfbs/git_magic.py +++ b/cfbs/git_magic.py @@ -65,7 +65,7 @@ def with_git_commit( files_to_commit, commit_msg, positional_args_lambdas=None, - failed_return=False, + failure_exit_code=1, ): def decorator(fn): def decorated_fn(*args, **kwargs): @@ -125,7 +125,7 @@ def decorated_fn(*args, **kwargs): print(str(e)) else: print("Failed to commit changes, discarding them...") - return failed_return + return failure_exit_code return ret return decorated_fn @@ -133,6 +133,4 @@ def decorated_fn(*args, **kwargs): return decorator -commit_after_command = partial( - with_git_commit, (0,), ("cfbs.json",), failed_return=True -) +commit_after_command = partial(with_git_commit, (0,), ("cfbs.json",)) From 0da37c2f9edbf03b9a2d3c7c661b7dd426a09234 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Tue, 29 Jul 2025 22:25:01 +0200 Subject: [PATCH 05/14] Changed the constructor of the `Result` type to allow optional arguments, and changed cfbs command functions with `git_magic` decorators to always return `Result` before decoration Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/commands.py | 11 ++++++----- cfbs/git_magic.py | 8 +++----- cfbs/result.py | 15 ++++++--------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/cfbs/commands.py b/cfbs/commands.py index b355ae4f..215345d6 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -511,7 +511,8 @@ def _get_modules_by_url(name) -> list: @cfbs_command("clean") @commit_after_command("Cleaned unused modules") def clean_command(config=None): - return _clean_unused_modules(config) + r = _clean_unused_modules(config) + return Result(r) def _clean_unused_modules(config=None): @@ -1122,19 +1123,19 @@ def set_input_command(name, infile): module = config.get_module_from_build(name) if module is None: log.error("Module '%s' not found" % name) - return 1 + return Result(1) spec = module.get("input") if spec is None: log.error("Module '%s' does not accept input" % name) - return 1 + return Result(1) log.debug("Input spec for module '%s': %s" % (name, pretty(spec))) try: data = json.load(infile, object_pairs_hook=OrderedDict) except json.decoder.JSONDecodeError as e: log.error("Error reading json from stdin: %s" % e) - return 1 + return Result(1) log.debug("Input data for module '%s': %s" % (name, pretty(data))) def _compare_dict(a, b, ignore=None): @@ -1178,7 +1179,7 @@ def _compare_list(a, b): "Input data for module '%s' does not conform with input definition" % name ) - return 1 + return Result(1) path = os.path.join(name, "input.json") diff --git a/cfbs/git_magic.py b/cfbs/git_magic.py index 87778672..86b92779 100644 --- a/cfbs/git_magic.py +++ b/cfbs/git_magic.py @@ -4,7 +4,7 @@ do prompts, etc. """ -from typing import Iterable, Union +from typing import Callable, Iterable, Union from cfbs.prompts import prompt_user_yesno from cfbs.cfbs_config import CFBSConfig, CFBSReturnWithoutCommit from cfbs.git import ( @@ -67,16 +67,14 @@ def with_git_commit( positional_args_lambdas=None, failure_exit_code=1, ): - def decorator(fn): + def decorator(fn: Callable[..., Result]): def decorated_fn(*args, **kwargs): try: result = fn(*args, **kwargs) except CFBSReturnWithoutCommit as e: # Legacy; do not use. Use the Result namedtuple instead. return e.retval - ret, should_commit, msg, files = ( - result if isinstance(result, Result) else (result, True, None, []) - ) + ret, should_commit, msg, files = result files += files_to_commit # Message from the Result namedtuple overrides message from decorator diff --git a/cfbs/result.py b/cfbs/result.py index ed4ee0a4..2854997f 100644 --- a/cfbs/result.py +++ b/cfbs/result.py @@ -1,11 +1,8 @@ from typing import List, NamedTuple, Union -Result = NamedTuple( - "Result", - ( - ("return_code", int), - ("do_commit", bool), - ("commit_message", Union[str, None]), - ("commit_files", List[str]), - ), -) + +class Result(NamedTuple): + return_code: int + do_commit: bool = True + commit_message: Union[str, None] = None + commit_files: List[str] = [] From 593bedfe038e71a26cb0d21bb379d7e6c5fc612a Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Tue, 29 Jul 2025 22:46:00 +0200 Subject: [PATCH 06/14] Added a return type hint to the `git_magic` decorator This fixes Python type checking tooling being confused about return types of functions decorated by the `git_magic` decorator - apparently, an explicit return type hint is needed for correct functioning even when the return type is correctly inferred Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/commands.py | 7 +------ cfbs/git_magic.py | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/cfbs/commands.py b/cfbs/commands.py index 215345d6..3135c0bf 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -37,9 +37,7 @@ def search_command(terms: List[str]) -> int: CFBSConfig in cfbs_config.py. Commands should generally not call other commands, instead they should call the correct CFBSConfig method(s). 2. Decorators, like the git ones, should not change the parameters nor types of - the functions they decorate. This makes them much harder to read, and - type checkers don't understand it either. It applies to both types and values of - parameters and returns. + the functions they decorate. This makes them much harder to read. It applies to both types and values of parameters and returns. """ import os @@ -287,9 +285,6 @@ def init_command( to_add = ["%s@%s" % (remote, commit), "masterfiles"] if to_add: result = add_command(to_add, added_by="cfbs init") - assert not isinstance( - result, Result - ), "Our git decorators are confusing the type checkers" if result != 0: return result # TODO: Do we need to make commits here? diff --git a/cfbs/git_magic.py b/cfbs/git_magic.py index 86b92779..897901c8 100644 --- a/cfbs/git_magic.py +++ b/cfbs/git_magic.py @@ -68,7 +68,7 @@ def with_git_commit( failure_exit_code=1, ): def decorator(fn: Callable[..., Result]): - def decorated_fn(*args, **kwargs): + def decorated_fn(*args, **kwargs) -> int: try: result = fn(*args, **kwargs) except CFBSReturnWithoutCommit as e: From dab3898b2a22f721d87f9cd1db7b0a45c7907f4a Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:57:00 +0200 Subject: [PATCH 07/14] Removed now-redundant docs Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/git.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cfbs/git.py b/cfbs/git.py index 1f9ff9e6..87a9686a 100644 --- a/cfbs/git.py +++ b/cfbs/git.py @@ -159,9 +159,7 @@ def git_commit( :param commit_msg: commit message to use for the commit :param scope: files to include in the commit or `"all"` (`git commit -a`) - :type scope: str or an iterable of str - :param edit_commit_msg=False: whether the user should be prompted to edit and - save the commit message or not + :param edit_commit_msg: whether the user should be prompted to edit and save the commit message or not :param user_name: override git config user name :param user_email: override git config user email From 8bd28210476defb634e604809ecba0b1b916b043 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:06:15 +0200 Subject: [PATCH 08/14] Fixed outdated return type hint of `_main` The command functions never return `Result`, and pyright now understands that. Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/main.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cfbs/main.py b/cfbs/main.py index d78fd513..ebcef324 100644 --- a/cfbs/main.py +++ b/cfbs/main.py @@ -8,10 +8,8 @@ import os import traceback import pathlib -from typing import Union from cfbs.git import CFBSGitError -from cfbs.result import Result from cfbs.validate import validate_index_string from cfbs.version import string as version from cfbs.utils import ( @@ -51,7 +49,7 @@ def does_log_info(level): return level == "info" or level == "debug" -def _main() -> Union[int, Result]: +def _main() -> int: """Actual body of main function. Mainly for getting command line arguments and calling the appropriate @@ -266,13 +264,9 @@ def main() -> int: """ if os.getenv("CFBACKTRACE") == "1": r = _main() - assert type(r) is int return r try: r = _main() - # TODO: I'm not exactly sure when the commands - # would actually return result and what that really means. - assert type(r) is int return r except CFBSValidationError as e: print("Error: " + str(e)) From f7d9f0db829f44821d93131c9da45921b874142e Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:26:01 +0200 Subject: [PATCH 09/14] Renamed the `Result` type The former name was highly generic and did not explain the purpose of the class. Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/cfbs_config.py | 8 ++++---- cfbs/commands.py | 29 +++++++++++++++++------------ cfbs/git_magic.py | 8 ++++---- cfbs/result.py | 2 +- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/cfbs/cfbs_config.py b/cfbs/cfbs_config.py index 6db6af56..db0595a5 100644 --- a/cfbs/cfbs_config.py +++ b/cfbs/cfbs_config.py @@ -21,7 +21,7 @@ from collections import OrderedDict from typing import List -from cfbs.result import Result +from cfbs.result import CFBSCommandGitResult from cfbs.utils import ( CFBSExitError, CFBSUserError, @@ -42,7 +42,7 @@ from cfbs.validate import validate_single_module -# Legacy; do not use. Use the 'Result' namedtuple instead. +# Legacy; do not use. Use the 'CFBSCommandGitResult' namedtuple instead. class CFBSReturnWithoutCommit(Exception): def __init__(self, r: int): self.retval = r @@ -403,7 +403,7 @@ def add_command( to_add: List[str], added_by="cfbs add", checksum=None, - ) -> Result: + ) -> CFBSCommandGitResult: if not to_add: raise CFBSUserError("Must specify at least one module to add") @@ -469,7 +469,7 @@ def add_command( msg = msg.replace("\n", "\n\n", 1) changes_made = count > 0 - return Result(0, changes_made, msg, files) + return CFBSCommandGitResult(0, changes_made, msg, files) def input_command(self, module_name, input_data): def _check_keys(keys, input_data): diff --git a/cfbs/commands.py b/cfbs/commands.py index 3135c0bf..0c77107c 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -103,7 +103,12 @@ def search_command(terms: List[str]) -> int: CFBSGitError, ls_remote, ) -from cfbs.git_magic import Result, commit_after_command, git_commit_maybe_prompt + +from cfbs.git_magic import ( + CFBSCommandGitResult, + commit_after_command, + git_commit_maybe_prompt, +) from cfbs.prompts import prompt_user, prompt_user_yesno from cfbs.module import Module, is_module_added_manually from cfbs.masterfiles.generate_release_information import generate_release_information @@ -500,14 +505,14 @@ def _get_modules_by_url(name) -> list: _clean_unused_modules(config) except CFBSReturnWithoutCommit: pass - return Result(0, changes_made, msg, files) + return CFBSCommandGitResult(0, changes_made, msg, files) @cfbs_command("clean") @commit_after_command("Cleaned unused modules") def clean_command(config=None): r = _clean_unused_modules(config) - return Result(r) + return CFBSCommandGitResult(r) def _clean_unused_modules(config=None): @@ -559,7 +564,7 @@ def _someone_needs_me(this) -> bool: @cfbs_command("update") @commit_after_command("Updated module%s", [PLURAL_S]) -def update_command(to_update) -> Result: +def update_command(to_update): config = CFBSConfig.get_instance() r = validate_config(config, empty_build_list_ok=True) valid_before = r == 0 @@ -726,7 +731,7 @@ def update_command(to_update) -> Result: else: print("Modules are already up to date") - return Result( + return CFBSCommandGitResult( 0, module_updates.changes_made, module_updates.msg, module_updates.files ) @@ -1077,7 +1082,7 @@ def analyze_command( @cfbs_command("input") @commit_after_command("Added input for module%s", [PLURAL_S]) -def input_command(args, input_from="cfbs input") -> Result: +def input_command(args, input_from="cfbs input"): config = CFBSConfig.get_instance() validate_config_raise_exceptions(config, empty_build_list_ok=True) do_commit = False @@ -1107,7 +1112,7 @@ def input_command(args, input_from="cfbs input") -> Result: do_commit = True files_to_commit.append(input_path) config.save() - return Result(0, do_commit, None, files_to_commit) + return CFBSCommandGitResult(0, do_commit, None, files_to_commit) @cfbs_command("set-input") @@ -1118,19 +1123,19 @@ def set_input_command(name, infile): module = config.get_module_from_build(name) if module is None: log.error("Module '%s' not found" % name) - return Result(1) + return CFBSCommandGitResult(1) spec = module.get("input") if spec is None: log.error("Module '%s' does not accept input" % name) - return Result(1) + return CFBSCommandGitResult(1) log.debug("Input spec for module '%s': %s" % (name, pretty(spec))) try: data = json.load(infile, object_pairs_hook=OrderedDict) except json.decoder.JSONDecodeError as e: log.error("Error reading json from stdin: %s" % e) - return Result(1) + return CFBSCommandGitResult(1) log.debug("Input data for module '%s': %s" % (name, pretty(data))) def _compare_dict(a, b, ignore=None): @@ -1174,7 +1179,7 @@ def _compare_list(a, b): "Input data for module '%s' does not conform with input definition" % name ) - return Result(1) + return CFBSCommandGitResult(1) path = os.path.join(name, "input.json") @@ -1190,7 +1195,7 @@ def _compare_list(a, b): else: log.debug("Input data for '%s' unchanged, nothing to write / commit" % name) - return Result(0, changes_made, None, [path]) + return CFBSCommandGitResult(0, changes_made, None, [path]) @cfbs_command("get-input") diff --git a/cfbs/git_magic.py b/cfbs/git_magic.py index 897901c8..b1d8d86c 100644 --- a/cfbs/git_magic.py +++ b/cfbs/git_magic.py @@ -15,7 +15,7 @@ git_check_tracked_changes, ) from cfbs.args import get_args -from cfbs.result import Result +from cfbs.result import CFBSCommandGitResult import logging as log from functools import partial @@ -67,17 +67,17 @@ def with_git_commit( positional_args_lambdas=None, failure_exit_code=1, ): - def decorator(fn: Callable[..., Result]): + def decorator(fn: Callable[..., CFBSCommandGitResult]): def decorated_fn(*args, **kwargs) -> int: try: result = fn(*args, **kwargs) except CFBSReturnWithoutCommit as e: - # Legacy; do not use. Use the Result namedtuple instead. + # Legacy; do not use. Use the CFBSCommandGitResult namedtuple instead. return e.retval ret, should_commit, msg, files = result files += files_to_commit - # Message from the Result namedtuple overrides message from decorator + # Message from the CFBSCommandGitResult namedtuple overrides message from decorator if not msg: if positional_args_lambdas: positional_args = ( diff --git a/cfbs/result.py b/cfbs/result.py index 2854997f..d2d98f9b 100644 --- a/cfbs/result.py +++ b/cfbs/result.py @@ -1,7 +1,7 @@ from typing import List, NamedTuple, Union -class Result(NamedTuple): +class CFBSCommandGitResult(NamedTuple): return_code: int do_commit: bool = True commit_message: Union[str, None] = None From bd75205b553575ea2a24b20fa5ae63c7d4e87879 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:32:40 +0200 Subject: [PATCH 10/14] Renamed the `result.py` file, fixed an indirect import This is useful in case more helper types are added. Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/cfbs_config.py | 2 +- cfbs/commands.py | 7 ++----- cfbs/git_magic.py | 2 +- cfbs/{result.py => types.py} | 0 4 files changed, 4 insertions(+), 7 deletions(-) rename cfbs/{result.py => types.py} (100%) diff --git a/cfbs/cfbs_config.py b/cfbs/cfbs_config.py index db0595a5..27718ae5 100644 --- a/cfbs/cfbs_config.py +++ b/cfbs/cfbs_config.py @@ -21,7 +21,7 @@ from collections import OrderedDict from typing import List -from cfbs.result import CFBSCommandGitResult +from cfbs.types import CFBSCommandGitResult from cfbs.utils import ( CFBSExitError, CFBSUserError, diff --git a/cfbs/commands.py b/cfbs/commands.py index 0c77107c..826fe560 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -52,6 +52,7 @@ def search_command(terms: List[str]) -> int: from cfbs.args import get_args from cfbs.cfbs_json import CFBSJson +from cfbs.types import CFBSCommandGitResult from cfbs.updates import ModuleUpdates, update_module from cfbs.utils import ( CFBSNetworkError, @@ -104,11 +105,7 @@ def search_command(terms: List[str]) -> int: ls_remote, ) -from cfbs.git_magic import ( - CFBSCommandGitResult, - commit_after_command, - git_commit_maybe_prompt, -) +from cfbs.git_magic import commit_after_command, git_commit_maybe_prompt from cfbs.prompts import prompt_user, prompt_user_yesno from cfbs.module import Module, is_module_added_manually from cfbs.masterfiles.generate_release_information import generate_release_information diff --git a/cfbs/git_magic.py b/cfbs/git_magic.py index b1d8d86c..e1f11183 100644 --- a/cfbs/git_magic.py +++ b/cfbs/git_magic.py @@ -15,7 +15,7 @@ git_check_tracked_changes, ) from cfbs.args import get_args -from cfbs.result import CFBSCommandGitResult +from cfbs.types import CFBSCommandGitResult import logging as log from functools import partial diff --git a/cfbs/result.py b/cfbs/types.py similarity index 100% rename from cfbs/result.py rename to cfbs/types.py From c8d88998760e7ce96167a946188b66fb6f37ee70 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Wed, 30 Jul 2025 16:08:55 +0200 Subject: [PATCH 11/14] Moved function doc from code comment to docstring Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/commands.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cfbs/commands.py b/cfbs/commands.py index 826fe560..8954fb2d 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -119,10 +119,13 @@ def search_command(terms: List[str]) -> int: _commands = OrderedDict() -# Decorator to specify that a function is a command (verb in the CLI) -# Adds the name + function pair to the global dict of commands -# Does not modify/wrap the function it decorates. def cfbs_command(name: str): + """ + Decorator to specify that a function is a command (verb in the CLI). + Adds the name + function pair to the global dict of commands. + Does not modify/wrap the function it decorates. + """ + def inner(function): _commands[name] = function return function # Unmodified, we've just added it to the dict From 9bf92b074b4efbf6196aae7194561ddb2665d2b8 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Wed, 30 Jul 2025 16:30:32 +0200 Subject: [PATCH 12/14] Ensured cfbs command functions always return integers The explicit type hints have been removed because they are redundant and could be a conflicting second source of truth Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/commands.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/cfbs/commands.py b/cfbs/commands.py index 8954fb2d..75e8182c 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -22,7 +22,7 @@ For example, cfbs status does not take any arguments, so the signature should be: -def status_command() -> int: +def status_command(): On the other hand, cfbs search takes a list of search terms: @@ -30,7 +30,7 @@ def status_command() -> int: So, the appropriate signature is: -def search_command(terms: List[str]) -> int: +def search_command(terms: List[str]): Todos: 1. Some of these functions are getting too long, business logic should be moved into @@ -46,7 +46,7 @@ def search_command(terms: List[str]) -> int: import logging as log import json import functools -from typing import List, Union +from typing import Callable, List, Union from collections import OrderedDict from cfbs.analyze import analyze_policyset from cfbs.args import get_args @@ -124,9 +124,10 @@ def cfbs_command(name: str): Decorator to specify that a function is a command (verb in the CLI). Adds the name + function pair to the global dict of commands. Does not modify/wrap the function it decorates. + Ensures cfbs command functions return an `int`. """ - def inner(function): + def inner(function: Callable[..., int]): _commands[name] = function return function # Unmodified, we've just added it to the dict @@ -139,7 +140,7 @@ def get_command_names(): @cfbs_command("pretty") -def pretty_command(filenames: List[str], check: bool, keep_order: bool) -> int: +def pretty_command(filenames: List[str], check: bool, keep_order: bool): if not filenames: raise CFBSExitError("Filenames missing for cfbs pretty command") @@ -174,7 +175,7 @@ def init_command( masterfiles=None, non_interactive=False, use_git: Union[bool, None] = None, -) -> int: +): if is_cfbs_repo(): raise CFBSUserError("Already initialized - look at %s" % cfbs_filename()) @@ -298,7 +299,7 @@ def init_command( @cfbs_command("status") -def status_command() -> int: +def status_command(): config = CFBSConfig.get_instance() validate_config_raise_exceptions(config, empty_build_list_ok=True) print("Name: %s" % config["name"]) @@ -345,7 +346,7 @@ def status_command() -> int: @cfbs_command("search") -def search_command(terms: List[str]) -> int: +def search_command(terms: List[str]): index = CFBSConfig.get_instance().index results = {} @@ -737,7 +738,7 @@ def update_command(to_update): @cfbs_command("validate") -def validate_command(paths=None, index_arg=None) -> int: +def validate_command(paths=None, index_arg=None): if paths: ret_value = 0 @@ -874,7 +875,7 @@ def _download_dependencies( @cfbs_command("download") -def download_command(force, ignore_versions=False) -> int: +def download_command(force, ignore_versions=False): config = CFBSConfig.get_instance() r = validate_config(config) if r != 0: @@ -888,7 +889,7 @@ def download_command(force, ignore_versions=False) -> int: @cfbs_command("build") -def build_command(ignore_versions=False) -> int: +def build_command(ignore_versions=False): config = CFBSConfig.get_instance() r = validate_config(config) if r != 0: @@ -906,7 +907,7 @@ def build_command(ignore_versions=False) -> int: @cfbs_command("install") -def install_command(args) -> int: +def install_command(args): if len(args) > 1: raise CFBSExitError( "Only one destination is allowed for command: cfbs install [destination]" @@ -1018,7 +1019,7 @@ def analyze_command( user_ignored_path_components=None, offline=False, verbose=False, -) -> int: +): if len(policyset_paths) == 0: # no policyset path is a shorthand for using the current directory as the policyset path log.info( @@ -1199,7 +1200,7 @@ def _compare_list(a, b): @cfbs_command("get-input") -def get_input_command(name, outfile) -> int: +def get_input_command(name, outfile): config = CFBSConfig.get_instance() config.warn_about_unknown_keys() module = config.get_module_from_build(name) From eaa6a0082825305c6eeda33c5343896cd1494d40 Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Wed, 30 Jul 2025 17:07:38 +0200 Subject: [PATCH 13/14] Defined a `CFBSCommandExitCode` type to improve maintainability Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/commands.py | 6 +++--- cfbs/git_magic.py | 4 ++-- cfbs/types.py | 2 ++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cfbs/commands.py b/cfbs/commands.py index 75e8182c..5362344e 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -52,7 +52,7 @@ def search_command(terms: List[str]): from cfbs.args import get_args from cfbs.cfbs_json import CFBSJson -from cfbs.types import CFBSCommandGitResult +from cfbs.types import CFBSCommandExitCode, CFBSCommandGitResult from cfbs.updates import ModuleUpdates, update_module from cfbs.utils import ( CFBSNetworkError, @@ -124,10 +124,10 @@ def cfbs_command(name: str): Decorator to specify that a function is a command (verb in the CLI). Adds the name + function pair to the global dict of commands. Does not modify/wrap the function it decorates. - Ensures cfbs command functions return an `int`. + Ensures cfbs command functions return a `CFBSCommandExitCode`. """ - def inner(function: Callable[..., int]): + def inner(function: Callable[..., CFBSCommandExitCode]): _commands[name] = function return function # Unmodified, we've just added it to the dict diff --git a/cfbs/git_magic.py b/cfbs/git_magic.py index e1f11183..30bcad1b 100644 --- a/cfbs/git_magic.py +++ b/cfbs/git_magic.py @@ -15,7 +15,7 @@ git_check_tracked_changes, ) from cfbs.args import get_args -from cfbs.types import CFBSCommandGitResult +from cfbs.types import CFBSCommandExitCode, CFBSCommandGitResult import logging as log from functools import partial @@ -68,7 +68,7 @@ def with_git_commit( failure_exit_code=1, ): def decorator(fn: Callable[..., CFBSCommandGitResult]): - def decorated_fn(*args, **kwargs) -> int: + def decorated_fn(*args, **kwargs) -> CFBSCommandExitCode: try: result = fn(*args, **kwargs) except CFBSReturnWithoutCommit as e: diff --git a/cfbs/types.py b/cfbs/types.py index d2d98f9b..0e9bf0ea 100644 --- a/cfbs/types.py +++ b/cfbs/types.py @@ -1,5 +1,7 @@ from typing import List, NamedTuple, Union +CFBSCommandExitCode = int + class CFBSCommandGitResult(NamedTuple): return_code: int From 2d7b74531f1bd8c1a26d66ea322d525d304e654f Mon Sep 17 00:00:00 2001 From: jakub-nt <175944085+jakub-nt@users.noreply.github.com> Date: Wed, 30 Jul 2025 17:08:12 +0200 Subject: [PATCH 14/14] Added documentation of cfbs command functions return types Signed-off-by: jakub-nt <175944085+jakub-nt@users.noreply.github.com> --- cfbs/commands.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cfbs/commands.py b/cfbs/commands.py index 5362344e..3e6de4cb 100644 --- a/cfbs/commands.py +++ b/cfbs/commands.py @@ -32,6 +32,11 @@ def status_command(): def search_command(terms: List[str]): +The return type (after any decoration) of the cfbs command functions is `CFBSCommandExitCode`. +This type is indistinguishably defined to be `int`. +The return type before decoration of functions decorated by `commit_after_command` is `CFBSCommandGitResult`. +The `commit_after_command` decorator then changes the return type to `CFBSCommandExitCode`. + Todos: 1. Some of these functions are getting too long, business logic should be moved into CFBSConfig in cfbs_config.py. Commands should generally not call other commands,