# Buggy code
def build_help():
    parser = argparse.ArgumentParser(
        description="Command line tool to handle configuration on devices using NAPALM."
        "The script will print the diff on the screen",
        epilog="Automate all the things!!!",
    )
    parser.add_argument(
        dest="hostname",
        action="store",
        help="Host where you want to deploy the configuration.",
    )
    parser.add_argument(
        "--user",
        "-u",
        dest="user",
        action="store",
        default=getpass.getuser(),
        help="User for authenticating to the host. Default: user running the script.",
    )
    parser.add_argument(
        "--password",
        "-p",
        dest="password",
        action="store",
        help="Password for authenticating to the host."
        "If you do not provide a password in the CLI you will be prompted.",
    )
    parser.add_argument(
        "--vendor",
        "-v",
        dest="vendor",
        action="store",
        required=True,
        help="Host Operating System.",
    )
    parser.add_argument(
        "--optional_args",
        "-o",
        dest="optional_args",
        action="store",
        help="String with comma separated key=value pairs passed via optional_args to the driver.",
    )
    parser.add_argument(
        "--debug",
        dest="debug",
        action="store_true",
        help="Enables debug mode; more verbosity.",
    )
    subparser = parser.add_subparsers(title="actions")

    config = subparser.add_parser("configure", help="Perform a configuration operation")
    config.set_defaults(which="config")
    config.add_argument(
        dest="config_file",
        action="store",
        help="File containing the configuration you want to deploy.",
    )
    config.add_argument(
        "--strategy",
        "-s",
        dest="strategy",
        action="store",
        choices=["replace", "merge"],
        default="replace",
        help="Strategy to use to deploy configuration. Default: replace.",
    )
    config.add_argument(
        "--dry-run",
        "-d",
        dest="dry_run",
        action="store_true",
        default=None,
        help="Only returns diff, it does not deploy the configuration.",
    )

    call = subparser.add_parser("call", help="Call a napalm method")
    call.set_defaults(which="call")
    call.add_argument(dest="method", action="store", help="Run this method")
    call.add_argument(
        "--method-kwargs",
        "-k",
        dest="method_kwargs",
        action="store",
        help='kwargs to pass to the method. For example: "destination=1.1.1.1,protocol=bgp"',
    )

    validate = subparser.add_parser("validate", help="Validate configuration/state")
    validate.set_defaults(which="validate")
    validate.add_argument(
        dest="validation_file",
        action="store",
        help="Validation file containing resources derised states",
    )
    args = parser.parse_args()

    if args.password is None:
        password = getpass.getpass("Enter password: ")
        setattr(args, "password", password)

    return args


