Skip to content

🖥️ Comandos — Python para Backend ​

Comandos de terminal, Python, pip, entornos virtuales, etc. que voy usando en el curso.

Comandos que cambian según el sistema operativo (crear/activar el venv). Styp usa macOS — esa es la columna que realmente corre en su terminal:

Qué hace🍎 macOS/Linux (el que usa Styp)🪟 Windows (referencia)
Crear el entorno virtualpython3 -m venv .venvpython -m venv .venv
Activarlosource .venv/bin/activate.\.venv\Scripts\activate
Ver la versión de Pythonpython3 --versionpython --version

📝 En macOS el binario se llama python3, no python a secas (no viene instalado por defecto) — ver [[python-command-not-found]]. En Windows sí existe python sin el "3". Una vez con el venv activado, en ambos sistemas ya se puede usar python (a secas) y pip sin el "3" — el venv se encarga de apuntar al binario correcto.

📝 ¿Por qué source y no correr .venv/bin/activate directo? activate es un script que modifica variables de entorno (PATH, VIRTUAL_ENV) para que la terminal use el Python del venv. Si lo corrés normal (./activate o .venv/bin/activate sin más), zsh abre un subshell para ejecutarlo: el subshell activa el venv y se cierra al toque, sin que tu terminal actual se entere del cambio (por eso nunca aparece (.venv) en el prompt). source (o su alias .) le dice al shell "ejecutá este script en mí mismo, no en un proceso aparte" — así los cambios quedan en tu sesión.

⚠️ Otro tropiezo típico: si hiciste cd dentro de la carpeta .venv (en vez de quedarte en la carpeta del proyecto/ejercicio), source .venv/bin/activate ya no encuentra la ruta — .venv no está dentro de sí misma. Verificá con pwd que estás un nivel arriba de .venv antes de activar — ver [[2026-08-14-activate-sin-source-no-funciona]].

El resto de comandos de pip son iguales en cualquier sistema operativo (una vez con el venv activado):

ComandoQué haceEjemplo
pip --versionMuestra la versión de pip del entorno virtual activo (falla con "command not found" si el venv no está activado — ver [[pip-command-not-found-venv-inactivo]])pip --version
pip install <paquete>Instala una librería en el venv activopip install fastapi "uvicorn[standard]"
python3 -m pip install <paquete>Igual que pip install, pero a prueba de errores: le pide a ese python3 puntual que use su propio pip (-m = ejecutar un módulo), en vez de confiar en cuál pip encuentre el PATH primeropython3 -m pip install alembic
pip show <paquete>Muestra la versión instalada de una librería (para no reinstalar de más)pip show fastapi
pip freeze > requirements.txtCongela todas las dependencias instaladas y sus versiones exactas en un archivopip freeze > requirements.txt
pip install -r requirements.txtInstala todas las dependencias listadas en el archivo (lo que corre alguien que clona el repo)pip install -r requirements.txt
python3 archivo.pyEjecuta un script de Pythonpython3 main.py

📝 No todo lo que se importa se instala con pip. Módulos como calendar, os, json, datetime, re, logging son parte de la librería estándar — vienen incluidos con Python, se usan con import directo, sin pip install de por medio. pip install python3-calendar da ERROR: Could not find a version that satisfies the requirement porque ese nombre (python3-algo) es la convención de paquetes de apt/Debian/Ubuntu (sudo apt install python3-algo), no de PyPI/pip — son dos ecosistemas de paquetes distintos.

python
import calendar
print(calendar.month(2026, 8))   # no requiere instalar nada

🧩 Microservicios — un venv por servicio (Clase 6) ​

Desde la Clase 6 cada microservicio (users_service, products_service, orders_service, ...) vive en su propia carpeta con su propio venv — no se comparte con los demás servicios ni con otros ejercicios del curso (ver [[curso-python-backend]] → "Servicio independiente"). Repetir esta secuencia dentro de la carpeta de cada servicio:

bash
cd <nombre_del_servicio>       # p.ej. users_service, products_service
python3 -m venv venv           # 1) crear el entorno (sin source)
source venv/bin/activate       # 2) activarlo (con source)

Dependencias instaladas en users_service (mismo comando sirve para cualquier otro servicio nuevo — ajustando paquetes según lo que use):

