Python Module 1 – Getting started: install, run and read Python
uv, pipx and inline script metadata in Python
Create a Python project with uv, run a single-file script that declares its own dependencies, and install command-line tools in isolation with pipx or uv.
What you will learn
- Create a project and add dependencies with uv init, uv add and uv run
- Explain what uv.lock records that pyproject.toml does not
- Write an inline metadata block that uv run and pipx run can read
- Install command-line tools in isolation with pipx or uv tool
- Choose between venv and pip, uv and pipx for a task
Before you start
What you’ll learn · Before you start
What you will learn
- Create a project and add dependencies with uv init, uv add and uv run
- Explain what uv.lock records that pyproject.toml does not
- Write an inline metadata block that uv run and pipx run can read
- Install command-line tools in isolation with pipx or uv tool
- Choose between venv and pip, uv and pipx for a task
Before you start
On this page
A project with three packages needs four steps on every computer it runs on: make an environment, install the right
versions into it, record those versions, and repeat all of it after each change. The venv and pip of the previous
lesson do each step, and the steps go stale the day someone forgets one. Two tools do this bookkeeping for you: uv
manages a whole project, and pipx installs command-line applications, each in an environment of its own.
Below you set up a project with uv and see what it checks before every run, compare a range of versions with a lock file, give a single script its own list of dependencies, and install tools so that they never clash with your projects. Neither tool is part of Python: both are open-source programs that you install once.
uv keeps a project’s environment in step with pyproject.toml
uv, made by Astral and licensed under MIT or Apache-2.0, is one program that does the jobs of several: it installs
Python versions (uv python install 3.14), creates virtual environments, works out which versions of your
dependencies fit together, records them, and runs your code. Install it with a package manager (brew install uv on
macOS, winget install --id=astral-sh.uv -e on Windows), with pipx install uv, or with the installer script from
uv’s documentation. A script you download and run has the same access to your computer as you, so look at it first;
the documentation shows how.
Version note
This lesson follows the documentation of uv 0.13 and pipx 1.18. Both change faster than Python, so if a command
behaves differently, compare uv --version or pipx --version with their documentation.
Three commands start a project:
uv init weather-notescreates the folderweather-noteswith apyproject.toml, a.python-versionfile that names the Python version to use, aREADME.md, a.gitignore, a Git repository and starter code insrc/weather_notes/__init__.py.uv add rich, in that folder, writes the requirementrich>=15.0.0, the newest version that fits as the lowest allowed, intopyproject.toml. It also creates.venvif there is none, installs rich into it and records the exact versions inuv.lock.uv run weather-notesruns the command thatuv initset up, anduv run some_file.pyruns any file, inside the project’s environment.
What makes uv run dependable is what it checks before it runs anything. Step through one run:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- You type uv run. In the project's folder you type uv run weather-notes. Nothing is activated, and you do not have to remember whether anyone changed the dependencies.
- The ranges. uv reads pyproject.toml. Its dependencies are ranges, such as rich>=15.0.0, which say which versions would be acceptable, not which one is installed.
- The lock. uv checks that uv.lock still matches pyproject.toml. If someone changed the dependencies since the last lock, it locks again first, so the lock always records one exact version of every package.
- The environment. uv checks that .venv holds what uv.lock lists, and installs anything missing or at another version. The very first run creates .venv as well.
- The command runs. Only now does uv run weather-notes, inside .venv. Every run passes these checks first, so nobody runs the project with a stale set of packages.
So you never activate anything and never run with a stale set of packages. uv remove rich takes a dependency out
again, uv sync makes .venv match uv.lock exactly (after you pull a teammate’s changes, for example), and
uv lock --upgrade-package rich moves one package to its newest allowed version.
pyproject.toml is not a uv invention: it is the standard file in which a Python project describes itself, written in
Tom's Obvious, Minimal Language, a text format for settings: key = value lines grouped under headings in square brackets.,
and any tool can read it. The standard library reads it with tomllib:
project/pyproject.toml
[project]
name = "weather-notes"
version = "0.1.0"
description = "Short notes about the weather"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"rich>=15.0.0",
]
[project.scripts]
weather-notes = "weather_notes:main"
[build-system]
requires = ["uv_build>=0.13.1,<0.14"]
build-backend = "uv_build" project/read_project.py
import tomllib
# tomllib, in the standard library since Python 3.11, reads TOML; it needs the file opened in binary mode.
with open("pyproject.toml", "rb") as file:
pyproject = tomllib.load(file)
project = pyproject["project"]
print("Project:", project["name"], project["version"])
print("Needs Python", project["requires-python"])
for requirement in project["dependencies"]:
print("Depends on", requirement)
for command, target in project["scripts"].items():
print("Adds the command", command, "which runs", target)
print("Built by", pyproject["build-system"]["build-backend"]) Output
Project: weather-notes 0.1.0 Needs Python >=3.14 Depends on rich>=15.0.0 Adds the command weather-notes which runs weather_notes:main Built by uv_build
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 read_project.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The [project] table holds the name, the version, the Python versions the project supports and its dependencies;
[project.scripts] adds the weather-notes command, and [build-system] names uv_build, the backend that turns the
project into a package. Since uv 0.12, uv init gives an application a build system by default.
Checkpoint
Check: A project managed by uv
A lock file turns ranges into one exact set of versions
A requirement such as rich>=15.0.0 is a version specifier: The part of a requirement that says which releases are acceptable, such as >=1.2,<2 or ~=1.2. Releases are compared number by number, and pip installs the newest one that meets every condition. More in “Finding and installing third-party packages”, a range: any
version from 15.0.0 on is acceptable. Which version you actually get is decided when the environment is built, and it
can change from one week to the next. This program follows a made-up package, tablekit, through its releases in
weeks 1, 5 and 9, and compares a fresh install from the range with an install from the lock:
# tablekit is a made-up package: the versions its index offers in three weeks.
releases = {
1: ["2.3.0"],
5: ["2.3.0", "2.4.0"],
9: ["2.3.0", "2.4.0", "3.0.0"],
}
locked = "2.3.0" # what uv.lock recorded in week 1
def as_numbers(version):
return tuple(int(part) for part in version.split("."))
def newest_allowed(available, minimum):
allowed = [v for v in available if as_numbers(v) >= as_numbers(minimum)]
return max(allowed, key=as_numbers)
print("Week tablekit>=2.3.0 gives uv.lock gives")
for week, available in releases.items():
print(f"{week:>4} {newest_allowed(available, '2.3.0'):<21} {locked}") Output
Week tablekit>=2.3.0 gives uv.lock gives 1 2.3.0 2.3.0 5 2.4.0 2.3.0 9 3.0.0 2.3.0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 range_or_lock.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
In week 9 a fresh install from the range gets 3.0.0, a new major version that may rename what your code calls, while
everyone who installs from the lock still gets 2.3.0. A lock file: A file that records every package an application installs, direct and indirect, with its exact version, where each file came from and the file's hash, so the next install gets the same files. pylock.toml is the standard format. records the exact version
of every package that was installed, including the dependencies of your dependencies: rich 15.0.0 itself needs
markdown-it-py and Pygments, so uv.lock lists them too, each with its exact version. Press Edit, add "3.1.0" to
week 9’s list and run it again: the range moves on, the lock does not.
uv keeps its lock in uv.lock, which belongs in version control and is never edited by hand (the .venv folder,
again, is not committed). A standard format for lock files, pylock.toml, is defined by PEP 751:
uv export --format pylock.toml writes one, and pip 26.2.1 has an experimental pip lock command that writes one too.
The requirements.txt that pip freeze writes is the older and simpler way to pin versions, and still the most
widely supported one.
Key idea
pyproject.toml says which versions would be acceptable; the lock file says which ones everyone installs, until
someone upgrades it on purpose and tests the result.
Checkpoint
Check: Ranges and lock files
A script can carry its own dependency list
Not everything is a project. A single file that you run now and then can say what it needs in a comment block at its top, called inline script metadata: A comment block in a Python script, opened by # /// script and closed by # ///, whose lines hold TOML such as requires-python and dependencies. Script runners read it; Python itself ignores it., a packaging standard since PEP 723:
# /// script
# requires-python = ">=3.14"
# dependencies = [
# "rich",
# ]
# ///
from rich import print
print("[bold green]Report ready[/bold green]") Output (exit status 1)
Printed as an error (standard error)
Traceback (most recent call last):
File "report.py", line 7, in <module>
from rich import print
ModuleNotFoundError: No module named 'rich'
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 report.py
Python itself ignores the block: to python3 it is only comments. That is why the run above, with plain CPython
3.14.8, stops at the import with ModuleNotFoundError. A tool that reads the block does better: uv run report.py
and pipx run report.py read it, build an environment that contains rich, and run the script there. pipx keeps that
environment and builds a fresh one when the list of dependencies changes.
The rules are short. The block starts with a line that is exactly # /// script and ends with a line that is exactly
# ///. Every line between them is a comment: take away the # and the space after it, and what is left is TOML, with
the keys requires-python (the Python versions the script runs on) and dependencies (a list of requirements, like
the one in pyproject.toml). A script may have only one script block; tools refuse a file with two. Step through a
program that reads the block of report.py the way such a tool does:
Program · inline/read_metadata.py
import tomllib
from pathlib import Path
src = Path("report.py").read_text()
inside = False
toml = []
for line in src.splitlines():
if line == "# /// script":
inside = True
elif line == "# ///":
inside = False
elif inside:
toml.append(line[2:])
meta = tomllib.loads("\n".join(toml))
print("requires-python:", meta["requires-python"])
print("dependencies:", meta["dependencies"]) Variables
- just taken by the loop
- changed in this step
- passed by the loop
- read next
- where the loop stopped
Printed so far
Nothing printed yetrequires-python: >=3.14 dependencies: ['rich']
Every step was recorded from a run with Python 3.14.8 on macOS 26 arm64.
The drawing shows the variables when the program ends, and the program's 49 steps are listed below.
Change the program and run it here. Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Every step as text
- Start. No line has run yet, so there are no variables. Python starts with line 1.
- Line 1.
import tomllibloadstomllib. - Line 2.
from pathlib import PathloadsPath. - Line 4.
src = Path("report.py").read_text()setssrcto'# /// script\n# requires-python = ">=3.14"\n# dependencies…. - Line 5.
inside = FalsesetsinsidetoFalse. - Line 6.
toml = []makestomla list of 0 items. - Line 7.
for line in src.splitlines():takes the item at position 0 ofsrc.splitlines():lineis'# /// script'. - Line 8.
line == "# /// script"is true (lineis'# /// script'), so the block underif line == "# /// script":runs. - Line 9.
inside = TruesetsinsidetoTrue(it wasFalse). The opening line is exactly '# /// script', so the lines after it belong to the block. - Line 7.
for line in src.splitlines():takes the item at position 1 ofsrc.splitlines():lineis'# requires-python = ">=3.14"'. - Line 8.
line == "# /// script"is false (lineis'# requires-python = ">=3.14"'), so Python tests the next condition. - Line 10.
line == "# ///"is false (lineis'# requires-python = ">=3.14"'), so Python tests the next condition. - Line 12.
insideis true (insideisTrue), so the block underelif inside:runs. - Line 13.
toml.append(line[2:])adds'requires-python = ">=3.14"'to the end oftoml. A line inside the block loses its first two characters, the # and the space. What is left is a line of TOML. - Line 7.
for line in src.splitlines():takes the item at position 2 ofsrc.splitlines():lineis'# dependencies = ['. - Line 8.
line == "# /// script"is false (lineis'# dependencies = ['), so Python tests the next condition. - Line 10.
line == "# ///"is false (lineis'# dependencies = ['), so Python tests the next condition. - Line 12.
insideis true (insideisTrue), so the block underelif inside:runs. - Line 13.
toml.append(line[2:])adds'dependencies = ['to the end oftoml. - Line 7.
for line in src.splitlines():takes the item at position 3 ofsrc.splitlines():lineis'# "rich",'. - Line 8.
line == "# /// script"is false (lineis'# "rich",'), so Python tests the next condition. - Line 10.
line == "# ///"is false (lineis'# "rich",'), so Python tests the next condition. - Line 12.
insideis true (insideisTrue), so the block underelif inside:runs. - Line 13.
toml.append(line[2:])adds' "rich",'to the end oftoml. - Line 7.
for line in src.splitlines():takes the item at position 4 ofsrc.splitlines():lineis'# ]'. - Line 8.
line == "# /// script"is false (lineis'# ]'), so Python tests the next condition. - Line 10.
line == "# ///"is false (lineis'# ]'), so Python tests the next condition. - Line 12.
insideis true (insideisTrue), so the block underelif inside:runs. - Line 13.
toml.append(line[2:])adds']'to the end oftoml. - Line 7.
for line in src.splitlines():takes the item at position 5 ofsrc.splitlines():lineis'# ///'. - Line 8.
line == "# /// script"is false (lineis'# ///'), so Python tests the next condition. - Line 10.
line == "# ///"is true (lineis'# ///'), so the block underelif line == "# ///":runs. - Line 11.
inside = FalsesetsinsidetoFalse(it wasTrue). Key point '# ///' closes the block. The lines after it are the program's own code, which is not part of the metadata. - Line 7.
for line in src.splitlines():takes the item at position 6 ofsrc.splitlines():lineis'from rich import print'. - Line 8.
line == "# /// script"is false (lineis'from rich import print'), so Python tests the next condition. - Line 10.
line == "# ///"is false (lineis'from rich import print'), so Python tests the next condition. - Line 12.
insideis false (insideisFalse), so Python skips the block underelif inside:. - Line 7.
for line in src.splitlines():takes the item at position 7 ofsrc.splitlines():lineis''. - Line 8.
line == "# /// script"is false (lineis''), so Python tests the next condition. - Line 10.
line == "# ///"is false (lineis''), so Python tests the next condition. - Line 12.
insideis false (insideisFalse), so Python skips the block underelif inside:. - Line 7.
for line in src.splitlines():takes the item at position 8 ofsrc.splitlines():lineis'print("[bold green]Report ready[/bold green]")'. - Line 8.
line == "# /// script"is false (lineis'print("[bold green]Report ready[/bold green]")'), so Python tests the next condition. - Line 10.
line == "# ///"is false (lineis'print("[bold green]Report ready[/bold green]")'), so Python tests the next condition. - Line 12.
insideis false (insideisFalse), so Python skips the block underelif inside:. - Line 7.
for line in src.splitlines():finds no item at position 9 ofsrc.splitlines()(it has 9 items), so the loop ends. - Line 15.
meta = tomllib.loads("\n".join(toml))setsmetato{'requires-python': '>=3.14', 'dependencies': ['rich']}. tomllib reads the collected lines as TOML, which gives a string for requires-python and a list for dependencies. - Line 16.
print("requires-python:", meta["requires-python"])printsrequires-python: >=3.14. - Line 17.
print("dependencies:", meta["dependencies"])printsdependencies: ['rich']. That was the last line, so the program ends.
The loop kept the four lines between the two markers, each without its first two characters, and tomllib turned
them into the string >=3.14 and the list ['rich'].
uv can also write the block for you: uv init --script report.py creates a new script with one, and
uv add --script report.py rich adds a dependency to it. uv expects a dependencies key even when the list is
empty, and a script is locked only when you ask, with uv lock --script report.py, which writes report.py.lock.
Checkpoint
Check: Inline script metadata
pipx and uv tool give each application its own environment
Some packages are applications that you run as commands, such as the linter Ruff, rather than libraries that your code
imports. Install those with pipx: A tool that installs each Python application into a virtual environment of its own and links only its commands into a folder on PATH, ~/.local/bin by default. uv tool does the same job. or with uv tool: each application gets a virtual
environment of its own, so its dependencies never clash with your projects or with each other, and only its commands
are put on your PATH. Step through one install:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- An environment for ruff alone. pipx makes a virtual environment for ruff alone and installs ruff and everything it needs into it. Nothing goes into your project or into the system's Python.
- Only the command is linked. pipx puts ruff's command into a folder on PATH, ~/.local/bin on Linux; pipx ensurepath adds that folder to PATH if it is missing.
- Run it from anywhere. Typing ruff check in any folder finds the command on PATH and runs ruff from its own environment, so the same ruff checks every project.
- Your project stays as it was. Your project's .venv is untouched. ruff cannot be imported there, and nothing ruff needs can clash with the versions your project uses.
- The same with uv. uv tool install ruff does the same job with uv, and uvx ruff check runs ruff once in a temporary environment without installing it at all.
| Task | With pipx | With uv |
|---|---|---|
Install a tool and put its commands on PATH |
pipx install ruff |
uv tool install ruff |
| Run a tool once, without installing it | pipx run ruff check |
uvx ruff check |
| Upgrade an installed tool | pipx upgrade ruff |
uv tool upgrade ruff |
Make sure the tools’ folder is on PATH |
pipx ensurepath |
uv tool update-shell |
pipx itself comes from a package manager (brew install pipx on macOS, sudo apt install pipx on Ubuntu,
scoop install pipx on Windows) or, on Windows, from py -m pip install --user pipx. uvx is short for
uv tool run. A tool installed this way is not importable by your own code: it lives in its own environment, which is
the point. A tool that has to import your project, such as pytest running your tests, belongs in the project instead,
as a development dependency (uv add --dev pytest), and runs with uv run pytest.
Checkpoint
Check: Tools in environments of their own
Choose the tool by the job
All three do the same steps underneath: a virtual environment, an install, a record of versions. They differ in how much of it they do for you and in what they are for:
| Criterion | venv + pip | uv | pipx |
|---|---|---|---|
| Comes with Python | Yes | No: install it once | No: install it once |
| Creates virtual environments | Yes, when you ask | Yes, by itself, for a project or a script | Yes, one for each application |
| Can install Python itself | No | Yes | Yes, for an application, if you ask |
| Records exact versions | pip freeze > requirements.txt | uv.lock, exported as pylock.toml on request | Not for projects: it manages applications |
| When to choose | Any computer, with nothing extra to install | Projects and scripts with dependencies | Command-line applications you use everywhere |
Read the table from the job you have. On a school computer where you may install nothing but Python, venv and
pip are what you have, and they are enough. A team project with dependencies and a test run on every change is uv’s
job, with uv.lock committed. A linter you use in every folder goes to pipx or uv tool, and a script you run once a
month carries its own block and runs with uv run.
None of these replaces knowing venv and pip: uv and pipx automate the same steps, and when one of them does
something unexpected, the previous lesson’s commands are how you look inside.
Checkpoint
Check: Choosing venv and pip, uv or pipx
Practise: read a script’s metadata
Write the part of a script runner that reads the block: return its TOML as a dict, None when there is no block, and
raise ValueError for a script with two. The sample tests run in your browser:
Exercise · Medium · Python
Read a script's inline metadata
Write parse_script_metadata(text). text is the source of a Python script. Return the TOML of its # /// script block, read with tomllib.loads(), as a dict, following these rules of the specification:
- The block starts with a line that is exactly
# /// scriptand ends with a line that is exactly# ///. - Every line in between is a comment: take away its first two characters,
#and a space. A line that is only#becomes an empty line. - A script without a block gives
None. - A script with two
scriptblocks is refused: raiseValueError.
Starter code · script_meta.py
import tomllib
def parse_script_metadata(text):
"""Return the TOML of the script's '# /// script' block as a dict, or None if it has none."""
return None The sample tests · test_script_meta.py
from script_meta import parse_script_metadata
BLOCK = """# /// script
# requires-python = ">=3.14"
# dependencies = [
# "rich",
# ]
# ///
print("Report ready")
"""
def test_one_block():
"""reads the TOML of the block"""
assert parse_script_metadata(BLOCK) == {"requires-python": ">=3.14", "dependencies": ["rich"]}
def test_no_block():
"""gives None for a script without a block"""
assert parse_script_metadata('print("Report ready")\n') is None
def test_two_blocks():
"""refuses a script with two script blocks"""
try:
parse_script_metadata(BLOCK + BLOCK)
except ValueError:
return
raise AssertionError("a script with two script blocks must raise ValueError")
def test_empty_dependencies():
"""reads a block whose dependencies list is empty"""
assert parse_script_metadata("# /// script\n# dependencies = []\n# ///\n") == {"dependencies": []}
def test_line_of_only_a_hash():
"""treats a line that is only # as an empty line of the block"""
text = '# /// script\n# requires-python = ">=3.14"\n#\n# dependencies = []\n# ///\n'
assert parse_script_metadata(text) == {"requires-python": ">=3.14", "dependencies": []} A hint
Loop over text.splitlines() with a variable that holds the lines of the block you are in, or None outside a block. The opening line starts a new list; # /// closes it and adds it to a list of finished blocks. After the loop, count the finished blocks: none gives None, one goes to tomllib.loads("\n".join(...)), more raise ValueError.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Finish with the questions below, which follow your answers: a miss brings a simpler question on the same idea, and each choice carries its own explanation:
Practice that adapts to you
Practise this lesson
10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): what is the difference between pyproject.toml and uv.lock?
pyproject.toml is the project’s own description, the file you edit: its name and version, the Python versions it
supports, and its dependencies as ranges such as rich>=15.0.0, which say what would be acceptable. uv.lock is
written by uv: the exact version of every package the project installs, its dependencies’ dependencies included, so
that every computer and every later install gets the same set. You change pyproject.toml yourself or with uv add,
and you never edit uv.lock by hand; both are committed. When you want newer versions, you upgrade the lock on purpose,
with uv lock --upgrade-package, and test the result before you commit it.
Two harder questions start from this lesson: a build that broke over a weekend without a code change, and a team whose tools are installed in the wrong places. Work out your first step before you open each one:
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
Your CI installs a project with pip install . from pyproject.toml, whose dependencies are ranges such as tablekit>=2.3. On Monday the build fails with AttributeError where your code calls tablekit, though nothing changed since Friday's green build. Diagnose it, and decide what to commit and run so it cannot happen again.
-
Fresher to mid
A new teammate installed Ruff, pytest and the project's code generator with pipx. Ruff works, but pytest cannot import the project, and the generator fails with ModuleNotFoundError for a package the project uses. Which tools belong in the project's environment and which in pipx or uv tool, and why?
Key takeaways
- uv manages a project from
pyproject.toml:uv init,uv addanduv run, and before every run it checks thatuv.lockand.venvare up to date. pyproject.tomlholds ranges of acceptable versions;uv.lockrecords one exact set, dependencies included, and is committed so that everyone installs the same packages.- Inline script metadata is a
# /// scriptcomment block withrequires-pythonanddependencies;uv runandpipx runread it, while plainpython3ignores it. pipx installanduv tool installgive each command-line application its own environment and put only its commands on yourPATH; tools that import your project belong in the project.- uv and pipx automate the steps of
venvandpip, which still work on every computer.
References
- Inline script metadata (specification) (Python Packaging Authority)
- PEP 723: Inline script metadata (Python Software Foundation)
- Writing your pyproject.toml (Python Packaging Authority)
- tomllib, parse TOML files (Python Software Foundation)
- PEP 751: A file format to record Python dependencies for installation reproducibility (Python Software Foundation)
- pip lock (pip 26.2.1 documentation) (Python Packaging Authority)
- uv: working on projects (Astral)
- uv: creating projects (Astral)
- uv: managing dependencies (Astral)
- uv: locking and syncing (Astral)
- uv: exporting a lockfile (Astral)
- uv: running scripts (Astral)
- uv: using tools (Astral)
- uv: installation (Astral)
- uv: license (Astral)
- uv: installing and managing Python (Astral)
- pipx documentation (Python Packaging Authority)
- pipx: install pipx (Python Packaging Authority)
- pipx: run scripts (Python Packaging Authority)
- pipx: standalone Python (Python Packaging Authority)
- rich 15.0.0 on PyPI: release metadata with its dependencies (Python Package Index)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress