Skip to content

Commit 22142d6

Browse files
plagusspre-commit-ci[bot]pradyunsgchrysle
authored
Add documentation for CLI architecture (#12093)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Pradyun Gedam <[email protected]> Co-authored-by: chrysle <[email protected]>
1 parent 5545a15 commit 22142d6

File tree

2 files changed

+158
-9
lines changed

2 files changed

+158
-9
lines changed

docs/html/development/architecture/command-line-interface.rst

Lines changed: 157 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,167 @@ for parsing top level args.
2828

2929
``Command`` then uses another ``ConfigOptionParser`` instance, to parse command-specific args.
3030

31-
* TODO: How & where options are defined
32-
(cmdoptions, command-specific files).
31+
Command structure
32+
-----------------
3333

34-
* TODO: How & where arguments are processed.
35-
(main_parser, command-specific parser)
34+
This section shows the class hierarchy from which every command's class will inherit
35+
from.
3636

37-
* TODO: How processed arguments are accessed.
38-
(attributes on argument to ``Command.run()``)
37+
`base_command.py <https://github.com/pypa/pip/blob/main/src/pip/_internal/cli/base_command.py>`_
38+
defines the base ``Command`` class, from which every other command will inherit directly or
39+
indirectly (see the *command tree* at the end of this section).
3940

40-
* TODO: How configuration and CLI "blend".
41-
(implemented in ``ConfigOptionParser``)
41+
Using the ``ConfigOptionParser`` (see `Configuration and CLI "blend" <Configuration and CLI "blend"_>`_),
42+
this class adds the general options and instantiates the *cmd_opts* group, where every other specific
43+
option will be added if needed on each command's class. For those commands that define specific
44+
options, like ``--dry-run`` on ``pip install`` command, the options must be added to *cmd_opts*
45+
this is the job of *add_options* method), which will be automatically called on ``Command``'s initialization.
4246

43-
* TODO: progress bars and spinners
47+
The base ``Command`` has the following methods:
48+
49+
.. py:class:: Command
50+
51+
.. py:method:: main()
52+
53+
Main method of the class, it's always called (as can be seen in main.py's
54+
`main <https://github.com/pypa/pip/blob/main/src/pip/_internal/cli/main.py#L46>`_).
55+
It's in charge of calling the specific ``run`` method of the class and handling the possible errors.
56+
57+
.. py:method:: run()
58+
59+
Abstract method where the actual action of a command is defined.
60+
61+
.. py:method:: add_options()
62+
63+
Optional method to insert additional options on a class, called on ``Command`` initialization.
64+
65+
Some commands have more specialized behavior, (see for example ``pip index``).
66+
These commands instead will inherit from ``IndexGroupCommand``, which inherits from ``Command``
67+
and ``SessionCommandMixin`` to build build the pip session for the corresponding requests.
68+
69+
Lastly, ``RequirementCommand``, which inherits from ``IndexGroupCommand`` is the base class
70+
for those commands which make use of requirements in any form, like ``pip install``.
71+
72+
In addition to the previous classes, a last mixin class must be mentioned, from which
73+
``Command`` as well as ``SessionCommandMixin`` inherit: ``CommandContextMixIn``, in
74+
charge of the command's context.
75+
76+
In the following command tree we can see the hierarchy defined for the different pip
77+
commands, where each command is defined under the base class it inherits from:
78+
79+
| ``Command``
80+
| ├─ ``cache``, ``check``, ``completion``, ``configuration``, ``debug``, ``freeze``, ``hash``, ``help``, ``inspect``, ``show``, ``search``, ``uninstall``
81+
| └─ ``IndexGroupCommand``
82+
| ├─ ``index``, ``list``
83+
| └─ ``RequirementCommand``
84+
| └─ ``wheel``, ``download``, ``install``
85+
86+
87+
Option definition
88+
-----------------
89+
90+
The set of shared options are defined in `cmdoptions.py <https://github.com/pypa/pip/blob/main/src/pip/_internal/cli/cmdoptions.py>`_
91+
module, as well as the *general options* and *package index options* groups of options
92+
we see when we call a command's help, or the ``pip index``'s help message respectively.
93+
All options are defined in terms of functions that return `optparse.Option <https://docs.python.org/3/library/optparse.html#optparse.Option>`_
94+
instances once called, while specific groups of options, like *Config Options* for
95+
``pip config`` are defined in each specific command file (see for example the
96+
`configuration.py <https://github.com/pypa/pip/blob/main/src/pip/_internal/commands/configuration.py>`_).
97+
98+
Argument parsing
99+
----------------
100+
101+
The main entrypoint for the application is defined in the ``main`` function in the
102+
`main.py <https://github.com/pypa/pip/blob/main/src/pip/_internal/cli/main.py>`_ module.
103+
This function is in charge of the `autocompletion <https://github.com/pypa/pip/blob/main/src/pip/_internal/cli/autocompletion.py>`_,
104+
calling the ``parse_command`` function and creating and running the subprograms
105+
via ``create_command``, on which the ``main`` method is called.
106+
107+
The ``parse_command`` is defined in the `main_parser.py <https://github.com/pypa/pip/blob/main/src/pip/_internal/cli/main_parser.py>`_
108+
module, which defines the following two functions:
109+
110+
.. py:function:: parse_command()
111+
112+
Function in charge of the initial parse of ``pip``'s program. Creates the main parser (see
113+
the next function ``create_main_parser``) to extract the general options
114+
and the remaining arguments. For example, running ``pip --timeout=5 install --user INITools``
115+
will split ``['--timeout=5']`` as general option and ``['install', '--user', 'INITools']``
116+
as the remainder.
117+
118+
At this step the program deals with the options ``--python``, ``--version``, ``pip``
119+
or ``pip help``. If neither of the previous options is found, it tries to extract the command
120+
name and arguments.
121+
122+
.. py:function:: create_main_parser()
123+
124+
Creates the main parser (type ``pip`` in the console to see the description of the
125+
program). The internal parser (`ConfigOptionParser <Configuration and CLI "blend"_>`_),
126+
adds the general option group and the list of commands coming from ``cmdoptions.py``
127+
at this point.
128+
129+
After the initial parsing is done, ``create_command`` is in charge of creating the appropriate
130+
command using the information stored in `commands_dict <https://github.com/pypa/pip/blob/main/src/pip/_internal/commands/__init__.py>`_
131+
variable, and calling its ``main`` method (see `Command structure <Command structure>`_).
132+
133+
A second argument parsing is done at each specific command (defined in the base ``Command`` class),
134+
again using the ``ConfigOptionParser``.
135+
136+
Argument access
137+
---------------
138+
139+
To access all the options and arguments, ``Command.run()`` takes
140+
the options as `optparse.Values <https://docs.python.org/3/library/optparse.html#optparse.Values>`_
141+
and a list of strings for the arguments (parsed in ``Command.main()``). The internal methods of
142+
the base ``Command`` class are in charge of passing these variables after ``parse_args`` is
143+
called for a specific command.
144+
145+
Configuration and CLI "blend"
146+
-----------------------------
147+
148+
The base ``Command`` instantiates the class `ConfigOptionParser <https://github.com/pypa/pip/blob/main/src/pip/_internal/cli/parser.py>`_
149+
which is in charge of the parsing process (via its parent class
150+
`optparse.OptionParser <https://docs.python.org/3/library/optparse.html#optparse.OptionParser>`_).
151+
Its main addition consists of the following function:
152+
153+
.. py:class:: ConfigOptionParser(OptionParser)
154+
155+
.. py:method:: get_default_values()
156+
157+
Overrides the original method to allow updating the defaults ater the instantiation of the
158+
option parser.
159+
160+
It allows overriding the default options and arguments using the ``Configuration`` class
161+
(more information can be found on :ref:`Configuration`) to include environment variables and
162+
settings from configuration files.
163+
164+
Progress bars and spinners
165+
--------------------------
166+
167+
There are two more modules in the ``cli`` subpackage in charge of showing the state of the
168+
program.
169+
170+
* `progress_bars.py <https://github.com/pypa/pip/blob/main/src/pip/_internal/cli/progress_bars.py>`_
171+
172+
This module contains the following function:
173+
174+
.. py:function:: get_download_progress_renderer()
175+
176+
It uses `rich <https://rich.readthedocs.io/en/stable/reference/progress.html#module-rich.progress>`_
177+
functionalities to render the download progress.
178+
179+
This function (used in `download.py <https://github.com/pypa/pip/blob/main/src/pip/_internal/network/download.py>`_,
180+
inside the ``Downloader`` class), allows watching the download process when running
181+
``pip install`` on *big* packages.
182+
183+
* `spinner.py <https://github.com/pypa/pip/blob/main/src/pip/_internal/cli/spinners.py>`_
184+
185+
The main function of this module is:
186+
187+
.. py:function:: open_spinner()
188+
189+
It yields the appropriate type of spinner, which is used in ``call_subprocess``
190+
function, inside `subprocess.py <https://github.com/pypa/pip/blob/main/src/pip/_internal/utils/subprocess.py>`_
191+
module, so the user can see there is a program running.
44192

45193
* TODO: quirks / standard practices / broad ideas.
46194
(avoiding lists in option def'n, special cased option value types,

news/6831.doc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update architecture documentation for command line interface.

0 commit comments

Comments
 (0)