You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
pronoun ="her"if feminine else"his"if masculine or knight else"its"
55
-
greeting +=f"what's-{pronoun}-name!"
56
-
else:
57
-
if name:
58
-
greeting +=f"{name}!"
59
-
elifnot gender:
60
-
greeting +="friend!"
61
-
for i inrange(0, count):
62
-
print(greeting)
38
+
import typer
39
+
from typing_extensions import Annotated
40
+
41
+
42
+
defgreet(
43
+
name: Annotated[str, typer.Argument(help="The (last, if --gender is given) name of the person to greet")] ="",
44
+
gender: Annotated[str, typer.Option(help="The gender of the person to greet")] ="",
45
+
knight: Annotated[bool, typer.Option(help="Whether the person is a knight")] =False,
46
+
count: Annotated[int, typer.Option(help="Number of times to greet the person")] =1
47
+
):
48
+
greeting ="Greetings, dear "
49
+
masculine = gender =="masculine"
50
+
feminine = gender =="feminine"
51
+
if gender or knight:
52
+
salutation =""
53
+
if knight:
54
+
salutation ="Sir "
55
+
elif masculine:
56
+
salutation ="Mr. "
57
+
elif feminine:
58
+
salutation ="Ms. "
59
+
greeting += salutation
60
+
if name:
61
+
greeting +=f"{name}!"
62
+
else:
63
+
pronoun ="her"if feminine else"his"if masculine or knight else"its"
64
+
greeting +=f"what's-{pronoun}-name"
65
+
else:
66
+
if name:
67
+
greeting +=f"{name}!"
68
+
elifnot gender:
69
+
greeting +="friend!"
70
+
for i inrange(0, count):
71
+
print(greeting)
63
72
64
73
The above function receives several keyword arguments that determine how the greeting to output is constructed.
65
74
Now, construct the command-line interface to provision it with the same, which is done
66
75
in :file:`cli.py`:
67
76
68
77
.. code-block:: python
69
78
70
-
import argparse
71
-
import sys
72
-
73
-
from .greet import greet
74
-
75
-
_arg_spec = {
76
-
'--name': {
77
-
'metavar': 'STRING',
78
-
'type': str,
79
-
'help': 'The (last, if "gender" is given) name of the person to greet',
80
-
},
81
-
'--count': {
82
-
'metavar': 'INT',
83
-
'type': int,
84
-
'default': 1,
85
-
'help': 'Number of times to greet the person',
86
-
},
87
-
88
-
}
89
-
_arg_spec_mutually_exclusive = {
90
-
'--gender': {
91
-
'metavar': 'STRING',
92
-
'type': str,
93
-
'help': 'The gender of the person to greet',
94
-
},
95
-
'--knight': {
96
-
'action': 'store_true',
97
-
'default': False,
98
-
'help': 'Whether the person is a knight',
99
-
},
100
-
}
101
-
102
-
103
-
defmain():
104
-
parser = argparse.ArgumentParser(
105
-
description="Greet a person (semi-)formally."
106
-
)
107
-
group = parser.add_mutually_exclusive_group()
108
-
for arg, spec in _arg_spec.items():
109
-
parser.add_argument(arg, **spec)
110
-
for arg, spec in _arg_spec_mutually_exclusive.items():
111
-
group.add_argument(arg, **spec)
112
-
parsed_args = parser.parse_args()
113
-
args = {
114
-
arg: value
115
-
for arg, value invars(parsed_args).items()
116
-
if value isnotNone
117
-
}
118
-
# Run the function with the command-line arguments as keyword arguments.
119
-
# A more complex setup is normally initialized at this point.
120
-
greet(**args)
79
+
import typer
121
80
81
+
from .hello import greet
82
+
83
+
84
+
app = typer.Typer()
85
+
app.command()(greet)
122
86
123
-
if__name__=="__main__":
124
-
sys.exit(main())
125
87
126
-
The command-line interface is built with :py:mod:`argparse`, a command-line parser which is included in Python's
127
-
standard library. It is a bit rudimentary but sufficient for most needs. Another easy-to-use alternative is docopt_;
128
-
advanced users are encouraged to make use of click_ or typer_.
88
+
if__name__=="__main__":
89
+
app()
90
+
91
+
The command-line interface is built with typer_, an easy-to-use CLI parser based on Python type hints. It provides
92
+
auto-completion and nicely styled command-line help out of the box. Another option would be :py:mod:`argparse`,
93
+
a command-line parser which is included in Python's standard library. It is sufficient for most needs, but requires
94
+
a lot of code, usually in ``cli.py``, to function properly. Alternatively, docopt_ makes it possible to create CLI
95
+
interfaces based solely on docstrings; advanced users are encouraged to make use of click_ (on which ``typer`` is based).
129
96
130
97
Now, add an empty :file:`__init__.py` file, to define the project as a regular :term:`import package <Import Package>`.
131
98
@@ -135,11 +102,9 @@ so initizalize the command-line interface here:
135
102
136
103
.. code-block:: python
137
104
138
-
import sys
139
-
140
105
if__name__=="__main__":
141
-
from greetings.cli importmain
142
-
sys.exit(main())
106
+
from greetings.cli importapp
107
+
app()
143
108
144
109
.. note::
145
110
@@ -151,14 +116,15 @@ so initizalize the command-line interface here:
151
116
``pyproject.toml``
152
117
------------------
153
118
154
-
The project's :term:`metadata <Pyproject Metadata>` is placed in :term:`pyproject.toml`. The :term:`pyproject metadata keys <Pyproject Metadata Key>` and the ``[build-system]`` table may be filled in as described in :ref:`writing-pyproject-toml`.
119
+
The project's :term:`metadata <Pyproject Metadata>` is placed in :term:`pyproject.toml`. The :term:`pyproject metadata keys <Pyproject Metadata Key>` and the ``[build-system]`` table may be filled in as described in :ref:`writing-pyproject-toml`, adding a dependency
120
+
on ``typer`` (this tutorial uses version *0.12.3*).
155
121
156
122
For the project to be recognised as a command-line tool, additionally a ``console_scripts`` :ref:`entry point <entry-points>` (see :ref:`console_scripts`) needs to be added as a :term:`subkey <Pyproject Metadata Subkey>`:
157
123
158
124
.. code-block:: toml
159
125
160
126
[project.scripts]
161
-
greet = "greetings.cli:main"
127
+
greet = "greetings.cli:app"
162
128
163
129
Now, the project's source tree is ready to be transformed into a :term:`distribution package <Distribution Package>`,
164
130
which makes it installable.
@@ -179,14 +145,18 @@ Let's test it:
179
145
180
146
.. code-block:: console
181
147
182
-
$ greet --knight --name Lancelot
148
+
$ greet --knight Lancelot
183
149
Greetings, dear Sir Lancelot!
184
-
$ greet --gender feminine --name Parks
150
+
$ greet --gender feminine Parks
185
151
Greetings, dear Ms. Parks!
186
152
$ greet --gender masculine
187
153
Greetings, dear Mr. what's-his-name!
188
154
189
-
To just run the program without installing it permanently, use ``pipx run``, which will create a temporary (but cached) virtual environment for it:
155
+
Since this example uses ``typer``, you could now also get an overview of the program's usage by calling it with
156
+
the ``--help`` option, or configure completions via the ``--install-completion`` option.
157
+
158
+
To just run the program without installing it permanently, use ``pipx run``, which will create a temporary
159
+
(but cached) virtual environment for it:
190
160
191
161
.. code-block:: console
192
162
@@ -201,7 +171,7 @@ The same can be defined as follows in :file:`pyproject.toml`:
201
171
.. code-block:: toml
202
172
203
173
[project.entry-points."pipx.run"]
204
-
greetings = "greetings.cli:main"
174
+
greetings = "greetings.cli:app"
205
175
206
176
207
177
Thanks to this entry point (which *must* match the package name), ``pipx`` will pick up the executable script as the
0 commit comments