# Traceback (AttributeError)
user@host:/opt/netbox$ napalm --user username--password xxxxx --vendor nxos_ssh --debug 10.28.210.5
2019-02-28 15:07:06,834 - napalm - DEBUG - Starting napalm's debugging tool
2019-02-28 15:07:06,834 - napalm - DEBUG - Gathering napalm packages
2019-02-28 15:07:06,835 - napalm - DEBUG - napalm==2.4.0
2019-02-28 15:07:06,835 - napalm - DEBUG - get_network_driver - Calling with args: ('nxos_ssh',), {}
2019-02-28 15:07:06,971 - napalm - DEBUG - get_network_driver - Successful
2019-02-28 15:07:06,971 - napalm - DEBUG - __init__ - Calling with args: (<class 'napalm.nxos_ssh.nxos_ssh.NXOSSSHDriver'>, '10.28.210.5', 'magna'), {'password': '*******', 'timeout': 60, 'optional_args': {}}
2019-02-28 15:07:06,972 - napalm - DEBUG - __init__ - Successful
2019-02-28 15:07:06,972 - napalm - DEBUG - pre_connection_tests - Calling with args: (<napalm.nxos_ssh.nxos_ssh.NXOSSSHDriver object at 0x7f4ff9fe9ef0>,), {}
2019-02-28 15:07:06,972 - napalm - DEBUG - open - Calling with args: (<napalm.nxos_ssh.nxos_ssh.NXOSSSHDriver object at 0x7f4ff9fe9ef0>,), {}
2019-02-28 15:07:11,190 - napalm - DEBUG - open - Successful
2019-02-28 15:07:11,191 - napalm - DEBUG - connection_tests - Calling with args: (<napalm.nxos_ssh.nxos_ssh.NXOSSSHDriver object at 0x7f4ff9fe9ef0>,), {}
2019-02-28 15:07:11,191 - napalm - DEBUG - get_facts - Calling with args: (<napalm.nxos_ssh.nxos_ssh.NXOSSSHDriver object at 0x7f4ff9fe9ef0>,), {}
2019-02-28 15:07:14,198 - napalm - DEBUG - Gathered facts:
{
"uptime": 7532976,
"vendor": "Cisco",
"os_version": "7.0(3)I2(3)",
"serial_number": "FOCXXX080WJ",
"model": "Nexus 3172T Chassis",
"hostname": "XXXXXXX",
"fqdn": "xxx.xxx.xxxl",
"interface_list": [
"mgmt0",
"Ethernet1/1",
"Ethernet1/2",
"Ethernet1/3",
"Ethernet1/4",
"Ethernet1/5",
"Ethernet1/6",
"Ethernet1/7",
"Ethernet1/8",
"Ethernet1/9",
"Ethernet1/10",
"Ethernet1/11",
"Ethernet1/12",
"Ethernet1/13",
"Ethernet1/14",
"Ethernet1/15",
"Ethernet1/16",
"Ethernet1/17",
"Ethernet1/18",
"Ethernet1/19",
"Ethernet1/20",
"Ethernet1/21",
"Ethernet1/22",
"Ethernet1/23",
"Ethernet1/24",
"Ethernet1/25",
"Ethernet1/26",
"Ethernet1/27",
"Ethernet1/28",
"Ethernet1/29",
"Ethernet1/30",
"Ethernet1/31",
"Ethernet1/32",
"Ethernet1/33",
"Ethernet1/34",
"Ethernet1/35",
"Ethernet1/36",
"Ethernet1/37",
"Ethernet1/38",
"Ethernet1/39",
"Ethernet1/40",
"Ethernet1/41",
"Ethernet1/42",
"Ethernet1/43",
"Ethernet1/44",
"Ethernet1/45",
"Ethernet1/46",
"Ethernet1/47",
"Ethernet1/48",
"Ethernet1/49",
"Ethernet1/50",
"Ethernet1/51/1",
"Ethernet1/51/2",
"Ethernet1/51/3",
"Ethernet1/51/4",
"Ethernet1/52/1",
"Ethernet1/52/2",
"Ethernet1/52/3",
"Ethernet1/52/4",
"Ethernet1/53",
"Ethernet1/54",
"Port-channel1",
"Port-channel100",
"Port-channel101",
"Port-channel102",
"Port-channel103",
"Vlan1",
"Vlan888",
"Vlan889",
"Vlan890",
"Vlan891"
]
}
{
"uptime": 7532976,
"vendor": "Cisco",
"os_version": "7.0(3)I2(3)",
"serial_number": "FOC202080WJ",
"model": "Nexus 3172T Chassis",
"hostname": "SAISW_NEX005CIS",
"fqdn": "SAISW_NEX005CIS.magna.global",
"interface_list": [
"mgmt0",
"Ethernet1/1",
"Ethernet1/2",
"Ethernet1/3",
"Ethernet1/4",
"Ethernet1/5",
"Ethernet1/6",
"Ethernet1/7",
"Ethernet1/8",
"Ethernet1/9",
"Ethernet1/10",
"Ethernet1/11",
"Ethernet1/12",
"Ethernet1/13",
"Ethernet1/14",
"Ethernet1/15",
"Ethernet1/16",
"Ethernet1/17",
"Ethernet1/18",
"Ethernet1/19",
"Ethernet1/20",
"Ethernet1/21",
"Ethernet1/22",
"Ethernet1/23",
"Ethernet1/24",
"Ethernet1/25",
"Ethernet1/26",
"Ethernet1/27",
"Ethernet1/28",
"Ethernet1/29",
"Ethernet1/30",
"Ethernet1/31",
"Ethernet1/32",
"Ethernet1/33",
"Ethernet1/34",
"Ethernet1/35",
"Ethernet1/36",
"Ethernet1/37",
"Ethernet1/38",
"Ethernet1/39",
"Ethernet1/40",
"Ethernet1/41",
"Ethernet1/42",
"Ethernet1/43",
"Ethernet1/44",
"Ethernet1/45",
"Ethernet1/46",
"Ethernet1/47",
"Ethernet1/48",
"Ethernet1/49",
"Ethernet1/50",
"Ethernet1/51/1",
"Ethernet1/51/2",
"Ethernet1/51/3",
"Ethernet1/51/4",
"Ethernet1/52/1",
"Ethernet1/52/2",
"Ethernet1/52/3",
"Ethernet1/52/4",
"Ethernet1/53",
"Ethernet1/54",
"Port-channel1",
"Port-channel100",
"Port-channel101",
"Port-channel102",
"Port-channel103",
"Vlan1",
"Vlan888",
"Vlan889",
"Vlan890",
"Vlan891"
]
}
2019-02-28 15:07:14,198 - napalm - DEBUG - get_facts - Successful
Traceback (most recent call last):
File "/usr/local/bin/napalm", line 11, in <module>
sys.exit(main())
File "/usr/local/lib/python3.6/dist-packages/napalm/base/clitools/cl_napalm.py", line 309, in main
run_tests(args)
File "/usr/local/lib/python3.6/dist-packages/napalm/base/clitools/cl_napalm.py", line 290, in run_tests
if args.which == "call":
AttributeError: 'Namespace' object has no attribute 'which'
user@host:/opt/netbox$

