Build AWESOME CLIs With Click in Python

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
this is what we're going to build today it's a CLI a command line interface to buil in Python using the click package this is a simple note taking tool that writes reads updates and deletes notes this is going to be an in-depth video that will teach you everything you need to know to build a CLI yourself the code I'll work on today is in the examples get repository the link is in the description there are some things that you get for free out of the box with click that few people know about I'll talk more about that later I'm going to start with the story when I was a student at the University I lived in student housing with 11 other students we had a shared fridge with beers and also some non-alcoholic drinks we needed a way to keep track of who drank what so we could split the cost fairly being a computer science student I of course immediately started building software to do that in modula 2 this is similar to Pascal it's a language that I was taught at University I had an old XT machine Ling around with a green monochrome monitor I built a whole gooey system from scratch that was fully text character based no Graphics just asky it had very slow animated Windows you could navigate through the guy using your keyboard only no mouse to get a drink you simply selected your name from a list then wait until another window opens with an animation and then you could select which drink you wanted there were also a few other options like viewing your past consumptions next to that gooey I also made a quick CLI mostly just for me for testing the system the CLI was a really simple command you write the command you pass your name and the drink that you got from the fridge as an argument and then it would just register that now I assumed everybody just wanted to use the guey I mean who doesn't like painfully slow animated Windows drawn with asky characters right well as it turned out everybody just preferred the C because it was just was simple to use so I added a few more options to the CLI to also print your past consumptions and your balance and I basically threw out all of the guey code that experience taught me how powerful and simple clis can be and there are a ton of other reasons why CIS are cool you can make them part of aut scripts to automate repetitive tasks you can access them over remote connections and they can be more accommodating for visually impaired users besides that you as a software developer will probably already use CLI tools and most of them follow the same rules and format if you know the basic building blocks that I'll teach you today you'll be able to build great CIS very quickly and using them yourself is also going to be easier I'm going to start by creating the project for our not taking tool and let's call this notes I know it's not a very creative name but if you can think of a better name post a comment below with your name suggestion and I might update the code example in kit when creating projects like this you want to virtualize the environment manage dependencies and isolation from the other projects that you working on I normally use poetry for this so like I said let's call this project notes version 0.1.0 that's fine for me description this is an awesome not taking tool then let's select the dependencies we're only going to need one dependency in this example that's the library click that's Now by project tomile generated with all the dependencies and metadata there's one more thing I need to do which is add an entry point to our CLI and there we go I'll talk more about this later before we start let's run po shell to activate the virtual environment for our project and then we run po reinstall to install the dependencies now we're all set up let's create the CLI this file is going to be our main file the file is going to connect everything needed for our C tool so let's create a simple function CLI inside this file this function CLI is also the entry point that I set in the P project terminal file that's what's defined here so if we run this command in the terminal then from this python file nodes. pii we're going to run this function before I get into the commands let's see if the entry point works as expected this command installs our package locally but it's also hot reloads and updates if changes are made like adding another function for example as you can see our entry point works as expected however it doesn't do anything except print that it's working so let's now start creating our CLI to using click commands are the basic building block of command line interfaces in Click a command is a primary instruction or directive that's given to a CI tool it's the first keyword that you enter after the tool name that tells the program what action to perform for example in a CLI tool like git git itself is the tool clone commit and push are examples of commands that direct the tool to perform specific top level tasks so let's create some basic crud commands for our notes CLI and I usually put these in a separate file let's call that C.P let's begin with the command to create a note so first you need to define the function that's going to be run when that command is called in order to make this a CLI command you can use the click decorator so for that we of course first have to import click and then we write the click command decorator above function so this decorator creates a command instance from the command class based on our function here's an example of the actual code in the click Library the interesting part here is that the command class which we're in at the moment has a call D method implemented this means that the create function that I just wrote In My Own python code is going to be a callable instance with this callable instance with some help by some other click decorators you can provide additional metadata like arguments and options arguments are basic parameters that are passed to commands they typically mandatory unless specified otherwise and they must appear in a specific order after the command name here's an example of the docker CLI my image is passed as an argument to the Run command which specifies the image that should be used options on the other hand are more flexible parameters and they typically optional they're called options they're prefixed with dashes like dashh or double dashel and they can be positioned almost anywhere in the command line this type of parameter is also sometimes called a flag the Dash D and Double Dash name options they modify the behavior of the Run command running the container in detach mode and naming it my container and that also shows the difference between arguments and options usually any information that a command needs in order to run should be an argument for example file paths URLs container names or other data without which the command can run if you don't provide an image to Docker run for example it doesn't know what to do options therefore modifying Behavior or setting preferences such as verbosity level whether a container should run under different name Etc with this difference in mind let's create our Command in this case we want the function to create a note which is nothing more than a text file with some contents and tags for easier search without a note name we can't create a file and that means we can't create a note so the note name should be an argument and you can specify an argument by adding the click. arguments decorator the first parameter of the argument decorates is the name of the argument let's call this title now how do we access this argument well this is where the decorator come in handy and why that's an overall design Choice by this particular Library by naming the argument title it will grab the argument value that's passed through the CLI tool and inject it into the create function as an argument with the same name the same ideas apply to options but typically you write Double Dash before the value by default both arguments and options are going to be seen as strings but if you want to have a parameter that is for example an inst value you can pass that as a parameter to The Decorator for example let's say we want to add a version option and the type of this thing is going to be an INT and we can even Supply a default value for this and now that we have this version option we can simply add this as an argument to the function and we can add more options here for example content so you can already specify the content of the notes or tags a list of tags or keywords that you can add now click is really nice here because it's going to handle all the type conversions for you now the functionality of course of this particular command is not that interesting but uh like a good TV chef I already prepared the content of this function before and this is what it looks like now there's one thing that I want to point out here and that's the use of click. echo usually you would use print to print to standard out or use a loger or something now click. Echo is very similar to printing something but it's written so that it works EX exactly the same in different terminal environments such as bass zshell or even Windows console it also has some additional functionality like styling the output for example you can pass the click do style function or use click do s Echo click Echo Plus click style to pass parameters like background color underlining bolt text color and so on so at this point we have an entry point defined in noes. pii and a click command in CR .pi but there's no connection between them click doesn't know that this command exists also we will probably add more commands to this CLI so how do we patch everything up a solution is turning our entry point the CLI function into a click group a group is another building block of Click behind the scenes it's a command that nests other commands also known as sub commands or more groups meaning it acts as a command but you can also attach other commands to it this is the most common way to implement nesting commands in Click why would you need this well a sub command is a secondary command that falls under a primary command that adds another level of specificity and control of more detailed tasks they're particularly useful in complex CI applications where a single command might have multiple subcommands that need to be defined separately for example the docker CLI here using the docker CLI tool container is a command and start stop inspect Etc there's sub commands to the container that specify what action to perform on a Docker container and then of course you can add a bunch of other arguments and options the docker CLI tool is not built with click at least not as far as I know but if it were the entry point of the doc CI would be a function containing a group of commands and sub commands under that so let's do that for our application at this point we've seen how to use the click basic building blocks but we haven't looked at the inner working so what I want to do uh before the last step of connecting this Inter Point is take a quick look at the design of the library and how this actually works when you decorate a function with click top command like I'm doing here then click internally wraps the function in a command object using the command class and in case of click. group the same thing happens but it creates a group object which in itself inherits from the command class and you can also see that in the code let me scroll down this is a pretty long file but here you see the class group is a subass of the command class and this object is responsible for handling the commands execution including parsing arguments and options when you use click. option or click. arguments these decorators don't immediately modify the command function instead they attach additional metadata to the function object object that's what this does and this metadata includes information about the option or the arguments such as the name type default value help text Etc and when the command is executed click reads the metadata attached to the function and combines it with the command's logic then it sets up the argument parer based on the metadata and parses the command line arguments and then finally it invokes the command function with the appropriate parameters here's what happens step by step click commands registers create as a click command click argument title adds a positional argument for the title of the note click option content adds an option to specify the content of the note and the same thing happens for click option tags when the command is invoked click uses the metadata to set of an argument parer then it parsers the arguments based on that parser and lastly it will call the function passing the arguments and option as parameters to the function creating a project like this takes time and is hard however with my free design guide which contains seven steps you can hopefully make it a lot easier and avoid some of the mistakes that I made in the past go to iron. c/d design guide to grab your copy the link is also in description of this video now that you're a bit more familiar with the design and inner workings of Click let's go back to connecting the entry points like I mentioned before with the click. group decorator this creates a group instance to which we can add commands this can be done in two ways using the function name when defining a new command or or the method add command of the group instance using CLI instead of Click we'll use the existing group instead of creating a new group or command and this group instance has access to a method called command which in turn returns a decorator when it's called which is also why we have these parenthesis right here I'm personally not a huge fan of this approach I would Rec recommend using the add command method from the class so let's import crud and then let's add the command crud doc create in my opinion this is just more clear and direct furthermore this lets us separate commands in functions modules and more which just makes the project a bit more manageable so let's try out this command before you run this command you need to create a notes folder because that's what's being used here in the create command itself so let's run notes create my first note and we're going to add some contents and let's also add a few tags so as you can see this created a note stored at my home folder. notmy first notes.txt let's see what's in the file so as you can see this is a nice little Json file that contains the content and the tags that we created through our Command however me manually having to create a directory in order for my not CIT tool to work is not really the best solution and furthermore this directory is also hardcoded into the create command that means the user can change the location of the noes but also this needs to be defined either globally or in each function separately this not the best solution forly there are two things that we can do first we could let our first group handle the config of which directory to use and also handle creating that directory and secondly what you can do is use a context that stores the path to the directory that's later passed to the create command and other possible commands in your CLI first let's create a click context here is what the click documentation says whenever a click command is executed a context object is created which holds state for this particular invocation it remembers past parameters what command created which resources need to be clean up at the end of the function and so forth can also optionally hold an application defined object in other words the context object holds a state for the execution but it can also contain a custom object like a dictionary or a class that can later be accessed in the commands the context is by default invisible so we do need to use a decorator in order for us to access that context and we also need to do that in any other commands where we want to use it similarly to how we add arguments and options to our functions first we need to access the context so we can modify it and the CLI function is the best option for that so we simply add a pass context decorator in order to achieve that and then we have to context as part as an argument of this particular function now let's import OS and then if not os. path exists the node directory then we're going to create that particular particular directory the next thing that we can do is use path from path lip and now we can store that directory as part of the context object once you have this you can also pass the context to any of the other commands so in this case the create command now also gets the context and then we can get the nodes directory from the context object instead of defining it hardcoded in this function so now solve the problem of the nodes directory being hardcoded everywhere so it's not just part of the context but the first problem Still Remains which is that the user can't specify where notes are going to be stored so here's the plan I'm going to create a Json config file and then this file will later include the path to the node storing directory by default we can of course still use the dot nodes folder so that as usually you don't have to specify that whenever you start using this tool but what we can then do is actually add support for loading and saving functionality for this particular config as part of the noes CLI now like a smooth TV chef I've also prepared this one before so we see this very simple we import Json and path lip and then we have a couple of defaults like where we store the config the config file which is config.js and also the default noes directory and then I have two functions one for loading the config and one for saving the config and then we can use these functions in our entry point so first from config I'm going to import load config and we're also going to need the default noes directory and then what I'm going to do is load the configuration as a first line in our CLI entry point and then I can get the node directory from the config directory like so and default we're simply going to get the default note directory and then what we can do is also store the config inside our context object so we can later on access it and modify it by the user by setting it up this way our not CLI tool is just going to always read the config if it exists otherwise it's going to use a default config that's stored in the script by the way a neater way of dealing with this it's a bit beyond the scope of this video is to use pantic then you can also add validation for configuration values and so on if you want me to dive into that deeper let me know by posting a comment now at the moment the user can create the config file so let's add a group called config so we have a command for that there's no real Convention of where to store the configuration of your CLI or even what format it should be however there's an interesting website c.de that gives commandline interface guidelin so I highly recommend that you follow these as much as possible you can see that the recommendation is to store it either under a config folder or under an Etc SL project name or uh project name doc config or just directly in the home folder meaning you have a few options here but I'm just going to keep it simple and put this configuration file under config what should this config file contain well basically it can contain anything anything from default pass timeouts and so on but don't store credentials there please load those through environment variables also make sure to not pass credentials as options or arguments as part of the commands command are going to get stored in the terminal history in plain text the risk is of course low that somebody going to access that without you knowing but better safe than sorry question what do you think is the best place for storing configuration files for tools like this let me know in the comments below in order to Define these commands I'm going again into TV chef mode because I already prepared this so as you can see here I have my config group so that's part of the CLI and then as part of config I have a number of sub commands there is create which creates a new configuration then we have show which shows the current configuration then we have update where we can pass in this case the notes directory and that simply sets that note directory and then saves the configuration file let's try these commands as you can see currently there's no config file so let's create that let's see if that config is created all right so that works now let's update this so that it stores my notes in a my notes folder instead of topnotes so now that the nodes directory is set newly created nodes are going to be stored in this new directory and let's take a look at that folder and indeed we have a new location. text file here like I said at the start of the video there are some things you get for free with click for example click automatically generates help and usage messages the help text that's displayed here comes from both the help parameter in our decorators you can see that right here but also the block comments inside the function which is what you see right here besides the help text displaying the version of your tool is also implemented for you you indicate that this needs to happen by adding the click. version option decorator and there you can simply type notes version and it's going to print the version of your CI The Click also supports more advanced types like paths like you've seen for the noes directory but you do need to specify that when you define in this case the option as you can see here we're using click. path in order to do that and you can even add Flags like checking that the file actually exists next to these things there are other things that you get like there support for interactive prompting confirmations and choices it also supports colored and styled text output and finally it easily integrate with testing Frameworks like py test if you want me to do a video about that let me know in the comments so I've covered a lot of ground in this video Let's recap things I've talked about the basics of building a CI with click in Python I also talked about the design of Click itself and the benefits of using SC2 also when to use arguments versus options how to deal with configuring your CLI and like I mentioned click has a ton of other features as well that I didn't get to in this video if you want to see a more extended example that contains different variations of command definitions check out the report story where I store all my code examples that I use in my videos and there you're going to find an extended version you can check it out by going to github.com codes examples you now know how to easily create your own CLI tool what I didn't talk about yet is how to distribute it if you want to learn more about that check out the following video this covers packages but it's going to work with CLI tools as well and as always thanks for watching and see you next time
Info
Channel: ArjanCodes
Views: 24,670
Rating: undefined out of 5
Keywords: command line interface tool, command line interface, command line interface python, python cli, python click, python click command, python click command line, python click command line interface, click command line interface, click package, python click package, click package python, command line, python command line, python cli app, command line interface explained, command line interface basics, command line interface tutorial, command line interface example, cli example
Id: FWacanslfFM
Channel Id: undefined
Length: 25min 11sec (1511 seconds)
Published: Fri Jul 05 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.