bash
pip install fastapi "uvicorn[standard]" pydantic sqlalchemy pydantic-settings
ComandoQué haceEjemplo
pip install fastapiFramework de API — ya visto en Clase 3pip install fastapi
pip install "uvicorn[standard]"Servidor ASGI que corre la app + extras de rendimiento (uvloop, websockets, etc.). Las comillas son obligatorias en zsh: sin ellas, [standard] se interpreta como patrón glob — ver [[2026-08-20-falta-pip-install-uvicorn-standard]]pip install fastapi "uvicorn[standard]"
pip install pydantic-settingsLee configuración (Settings) desde un .env — ver Clase 4 y Clase 6pip install pydantic-settings
pip install pytest httpxpytest (framework de testing) + httpx (cliente HTTP moderno, recomendado por FastAPI para testear endpoints) — para llenar la carpeta tests/ de cada servicio (Clase 6)pip install pytest httpx

orders_service y products_service (Clase 7) — el requirements.txt real (traído del repo oficial del curso) es el mismo para los dos, con rangos de versión en vez de pip freeze exacto:

bash
cd orders_service   # (o products_service)
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
fastapi>=0.116,<1.0
uvicorn[standard]>=0.35,<1.0
sqlalchemy>=2.0,<3.0
psycopg[binary]>=3.2,<4.0
pydantic-settings>=2.10,<3.0
alembic>=1.16,<2.0
email-validator>=2.2,<3.0
PyJWT>=2.10,<3.0
pwdlib[argon2]>=0.2,<1.0
httpx>=0.28,<1.0
pytest>=8.4,<9.0
Paquete nuevo (no estaba en Clase 6)Para qué lo usa
httpxEl cliente REST hacia products_service — ver app/clients/products_client.py en Clase 7 (solo lo usa orders_service)
alembicMigraciones de base de datos (existe alembic.ini + carpeta migrations/ en el proyecto)
psycopg[binary]Driver de Postgres v3 (URL postgresql+psycopg://...) — no psycopg2
PyJWT, pwdlib[argon2]Validar el JWT (get_current_user) que emite users_service — mismo JWT_SECRET en los 3 .env

users_service también se amplió en esta clase (antes, en la 6, era el más simple — sin base de datos). Mismo alembic, más dos dependencias específicas de autenticación:

bash
cd users_service
python3 -m venv venv
source venv/bin/activate
pip install fastapi "uvicorn[standard]" sqlalchemy psycopg2-binary pydantic-settings "pydantic[email]" alembic pyjwt "pwdlib[argon2,bcrypt]"
pip freeze > requirements.txt
Paquete nuevo (no estaba en Clase 6)Para qué lo usa users_service
pyjwtFirmar y decodificar JWT (jwt.encode/jwt.decode) — ver app/security.py en Clase 7
pwdlib[argon2,bcrypt]Hashear y verificar contraseñas (PasswordHash.recommended() → Argon2 por defecto) — nunca se guarda una contraseña en texto plano. Sucesora moderna de passlib (que dejó de mantenerse)
pydantic[email]Habilita EmailStr en schemas.py — ver [[2026-08-20-importerror-email-validator]]
psycopg2-binaryDriver de Postgres — mismo que en la Clase 4, ver 00-Notas/01-Comandos.md → Docker

⚠️ Errores típicos al armar el venv de un servicio nuevo — ya documentados:

  • source python3 -m venv venv → source no va con el comando de crear, solo con el de activar — ver [[2026-08-20-source-antes-de-python3-venv]].
  • venv\Scripts\activate (sintaxis de Windows) en zsh → usar venv/bin/activate — ver [[2026-08-14-activate-sin-source-no-funciona]].
  • "uvicorn[standard]" sin pip install adelante → no es un comando, es un argumento — ver [[2026-08-20-falta-pip-install-uvicorn-standard]].
  • ImportError: email-validator is not installed al levantar el servidor (si el schemas.py del servicio usa EmailStr) → falta el extra pydantic[email] — ver [[2026-08-20-importerror-email-validator]].

▶️ Levantar un microservicio (con el venv activado) ​

ComandoQué haceEjemplo
uvicorn app.main:app --port <puerto>Arranca el servidor ASGI, apuntando a la variable app de app/main.pyuvicorn app.main:app --port 8001
uvicorn app.main:app --port <puerto> --reloadIgual, pero reinicia solo al guardar un cambio en el código — útil mientras se desarrollauvicorn app.main:app --port 8001 --reload
python3 -m uvicorn app.main:app --reload --port <puerto>Mismo resultado que el de arriba, pero pidiéndoselo a ese python3 puntual (-m) en vez de confiar en qué uvicorn encuentre el PATH — mismo motivo que python3 -m pip install más arriba. Así lo corrió el profe (con python a secas, por estar en Windows)python3 -m uvicorn app.main:app --reload --port 8001

Cada microservicio usa su propio puerto (definido en su app/config.py — ver Clase 6), así los dos corren al mismo tiempo sin chocar:

bash
# terminal 1 — dentro de users_service, venv activado
uvicorn app.main:app --port 8001 --reload

# terminal 2 — dentro de products_service, venv activado
uvicorn app.main:app --port 8002 --reload

Con el servidor corriendo, la documentación interactiva (Swagger, vista en Clase 3) queda en http://127.0.0.1:<puerto>/docs.

🐳 Docker ​

Desde la Clase 4 corremos PostgreSQL en un contenedor en vez de instalarlo directo en el Mac — estos son los comandos base para manejarlo.

ComandoQué haceEjemplo
docker --versionMuestra la versión de Docker instaladadocker --version
docker psLista los contenedores corriendo ahora mismodocker ps
docker ps -aLista todos los contenedores, también los detenidos (-a = all)docker ps -a
docker imagesLista las imágenes descargadas en tu Macdocker images
docker run <imagen>Crea y arranca un contenedor nuevo a partir de una imagenver ejemplo completo abajo
docker stop <nombre|id>Detiene un contenedor corriendo (sin borrarlo)docker stop n8n-local
docker start <nombre|id>Vuelve a arrancar un contenedor ya creado (que quedó detenido)docker start bd_test_backend
docker logs <nombre|id>Muestra la salida/errores de un contenedor — el primer lugar donde mirar si algo no arrancadocker logs bd_test_backend
docker rm <nombre|id>Borra un contenedor detenido (no lo corras sobre uno que quieras conservar)docker rm mi_contenedor_viejo

Ejemplo real usado en la Clase 4 — levantar Postgres:

bash
docker run -d \
  --name bd_test_backend \
  -e POSTGRES_USER=postgres \
  -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=curso_backend \
  -p 5432:5432 \
  postgres:16-alpine
FlagSignificado
-ddetached — corre el contenedor "de fondo", sin bloquear la terminal
--name <nombre>Le pone un nombre fácil de recordar (si no, Docker le asigna uno random tipo stupefied_rosalind)
-e VARIABLE=valorDefine una variable de entorno dentro del contenedor (acá, usuario/password/db de Postgres)
-p <host>:<contenedor>Mapea un puerto: "lo que llegue a localhost:5432 de mi Mac, redirigilo al puerto 5432 de adentro del contenedor"
postgres:16-alpineLa imagen a usar, con su tag de versión (16-alpine = Postgres 16 sobre Alpine Linux, una base liviana)

⚠️ Error típico si te olvidás el -e POSTGRES_PASSWORD: el contenedor arranca y se cae solo (docker ps -a lo muestra como Exited (1)), con este mensaje en docker logs: "Database is uninitialized and superuser password is not specified". Postgres exige una password para el usuario superadministrador antes de inicializar.

🗄️ Clase 7 — una base de datos nueva por microservicio (mismo contenedor) ​

orders_service, products_service y users_service necesitan persistir en Postgres, pero no deben compartir base entre sí ni con curso_backend (la de la Clase 4) — mismo principio "database per service" de siempre. En vez de levantar un contenedor por servicio, se crearon tres bases nuevas dentro del bd_test_backend que ya estaba corriendo — con el nombre exacto del sql/create_databases.sql del repo oficial:

bash
docker exec bd_test_backend psql -U postgres -c "CREATE DATABASE orderflow_users_db;"
docker exec bd_test_backend psql -U postgres -c "CREATE DATABASE orderflow_products_db;"
docker exec bd_test_backend psql -U postgres -c "CREATE DATABASE orderflow_orders_db;"

# verificar
docker exec bd_test_backend psql -U postgres -c "\l"
Comando/flagQué hace
docker exec <contenedor> <comando>Corre un comando DENTRO del contenedor ya corriendo (no crea uno nuevo)
psql -U postgres -c "SQL..."Se conecta como el usuario postgres y ejecuta una sentencia SQL suelta, sin entrar a una sesión interactiva
\lComando de psql (no SQL) que lista todas las bases de datos del servidor

Cada servicio apunta a la suya en su .env (no versionado — mismo usuario/password/puerto de siempre, solo cambia el nombre de la base al final). Nótese también el driver: postgresql+psycopg:// (psycopg v3), no postgresql:// a secas (que usaría psycopg2):

bash
# users_service/.env
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/orderflow_users_db

# products_service/.env
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/orderflow_products_db

# orders_service/.env
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/orderflow_orders_db

💡 Un solo contenedor, varias bases es perfectamente válido para "database per service" — lo que importa es que cada servicio tenga su propia base (su propio namespace de tablas), no que cada una viva en un contenedor/servidor físico distinto. En producción sí sería común separarlas más (por escalado, backups independientes, etc.), pero para desarrollo local un solo Postgres con varias bases alcanza y sobra.

⚠️ Falta el driver: si al conectar sale ModuleNotFoundError: No module named 'psycopg', falta instalarlo en el venv del servicio (pip install "psycopg[binary]", ya incluido en el requirements.txt real de arriba).

📝 Corrección de nombres: al principio de la clase se habían creado orders_db y users_db (nombres inventados, antes de tener el .env.example real) — ya se borraron y se reemplazaron por los nombres correctos de arriba.

🧬 Alembic — crear las tablas reales (mismo patrón de la Clase 4) ​

Con la base de datos ya creada (arriba) pero vacía, Alembic es quien crea las tablas a partir de los modelos (User, Order, ...). Mismos 3 comandos que en la Clase 4, corridos dentro de la carpeta del servicio con el venv activado:

bash
alembic init migrations              # una sola vez por servicio
# → editar migrations/env.py a mano (importar Base/modelos, fijar sqlalchemy.url)
alembic revision --autogenerate -m "crea tabla users"   # genera el script
alembic upgrade head                                    # lo aplica de verdad
ComandoQué hace
alembic init migrationsCrea la carpeta migrations/ con env.py, script.py.mako y versions/ — y el archivo alembic.ini en la raíz del servicio
alembic revision --autogenerate -m "mensaje"Compara target_metadata (los modelos) contra la base real y escribe un script de Python con la diferencia — todavía no toca la base
alembic upgrade headEjecuta ese script de verdad contra Postgres — acá se crea la tabla

💡 Detalle de esta clase (distinto a la Clase 4): acá los imports en env.py sí llevan el prefijo app. (from app.db import Base, from app.models import User) porque este proyecto usa app como paquete real — al revés del proyecto de la Clase 4, que evitaba ese prefijo. Ver [[2026-08-11-modulenotfounderror-app-prefix]] para el porqué de esa diferencia.

🧪 Tip de entrevista: ¿diferencia entre docker stop y docker rm? stop pausa el contenedor (los datos quedan, se puede volver a arrancar con docker start); rm lo borra — si el contenedor no usaba un volumen externo, los datos de adentro se pierden para siempre.

🐘 psql dentro del contenedor — consultar/insertar sin salir de la terminal ​

docker exec corre un comando adentro de un contenedor que ya está corriendo (no crea uno nuevo, a diferencia de docker run). Sirve para meterse a la base de Postgres del curso sin instalar psql en el Mac:

ComandoQué haceEjemplo
docker exec -it <contenedor> psql -U <user> -d <db>Abre una sesión interactiva de psql — queda ahí escribiendo SQL hasta que salís con \qdocker exec -it bd_test_backend psql -U postgres -d curso_backend
docker exec <contenedor> psql -U <user> -d <db> -c "<SQL>"Corre una sola consulta y vuelve directo a tu terminal — sin quedar "adentro"docker exec bd_test_backend psql -U postgres -d curso_backend -c "SELECT COUNT(*) FROM tickets;"

⚠️ -it vs. sin -it: -it (interactive + tty) es para cuando vos vas a tipear en la sesión de psql. Si el comando lo corre un script/otro proceso (no una persona escribiendo), sacá el -it — con él puesto sin una terminal real detrás tira the input device is not a TTY.

Ejemplos reales usados en la Clase 4 — ver tablas, contar filas, insertar datos de prueba (útil cuando un POST /tickets/ da 500 por una FK a un user/category que todavía no existe — ver [[500-foreign-key-inexistente-sin-datos-previos]]):

bash
# Ver qué tablas existen
docker exec bd_test_backend psql -U postgres -d curso_backend -c "\dt"

# Contar filas de una tabla
docker exec bd_test_backend psql -U postgres -d curso_backend -c "SELECT COUNT(*) FROM tickets;"

# Insertar datos de prueba (varias -c seguidas = varias sentencias, en orden)
docker exec bd_test_backend psql -U postgres -d curso_backend -c \
  "INSERT INTO users (name, email) VALUES ('Styp Canto', 'styp@example.com');" \
  -c "INSERT INTO categories (name) VALUES ('Infraestructura');"

🏗️ Clase 10 — construir tu propia imagen (docker build) ​

Hasta la Clase 7 solo usábamos una imagen ya hecha (postgres:16-alpine, bajada de Docker Hub). En la Clase 10 pasamos a construir la nuestra propia a partir de un Dockerfile (ver Clase 10, sección 4) — el comando es:

bash
cd 02-Ejercicios/Clase-10/orders_service
docker build -t orders_service:1.0 .
Parte del comandoQué es
docker buildSubcomando que construye una imagen leyendo un Dockerfile (no arranca nada — a diferencia de docker run).
-t orders_service:1.0tag — le pone nombre:versión a la imagen resultante. Sin -t, la imagen queda sin nombre (solo un ID hash difícil de referenciar).
orders_serviceEl nombre que le elegís a la imagen — luego es lo que usás en docker run <nombre>:<tag>.
1.0La versión/tag — convención libre (1.0, latest, dev...). Si no ponés :tag, Docker asume :latest.
. (el punto final)El contexto de build: la carpeta que Docker empaqueta y le manda al motor de build — de ahí salen los archivos que referencian los COPY del Dockerfile. Por eso hay que pararse dentro de la carpeta del servicio antes de correrlo.

💡 Docker busca un archivo llamado exactamente Dockerfile (sin extensión) dentro del contexto (.). Si tu archivo tiene otro nombre o está en otra carpeta, hace falta el flag -f ruta/al/Dockerfile.

Para correr la imagen ya construida (distinto al docker run de Postgres de más arriba, porque acá las variables de entorno vienen de un .env, no de -e sueltos):

bash
docker run -d --name orders-service \
  -p 8001:8000 \
  --add-host=host.docker.internal:host-gateway \
  --env-file .env \
  orders_service:1.0
FlagSignificado
-ddetached — igual que con Postgres, corre de fondo.
--name orders-serviceNombre fijo del contenedor (no de la imagen — la imagen es orders_service:1.0, el contenedor es orders-service).
-p 8001:8000Mapea localhost:8001 (tu Mac) → 8000 (adentro del contenedor, donde escucha Uvicorn).
--add-host=host.docker.internal:host-gatewayFuerza que host.docker.internal resuelva a la IP del host. Sin esto, en algunos setups de Docker Desktop ese nombre no resolvía solo (error real: failed to resolve host 'host.docker.internal') — con el flag, resuelve siempre.
--env-file .envCarga todas las variables de un archivo de una — alternativa a poner varios -e VARIABLE=valor sueltos como en el ejemplo de Postgres.
orders_service:1.0La imagen (con su tag) de la que se crea el contenedor.

⚠️ host.docker.internal en vez de localhost: dentro del .env que usa este docker run, DATABASE_URL y PRODUCTS_URL apuntan a host.docker.internal y no a localhost/127.0.0.1. Desde dentro de un contenedor, localhost es el propio contenedor — para llegar a Postgres o a otro servicio corriendo en tu Mac hace falta ese nombre especial que resuelve Docker Desktop. Ver Clase 10.

🐛 Error real que salió sin el --add-host: conectando desde adentro del contenedor (psycopg.connect(...)) tiraba failed to resolve host 'host.docker.internal': Name or service not known — docker exec orders-service cat /etc/hosts confirmó que ese nombre ni aparecía. Recrear el contenedor con --add-host=host.docker.internal:host-gateway lo arregló al toque (verificado conectando de nuevo: CONEXION OK).

Verificar y limpiar:

bash
docker ps                          # confirma que "orders-service" está Up
docker logs orders-service         # log de arranque de Uvicorn
curl http://localhost:8001/health  # {"status":"ok", ...}

docker stop orders-service && docker rm orders-service   # apagar y borrar

🌐 Sitio de apuntes (VitePress) ​

Los .md de este repo también se ven como sitio navegable (sidebar automático, buscador, modo oscuro) — ver README.md (en la raíz del repo) para el detalle completo.

ComandoQué hace
npm installInstala las dependencias del sitio (Vitepress, etc.) — una sola vez.
npm run docs:devLevanta el sitio local en http://localhost:5173, con recarga automática al guardar un .md.
npm run docs:buildCompila el sitio a estático — sirve para confirmar que no hay errores (imágenes rotas, {{ }} sin escapar, etc.) antes de dar por terminada una clase.
npm run deployCompila y publica por rsync al subdominio en Hostinger (python-backend.stypcanto.com).
bash
cd /Users/styp/Documents/Cursos/Python_para_backend
npm run docs:dev

💡 Corre en segundo plano mientras documentás — cada vez que se guarda un archivo (Clase-10.md, una imagen nueva en public/), la página recarga sola. Para pararlo: Ctrl+C en la terminal donde corre (o, si lo lanzó Claude como tarea de fondo, pedirle que lo detenga).