docker, test, db, wait_for_db, db conf, core app
This commit is contained in:
parent
f8e0559424
commit
ca9ca658c7
3
.github/workflows/checks.yml
vendored
3
.github/workflows/checks.yml
vendored
@ -16,6 +16,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Test
|
||||
run: docker compose run --rm app sh -c "python manage.py test"
|
||||
run: docker compose run --rm app sh -c "python manage.py wait_for_db &&
|
||||
python manage.py test"
|
||||
- name: Lint
|
||||
run: docker compose run --rm app sh -c "flake8"
|
||||
|
@ -10,7 +10,12 @@ WORKDIR /app
|
||||
EXPOSE 8000
|
||||
|
||||
ARG DEV=false
|
||||
|
||||
# apt install -y python3-dev libpq-dev python3-psycopg2 && \
|
||||
RUN python -m venv /py && \
|
||||
apt update && \
|
||||
apt install -y postgresql-client python3-dev libpq-dev gcc python3-psycopg2 && \
|
||||
apt clean && \
|
||||
/py/bin/pip install --upgrade pip && \
|
||||
/py/bin/pip install -r /tmp/requirements.txt && \
|
||||
if [ $DEV = "true" ]; \
|
||||
|
585
README.md
585
README.md
@ -134,7 +134,7 @@ ej. `docker-compose run --rm app sh -c "python manage.py collectstatic"`
|
||||
- `docker-compose` Ejecuta un comando de Docker Compose
|
||||
- `run` comienza un contenedor específico definido en la configuración
|
||||
- `--rm` remueve el contenedor
|
||||
- `app` es el nombre del servicio/applicación
|
||||
- `app` es el nombre del servicio/aplicación
|
||||
- `sh -c` pasa una orden a la shell del container
|
||||
- `"python manage.py ..."` comando a correr dentro del contenedor
|
||||
|
||||
@ -163,7 +163,7 @@ docker-compose build
|
||||
### Testing
|
||||
|
||||
- Django test suite
|
||||
- Configurar test por cada applicación Django
|
||||
- Configurar test por cada aplicación Django
|
||||
- Correr a travez de docker-compose `docker-compose run --rm app sh -c "python
|
||||
manage.py test"`
|
||||
|
||||
@ -213,7 +213,7 @@ TG ==> JB ==> RS
|
||||
- Se cobra por minutos de uso
|
||||
- 2.000 minutos *gratis*
|
||||
|
||||
#### Configranción GitHub Actions
|
||||
#### Configuranción GitHub Actions
|
||||
|
||||
- Creación de archivo [`checks.yml`](./.github/workflows/checks.yml)
|
||||
- Set Trigger
|
||||
@ -226,3 +226,582 @@ TG ==> JB ==> RS
|
||||
- GitHub Actions usan IP compartida, la limitación aplica para todos los usuarios
|
||||
al autenticar con DockerHub se obtienen 200/6h de uso exclusivo
|
||||
|
||||
## Django Tests
|
||||
|
||||
- Basado en la biblioteca **unittest**
|
||||
- Caracteristicas añadidas de Django
|
||||
- Cliente del pruebas *dummy web browser*
|
||||
- Simula autenticación
|
||||
- Base de datos temporal
|
||||
- Caracteristicas añadidas de REST Framework
|
||||
- Cliente de pruebas de la API
|
||||
|
||||
### ¿Donde van los tests?
|
||||
|
||||
- `test.py` por aplicación
|
||||
- O crear un subdirectorio `tests/` para dividir las pruebas
|
||||
- Recordar
|
||||
- Solo usar `tests.py` o directorio `tests/`, no ambos
|
||||
- Los moduloes de prueba deben comenzar con `test_`
|
||||
- Los directorios de pruebas deben contener el archivo `__init__.py`
|
||||
|
||||
```txt
|
||||
demo_code
|
||||
├── app_one
|
||||
│ ├── __init__.py
|
||||
│ ├── admin.py
|
||||
│ ├── apps.py
|
||||
│ ├── models.py
|
||||
│ ├── tests.py
|
||||
│ ├── views.py
|
||||
│ └── admin.py
|
||||
└── app_two
|
||||
├── tests
|
||||
│ ├── __init__.py
|
||||
│ ├── test_module_one.py
|
||||
│ └── test_module_two.py
|
||||
├── __init__.py
|
||||
├── admin.py
|
||||
├── apps.py
|
||||
├── models.py
|
||||
├── views.py
|
||||
└── admin.py
|
||||
```
|
||||
|
||||
### Test DB
|
||||
|
||||
- Codigo de pruebas que usa la base de datos
|
||||
- Base de datos específica para pruebas
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'dark','themeVariables': {'clusterBkg': '#2b2f38'}, 'flowchart': {'curve': 'natural'}}}%%
|
||||
flowchart
|
||||
subgraph " "
|
||||
direction LR
|
||||
RT["<b>Runs Tests</b>"]
|
||||
CD["<b>Clears data</b>"]
|
||||
end
|
||||
RT ==> CD ==> RT
|
||||
```
|
||||
|
||||
- Por defecto, esto ocurre para cada test
|
||||
|
||||
### Clases de Test
|
||||
|
||||
- `SimpleTestCase`
|
||||
- Sin integración con BD
|
||||
- Util si no se require una BD para la lógica a probar
|
||||
- Ahorra tiempo de ejecución
|
||||
- `TestCase`
|
||||
- Integración con BD
|
||||
- Util para probar código que usa la BD
|
||||
|
||||
#### Ej. test
|
||||
|
||||
```py
|
||||
""""
|
||||
Unit test for views
|
||||
""""
|
||||
from django.test import SimpleTestCase
|
||||
form app_two import views
|
||||
|
||||
class ViewsTests(SimpleTestCase):
|
||||
|
||||
def test_make_list_unique(self):
|
||||
""" Test making a list unique. """
|
||||
sample_items = [1, 1, 2, 2, 3, 4, 5, 5]
|
||||
res = views.remove_duplicates(sample_items)
|
||||
self.assertEqual(res, [1, 2, 3, 4, 5])
|
||||
```
|
||||
|
||||
`python manage.py test`
|
||||
|
||||
## Creación del primer test
|
||||
|
||||
Modulo a testear [`calc.py`](./app/app/calc.py) con [`tests.py`](app/app/tests.py)
|
||||
|
||||
```py
|
||||
"""
|
||||
Sample tests
|
||||
"""
|
||||
from django.test import SimpleTestCase
|
||||
from app import calc
|
||||
|
||||
class CalcTests(SimpleTestCase):
|
||||
""" Test the calc module. """
|
||||
|
||||
def test_add_numbers(self):
|
||||
""" Test adding numbers together. """
|
||||
res = calc.add(5, 6)
|
||||
|
||||
self.assertEqual(res, 11)
|
||||
|
||||
```
|
||||
|
||||
### Correr el test
|
||||
|
||||
`docker compose run --rm app sh -c "python manage.py test"`
|
||||
|
||||
```python
|
||||
Found 1 test(s).
|
||||
System check identified no issues (0 silenced).
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 1 test in 0.000s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
### Usando TDD
|
||||
|
||||
1. Crear la prueba para el comportamiento esperado [tests.py](./app/app/tests.py)
|
||||
2. La prueba debe fallar
|
||||
3. Crear el código para que el test pase (añadir la funcionalidad)
|
||||
[calc.py](./app/app/calc.py)
|
||||
|
||||
## Mocking
|
||||
|
||||
- Evita depender de servicios externos, pues estos
|
||||
- No garantizan disponibilidad
|
||||
- Pueden hacer que los tests sean impredecibles e incositentes
|
||||
- Evita consecuencias no intecionadas, por ejm.
|
||||
- Enviar mails accidentalmente
|
||||
- Sobrecargar servicios externos
|
||||
|
||||
ej.
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'dark','themeVariables': {'clusterBkg': '#2b2f38'}, 'flowchart': {'curve': 'natural'}}}%%
|
||||
flowchart
|
||||
subgraph " "
|
||||
direction LR
|
||||
RU["<b>register_user()</b>"]
|
||||
CIDB["<b>create_in_db()</b>"]
|
||||
SWE["<b>send_welcome_email()</b>"]
|
||||
MS[e-mail sent]
|
||||
|
||||
end
|
||||
RU --> CIDB --> SWE --x MS
|
||||
```
|
||||
|
||||
- Previene el envio del correo electónico
|
||||
- Asegura que `send_welcome_email()` es llamado correctamente
|
||||
|
||||
### Otro beneficio de Mocking
|
||||
|
||||
- Acelera las pruebas
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'dark','themeVariables': {'clusterBkg': '#2b2f38'}, 'flowchart': {'curve': 'natural'}}}%%
|
||||
flowchart
|
||||
subgraph " "
|
||||
direction LR
|
||||
CDB["<b>check_db()</b>"]
|
||||
SLP["<b>sleep()</b>"]
|
||||
|
||||
end
|
||||
CDB --x SLP --> CDB
|
||||
```
|
||||
|
||||
### Como usar mock
|
||||
|
||||
- Se usa `unittest.mock`
|
||||
- `MagicMock/Mock` reemplaza objetos reales
|
||||
- `patch` sobreescribe el código de las pruebas
|
||||
|
||||
|
||||
## Testing Web Request
|
||||
|
||||
Probando la API
|
||||
|
||||
- Hacer peticiones reales
|
||||
- Comprobar resultados
|
||||
|
||||
Django REST Framework provee un cliente para la API basado en Django `TestClient`,
|
||||
este realiza los requests y permite verificar resultados. Incluso permite
|
||||
sobreescribir la autenticación, para probar la funcionalidad de la API, haciendo
|
||||
que esta asuma que se esta autentificado.
|
||||
|
||||
```py
|
||||
from django.test import SimpleTestCase
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
class TestViews(SimpleTestCase):
|
||||
|
||||
def test_get_greetings(self):
|
||||
""" Test getting greetings. """
|
||||
client = APIClient()
|
||||
res = client.get('/greetings/')
|
||||
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertEqual(
|
||||
res.data,
|
||||
["Hello!", "Bonjour!", "Hola!"],
|
||||
)
|
||||
```
|
||||
|
||||
### Problemas comunes en las pruebas
|
||||
|
||||
#### El test no se ejecuta
|
||||
|
||||
```py
|
||||
System check identified no issues (0 silenced).
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 0 test in 0.000s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
- Se ejecutan menos tests que la cantidad creada
|
||||
|
||||
**Razones posibles**
|
||||
|
||||
- Falta el archivo `__init__.py` en el directorio `tests.py`
|
||||
- Indentación de los casos de prueba
|
||||
- Prefijo `test` faltante en los metodos `test_some_function(self):`
|
||||
|
||||
#### `ImportError` al correr las pruebas
|
||||
|
||||
```py
|
||||
raise ImportError(
|
||||
ImportError: 'tests' module incorrectly imported from ....
|
||||
Expected ....Is this module globally installed?
|
||||
)
|
||||
```
|
||||
|
||||
**Razon posible**
|
||||
|
||||
- Existencia de `tests/` y `tests.py` en la misma aplicación
|
||||
|
||||
## Configurando la base de datos
|
||||
|
||||
PostgreSQL se integra bien con django, es oficialmente soportada.
|
||||
|
||||
Se utiliza *Docker compose*, se define la configuración dentro del proyecto
|
||||
para que sea reutilizable. Datos persistentes utilizando *volumes*. Maneja la
|
||||
configuración de la red. Configuranción usando variables de entorno.
|
||||
|
||||
### Arquitectura
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'dark','themeVariables': {'clusterBkg': '#2b2f38'}, 'flowchart': {'curve': 'basis'}}}%%
|
||||
flowchart
|
||||
direction TB
|
||||
subgraph "<b>Docker Compose</b>"
|
||||
DB[(PostgreSQL)]
|
||||
direction LR
|
||||
APP("<b>App</b>
|
||||
Django")
|
||||
DB <-..-> APP
|
||||
end
|
||||
```
|
||||
|
||||
### Conectividad de red
|
||||
|
||||
```yml
|
||||
services:
|
||||
app:
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
```
|
||||
|
||||
- Establecer `depends_on` para iniciar `db` primero
|
||||
- El servicio `app` puede usar el hostname de `db`
|
||||
|
||||
### Volumes
|
||||
|
||||
- Persistencia de datos
|
||||
- Mapea el directorio del contenedeor con la máquina local
|
||||
|
||||
```yml
|
||||
db:
|
||||
image: postgres:16-alphine
|
||||
volumes:
|
||||
- dev-db-data:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
dev-db-data:
|
||||
dev-static-data:
|
||||
```
|
||||
|
||||
### Agregando el servicio base de datos
|
||||
|
||||
[docker-compose.yml](./docker-compose.yml)
|
||||
|
||||
```yml
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
args:
|
||||
- DEV=true
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./app:/app
|
||||
command: >
|
||||
sh -c "python manage.py runserver 0.0.0.0:8000"
|
||||
environment:
|
||||
- DB_HOST=db
|
||||
- DB_NAME=devdb
|
||||
- DB_USER=devuser
|
||||
- DB_PASS=changeme
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
volumes:
|
||||
- dev-db-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_DB=devdb
|
||||
- POSTGRES_USER=devuser
|
||||
- POSTGRES_PASSWORD=changeme
|
||||
|
||||
|
||||
volumes:
|
||||
dev-db-data:
|
||||
```
|
||||
|
||||
Probar que todo funcione correctamente `docker compose up`
|
||||
|
||||
### Configuración de la BD en Django
|
||||
|
||||
- Especificar como django conecta con la BD [`settings.py`](./app/app/settings.py)
|
||||
- Engine (tipo de BD)
|
||||
- Hostname (DB IP o dominio)
|
||||
- Port
|
||||
- DB name
|
||||
- DB user
|
||||
- DB password
|
||||
- Instalar las dependencias del conector
|
||||
- Actualizar los requerimientos
|
||||
|
||||
```py
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE' django.db.backends.postgresql',
|
||||
'HOST': os.environ.get('DB_HOST'),
|
||||
'NAME': os.environ.get('DB_NAME'),
|
||||
'USER': os.environ.get('DB_USER'),
|
||||
'PASSWORD': os.environ.get('DB_PASSWORD'),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Psycopg2
|
||||
|
||||
Packete necesario para conectar Django con la base de datos PostgreSQL, es el
|
||||
conector mas popular en Python y es soportado por Django. Se puede instalar de
|
||||
las siguientes maneras:
|
||||
|
||||
- `psycopg2-binary` (Ok para desarrollo, no para producción)
|
||||
- `psycopg2` (se compila desde el código fuente, hay que satisfacer
|
||||
dependencias para esto)
|
||||
- Fácil de instalar con Docker
|
||||
|
||||
Lista de dependencias
|
||||
|
||||
- Compilador C
|
||||
- python3-dev
|
||||
- libpq-dev
|
||||
|
||||
Equivalentes para Apine
|
||||
|
||||
- postgresql-client
|
||||
- build-base
|
||||
- postgresql-dev
|
||||
- musl-dev
|
||||
|
||||
Equivalentes para Debian (python:3.12.0-slim-bookworm)
|
||||
|
||||
- postgresql-client
|
||||
- python3-dev
|
||||
- libpq-dev
|
||||
- gcc *confimar*
|
||||
- python3-psycopg2 *confirmar*
|
||||
|
||||
Una **buena practica** es limpiar las dependencias que ya no serán necesarias
|
||||
|
||||
### Previniendo Race Condition con docker compose
|
||||
|
||||
Usar `depends_on` asegura que el `service` comience *(no asegura que la app
|
||||
este corriendo)*
|
||||
|
||||
### Docker services timeline
|
||||
|
||||
![img](./imgs_readme/docker_services_timeline1.png)
|
||||
|
||||
La **solución** es hace que Django espere a la base de datos `db`. Este chequea
|
||||
la disponibilidad de la base de datos y continua cuando esta disponible
|
||||
|
||||
![img](./imgs_readme/docker_services_timeline2.png)
|
||||
|
||||
Comando personalizado de administración de Django en app **core**
|
||||
|
||||
## Creación de aplicación core
|
||||
|
||||
`docker compose run --rm app sh -c "python manage.py startapp core"`
|
||||
|
||||
Se eliminal el archvo `tests.py` para crear y usar el directorio `app/core/tests/`
|
||||
donde se agrega el correspondiente archivo `__init__.py`
|
||||
|
||||
Se crean los directorios `app/core/management/commands/` con los archivos
|
||||
`__init__.py` y `commands.py`
|
||||
|
||||
```py
|
||||
"""
|
||||
Django command to wait for the DB to be available.
|
||||
"""
|
||||
import time
|
||||
|
||||
from psycopg2 import OperationalError as Psycopg2OpError
|
||||
|
||||
from django.db.utils import OperationalError
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
class Command(BaseCommand):
|
||||
""" Django commando to wait for database. """
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Entrypoint for command."""
|
||||
self.stdout.write('Waiting for database...')
|
||||
db_up = False
|
||||
while db_up is False:
|
||||
try:
|
||||
self.check(databases=['default'])
|
||||
db_up = True
|
||||
except (Psycopg2OpError, OperationalError):
|
||||
self.stdout.write('Database unavailable, waiting 1 second...')
|
||||
time.sleep(1)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('Database available!'))
|
||||
```
|
||||
|
||||
### Correr tests
|
||||
|
||||
`docker compose run --rm app sh -c "python manage.py test"`
|
||||
|
||||
```sh
|
||||
[+] Creating 1/0
|
||||
✔ Container django_rest_api_udemy-db-1 Running 0.0s
|
||||
Found 4 test(s).
|
||||
System check identified no issues (0 silenced).
|
||||
..Waiting for database...
|
||||
Database unavailable, waiting 1 second...
|
||||
Database unavailable, waiting 1 second...
|
||||
Database unavailable, waiting 1 second...
|
||||
Database unavailable, waiting 1 second...
|
||||
Database unavailable, waiting 1 second...
|
||||
Database available!
|
||||
.Waiting for database...
|
||||
Database available!
|
||||
.
|
||||
----------------------------------------------------------------------
|
||||
Ran 4 tests in 0.005s
|
||||
|
||||
OK
|
||||
```
|
||||
|
||||
### Correr command
|
||||
|
||||
`docker compose run --rm app sh -c "python manage.py wait_for_db"`
|
||||
|
||||
```sh
|
||||
[+] Creating 1/0
|
||||
✔ Container django_rest_api_udemy-db-1 Running 0.0s
|
||||
Waiting for database...
|
||||
Database available!
|
||||
```
|
||||
|
||||
Correr linter `docker compose run --rm app sh -c "flake8"` y corregir
|
||||
|
||||
## Migraciones de la base de datos
|
||||
|
||||
### Django ORM
|
||||
|
||||
- Object Reational Mapper (ORM)
|
||||
- Capa de abstracción para los datos
|
||||
- Django maneja la estructura y cambios de la BD
|
||||
- Ayuda a enfocarse en el código de Python
|
||||
- Permite usar otras bases de datos
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'dark','themeVariables': {'clusterBkg': '#2b2f38'}, 'flowchart': {'curve': 'basis'}}}%%
|
||||
flowchart
|
||||
subgraph "ORM"
|
||||
DM["Define models"]
|
||||
GMF["Generate
|
||||
migration files"]
|
||||
SD["Setup database"]
|
||||
STD["Store data"]
|
||||
DM -.-> GMF -.-> SD -.-> STD
|
||||
end
|
||||
```
|
||||
|
||||
### Models
|
||||
|
||||
- Cada modelo mapea a una tabla
|
||||
- Los modelos contienen
|
||||
- Nombre
|
||||
- Campos
|
||||
- Otra metadata
|
||||
- Lógica en Python personalizada
|
||||
|
||||
Modelo ejemplo
|
||||
|
||||
```py
|
||||
class Ingredient(models.Model):
|
||||
"""Ingredient for recipies."""
|
||||
name = models.CharField(max_length=255)
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
```
|
||||
|
||||
### Creación de las migraciones
|
||||
|
||||
- Asegura que la app esta activa en `settings.py`
|
||||
- Se utiliza el CLI de DJango `python manage.py makemigrations`
|
||||
- Aplicar migraciones `python manage.py makemigrations`
|
||||
- Correr despues de esperar por la base de datos
|
||||
|
||||
## Actualización de Docker Compose y CI/CD
|
||||
|
||||
```yml
|
||||
...
|
||||
command: >
|
||||
sh -c "python manage.py wait_for_db &&
|
||||
python manage.py migrate &&
|
||||
python manage.py runserver 0.0.0.0:8000"
|
||||
...
|
||||
```
|
||||
|
||||
`docker compose down`
|
||||
|
||||
```sh
|
||||
[+] Running 3/3
|
||||
✔ Container django_rest_api_udemy-app-1 Removed 0.0s
|
||||
✔ Container django_rest_api_udemy-db-1 Removed 0.1s
|
||||
✔ Network django_rest_api_udemy_default Removed 0.1s
|
||||
```
|
||||
|
||||
`docker compose up`
|
||||
|
||||
#### Corriendo `wait_for_db` antes de los tests en GitHub Actions
|
||||
|
||||
[checks.yml](./.github/workflows/checks.yml)
|
||||
|
||||
```yml
|
||||
...
|
||||
- name: Test
|
||||
run: >
|
||||
docker compose run --rm app sh -c "python manage.py wait_for_db &&
|
||||
python manage.py test"
|
||||
...
|
||||
```
|
||||
|
13
app/app/calc.py
Normal file
13
app/app/calc.py
Normal file
@ -0,0 +1,13 @@
|
||||
"""
|
||||
Calculator functions
|
||||
"""
|
||||
|
||||
|
||||
def add(x, y):
|
||||
""" Add x and y and return result. """
|
||||
return x + y
|
||||
|
||||
|
||||
def subtract(x, y):
|
||||
""" Subtract x from y and return resutl. """
|
||||
return x - y
|
@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
@ -37,6 +37,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'core',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@ -75,8 +76,11 @@ WSGI_APPLICATION = 'app.wsgi.application'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'HOST': os.environ.get('DB_HOST'),
|
||||
'NAME': os.environ.get('DB_NAME'),
|
||||
'USER': os.environ.get('DB_USER'),
|
||||
'PASSWORD': os.environ.get('DB_PASS'),
|
||||
}
|
||||
}
|
||||
|
||||
|
21
app/app/tests.py
Normal file
21
app/app/tests.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""
|
||||
Sample tests
|
||||
"""
|
||||
from django.test import SimpleTestCase
|
||||
from app import calc
|
||||
|
||||
|
||||
class CalcTests(SimpleTestCase):
|
||||
""" Test the calc module. """
|
||||
|
||||
def test_add_numbers(self):
|
||||
""" Test adding numbers together. """
|
||||
res = calc.add(5, 6)
|
||||
|
||||
self.assertEqual(res, 11)
|
||||
|
||||
def test_subtract_numbers(self):
|
||||
""" Test subtracting numbers. """
|
||||
res = calc.subtract(10, 5)
|
||||
|
||||
self.assertEqual(res, 5)
|
0
app/core/__init__.py
Normal file
0
app/core/__init__.py
Normal file
3
app/core/admin.py
Normal file
3
app/core/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin # noqa
|
||||
|
||||
# Register your models here.
|
6
app/core/apps.py
Normal file
6
app/core/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'core'
|
0
app/core/management/commands/__init__.py
Normal file
0
app/core/management/commands/__init__.py
Normal file
27
app/core/management/commands/wait_for_db.py
Normal file
27
app/core/management/commands/wait_for_db.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""
|
||||
Django command to wait for the DB to be available.
|
||||
"""
|
||||
import time
|
||||
|
||||
from psycopg2 import OperationalError as Psycopg2OpError
|
||||
|
||||
from django.db.utils import OperationalError
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
""" Django commando to wait for database. """
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Entrypoint for command."""
|
||||
self.stdout.write('Waiting for database...')
|
||||
db_up = False
|
||||
while db_up is False:
|
||||
try:
|
||||
self.check(databases=['default'])
|
||||
db_up = True
|
||||
except (Psycopg2OpError, OperationalError):
|
||||
self.stdout.write('Database unavailable, waiting 1 second...')
|
||||
time.sleep(1)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('Database available!'))
|
0
app/core/migrations/__init__.py
Normal file
0
app/core/migrations/__init__.py
Normal file
3
app/core/models.py
Normal file
3
app/core/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models # noqa
|
||||
|
||||
# Create your models here.
|
0
app/core/tests/__init__.py
Normal file
0
app/core/tests/__init__.py
Normal file
35
app/core/tests/test_commands.py
Normal file
35
app/core/tests/test_commands.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""
|
||||
Test custom Django management commands.
|
||||
"""
|
||||
from unittest.mock import patch
|
||||
|
||||
from psycopg2 import OperationalError as Psycopg2OpError
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.db.utils import OperationalError
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
|
||||
@patch('core.management.commands.wait_for_db.Command.check')
|
||||
class CommandTests(SimpleTestCase):
|
||||
"""Test Comands."""
|
||||
|
||||
def test_wait_for_db_ready(self, patched_check):
|
||||
"""Test waiting for database if database ready."""
|
||||
patched_check.return_value = True
|
||||
|
||||
call_command('wait_for_db')
|
||||
|
||||
patched_check.assert_called_once_with(databases=['default'])
|
||||
|
||||
# ojo con el orden de los parametros "desde dentro hacia fuera"
|
||||
@patch('time.sleep')
|
||||
def test_wait_for_db_delay(self, patched_sleep, patched_check):
|
||||
"""Test waiting for database when getting OperationalError."""
|
||||
patched_check.side_effect = [Psycopg2OpError] * 2 + \
|
||||
[OperationalError] * 3 + [True]
|
||||
|
||||
call_command('wait_for_db')
|
||||
|
||||
self.assertEqual(patched_check.call_count, 6)
|
||||
patched_check.assert_called_with(databases=['default'])
|
@ -4,9 +4,33 @@ services:
|
||||
context: .
|
||||
args:
|
||||
- DEV=true
|
||||
- BUILDKIT_PROGRESS=plain docker compose build
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- ./app:/app
|
||||
command: >
|
||||
sh -c "python manage.py runserver 0.0.0.0:8000"
|
||||
sh -c "python manage.py wait_for_db &&
|
||||
python manage.py migrate &&
|
||||
python manage.py runserver 0.0.0.0:8000"
|
||||
environment:
|
||||
- DB_HOST=db
|
||||
- DB_NAME=devdb
|
||||
- DB_USER=devuser
|
||||
- DB_PASS=changeme
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
volumes:
|
||||
- dev-db-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_DB=devdb
|
||||
- POSTGRES_USER=devuser
|
||||
- POSTGRES_PASSWORD=changeme
|
||||
|
||||
|
||||
volumes:
|
||||
dev-db-data:
|
||||
|
||||
|
BIN
imgs_readme/docker_services_timeline1.png
Normal file
BIN
imgs_readme/docker_services_timeline1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 102 KiB |
BIN
imgs_readme/docker_services_timeline2.png
Normal file
BIN
imgs_readme/docker_services_timeline2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
@ -1,2 +1,3 @@
|
||||
Django==4.2.5
|
||||
djangorestframework==3.14.0
|
||||
psycopg2>=2.9.9
|
||||
|
Loading…
Reference in New Issue
Block a user