Desarrollar código Python limpio con pre-commit
En artículo anterior (Herramientas de Python para desarrollar código seguro y de calidad) se tocó el tema de herramientas que facilitan a cumplir el PEP8 de Python, mejoras de seguridad del código y formateo del mismo, en este caso se usa circle-ci para las evaluaciones.
En este artículo se usará la evaluación antes de subir código a un repositorio de git. El artículo se basa en los siguientes artículos:
- Python Code Quality: Tools & Best Practices
- Python Code Quality
- pre-commit hooks you must know
- Simplify your Python Code: Automating Code Complexity Analysis with Wily
- How to Improve your Python Code Style with Pre-commit Hooks
- Keeping python code clean with pre-commit hooks: black, flake8 and isort
Se volvera a tocar las herramientas que se explicaron en el artículo anterior.
El código que se usará para evaluar es el siguiente (no cumple con las comprobaciones):
import numpy as np
from scipy import signal as sp
import matplotlib.pylab as plt
import scipy.signal as signal
import scipy.stats as stats
import streamlit as st
def u(amplitud, t):
"""Función escalón unitario Args:
amplitud (int): Amplitud del escalon
t (list): Lista de tiempo Returns:
list: Lista de valores
"""
return amplitud*np.piecewise(t, [t < 0.0, t >= 0.0], [0, 1])
def OndaCuadrada(amplitud, t, fs=1):
"""Función de Onda Cuadrada Args:
amplitud (int): Amplitud de la segnal
t (list): Lista de valores de tiempo
fs (int, optional): Frecuencia.. Defaults to 1. Returns:
list: Lista de valores
"""
return ((sp.square(2 * fs*t)) * (amplitud / 2.0)) + (amplitud / 2.0)
def segnal_triangular(amplitud, simetria, t, fs=1):
"""Señal triangular Args:
amplitud (int): Amplitud de la señal
simetria (float): simetria de la señal
t (list): Lista de valores que definen el tiempo.
fs (int, optional): Frecuencia de la señal. Defaults to 1. Returns:
list: Lista de valores de la señal
"""
return amplitud*(signal.sawtooth(2 * np.pi * fs * t, simetria))
def seno(amplitud, t, fs=1):
""" Onda Seno Args:
amplitud (int): Amplitud de la señal
t (list): Lista de valores de tiempo
fs (int, optional): Frecuencia de la señal. Defaults to 1. Returns:
list: Lista de valores de la señal de seno
"""
return amplitud*np.sin(fs*t)
def coseno(amplitud, t, fs=1):
"""Señal de coseno Args:
amplitud (int): Amplitud de la señal
t (list): lista de valores para generar la señal
fs (int, optional): Frecuencia de la señal. Defaults to 1. Returns:
list: Lista de valores de la señal
"""
return amplitud*np.cos(fs*t)
def tiempo(lim_inf, lim_sup, n):
"""Lista de valores que definen el tiempo de la señal Args:
lim_inf (int): Límite inferior del tiempo
lim_sup (int): Límite superior del tiempo
n (int): Cantidad de valores a generar del tiempo Returns:
list: Lista de valores del tiemmpo
"""
return np.linspace(lim_inf, lim_sup, n)
def plot_signal(xi, xf, yi, yf, t, titulo, etiqueta, values):
"""Generación de la gráfica de la señal. Args:
xi (int): x inicial
xf (int): x final
yi (int): y inicial
yf (int): y final
t (list): lista de valores de tiempo
titulo (str): Título de la gráfica
etiqueta (str): Etiqueta de la señal.
values (list): Valores de la señal
"""
plot(t, values, 'k', label=etiqueta, lw=2)
xlim(xi, xf)
def main():
# Definir título
st.title("Generación de gráficas de señales")
st.sidebar.header("Entradas:")
segnales = ["Escalon Unitario", "Onda Cuadrada",
"Onda triangular", "Seno", "Coseno"] resp = st.sidebar.selectbox("Tipo de señal", segnales) st.sidebar.header("Definición del tiempo:")
st.sidebar.subheader("Rango")
# SelectBox
t0 = int(st.sidebar.selectbox(
"", range(0, 10)
))
ti = 0
tf = t0
n = 10000
t = tiempo(ti, tf, n)
st.sidebar.subheader("Amplitud de la señal")
amplitud = int(st.sidebar.selectbox(
"", range(1, 10)
)) # numpy.ndarray
if resp == "Escalon Unitario":
ti = -tf
resultado = u(amplitud, t)
elif resp == "Onda Cuadrada":
st.sidebar.subheader("Frecuencia de la señal")
fs = int(st.sidebar.selectbox(
"", range(1, 11)
))
resultado = OndaCuadrada(amplitud, t, fs)
elif resp == "Onda triangular":
simetria = 0.5
st.sidebar.subheader("Frecuencia de la señal")
fs = int(st.sidebar.selectbox(
"", range(1, 11)
))
resultado = segnal_triangular(amplitud, simetria, t, fs)
elif resp == "Seno":
st.sidebar.subheader("Frecuencia de la señal")
fs = int(st.sidebar.selectbox(
"", range(1, 11)
))
resultado = seno(amplitud, t, fs)
elif resp == "Coseno":
st.sidebar.subheader("Frecuencia de la señal")
fs = int(st.sidebar.selectbox(
"", range(1, 11)
))
resultado = coseno(amplitud, t, fs)
else:
resultado = 0
st.error("Error") fig = plt.figure()
ax = fig.add_subplot(111)
st.header(f"Gráfica de {resp}")
ax.plot(t, resultado)
ax.set_xlim(ti, tf)
ax.set_xlabel("Tiempo")
ax.set_ylabel("f(t)")
ax.set_ylim(2*amplitud*-1, 2*amplitud)
st.pyplot(fig)
if __name__ == "__main__":
main()
Su archivo de requerimientos es el siguiente:
streamlit==0.70.0
scipy==1.5.4
numpy==1.19.3
matplotlib==3.3.3Su estructura inicial de archivos es la siguiente (incluyendo archivos ocultos):
signals_fft_streamlit
├── .git
│ ├── branches
│ ├── config
│ ├── description
│ ├── HEAD
│ ├── hooks
│ │ ├── applypatch-msg.sample
│ │ ├── commit-msg.sample
│ │ ├── fsmonitor-watchman.sample
│ │ ├── post-update.sample
│ │ ├── pre-applypatch.sample
│ │ ├── pre-commit.sample
│ │ ├── pre-merge-commit.sample
│ │ ├── prepare-commit-msg.sample
│ │ ├── pre-push.sample
│ │ ├── pre-rebase.sample
│ │ ├── pre-receive.sample
│ │ └── update.sample
│ ├── index
│ ├── info
│ │ └── exclude
│ ├── logs
│ │ ├── HEAD
│ │ └── refs
│ │ ├── heads
│ │ │ └── main
│ │ └── remotes
│ │ └── origin
│ │ └── HEAD
│ ├── objects
│ │ ├── info
│ │ └── pack
│ │ ├── pack-a6d9863d11286fbea66f5aafa93a9dbafaf3f4fc.idx
│ │ └── pack-a6d9863d11286fbea66f5aafa93a9dbafaf3f4fc.pack
│ ├── packed-refs
│ └── refs
│ ├── heads
│ │ └── main
│ ├── remotes
│ │ └── origin
│ │ └── HEAD
│ └── tags
├── .gitignore
├── LICENSE
├── README.md
├── requirements.txt
└── src
└── app.pyDentro de los hooks se puede definir ejecuciones antes de hacer commit usando pre-commit, que se verá más adelante.
Validación de código
Linters
Los linters son herramientas de programación que realizan la comprobación de cualquier lenguaje de programamción.
Flake8
Herramienta que permite comprobar que el código cumple con el PEP-8 de Python.
Se puede instalar vía pip:
pip install flake8Se ejecuta flake8 al archivo app.py:
python -m flake8 src/app.py
src/app.py:5:1: F401 'scipy.stats' imported but unused
src/app.py:22:6: N802 function name 'OndaCuadrada' should be lowercase
src/app.py:37:20: W291 trailing whitespace
src/app.py:83:50: W291 trailing whitespace
src/app.py:104:43: W291 trailing whitespace
src/app.py:106:5: F821 undefined name 'plot'
src/app.py:107:5: F821 undefined name 'xlim'La salida del comando muestra las líneas de código donde hay error, el tipo de error y una descripción del mismo.
pep8-naming
Adicional a flake se puede usar pep8-naming para cumplir con las normas de definición de nombres de PEP8.
Para instalarlo se usa pip:
pip install pep8-namingSe ejecuta el mismo comando de flake8, el mensaje de error es :
src/app.py:22:6: N802 function name 'OndaCuadrada' should be lowercaseQue ya apareció en la ejecución anterior.
pycodestyle
Es otra herramienta de validación del PEP-8. png
Se instala:
pip install pycodestyleSe ejecuta:
pycodestyle src/app.pyEn este caso como ya se arreglaron los mensajes de error de flake8, no devuelve mensajes.
pylint
Se instala:
pip install pylintSe ejecuta:
pylint src/app.pyFormateador de código
Permite reformatear el código para que cumpla una serie de estándar.
isort
Reordena los imports de la siguiente forma:
- Librería estándar
- Librerías de terceros
- Librerías local
Se instala:
pip install isortSe usa de la siguiente forma:
isort src/app.py
Fixing /home/ernesto/proyectos/signals_fft_streamlit/src/app.pyAnteriormente las importaciones estaban así:
import numpy as np
from scipy import signal as sp
import matplotlib.pylab as plt
import scipy.signal as signal
import scipy.stats as stats
import streamlit as stAhora:
import matplotlib.pylab as plt
import numpy as np
import scipy.signal as signal
import streamlit as st
from scipy import signal as spPara solo chequear el orden de la importación sin realizar el cambio se puede ejecutar:
python -m isort src/app.py --check-onlyPara ver los cambios sin aplicarlos se puede usar:
python -m isort src/app.py --diffBlack
Formateador de código que se usa para reformatear código basado en Black code style que es muy cercano al PEP-8.
Para instalarlo:
pip install blackPara chequear el código sin modificarlo:
python -m black src/app.py --check
would reformat src/app.py
Oh no! 💥 💔 💥
1 file would be reformatted.Para ver las diferencias sin modificarlo (sólo se muestra una parte del código):
--- src/app.py 2020-11-15 01:20:29.525556 +0000
+++ src/app.py 2020-11-15 01:29:54.278851 +0000
@@ -13,11 +13,11 @@
t (list): Lista de tiempo Returns:
list: Lista de valores
"""
- return amplitud*np.piecewise(t, [t < 0.0, t >= 0.0], [0, 1])
+ return amplitud * np.piecewise(t, [t < 0.0, t >= 0.0], [0, 1])
Para realizar las modificaciones:
python -m black src/app.py
reformatted src/app.py
All done! ✨ 🍰 ✨
1 file reformatted.El código cambio a (se muestra una parte del código):
def u(amplitud, t):
"""Función escalón unitario Args:
amplitud (int): Amplitud del escalon
t (list): Lista de tiempo Returns:
list: Lista de valores
"""
return amplitud * np.piecewise(t, [t < 0.0, t >= 0.0], [0, 1])
Otras herramientas parecidas son YAPF y autopep8
Escaneo de vulnerabilidades de seguridad
Hay herramientas que evaluan las vulnerabilidades de seguridad del código y de sus dependencias.
Bandit
Bandit Es una herramienta diseñada para encontrar problemas de seguridad en código Python.
Se instala:
pip install banditSe ejecuta de la siguiente forma:
bandit -r src/app.py
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.8.3
Run started:2020-11-15 01:44:06.048642Test results:
No issues identified.Code scanned:
Total lines of code: 119
Total lines skipped (#nosec): 0Run metrics:
Total issues (by severity):
Undefined: 0.0
Low: 0.0
Medium: 0.0
High: 0.0
Total issues (by confidence):
Undefined: 0.0
Low: 0.0
Medium: 0.0
High: 0.0
Files skipped (0):
Por lo que se pueded ver, no tiene problemas de seguridad el código.
Safety
Safety es una herramienta que verifica problemas de seguridad de las dependencias de la aplicación.
Se instala:
pip install safetySe ejecuta de la siguiente forma:
safety check
+==============================================================================+
| |
| /$$$$$$ /$$ |
| /$$__ $$ | $$ |
| /$$$$$$$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$ /$$ |
| /$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$ |
| | $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$ |
| \____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$ |
| /$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$ |
| |_______/ \_______/|__/ \_______/ \___/ \____ $$ |
| /$$ | $$ |
| | $$$$$$/ |
| by pyup.io \______/ |
| |
+==============================================================================+
| REPORT |
| checked 136 packages, using default DB |
+==============================================================================+
| No known security vulnerabilities found. |
+==============================================================================+Para verificar solamente la lista de paquetes que tiene requirements.txt se ejecuta:
safety check -r requirements.txt
+==============================================================================+
| |
| /$$$$$$ /$$ |
| /$$__ $$ | $$ |
| /$$$$$$$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$ /$$ |
| /$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$ |
| | $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$ |
| \____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$ |
| /$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$ |
| |_______/ \_______/|__/ \_______/ \___/ \____ $$ |
| /$$ | $$ |
| | $$$$$$/ |
| by pyup.io \______/ |
| |
+==============================================================================+
| REPORT |
| checked 4 packages, using default DB |
+==============================================================================+
| No known security vulnerabilities found. |
+==============================================================================+Verificación de tipado estático
Cómo a partir de Python 3.7 , python soporta a manera de documentación la definición de tipos de la salida de una función y sus argumentos, se tiene ahora herramientas que verifica el tipado estático.
Mypy
Mypy es un verificador de tipo estático opcional para Python que tiene como objetivo combinar los beneficios de la escritura dinámica (o “duck”) y la escritura estática.
Se instala:
pip install mypySe usa:
mypy src/app.py
src/app.py:1: error: No library stub file for module 'matplotlib.pylab'
src/app.py:1: note: (Stub files are from https://github.com/python/typeshed)
src/app.py:1: error: No library stub file for module 'matplotlib'
src/app.py:2: error: No library stub file for module 'numpy'
src/app.py:3: error: No library stub file for module 'scipy.signal'
src/app.py:3: error: No library stub file for module 'scipy'
src/app.py:4: error: Cannot find module named 'streamlit'
src/app.py:4: note: See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-importsPara ignorar estos errores se usa # type: ignore, como se muestra a continuación:
import matplotlib.pylab as plt # type: ignore
import numpy as np # type: ignore
import scipy.signal as signal # type: ignore
import streamlit as st # type: ignore
from scipy import signal # type: ignoreSe vuelve a ejecutar y ya no aparecen esos errores iniciales.
Un ejemplo de tipado estático se muestra a continuación:
def u(amplitud: int, t: np.ndarray) -> np.ndarray:
"""Función escalón unitario Args:
amplitud (int): Amplitud del escalon
t (np.ndarray): Lista de tiempo Returns:
np.ndarray: Lista de valores
"""
return amplitud * np.piecewise(t, [t < 0.0, t >= 0.0], [0, 1])def onda_cuadrada(amplitud: int, t: np.ndarray, fs: int = 1) ->np.ndarray:
"""Función de Onda Cuadrada Args:
amplitud (int): Amplitud de la segnal
t (list): Lista de valores de tiempo
fs (int, optional): Frecuencia.. Defaults to 1. Returns:
list: Lista de valores
"""
return ((sp.square(2 * fs * t)) * (amplitud / 2.0)) + (amplitud / 2.0)
Otra forma de ignorar todos los errores es la siguiente:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# mypy: ignore-errorsValidación de la documentación
pydocstyle
pydocstyleEs una herramienta que permite validar el formato de la documentación.
Se instala:
pip install pydocstyleSe utiliza:
pydocstyle src/app.py
src/app.py:1 at module level:
D100: Missing docstring in public module
src/app.py:9 in public function `u`:
D400: First line should end with a period (not 'o')
src/app.py:22 in public function `onda_cuadrada`:
D400: First line should end with a period (not 'a')
src/app.py:36 in public function `segnal_triangular`:
D205: 1 blank line required between summary line and description (found 0)
src/app.py:36 in public function `segnal_triangular`:
D400: First line should end with a period (not 'r')
src/app.py:50 in public function `seno`:
D400: First line should end with a period (not 'o')
src/app.py:64 in public function `coseno`:
D400: First line should end with a period (not 'o')
src/app.py:78 in public function `tiempo`:
D205: 1 blank line required between summary line and description (found 0)
src/app.py:78 in public function `tiempo`:
D400: First line should end with a period (not 'l')
src/app.py:90 in public function `main`:
D103: Missing docstring in public functionComplejidad Ciclomática, número de líneas del código
Existen herramientas para validar la complejidad ciclomática, como lo son:
En este caso se probará con wily
Wily
Wily es una aplicación para trazar la complejidad de código Python y aplicaciones.
Se instala:
pip install wilySe necesita indexar el proyecto ejecutando:
wily build src/
Found 1 revisions from 'git' archiver in '/home/ernesto/proyectos/signals_fft_streamlit'.
Running operators - maintainability,raw,cyclomatic,halstead
Processing |################################| 4/4
Completed building wily history, run `wily report <file>` or `wily index` to see more.Para generar el reporte de app.py se ejecuta:
wily report src/app.py
Using default metrics ['maintainability.mi', 'raw.loc', 'cyclomatic.complexity', 'halstead.h1']
-----------History for ['maintainability.mi', 'raw.loc', 'cyclomatic.complexity', 'halstead.h1']------------
╒════════════╤════════════════╤════════════╤═════════════════════════╤═════════════════╤═════════════════════════╤═══════════════════╕
│ Revision │ Author │ Date │ Maintainability Index │ Lines of Code │ Cyclomatic Complexity │ Unique Operands │
╞════════════╪════════════════╪════════════╪═════════════════════════╪═════════════════╪═════════════════════════╪═══════════════════╡
│ f2679c6 │ Ernesto Crespo │ 2020-11-14 │ 68.1377 (0) │ 153 (0) │ 12 (0) │ 7 (0) │
╘════════════╧════════════════╧════════════╧═════════════════════════╧═════════════════╧═════════════════════════╧═══════════════════╛En el reporte se puede pasar metricas específicas:
wily report src/app.py loc sloc comments --message
-----------History for ('loc', 'sloc', 'comments')------------
╒════════════╤════════════════╤════════════════╤════════════╤═════════════════╤═══════════════════╤═══════════════════════╕
│ Revision │ Message │ Author │ Date │ Lines of Code │ S Lines of Code │ Multi-line comments │
╞════════════╪════════════════╪════════════════╪════════════╪═════════════════╪═══════════════════╪═══════════════════════╡
│ f2679c6 │ Initial commit │ Ernesto Crespo │ 2020-11-14 │ 153 (0) │ 71 (0) │ 8 (0) │
╘════════════╧════════════════╧════════════════╧════════════╧═════════════════╧═══════════════════╧═══════════════════════╛Complejidad Ciclomática
Según publicación de The Software Engineering Institute at Carnegie Mellon defines donde define los rangos de complejidad: * 1–10: Bajo riesgo, programa simple. * 11–20: Riesgo moderado, programa más complejo. * 21–50: Riesgo alto, programa muy dificil. * >50: Riesgo muy alto, programa inestable.
Indice de mantenibilidad
Para este caso: * <65: Fuertemente mantenible. * 65–85: Moderadamente mantenible. * >85: Fácil de mantener.
En el caso de app.py tiene un riesgo moderado y es moderadamente mantenible.
Para más información sobre la complejidad ciclomática pueden revisar Simplify your Python Code: Automating Code Complexity Analysis with Wily o el pdf de The Software Engineering Institute at Carnegie Mellon defines.
Juntando todo con pre-commit
pre-commit
Pre-commit es un framework para manejar git hooks.
Para instalarlo:
pip install pre-commitPara inicializar pre-commit se ejecuta:
$pre-commit install
pre-commit installed at .git/hooks/pre-commitVerificación de Yaml
Para formatear yaml se puede usar la siguiente configuración:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-added-large-files
- id: check-yaml
exclude: (template|ci).*\.(json|yml|yaml)$isort
Para validar con isort se tiene lo siguiente:
- repo: https://github.com/asottile/seed-isort-config
rev: v1.9.3
hooks:
- id: seed-isort-config
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21
hooks:
- id: isortPara Wily
- repo: local
hooks:
- id: wily
name: wily
entry: wily diff
verbose: true
language: python
additional_dependencies: [wily]Safety
- repo: git://github.com/Lucas-C/pre-commit-hooks-safety
rev: v1.1.3
hooks:
- id: python-safety-dependencies-checkBlack
- repo: https://github.com/ambv/black
rev: stable
hooks:
- id: black
language_version: python3.8Pylint
- repo: https://github.com/PyCQA/pylint
rev: "pylint-2.6.0"
hooks:
- id: pylint
args: ["--disable=similarities"]Mypy
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.790
hooks:
- id: mypy
exclude: ^testing/resources/Pyupgrade
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.3
hooks:
- id: pyupgrade
args: [--py36-plus]Flake8
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.4
hooks:
- id: flake8
additional_dependencies: [flake8-typing-imports==1.10.0]Autopep8
- repo: https://github.com/pre-commit/mirrors-autopep8
rev: v1.5.4
hooks:
- id: autopep8Blacken
- repo: https://github.com/asottile/blacken-docs
rev: v1.8.0
hooks:
- id: blacken-docs
additional_dependencies: [black==20.8b1]Bandit
- repo: git://github.com/Lucas-C/pre-commit-hooks-bandit
rev: v1.0.4
hooks:
- id: python-bandit-vulnerability-check
exclude: /home/ernesto/proyectos/magnetic_fields/.virtualenvs/*pydoctstyle
- repo: local
hooks:
- id: pydocstyle
name: pydocstyle
entry: pydocstyle
files: src/app.py
verbose: true
language: python
additional_dependencies: [pydocstyle]Archivo completo
El archivo completo de .pre-commit-config.yaml contiene los siguiente:
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-added-large-files
- id: check-yaml
exclude: (template|ci).*\.(json|yml|yaml)$
- repo: https://github.com/asottile/seed-isort-config
rev: v1.9.3
hooks:
- id: seed-isort-config
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.21
hooks:
- id: isort
- repo: local
hooks:
- id: wily
name: wily
entry: wily diff
verbose: true
language: python
additional_dependencies: [wily]
- repo: local
hooks:
- id: pydocstyle
name: pydocstyle
entry: pydocstyle
files: src/app.py
verbose: true
language: python
additional_dependencies: [pydocstyle]
- repo: git://github.com/Lucas-C/pre-commit-hooks-safety
rev: v1.1.3
hooks:
- id: python-safety-dependencies-check
- repo: https://github.com/ambv/black
rev: stable
hooks:
- id: black
language_version: python3.8
- repo: https://github.com/PyCQA/pylint
rev: "pylint-2.6.0"
hooks:
- id: pylint
args: ["--disable=similarities"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.790
hooks:
- id: mypy
exclude: ^testing/resources/
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.3
hooks:
- id: pyupgrade
args: [--py36-plus]
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.4
hooks:
- id: flake8
additional_dependencies: [flake8-typing-imports==1.10.0]
- repo: https://github.com/pre-commit/mirrors-autopep8
rev: v1.5.4
hooks:
- id: autopep8
- repo: https://github.com/asottile/blacken-docs
rev: v1.8.0
hooks:
- id: blacken-docs
additional_dependencies: [black==20.8b1]
- repo: git://github.com/Lucas-C/pre-commit-hooks-bandit
rev: v1.0.4
hooks:
- id: python-bandit-vulnerability-check
exclude: /home/ernesto/proyectos/magnetic_fields/.virtualenvs/*Se puede ejecutar el siguiente comando para la verificación:
pre-commit run --all-filesEste devuelve los mensajes como se muestra en la siguiente imagen:
Desde ahora que se quiere hacer un commit pre-commit evaluará los hooks y se tiene que corregir los errores hasta no tener ninguno, y es allí que se realizará el commit.
Estructura de archivos final.
ignals_fft_streamlit
├── .bandit
├── .git
│ ├── branches
│ ├── COMMIT_EDITMSG
│ ├── config
│ ├── description
│ ├── HEAD
│ ├── hooks
│ │ ├── applypatch-msg.sample
│ │ ├── commit-msg.sample
│ │ ├── fsmonitor-watchman.sample
│ │ ├── post-update.sample
│ │ ├── pre-applypatch.sample
│ │ ├── pre-commit
│ │ ├── pre-commit.sample
│ │ ├── pre-merge-commit.sample
│ │ ├── prepare-commit-msg.sample
│ │ ├── pre-push.sample
│ │ ├── pre-rebase.sample
│ │ ├── pre-receive.sample
│ │ └── update.sample
│ ├── index
│ ├── info
│ │ └── exclude
│ ├── logs
│ │ ├── HEAD
│ │ └── refs
│ │ ├── heads
│ │ │ ├── feat
│ │ │ │ └── validado
│ │ │ └── main
│ │ └── remotes
│ │ └── origin
│ │ └── HEAD
│ ├── ORIG_HEAD
│ ├── packed-refs
│ └── refs
│ ├── heads
│ │ ├── feat
│ │ │ └── validado
│ │ └── main
│ ├── remotes
│ │ └── origin
│ │ └── HEAD
│ └── tags
├── .gitignore
├── .isort.cfg
├── LICENSE
├── .pre-commit-config.yaml
├── .pylintrc
├── README.md
├── requirements.txt
└── src
└── app.pyEl archivo final de app.py quedó de la siguiente manera:
"""WebApp with streamlit graph signals."""
import matplotlib.pylab as plt # type: ignore
import numpy as np # type: ignore
import streamlit as st # type: ignore
from scipy import signal # type: ignore # pylint: disable=import-error
def u(amplitud: int, t: np.ndarray) -> np.ndarray:
"""Función escalón unitario. Args:
amplitud(int): Amplitud del escalon
t(np.ndarray): Lista de tiempo Returns:
np.ndarray: Lista de valores
"""
return amplitud * np.piecewise(t, [t < 0.0, t >= 0.0], [0, 1])
def onda_cuadrada(amplitud: int, t: np.ndarray, fs: int = 1) -> np.ndarray:
"""Función de Onda Cuadrada. Args:
amplitud(int): Amplitud de la segnal
t(list): Lista de valores de tiempo
fs(int, optional): Frecuencia.. Defaults to 1. Returns:
list: Lista de valores
"""
return ((signal.square(2 * fs * t)) * (amplitud / 2.0)) + (amplitud / 2.0)
def segnal_triangular(
amplitud: int, simetria: float, t: np.ndarray, fs: int = 1
) -> np.ndarray:
"""Señal triangular. Args:
amplitud(int): Amplitud de la señal
simetria(float): simetria de la señal
t(np.ndarray): Lista de valores que definen el tiempo.
fs(int, optional): Frecuencia de la señal. Defaults to 1. Returns:
np.ndarray: Lista de valores de la señal
"""
return amplitud * (signal.sawtooth(2 * np.pi * fs * t, simetria))
def seno(amplitud: int, t: np.ndarray, fs: int = 1) -> np.ndarray:
"""Onda Seno. Args:
amplitud(int): Amplitud de la señal
t (np.ndarray): Lista de valores de tiempo
fs (int, optional): Frecuencia de la señal. Defaults to 1. Returns:
np.ndarray: Lista de valores de la señal de seno
"""
return amplitud * np.sin(fs * t)
def coseno(amplitud: int, t: np.ndarray, fs: int = 1) -> np.ndarray:
"""Señal de coseno. Args:
amplitud (int): Amplitud de la señal
t (np.ndarray): lista de valores para generar la señal
fs (int, optional): Frecuencia de la señal. Defaults to 1. Returns:
np.ndarray: Lista de valores de la señal
"""
return amplitud * np.cos(fs * t)
def tiempo(lim_inf: int, lim_sup: int, n: int) -> np.ndarray:
"""Lista de valores que definen el tiempo de la señal. Args:
lim_inf (int): Límite inferior del tiempo
lim_sup (int): Límite superior del tiempo
n (int): Cantidad de valores a generar del tiempo Returns:
np.ndarray: Lista de valores del tiemmpo
"""
return np.linspace(lim_inf, lim_sup, n)
def main():
"""Ejecución Streamlit webApp."""
st.title( # pylint: disable=no-value-for-parameter
"Generación de gráficas de señales"
) # pylint: disable=no-value-for-parameter
st.sidebar.header("Entradas:") # pylint: disable=no-value-for-parameter
segnales = [
"Escalon Unitario",
"Onda Cuadrada",
"Onda triangular",
"Seno",
"Coseno",
] resp = st.sidebar.selectbox("Tipo de señal", segnales) st.sidebar.header("Definición del tiempo:")
st.sidebar.subheader("Rango")
# SelectBox
t0 = int(st.sidebar.selectbox("", range(0, 10)))
ti = 0
tf = t0
n = 10000
t = tiempo(ti, tf, n)
st.sidebar.subheader("Amplitud de la señal")
amplitud = int(st.sidebar.selectbox("", range(1, 10))) # numpy.ndarray
if resp == "Escalon Unitario":
ti = -tf
resultado = u(amplitud, t)
elif resp == "Onda Cuadrada":
st.sidebar.subheader("Frecuencia de la señal")
fs = int(st.sidebar.selectbox("", range(1, 11)))
resultado = onda_cuadrada(amplitud, t, fs)
elif resp == "Onda triangular":
simetria = 0.5
st.sidebar.subheader("Frecuencia de la señal")
fs = int(st.sidebar.selectbox("", range(1, 11)))
resultado = segnal_triangular(amplitud, simetria, t, fs)
elif resp == "Seno":
st.sidebar.subheader("Frecuencia de la señal")
fs = int(st.sidebar.selectbox("", range(1, 11)))
resultado = seno(amplitud, t, fs)
elif resp == "Coseno":
st.sidebar.subheader("Frecuencia de la señal")
fs = int(st.sidebar.selectbox("", range(1, 11)))
resultado = coseno(amplitud, t, fs)
else:
resultado = 0
st.error("Error") fig = plt.figure()
ax = fig.add_subplot(111)
st.header(f"Gráfica de {resp}")
ax.plot(t, resultado)
ax.set_xlim(ti, tf)
ax.set_xlabel("Tiempo")
ax.set_ylabel("f(t)")
ax.set_ylim(2 * amplitud * -1, 2 * amplitud)
st.pyplot(fig)
if __name__ == "__main__":
main()
El respositorio donde pueden ver los archivos se encuentra en github
¡Haz tu donativo! Si te gustó el artículo puedes realizar un donativo con Bitcoin (BTC) usando la billetera digital de tu preferencia a la siguiente dirección: 17MtNybhdkA9GV3UNS6BTwPcuhjXoPrSzV
O Escaneando el código QR desde la billetera:
