python-threatexchange/threatexchange/cli/command_base.py (60 lines of code) (raw):
##!/usr/bin/env python
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
"""
Common helpers and libraries for the all-in-one command.
Strongly consider moving classes and functions out of this file if it starts
to fill up.
"""
import argparse
import typing as t
import sys
from threatexchange import common
from threatexchange.cli.cli_config import CLISettings
from threatexchange.cli.exceptions import CommandError
class Command:
"""
Simple wrapper around setting up commands for an argparse-based cli.
"""
@classmethod
def add_command_to_subparser(
cls, settings: CLISettings, subparsers
) -> argparse.ArgumentParser:
"""
Shortcut for adding the command to the parser.
Propbably don't override.
"""
command_ap = subparsers.add_parser(
cls.get_name(),
description=cls.get_description(),
help=cls.get_help(),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
command_ap.set_defaults(command_cls=cls)
cls.init_argparse(settings, command_ap)
return command_ap
@classmethod
def init_argparse(
cls, settings: CLISettings, argparse: argparse.ArgumentParser
) -> None:
"""
Program the command subparser for __init__
Your argument names should match the argument names in __init__.
Be careful of collisions with the top level arg names from main.py
"""
pass
@classmethod
def get_name(cls) -> str:
"""The display name of the command"""
return common.class_name_to_human_name(cls.__name__, "Command").replace(
"_", "-"
)
@classmethod
def get_description(self) -> str:
"""The long help of the command"""
return self.__doc__ or ""
@classmethod
def get_help(cls) -> str:
"""The short help of the command"""
line = cls.get_description().strip().partition("\n")[0]
# Good luck debugging this! Slightly reformat short description
# (toplevel --help)
if line and line[0].isupper():
first_word, sp, rem = line.partition(" ")
line = f"{first_word.lower()}{sp}{rem}"
if line and line[-1] == ".":
line = line[:-1]
return line
@staticmethod
def stderr(*args, **kwargs) -> None:
"""Convenience accessor to stderr"""
print(*args, file=sys.stderr, **kwargs)
def execute(self, settings: CLISettings) -> None:
raise NotImplementedError
class CommandWithSubcommands(Command):
_SUBCOMMANDS: t.List[t.Type[Command]] = []
@classmethod
def add_command_to_subparser(
cls, settings: CLISettings, subparsers
) -> argparse.ArgumentParser:
command_ap = super().add_command_to_subparser(settings, subparsers)
sub_subparsers = command_ap.add_subparsers()
for command in cls._SUBCOMMANDS:
command.add_command_to_subparser(settings, sub_subparsers)
return command_ap
def execute(self, settings: CLISettings) -> None:
raise CommandError("subcommand is required", 2)