Python Módulo 1 – Primeros pasos: instalar, ejecutar y leer Python
uv, pipx y metadatos en línea en scripts de Python
Crea un proyecto de Python con uv, ejecuta un script de un solo archivo que declara sus propias dependencias e instala herramientas aisladas con pipx o uv.
Qué vas a aprender
- Crear un proyecto y añadir dependencias con uv init, uv add y uv run
- Explicar qué anota uv.lock que pyproject.toml no anota
- Escribir un bloque de metadatos en línea que puedan leer uv run y pipx run
- Instalar herramientas de línea de órdenes aisladas con pipx o uv tool
- Elegir entre venv y pip, uv y pipx para cada tarea
Antes de empezar
Qué vas a aprender · Antes de empezar
Qué vas a aprender
- Crear un proyecto y añadir dependencias con uv init, uv add y uv run
- Explicar qué anota uv.lock que pyproject.toml no anota
- Escribir un bloque de metadatos en línea que puedan leer uv run y pipx run
- Instalar herramientas de línea de órdenes aisladas con pipx o uv tool
- Elegir entre venv y pip, uv y pipx para cada tarea
Antes de empezar
En esta página
Un proyecto con tres paquetes necesita cuatro pasos en cada computadora en la que se ejecuta: crear un entorno, instalar en él las versiones correctas, anotar esas versiones y repetirlo todo después de cada cambio. El venv y el pip de la lección anterior hacen cada paso, y los pasos se quedan anticuados el día en que alguien olvida uno. Dos herramientas hacen esta contabilidad por ti: uv gestiona un proyecto entero, y pipx instala aplicaciones de línea de órdenes, cada una en un entorno propio.
A continuación configuras un proyecto con uv y ves qué comprueba antes de cada ejecución, comparas un rango de versiones con un archivo de bloqueo, das a un solo script su propia lista de dependencias e instalas herramientas para que nunca choquen con tus proyectos. Ninguna de las dos herramientas forma parte de Python: las dos son programas de código abierto que instalas una vez.
uv mantiene el entorno de un proyecto al día con pyproject.toml
uv, creado por Astral y con licencia MIT o Apache-2.0, es un solo programa que hace el trabajo de varios: instala versiones de Python (uv python install 3.14), crea entornos virtuales, calcula qué versiones de tus dependencias encajan entre sí, las anota y ejecuta tu código. Instálalo con un gestor de paquetes (brew install uv en macOS, winget install --id=astral-sh.uv -e en Windows), con pipx install uv o con el script de instalación de la documentación de uv. Un script que descargas y ejecutas tiene el mismo acceso a tu computadora que tú, así que míralo antes; la documentación explica cómo.
Nota de versión
Esta lección sigue la documentación de uv 0.13 y pipx 1.18. Los dos cambian más deprisa que Python, así que si una orden se comporta de otra forma, compara uv --version o pipx --version con su documentación.
Tres órdenes ponen en marcha un proyecto:
uv init weather-notescrea la carpetaweather-notescon unpyproject.toml, un archivo.python-versionque nombra la versión de Python que hay que usar, unREADME.md, un.gitignore, un repositorio de Git y código inicial ensrc/weather_notes/__init__.py.uv add rich, en esa carpeta, escribe el requisitorich>=15.0.0, con la versión más nueva que encaja como la mínima permitida, enpyproject.toml. También crea.venvsi no existe, instala rich en él y anota las versiones exactas enuv.lock.uv run weather-notesejecuta la orden que configuróuv init, yuv run some_file.pyejecuta cualquier archivo, dentro del entorno del proyecto.
Lo que hace fiable a uv run es lo que comprueba antes de ejecutar nada. Recorre una ejecución:
El dibujo muestra la escena completa; sus 5 pasos aparecen abajo.
Todos los pasos en texto
- Escribes uv run. En la carpeta del proyecto escribes uv run weather-notes. No se activa nada, y no tienes que acordarte de si alguien cambió las dependencias.
- Los rangos. uv lee pyproject.toml. Sus dependencias son rangos, como rich>=15.0.0, que dicen qué versiones serían aceptables, no cuál está instalada.
- El bloqueo. uv comprueba que uv.lock sigue coincidiendo con pyproject.toml. Si alguien cambió las dependencias desde el último bloqueo, primero vuelve a bloquear, así que el bloqueo siempre anota una versión exacta de cada paquete.
- El entorno. uv comprueba que .venv contiene lo que enumera uv.lock, e instala lo que falte o esté en otra versión. La primera ejecución también crea .venv.
- Se ejecuta la orden. Solo ahora uv ejecuta weather-notes, dentro de .venv. Cada ejecución pasa antes estas comprobaciones, así que nadie ejecuta el proyecto con un conjunto de paquetes anticuado.
Así que nunca activas nada y nunca ejecutas con un conjunto de paquetes anticuado. uv remove rich vuelve a quitar una dependencia, uv sync hace que .venv coincida exactamente con uv.lock (después de traer los cambios de un compañero, por ejemplo), y uv lock --upgrade-package rich pasa un paquete a su versión permitida más nueva.
pyproject.toml no es un invento de uv: es el archivo estándar en el que un proyecto de Python se describe a sí mismo, escrito en Tom's Obvious, Minimal Language, un formato de texto para configuraciones: líneas clave = valor agrupadas bajo encabezados entre corchetes., y cualquier herramienta puede leerlo. La biblioteca estándar lo lee con 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"]) Salida
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
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 read_project.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
La tabla [project] contiene el nombre, la versión, las versiones de Python que admite el proyecto y sus dependencias; [project.scripts] añade la orden weather-notes, y [build-system] nombra uv_build, el backend que convierte el proyecto en un paquete. Desde uv 0.12, uv init da por defecto un sistema de construcción a una aplicación.
Punto de control
Comprueba: un proyecto gestionado con uv
Un archivo de bloqueo convierte rangos en un único conjunto exacto de versiones
Un requisito como rich>=15.0.0 es un especificador de versión: La parte de un requisito que dice qué versiones son aceptables, como >=1.2,<2 o ~=1.2. Las versiones se comparan número a número, y pip instala la más nueva que cumple todas las condiciones. Más en “Buscar e instalar paquetes de terceros” (en inglés), un rango: vale cualquier versión a partir de la 15.0.0. Qué versión obtienes de verdad se decide al construir el entorno, y puede cambiar de una semana a la siguiente. Este programa sigue un paquete inventado, tablekit, a través de sus versiones en las semanas 1, 5 y 9, y compara una instalación nueva desde el rango con una instalación desde el bloqueo:
# 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}") Salida
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
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 range_or_lock.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
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 guarda su bloqueo en uv.lock, que va en el control de versiones y nunca se edita a mano (la carpeta .venv, de nuevo, no se sube). Un formato estándar para los archivos de bloqueo, pylock.toml, lo define la PEP 751: uv export --format pylock.toml escribe uno, y pip 26.2.1 tiene una orden experimental pip lock que también lo escribe. El requirements.txt que escribe pip freeze es la forma más antigua y sencilla de fijar versiones, y todavía la que más se admite.
Idea clave
pyproject.toml dice qué versiones serían aceptables; el archivo de bloqueo dice cuáles instala todo el mundo, hasta que alguien lo actualiza a propósito y prueba el resultado.
Punto de control
Comprueba: rangos y archivos de bloqueo
Un script puede llevar su propia lista de dependencias
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]") Salida (código de salida 1)
Impreso como error (salida de errores estándar)
Traceback (most recent call last):
File "report.py", line 7, in <module>
from rich import print
ModuleNotFoundError: No module named 'rich'
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 report.py
El propio Python ignora el bloque: para python3 solo son comentarios. Por eso la ejecución de arriba, con CPython 3.14.8 sin más, se detiene en la importación con ModuleNotFoundError. Una herramienta que lee el bloque lo hace mejor: uv run report.py y pipx run report.py lo leen, construyen un entorno que contiene rich y ejecutan el script en él. pipx conserva ese entorno y construye uno nuevo cuando cambia la lista de dependencias.
Las reglas son pocas. El bloque empieza con una línea que es exactamente # /// script y termina con una línea que es exactamente # ///. Todas las líneas entre ellas son comentarios: quita el # y el espacio que lo sigue, y lo que queda es TOML, con las claves requires-python (las versiones de Python en las que funciona el script) y dependencies (una lista de requisitos, como la de pyproject.toml). Un script solo puede tener un bloque script; las herramientas rechazan un archivo con dos. Recorre un programa que lee el bloque de report.py como lo hace una de esas herramientas:
Programa · 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
- recién tomado por el bucle
- cambiado en este paso
- ya recorrido por el bucle
- se lee a continuación
- donde se detuvo el bucle
Impreso hasta ahora
Todavía no hay nada impresorequires-python: >=3.14 dependencies: ['rich']
Cada paso se registró de una ejecución con Python 3.14.8 en macOS 26 arm64.
El dibujo muestra las variables cuando termina el programa, y los 49 pasos del programa aparecen abajo.
Cambia el programa y ejecútalo aquí. Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
Todos los pasos en texto
- Inicio. Todavía no se ha ejecutado ninguna línea, así que no hay variables. Python empieza por la línea 1.
- Línea 1.
import tomllibcargatomllib. - Línea 2.
from pathlib import PathcargaPath. - Línea 4.
src = Path("report.py").read_text()asigna'# /// script\n# requires-python = ">=3.14"\n# dependencies…asrc. - Línea 5.
inside = FalseasignaFalseainside. - Línea 6.
toml = []hace detomluna lista de 0 elementos. - Línea 7.
for line in src.splitlines():toma el elemento de la posición 0 desrc.splitlines():linees'# /// script'. - Línea 8.
line == "# /// script"es verdadero (linees'# /// script'), así que se ejecuta el bloque deif line == "# /// script":. - Línea 9.
inside = TrueasignaTrueainside(antes eraFalse). La línea de apertura es exactamente '# /// script', así que las líneas que la siguen pertenecen al bloque. - Línea 7.
for line in src.splitlines():toma el elemento de la posición 1 desrc.splitlines():linees'# requires-python = ">=3.14"'. - Línea 8.
line == "# /// script"es falso (linees'# requires-python = ">=3.14"'), así que Python prueba la siguiente condición. - Línea 10.
line == "# ///"es falso (linees'# requires-python = ">=3.14"'), así que Python prueba la siguiente condición. - Línea 12.
insidees verdadero (insideesTrue), así que se ejecuta el bloque deelif inside:. - Línea 13.
toml.append(line[2:])añade'requires-python = ">=3.14"'al final detoml. Una línea dentro del bloque pierde sus dos primeros caracteres, el # y el espacio. Lo que queda es una línea de TOML. - Línea 7.
for line in src.splitlines():toma el elemento de la posición 2 desrc.splitlines():linees'# dependencies = ['. - Línea 8.
line == "# /// script"es falso (linees'# dependencies = ['), así que Python prueba la siguiente condición. - Línea 10.
line == "# ///"es falso (linees'# dependencies = ['), así que Python prueba la siguiente condición. - Línea 12.
insidees verdadero (insideesTrue), así que se ejecuta el bloque deelif inside:. - Línea 13.
toml.append(line[2:])añade'dependencies = ['al final detoml. - Línea 7.
for line in src.splitlines():toma el elemento de la posición 3 desrc.splitlines():linees'# "rich",'. - Línea 8.
line == "# /// script"es falso (linees'# "rich",'), así que Python prueba la siguiente condición. - Línea 10.
line == "# ///"es falso (linees'# "rich",'), así que Python prueba la siguiente condición. - Línea 12.
insidees verdadero (insideesTrue), así que se ejecuta el bloque deelif inside:. - Línea 13.
toml.append(line[2:])añade' "rich",'al final detoml. - Línea 7.
for line in src.splitlines():toma el elemento de la posición 4 desrc.splitlines():linees'# ]'. - Línea 8.
line == "# /// script"es falso (linees'# ]'), así que Python prueba la siguiente condición. - Línea 10.
line == "# ///"es falso (linees'# ]'), así que Python prueba la siguiente condición. - Línea 12.
insidees verdadero (insideesTrue), así que se ejecuta el bloque deelif inside:. - Línea 13.
toml.append(line[2:])añade']'al final detoml. - Línea 7.
for line in src.splitlines():toma el elemento de la posición 5 desrc.splitlines():linees'# ///'. - Línea 8.
line == "# /// script"es falso (linees'# ///'), así que Python prueba la siguiente condición. - Línea 10.
line == "# ///"es verdadero (linees'# ///'), así que se ejecuta el bloque deelif line == "# ///":. - Línea 11.
inside = FalseasignaFalseainside(antes eraTrue). Lo clave '# ///' cierra el bloque. Las líneas que lo siguen son el propio código del programa, que no forma parte de los metadatos. - Línea 7.
for line in src.splitlines():toma el elemento de la posición 6 desrc.splitlines():linees'from rich import print'. - Línea 8.
line == "# /// script"es falso (linees'from rich import print'), así que Python prueba la siguiente condición. - Línea 10.
line == "# ///"es falso (linees'from rich import print'), así que Python prueba la siguiente condición. - Línea 12.
insidees falso (insideesFalse), así que Python se salta el bloque deelif inside:. - Línea 7.
for line in src.splitlines():toma el elemento de la posición 7 desrc.splitlines():linees''. - Línea 8.
line == "# /// script"es falso (linees''), así que Python prueba la siguiente condición. - Línea 10.
line == "# ///"es falso (linees''), así que Python prueba la siguiente condición. - Línea 12.
insidees falso (insideesFalse), así que Python se salta el bloque deelif inside:. - Línea 7.
for line in src.splitlines():toma el elemento de la posición 8 desrc.splitlines():linees'print("[bold green]Report ready[/bold green]")'. - Línea 8.
line == "# /// script"es falso (linees'print("[bold green]Report ready[/bold green]")'), así que Python prueba la siguiente condición. - Línea 10.
line == "# ///"es falso (linees'print("[bold green]Report ready[/bold green]")'), así que Python prueba la siguiente condición. - Línea 12.
insidees falso (insideesFalse), así que Python se salta el bloque deelif inside:. - Línea 7.
for line in src.splitlines():no encuentra ningún elemento en la posición 9 desrc.splitlines()(tiene 9 elementos), así que el bucle termina. - Línea 15.
meta = tomllib.loads("\n".join(toml))asigna{'requires-python': '>=3.14', 'dependencies': ['rich']}ameta. tomllib lee las líneas reunidas como TOML, lo que da una cadena para requires-python y una lista para dependencies. - Línea 16.
print("requires-python:", meta["requires-python"])imprimerequires-python: >=3.14. - Línea 17.
print("dependencies:", meta["dependencies"])imprimedependencies: ['rich']. Era la última línea, así que el programa termina.
El bucle guardó las cuatro líneas entre las dos marcas, cada una sin sus dos primeros caracteres, y tomllib las convirtió en la cadena >=3.14 y la lista ['rich'].
uv también puede escribir el bloque por ti: uv init --script report.py crea un script nuevo con uno, y uv add --script report.py rich le añade una dependencia. uv espera una clave dependencies incluso cuando la lista está vacía, y un script solo se bloquea cuando lo pides, con uv lock --script report.py, que escribe report.py.lock.
Punto de control
Comprueba: metadatos en línea de los scripts
pipx y uv tool dan a cada aplicación su propio entorno
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:
El dibujo muestra la escena completa; sus 5 pasos aparecen abajo.
Todos los pasos en texto
- Un entorno solo para ruff. pipx crea un entorno virtual solo para ruff e instala en él ruff y todo lo que necesita. No va nada a tu proyecto ni al Python del sistema.
- Solo se enlaza la orden. pipx pone la orden de ruff en una carpeta de PATH, ~/.local/bin en Linux; pipx ensurepath añade esa carpeta a PATH si falta.
- Ejecútalo desde cualquier sitio. Escribir ruff check en cualquier carpeta encuentra la orden en PATH y ejecuta ruff desde su propio entorno, así que el mismo ruff revisa todos los proyectos.
- Tu proyecto se queda como estaba. El .venv de tu proyecto queda intacto. Ahí no se puede importar ruff, y nada de lo que necesita ruff puede chocar con las versiones que usa tu proyecto.
- Lo mismo con uv. uv tool install ruff hace el mismo trabajo con uv, y uvx ruff check ejecuta ruff una vez en un entorno temporal sin instalarlo.
| Tarea | Con pipx | Con uv |
|---|---|---|
Instalar una herramienta y poner sus órdenes en PATH |
pipx install ruff |
uv tool install ruff |
| Ejecutar una herramienta una vez, sin instalarla | pipx run ruff check |
uvx ruff check |
| Actualizar una herramienta instalada | pipx upgrade ruff |
uv tool upgrade ruff |
Asegurarte de que la carpeta de las herramientas está en PATH |
pipx ensurepath |
uv tool update-shell |
pipx viene de un gestor de paquetes (brew install pipx en macOS, sudo apt install pipx en Ubuntu, scoop install pipx en Windows) o, en Windows, de py -m pip install --user pipx. uvx es la forma corta de uv tool run. Una herramienta instalada así no la puede importar tu propio código: vive en su propio entorno, que es justo la idea. Una herramienta que tiene que importar tu proyecto, como pytest al ejecutar tus pruebas, va en el propio proyecto, como dependencia de desarrollo (uv add --dev pytest), y se ejecuta con uv run pytest.
Punto de control
Comprueba: herramientas en entornos propios
Elige la herramienta según el trabajo
Los tres hacen por debajo los mismos pasos: un entorno virtual, una instalación, un registro de versiones. Se diferencian en cuánto hacen por ti y en para qué sirven:
| Criterio | venv + pip | uv | pipx |
|---|---|---|---|
| Viene con Python | Sí | No: se instala una vez | No: se instala una vez |
| Crea entornos virtuales | Sí, cuando se lo pides | Sí, por sí solo, para un proyecto o un script | Sí, uno para cada aplicación |
| Puede instalar el propio Python | No | Sí | Sí, para una aplicación, si se lo pides |
| Anota las versiones exactas | pip freeze > requirements.txt | uv.lock, exportado como pylock.toml si se pide | No para proyectos: gestiona aplicaciones |
| Cuándo elegirlo | Cualquier computadora, sin instalar nada más | Proyectos y scripts con dependencias | Aplicaciones de línea de órdenes que usas en todas partes |
Lee la tabla desde el trabajo que tienes. En una computadora del colegio en la que no puedes instalar nada más que Python, venv y pip es lo que tienes, y bastan. Un proyecto de equipo con dependencias y pruebas en cada cambio es trabajo de uv, con uv.lock en el repositorio. Un linter que usas en todas las carpetas va a pipx o a uv tool, y un script que ejecutas una vez al mes lleva su propio bloque y se ejecuta con uv run.
Nada de esto sustituye a saber usar venv y pip: uv y pipx automatizan los mismos pasos, y cuando alguno hace algo inesperado, las órdenes de la lección anterior son la forma de mirar dentro.
Punto de control
Comprueba: elegir entre venv y pip, uv o pipx
Practica: leer los metadatos de un script
Escribe la parte de un ejecutor de scripts que lee el bloque: devuelve su TOML como un dict, None cuando no hay bloque, y lanza ValueError para un script con dos. Las pruebas de ejemplo se ejecutan en tu navegador:
Ejercicio · Intermedio · Python
Leer los metadatos en línea de un script
Escribe parse_script_metadata(text). text es el código fuente de un script de Python. Devuelve el TOML de su bloque # /// script, leído con tomllib.loads(), como un dict, siguiendo estas reglas de la especificación:
- El bloque empieza con una línea que es exactamente
# /// scripty termina con una línea que es exactamente# ///. - Todas las líneas intermedias son comentarios: quítales sus dos primeros caracteres,
#y un espacio. Una línea que solo es#se convierte en una línea vacía. - Un script sin bloque da
None. - Un script con dos bloques
scriptse rechaza: lanzaValueError.
Código inicial · 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 Las pruebas de ejemplo · 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": []} Una pista
Recorre text.splitlines() con una variable que contenga las líneas del bloque en el que estás, o None fuera de un bloque. La línea de apertura empieza una lista nueva; # /// la cierra y la añade a una lista de bloques terminados. Después del bucle, cuenta los bloques terminados: ninguno da None, uno va a tomllib.loads("\n".join(...)) y más lanzan ValueError.
Resultados de las pruebas de ejemplo
| Prueba | Resultado | Detalles |
|---|
Lo que imprimió tu código
Las pruebas de ejemplo se ejecutan en este dispositivo, en tu navegador (Pyodide): no se envía nada a mysmartcopilot.com. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes. Una comprobación en tu navegador es una orientación para ti, no la prueba de que el código sea correcto con cualquier entrada.
Termina con las preguntas de abajo, que siguen tus respuestas: un fallo trae una pregunta más sencilla sobre la misma idea, y cada opción lleva su propia explicación:
Práctica que se adapta a ti
Practica esta lección
10 preguntas sobre 5 ideas de esta lección. Cada respuesta se explica al momento, y lo que aciertas se guarda en este navegador.
Preguntas de entrevista
Calentamiento (júnior): ¿qué diferencia hay entre pyproject.toml y uv.lock?
pyproject.toml es la descripción propia del proyecto, el archivo que editas: su nombre y su versión, las versiones de Python que admite y sus dependencias como rangos, como rich>=15.0.0, que dicen qué sería aceptable. uv.lock lo escribe uv: la versión exacta de cada paquete que instala el proyecto, incluidas las dependencias de sus dependencias, para que todas las computadoras y todas las instalaciones posteriores obtengan el mismo conjunto. pyproject.toml lo cambias tú o con uv add, y uv.lock nunca se edita a mano; los dos se suben al repositorio. Cuando quieras versiones más nuevas, actualizas el bloqueo a propósito, con uv lock --upgrade-package, y pruebas el resultado antes de subirlo.
Dos preguntas más difíciles parten de esta lección: una compilación que se rompió durante un fin de semana sin ningún cambio de código, y un equipo con sus herramientas instaladas en sitios equivocados. Piensa tu primer paso antes de abrir cada una:
Las respuestas modelo se abren con cualquier pase: 79 ₹ por 1 semana (2,99 US$), un solo pago que nunca se renueva.
-
De júnior a nivel medio
Tu integración continua instala un proyecto con pip install . desde pyproject.toml, cuyas dependencias son rangos como tablekit>=2.3. El lunes la compilación falla con AttributeError donde tu código invoca tablekit, aunque nada ha cambiado desde la compilación correcta del viernes. Diagnostícalo y decide qué subir y ejecutar para que no pueda volver a pasar.
-
De júnior a nivel medio
Una compañera nueva instaló Ruff, pytest y el generador de código del proyecto con pipx. Ruff funciona, pero pytest no puede importar el proyecto, y el generador falla con ModuleNotFoundError por un paquete que usa el proyecto. ¿Qué herramientas van en el entorno del proyecto y cuáles en pipx o uv tool, y por qué?
Para recordar
- uv gestiona un proyecto desde
pyproject.toml:uv init,uv addyuv run, y antes de cada ejecución comprueba queuv.locky.venvestán al día. pyproject.tomlcontiene rangos de versiones aceptables;uv.lockanota un conjunto exacto, dependencias incluidas, y se sube al repositorio para que todo el mundo instale los mismos paquetes.- Los metadatos en línea de un script son un bloque de comentarios
# /// scriptconrequires-pythonydependencies;uv runypipx runlo leen, mientras que unpython3sin más lo ignora. pipx installyuv tool installdan a cada aplicación de línea de órdenes su propio entorno y ponen solo sus órdenes en tuPATH; las herramientas que importan tu proyecto van en el proyecto.- uv y pipx automatizan los pasos de
venvypip, que siguen funcionando en cualquier computadora.
Referencias
- Metadatos en línea de los scripts (especificación, en inglés) (Python Packaging Authority)
- PEP 723: metadatos en línea de los scripts (en inglés) (Python Software Foundation)
- Escribir tu pyproject.toml (en inglés) (Python Packaging Authority)
- tomllib, analizar archivos TOML (en inglés) (Python Software Foundation)
- PEP 751: un formato de archivo para anotar las dependencias de Python y reproducir instalaciones (en inglés) (Python Software Foundation)
- pip lock (documentación de pip 26.2.1, en inglés) (Python Packaging Authority)
- uv: trabajar con proyectos (en inglés) (Astral)
- uv: crear proyectos (en inglés) (Astral)
- uv: gestionar dependencias (en inglés) (Astral)
- uv: bloquear y sincronizar (en inglés) (Astral)
- uv: exportar un archivo de bloqueo (en inglés) (Astral)
- uv: ejecutar scripts (en inglés) (Astral)
- uv: usar herramientas (en inglés) (Astral)
- uv: instalación (en inglés) (Astral)
- uv: licencia (en inglés) (Astral)
- uv: instalar y gestionar Python (en inglés) (Astral)
- Documentación de pipx (en inglés) (Python Packaging Authority)
- pipx: instalar pipx (en inglés) (Python Packaging Authority)
- pipx: ejecutar scripts (en inglés) (Python Packaging Authority)
- pipx: Python independiente (en inglés) (Python Packaging Authority)
- rich 15.0.0 en PyPI: metadatos de la versión con sus dependencias (en inglés) (Python Package Index)
Herramientas relacionadas
Informar de un problema con esta lección (en inglés)
Solo se guarda en este navegador. Tu progreso en Learn (en inglés)