Skip to content

Commit 6729b3d

Browse files
chryslejaraco
andcommitted
Refine structure
Make the guide more easily comprehensible, mention difference between src and flat layout concerning `runpy` behaviour, mention typer CLI parser, tighten language Co-authored-by: Jason R. Coombs <[email protected]>
1 parent 85dc060 commit 6729b3d

File tree

2 files changed

+63
-30
lines changed

2 files changed

+63
-30
lines changed

source/discussions/src-layout-vs-flat-layout.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,27 @@ layout and the flat layout:
7979
``tox.ini``) and packaging/tooling configuration files (eg: ``setup.py``,
8080
``noxfile.py``) on the import path. This would make certain imports work
8181
in editable installations but not regular installations.
82+
83+
.. _running-cli-from-source-src-layout:
84+
85+
Running a command-line interface from source with src-layout
86+
============================================================
87+
88+
Due to the firstly mentioned specialty of the src layout, a command-line
89+
interface can not be run directly from the :term:`source tree <Project Source Tree>`,
90+
but requires installation of the package in
91+
:doc:`Development Mode <setuptools:userguide/development_mode>`
92+
for testing purposes. Since this can be unpractical in some situations,
93+
a workaround could be to prepend the package folder to Python's
94+
:py:data:`sys.path` when called via its :file:`__main__.py` file:
95+
96+
.. code-block:: python
97+
98+
import os
99+
import sys
100+
101+
if not __package__:
102+
# Make CLI runnable from source tree with
103+
# python src/package
104+
package_source_path = os.path.dirname(os.path.dirname(__file__))
105+
sys.path.insert(0, package_source_path)

source/guides/creating-command-line-tools.rst

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ and exposing the executable scripts of packages (and available manual pages) for
1111
Creating the package
1212
====================
1313

14-
First of all, we'll need to create a source tree for the :term:`project <Project>`. For the sake of an example, we'll
15-
create a simple tool outputting a greeting (a string) for a person based on arguments given on the command-line.
14+
First of all, create a source tree for the :term:`project <Project>`. For the sake of an example, we'll
15+
build a simple tool outputting a greeting (a string) for a person based on arguments given on the command-line.
1616

1717
.. todo:: Advise on the optimal structure of a Python package in another guide or discussion and link to it here.
1818

@@ -62,7 +62,7 @@ named after the main module:
6262
print(greeting)
6363
6464
The above function receives several keyword arguments that determine how the greeting to output is constructed.
65-
Now, the command-line interface to provision it with the same needs to be constructed, which is done
65+
Now, construct the command-line interface to provision it with the same, which is done
6666
in :file:`cli.py`:
6767

6868
.. code-block:: python
@@ -125,29 +125,28 @@ in :file:`cli.py`:
125125
126126
The command-line interface is built with :py:mod:`argparse`, a command-line parser which is included in Python's
127127
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_.
128+
advanced users are encouraged to make use of click_ or typer_.
129129

130-
We'll add an empty :file:`__init__.py` file, too, to define the project as a regular :term:`import package <Import Package>`.
130+
Now, add an empty :file:`__init__.py` file, to define the project as a regular :term:`import package <Import Package>`.
131131

132-
The file :file:`__main__.py` marks the main entry point for the application when running it via ``python -m greetings``,
133-
so we'll just initizalize the command-line interface here. The first condition isn't necessary, but may be added in order
134-
to make the package runnable directly from the source tree, by prepending the package folder to Python's :py:data:`sys.path`:
132+
The file :file:`__main__.py` marks the main entry point for the application when running it via :mod:`runpy`
133+
(i.e. ``python -m greetings``, which works immediately with flat layout, but requires installation of the package with src layout),
134+
so initizalize the command-line interface here:
135135

136136
.. code-block:: python
137137
138-
import os
139138
import sys
140139
141-
if not __package__:
142-
# Make package runnable from source tree with
143-
# python src/greetings
144-
package_source_path = os.path.dirname(os.path.dirname(__file__))
145-
sys.path.insert(0, package_source_path)
146-
147140
if __name__ == "__main__":
148141
from greetings.cli import main
149142
sys.exit(main())
150143
144+
.. note::
145+
146+
In order to enable calling the command-line interface directly from the :term:`source tree <Project Source Tree>`,
147+
i.e. as ``python src/greetings``, a certain hack could be placed in this file; read more at
148+
:ref:`running-cli-from-source-src-layout`.
149+
151150

152151
``pyproject.toml``
153152
------------------
@@ -161,28 +160,21 @@ For the project to be recognised as a command-line tool, additionally a ``consol
161160
[project.scripts]
162161
greet = "greetings.cli:main"
163162
164-
Besides, it could prove rewarding to add a ``pipx``-specific entry point, the meaning of which is described below:
165-
166-
.. code-block:: toml
167-
168-
[project.entry-points."pipx.run"]
169-
greetings = "greetings.cli:main"
170-
171-
172163
Now, the project's source tree is ready to be transformed into a :term:`distribution package <Distribution Package>`,
173164
which makes it installable.
174165

175166

176167
Installing the package with ``pipx``
177168
====================================
178169

179-
After installing ``pipx`` as described in :ref:`installing-stand-alone-command-line-tools`, you're ready to install your project:
170+
After installing ``pipx`` as described in :ref:`installing-stand-alone-command-line-tools`, install your project:
180171

181172
.. code-block:: console
182173
183-
$ pipx install ./greetings/
174+
$ cd path/to/greetings/
175+
$ pipx install .
184176
185-
This will expose the executable script we defined as an entry point and make the command ``greet`` available to you.
177+
This will expose the executable script we defined as an entry point and make the command ``greet`` available.
186178
Let's test it:
187179

188180
.. code-block:: console
@@ -194,14 +186,30 @@ Let's test it:
194186
$ greet --gender masculine
195187
Greetings, dear Mr. what's-his-name!
196188
197-
To just run the program without installing it permanently, you could use ``pipx run``, which will create a temporary (but cached) virtual environment for it:
189+
To just run the program without installing it permanently, use ``pipx run``, which will create a temporary (but cached) virtual environment for it:
198190

199191
.. code-block:: console
200192
201-
$ pipx run ./greetings/ --knight
193+
$ pipx run --spec . greet --knight
194+
195+
This syntax is a bit unpractical, however; as the name of the entry point we defined above does not match the package name,
196+
we need to state explicitly which executable script to run (even though there is only on in existence).
197+
198+
There is, however, a more practical solution to this problem, in the form of an entry point specific to ``pipx run``.
199+
The same can be defined as follows in :file:`pyproject.toml`:
200+
201+
.. code-block:: toml
202+
203+
[project.entry-points."pipx.run"]
204+
greetings = "greetings.cli:main"
205+
206+
207+
Thanks to this entry point (which *must* match the package name), ``pipx`` will pick up the executable script as the
208+
default one and run it, which makes this command possible:
209+
210+
.. code-block:: console
202211
203-
Thanks to the entry point we defined above (which *must* match the package name), ``pipx`` will pick up the executable script as the
204-
default one and run it; otherwise, you'd need to specify the entry point's name explicitly with ``pipx run --spec ./greetings/ greet --knight``.
212+
$ pipx run . --knight
205213
206214
Conclusion
207215
==========
@@ -211,3 +219,4 @@ meaning uploading it to a :term:`package index <Package Index>`, most commonly :
211219

212220
.. _click: https://click.palletsprojects.com/
213221
.. _docopt: https://docopt.readthedocs.io/en/latest/
222+
.. _typer: https://typer.tiangolo.com/

0 commit comments

Comments
 (0)