Skip to content

Commit

Permalink
rework CLI chapter and open a new lesson
Browse files Browse the repository at this point in the history
  • Loading branch information
lubojr committed Jan 7, 2024
1 parent c64ff7a commit 7cb6300
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 58 deletions.
140 changes: 107 additions & 33 deletions lessons/beginners-en/argparse/index.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,109 @@
argparse
========
Command line interface in Python
=======================================

The `argparse` library is used to create a command line interface - CLI.
Primarily this means program argument processing. It is a part of the standard library, so you do not need to
install anything extra.
In this lesson, we will show you how to create your own command line interface tool `(CLI)` using Python using the `argparse` library. Primarily this means program argument processing.

## Command line interface

... is one of ways how to interact with or control a program (or Python script) as a user - **interface** with it
from inside the computer itself where it is installed (not over the internet).
... is one of ways how to interact with or control a program (not only Python scripts!) as a user - **interface** with it from inside the computer itself where it is installed (not over the internet).
That user can be you as a creator of code and someone else you give access to it.

### Motivation

In the <link to testing lesson> we have shown that sometimes you need to call a Python program
independently with different arguments in order to run computations with different values.

Letting the tester or user pass these **arguments** from command line (which means not manually editing them inside the Python script) is a great option.

The behavior of a program usually is varying - it depends on the instructions
you give to it.
you give to it as **arguments**.

### Example

An example of a tool which is usually installed on both Windows and Unix is ``curl``. It allows you to send HTTP requests to the internet and receive responses.

Adding command line arguments to a program enables terminal magic using `find` command like this:
If you want to know what arguments does existing program allow, the argument to use is **help**. This works for most of programs on your computer (if their author created a help page). Also usually there is a `-help` or `--help` command line argument which also shows the expected usage of a tool to any user.

#### git

As an example of a tool which works on both Linux and Windows mostly the same way, you can take our beloved [git]({{ lesson_url('git-en/basics') }}).

Try running this in your terminal:

```console
find . -type f -name "*.txt" -empty -mtime 30
git --help
```

Which searches current directory for empty files with .txt suffix and a modification date older than 30 days.
The find command internally has a lot of switches controlling its
behavior while filtering the folder(s) is is searching through
based on user given parameters of the search.
and for any subcommand as well:

### Examples of libraries & argparse
```console
git log --help
```

Quite many tools for processing arguments from the CLI are also present in the standard Python:
`sys.argv`, `optparse`, `getopt`.
Additionally there is quite commonly used `click` library, which you would need to install via pip
and uses a decorator syntax, that we have not seen yet, so we have decided not to delve into it.
Now that you know that programs usually accept arguments and they are documented in ``help``, you can start using ``git`` in many ways.
For example you can heavily customize output of `git log` in some existing git repository:

```console
git log --oneline --graph --decorate --cherry-mark --boundary
```

Another example of a command line tool with arguments is **find** but does different things on Windows and Linux. This example is valid for Linux:

```console
find . -type f -name "*.txt" -empty
```

Which searches current directory for empty files with .txt suffix.

### Argparse

How can we create a CLI in Python? Today, we will show usage of a `argparse` tool.

It is a part of the standard library, so you do not need to install anything extra.

You can find the official documentation on:

- [argparse](https://docs.python.org/3/library/argparse.html)
- [sys.argv](https://docs.python.org/3/library/sys.html#sys.argv)
- [optparse](https://docs.python.org/3/library/optparse.html)
- [getopt](https://docs.python.org/3/library/getopt.html)

And a quite handy tutorial going through most of functionalities, you could ever encounter:
[argparse-tutorial](https://docs.python.org/3/howto/argparse.html)

Here's how to easily create Python command line application with switches:
Additionally there is quite commonly used `click` library, which you would need to install via pip
and uses a decorator syntax, that we have not seen yet, so it can remain as self study.

#### argparse basic usage

Usually when we send our Python code to someone else, we do not expect them to read the whole code but reading the help should be enough for them to use it and change parameters.

For Python CLI tools, you would get the help this way, which you see is the same as for `git`:

```console
python3 hello.py --help
```

Here's how to create Python command line application with switches:

But first lets start with a function that greets the user for a given number of times and optionally can indent the greeting.

Lets save the following code into `hello.py` file.

```python
def hello(count, name, indent=False):
"""Simple program that greets 'name' for a total of 'count' times and optionally indents."""
for _ in range(count):
if indent:
print(" ", end="")
print(f"Hello {name}!")

count = 5
name = "Tyna"
indent = True
hello(count, name, indent)
```

And run it as usual as `python hello.py`.

Now we want the arguments `count`, `name` and `indent` to be enabled as CLI options and come from command line arguments when you start the script.

```python
import argparse
Expand All @@ -53,29 +113,26 @@ parser.add_argument('-n', '--name', help='a name to repeat', required=True)
parser.add_argument('-c', '--count', help='how many times', required=True, type=int)
parser.add_argument("--indent", action="store_true", help=("name will be indented by 4 spaces"))

args = parser.parse_args()

def hello(count, name, indent=False):
"""Simple program that greets 'name' for a total of 'count' times and optionally indents."""
for _ in range(count):
if indent:
print(" ", end="")
print(f"Hello {name}!")

args = parser.parse_args()
hello(args.count, args.name, args.indent)
```

The first step in using the argparse is creating an `ArgumentParser` object with some description.
Then you fill an ArgumentParser with information about program
Then you fill an `ArgumentParser` with information about program
arguments, which is done by making calls to the `add_argument()` method.
This information is stored and used when `parse_args()` is called.

You can set parameters as required by adding `required=True` option.
It is also possible to their `type`, which will try to convert the variable to the data type announced.
In order to allow simple storing of boolean flags `True/False`, you can use the `action="store_true"` parameter.

Try it! If you have it saved as `hello.py`, try:

```console
python3 hello.py
python3 hello.py --help
Expand All @@ -89,16 +146,31 @@ That is already a very solid first program is it not?

## Positional arguments

You can of course define arguments, which are `positional` in the same way as when you are defining and calling
a function. The parsing will expect all arguments to be in the order, you defined them.
You can of course define arguments, which are `positional` in the same way as when you are defining and using function arguments. The parsing will expect all arguments to be in the order, in which you defined them.

To try it, replace the first two lines with `name` and `count` arguments with following lines:

```python
parser.add_argument("input_file", default=None, help=("Input file to read"))
parser.add_argument("name", help='a name to repeat')
parser.add_argument("count", help='how many times', type=int)
```

From now on, the order in which you provide `name` and `count` arguments will be important. The named arguments can still be provided before or after the positional arguments.

```console
python3 hello.py PyLady 5 --indent
```

An example of a wrong call would be:

```console
python3 hello.py input.txt --count 5 --name PyLady
python3 hello.py 5 PyLady
```

Which should run into following error:

```
hello.py: error: argument count: invalid int value: 'PyLady'
```

## Other options
Expand All @@ -107,6 +179,8 @@ Switch names begin, according to Unix convention, with hyphens: one hyphen `-`
for one-letter abbreviations, two hyphens `--` for multi-letter names.
One switch can have more than one name - short option and long option.

This example shows how it is usually done for example of `logging` setup - although it does not apply for our simple example.

```python
parser.add_argument(
"-v", "--verbosity", type=int, default=3, choices=[0, 1, 2, 3, 4],
Expand All @@ -118,7 +192,7 @@ parser.add_argument(
```

Parameter names with `hyphens` inside them will automatically turn them into variable names
with `underscores`, as it is not possible to have a `hyphen` in variable name in Python.
with `underscores`, as it is not possible to have a `hyphen -` in variable name in Python.

```python
parser.add_argument(
Expand Down
24 changes: 10 additions & 14 deletions lessons/beginners-en/files/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,11 @@ Create a `poem.txt` file in the editor and write any poem into it.
Save the file.

> [note]
> I recommend to use the same editor that you use for your
> Python program to edit the file with the poem.
>
> If you use a different editor than Atom, be sure to keep in mind when coding:
> * If the editor offers you a choice of encoding, choose `UTF-8`.
> * If `UTF-8 without BOM` is available, use it.
> * If you have to use Notepad then use the non-standard `utf-8-sig` instead.
>
> [`utf-8`] is the name of the standard encoding.
> You can store any emoji or accented characters to files with this encoding.
> 🎉
[`utf-8`]: https://en.wikipedia.org/wiki/UTF-8
> In the following example, we are using encoding `utf-8`, which
> covers majority of european characters.
> [`utf-8`]: https://en.wikipedia.org/wiki/UTF-8
> There are other encodings covering different character sets.
> If you want to learn more about encodings, you can read this nicely readable [Encoding article](https://tonsky.me/blog/unicode)
Let's create a following code:

Expand Down Expand Up @@ -235,7 +227,11 @@ with open('json_data.txt') as json_file:
print(data)
```

In order to read or write `csv` you can use the inbuilt `csv` library.
In previous lesson [json]({{ lesson_url('beginners-en/json') }}) we have shown also loading JSON from a string and writing to a string, which used methods `loads` and `dumps`. The ending `s` represents `string`. You can remember the difference between `load` and `loads` like that.

## Other common data formats

In order to read or write `csv` you can use the built-in `csv` library.

For other data formats like `Excel` files, you need to install external libraries.

Expand Down
2 changes: 1 addition & 1 deletion lessons/beginners-en/json/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ which, for its simplicity, has expanded the most:
## JSON in Python

Object encoding in JSON is simple: there is a `json` module,
whose `load` method retrieves data from the string:
whose `loads` method retrieves data from the string:

```python
import json
Expand Down
20 changes: 10 additions & 10 deletions runs/2023/pyladies-en-vienna-2023-autumn/info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,16 +220,16 @@ plan:
- title: Working with Files and CLI
slug: files_cli
date: 2024-01-09
# materials:
# - lesson: beginners-en/json
# - lesson: beginners-en/files
# - lesson: beginners-en/argparse
# - title: Files/Exceptions cheatsheet
# url: https://github.com/ehmatthes/pcc/releases/download/v1.0.0/beginners_python_cheat_sheet_pcc_files_exceptions.pdf
# type: cheatsheet
# - title: Homework (PDF)
# url: https://drive.google.com/file/d/1y04_M3994x9GaS19IyYyqIAe0N4YdTI1/view
# type: homework
materials:
- lesson: beginners-en/json
- lesson: beginners-en/files
- lesson: beginners-en/argparse
- title: Files/Exceptions cheatsheet
url: https://github.com/ehmatthes/pcc/releases/download/v1.0.0/beginners_python_cheat_sheet_pcc_files_exceptions.pdf
type: cheatsheet
- title: Homework
url: https://docs.google.com/document/d/1Tjy0uSqdVMmQI2tbHWBmDtrnwMl-Px3J7-N6_xRh12o
type: homework

- title: Final Project
slug: final_project
Expand Down

0 comments on commit 7cb6300

Please sign in to comment.