Apariencia
🖥️ 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 virtual | python3 -m venv .venv | python -m venv .venv |
| Activarlo | source .venv/bin/activate | .\.venv\Scripts\activate |
| Ver la versión de Python | python3 --version | python --version |
📝 En macOS el binario se llama
python3, nopythona secas (no viene instalado por defecto) — ver [[python-command-not-found]]. En Windows sí existepythonsin el "3". Una vez con el venv activado, en ambos sistemas ya se puede usarpython(a secas) ypipsin el "3" — el venv se encarga de apuntar al binario correcto.
📝 ¿Por qué
sourcey no correr.venv/bin/activatedirecto?activatees un script que modifica variables de entorno (PATH,VIRTUAL_ENV) para que la terminal use el Python del venv. Si lo corrés normal (./activateo.venv/bin/activatesin 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
cddentro de la carpeta.venv(en vez de quedarte en la carpeta del proyecto/ejercicio),source .venv/bin/activateya no encuentra la ruta —.venvno está dentro de sí misma. Verificá conpwdque estás un nivel arriba de.venvantes 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):
| Comando | Qué hace | Ejemplo |
|---|---|---|
pip --version | Muestra 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 activo | pip 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 primero | python3 -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.txt | Congela todas las dependencias instaladas y sus versiones exactas en un archivo | pip freeze > requirements.txt |
pip install -r requirements.txt | Instala todas las dependencias listadas en el archivo (lo que corre alguien que clona el repo) | pip install -r requirements.txt |
python3 archivo.py | Ejecuta un script de Python | python3 main.py |
📝 No todo lo que se importa se instala con
pip. Módulos comocalendar,os,json,datetime,re,loggingson parte de la librería estándar — vienen incluidos con Python, se usan conimportdirecto, sinpip installde por medio.pip install python3-calendardaERROR: Could not find a version that satisfies the requirementporque 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.pythonimport 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| Comando | Qué hace | Ejemplo |
|---|---|---|
pip install fastapi | Framework de API — ya visto en Clase 3 | pip 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-settings | Lee configuración (Settings) desde un .env — ver Clase 4 y Clase 6 | pip install pydantic-settings |
pip install pytest httpx | pytest (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.txtfastapi>=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 |
|---|---|
httpx | El cliente REST hacia products_service — ver app/clients/products_client.py en Clase 7 (solo lo usa orders_service) |
alembic | Migraciones 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 |
|---|---|
pyjwt | Firmar 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-binary | Driver 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→sourceno 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 → usarvenv/bin/activate— ver [[2026-08-14-activate-sin-source-no-funciona]]."uvicorn[standard]"sinpip installadelante → no es un comando, es un argumento — ver [[2026-08-20-falta-pip-install-uvicorn-standard]].ImportError: email-validator is not installedal levantar el servidor (si elschemas.pydel servicio usaEmailStr) → falta el extrapydantic[email]— ver [[2026-08-20-importerror-email-validator]].
▶️ Levantar un microservicio (con el venv activado)
| Comando | Qué hace | Ejemplo |
|---|---|---|
uvicorn app.main:app --port <puerto> | Arranca el servidor ASGI, apuntando a la variable app de app/main.py | uvicorn app.main:app --port 8001 |
uvicorn app.main:app --port <puerto> --reload | Igual, pero reinicia solo al guardar un cambio en el código — útil mientras se desarrolla | uvicorn 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 --reloadCon 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.
| Comando | Qué hace | Ejemplo |
|---|---|---|
docker --version | Muestra la versión de Docker instalada | docker --version |
docker ps | Lista los contenedores corriendo ahora mismo | docker ps |
docker ps -a | Lista todos los contenedores, también los detenidos (-a = all) | docker ps -a |
docker images | Lista las imágenes descargadas en tu Mac | docker images |
docker run <imagen> | Crea y arranca un contenedor nuevo a partir de una imagen | ver 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 arranca | docker 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| Flag | Significado |
|---|---|
-d | detached — 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=valor | Define 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-alpine | La 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 -alo muestra comoExited (1)), con este mensaje endocker 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/flag | Qué 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 |
\l | Comando 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 elrequirements.txtreal de arriba).
📝 Corrección de nombres: al principio de la clase se habían creado
orders_dbyusers_db(nombres inventados, antes de tener el.env.examplereal) — 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| Comando | Qué hace |
|---|---|
alembic init migrations | Crea 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 head | Ejecuta 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.pysí llevan el prefijoapp.(from app.db import Base,from app.models import User) porque este proyecto usaappcomo 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 stopydocker rm?stoppausa el contenedor (los datos quedan, se puede volver a arrancar condocker start);rmlo 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:
| Comando | Qué hace | Ejemplo |
|---|---|---|
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 \q | docker 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;" |
⚠️
-itvs. sin-it:-it(interactive + tty) es para cuando vos vas a tipear en la sesión depsql. 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 tirathe 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 comando | Qué es |
|---|---|
docker build | Subcomando que construye una imagen leyendo un Dockerfile (no arranca nada — a diferencia de docker run). |
-t orders_service:1.0 | tag — 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_service | El nombre que le elegís a la imagen — luego es lo que usás en docker run <nombre>:<tag>. |
1.0 | La 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| Flag | Significado |
|---|---|
-d | detached — igual que con Postgres, corre de fondo. |
--name orders-service | Nombre fijo del contenedor (no de la imagen — la imagen es orders_service:1.0, el contenedor es orders-service). |
-p 8001:8000 | Mapea localhost:8001 (tu Mac) → 8000 (adentro del contenedor, donde escucha Uvicorn). |
--add-host=host.docker.internal:host-gateway | Fuerza 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 .env | Carga 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.0 | La imagen (con su tag) de la que se crea el contenedor. |
⚠️
host.docker.internalen vez delocalhost: dentro del.envque usa estedocker run,DATABASE_URLyPRODUCTS_URLapuntan ahost.docker.internaly no alocalhost/127.0.0.1. Desde dentro de un contenedor,localhostes 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(...)) tirabafailed to resolve host 'host.docker.internal': Name or service not known—docker exec orders-service cat /etc/hostsconfirmó que ese nombre ni aparecía. Recrear el contenedor con--add-host=host.docker.internal:host-gatewaylo 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.
| Comando | Qué hace |
|---|---|
npm install | Instala las dependencias del sitio (Vitepress, etc.) — una sola vez. |
npm run docs:dev | Levanta el sitio local en http://localhost:5173, con recarga automática al guardar un .md. |
npm run docs:build | Compila 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 deploy | Compila 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 enpublic/), la página recarga sola. Para pararlo:Ctrl+Cen la terminal donde corre (o, si lo lanzó Claude como tarea de fondo, pedirle que lo detenga).