CLI

Common CLI Modules

This module includes common CLI methods and parser extension

Attributes

BOLD module-attribute

BOLD = '\x1b[1m'

GRN module-attribute

GRN = '\x1b[1;32m'

NC module-attribute

NC = '\x1b[0m'

RED module-attribute

RED = '\x1b[1;31m'

YLW module-attribute

YLW = '\x1b[1;33m'

Classes

CLIArgumentParser

CLIArgumentParser(subparser, parser=None)
Source code in fedbiomed/common/cli.py
def __init__(self, subparser: argparse.ArgumentParser, parser = None):

    self._subparser = subparser
    # Parser that is going to be add using subparser
    self._parser = None

    self._main_parser = parser

Functions

default
default(args=None)

Default function for subparser command

Source code in fedbiomed/common/cli.py
def default(self, args: argparse.Namespace = None) -> None:
    """Default function for subparser command"""

    self._parser.print_help()

    return None

CommonCLI

CommonCLI()
Source code in fedbiomed/common/cli.py
def __init__(self) -> None:
    self._parser: argparse.ArgumentParser = argparse.ArgumentParser(
        prog="fedbiomed", formatter_class=argparse.RawTextHelpFormatter
    )

    self._subparsers = self._parser.add_subparsers()
    self._certificate_manager: CertificateManager = CertificateManager()
    self._description: str = ""
    self._args = None

    # Initialize configuration parser
    self.configuration_parser = ComponentParser(self._subparsers)

Attributes

arguments property
arguments

Gets global parser arguments

Returns:

Type Description
Namespace

Parser arguments

config instance-attribute
config
configuration_parser instance-attribute
configuration_parser = ComponentParser(_subparsers)
description property writable
description

Gets description of CLI

Returns:

Type Description
str

Description (Intro) for the CLI

parser property
parser

Gets parser for CLI

Returns:

Type Description
ArgumentParser

Main argument parser object

subparsers property
subparsers

Gets subparsers of common cli

Returns:

Type Description

Subparsers of CLI parser

Functions

config_action staticmethod
config_action(this, component)

Returns CLI argument action for config file name

Source code in fedbiomed/common/cli.py
@staticmethod
def config_action(this: "CommonCLI", component: ComponentType):
    """Returns CLI argument action for config file name"""
    return ComponentDirectoryAction
error staticmethod
error(message)

Prints given error message

Parameters:

Name Type Description Default
message str

Error message

required
Source code in fedbiomed/common/cli.py
@staticmethod
def error(message: str) -> None:
    """Prints given error message

    Args:
        message: Error message
    """
    print(f"{RED}ERROR:{NC}")
    print(f"{BOLD}{message}{NC}")
    logger.critical(message)
    sys.exit(1)
initialize
initialize()

Initializes parser classes and common parser for child classes.

This parser classes will be added by child classes.

Source code in fedbiomed/common/cli.py
def initialize(self):
    """Initializes parser classes and common parser for child classes.

    This parser classes will be added by child classes.
    """

    self._parser.add_argument(
        "-y",
        action="store_true"
    )

    for arg_parser in self._arg_parsers_classes:
        p = arg_parser(self._subparsers, self._parser)
        p.initialize()
        self._arg_parsers.update({arg_parser.__name__: p})

    self.initialize_certificate_parser()
initialize_certificate_parser
initialize_certificate_parser()

Common arguments

