Add new exercise
This commit is contained in:
parent
d7c1cee2a8
commit
71d0a3c4d8
90
catch-all/05_redis_flask_docker/.dockerignore
Normal file
90
catch-all/05_redis_flask_docker/.dockerignore
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
README.md
|
||||||
|
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
|
||||||
|
# CI
|
||||||
|
.codeclimate.yml
|
||||||
|
.travis.yml
|
||||||
|
.taskcluster.yml
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile
|
||||||
|
.docker
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
**/__pycache__/
|
||||||
|
**/*.py[cod]
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Virtual environment
|
||||||
|
# .env
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Python mode for VIM
|
||||||
|
.ropeproject
|
||||||
|
**/.ropeproject
|
||||||
|
|
||||||
|
# Vim swap files
|
||||||
|
**/*.swp
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode/
|
12
catch-all/05_redis_flask_docker/Dockerfile
Normal file
12
catch-all/05_redis_flask_docker/Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM python:3.12-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
CMD ["python", "app.py"]
|
||||||
|
|
188
catch-all/05_redis_flask_docker/README.md
Normal file
188
catch-all/05_redis_flask_docker/README.md
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
# Crear una API Caching con Redis, Flask y Docker
|
||||||
|
|
||||||
|
<!-- Artículo original: https://dev.to/vjanz/implement-api-caching-with-redis-flask-and-docker-step-by-step-5h01 -->
|
||||||
|
|
||||||
|
![](https://res.cloudinary.com/practicaldev/image/fetch/s--MflWnlWv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/4350/1%2AgEpkD_3NMTxK-w96c_QBBA.png)
|
||||||
|
|
||||||
|
## Prueba 1: Sin Redis
|
||||||
|
|
||||||
|
Primero vamos a hacer una prueba de la aplicación sin redis.
|
||||||
|
|
||||||
|
Vamos al directorio donde queremos trabajar, creamos un entorno virtual y lo activamos:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
Ahora instalamos las dependencias en nuestro entorno:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(venv) pip install Flask redis flask_caching requests
|
||||||
|
```
|
||||||
|
|
||||||
|
Y guardamos estas dependencias en un archivo `requirements.txt`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(venv) pip freeze > requirements.txt
|
||||||
|
```
|
||||||
|
Vamos a crear un archivo `app.py` con el siguiente contenido:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/universities")
|
||||||
|
def get_universities():
|
||||||
|
API_URL = "http://universities.hipolabs.com/search?country="
|
||||||
|
search = request.args.get('country')
|
||||||
|
r = requests.get(f"{API_URL}{search}")
|
||||||
|
return jsonify(r.json())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True, host="0.0.0.0", port=5000)
|
||||||
|
```
|
||||||
|
|
||||||
|
Ahora vamos a ejecutar la aplicación:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export FLASK_APP=app.py
|
||||||
|
export FLASK_ENV=development
|
||||||
|
flask run
|
||||||
|
```
|
||||||
|
|
||||||
|
Y si nos vamos a postman podremos comprobar cuanto tarda en responder la petición:
|
||||||
|
|
||||||
|
![](https://res.cloudinary.com/practicaldev/image/fetch/s--cFeIXNRd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2898/1%2A9o8_94EDMwGO-BWWKnshBA.png)
|
||||||
|
|
||||||
|
|
||||||
|
## Prueba 2: Dockerizar nuestra aplicación
|
||||||
|
|
||||||
|
Vamos a dockerizar nuestra aplicación, para ello vamos a crear un archivo `Dockerfile` con el siguiente contenido:
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
FROM python:3.12-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
CMD ["python", "app.py"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Vamos a crear también un archivo `docker-compose.yaml` con el siguiente contenido:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
container_name: app-python-flask-with-redis
|
||||||
|
build: .
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
ports:
|
||||||
|
- '5000:5000'
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7.0-alpine
|
||||||
|
container_name: redis-python
|
||||||
|
ports:
|
||||||
|
- '6379:6379'
|
||||||
|
```
|
||||||
|
|
||||||
|
Incluimos también el contenedor de Redis. Lanzamos nuestra aplicación con el comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Si vemos `docker ps` veremos que tenemos dos contenedores corriendo. También podemos revisar los logs del contenedor de la aplicación con `docker logs app-python-flask-with-redis`.
|
||||||
|
|
||||||
|
Comprobamos que nuestra aplicación sigue funcionando en docker igual que lo hacía en local.
|
||||||
|
|
||||||
|
## Prueba 3: Añadir Redis a nuestra aplicación
|
||||||
|
|
||||||
|
Ahora vamos a añadir Redis a nuestra aplicación. Vamos a modificar el archivo `app.py` para que use Redis:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
|
from flask_caching import Cache
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object('config.BaseConfig')
|
||||||
|
cache = Cache(app)
|
||||||
|
|
||||||
|
@app.route("/universities")
|
||||||
|
@cache.cached(timeout=30, query_string=True)
|
||||||
|
def get_universities():
|
||||||
|
API_URL = "http://universities.hipolabs.com/search?country="
|
||||||
|
search = request.args.get('country')
|
||||||
|
r = requests.get(f"{API_URL}{search}")
|
||||||
|
return jsonify(r.json())
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0')
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Y vamos a crear un archivo `config.py` con el siguiente contenido:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
|
||||||
|
class BaseConfig(object):
|
||||||
|
CACHE_TYPE = os.environ['CACHE_TYPE']
|
||||||
|
CACHE_REDIS_HOST = os.environ['CACHE_REDIS_HOST']
|
||||||
|
CACHE_REDIS_PORT = os.environ['CACHE_REDIS_PORT']
|
||||||
|
CACHE_REDIS_DB = os.environ['CACHE_REDIS_DB']
|
||||||
|
CACHE_REDIS_URL = os.environ['CACHE_REDIS_URL']
|
||||||
|
CACHE_DEFAULT_TIMEOUT = os.environ['CACHE_DEFAULT_TIMEOUT']
|
||||||
|
```
|
||||||
|
|
||||||
|
Este fichero recoge las variables de entorno que vamos a usar en nuestra aplicación. Vamos a crear un archivo `.env` con el siguiente contenido:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .e
|
||||||
|
CACHE_TYPE=redis
|
||||||
|
CACHE_REDIS_HOST=redis
|
||||||
|
CACHE_REDIS_PORT=6379
|
||||||
|
CACHE_REDIS_DB=0
|
||||||
|
CACHE_REDIS_URL=redis://redis:6379/0
|
||||||
|
CACHE_DEFAULT_TIMEOUT=300
|
||||||
|
```
|
||||||
|
|
||||||
|
Al finalizar la práctica, tendremos esta estructura:
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── app.py
|
||||||
|
├── config.py
|
||||||
|
├── docker-compose.yaml
|
||||||
|
├── Dockerfile
|
||||||
|
├── .env
|
||||||
|
└── requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Volvemos a lanzar nuestra aplicación con `docker-compose up -d --build` y comprobamos que todo sigue funcionando correctamente.
|
||||||
|
|
||||||
|
Volvemos a lanzar la misma petición desde postman y comprobamos que la respuesta es mucho más rápida que antes:
|
||||||
|
|
||||||
|
![](https://res.cloudinary.com/practicaldev/image/fetch/s--mHDsWyzq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/2922/1%2Au4a8OUBQu2gBzvc6iL5nsw.png)
|
||||||
|
|
||||||
|
|
||||||
|
Podemos probar con otros países, la primera vez tardará más porque no estará en caché, pero las siguientes veces será mucho más rápido.
|
||||||
|
|
||||||
|
Esta es la magia de Redis, una base de datos en memoria que nos permite almacenar datos en caché y acelerar nuestras aplicaciones 🚀
|
||||||
|
|
21
catch-all/05_redis_flask_docker/app.py
Normal file
21
catch-all/05_redis_flask_docker/app.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import requests
|
||||||
|
from flask import Flask, jsonify, request
|
||||||
|
from flask_caching import Cache
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object('config.BaseConfig')
|
||||||
|
cache = Cache(app)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/universities")
|
||||||
|
@cache.cached(timeout=30, query_string=True)
|
||||||
|
def get_universities():
|
||||||
|
API_URL = "http://universities.hipolabs.com/search?country="
|
||||||
|
search = request.args.get('country')
|
||||||
|
r = requests.get(f"{API_URL}{search}")
|
||||||
|
return jsonify(r.json())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0')
|
11
catch-all/05_redis_flask_docker/config.py
Normal file
11
catch-all/05_redis_flask_docker/config.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class BaseConfig(object):
|
||||||
|
CACHE_TYPE = os.environ['CACHE_TYPE']
|
||||||
|
CACHE_REDIS_HOST = os.environ['CACHE_REDIS_HOST']
|
||||||
|
CACHE_REDIS_PORT = os.environ['CACHE_REDIS_PORT']
|
||||||
|
CACHE_REDIS_DB = os.environ['CACHE_REDIS_DB']
|
||||||
|
CACHE_REDIS_URL = os.environ['CACHE_REDIS_URL']
|
||||||
|
CACHE_DEFAULT_TIMEOUT = os.environ['CACHE_DEFAULT_TIMEOUT']
|
||||||
|
|
19
catch-all/05_redis_flask_docker/docker-compose.yaml
Normal file
19
catch-all/05_redis_flask_docker/docker-compose.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
container_name: app-python-flask-with-redis
|
||||||
|
build: .
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
ports:
|
||||||
|
- '5000:5000'
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7.0-alpine
|
||||||
|
container_name: redis-python
|
||||||
|
ports:
|
||||||
|
- '6379:6379'
|
||||||
|
|
16
catch-all/05_redis_flask_docker/requirements.txt
Normal file
16
catch-all/05_redis_flask_docker/requirements.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
async-timeout==4.0.3
|
||||||
|
blinker==1.8.2
|
||||||
|
cachelib==0.9.0
|
||||||
|
certifi==2024.6.2
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
click==8.1.7
|
||||||
|
Flask==3.0.3
|
||||||
|
Flask-Caching==2.3.0
|
||||||
|
idna==3.7
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
Jinja2==3.1.4
|
||||||
|
MarkupSafe==2.1.5
|
||||||
|
redis==5.0.5
|
||||||
|
requests==2.32.3
|
||||||
|
urllib3==2.2.1
|
||||||
|
Werkzeug==3.0.3
|
@ -12,3 +12,4 @@ Aquí iré dejando scripts y ejercicios que se me ocurran, con lo que no hay un
|
|||||||
| [Descifrador wargame](./02_scripts_descifrador_wargame/) | Script descifrador de código al estilo wargame | intermedio |
|
| [Descifrador wargame](./02_scripts_descifrador_wargame/) | Script descifrador de código al estilo wargame | intermedio |
|
||||||
| [Clima España](./03_clima/) | Script conectado a API AEMET | intermedio |
|
| [Clima España](./03_clima/) | Script conectado a API AEMET | intermedio |
|
||||||
| [acortador de enlaces](./04_acortador_url/) | Script para acortar enlaces y redirigirlos con app Flask | intermedio |
|
| [acortador de enlaces](./04_acortador_url/) | Script para acortar enlaces y redirigirlos con app Flask | intermedio |
|
||||||
|
| [API Caching](./05_redis_flask_docker) | App Flask con caché en Redis y con una bbdd | intermedio |
|
||||||
|
Loading…
Reference in New Issue
Block a user