Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to develop Python Command Line tool with Plumbum

2025-03-30 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

How to use Plumbum to develop Python command line tools, I believe that many inexperienced people do not know what to do. Therefore, this paper summarizes the causes and solutions of the problem. Through this article, I hope you can solve this problem.

This paper mainly introduces how to use the Plumbum CLI toolkit to develop Python command line applications. This is a very Pythonic, easy to use, powerful toolkit, which is worthy of being mastered and used by Python programmers.

Another aspect of easy execution is that it is easy to write CLI programs. Python scripts typically use optparse or * argparse and its derivatives to develop command-line tools, but all of these are limited expressive and very unintuitive (not even Pythonic). Plumbum's CLI toolkit provides a programmatic way to build command-line applications without creating a parser object and then populating a series of "options". The CLI toolkit uses introspection mechanisms to escape these primitives into Pythonic structures.

In general, a Plumbum CLI application is a class that inherits from plumbum.cli.Application. These classes define a main () method and optionally expose methods and properties as command-line options. These options may require arguments, and any remaining positional parameters are assigned to the main method based on the declaration of the main function. A simple CLI application looks like this:

From plumbum import cli class MyApp (cli.Application): verbose = cli.Flag (["v", "verbose"], help = "If given, I will be very talkative") def main (self, filename): print ("I will now read {0}" .format (filename)) if self.verbose: print ("Yadda" * 200) if _ name__ = = "_ main__": MyApp.run ()

You can run the program:

$python example.py fooI will now read foo $python example.py-- helpexample.py v1.0 Usage: example.py [SWITCHES] filenameMeta-switches:-h,-help Prints this help message and quits-- version Prints the program's version and quits Switches:-v,-- verbose If given, I will be very talkative

So far, you have only seen very basic use. We now begin to explore the library.

New version 1.6.1: you can run the application MyApp () directly without arguments or calls to .main ().

Application program

The Application class is the "container" of your application, which consists of a main () method you need to implement and any number of option functions and properties that you need to expose. The entry to your application is the class method run, which instantiates your class, parses parameters, calls all option functions, and then calls the main () function with the given positional arguments. To run your application from the command line, all you need to do is:

If _ _ name__ = "_ _ main__": MyApp.run ()

In addition to run () and main (), the Application class exposes two built-in option functions: help () and version (), which are used to display versions of the help and program, respectively. By default,-- hep and-h call help (),-- version and-v call version (), and these functions display the appropriate information and exit (no other options are processed).

You can customize the information displayed by help () and version () by defining class properties, such as PROGNAME, VERSION, and DESCRIPTION. For example:

Class MyApp (cli.Application): PROGNAME = "Foobar" VERSION = "7.3" color

New version 1.6

The library also supports terminal character color control. You can directly change PROGNAME, VERSION and DESCRIPTION into colored strings. If you set the color for PROGNAME, you will get a custom program name and color. The color of the method string can take effect by setting COLOR_USAGE, and the color of different option groups can take effect by setting the COLOR_GROUPS dictionary.

Examples are as follows:

Class MyApp (cli.Application): PROGNAME = colors.green VERSION = colors.blue | "1.0.2" COLOR_GROUPS = {"Meta-switches": colors.bold & colors.yellow} opts = cli.Flag ("--ops", help=colors.magenta | "This is help") SimpleColorCLI.py 1.0.2 Usage: SimpleColorCLI.py [SWITCHES] Meta-switches-h -help Prints this help message and quits-- help-all Print help messages of all subcommands and quit-v,-- version Prints the program's version and quits Switches-- ops This is help option function

The switch decorator is the "soul" of the CLI SDK, which exposes your CLI application's methods as CLI command-line options, which are run and called from the command line. Let's test the following applications:

Class MyApp (cli.Application): _ allow_root = False # provide a default @ cli.switch ("--log-to-file", str) def log_to_file (self, filename): "" Sets the file into which logs will be emitted "logger.addHandler (FileHandle (filename)) @ cli.switch (["-r ","-root "]) def allow_as_root (self):"If given Allow running as root "" self._allow_root = True def main (self): if os.geteuid () = = 0 and not self._allow_root: raise ValueError ("cannot run as root")

When the program is running, the option function is called with the corresponding parameters. For example, $. / myapp.py-log-to-file=/tmp/log will be converted into a call to app.log_to_file ("/ tmp/log"). After the option function is executed, control of the program is passed to the main method.

Be careful