Source code in fedbiomed/common/cli.py
def initialize_certificate_parser(self):
    """Common arguments"""

    # Add certificate sub parser (sub-command)
    certificate_parser = self._subparsers.add_parser(
        "certificate",
        help="Command to manage certificates in node and researcher components. "
        "Please see 'certificate --help' for more information.",
        prog="fedbiomed [ node | researcher ] [--path [COMPONENT_DIRECTORY]] certificate",
    )

    def print_help(args):
        certificate_parser.print_help()

    certificate_parser.set_defaults(func=print_help)

    # Create sub parser under `certificate` command
    certificate_sub_parsers = certificate_parser.add_subparsers(
        description="Commands that can be used with the option `certificate`",
        title="Subcommands",
    )

    register_parser = certificate_sub_parsers.add_parser(
        "register",
        help="Register certificate of specified party. Please run 'fedbiomed' "
            "[COMPONENT SPECIFICATION] certificate register --help'",
    )  # command register

    list_parser = certificate_sub_parsers.add_parser(
        "list", help="Lists registered certificates"
    )  # command list
    delete_parser = certificate_sub_parsers.add_parser(
        "delete", help="Deletes specified certificate from database"
    )  # command delete

    # Command `certificate generate`
    generate = certificate_sub_parsers.add_parser(
        "generate",
        help="Generates certificate for given component/party if files don't exist yet. "
        "Uses an alternate directory if '--path DIRECTORY' is given."
        " If files already exist, overwrite existing certificate.\n"
        "Certificate are here refering to the public certificate and its associated private key "
        "(the latter should remain secret and not shared to other parties)."
    )

    # Command `certificate generate`
    prepare = certificate_sub_parsers.add_parser(
        "registration-instructions",
        help="Prepares certificate of current component to send other FL participant"
             " through trusted channel.",
    )

    register_parser.set_defaults(func=self._register_certificate)
    list_parser.set_defaults(func=self._list_certificates)
    delete_parser.set_defaults(func=self._delete_certificate)
    generate.set_defaults(func=self._generate_certificate)
    prepare.set_defaults(func=self._prepare_certificate_for_registration)

    # Add arguments
    register_parser.add_argument(
        "-pk",
        "--public-key",
        metavar="PUBLIC_KEY",
        type=str,
        nargs="?",
        required=True,
        help="Certificate/key that will be registered",
    )

    register_parser.add_argument(
        "-pi",
        "--party-id",
        metavar="PUBLIC_ID",
        type=str,
        nargs="?",
        required=True,
        help="ID of the party to which the certificate is to be registered (component ID).",
    )

    register_parser.add_argument(
        "--upsert",
        action="store_true",
        help="Updates if certificate of given party id is already existing.",
    )

    generate.add_argument(
        "--path",
        type=str,
        nargs="?",
        required=False,
        help="The path to the RESEARCHER|NODE component, in which certificate will be saved."
        " By default it will overwrite existing certificate.",
    )
initialize_magic_dev_environment_parsers
initialize_magic_dev_environment_parsers()

Initializes argument parser for the option to create development environment.

Source code in fedbiomed/common/cli.py
def initialize_magic_dev_environment_parsers(self) -> None:
    """Initializes argument parser for the option to create development environment."""
    magic = self._subparsers.add_parser(
        "certificate-dev-setup",
        description="Prepares development environment by registering certificates "
                    "of each component created in a single clone of Fed-BioMed. Parses "
                    "configuration files ends with '.ini' that are created in 'etc' "
                    "directory. This setup requires to have one 'researcher' and "
                    "at least 2 nodes.",
        help="Prepares development environment by registering certificates of each "
             "component created in a single clone of Fed-BioMed.",
    )
    magic.set_defaults(func=self._create_magic_dev_environment)
initialize_optional
initialize_optional()

Initializes optional subparser

Optional subparsers are not going to be visible for the CLI that are inherited from CommonCLI class as long as intialize_optional method is not executed.

Source code in fedbiomed/common/cli.py
def initialize_optional(self):
    """Initializes optional subparser

    Optional subparsers are not going to be visible for the CLI that are
    inherited from CommonCLI class as long as `intialize_optional` method
    is not executed.
    """

    self.configuration_parser.initialize()
    self.initialize_magic_dev_environment_parsers()
    self.initialize_version()
initialize_version
initialize_version()

Initializes argument parser for common options.

Source code in fedbiomed/common/cli.py
def initialize_version(self):
    """Initializes argument parser for common options."""
    self._parser.add_argument(
        "--version",
        "-v",
        action='version',
        version=str(__version__),
        help="Print software version",
    )
parse_args
parse_args(args_=None)

Parse arguments after adding the arguments

Attention

Please make sure this method is called after all necessary arguments are set
Source code in fedbiomed/common/cli.py
def parse_args(self, args_=None):
    """Parse arguments after adding the arguments

    !!! warning "Attention"
            Please make sure this method is called after all necessary arguments are set
    """
    args, unknown_args = self._parser.parse_known_args(args_)
    if hasattr(args, "func"):
        specs = get_method_spec(args.func)
        if specs:
            # If default function has 2 arguments
            if len(specs) > 1:
                return args.func(args, unknown_args)

            # Run parser_args to raise error for unrecognized arguments
            if unknown_args:
                args = self._parser.parse_args(args_)
            args.func(args)
        else:
            # Raise for unrecognized arguments
            if unknown_args:
                self._parser.parse_args(args_)
            args.func()
    else:
        self._parser.print_help()
success staticmethod
success(message)

Prints given message with success tag

Parameters:

Name Type Description Default
message str

Message to print as successful operation

required
Source code in fedbiomed/common/cli.py
@staticmethod
def success(message: str) -> None:
    """Prints given message with success tag

    Args:
        message: Message to print as successful operation
    """
    print(f"{GRN}Operation successful! {NC}")
    print(f"{BOLD}{message}{NC}")

ComponentDirectoryAction

ComponentDirectoryAction(*args, **kwargs)

Bases: ABC, Action

Action for the argument config

This action class gets the config file name and set config object before executing any command.

Source code in fedbiomed/common/cli.py
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

    # Sets config by default if option string for config is not present.
    # The default is defined by the argument parser.
    if (
        not set(self.option_strings).intersection(set(sys.argv)) and
        not set(["--help", "-h"]).intersection(set(sys.argv)) and
        len(sys.argv) > 2
    ):
        self._create_config(self.default)

    super().__init__(*args, **kwargs)

Functions

set_component abstractmethod
set_component(component_dir)

Implements configuration import

Parameters:

Name Type Description Default
component_dir str

Name of the config file for the component

required
Source code in fedbiomed/common/cli.py
@abstractmethod
def set_component(self, component_dir: str) -> None:
    """Implements configuration import

    Args:
        component_dir: Name of the config file for the component
    """

ComponentParser

ComponentParser(subparser, parser=None)

Bases: CLIArgumentParser

Instantiates configuration parser

Source code in fedbiomed/common/cli.py
def __init__(self, subparser: argparse.ArgumentParser, parser = None):

    self._subparser = subparser
    # Parser that is going to be add using subparser
    self._parser = None

    self._main_parser = parser

Functions

create
create(args)

CLI Handler for creating configuration file and assets for given component

Source code in fedbiomed/common/cli.py
def create(self, args):
    """CLI Handler for creating configuration file and assets for given component
    """
    if not args.path:
        if args.component.lower() == "researcher":
            component_path = os.path.join(os.getcwd(), DEFAULT_RESEARCHER_NAME)
        else:
            component_path = os.path.join(os.getcwd(), DEFAULT_NODE_NAME)
    else:
        component_path = args.path

    # Researcher specific case ----------------------------------------------------
    # This is a special case since researcher import
    if args.component is None:
        CommonCLI.error("Error: bad command line syntax")
    if args.component.lower() == "researcher":
        if DEFAULT_RESEARCHER_NAME in component_path and \
            os.path.isdir(component_path) and \
            not docker_special_case(component_path):
            if not args.exist_ok:
                CommonCLI.error(
                    f"Default component is already existing. In the directory {component_path} "
                    "please remove existing one to re-initiate"
                )
                sys.exit(1)
            else:
                CommonCLI.success(
                    "Component is already existing. Using existing component."
                )
                sys.exit(0)
        else:
            self._get_component_instance(component_path, args.component)
            return
    else:
        component = self._get_component_instance(component_path, args.component)
        # Overwrite force configuration file
        if component.is_component_existing(component_path):
            if not args.exist_ok:
                CommonCLI.error(
                    f"Component is already existing in the directory `{component_path}`. To ignore "
                   "this error please execute component creation using `--exist-ok`"
            )
            else:
                CommonCLI.success(
                    "Component is already exsiting. Using existing component."
                )
                return

        component.initiate(component_path)

    CommonCLI.success(f"Component has been initialized in {component_path}")
initialize
initialize()

Initializes argument parser for creating configuration file.

Source code in fedbiomed/common/cli.py
def initialize(self):
    """Initializes argument parser for creating configuration file."""

    self._parser = self._subparser.add_parser(
        "component",
        help="The helper for generating or updating component configuration files, see `configuration -h`"
        " for more details",
    )

    self._parser.set_defaults(func=self.default)

    # Common parser to register common arguments for create and refresh
    common_parser = argparse.ArgumentParser(add_help=False)
    common_parser.add_argument(
        "-p",
        "--path",
        action=UniqueStore,
        metavar="COMPONENT_PATH",
        type=str,
        nargs="?",
        required=False,
        help="Path to specificy where Fed-BioMed component will be intialized.",
    )

    common_parser.add_argument(
        "-c",
        "--component",
        metavar="COMPONENT_TYPE[ NODE|RESEARCHER ]",
        type=str,
        nargs="?",
        required=True,
        help="Component type NODE or RESEARCHER",
    )

    # Create sub parser under `configuration` command
    component_sub_parsers = self._parser.add_subparsers()

    create = component_sub_parsers.add_parser(
        "create",
        parents=[common_parser],
        help="Creates component folder for the specified component if it does not exist. "
        "If the component folder exists, leave it unchanged",
    )

    create.add_argument(
        "-eo",
        "--exist-ok",
        action="store_true",
        help="Creates configuration only if there isn't an existing one",
    )

    create.set_defaults(func=self.create)

UniqueStore

Bases: Action

Argparse action for avoiding having several time the same optional argument

Functions