# Fixed code
def build_help():
    parser = argparse.ArgumentParser(
        description="Command line tool to handle configuration on devices using NAPALM."
        "The script will print the diff on the screen",
        epilog="Automate all the things!!!",
    )
    parser.add_argument(
        dest="hostname",
        action="store",
        help="Host where you want to deploy the configuration.",
    )
    parser.add_argument(
        "--user",
        "-u",
        dest="user",
        action="store",
        default=getpass.getuser(),
        help="User for authenticating to the host. Default: user running the script.",
    )
    parser.add_argument(
        "--password",
        "-p",
        dest="password",
        action="store",
        help="Password for authenticating to the host."
        "If you do not provide a password in the CLI you will be prompted.",
    )
    parser.add_argument(
        "--vendor",
        "-v",
        dest="vendor",
        action="store",
        required=True,
        help="Host Operating System.",
    )
    parser.add_argument(
        "--optional_args",
        "-o",
        dest="optional_args",
        action="store",
        help="String with comma separated key=value pairs passed via optional_args to the driver.",
    )
    parser.add_argument(
        "--debug",
        dest="debug",
        action="store_true",
        help="Enables debug mode; more verbosity.",
    )
    subparser = parser.add_subparsers(title="actions")

    config = subparser.add_parser("configure", help="Perform a configuration operation")
    config.set_defaults(which="config")
    config.add_argument(
        dest="config_file",
        action="store",
        help="File containing the configuration you want to deploy.",
    )
    config.add_argument(
        "--strategy",
        "-s",
        dest="strategy",
        action="store",
        choices=["replace", "merge"],
        default="replace",
        help="Strategy to use to deploy configuration. Default: replace.",
    )
    config.add_argument(
        "--dry-run",
        "-d",
        dest="dry_run",
        action="store_true",
        default=None,
        help="Only returns diff, it does not deploy the configuration.",
    )

    call = subparser.add_parser("call", help="Call a napalm method")
    call.set_defaults(which="call")
    call.add_argument(dest="method", action="store", help="Run this method")
    call.add_argument(
        "--method-kwargs",
        "-k",
        dest="method_kwargs",
        action="store",
        help='kwargs to pass to the method. For example: "destination=1.1.1.1,protocol=bgp"',
    )

    validate = subparser.add_parser("validate", help="Validate configuration/state")
    validate.set_defaults(which="validate")
    validate.add_argument(
        dest="validation_file",
        action="store",
        help="Validation file containing resources derised states",
    )
    args = parser.parse_args()

    if not hasattr(args, "which"):
        args.which = None

    if args.password is None:
        password = getpass.getpass("Enter password: ")
        setattr(args, "password", password)

    return args