The document string and parameter name of the method will be used to render the help message, keeping your code DRY as much as possible.

Autoswitch can infer the name of the option from the function name, as an example:

@ cli.autoswitch (str) def log_to_file (self, filename): pass

This binds the option function to-- log-to-file.

Option parameter

As shown in the example above, the option function may have no arguments (excluding self) or may have one argument. If the option function accepts a parameter, it must indicate the type of the parameter. If you don't need special validation, just pass str, otherwise, you may pass any type (or any type that can actually be called) that will take a string and convert it to a meaningful object. If the conversion is not feasible, a TypeError or ValueError exception is thrown.

For example:

Class MyApp (cli.Application): _ port = 8080 @ cli.switch (["- p"], int) def server_port (self, port): self._port = port def main (self): print (self._port) $. / example.py-p 1717 $. / example.py-p fooArgument of-p expected to be, not 'foo': ValueError ("invalid literal for int () with base 10:' foo'",)

The toolkit contains two additional "types" (or validators): Range and Set. Range specifies a minimum value and a * value, limiting an integer to that range (closed interval). Set specifies a set of allowed values and expects parameters to match one of these values. Examples are as follows:

Class MyApp (cli.Application): _ port = 8080 _ mode = "TCP" @ cli.switch ("- p", cli.Range (1024d65535)) def server_port (self, port): self._port = port @ cli.switch ("- m", cli.Set ("TCP", "UDP", case_sensitive = False)) def server_mode (self Mode): self._mode = mode def main (self): print (self._port, self._mode) $. / example.py-p 17Argument of-p expected to be [1024. 65535], not'17 percent: ValueError ('Not in range [1024.. 65535],) $. / example.py-m fooArgument of-m expected to be Set (' udp', 'tcp'), not' foo': ValueError ("Expected one of ['UDP']) 'TCP'] ",)

Note that there are other useful validators in the toolkit: ExistingFile (to ensure that the given parameter is an existing file), ExistingDirectory (to ensure that the given parameter is an existing directory), and NonexistentPath (to ensure that the given parameter is a non-existent path). All of these convert parameters to local paths.

Repeatable option

Many times, you need to specify an option multiple times on the same command line. For example, in gcc, you might use the-I parameter to introduce multiple directories. By default, options can only be specified once, unless you pass the list = True parameter to the switch decorator.

Class MyApp (cli.Application): _ dirs = [] @ cli.switch ("- I", str, list = True) def include_dirs (self, dirs): self._dirs = dirs def main (self): print (self._dirs) $. / example.py-I/foo/bar-I/usr/include ['/ foo/bar','/ usr/include']

Note that the option function is called only once and its parameters will become a list.

Mandatory option

If an option is required, you can pass mandatory = True to the switch decorator to implement it. In this way, the program cannot be run if the user does not specify this option.

Option dependence

In many cases, the emergence of one option depends on another, for example, if the-y option is not given, then the-x option cannot be given. This restriction can be achieved by passing the switch decorator a requires parameter, which is a list of option names on which the current option depends. If you do not specify other options on which an option depends, the user cannot run the program.

Class MyApp (cli.Application): @ cli.switch ("--log-to-file", str) def log_to_file (self, filename): logger.addHandler (logging.FileHandler (filename)) @ cli.switch ("--verbose", requires = ["--log-to-file"]) def verbose (self): logger.setLevel (logging.DEBUG) $. / example-- verboseGiven-- verbose, the following are missing ['log-to-file']

The order in which the warning option function is called is consistent with the order of the options specified on the command line. It is not supported to calculate the topological order of option function calls while the program is running, but it will be improved in the future.

Options are mutually exclusive

Some options depend on other options, but some options are mutually exclusive with others. For example, the co-existence of-- verbose and-- terse is unreasonable. To do this, you can specify an excludes list for the switch decorator.

Class MyApp (cli.Application): @ cli.switch ("--log-to-file", str) def log_to_file (self, filename): logger.addHandler (logging.FileHandler (filename)) @ cli.switch ("--verbose", requires = ["--log-to-file"], excludes = ["--terse"] def verbose (self): logger.setLevel (logging.DEBUG) @ cli.switch ("--terse") Requires = ["--log-to-file"], excludes = ["--verbose"]) def terse (self): logger.setLevel (logging.WARNING) $. / example-- log-to-file=log.txt-- verbose-- terseGiven-- verbose, the following are invalid ['--terse'] option grouping

If you want to combine some options in the help message, you can specify group = "Group Name" to the switch decorator, and the Group Name can be any string. When help information is displayed, all options that belong to the same group are aggregated. Note that grouping does not affect the processing of options, but can enhance the readability of help information.

Option Properti

In many cases, you only need to store the parameters of the option in the properties of the class, or set a flag when a property is given. To do this, the toolkit provides SwitchAttr, a data descriptor that stores parameters. The toolkit also provides two additional SwitchAttr:Flag (default values are given to an option if given) and CountOf (the number of times an option appears).

Class MyApp (cli.Application): log_file = cli.SwitchAttr ("- log-file", str, default = None) enable_logging = cli.Flag ("--no-log", default = True) verbosity_level = cli.CountOf ("- v") def main (self): print (self.log_file, self.enable_logging) Self.verbosity_level) $. / example.py-v-- log-file=log.txt-v-- no-log-vvvlog.txt False 5 environment variable

New version 1.6

You can use the envname parameter to use the environment variable as input to SwitchAttr. Examples are as follows:

Class MyApp (cli.Application): log_file = cli.SwitchAttr ("--log-file", str, envname= "MY_LOG_FILE") def main (self): print (self.log_file) $MY_LOG_FILE=this.log. / example.pythis.log

Giving a variable value on the command line overrides the value of the same environment variable.

Main

Once all the command-line arguments are processed, the main () method takes control of the program and can have any number of positional parameters, such as / foo and / bar are positional parameters in cp-r / foo / bar. The number of positional parameters accepted by the program depends on the declaration of the main () function: if the main method has five parameters and two have default values, then the user needs to provide at least three positional parameters and the total number cannot be more than five. If the declaration of the main method uses a variable parameter (* args), there is no limit to the number of positional parameters.

Class MyApp (cli.Application): def main (self, src, dst, mode = "normal"): print (src, dst, mode) $. / example.py / foo / bar/foo / bar normal$. / example.py / foo / bar spam/foo / bar spam$. / example.py / fooExpected at least 2 positional arguments, got ['/ foo'] $. / example.py / foo / bar spam baconExpected at most 3 positional arguments, got ['/ foo','/ bar', 'spam',' bacon']

Note that the declaration of this method is also used to generate help information, such as:

Usage: [SWITCHES] src dst [mode='normal']

Use variable parameters:

Class MyApp (cli.Application): def main (self, src, dst, * eggs): print (src, dst, eggs) $. / example.py a b c da b ('clocked,' d') $. / example.py-- helpUsage: [SWITCHES] src dst eggs...Meta-switches:-h,-- help Prints this help message and quits-v,-- version Prints the program's version and quits location verification

New version 1.6

You can use the validator provided by the cli.positional decorator to verify the location parameters. You just need to pass the validator that matches the main function in the decorator. For example:

Class MyApp (cli.Application): @ cli.positional (cli.ExistingFile, cli.NonexistentPath) def main (self, infile, * outfiles): "infile is a path, outfiles are a list of paths, proper errors are given"

If your program runs only in Python 3, you can use annotations to specify the validator, for example:

Class MyApp (cli.Application): def main (self, infile: cli.ExistingFile, * outfiles: cli.NonexistentPath): "Identical to above MyApp"

If the positional decorator exists, the comments will be ignored.

Subcommand

New version 1.1

With the expansion of CLI applications, there are more and more functions, and a common practice is to divide its logic into multiple sub-applications (or subcommands). A typical example is a version control system, such as git,git is the root command, under which subcommands such as commit or push are nested. Git even supports command aliases, which run users to create their own subcommands. It's easy for Plumbum to write programs like this.

Before we start to understand the code, let's emphasize two things:

In Plumbum, each subcommand is a complete cli.Application application, and you can execute it individually or separate from the so-called root command. When the application executes alone, its parent property is None, and when run as a child command, its parent attribute points to the parent application. Similarly, when the parent application executes with child commands, its embedded commands are set to embedded applications.

Each subcommand is only responsible for its own option parameters (until the next subcommand). This allows the application to process its own options and location parameters before the embedded application calls. For example, git-- foo=bar spam push origin-- tags: the root application git is responsible for the options-- foo and the location option spam, and the embedded application push is responsible for the parameters after it. In theory, you can nest multiple sub-applications into another application, but in practice, there is usually only one level of nesting.

This is an example of geet that mimics a version control system. We have a root application Geet, which has two subcommands GeetCommit and GeetPush: these two subcommands attach it to the root application through the subcommand decorator.

Class Geet (cli.Application): "The l33t version control" VERSION = "1.7.2" def main (self * args): if args: print ("Unknown command {0roomr}" .format (args [0])) return 1 # error exit code if not self.nested_command: # will be ``None`` if no sub-command follows print ("No command given") return 1 # error exit code @ Geet.subcommand ("commit") # attach 'geet commit'class GeetCommit (cli.Application): "creates a new commit in the current branch" auto_add = cli.Flag ("- a") Help = "automatically add changed files") message = cli.SwitchAttr ("- m", str, mandatory = True, help = "sets the commit message") def main (self): print ("doing the commit...") @ Geet.subcommand ("push") # attach 'geet push'class GeetPush (cli.Application): "" pushes the current local branch to the remote one "def main (self, remote) Branch = None): print ("doing the push...") If _ _ name__ = "_ _ main__": Geet.run ()

Be careful

Since GeetCommit is also a cli.Application, you can call GeetCommit.run () directly (which is reasonable in the context of the application).

You can also use the subcommand method without a decorator to attach a subcommand: Geet.subcommand ("push", GeetPush)

The following is an example of running the application:

$python geet.py-- helpgeet v1.7.2The l33t version control Usage: geet.py [SWITCHES] [SUBCOMMAND [SWITCHES]] args...Meta-switches:-h,-- help Prints this help message and quits-v,-- version Prints the program's version and quits Subcommands: commit creates a new commit in the current branch See 'geet commit-- help' for more info push pushes the current local branch to the remote one See 'geet push-- help' for more info $python geet.py commit-- helpgeet commit v1.7.2creates a new commit in the current branch Usage: geet commit [SWITCHES] Meta-switches:-h,-- help Prints this help message and quits-v,-- version Prints the program's version and quits Switches:-an automatically add changed files-m VALUE:str sets the commit message Required $python geet.py commit-m "foo" doing the commit... Configure the parser

Another common function of an application is the configuration file parser, which parses the background INI configuration file: Config (or ConfigINI). Examples of use:

From plumbum import cli with cli.Config ('~ / .myapp_rc') as conf: one = conf.get ('one',' 1') two = conf.get ('two',' 2')

If the configuration file does not exist, a configuration file will be created with the current key and the default value, the default value will be obtained when the .get method is called, and the file will be created when the context manager exists. If the configuration file exists, it will be read without any changes. You can also use the [] syntax to force a value or get a ValueError when the variable does not exist. If you want to avoid the context manager, you can also use .read and .write.

The ini parser uses the [DEFAULT] segment by default, just like Python's ConfigParser. If you want to use a different segment, just pass it in key. Separate the paragraph from the title. For example, conf ['section.item'] places item under [section]. All entries stored in ConfigINI are converted to str,str and are returned frequently.

Terminal utility program

There are several terminal utilities in plumbum.cli.terminal to help create terminal applications.

Get_terminal_size (default= (800.25)) allows cross-platform access to the terminal screen size, and the return value is a tuple (width, height). There are also several ways to ask the user for input, such as readline, ask, choose, and prompt are all available.

Progress (iterator) allows you to quickly create a progress bar from an iterator. Simply packaging a slow iterator and iterating will result in a nice text progress bar based on the width of the user's screen, with the remaining time displayed. If you want to create a progress bar for the fast iterator and include the code in the loop, use Progress.wrap or Progress.range. For example:

For i in Progress.range (10): time.sleep (1)

If you have other output in the terminal, but still need a progress bar, pass the has_output=True parameter to prevent the progress bar from erasing the historical output.

A command line plotter (Image) is provided in plumbum.cli.image. It can draw an image similar to PIL:

Image () show_pil (im)

The Image constructor accepts an optional size parameter (if None, the default is the current terminal size) and a character scale, which is a measure of the height and width of the current character, with a default value of 2.45. If set to None,ratio, the image will be ignored and the image will no longer be restricted to proportional scaling. To draw an image directly, show requires a file name and a pair of parameters. The show_pil and show_pil_double methods accept a PIL-like object directly. To draw an image from the command line, the module can be run directly: python-m plumbum.cli.image myimage.png.

After reading the above, have you mastered how to develop Python command line tools with Plumbum? If you want to learn more skills or want to know more about it, you are welcome to follow the industry information channel, thank you for reading!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 222

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report